oldschool-toontown/toontown/suit/DistributedSuit.py

699 lines
25 KiB
Python
Raw Normal View History

from panda3d.core import *
from panda3d.otp import *
from panda3d.toontown import *
2019-11-02 17:27:54 -05:00
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
2019-11-02 17:27:54 -05:00
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
2019-11-02 17:27:54 -05:00
from direct.directnotify import DirectNotifyGlobal
from . import SuitDialog
2019-11-02 17:27:54 -05:00
from toontown.battle import BattleProps
from toontown.distributed.DelayDeletable import DelayDeletable
import math
import copy
from . import DistributedSuitBase
2019-11-02 17:27:54 -05:00
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)
2019-11-02 17:27:54 -05:00
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))
2019-11-02 17:27:54 -05:00
return
if self.legList == None:
print("%s (%d) doesn't have a legList yet." % (self.getName(), self.getDoId()))
2019-11-02 17:27:54 -05:00
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.')
2019-11-02 17:27:54 -05:00
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(),
2019-11-02 17:27:54 -05:00
self.getDoId(),
self.fsm.getCurrentState().getName(),
diff))
print('Should be at (%0.2f, %0.2f), but is at (%0.2f, %0.2f).' % (x,
2019-11-02 17:27:54 -05:00
y,
localPos[0],
localPos[1]))
2019-11-02 17:27:54 -05:00
return
print('%s (%d) is in the correct position.' % (self.getName(), self.getDoId()))
2019-11-02 17:27:54 -05:00
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)