from direct.distributed.ClockDelta import * from panda3d.core import * from direct.showbase.PythonUtil import Functor, sameElements, list2dict, uniqueElements from direct.interval.IntervalGlobal import * from toontown.distributed.ToontownMsgTypes import * from toontown.toonbase import ToontownGlobals from otp.otpbase import OTPGlobals from direct.distributed import DistributedObject from . import Level from . import LevelConstants from direct.directnotify import DirectNotifyGlobal from . import EntityCreator from direct.gui import OnscreenText from direct.task import Task from . import LevelUtil import random class DistributedLevel(DistributedObject.DistributedObject, Level.Level): notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel') WantVisibility = config.GetBool('level-visibility', 1) ColorZonesAllDOs = 0 FloorCollPrefix = 'zoneFloor' OuchTaskName = 'ouchTask' VisChangeTaskName = 'visChange' EmulateEntrancePoint = True def __init__(self, cr): DistributedObject.DistributedObject.__init__(self, cr) Level.Level.__init__(self) self.lastToonZone = None self.lastCamZone = 0 self.titleColor = (1, 1, 1, 1) self.titleText = OnscreenText.OnscreenText('', fg=self.titleColor, shadow=(0, 0, 0, 1), font=ToontownGlobals.getSuitFont(), pos=(0, -0.5), scale=0.16, drawOrder=0, mayChange=1) self.smallTitleText = OnscreenText.OnscreenText('', fg=self.titleColor, font=ToontownGlobals.getSuitFont(), pos=(0.65, 0.9), scale=0.08, drawOrder=0, mayChange=1, bg=(0.5, 0.5, 0.5, 0.5), align=TextNode.ARight) self.titleTextSeq = None self.zonesEnteredList = [] self.fColorZones = 0 self.scenarioIndex = 0 return def generate(self): DistributedLevel.notify.debug('generate') DistributedObject.DistributedObject.generate(self) self.parent2pendingChildren = {} self.curSpec = None if base.cr.timeManager is not None: base.cr.timeManager.synchronize('DistributedLevel.generate') else: self.notify.warning('generate(): no TimeManager!') return def setLevelZoneId(self, zoneId): self.levelZone = zoneId def setPlayerIds(self, avIdList): self.avIdList = avIdList def setEntranceId(self, entranceId): self.entranceId = entranceId def getEntranceId(self): return self.entranceId def setZoneIds(self, zoneIds): DistributedLevel.notify.debug('setZoneIds: %s' % zoneIds) self.zoneIds = zoneIds def setStartTimestamp(self, timestamp): DistributedLevel.notify.debug('setStartTimestamp: %s' % timestamp) self.startTime = globalClockDelta.networkToLocalTime(timestamp, bits=32) self.privGotAllRequired() def privGotAllRequired(self): self.levelAnnounceGenerate() def levelAnnounceGenerate(self): pass def initializeLevel(self, levelSpec): if __dev__: self.candidateSpec = levelSpec self.sendUpdate('requestCurrentLevelSpec', [levelSpec.stringHash(), levelSpec.entTypeReg.getHashStr()]) else: self.privGotSpec(levelSpec) if __dev__: def reportModelSpecSyncError(self, msg): DistributedLevel.notify.error('%s\n\nyour spec does not match the level model\nuse SpecUtil.updateSpec, then restart your AI and client' % msg) def setSpecDeny(self, reason): DistributedLevel.notify.error(reason) def setSpecSenderDoId(self, doId): DistributedLevel.notify.debug('setSpecSenderDoId: %s' % doId) blobSender = base.cr.doId2do[doId] def setSpecBlob(specBlob, blobSender = blobSender, self = self): blobSender.sendAck() from .LevelSpec import LevelSpec spec = eval(specBlob) if spec is None: spec = self.candidateSpec del self.candidateSpec self.privGotSpec(spec) return if blobSender.isComplete(): setSpecBlob(blobSender.getBlob()) else: evtName = self.uniqueName('specDone') blobSender.setDoneEvent(evtName) self.acceptOnce(evtName, setSpecBlob) def privGotSpec(self, levelSpec): Level.Level.initializeLevel(self, self.doId, levelSpec, self.scenarioIndex) modelZoneNums = self.zoneNums specZoneNums = list(self.zoneNum2zoneId.keys()) if not sameElements(modelZoneNums, specZoneNums): self.reportModelSpecSyncError('model zone nums (%s) do not match spec zone nums (%s)' % (modelZoneNums, specZoneNums)) self.initVisibility() self.placeLocalToon() def announceLeaving(self): DistributedLevel.notify.debug('announceLeaving') self.doneBarrier() def placeLocalToon(self, moveLocalAvatar = True): initialZoneEnt = None if self.entranceId in self.entranceId2entity: epEnt = self.entranceId2entity[self.entranceId] if moveLocalAvatar: epEnt.placeToon(base.localAvatar, self.avIdList.index(base.localAvatar.doId), len(self.avIdList)) initialZoneEnt = self.getEntity(epEnt.getZoneEntId()) elif self.EmulateEntrancePoint: self.notify.debug('unknown entranceId %s' % self.entranceId) if moveLocalAvatar: base.localAvatar.reparentTo(render) base.localAvatar.setPosHpr(0, 0, 0, 0, 0, 0) self.notify.debug('showing all zones') self.setColorZones(1) zoneEntIds = list(self.entType2ids['zone']) zoneEntIds.remove(LevelConstants.UberZoneEntId) if len(zoneEntIds): zoneEntId = random.choice(zoneEntIds) initialZoneEnt = self.getEntity(zoneEntId) if moveLocalAvatar: base.localAvatar.setPos(render, initialZoneEnt.getZoneNode().getPos(render)) else: initialZoneEnt = self.getEntity(LevelConstants.UberZoneEntId) if moveLocalAvatar: base.localAvatar.setPos(render, 0, 0, 0) if initialZoneEnt is not None: self.enterZone(initialZoneEnt.entId) return def createEntityCreator(self): return EntityCreator.EntityCreator(level=self) def onEntityTypePostCreate(self, entType): Level.Level.onEntityTypePostCreate(self, entType) if entType == 'levelMgr': self.__handleLevelMgrCreated() def __handleLevelMgrCreated(self): levelMgr = self.getEntity(LevelConstants.LevelMgrEntId) self.geom = levelMgr.geom self.zoneNum2node = LevelUtil.getZoneNum2Node(self.geom) self.zoneNums = list(self.zoneNum2node.keys()) self.zoneNums.sort() self.zoneNumDict = list2dict(self.zoneNums) DistributedLevel.notify.debug('zones from model: %s' % self.zoneNums) self.fixupLevelModel() def fixupLevelModel(self): for zoneNum, zoneNode in list(self.zoneNum2node.items()): if zoneNum == LevelConstants.UberZoneEntId: continue allColls = zoneNode.findAllMatches('**/+CollisionNode') floorColls = [] for coll in allColls: bitmask = coll.node().getIntoCollideMask() if not (bitmask & ToontownGlobals.FloorBitmask).isZero(): floorColls.append(coll) if len(floorColls) > 0: floorCollName = '%s%s' % (DistributedLevel.FloorCollPrefix, zoneNum) others = zoneNode.findAllMatches('**/%s' % floorCollName) for other in others: other.setName('%s_renamed' % floorCollName) for floorColl in floorColls: floorColl.setName(floorCollName) def handleZoneEnter(collisionEntry, self = self, zoneNum = zoneNum): self.toonEnterZone(zoneNum) floorNode = collisionEntry.getIntoNode() if floorNode.hasTag('ouch'): ouchLevel = int(self.getFloorOuchLevel()) self.startOuch(ouchLevel) self.accept('enter%s' % floorCollName, handleZoneEnter) def handleZoneExit(collisionEntry, self = self, zoneNum = zoneNum): floorNode = collisionEntry.getIntoNode() if floorNode.hasTag('ouch'): self.stopOuch() self.accept('exit%s' % floorCollName, handleZoneExit) def getFloorOuchLevel(self): return 1 def announceGenerate(self): DistributedLevel.notify.debug('announceGenerate') DistributedObject.DistributedObject.announceGenerate(self) def disable(self): DistributedLevel.notify.debug('disable') if hasattr(self, 'geom'): del self.geom self.shutdownVisibility() self.destroyLevel() self.ignoreAll() if self.titleTextSeq: self.titleTextSeq.finish() self.titleTextSeq = None if self.smallTitleText: self.smallTitleText.cleanup() self.smallTitleText = None if self.titleText: self.titleText.cleanup() self.titleText = None self.zonesEnteredList = [] DistributedObject.DistributedObject.disable(self) return def delete(self): DistributedLevel.notify.debug('delete') DistributedObject.DistributedObject.delete(self) self.stopOuch() def requestReparent(self, entity, parentId, wrt = False): parent = self.getEntity(parentId) if parent is not None: if wrt: entity.wrtReparentTo(parent.getNodePath()) else: entity.reparentTo(parent.getNodePath()) else: DistributedLevel.notify.debug('entity %s requesting reparent to %s, not yet created' % (entity, parentId)) entity.reparentTo(hidden) if parentId not in self.parent2pendingChildren: self.parent2pendingChildren[parentId] = [] def doReparent(parentId = parentId, self = self, wrt = wrt): parent = self.getEntity(parentId) for child in self.parent2pendingChildren[parentId]: DistributedLevel.notify.debug('performing pending reparent of %s to %s' % (child, parent)) if wrt: child.wrtReparentTo(parent.getNodePath()) else: child.reparentTo(parent.getNodePath()) del self.parent2pendingChildren[parentId] self.ignore(self.getEntityCreateEvent(parentId)) self.accept(self.getEntityCreateEvent(parentId), doReparent) self.parent2pendingChildren[parentId].append(entity) return def getZoneNode(self, zoneEntId): return self.zoneNum2node.get(zoneEntId) def warpToZone(self, zoneNum): zoneNode = self.getZoneNode(zoneNum) if zoneNode is None: return base.localAvatar.setPos(zoneNode, 0, 0, 0) base.localAvatar.setHpr(zoneNode, 0, 0, 0) self.enterZone(zoneNum) return def showZone(self, zoneNum): zone = self.getZoneNode(zoneNum) zone.unstash() zone.clearColor() def setColorZones(self, fColorZones): self.fColorZones = fColorZones self.resetVisibility() def getColorZones(self): return self.fColorZones def hideZone(self, zoneNum): zone = self.getZoneNode(zoneNum) if self.fColorZones: zone.unstash() zone.setColor(1, 0, 0) else: zone.stash() def setTransparency(self, alpha, zone = None): self.geom.setTransparency(1) if zone is None: node = self.geom else: node = self.getZoneNode(zoneNum) node.setAlphaScale(alpha) return def initVisibility(self): self.curVisibleZoneNums = list2dict(self.zoneNums) del self.curVisibleZoneNums[LevelConstants.UberZoneEntId] self.curZoneNum = None self.visChangedThisFrame = 0 self.fForceSetZoneThisFrame = 0 def handleCameraRayFloorCollision(collEntry, self = self): name = collEntry.getIntoNode().getName() self.notify.debug('camera floor ray collided with: %s' % name) prefixLen = len(DistributedLevel.FloorCollPrefix) if name[:prefixLen] == DistributedLevel.FloorCollPrefix: try: zoneNum = int(name[prefixLen:]) except: DistributedLevel.notify.warning('Invalid zone floor collision node: %s' % name) else: self.camEnterZone(zoneNum) self.accept('on-floor', handleCameraRayFloorCollision) if not DistributedLevel.WantVisibility: zoneNums = list(self.zoneNums) zoneNums.remove(LevelConstants.UberZoneEntId) self.forceSetZoneThisFrame() self.setVisibility(zoneNums) taskMgr.add(self.visChangeTask, self.uniqueName(DistributedLevel.VisChangeTaskName), priority=49) return def shutdownVisibility(self): taskMgr.remove(self.uniqueName(DistributedLevel.VisChangeTaskName)) def toonEnterZone(self, zoneNum, ouchLevel = None): DistributedLevel.notify.debug('toonEnterZone%s' % zoneNum) if zoneNum != self.lastToonZone: self.lastToonZone = zoneNum self.notify.debug('toon is standing in zone %s' % zoneNum) messenger.send('factoryZoneChanged', [zoneNum]) def camEnterZone(self, zoneNum): DistributedLevel.notify.debug('camEnterZone%s' % zoneNum) self.enterZone(zoneNum) if zoneNum != self.lastCamZone: self.lastCamZone = zoneNum self.smallTitleText.hide() self.spawnTitleText() def lockVisibility(self, zoneNum = None, zoneId = None): if zoneId is not None: zoneNum = self.getZoneNumFromId(zoneId) self.notify.debug('lockVisibility to zoneNum %s' % zoneNum) self.lockVizZone = zoneNum self.enterZone(self.lockVizZone) return def unlockVisibility(self): self.notify.debug('unlockVisibility') if not hasattr(self, 'lockVizZone'): self.notify.warning('visibility already unlocked') else: del self.lockVizZone self.updateVisibility() def enterZone(self, zoneNum): DistributedLevel.notify.debug('entering zone %s' % zoneNum) if not DistributedLevel.WantVisibility: return if zoneNum == self.curZoneNum: return if zoneNum not in self.zoneNumDict: DistributedLevel.notify.error('no ZoneEntity for this zone (%s)!!' % zoneNum) self.updateVisibility(zoneNum) def updateVisibility(self, zoneNum = None): if zoneNum is None: zoneNum = self.curZoneNum if zoneNum is None: return if hasattr(self, 'lockVizZone'): zoneNum = self.lockVizZone zoneEnt = self.getEntity(zoneNum) visibleZoneNums = list2dict([zoneNum]) visibleZoneNums.update(list2dict(zoneEnt.getVisibleZoneNums())) if not __debug__: if self.lastToonZone not in visibleZoneNums: if self.lastToonZone is not None: self.notify.warning('adding zoneNum %s to visibility list because toon is standing in that zone!' % self.lastToonZone) visibleZoneNums.update(list2dict([self.lastToonZone])) zoneEntIds = list(self.entType2ids['zone']) zoneEntIds.remove(LevelConstants.UberZoneEntId) if len(zoneEntIds): pass vizZonesChanged = 1 addedZoneNums = [] removedZoneNums = [] allVZ = dict(visibleZoneNums) allVZ.update(self.curVisibleZoneNums) for vz, dummy in list(allVZ.items()): new = vz in visibleZoneNums old = vz in self.curVisibleZoneNums if new and old: continue if new: addedZoneNums.append(vz) else: removedZoneNums.append(vz) if not addedZoneNums and not removedZoneNums: DistributedLevel.notify.debug('visible zone list has not changed') vizZonesChanged = 0 else: DistributedLevel.notify.debug('showing zones %s' % addedZoneNums) for az in addedZoneNums: self.showZone(az) DistributedLevel.notify.debug('hiding zones %s' % removedZoneNums) for rz in removedZoneNums: self.hideZone(rz) if vizZonesChanged or self.fForceSetZoneThisFrame: self.setVisibility(list(visibleZoneNums.keys())) self.fForceSetZoneThisFrame = 0 self.curZoneNum = zoneNum self.curVisibleZoneNums = visibleZoneNums return def setVisibility(self, vizList): if self.fColorZones and DistributedLevel.ColorZonesAllDOs: vizList = list(self.zoneNums) vizList.remove(LevelConstants.UberZoneEntId) uberZone = self.getZoneId(LevelConstants.UberZoneEntId) visibleZoneIds = [self.levelZone, uberZone] for vz in vizList: if vz is not LevelConstants.UberZoneEntId: visibleZoneIds.append(self.getZoneId(vz)) DistributedLevel.notify.debug('new viz list: %s' % visibleZoneIds) base.cr.sendSetZoneMsg(self.levelZone, visibleZoneIds) def resetVisibility(self): self.curVisibleZoneNums = list2dict(self.zoneNums) del self.curVisibleZoneNums[LevelConstants.UberZoneEntId] for vz, dummy in list(self.curVisibleZoneNums.items()): self.showZone(vz) self.updateVisibility() def handleVisChange(self): Level.Level.handleVisChange(self) self.visChangedThisFrame = 1 def forceSetZoneThisFrame(self): self.fForceSetZoneThisFrame = 1 def visChangeTask(self, task): if self.visChangedThisFrame or self.fForceSetZoneThisFrame: self.updateVisibility() self.visChangedThisFrame = 0 return Task.cont if __dev__: def setAttribChange(self, entId, attribName, valueStr, username): value = eval(valueStr) self.levelSpec.setAttribChange(entId, attribName, value, username) def spawnTitleText(self): def getDescription(zoneNum, self = self): ent = self.entities.get(zoneNum) if ent and hasattr(ent, 'description'): return ent.description return None description = getDescription(self.lastCamZone) if description and description != '': if self.titleTextSeq: self.titleTextSeq.finish() self.titleTextSeq = None self.smallTitleText.setText(description) self.titleText.setText(description) self.titleText.setColor(Vec4(*self.titleColor)) self.titleText.setFg(self.titleColor) titleSeq = None if self.lastCamZone not in self.zonesEnteredList: self.zonesEnteredList.append(self.lastCamZone) titleSeq = Sequence(Func(self.hideSmallTitleText), Func(self.showTitleText), Wait(0.1), Wait(6.0), self.titleText.colorInterval(0.5, Vec4(self.titleColor[0], self.titleColor[1], self.titleColor[2], self.titleColor[3]), startColor=Vec4(self.titleColor[0], self.titleColor[1], self.titleColor[2], 0.0))) smallTitleSeq = Sequence(Func(self.hideTitleText), Func(self.showSmallTitle)) if titleSeq: self.titleTextSeq = Sequence(titleSeq, smallTitleSeq, name=self.uniqueName('titleText')) else: self.titleTextSeq = Sequence(smallTitleSeq, name=self.uniqueName('titleText')) self.titleTextSeq.start() return def showInfoText(self, text = 'hello world'): description = text if description and description != '': if self.titleTextSeq: self.titleTextSeq.finish() self.titleTextSeq = None self.smallTitleText.setText(description) self.titleText.setText(description) self.titleText.setColor(Vec4(*self.titleColor)) self.titleText.setFg(self.titleColor) titleSeq = None titleSeq = Sequence(Func(self.hideSmallTitleText), Func(self.showTitleText), Wait(0.1), Wait(3.0), self.titleText.colorInterval(0.5, Vec4(self.titleColor[0], self.titleColor[1], self.titleColor[2], self.titleColor[3]), startColor=Vec4(self.titleColor[0], self.titleColor[1], self.titleColor[2], 0.0))) if titleSeq: self.titleTextSeq = Sequence(titleSeq, name=self.uniqueName('titleText')) self.titleTextSeq.start() return def showTitleText(self): self.titleText.show() def hideTitleText(self): if self.titleText: self.titleText.hide() def showSmallTitle(self): if self.titleText: self.titleText.hide() self.smallTitleText.show() def hideSmallTitleText(self): if self.smallTitleText: self.smallTitleText.hide() def startOuch(self, ouchLevel, period = 2): self.notify.debug('startOuch %s' % ouchLevel) if not hasattr(self, 'doingOuch'): def doOuch(task, self = self, ouchLevel = ouchLevel, period = period): self.b_setOuch(ouchLevel) self.lastOuchTime = globalClock.getFrameTime() taskMgr.doMethodLater(period, doOuch, DistributedLevel.OuchTaskName) delay = 0 if hasattr(self, 'lastOuchTime'): curFrameTime = globalClock.getFrameTime() timeSinceLastOuch = curFrameTime - self.lastOuchTime if timeSinceLastOuch < period: delay = period - timeSinceLastOuch if delay > 0: taskMgr.doMethodLater(period, doOuch, DistributedLevel.OuchTaskName) else: doOuch(None) self.doingOuch = 1 return def stopOuch(self): if hasattr(self, 'doingOuch'): taskMgr.remove(DistributedLevel.OuchTaskName) del self.doingOuch def b_setOuch(self, penalty, anim = None): self.notify.debug('b_setOuch %s' % penalty) av = base.localAvatar if not av.isStunned: self.d_setOuch(penalty) self.setOuch(penalty, anim) def d_setOuch(self, penalty): self.sendUpdate('setOuch', [penalty]) def setOuch(self, penalty, anim = None): if anim == 'Squish': if base.cr.playGame.getPlace(): base.cr.playGame.getPlace().fsm.request('squished') elif anim == 'Fall': if base.cr.playGame.getPlace(): base.cr.playGame.getPlace().fsm.request('fallDown') av = base.localAvatar av.stunToon() av.playDialogueForString('!') def complexVis(self): return 1