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 pandac.PandaModules 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): FSM.FSM.__init__(self, 'InteractiveProp-%s' % str(node)) 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) def delete(self): self.exit() GenericAnimatedProp.GenericAnimatedProp.delete(self) self.idleInterval = None self.battleCheerInterval = None self.sadInterval = None self.victoryInterval = None 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() 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') self.node.stop() self.node.pose('idle0', 0) self.idleInterval.loop() 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) self.request('Faceoff') def gotoBattleCheer(self): self.notify.debugStateCall(self) self.request('BattleCheer') def gotoIdle(self): self.notify.debugStateCall(self) self.request('DoIdleAnim') def gotoVictory(self): self.notify.debugStateCall(self) self.request('Victory') def gotoSad(self, buildingDoId): self.notify.debugStateCall(self) self.buildingsMakingMeSad.add(buildingDoId) self.request('Sad') 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 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