historical/toontown-classic.git/toontown/hood/InteractiveAnimatedProp.py
2024-01-16 11:20:27 -06:00

508 lines
20 KiB
Python

import math
import random
import GenericAnimatedProp
from direct.actor import Actor
from direct.interval.IntervalGlobal import Sequence, ActorInterval, Wait, Func, SoundInterval, Parallel
from direct.fsm import FSM
from direct.showbase.PythonUtil import weightedChoice
from panda3d.core import TextNode, Vec3
from toontown.toonbase import ToontownGlobals
from toontown.hood import ZoneUtil
def clearPythonIvals(ival):
if hasattr(ival, 'function'):
ival.function = None
if hasattr(ival, 'pythonIvals'):
for oneIval in ival.pythonIvals:
clearPythonIvals(oneIval)
ival.pythonIvals = []
return
class InteractiveAnimatedProp(GenericAnimatedProp.GenericAnimatedProp, FSM.FSM):
ZoneToIdles = {}
ZoneToIdleIntoFightAnims = {}
ZoneToFightAnims = {}
ZoneToVictoryAnims = {}
ZoneToSadAnims = {}
IdlePauseTime = base.config.GetFloat('prop-idle-pause-time', 0.0)
HpTextGenerator = TextNode('HpTextGenerator')
BattleCheerText = '+'
def __init__(self, node, holidayId = -1):
FSM.FSM.__init__(self, 'InteractiveProp-%s' % str(node))
self.holidayId = holidayId
self.numIdles = 0
self.numFightAnims = 0
self.idleInterval = None
self.battleCheerInterval = None
self.sadInterval = None
self.victoryInterval = None
self.lastIdleAnimName = ''
self.lastIdleTime = 0
self.curIval = None
self.okToStartNextAnim = False
cellIndexStr = node.getTag('DNACellIndex')
self.cellIndex = ord(cellIndexStr)
self.origAnimNameToSound = {}
self.lastPlayingAnimPhase = 0
self.buildingsMakingMeSad = set()
GenericAnimatedProp.GenericAnimatedProp.__init__(self, node)
return
def delete(self):
self.exit()
GenericAnimatedProp.GenericAnimatedProp.delete(self)
self.idleInterval = None
self.battleCheerInterval = None
self.sadInterval = None
self.victoryInterval = None
return
def getCellIndex(self):
return self.cellIndex
def playBattleCheerAnim(self):
self.node.loop('battleCheer')
def setupActor(self, node):
if self.hoodId in self.ZoneToIdles:
self.numIdles = len(self.ZoneToIdles[self.hoodId])
if self.hoodId in self.ZoneToFightAnims:
self.numFightAnims = len(self.ZoneToFightAnims[self.hoodId])
self.idleInterval = None
anim = node.getTag('DNAAnim')
self.trashcan = Actor.Actor(node, copy=0)
self.trashcan.reparentTo(node)
animDict = {}
animDict['anim'] = '%s/%s' % (self.path, anim)
for i in xrange(self.numIdles):
baseAnim = self.ZoneToIdles[self.hoodId][i]
if isinstance(baseAnim, tuple):
baseAnim = baseAnim[0]
animStr = self.path + '/' + baseAnim
animKey = 'idle%d' % i
animDict[animKey] = animStr
settleName = self.getSettleName(i)
if settleName:
settleStr = self.path + '/' + settleName
settleKey = 'settle%d' % i
animDict[settleKey] = settleStr
for i in xrange(self.numFightAnims):
animStr = self.path + '/' + self.ZoneToFightAnims[self.hoodId][i]
animKey = 'fight%d' % i
animDict[animKey] = animStr
if self.hoodId in self.ZoneToIdleIntoFightAnims:
animStr = self.path + '/' + self.ZoneToIdleIntoFightAnims[self.hoodId]
animKey = 'idleIntoFight'
animDict[animKey] = animStr
if self.hoodId in self.ZoneToIdleIntoFightAnims:
animStr = self.path + '/' + self.ZoneToVictoryAnims[self.hoodId]
animKey = 'victory'
animDict[animKey] = animStr
if self.hoodId in self.ZoneToSadAnims:
animStr = self.path + '/' + self.ZoneToSadAnims[self.hoodId]
animKey = 'sad'
animDict[animKey] = animStr
self.trashcan.loadAnims(animDict)
self.trashcan.pose('anim', 0)
self.node = self.trashcan
self.idleInterval = self.createIdleInterval()
self.battleCheerInterval = self.createBattleCheerInterval()
self.victoryInterval = self.createVictoryInterval()
self.sadInterval = self.createSadInterval()
return
def createIdleInterval(self):
result = Sequence()
if self.numIdles >= 3:
numberOfAnimsAbove2 = self.numIdles - 2
for rareIdle in xrange(2, self.numIdles):
for i in xrange(2):
result.append(ActorInterval(self.node, 'idle0'))
result.append(Wait(self.IdlePauseTime))
result.append(ActorInterval(self.node, 'idle1'))
result.append(Wait(self.IdlePauseTime))
result.append(ActorInterval(self.node, 'idle%d' % rareIdle))
result.append(Wait(self.IdlePauseTime))
else:
for i in xrange(self.numIdles):
result.append(ActorInterval(self.node, 'idle%d' % i))
self.notify.debug('idle interval=%s' % result)
return result
def createBattleCheerText(self):
self.HpTextGenerator.setFont(ToontownGlobals.getSignFont())
self.HpTextGenerator.setText(self.BattleCheerText)
self.HpTextGenerator.clearShadow()
self.HpTextGenerator.setAlign(TextNode.ACenter)
r = 0
g = 0
b = 1
a = 1
self.HpTextGenerator.setTextColor(r, g, b, a)
self.hpTextNode = self.HpTextGenerator.generate()
self.hpText = self.node.attachNewNode(self.hpTextNode)
self.hpText.setScale(1)
self.hpText.setBillboardPointEye()
self.hpText.setBin('fixed', 100)
self.hpText.setPos(0, 0, 4)
self.hpText.hide()
def createBattleCheerInterval(self):
result = Sequence()
for i in xrange(self.numFightAnims):
animKey = 'fight%d' % i
animAndSoundIval = self.createAnimAndSoundIval(animKey)
origAnimName = self.node.getAnimFilename(animKey).split('/')[-1]
if self.hasOverrideIval(origAnimName):
result.append(self.getOverrideIval(origAnimName))
elif self.hasSpecialIval(origAnimName):
result.append(Parallel(animAndSoundIval, self.getSpecialIval(origAnimName)))
else:
result.append(animAndSoundIval)
self.createBattleCheerText()
battleCheerTextIval = Sequence(Func(self.hpText.show), self.hpText.posInterval(duration=4.0, pos=Vec3(0, 0, 7), startPos=(0, 0, 3)), Func(self.hpText.hide))
ivalWithText = Parallel(battleCheerTextIval, result)
return ivalWithText
def createSadInterval(self):
result = Sequence()
if self.hoodId in self.ZoneToSadAnims:
result = self.createAnimAndSoundIval('sad')
return result
def hasSpecialIval(self, origAnimName):
return False
def getSpecialIval(self, origAnimName):
return Sequence()
def hasOverrideIval(self, origAnimName):
return False
def getOverrideIval(self, origAnimName):
return Sequence()
def createVictoryInterval(self):
result = Sequence()
if self.hoodId in self.ZoneToVictoryAnims:
animAndSoundIval = self.createAnimAndSoundIval('victory')
result.append(animAndSoundIval)
return result
def enter(self):
GenericAnimatedProp.GenericAnimatedProp.enter(self)
if base.config.GetBool('props-buff-battles', True):
self.notify.debug('props buff battles is true')
if base.cr.newsManager.isHolidayRunning(self.holidayId):
self.notify.debug('holiday is running, doing idle interval')
self.node.stop()
self.node.pose('idle0', 0)
if base.config.GetBool('interactive-prop-random-idles', 1):
self.requestIdleOrSad()
else:
self.idleInterval.loop()
else:
self.notify.debug('holiday is NOT running, doing nothing')
self.node.stop()
self.node.pose('idle0', 0)
else:
self.notify.debug('props do not buff battles')
self.node.stop()
self.node.pose('idle0', 0)
def exit(self):
self.okToStartNextAnim = False
self.notify.debug('%s %d okToStartNextAnim=%s' % (self, self.visId, self.okToStartNextAnim))
GenericAnimatedProp.GenericAnimatedProp.exit(self)
self.request('Off')
def requestIdleOrSad(self):
if not hasattr(self, 'node') or not self.node:
self.notify.warning("requestIdleOrSad returning hasattr(self,'node')=%s" % hasattr(self, 'node'))
return
if self.buildingsMakingMeSad:
self.request('Sad')
else:
self.request('DoIdleAnim')
def enterDoIdleAnim(self):
self.notify.debug('enterDoIdleAnim numIdels=%d' % self.numIdles)
self.okToStartNextAnim = True
self.notify.debug('%s %d okToStartNextAnim=%s' % (self, self.visId, self.okToStartNextAnim))
self.startNextIdleAnim()
def exitDoIdleAnim(self):
self.notify.debug('exitDoIdlesAnim numIdles=%d' % self.numIdles)
self.okToStartNextAnim = False
self.notify.debug('%s %d okToStartNextAnim=%s' % (self, self.visId, self.okToStartNextAnim))
self.calcLastIdleFrame()
self.clearCurIval()
def calcLastIdleFrame(self):
if self.curIval and self.curIval.ivals:
firstIval = self.curIval.ivals[0]
if isinstance(firstIval, ActorInterval):
self.lastIdleFrame = firstIval.getCurrentFrame()
self.lastIdleAnimName = firstIval.animName
elif isinstance(firstIval, Parallel):
for testIval in firstIval.ivals:
if isinstance(firstIval, ActorInterval):
self.lastIdleTime = testIval.getT()
self.lastIdleAnimName = testIval.animName
break
def chooseIdleAnimToRun(self):
result = self.numIdles - 1
if base.config.GetBool('randomize-interactive-idles', True):
pairs = []
for i in xrange(self.numIdles):
reversedChance = self.numIdles - i - 1
pairs.append((math.pow(2, reversedChance), i))
sum = math.pow(2, self.numIdles) - 1
result = weightedChoice(pairs, sum=sum)
self.notify.debug('chooseAnimToRun numIdles=%s pairs=%s result=%s' % (self.numIdles, pairs, result))
else:
result = self.lastPlayingAnimPhase + 1
if result >= len(self.ZoneToIdles[self.hoodId]):
result = 0
return result
def startNextIdleAnim(self):
self.notify.debug('startNextAnim self.okToStartNextAnim=%s' % self.okToStartNextAnim)
if not hasattr(self, 'node') or not self.node:
self.notify.warning("startNextIdleAnim returning hasattr(self,'node')=%s" % hasattr(self, 'node'))
return
self.curIval = None
if self.okToStartNextAnim:
self.notify.debug('got pass okToStartNextAnim')
whichAnim = self.chooseIdleAnimToRun()
if self.visId == localAvatar.zoneId:
self.notify.debug('whichAnim=%s' % whichAnim)
if __dev__:
self.notify.info('whichAnim=%s %s' % (whichAnim, self.getOrigIdleAnimName(whichAnim)))
self.lastPlayingAnimPhase = whichAnim
self.curIval = self.createIdleAnimSequence(whichAnim)
self.notify.debug('starting curIval of length %s' % self.curIval.getDuration())
self.curIval.start()
else:
self.curIval = Wait(10)
self.notify.debug('false self.okToStartNextAnim=%s' % self.okToStartNextAnim)
return
def createIdleAnimAndSoundInterval(self, whichIdleAnim, startingTime = 0):
animIval = self.node.actorInterval('idle%d' % whichIdleAnim, startTime=startingTime)
animIvalDuration = animIval.getDuration()
origAnimName = self.ZoneToIdles[self.hoodId][whichIdleAnim]
if isinstance(origAnimName, tuple):
origAnimName = origAnimName[0]
soundIval = self.createSoundInterval(origAnimName, animIvalDuration)
soundIvalDuration = soundIval.getDuration()
if self.hasSpecialIval(origAnimName):
specialIval = self.getSpecialIval(origAnimName)
idleAnimAndSound = Parallel(animIval, soundIval, specialIval)
else:
idleAnimAndSound = Parallel(animIval, soundIval)
return idleAnimAndSound
def createIdleAnimSequence(self, whichIdleAnim):
dummyResult = Sequence(Wait(self.IdlePauseTime))
if not hasattr(self, 'node') or not self.node:
self.notify.warning("createIdleAnimSequence returning dummyResult hasattr(self,'node')=%s" % hasattr(self, 'node'))
return dummyResult
idleAnimAndSound = self.createIdleAnimAndSoundInterval(whichIdleAnim)
result = Sequence(idleAnimAndSound, Wait(self.IdlePauseTime), Func(self.startNextIdleAnim))
if isinstance(self.ZoneToIdles[self.hoodId][whichIdleAnim], tuple) and len(self.ZoneToIdles[self.hoodId][whichIdleAnim]) > 2:
info = self.ZoneToIdles[self.hoodId][whichIdleAnim]
origAnimName = info[0]
minLoop = info[1]
maxLoop = info[2]
settleAnim = info[3]
minPauseTime = info[4]
maxPauseTime = info[5]
numberOfLoops = random.randrange(minLoop, maxLoop + 1)
pauseTime = random.randrange(minPauseTime, maxPauseTime + 1)
result = Sequence()
for i in xrange(numberOfLoops):
result.append(idleAnimAndSound)
if self.getSettleName(whichIdleAnim):
result.append(self.node.actorInterval('settle%d' % whichIdleAnim))
result.append(Wait(pauseTime))
result.append(Func(self.startNextIdleAnim))
return result
def gotoFaceoff(self):
self.notify.debugStateCall(self)
if base.cr.newsManager.isHolidayRunning(self.holidayId):
self.request('Faceoff')
else:
self.notify.debug('not going to faceoff because holiday %d is not running' % self.holidayId)
def gotoBattleCheer(self):
self.notify.debugStateCall(self)
if base.cr.newsManager.isHolidayRunning(self.holidayId):
self.request('BattleCheer')
else:
self.notify.debug('not going to battleCheer because holiday %d is not running' % self.holidayId)
def gotoIdle(self):
self.notify.debugStateCall(self)
if base.cr.newsManager.isHolidayRunning(self.holidayId):
self.request('DoIdleAnim')
else:
self.notify.debug('not going to idle because holiday %d is not running' % self.holidayId)
def gotoVictory(self):
self.notify.debugStateCall(self)
if base.cr.newsManager.isHolidayRunning(self.holidayId):
self.request('Victory')
else:
self.notify.debug('not going to victory because holiday %d is not running' % self.holidayId)
def gotoSad(self, buildingDoId):
self.notify.debugStateCall(self)
self.buildingsMakingMeSad.add(buildingDoId)
if base.cr.newsManager.isHolidayRunning(self.holidayId):
self.request('Sad')
else:
self.notify.debug('not going to sad because holiday %d is not running' % self.holidayId)
def buildingLiberated(self, buildingDoId):
self.buildingsMakingMeSad.discard(buildingDoId)
if not self.buildingsMakingMeSad:
self.gotoIdle()
def enterFaceoff(self):
self.notify.debugStateCall(self)
self.curIval = self.createFaceoffInterval()
self.curIval.start()
def exitFaceoff(self):
self.notify.debugStateCall(self)
self.curIval.pause()
self.curIval = None
return
def calcWhichIdleAnim(self, animName):
result = 0
info = self.ZoneToIdles[self.hoodId]
for index, curInfo in enumerate(info):
if isinstance(curInfo, tuple):
if curInfo[0] == animName:
result = index
break
elif isinstance(curInfo, str):
if curInfo == animName:
result = index
breal
return result
def createFaceoffInterval(self):
result = Sequence()
if self.lastIdleAnimName:
whichIdleAnim = self.calcWhichIdleAnim(self.lastIdleAnimName)
animAndSound = self.createIdleAnimAndSoundInterval(whichIdleAnim, self.lastIdleTime)
result.append(animAndSound)
idleIntoFightIval = self.createAnimAndSoundIval('idleIntoFight')
result.append(idleIntoFightIval)
result.append(Func(self.gotoBattleCheer))
return result
def enterBattleCheer(self):
self.notify.debugStateCall(self)
self.curIval = self.battleCheerInterval
if self.curIval:
self.curIval.loop()
def exitBattleCheer(self):
self.notify.debugStateCall(self)
if self.curIval:
self.curIval.finish()
self.curIval = None
return
def enterVictory(self):
self.notify.debugStateCall(self)
self.curIval = self.victoryInterval
if self.curIval:
self.curIval.loop()
def exitVictory(self):
self.notify.debugStateCall(self)
if self.curIval:
self.curIval.finish()
self.curIval = None
return
def enterSad(self):
self.notify.debugStateCall(self)
self.curIval = self.sadInterval
if self.curIval:
self.curIval.loop()
def exitSad(self):
self.notify.debugStateCall(self)
if self.curIval:
self.curIval.finish()
self.curIval = None
return
def getSettleName(self, whichIdleAnim):
result = None
if isinstance(self.ZoneToIdles[self.hoodId][whichIdleAnim], tuple) and len(self.ZoneToIdles[self.hoodId][whichIdleAnim]) > 3:
result = self.ZoneToIdles[self.hoodId][whichIdleAnim][3]
return result
def getOrigIdleAnimName(self, whichIdleAnim):
result = None
if isinstance(self.ZoneToIdles[self.hoodId][whichIdleAnim], tuple):
result = self.ZoneToIdles[self.hoodId][whichIdleAnim][0]
else:
result = self.ZoneToIdles[self.hoodId][whichIdleAnim]
return result
def createAnimAndSoundIval(self, animKey):
animIval = self.node.actorInterval(animKey)
animIvalDuration = animIval.getDuration()
origAnimName = self.node.getAnimFilename(animKey)
soundIval = self.createSoundInterval(origAnimName, animIvalDuration)
soundIvalDuration = soundIval.getDuration()
printFunc = Func(self.printAnimIfClose, animKey)
if self.hasSpecialIval(origAnimName):
specialIval = self.getSpecialIval(origAnimName)
idleAnimAndSound = Parallel(animIval, soundIval, specialIval)
if base.config.GetBool('interactive-prop-info', False):
idleAnimAndSound.append(printFunc)
else:
idleAnimAndSound = Parallel(animIval, soundIval)
if base.config.GetBool('interactive-prop-info', False):
idleAnimAndSound.append(printFunc)
return idleAnimAndSound
def printAnimIfClose(self, animKey):
if base.config.GetBool('interactive-prop-info', False):
try:
animName = self.node.getAnimFilename(animKey)
baseAnimName = animName.split('/')[-1]
if localAvatar.zoneId == self.visId:
self.notify.info('playing %s' % baseAnimName)
except Exception, e:
self.notify.warning('Unknown error in printAnimIfClose, giving up:\n%s' % str(e))
def clearCurIval(self):
if self.curIval:
self.curIval.finish()
clearPythonIvals(self.curIval)
self.curIval = None
return