from pandac.PandaModules 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 import Suit from toontown.toonbase import ToontownGlobals from toontown.battle import DistributedBattle from direct.fsm import ClassicFSM, State from direct.fsm import State import SuitTimings import SuitBase import DistributedSuitPlanner from direct.directnotify import DirectNotifyGlobal import SuitDialog from toontown.battle import BattleProps from toontown.distributed.DelayDeletable import DelayDeletable import math import copy 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)