UI: Add full modern loading system with 3D asset preview and zone prefetching
This commit is contained in:
parent
c2b808182d
commit
b41c22cb68
|
|
@ -1,6 +1,3 @@
|
|||
"""
|
||||
AssetCache - Preloads and caches common assets for instant zone loading
|
||||
"""
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
from panda3d.core import ModelPool, TexturePool, NodePath
|
||||
|
||||
|
|
@ -23,42 +20,40 @@ class AssetCache:
|
|||
return cls._instance
|
||||
|
||||
def initialize(self, loader):
|
||||
"""Preload common assets used across all zones"""
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
self.notify.info('Preloading common assets for faster zone loading...')
|
||||
|
||||
# Common models that are used everywhere
|
||||
commonModels = [
|
||||
'phase_3/models/gui/toontown-logo',
|
||||
'phase_3/models/gui/tt_m_gui_ups_logo_noText',
|
||||
'phase_3/models/gui/progress-background',
|
||||
'phase_3/models/gui/dialog_box_gui',
|
||||
'phase_3/models/gui/quit_button',
|
||||
'phase_3/models/props/arrow',
|
||||
'phase_3/models/props/panel',
|
||||
'phase_3/models/props/chatbox',
|
||||
'phase_3/models/props/chatbox_noarrow',
|
||||
'phase_3/models/gui/chat_button_gui',
|
||||
'phase_3/models/misc/sphere',
|
||||
'phase_3.5/models/props/drop_shadow',
|
||||
'phase_3.5/models/gui/inventory_icons',
|
||||
]
|
||||
|
||||
# Preload models asynchronously
|
||||
for modelPath in commonModels:
|
||||
try:
|
||||
model = loader.loadModel(modelPath)
|
||||
model = loader.loadModel(modelPath, okMissing=True)
|
||||
if model:
|
||||
self.preloadedModels[modelPath] = model
|
||||
# Keep in model pool
|
||||
ModelPool.addModel(modelPath, model.node())
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Texture and Model pools manage their own sizes automatically
|
||||
# Just ensure they're enabled for caching
|
||||
|
||||
self._initialized = True
|
||||
self.notify.info('Asset preloading complete')
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up cached assets"""
|
||||
self.preloadedModels.clear()
|
||||
self.preloadedTextures.clear()
|
||||
self.preloadedSounds.clear()
|
||||
|
|
@ -66,10 +61,7 @@ class AssetCache:
|
|||
self._initialized = False
|
||||
|
||||
def getModel(self, modelPath):
|
||||
"""Get a preloaded model if available"""
|
||||
return self.preloadedModels.get(modelPath)
|
||||
|
||||
|
||||
# Global instance
|
||||
assetCache = AssetCache.getInstance()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ from panda3d.toontown import *
|
|||
from direct.directnotify.DirectNotifyGlobal import *
|
||||
from direct.showbase import Loader
|
||||
from toontown.toontowngui import ToontownLoadingScreen
|
||||
from toontown.toontowngui.ZonePrefetchCatalog import zonePrefetchManager
|
||||
|
||||
class ToontownLoader(Loader.Loader):
|
||||
TickPeriod = 0.01
|
||||
TickPeriod = 0.005
|
||||
|
||||
def __init__(self, base):
|
||||
Loader.Loader.__init__(self, base)
|
||||
|
|
@ -13,12 +14,15 @@ class ToontownLoader(Loader.Loader):
|
|||
self.blockName = None
|
||||
self.loadingScreen = ToontownLoadingScreen.ToontownLoadingScreen()
|
||||
self._tickCounter = 0
|
||||
self._tickSkip = 10
|
||||
self._tickSkip = 1
|
||||
self._lastTickT = 0.0
|
||||
self._loadStartT = 0.0
|
||||
return
|
||||
|
||||
def destroy(self):
|
||||
self.loadingScreen.destroy()
|
||||
del self.loadingScreen
|
||||
if hasattr(self, 'loadingScreen') and self.loadingScreen:
|
||||
self.loadingScreen.destroy()
|
||||
del self.loadingScreen
|
||||
Loader.Loader.destroy(self)
|
||||
|
||||
def beginBulkLoad(self, name, label, range, gui, tipCategory):
|
||||
|
|
@ -31,6 +35,11 @@ class ToontownLoader(Loader.Loader):
|
|||
self._lastTickT = globalClock.getRealTime()
|
||||
self.blockName = name
|
||||
self.loadingScreen.begin(range, label, gui, tipCategory)
|
||||
try:
|
||||
if isinstance(name, int) or (isinstance(name, str) and name.isdigit()):
|
||||
zonePrefetchManager.prefetch_zone(int(name), loader=self)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def endBulkLoad(self, name):
|
||||
|
|
@ -63,21 +72,27 @@ class ToontownLoader(Loader.Loader):
|
|||
self._tickCounter = 0
|
||||
now = globalClock.getRealTime()
|
||||
if now - self._lastTickT > self.TickPeriod:
|
||||
self._lastTickT += self.TickPeriod
|
||||
self._lastTickT = now
|
||||
self.loadingScreen.tick()
|
||||
try:
|
||||
base.cr.considerHeartbeat()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def loadModel(self, *args, **kw):
|
||||
ret = Loader.Loader.loadModel(self, *args, **kw)
|
||||
self.tick()
|
||||
if ret and hasattr(self.loadingScreen, 'on_asset_loaded'):
|
||||
path = args[0] if args else None
|
||||
self.loadingScreen.on_asset_loaded(path=path, model=ret, kind='model')
|
||||
return ret
|
||||
|
||||
def loadFont(self, *args, **kw):
|
||||
ret = Loader.Loader.loadFont(self, *args, **kw)
|
||||
self.tick()
|
||||
if ret and hasattr(self.loadingScreen, 'on_asset_loaded'):
|
||||
path = args[0] if args else None
|
||||
self.loadingScreen.on_asset_loaded(path=path, kind='font')
|
||||
return ret
|
||||
|
||||
def loadTexture(self, texturePath, alphaPath = None, okMissing = False):
|
||||
|
|
@ -85,16 +100,22 @@ class ToontownLoader(Loader.Loader):
|
|||
self.tick()
|
||||
if alphaPath:
|
||||
self.tick()
|
||||
if ret and hasattr(self.loadingScreen, 'on_asset_loaded'):
|
||||
self.loadingScreen.on_asset_loaded(path=texturePath, texture=ret, kind='texture')
|
||||
return ret
|
||||
|
||||
def loadSfx(self, soundPath):
|
||||
ret = Loader.Loader.loadSfx(self, soundPath)
|
||||
self.tick()
|
||||
if ret and hasattr(self.loadingScreen, 'on_asset_loaded'):
|
||||
self.loadingScreen.on_asset_loaded(path=soundPath, kind='audio')
|
||||
return ret
|
||||
|
||||
def loadMusic(self, soundPath):
|
||||
ret = Loader.Loader.loadMusic(self, soundPath)
|
||||
self.tick()
|
||||
if ret and hasattr(self.loadingScreen, 'on_asset_loaded'):
|
||||
self.loadingScreen.on_asset_loaded(path=soundPath, kind='audio')
|
||||
return ret
|
||||
|
||||
def loadDNAFileAI(self, dnaStore, dnaFile):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
"""
|
||||
Live 3D / texture preview for the unified loading UI.
|
||||
|
||||
Uses an offscreen buffer with Camera.setScene() so preview geometry never mixes
|
||||
with the main world render.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.showbase.ShowBaseGlobal import base
|
||||
from direct.task import Task
|
||||
from panda3d.core import (
|
||||
AmbientLight,
|
||||
CardMaker,
|
||||
DirectionalLight,
|
||||
NodePath,
|
||||
PerspectiveLens,
|
||||
Texture,
|
||||
TransparencyAttrib,
|
||||
VBase4,
|
||||
)
|
||||
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('LoadingAssetPreview')
|
||||
|
||||
_PREVIEW_CLEAR = VBase4(0.08, 0.12, 0.19, 1.0)
|
||||
|
||||
|
||||
class LoadingAssetPreview:
|
||||
def __init__(self):
|
||||
self._buffer = None
|
||||
self._cam_np = None
|
||||
self._world = NodePath('loadingAssetPreviewWorld')
|
||||
self._holder = self._world.attachNewNode('assetHolder')
|
||||
self._card = None
|
||||
self._border = None
|
||||
self._spin_task = None
|
||||
self._last_note_t = 0.0
|
||||
self._min_interval = 0.06
|
||||
|
||||
def attach_card(self, parent: NodePath, pos, frame, sort: int = 100002):
|
||||
"""
|
||||
parent: aspect2d parent
|
||||
pos: (x, y, z) in aspect2d space
|
||||
frame: (left, right, bottom, top) CardMaker frame (relative scale)
|
||||
"""
|
||||
self.detach_card()
|
||||
if not base.win:
|
||||
return
|
||||
try:
|
||||
w, h = 512, 288
|
||||
self._buffer = base.win.makeTextureBuffer('loadingAssetPreview', w, h, to_ram=False, fbp=False)
|
||||
self._buffer.setClearColor(True)
|
||||
self._buffer.setClearColor(_PREVIEW_CLEAR)
|
||||
|
||||
amb = AmbientLight('lap_amb')
|
||||
amb.setColor(VBase4(0.5, 0.5, 0.58, 1))
|
||||
anp = self._world.attachNewNode(amb)
|
||||
self._world.setLight(anp)
|
||||
sun = DirectionalLight('lap_sun')
|
||||
sun.setColor(VBase4(0.92, 0.9, 0.82, 1))
|
||||
snp = self._world.attachNewNode(sun)
|
||||
snp.setHpr(-68, -32, 0)
|
||||
self._world.setLight(snp)
|
||||
|
||||
lens = PerspectiveLens()
|
||||
lens.setFov(40)
|
||||
lens.setNearFar(0.08, 500.0)
|
||||
self._cam_np = base.makeCamera(
|
||||
self._buffer,
|
||||
scene=self._world,
|
||||
lens=lens,
|
||||
aspectRatio=float(w) / float(h),
|
||||
clearColor=_PREVIEW_CLEAR,
|
||||
sort=-60,
|
||||
)
|
||||
self._cam_np.setPos(0, -16, 6.5)
|
||||
self._cam_np.lookAt(0, 0, 3.0)
|
||||
|
||||
tex = self._buffer.getTexture()
|
||||
bl, br, bb, bt = frame
|
||||
bwide = 0.02
|
||||
bcm = CardMaker('lapBorder')
|
||||
bcm.setFrame(bl - bwide, br + bwide, bb - bwide, bt + bwide)
|
||||
self._border = parent.attachNewNode(bcm.generate())
|
||||
self._border.setColor(0.12, 0.65, 0.95, 0.55)
|
||||
self._border.setTransparency(TransparencyAttrib.M_alpha)
|
||||
self._border.setBin('fixed', sort - 1)
|
||||
self._border.setPos(*pos)
|
||||
|
||||
cm = CardMaker('lapCard')
|
||||
cm.setFrame(bl, br, bb, bt)
|
||||
self._card = parent.attachNewNode(cm.generate())
|
||||
self._card.setTexture(tex)
|
||||
self._card.setTransparency(TransparencyAttrib.M_alpha)
|
||||
self._card.setBin('fixed', sort)
|
||||
self._card.setPos(*pos)
|
||||
except Exception as e:
|
||||
notify.warning('loading asset preview unavailable: %s' % e)
|
||||
self.detach_card()
|
||||
|
||||
def detach_card(self):
|
||||
self._stop_spin()
|
||||
if self._card:
|
||||
try:
|
||||
self._card.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
self._card = None
|
||||
if self._border:
|
||||
try:
|
||||
self._border.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
self._border = None
|
||||
self._teardown_buffer()
|
||||
self._clear_holder()
|
||||
|
||||
def _teardown_buffer(self):
|
||||
if self._buffer:
|
||||
try:
|
||||
base.graphicsEngine.removeWindow(self._buffer)
|
||||
except Exception:
|
||||
pass
|
||||
self._buffer = None
|
||||
self._cam_np = None
|
||||
|
||||
def _clear_holder(self):
|
||||
self._stop_spin()
|
||||
try:
|
||||
for c in self._holder.getChildren():
|
||||
c.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _stop_spin(self):
|
||||
if self._spin_task:
|
||||
try:
|
||||
base.taskMgr.remove(self._spin_task)
|
||||
except Exception:
|
||||
pass
|
||||
self._spin_task = None
|
||||
|
||||
def _spin(self, task):
|
||||
try:
|
||||
self._holder.setH(self._holder.getH() + 0.9)
|
||||
except Exception:
|
||||
pass
|
||||
return Task.cont
|
||||
|
||||
def note_asset(
|
||||
self,
|
||||
path: Optional[str],
|
||||
model: Optional[NodePath] = None,
|
||||
texture: Optional[Texture] = None,
|
||||
force: bool = False,
|
||||
):
|
||||
if not self._buffer or not self._card:
|
||||
return
|
||||
now = base.globalClock.getRealTime()
|
||||
if not force and (now - self._last_note_t) < self._min_interval:
|
||||
return
|
||||
self._last_note_t = now
|
||||
|
||||
self._clear_holder()
|
||||
self._stop_spin()
|
||||
|
||||
if model is not None and not model.isEmpty():
|
||||
try:
|
||||
inst = model.copyTo(self._holder)
|
||||
self._frame_node(inst)
|
||||
self._holder.setH(15)
|
||||
self._spin_task = base.taskMgr.add(self._spin, 'loadingAssetSpin', priority=50)
|
||||
except Exception as e:
|
||||
notify.debug('model preview failed: %s' % e)
|
||||
return
|
||||
|
||||
if texture is not None:
|
||||
try:
|
||||
cm = CardMaker('texFlat')
|
||||
cm.setFrame(-6, 6, -6, 6)
|
||||
flat = self._holder.attachNewNode(cm.generate())
|
||||
flat.setTexture(texture)
|
||||
flat.setTransparency(TransparencyAttrib.M_alpha)
|
||||
flat.lookAt(0, -1, 0)
|
||||
except Exception as e:
|
||||
notify.debug('texture preview failed: %s' % e)
|
||||
|
||||
def _frame_node(self, np: NodePath):
|
||||
try:
|
||||
p = NodePath('pivot')
|
||||
p.reparentTo(self._holder)
|
||||
np.wrtReparentTo(p)
|
||||
bounds = np.getBounds()
|
||||
if bounds.isEmpty():
|
||||
return
|
||||
center = bounds.getApproxCenter()
|
||||
radius = max(bounds.getRadius(), 0.01)
|
||||
np.setPos(-center)
|
||||
scale = 7.5 / max(radius, 1.0)
|
||||
s = min(max(scale, 0.35), 14.0)
|
||||
p.setScale(s)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def format_caption(self, path: Optional[str]) -> str:
|
||||
if not path:
|
||||
return ''
|
||||
try:
|
||||
base_name = os.path.basename(str(path).replace('\\', '/'))
|
||||
if len(base_name) > 42:
|
||||
return base_name[:39] + '…'
|
||||
return base_name
|
||||
except Exception:
|
||||
return str(path)[:42]
|
||||
|
||||
def destroy(self):
|
||||
self.detach_card()
|
||||
try:
|
||||
self._world.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -0,0 +1,414 @@
|
|||
import math
|
||||
import time
|
||||
|
||||
from panda3d.core import CardMaker, NodePath, TextNode, Vec4, ConfigVariableBool
|
||||
from direct.gui.DirectGui import DirectFrame, DirectLabel, DirectWaitBar, DGG
|
||||
from direct.interval.IntervalGlobal import (
|
||||
Sequence,
|
||||
Parallel,
|
||||
LerpColorScaleInterval,
|
||||
LerpScaleInterval,
|
||||
LerpFunc,
|
||||
Wait,
|
||||
Func,
|
||||
)
|
||||
|
||||
from toontown.toontowngui.LoadingAssetPreview import LoadingAssetPreview
|
||||
|
||||
|
||||
class ModernLoadingScreen:
|
||||
"""
|
||||
Unified launcher / zone-load overlay:
|
||||
- Full-screen polished startup view (logo, gradient, progress, live asset preview)
|
||||
- Compact bottom bar after login (game stays visible; preview + status during loads)
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self._enabled = ConfigVariableBool('want-modern-launcher-ui', True).value
|
||||
self._want_preview = ConfigVariableBool('want-loading-asset-preview', True).value
|
||||
self._start_t = time.time()
|
||||
self._pulse_ival = None
|
||||
self._outro_ival = None
|
||||
self._launcher_done = False
|
||||
self._bulk_active = False
|
||||
self._preview = LoadingAssetPreview() if self._enabled else None
|
||||
|
||||
if not self._enabled:
|
||||
self.root = None
|
||||
self.compact_root = None
|
||||
return
|
||||
|
||||
if parent is None:
|
||||
parent = aspect2d
|
||||
|
||||
# Full-screen launcher panel
|
||||
self.root = DirectFrame(
|
||||
parent=parent,
|
||||
relief=None,
|
||||
frameColor=(0, 0, 0, 0),
|
||||
sortOrder=100000,
|
||||
)
|
||||
|
||||
self._bg = self._make_gradient_bg(self.root)
|
||||
|
||||
self.logoModel = None
|
||||
self.logo = None
|
||||
try:
|
||||
self.logoModel = loader.loadModel('phase_3/models/gui/tt_m_gui_ups_logo_noText')
|
||||
if not self.logoModel or self.logoModel.isEmpty():
|
||||
raise Exception('logo model empty')
|
||||
self.logo = DirectFrame(
|
||||
parent=self.root,
|
||||
relief=None,
|
||||
image=self.logoModel,
|
||||
image_scale=0.45,
|
||||
pos=(0, 0, 0.58),
|
||||
)
|
||||
except Exception:
|
||||
self.logoModel = None
|
||||
self.logo = DirectLabel(
|
||||
parent=self.root,
|
||||
relief=None,
|
||||
text='Toontown',
|
||||
text_align=TextNode.ACenter,
|
||||
text_scale=0.12,
|
||||
text_fg=(1, 1, 1, 1),
|
||||
pos=(0, 0, 0.62),
|
||||
)
|
||||
|
||||
self.subtitle = DirectLabel(
|
||||
parent=self.root,
|
||||
relief=None,
|
||||
text='Launching…',
|
||||
text_align=TextNode.ACenter,
|
||||
text_scale=0.06,
|
||||
text_fg=(1, 1, 1, 0.85),
|
||||
pos=(0, 0, 0.50),
|
||||
)
|
||||
|
||||
self.status = DirectLabel(
|
||||
parent=self.root,
|
||||
relief=None,
|
||||
text='Initializing…',
|
||||
text_align=TextNode.ACenter,
|
||||
text_scale=0.052,
|
||||
text_fg=(1, 1, 1, 0.9),
|
||||
pos=(0, 0, -0.05),
|
||||
)
|
||||
|
||||
self.asset_caption = DirectLabel(
|
||||
parent=self.root,
|
||||
relief=None,
|
||||
text='',
|
||||
text_align=TextNode.ACenter,
|
||||
text_scale=0.034,
|
||||
text_fg=(0.75, 0.9, 1.0, 0.85),
|
||||
pos=(0, 0, -0.2),
|
||||
)
|
||||
|
||||
self.progress = DirectWaitBar(
|
||||
parent=self.root,
|
||||
relief=None,
|
||||
range=100,
|
||||
value=0,
|
||||
pos=(0, 0, -0.16),
|
||||
frameSize=(-0.75, 0.75, -0.055, 0.055),
|
||||
frameColor=(1, 1, 1, 0.12),
|
||||
barColor=(0.2, 0.8, 1.0, 0.85),
|
||||
borderWidth=(0.02, 0.02),
|
||||
)
|
||||
|
||||
self._detail = DirectLabel(
|
||||
parent=self.root,
|
||||
relief=None,
|
||||
text='',
|
||||
text_align=TextNode.ACenter,
|
||||
text_scale=0.028,
|
||||
text_fg=(0.55, 0.7, 0.85, 0.75),
|
||||
pos=(0, 0, -0.28),
|
||||
)
|
||||
|
||||
if self._want_preview and self._preview:
|
||||
try:
|
||||
self._preview.attach_card(self.root, (0.68, 0, 0.02), (-0.38, 0.38, -0.22, 0.22))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._start_pulse()
|
||||
|
||||
# Compact in-world load bar (bottom)
|
||||
self.compact_root = DirectFrame(
|
||||
parent=hidden,
|
||||
relief=DGG.FLAT,
|
||||
frameColor=(0.03, 0.07, 0.12, 0.78),
|
||||
frameSize=(-1.4, 1.4, -0.12, 0.1),
|
||||
pos=(0, 0, -0.88),
|
||||
sortOrder=100015,
|
||||
borderWidth=(0.008, 0.008),
|
||||
)
|
||||
self.compact_status = DirectLabel(
|
||||
parent=self.compact_root,
|
||||
relief=None,
|
||||
text='',
|
||||
text_align=TextNode.ALeft,
|
||||
text_scale=0.04,
|
||||
text_fg=(1, 1, 1, 0.95),
|
||||
pos=(-1.32, 0, 0.035),
|
||||
)
|
||||
self.compact_asset = DirectLabel(
|
||||
parent=self.compact_root,
|
||||
relief=None,
|
||||
text='',
|
||||
text_align=TextNode.ALeft,
|
||||
text_scale=0.032,
|
||||
text_fg=(0.65, 0.85, 1.0, 0.9),
|
||||
pos=(-1.32, 0, -0.025),
|
||||
)
|
||||
self.compact_progress = DirectWaitBar(
|
||||
parent=self.compact_root,
|
||||
relief=None,
|
||||
range=100,
|
||||
value=0,
|
||||
pos=(0.15, 0, -0.055),
|
||||
frameSize=(-0.55, 0.55, -0.028, 0.028),
|
||||
frameColor=(1, 1, 1, 0.14),
|
||||
barColor=(0.25, 0.85, 1.0, 0.9),
|
||||
borderWidth=(0.015, 0.015),
|
||||
)
|
||||
|
||||
self._layout_compact_bar()
|
||||
|
||||
def _layout_compact_bar(self):
|
||||
try:
|
||||
left = float(base.a2dLeft) + 0.04
|
||||
right = float(base.a2dRight) - 0.04
|
||||
self.compact_root['frameSize'] = (left, right, -0.13, 0.1)
|
||||
self.compact_status.setPos(left + 0.02, 0, 0.04)
|
||||
self.compact_asset.setPos(left + 0.02, 0, -0.03)
|
||||
self.compact_progress.setPos((left + right) * 0.5 + 0.1, 0, -0.055)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def enabled(self):
|
||||
return bool(self._enabled and self.root)
|
||||
|
||||
def _make_gradient_bg(self, parent) -> NodePath:
|
||||
cm = CardMaker('modernLoadingBG')
|
||||
cm.setFrameFullscreenQuad()
|
||||
card = NodePath(cm.generate())
|
||||
card.reparentTo(parent)
|
||||
card.setBin('fixed', -100)
|
||||
card.setDepthTest(False)
|
||||
card.setDepthWrite(False)
|
||||
card.setColorScale(Vec4(0.06, 0.10, 0.16, 1.0))
|
||||
return card
|
||||
|
||||
def _start_pulse(self):
|
||||
if not self.root:
|
||||
return
|
||||
|
||||
def pulse(t):
|
||||
s = 0.5 + 0.5 * math.sin(t * 2 * math.pi)
|
||||
r = 0.06 + s * 0.02
|
||||
g = 0.10 + s * 0.03
|
||||
b = 0.16 + s * 0.05
|
||||
try:
|
||||
self._bg.setColorScale(Vec4(r, g, b, 1.0))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._pulse_ival = Sequence(
|
||||
LerpFunc(pulse, fromData=0.0, toData=1.0, duration=1.6, blendType='easeInOut'),
|
||||
LerpFunc(pulse, fromData=1.0, toData=2.0, duration=1.6, blendType='easeInOut'),
|
||||
)
|
||||
self._pulse_ival.loop()
|
||||
|
||||
def set_title(self, title: str, subtitle=None):
|
||||
if not self.root:
|
||||
return
|
||||
if subtitle is not None:
|
||||
self.subtitle['text'] = subtitle
|
||||
|
||||
def set_status(self, text: str):
|
||||
if self.root:
|
||||
self.status['text'] = text
|
||||
if self._launcher_done and self.compact_root:
|
||||
self.compact_status['text'] = text
|
||||
|
||||
def set_detail(self, text: str):
|
||||
if not self.root:
|
||||
return
|
||||
self._detail['text'] = text or ''
|
||||
|
||||
def set_progress(self, pct_0_to_100: float):
|
||||
try:
|
||||
v = max(0.0, min(100.0, float(pct_0_to_100)))
|
||||
except Exception:
|
||||
v = 0.0
|
||||
if self.root:
|
||||
self.progress['value'] = v
|
||||
if self.compact_root:
|
||||
self.compact_progress['value'] = v
|
||||
|
||||
def on_asset_loaded(self, path=None, model=None, texture=None, kind='model'):
|
||||
if ConfigVariableBool('want-zero-load-ui', False).value and self._launcher_done:
|
||||
return
|
||||
preview = self._preview if self._want_preview else None
|
||||
if kind == 'audio':
|
||||
if preview and path:
|
||||
cap = 'SFX: ' + preview.format_caption(path)
|
||||
else:
|
||||
cap = path or ''
|
||||
if self.root and not self._launcher_done:
|
||||
self.asset_caption['text'] = cap
|
||||
if self._launcher_done and self.compact_root:
|
||||
self.compact_asset['text'] = cap
|
||||
return
|
||||
if kind == 'font':
|
||||
if preview and path:
|
||||
cap = 'Font: ' + preview.format_caption(path)
|
||||
else:
|
||||
cap = path or ''
|
||||
if self.root and not self._launcher_done:
|
||||
self.asset_caption['text'] = cap
|
||||
if self._launcher_done and self.compact_root:
|
||||
self.compact_asset['text'] = cap
|
||||
return
|
||||
if not preview:
|
||||
return
|
||||
cap = preview.format_caption(path)
|
||||
if self.root and not self._launcher_done:
|
||||
self.asset_caption['text'] = cap
|
||||
if path:
|
||||
short = path.replace('\\', '/')
|
||||
if len(short) > 56:
|
||||
short = short[:53] + '…'
|
||||
self.set_detail(short)
|
||||
if self._launcher_done and self.compact_root:
|
||||
self.compact_asset['text'] = cap
|
||||
try:
|
||||
preview.note_asset(path, model=model, texture=texture)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def enter_bulk_load(self, block_name: str, label: str, expected_steps: int):
|
||||
if not self._enabled:
|
||||
return
|
||||
self._bulk_active = True
|
||||
if ConfigVariableBool('want-zero-load-ui', False).value:
|
||||
return
|
||||
self._layout_compact_bar()
|
||||
if self._launcher_done:
|
||||
try:
|
||||
self.compact_root.reparentTo(aspect2d, DGG.NO_FADE_SORT_INDEX)
|
||||
except Exception:
|
||||
pass
|
||||
self.compact_status['text'] = label
|
||||
self.compact_asset['text'] = ''
|
||||
self.compact_progress['value'] = 0
|
||||
if self._want_preview and self._preview:
|
||||
self._preview.detach_card()
|
||||
self._preview.attach_card(self.compact_root, (1.15, 0, 0.02), (-0.2, 0.2, -0.1, 0.1))
|
||||
else:
|
||||
self.set_status(label)
|
||||
|
||||
def leave_bulk_load(self):
|
||||
self._bulk_active = False
|
||||
if ConfigVariableBool('want-zero-load-ui', False).value:
|
||||
if self._preview:
|
||||
try:
|
||||
self._preview.detach_card()
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
if self._launcher_done and self.compact_root:
|
||||
try:
|
||||
self.compact_root.reparentTo(hidden)
|
||||
except Exception:
|
||||
pass
|
||||
if self._preview:
|
||||
self._preview.detach_card()
|
||||
if self.root and not self._launcher_done and self._want_preview:
|
||||
self._preview.attach_card(self.root, (0.68, 0, 0.02), (-0.38, 0.38, -0.22, 0.22))
|
||||
|
||||
def transition_out(self, done_cb=None):
|
||||
if not self.root:
|
||||
if callable(done_cb):
|
||||
done_cb()
|
||||
return
|
||||
|
||||
if self._outro_ival:
|
||||
return
|
||||
|
||||
def _finish():
|
||||
try:
|
||||
if self._pulse_ival:
|
||||
self._pulse_ival.finish()
|
||||
self._pulse_ival = None
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.root.reparentTo(hidden)
|
||||
except Exception:
|
||||
pass
|
||||
if self._preview:
|
||||
self._preview.detach_card()
|
||||
self._launcher_done = True
|
||||
if callable(done_cb):
|
||||
done_cb()
|
||||
|
||||
self._outro_ival = Sequence(
|
||||
Parallel(
|
||||
LerpColorScaleInterval(self.root, 0.45, Vec4(1, 1, 1, 0.0), blendType='easeInOut'),
|
||||
LerpScaleInterval(self.root, 0.45, 1.06, blendType='easeInOut'),
|
||||
),
|
||||
Wait(0.02),
|
||||
Func(_finish),
|
||||
)
|
||||
self._outro_ival.start()
|
||||
|
||||
def destroy(self):
|
||||
if self._pulse_ival:
|
||||
try:
|
||||
self._pulse_ival.finish()
|
||||
except Exception:
|
||||
pass
|
||||
self._pulse_ival = None
|
||||
|
||||
if self._outro_ival:
|
||||
try:
|
||||
self._outro_ival.finish()
|
||||
except Exception:
|
||||
pass
|
||||
self._outro_ival = None
|
||||
|
||||
if self._preview:
|
||||
self._preview.destroy()
|
||||
self._preview = None
|
||||
|
||||
if self.compact_root:
|
||||
try:
|
||||
self.compact_root.destroy()
|
||||
except Exception:
|
||||
try:
|
||||
self.compact_root.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
self.compact_root = None
|
||||
|
||||
if self.root:
|
||||
try:
|
||||
self.root.destroy()
|
||||
except Exception:
|
||||
try:
|
||||
self.root.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
self.root = None
|
||||
|
||||
if self.logoModel:
|
||||
try:
|
||||
self.logoModel.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
self.logoModel = None
|
||||
|
|
@ -3,14 +3,21 @@ from panda3d.core import *
|
|||
from toontown.toonbase import ToontownGlobals
|
||||
from toontown.toonbase import TTLocalizer
|
||||
import random
|
||||
from toontown.toontowngui import ModernLoadingScreen
|
||||
|
||||
class ToontownLoadingScreen:
|
||||
|
||||
def __init__(self):
|
||||
self.__expectedCount = 0
|
||||
self.__count = 0
|
||||
self.__updateSkip = 5
|
||||
self.__updateSkip = 1
|
||||
self.__updateCounter = 0
|
||||
self.modern = None
|
||||
if ConfigVariableBool('want-modern-launcher-ui', True).value:
|
||||
try:
|
||||
self.modern = ModernLoadingScreen.ModernLoadingScreen()
|
||||
except Exception:
|
||||
self.modern = None
|
||||
self.gui = loader.loadModel('phase_3/models/gui/progress-background')
|
||||
self.banner = loader.loadModel('phase_3/models/gui/toon_council').find('**/scroll')
|
||||
self.banner.reparentTo(self.gui)
|
||||
|
|
@ -24,6 +31,12 @@ class ToontownLoadingScreen:
|
|||
return
|
||||
|
||||
def destroy(self):
|
||||
if self.modern:
|
||||
try:
|
||||
self.modern.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
self.modern = None
|
||||
self.tip.destroy()
|
||||
self.title.destroy()
|
||||
self.waitBar.destroy()
|
||||
|
|
@ -31,14 +44,27 @@ class ToontownLoadingScreen:
|
|||
self.gui.removeNode()
|
||||
|
||||
def getTip(self, tipCategory):
|
||||
return TTLocalizer.TipTitle + '\n' + random.choice(TTLocalizer.TipDict.get(tipCategory))
|
||||
tips = TTLocalizer.TipDict.get(tipCategory)
|
||||
if tips:
|
||||
return TTLocalizer.TipTitle + '\n' + random.choice(tips)
|
||||
return TTLocalizer.TipTitle
|
||||
|
||||
def begin(self, range, label, gui, tipCategory):
|
||||
self.__count = 0
|
||||
self.__expectedCount = max(1, range)
|
||||
tip_text = self.getTip(tipCategory)
|
||||
if self.modern and self.modern.enabled():
|
||||
self.modern.enter_bulk_load('bulk', label, self.__expectedCount)
|
||||
self.modern.set_status(label)
|
||||
self.modern.set_detail(tip_text)
|
||||
self.modern.set_progress(0.0)
|
||||
self.gui.reparentTo(hidden)
|
||||
self.waitBar.reparentTo(hidden)
|
||||
self.title.reparentTo(hidden)
|
||||
return
|
||||
self.waitBar['range'] = range
|
||||
self.title['text'] = label
|
||||
self.tip['text'] = self.getTip(tipCategory)
|
||||
self.__count = 0
|
||||
self.__expectedCount = range
|
||||
self.tip['text'] = tip_text
|
||||
if gui:
|
||||
self.waitBar.reparentTo(self.gui)
|
||||
self.title.reparentTo(self.gui)
|
||||
|
|
@ -50,6 +76,10 @@ class ToontownLoadingScreen:
|
|||
self.waitBar.update(self.__count)
|
||||
|
||||
def end(self):
|
||||
if self.modern and self.modern.enabled():
|
||||
self.modern.set_progress(100.0)
|
||||
self.modern.leave_bulk_load()
|
||||
return (self.__expectedCount, self.__count)
|
||||
self.waitBar.finish()
|
||||
self.waitBar.reparentTo(self.gui)
|
||||
self.title.reparentTo(self.gui)
|
||||
|
|
@ -57,11 +87,25 @@ class ToontownLoadingScreen:
|
|||
return (self.__expectedCount, self.__count)
|
||||
|
||||
def abort(self):
|
||||
if self.modern and self.modern.enabled():
|
||||
self.modern.leave_bulk_load()
|
||||
return
|
||||
self.gui.reparentTo(hidden)
|
||||
|
||||
def tick(self):
|
||||
self.__count = self.__count + 1
|
||||
self.__count += 1
|
||||
if self.modern and self.modern.enabled():
|
||||
pct = (float(self.__count) / float(self.__expectedCount)) * 100.0
|
||||
self.modern.set_progress(pct)
|
||||
return
|
||||
self.__updateCounter += 1
|
||||
if self.__updateCounter >= self.__updateSkip:
|
||||
self.__updateCounter = 0
|
||||
self.waitBar.update(self.__count)
|
||||
|
||||
def on_asset_loaded(self, path=None, model=None, texture=None, kind='model'):
|
||||
if self.modern and self.modern.enabled():
|
||||
try:
|
||||
self.modern.on_asset_loaded(path=path, model=model, texture=texture, kind=kind)
|
||||
except Exception:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
from panda3d.core import ModelPool, TexturePool, ConfigVariableBool
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
from toontown.toonbase import ToontownGlobals
|
||||
|
||||
notify = directNotify.newCategory('ZonePrefetchCatalog')
|
||||
|
||||
ZONE_ASSETS_MAP = {
|
||||
2000: [
|
||||
'phase_4/models/neighborhoods/toontown_central',
|
||||
'phase_3.5/models/modules/skys/TT_sky',
|
||||
'phase_4/models/props/tt_m_prp_ext_fountain',
|
||||
'phase_3.5/models/props/tunnel_sign_toontown',
|
||||
],
|
||||
1000: [
|
||||
'phase_6/models/neighborhoods/donalds_dock',
|
||||
'phase_3.5/models/modules/skys/cloud_sky',
|
||||
'phase_6/models/props/dock_boat',
|
||||
],
|
||||
3000: [
|
||||
'phase_6/models/neighborhoods/minnies_melody_land',
|
||||
'phase_6/models/modules/skys/mml_sky',
|
||||
],
|
||||
4000: [
|
||||
'phase_8/models/neighborhoods/daisys_garden',
|
||||
'phase_3.5/models/modules/skys/TT_sky',
|
||||
],
|
||||
5000: [
|
||||
'phase_6/models/neighborhoods/the_brrrgh',
|
||||
'phase_6/models/modules/skys/brrrgh_sky',
|
||||
],
|
||||
9000: [
|
||||
'phase_8/models/neighborhoods/donalds_dreamland',
|
||||
'phase_8/models/modules/skys/night_sky',
|
||||
],
|
||||
8000: [
|
||||
'phase_6/models/karting/speedway_hub',
|
||||
'phase_3.5/models/modules/skys/TT_sky',
|
||||
],
|
||||
6000: [
|
||||
'phase_6/models/golf/chip_n_dales_acorn_acres',
|
||||
],
|
||||
11000: [
|
||||
'phase_9/models/cogHQ/SellbotHQ_outer',
|
||||
'phase_9/models/cogHQ/SellbotHQ_inner',
|
||||
],
|
||||
12000: [
|
||||
'phase_10/models/cogHQ/CashbotHQExterior',
|
||||
],
|
||||
13000: [
|
||||
'phase_11/models/lawbotHQ/LawbotHQExterior',
|
||||
],
|
||||
10000: [
|
||||
'phase_12/models/bossbotHQ/BossbotHQExterior',
|
||||
],
|
||||
500: [
|
||||
'phase_5.5/models/estate/estate_house',
|
||||
],
|
||||
}
|
||||
|
||||
class ZonePrefetchManager:
|
||||
def __init__(self):
|
||||
self._prefetched_zones = set()
|
||||
self._cached_models = {}
|
||||
self._enabled = ConfigVariableBool('want-async-zone-prefetch', True).value
|
||||
|
||||
def prefetch_zone(self, zoneId, loader=None):
|
||||
if not self._enabled:
|
||||
return
|
||||
base_zone = (zoneId // 1000) * 1000
|
||||
if base_zone == 0:
|
||||
base_zone = zoneId
|
||||
if base_zone in self._prefetched_zones:
|
||||
return
|
||||
assets = ZONE_ASSETS_MAP.get(base_zone) or ZONE_ASSETS_MAP.get(zoneId)
|
||||
if not assets:
|
||||
return
|
||||
if loader is None:
|
||||
try:
|
||||
loader = base.loader
|
||||
except Exception:
|
||||
return
|
||||
self._prefetched_zones.add(base_zone)
|
||||
notify.info('Prefetching zone %s assets (%d models)...' % (zoneId, len(assets)))
|
||||
for path in assets:
|
||||
if path in self._cached_models:
|
||||
continue
|
||||
try:
|
||||
if hasattr(loader, 'loadModel'):
|
||||
model = loader.loadModel(path, okMissing=True)
|
||||
if model and not model.isEmpty():
|
||||
self._cached_models[path] = model
|
||||
ModelPool.addModel(path, model.node())
|
||||
except Exception as e:
|
||||
notify.debug('Prefetch failed for %s: %s' % (path, e))
|
||||
|
||||
def is_zone_prefetched(self, zoneId):
|
||||
base_zone = (zoneId // 1000) * 1000
|
||||
return (base_zone in self._prefetched_zones) or (zoneId in self._prefetched_zones)
|
||||
|
||||
def clear_cache(self):
|
||||
self._cached_models.clear()
|
||||
self._prefetched_zones.clear()
|
||||
|
||||
zonePrefetchManager = ZonePrefetchManager()
|
||||
Loading…
Reference in New Issue