from panda3d.core import * from panda3d.otp import * from panda3d.toontown import * from direct.interval.IntervalGlobal import * from direct.distributed.ClockDelta import * from direct.directtools.DirectGeometry import CLAMP from direct.task import Task from otp.avatar import DistributedAvatar from . import Suit from toontown.toonbase import ToontownGlobals from toontown.battle import DistributedBattle from direct.fsm import ClassicFSM, State from direct.fsm import State from . import SuitTimings from . import SuitBase from . import DistributedSuitPlanner from direct.directnotify import DirectNotifyGlobal from . import SuitDialog from toontown.battle import BattleProps from toontown.distributed.DelayDeletable import DelayDeletable import math import copy from . import DistributedSuitBase from otp.otpbase import OTPLocalizer import random STAND_OUTSIDE_DOOR = 2.5 BATTLE_IGNORE_TIME = 6 BATTLE_WAIT_TIME = 3 CATCHUP_SPEED_MULTIPLIER = 3 ALLOW_BATTLE_DETECT = 1 class DistributedSuit(DistributedSuitBase.DistributedSuitBase, DelayDeletable): notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSuit') ENABLE_EXPANDED_NAME = 0 def __init__(self, cr): try: self.DistributedSuit_initialized return except: self.DistributedSuit_initialized = 1 DistributedSuitBase.DistributedSuitBase.__init__(self, cr) self.spDoId = None self.pathEndpointStart = 0 self.pathEndpointEnd = 0 self.minPathLen = 0 self.maxPathLen = 0 self.pathPositionIndex = 0 self.pathPositionTimestamp = 0.0 self.pathState = 0 self.path = None self.localPathState = 0 self.currentLeg = -1 self.pathStartTime = 0.0 self.legList = None self.initState = None self.finalState = None self.buildingSuit = 0 self.fsm = ClassicFSM.ClassicFSM('DistributedSuit', [ State.State('Off', self.enterOff, self.exitOff, [ 'FromSky', 'FromSuitBuilding', 'Walk', 'Battle', 'neutral', 'ToToonBuilding', 'ToSuitBuilding', 'ToCogHQ', 'FromCogHQ', 'ToSky', 'FlyAway', 'DanceThenFlyAway', 'WalkToStreet', 'WalkFromStreet']), State.State('FromSky', self.enterFromSky, self.exitFromSky, [ 'Walk', 'Battle', 'neutral', 'ToSky', 'WalkFromStreet']), State.State('FromSuitBuilding', self.enterFromSuitBuilding, self.exitFromSuitBuilding, [ 'WalkToStreet', 'Walk', 'Battle', 'neutral', 'ToSky']), State.State('WalkToStreet', self.enterWalkToStreet, self.exitWalkToStreet, [ 'Walk', 'Battle', 'neutral', 'ToSky', 'ToToonBuilding', 'ToSuitBuilding', 'ToCogHQ', 'WalkFromStreet']), State.State('WalkFromStreet', self.enterWalkFromStreet, self.exitWalkFromStreet, [ 'ToToonBuilding', 'ToSuitBuilding', 'ToCogHQ', 'Battle', 'neutral', 'ToSky']), State.State('Walk', self.enterWalk, self.exitWalk, [ 'WaitForBattle', 'Battle', 'neutral', 'WalkFromStreet', 'ToSky', 'ToCogHQ', 'Walk']), State.State('Battle', self.enterBattle, self.exitBattle, [ 'Walk', 'ToToonBuilding', 'ToCogHQ', 'ToSuitBuilding', 'ToSky']), State.State('neutral', self.enterNeutral, self.exitNeutral, []), State.State('WaitForBattle', self.enterWaitForBattle, self.exitWaitForBattle, [ 'Battle', 'neutral', 'Walk', 'WalkToStreet', 'WalkFromStreet', 'ToToonBuilding', 'ToCogHQ', 'ToSuitBuilding', 'ToSky']), State.State('ToToonBuilding', self.enterToToonBuilding, self.exitToToonBuilding, [ 'neutral', 'Battle']), State.State('ToSuitBuilding', self.enterToSuitBuilding, self.exitToSuitBuilding, [ 'neutral', 'Battle']), State.State('ToCogHQ', self.enterToCogHQ, self.exitToCogHQ, [ 'neutral', 'Battle']), State.State('FromCogHQ', self.enterFromCogHQ, self.exitFromCogHQ, [ 'neutral', 'Battle', 'Walk']), State.State('ToSky', self.enterToSky, self.exitToSky, [ 'Battle']), State.State('FlyAway', self.enterFlyAway, self.exitFlyAway, []), State.State('DanceThenFlyAway', self.enterDanceThenFlyAway, self.exitDanceThenFlyAway, [])], 'Off', 'Off') self.fsm.enterInitialState() self.soundSequenceList = [] self.__currentDialogue = None return def generate(self): DistributedSuitBase.DistributedSuitBase.generate(self) def disable(self): for soundSequence in self.soundSequenceList: soundSequence.finish() self.soundSequenceList = [] self.notify.debug('DistributedSuit %d: disabling' % self.getDoId()) self.resumePath(0) self.stopPathNow() self.setState('Off') DistributedSuitBase.DistributedSuitBase.disable(self) def delete(self): try: self.DistributedSuit_deleted except: self.DistributedSuit_deleted = 1 self.notify.debug('DistributedSuit %d: deleting' % self.getDoId()) del self.fsm DistributedSuitBase.DistributedSuitBase.delete(self) def setPathEndpoints(self, start, end, minPathLen, maxPathLen): if self.pathEndpointStart == start and self.pathEndpointEnd == end and self.minPathLen == minPathLen and self.maxPathLen == maxPathLen and self.path != None: return self.pathEndpointStart = start self.pathEndpointEnd = end self.minPathLen = minPathLen self.maxPathLen = maxPathLen self.path = None self.pathLength = 0 self.currentLeg = -1 self.legList = None if self.maxPathLen == 0: return if not self.verifySuitPlanner(): return self.startPoint = self.sp.pointIndexes[self.pathEndpointStart] self.endPoint = self.sp.pointIndexes[self.pathEndpointEnd] path = self.sp.genPath(self.startPoint, self.endPoint, self.minPathLen, self.maxPathLen) self.setPath(path) self.makeLegList() return def verifySuitPlanner(self): if self.sp == None and self.spDoId != 0: self.notify.warning('Suit %d does not have a suit planner! Expected SP doId %s.' % (self.doId, self.spDoId)) self.sp = self.cr.doId2do.get(self.spDoId, None) if self.sp == None: return 0 return 1 def setPathPosition(self, index, timestamp): if not self.verifySuitPlanner(): return if self.path == None: self.setPathEndpoints(self.pathEndpointStart, self.pathEndpointEnd, self.minPathLen, self.maxPathLen) self.pathPositionIndex = index self.pathPositionTimestamp = globalClockDelta.networkToLocalTime(timestamp) if self.legList != None: self.pathStartTime = self.pathPositionTimestamp - self.legList.getStartTime(self.pathPositionIndex) return def setPathState(self, state): self.pathState = state self.resumePath(state) def debugSuitPosition(self, elapsed, currentLeg, x, y, timestamp): now = globalClock.getFrameTime() chug = globalClock.getRealTime() - now messageAge = now - globalClockDelta.networkToLocalTime(timestamp, now) if messageAge < -(chug + 0.5) or messageAge > chug + 1.0: print('Apparently out of sync with AI by %0.2f seconds. Suggest resync!' % messageAge) return localElapsed = now - self.pathStartTime timeDiff = localElapsed - (elapsed + messageAge) if abs(timeDiff) > 0.2: print("%s (%d) appears to be %0.2f seconds out of sync along its path. Suggest '~cogs sync'." % (self.getName(), self.getDoId(), timeDiff)) return if self.legList == None: print("%s (%d) doesn't have a legList yet." % (self.getName(), self.getDoId())) return netPos = Point3(x, y, 0.0) leg = self.legList.getLeg(currentLeg) calcPos = leg.getPosAtTime(elapsed - leg.getStartTime()) calcPos.setZ(0.0) calcDelta = Vec3(netPos - calcPos) diff = calcDelta.length() if diff > 4.0: print('%s (%d) is %0.2f feet from the AI computed path!' % (self.getName(), self.getDoId(), diff)) print('Probably your DNA files are out of sync.') return localPos = Point3(self.getX(), self.getY(), 0.0) localDelta = Vec3(netPos - localPos) diff = localDelta.length() if diff > 10.0: print('%s (%d) in state %s is %0.2f feet from its correct position!' % (self.getName(), self.getDoId(), self.fsm.getCurrentState().getName(), diff)) print('Should be at (%0.2f, %0.2f), but is at (%0.2f, %0.2f).' % (x, y, localPos[0], localPos[1])) return print('%s (%d) is in the correct position.' % (self.getName(), self.getDoId())) return def denyBattle(self): DistributedSuitBase.DistributedSuitBase.denyBattle(self) self.disableBattleDetect() def resumePath(self, state): if self.localPathState != state: self.localPathState = state if state == 0: self.stopPathNow() elif state == 1: self.moveToNextLeg(None) elif state == 2: self.stopPathNow() if self.sp != None: self.setState('Off') self.setState('FlyAway') elif state == 3: pass elif state == 4: self.stopPathNow() if self.sp != None: self.setState('Off') self.setState('DanceThenFlyAway') else: self.notify.error('No such state as: ' + str(state)) return def moveToNextLeg(self, task): if self.legList == None: self.notify.warning('Suit %d does not have a path!' % self.getDoId()) return Task.done now = globalClock.getFrameTime() elapsed = now - self.pathStartTime nextLeg = self.legList.getLegIndexAtTime(elapsed, self.currentLeg) numLegs = self.legList.getNumLegs() if self.currentLeg != nextLeg: self.currentLeg = nextLeg self.doPathLeg(self.legList[nextLeg], elapsed - self.legList.getStartTime(nextLeg)) nextLeg += 1 if nextLeg < numLegs: nextTime = self.legList.getStartTime(nextLeg) delay = nextTime - elapsed name = self.taskName('move') taskMgr.remove(name) taskMgr.doMethodLater(delay, self.moveToNextLeg, name) return Task.done def doPathLeg(self, leg, time): self.fsm.request(SuitLeg.getTypeName(leg.getType()), [leg, time]) return 0 def stopPathNow(self): name = self.taskName('move') taskMgr.remove(name) self.currentLeg = -1 def calculateHeading(self, a, b): xdelta = b[0] - a[0] ydelta = b[1] - a[1] if ydelta == 0: if xdelta > 0: return -90 else: return 90 elif xdelta == 0: if ydelta > 0: return 0 else: return 180 else: angle = math.atan2(ydelta, xdelta) return rad2Deg(angle) - 90 def beginBuildingMove(self, moveIn, doneEvent, suit = 0): doorPt = Point3(0) buildingPt = Point3(0) streetPt = Point3(0) if self.virtualPos: doorPt.assign(self.virtualPos) else: doorPt.assign(self.getPos()) if moveIn: streetPt = self.prevPointPos() else: streetPt = self.currPointPos() dx = doorPt[0] - streetPt[0] dy = doorPt[1] - streetPt[1] buildingPt = Point3(doorPt[0] + dx, doorPt[1] + dy, doorPt[2]) if moveIn: if suit: moveTime = SuitTimings.toSuitBuilding else: moveTime = SuitTimings.toToonBuilding return self.beginMove(doneEvent, buildingPt, time=moveTime) else: return self.beginMove(doneEvent, doorPt, buildingPt, time=SuitTimings.fromSuitBuilding) return None def setSPDoId(self, doId): self.spDoId = doId self.sp = self.cr.doId2do.get(doId, None) if self.sp == None and self.spDoId != 0: self.notify.warning('Suit %s created before its suit planner, %d' % (self.doId, self.spDoId)) return def d_requestBattle(self, pos, hpr): self.cr.playGame.getPlace().setState('WaitForBattle') self.sendUpdate('requestBattle', [pos[0], pos[1], pos[2], hpr[0], hpr[1], hpr[2]]) def __handleToonCollision(self, collEntry): if not base.localAvatar.wantBattles: return toonId = base.localAvatar.getDoId() self.notify.debug('Distributed suit: requesting a Battle with ' + 'toon: %d' % toonId) self.d_requestBattle(self.getPos(), self.getHpr()) self.setState('WaitForBattle') def setAnimState(self, state): self.setState(state) def enterFromSky(self, leg, time): self.enableBattleDetect('fromSky', self.__handleToonCollision) self.loop('neutral', 0) if not self.verifySuitPlanner(): return a = leg.getPosA() b = leg.getPosB() h = self.calculateHeading(a, b) self.setPosHprScale(a[0], a[1], a[2], h, 0.0, 0.0, 1.0, 1.0, 1.0) self.mtrack = self.beginSupaFlyMove(a, 1, 'fromSky') self.mtrack.start(time) def exitFromSky(self): self.disableBattleDetect() self.mtrack.finish() del self.mtrack self.detachPropeller() def enterWalkToStreet(self, leg, time): self.enableBattleDetect('walkToStreet', self.__handleToonCollision) self.loop('walk', 0) a = leg.getPosA() b = leg.getPosB() delta = Vec3(b - a) length = delta.length() delta *= (length - STAND_OUTSIDE_DOOR) / length a1 = Point3(b - delta) self.enableRaycast(1) h = self.calculateHeading(a, b) self.setHprScale(h, 0.0, 0.0, 1.0, 1.0, 1.0) self.mtrack = Sequence(LerpPosInterval(self, leg.getLegTime(), b, startPos=a1), name=self.taskName('walkToStreet')) self.mtrack.start(time) def exitWalkToStreet(self): self.disableBattleDetect() self.enableRaycast(0) self.mtrack.finish() del self.mtrack def enterWalkFromStreet(self, leg, time): self.enableBattleDetect('walkFromStreet', self.__handleToonCollision) self.loop('walk', 0) a = leg.getPosA() b = leg.getPosB() delta = Vec3(b - a) length = delta.length() delta *= (length - STAND_OUTSIDE_DOOR) / length b1 = Point3(a + delta) self.enableRaycast(1) h = self.calculateHeading(a, b) self.setHprScale(h, 0.0, 0.0, 1.0, 1.0, 1.0) self.mtrack = Sequence(LerpPosInterval(self, leg.getLegTime(), b1, startPos=a), name=self.taskName('walkFromStreet')) self.mtrack.start(time) def exitWalkFromStreet(self): self.disableBattleDetect() self.enableRaycast(0) self.mtrack.finish() del self.mtrack def enterWalk(self, leg, time): self.enableBattleDetect('bellicose', self.__handleToonCollision) self.loop('walk', 0) a = leg.getPosA() b = leg.getPosB() h = self.calculateHeading(a, b) pos = leg.getPosAtTime(time) self.setPosHprScale(pos[0], pos[1], pos[2], h, 0.0, 0.0, 1.0, 1.0, 1.0) self.mtrack = Sequence(LerpPosInterval(self, leg.getLegTime(), b, startPos=a), name=self.taskName('bellicose')) self.mtrack.start(time) def exitWalk(self): self.disableBattleDetect() self.mtrack.pause() del self.mtrack def enterToSky(self, leg, time): self.enableBattleDetect('toSky', self.__handleToonCollision) if not self.verifySuitPlanner(): return a = leg.getPosA() b = leg.getPosB() h = self.calculateHeading(a, b) self.setPosHprScale(b[0], b[1], b[2], h, 0.0, 0.0, 1.0, 1.0, 1.0) self.mtrack = self.beginSupaFlyMove(b, 0, 'toSky') self.mtrack.start(time) def exitToSky(self): self.disableBattleDetect() self.mtrack.finish() del self.mtrack self.detachPropeller() def enterFromSuitBuilding(self, leg, time): self.enableBattleDetect('fromSuitBuilding', self.__handleToonCollision) self.loop('walk', 0) if not self.verifySuitPlanner(): return a = leg.getPosA() b = leg.getPosB() delta = Vec3(b - a) length = delta.length() delta2 = delta * (self.sp.suitWalkSpeed * leg.getLegTime()) / length delta *= (length - STAND_OUTSIDE_DOOR) / length b1 = Point3(b - delta) a1 = Point3(b1 - delta2) self.enableRaycast(1) h = self.calculateHeading(a, b) self.setHprScale(h, 0.0, 0.0, 1.0, 1.0, 1.0) self.mtrack = Sequence(LerpPosInterval(self, leg.getLegTime(), b1, startPos=a1), name=self.taskName('fromSuitBuilding')) self.mtrack.start(time) def exitFromSuitBuilding(self): self.disableBattleDetect() self.mtrack.finish() del self.mtrack def enterToToonBuilding(self, leg, time): self.loop('neutral', 0) def exitToToonBuilding(self): pass def enterToSuitBuilding(self, leg, time): self.loop('walk', 0) if not self.verifySuitPlanner(): return a = leg.getPosA() b = leg.getPosB() delta = Vec3(b - a) length = delta.length() delta2 = delta * (self.sp.suitWalkSpeed * leg.getLegTime()) / length delta *= (length - STAND_OUTSIDE_DOOR) / length a1 = Point3(a + delta) b1 = Point3(a1 + delta2) self.enableRaycast(1) h = self.calculateHeading(a, b) self.setHprScale(h, 0.0, 0.0, 1.0, 1.0, 1.0) self.mtrack = Sequence(LerpPosInterval(self, leg.getLegTime(), b1, startPos=a1), name=self.taskName('toSuitBuilding')) self.mtrack.start(time) def exitToSuitBuilding(self): self.mtrack.finish() del self.mtrack def enterToCogHQ(self, leg, time): self.loop('neutral', 0) def exitToCogHQ(self): pass def enterFromCogHQ(self, leg, time): self.loop('neutral', 0) self.detachNode() def exitFromCogHQ(self): self.reparentTo(render) def enterBattle(self): DistributedSuitBase.DistributedSuitBase.enterBattle(self) self.resumePath(0) def enterNeutral(self): self.notify.debug('DistributedSuit: Neutral (entering a Door)') self.resumePath(0) self.loop('neutral', 0) def exitNeutral(self): pass def enterWaitForBattle(self): DistributedSuitBase.DistributedSuitBase.enterWaitForBattle(self) self.resumePath(0) def enterFlyAway(self): self.enableBattleDetect('flyAway', self.__handleToonCollision) if not self.verifySuitPlanner(): return b = Point3(self.getPos()) self.mtrack = self.beginSupaFlyMove(b, 0, 'flyAway') self.mtrack.start() def exitFlyAway(self): self.disableBattleDetect() self.mtrack.finish() del self.mtrack self.detachPropeller() def enterDanceThenFlyAway(self): self.enableBattleDetect('danceThenFlyAway', self.__handleToonCollision) if not self.verifySuitPlanner(): return danceTrack = self.actorInterval('victory') b = Point3(self.getPos()) flyMtrack = self.beginSupaFlyMove(b, 0, 'flyAway') self.mtrack = Sequence(danceTrack, flyMtrack, name=self.taskName('danceThenFlyAway')) self.mtrack.start() def exitDanceThenFlyAway(self): self.disableBattleDetect() self.mtrack.finish() del self.mtrack self.detachPropeller() def playCurrentDialogue(self, dialogue, chatFlags, interrupt = 1): if interrupt and self.__currentDialogue is not None: self.__currentDialogue.stop() self.__currentDialogue = dialogue if dialogue: base.playSfx(dialogue, node=self) elif chatFlags & CFSpeech != 0: if self.nametag.getNumChatPages() > 0: self.playDialogueForString(self.nametag.getChat()) if self.soundChatBubble != None: base.playSfx(self.soundChatBubble, node=self) elif self.nametag.getChatStomp() > 0: self.playDialogueForString(self.nametag.getStompText(), self.nametag.getStompDelay()) return def playDialogueForString(self, chatString, delay = 0.0): if len(chatString) == 0: return searchString = chatString.lower() if searchString.find(OTPLocalizer.DialogSpecial) >= 0: type = 'special' elif searchString.find(OTPLocalizer.DialogExclamation) >= 0: type = 'exclamation' elif searchString.find(OTPLocalizer.DialogQuestion) >= 0: type = 'question' elif random.randint(0, 1): type = 'statementA' else: type = 'statementB' stringLength = len(chatString) if stringLength <= OTPLocalizer.DialogLength1: length = 1 elif stringLength <= OTPLocalizer.DialogLength2: length = 2 elif stringLength <= OTPLocalizer.DialogLength3: length = 3 else: length = 4 self.playDialogue(type, length, delay) def playDialogue(self, type, length, delay = 0.0): dialogueArray = self.getDialogueArray() if dialogueArray == None: return sfxIndex = None if type == 'statementA' or type == 'statementB': if length == 1: sfxIndex = 0 elif length == 2: sfxIndex = 1 elif length >= 3: sfxIndex = 2 elif type == 'question': sfxIndex = 3 elif type == 'exclamation': sfxIndex = 4 elif type == 'special': sfxIndex = 5 else: notify.error('unrecognized dialogue type: ', type) if sfxIndex != None and sfxIndex < len(dialogueArray) and dialogueArray[sfxIndex] != None: soundSequence = Sequence(Wait(delay), SoundInterval(dialogueArray[sfxIndex], node=None, listenerNode=base.localAvatar, loop=0, volume=1.0)) self.soundSequenceList.append(soundSequence) soundSequence.start() self.cleanUpSoundList() return def cleanUpSoundList(self): removeList = [] for soundSequence in self.soundSequenceList: if soundSequence.isStopped(): removeList.append(soundSequence) for soundSequence in removeList: self.soundSequenceList.remove(soundSequence)