historical/toontown-just-works.git/toontown/suit/BossCog.py
2024-01-16 11:20:27 -06:00

478 lines
21 KiB
Python

from panda3d.core import *
from direct.actor import Actor
from direct.directnotify import DirectNotifyGlobal
from direct.fsm import FSM
from direct.interval.IntervalGlobal import *
from direct.task.Task import Task
from otp.avatar import Avatar
from otp.nametag.NametagGroup import NametagGroup
from otp.nametag.NametagConstants import *
from toontown.battle import BattleParticles, BattleProps
from toontown.toonbase import TTLocalizer, ToontownGlobals
import Suit, SuitDNA, SuitHealthBar
import types, random
GenericModel = 'phase_9/models/char/bossCog'
ModelDict = {'s': 'phase_9/models/char/sellbotBoss',
'm': 'phase_10/models/char/cashbotBoss',
'l': 'phase_11/models/char/lawbotBoss',
'c': 'phase_12/models/char/bossbotBoss'}
AnimList = ('Ff_speech', 'ltTurn2Wave', 'wave', 'Ff_lookRt', 'turn2Fb', 'Ff_neutral', 'Bb_neutral', 'Ff2Bb_spin', 'Bb2Ff_spin', 'Fb_neutral', 'Bf_neutral', 'Fb_firstHit', 'Fb_downNeutral', 'Fb_downHit', 'Fb_fall', 'Fb_down2Up', 'Fb_downLtSwing', 'Fb_downRtSwing', 'Fb_DownThrow', 'Fb_UpThrow', 'Fb_jump', 'golf_swing')
class BossCog(Avatar.Avatar):
notify = DirectNotifyGlobal.directNotify.newCategory('BossCog')
def __init__(self):
Avatar.Avatar.__init__(self)
self.setFont(ToontownGlobals.getSuitFont())
self.nametag.setSpeechFont(ToontownGlobals.getSuitFont())
self.setPlayerType(NametagGroup.CCSuit)
self.setPickable(0)
self.doorA = None
self.doorB = None
self.bubbleL = None
self.bubbleR = None
self.raised = 1
self.forward = 1
self.happy = 1
self.dizzy = 0
self.nowRaised = 1
self.nowForward = 1
self.nowHappy = 1
self.currentAnimIval = None
self.queuedAnimIvals = []
self.treadsLeftPos = 0
self.treadsRightPos = 0
self.healthBar = SuitHealthBar.SuitHealthBar()
self.animDoneEvent = 'BossCogAnimDone'
self.animIvalName = 'BossCogAnimIval'
self.warningSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_GOON_tractor_beam_alarmed.ogg')
def delete(self):
Avatar.Avatar.delete(self)
self.healthBar.delete()
self.setDizzy(0)
self.stopAnimate()
if self.doorA:
self.doorA.request('Off')
self.doorB.request('Off')
self.doorA = None
self.doorB = None
def setDNAString(self, dnaString):
self.dna = SuitDNA.SuitDNA()
self.dna.makeFromNetString(dnaString)
self.setDNA(self.dna)
def setDNA(self, dna):
if self.style:
pass
else:
self.style = dna
self.generateBossCog()
self.initializeDropShadow()
if base.wantNametags:
self.initializeNametag3d()
def generateBossCog(self):
self.throwSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_frisbee_gears.ogg')
self.swingSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_swipe.ogg')
self.spinSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_spin.ogg')
self.rainGearsSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_raining_gears.ogg')
self.swishSfx = loader.loadSfx('phase_5/audio/sfx/General_throw_miss.ogg')
self.boomSfx = loader.loadSfx('phase_3.5/audio/sfx/ENC_cogfall_apart_%s.ogg' % random.randint(1, 6))
self.deathSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_big_death.ogg')
self.upSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_raise_up.ogg')
self.downSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_collapse.ogg')
self.reelSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_reeling_backwards.ogg')
self.birdsSfx = loader.loadSfx('phase_4/audio/sfx/SZ_TC_bird1.ogg')
self.dizzyAlert = loader.loadSfx('phase_5/audio/sfx/AA_sound_aoogah.ogg')
self.grunt = loader.loadSfx('phase_9/audio/sfx/Boss_COG_VO_grunt.ogg')
self.murmur = loader.loadSfx('phase_9/audio/sfx/Boss_COG_VO_murmur.ogg')
self.statement = loader.loadSfx('phase_9/audio/sfx/Boss_COG_VO_statement.ogg')
self.question = loader.loadSfx('phase_9/audio/sfx/Boss_COG_VO_question.ogg')
self.dialogArray = [self.grunt,
self.murmur,
self.statement,
self.question,
self.statement,
self.statement]
dna = self.style
filePrefix = ModelDict[dna.dept]
self.loadModel(GenericModel + '-legs-zero', 'legs')
self.loadModel(filePrefix + '-torso-zero', 'torso')
self.loadModel(filePrefix + '-head-zero', 'head')
self.twoFaced = dna.dept == 's'
self.attach('head', 'torso', 'joint34')
self.attach('torso', 'legs', 'joint_pelvis')
self.rotateNode = self.attachNewNode('rotate')
geomNode = self.getGeomNode()
geomNode.reparentTo(self.rotateNode)
self.frontAttack = self.rotateNode.attachNewNode('frontAttack')
self.frontAttack.setPos(0, -10, 10)
self.frontAttack.setScale(2)
self.setHeight(26)
self.nametag3d.setScale(2)
for partName in ('legs', 'torso', 'head'):
animDict = {}
for anim in AnimList:
animDict[anim] = '%s-%s-%s' % (GenericModel, partName, anim)
self.loadAnims(animDict, partName)
self.stars = BattleProps.globalPropPool.getProp('stun')
self.stars.setPosHprScale(7, 0, 0, 0, 0, -90, 3, 3, 3)
self.stars.loop('stun')
self.pelvis = self.getPart('torso')
self.pelvisForwardHpr = VBase3(0, 0, 0)
self.pelvisReversedHpr = VBase3(-180, 0, 0)
self.neck = self.getPart('head')
self.neckForwardHpr = VBase3(0, 0, 0)
self.neckReversedHpr = VBase3(0, -540, 0)
self.axle = self.find('**/joint_axle')
self.doorA = self.__setupDoor('**/joint_doorFront', 'doorA', self.doorACallback, VBase3(0, 0, 0), VBase3(0, 0, -80), CollisionPolygon(Point3(5, -4, 0.32), Point3(0, -4, 0), Point3(0, 4, 0), Point3(5, 4, 0.32)))
self.doorB = self.__setupDoor('**/joint_doorRear', 'doorB', self.doorBCallback, VBase3(0, 0, 0), VBase3(0, 0, 80), CollisionPolygon(Point3(-5, 4, 0.84), Point3(0, 4, 0), Point3(0, -4, 0), Point3(-5, -4, 0.84)))
treadsModel = loader.loadModel('%s-treads' % GenericModel)
treadsModel.reparentTo(self.axle)
self.treadsLeft = treadsModel.find('**/right_tread')
self.treadsRight = treadsModel.find('**/left_tread')
self.doorA.request('Closed')
self.doorB.request('Closed')
def initializeBodyCollisions(self, collIdStr):
Avatar.Avatar.initializeBodyCollisions(self, collIdStr)
if not self.ghostMode:
self.collNode.setCollideMask(self.collNode.getIntoCollideMask() | ToontownGlobals.PieBitmask)
def generateHealthBar(self):
self.healthBar.generate()
self.healthBar.geom.reparentTo(self.find('**/joint_lifeMeter'))
self.healthBar.geom.setScale(6.0)
self.healthBar.geom.setHpr(0, -20, 0)
self.healthBar.geom.show()
def updateHealthBar(self):
if not self.healthBar:
return
self.healthBar.update(1.0 - float(self.bossDamage) / float(self.bossMaxDamage))
def reverseHead(self):
self.neck.setHpr(self.neckReversedHpr)
def forwardHead(self):
self.neck.setHpr(self.neckForwardHpr)
def reverseBody(self):
self.pelvis.setHpr(self.pelvisReversedHpr)
def forwardBody(self):
self.pelvis.setHpr(self.pelvisForwardHpr)
def getShadowJoint(self):
return self.getGeomNode()
def getNametagJoints(self):
return []
def getDialogueArray(self):
return self.dialogArray
def doorACallback(self, isOpen):
pass
def doorBCallback(self, isOpen):
pass
def __rollTreadsInterval(self, object, start = 0, duration = 0, rate = 1):
def rollTexMatrix(t, object = object):
object.setTexOffset(TextureStage.getDefault(), t, 0)
return LerpFunctionInterval(rollTexMatrix, fromData=start, toData=start + rate * duration, duration=duration)
def rollLeftTreads(self, duration, rate):
start = self.treadsLeftPos
self.treadsLeftPos += duration * rate
return self.__rollTreadsInterval(self.treadsLeft, start=start, duration=duration, rate=rate)
def rollRightTreads(self, duration, rate):
start = self.treadsRightPos
self.treadsRightPos += duration * rate
return self.__rollTreadsInterval(self.treadsRight, start=start, duration=duration, rate=rate)
class DoorFSM(FSM.FSM):
def __init__(self, name, animate, callback, openedHpr, closedHpr, uniqueName):
FSM.FSM.__init__(self, name)
self.animate = animate
self.callback = callback
self.openedHpr = openedHpr
self.closedHpr = closedHpr
self.uniqueName = uniqueName
self.ival = 0
self.openSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_door_open.ogg')
self.closeSfx = loader.loadSfx('phase_9/audio/sfx/CHQ_VP_door_close.ogg')
self.request('Closed')
def filterOpening(self, request, args):
if request == 'close':
return 'Closing'
return self.defaultFilter(request, args)
def enterOpening(self):
intervalName = self.uniqueName('open-%s' % self.animate.getName())
self.callback(0)
ival = Parallel(SoundInterval(self.openSfx, node=self.animate, volume=0.2), self.animate.hprInterval(1, self.openedHpr, blendType='easeInOut'), Sequence(Wait(0.2), Func(self.callback, 1)), name=intervalName)
ival.start()
self.ival = ival
def exitOpening(self):
self.ival.pause()
self.ival = None
return
def filterOpened(self, request, args):
if request == 'close':
return 'Closing'
return self.defaultFilter(request, args)
def enterOpened(self):
self.animate.setHpr(self.openedHpr)
self.callback(1)
def filterClosing(self, request, args):
if request == 'open':
return 'Opening'
return self.defaultFilter(request, args)
def enterClosing(self):
intervalName = self.uniqueName('close-%s' % self.animate.getName())
self.callback(1)
ival = Parallel(SoundInterval(self.closeSfx, node=self.animate, volume=0.2), self.animate.hprInterval(1, self.closedHpr, blendType='easeInOut'), Sequence(Wait(0.8), Func(self.callback, 0)), name=intervalName)
ival.start()
self.ival = ival
def exitClosing(self):
self.ival.pause()
self.ival = None
return
def filterClosed(self, request, args):
if request == 'open':
return 'Opening'
return self.defaultFilter(request, args)
def enterClosed(self):
self.animate.setHpr(self.closedHpr)
self.callback(0)
def __setupDoor(self, jointName, name, callback, openedHpr, closedHpr, cPoly):
joint = self.find(jointName)
children = joint.getChildren()
animate = joint.attachNewNode(name)
children.reparentTo(animate)
cnode = CollisionNode('BossZap')
cnode.setCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.WallBitmask | ToontownGlobals.CameraBitmask)
cnode.addSolid(cPoly)
animate.attachNewNode(cnode)
fsm = self.DoorFSM(name, animate, callback, openedHpr, closedHpr, self.uniqueName)
return fsm
def doAnimate(self, anim = None, now = 0, queueNeutral = 1, raised = None, forward = None, happy = None):
if now:
self.stopAnimate()
if not self.twoFaced:
happy = 1
if raised == None:
raised = self.raised
if forward == None:
forward = self.forward
if happy == None:
happy = self.happy
if now:
self.raised = raised
self.forward = forward
self.happy = happy
if self.currentAnimIval == None:
self.accept(self.animDoneEvent, self.__getNextAnim)
else:
queueNeutral = 0
ival, changed = self.__getAnimIval(anim, raised, forward, happy)
if changed or queueNeutral:
self.queuedAnimIvals.append((ival,
self.raised,
self.forward,
self.happy))
if self.currentAnimIval == None:
self.__getNextAnim()
return
def stopAnimate(self):
self.ignore(self.animDoneEvent)
self.queuedAnimIvals = []
if self.currentAnimIval:
self.currentAnimIval.setDoneEvent('')
self.currentAnimIval.finish()
self.currentAnimIval = None
self.raised = self.nowRaised
self.forward = self.nowForward
self.happy = self.nowHappy
return
def __getNextAnim(self):
if self.queuedAnimIvals:
ival, raised, forward, happy = self.queuedAnimIvals[0]
del self.queuedAnimIvals[0]
else:
ival, changed = self.__getAnimIval(None, self.raised, self.forward, self.happy)
raised = self.raised
forward = self.forward
happy = self.happy
if self.currentAnimIval:
self.currentAnimIval.setDoneEvent('')
self.currentAnimIval.finish()
self.currentAnimIval = ival
self.currentAnimIval.start()
self.nowRaised = raised
self.nowForward = forward
self.nowHappy = happy
return
def __getAnimIval(self, anim, raised, forward, happy):
ival, changed = self.__doGetAnimIval(anim, raised, forward, happy)
seq = Sequence(ival, name=self.animIvalName)
seq.setDoneEvent(self.animDoneEvent)
return (seq, changed)
def __doGetAnimIval(self, anim, raised, forward, happy):
if raised == self.raised and forward == self.forward and happy == self.happy:
return (self.getAnim(anim), anim != None)
startsHappy = self.happy
endsHappy = self.happy
ival = Sequence()
if raised and not self.raised:
upIval = self.getAngryActorInterval('Fb_down2Up')
if self.forward:
ival = upIval
else:
ival = Sequence(Func(self.reverseBody), upIval, Func(self.forwardBody))
ival = Parallel(SoundInterval(self.upSfx, node=self), ival)
if forward != self.forward:
if forward:
animName = 'Bb2Ff_spin'
else:
animName = 'Ff2Bb_spin'
ival = Sequence(ival, ActorInterval(self, animName))
startsHappy = 1
endsHappy = 1
startNeckHpr = self.neckForwardHpr
endNeckHpr = self.neckForwardHpr
if self.happy != startsHappy:
startNeckHpr = self.neckReversedHpr
if happy != endsHappy:
endNeckHpr = self.neckReversedHpr
if startNeckHpr != endNeckHpr:
ival = Sequence(Func(self.neck.setHpr, startNeckHpr), ParallelEndTogether(ival, Sequence(self.neck.hprInterval(0.5, endNeckHpr, startHpr=startNeckHpr, blendType='easeInOut'), Func(self.neck.setHpr, self.neckForwardHpr))))
elif endNeckHpr != self.neckForwardHpr:
ival = Sequence(Func(self.neck.setHpr, startNeckHpr), ival, Func(self.neck.setHpr, self.neckForwardHpr))
if not raised and self.raised:
downIval = self.getAngryActorInterval('Fb_down2Up', playRate=-1)
if forward:
ival = Sequence(ival, downIval)
else:
ival = Sequence(ival, Func(self.reverseBody), downIval, Func(self.forwardBody))
ival = Parallel(SoundInterval(self.downSfx, node=self), ival)
self.raised = raised
self.forward = forward
self.happy = happy
if anim != None:
ival = Sequence(ival, self.getAnim(anim))
return (ival, 1)
def setDizzy(self, dizzy):
if dizzy and not self.dizzy:
base.playSfx(self.dizzyAlert)
self.dizzy = dizzy
if dizzy:
self.stars.reparentTo(self.neck)
base.playSfx(self.birdsSfx, looping=1)
else:
self.stars.detachNode()
self.birdsSfx.stop()
def getAngryActorInterval(self, animName, **kw):
if self.happy:
ival = Sequence(Func(self.reverseHead), ActorInterval(self, animName, **kw), Func(self.forwardHead))
else:
ival = ActorInterval(self, animName, **kw)
return ival
def getAnim(self, anim):
ival = None
if anim == None:
partName = None
if self.happy:
animName = 'Ff_neutral'
else:
animName = 'Fb_neutral'
if self.raised:
ival = ActorInterval(self, animName)
else:
ival = Parallel(ActorInterval(self, animName, partName=['torso', 'head']), ActorInterval(self, 'Fb_downNeutral', partName='legs'))
if not self.forward:
ival = Sequence(Func(self.reverseBody), ival, Func(self.forwardBody))
elif anim == 'down2Up':
ival = Parallel(SoundInterval(self.upSfx, node=self), self.getAngryActorInterval('Fb_down2Up'))
self.raised = 1
elif anim == 'up2Down':
ival = Parallel(SoundInterval(self.downSfx, node=self), self.getAngryActorInterval('Fb_down2Up', playRate=-1))
self.raised = 0
elif anim == 'throw':
self.doAnimate(None, raised=1, happy=0, queueNeutral=0)
ival = Parallel(Sequence(SoundInterval(self.throwSfx, node=self), duration=0), self.getAngryActorInterval('Fb_UpThrow'))
elif anim == 'hit':
if self.raised:
self.raised = 0
ival = self.getAngryActorInterval('Fb_firstHit')
else:
ival = self.getAngryActorInterval('Fb_downHit')
ival = Parallel(SoundInterval(self.reelSfx, node=self), ival)
elif anim == 'ltSwing' or anim == 'rtSwing':
self.doAnimate(None, raised=0, happy=0, queueNeutral=0)
if anim == 'ltSwing':
ival = Sequence(Track((0, self.getAngryActorInterval('Fb_downLtSwing')), (0.9, SoundInterval(self.swingSfx, node=self)), (1, Func(self.bubbleL.unstash))), Func(self.bubbleL.stash))
else:
ival = Sequence(Track((0, self.getAngryActorInterval('Fb_downRtSwing')), (0.9, SoundInterval(self.swingSfx, node=self)), (1, Func(self.bubbleR.unstash))), Func(self.bubbleR.stash))
elif anim == 'frontAttack':
self.doAnimate(None, raised=1, happy=0, queueNeutral=0)
pe = BattleParticles.loadParticleFile('bossCogFrontAttack.ptf')
ival = Sequence(Func(self.reverseHead), ActorInterval(self, 'Bb2Ff_spin'), Func(self.forwardHead))
if self.forward:
ival = Sequence(Func(self.reverseBody), ParallelEndTogether(ival, self.pelvis.hprInterval(0.5, self.pelvisForwardHpr, blendType='easeInOut')))
ival = Sequence(Track((0, ival), (0, Sequence(Func(self.setChatAbsolute, random.choice(TTLocalizer.VPSpinMessages), CFSpeech | CFTimeout), SoundInterval(self.spinSfx, node=self))), (0.9, Parallel(SoundInterval(self.rainGearsSfx, node=self), ParticleInterval(pe, self.frontAttack, worldRelative=0, duration=1.5, cleanup=True), duration=0)), (1.9, Func(self.bubbleF.unstash))), Func(self.bubbleF.stash))
self.forward = 1
self.happy = 0
self.raised = 1
elif anim == 'areaAttack':
if self.twoFaced:
self.doAnimate(None, raised=1, happy=0, queueNeutral=0)
else:
self.doAnimate(None, raised=1, happy=1, queueNeutral=1)
ival = Sequence()
if self.dna.dept == 'm':
ival.append(Func(self.loop, 'Ff_neutral'))
ival.append(Parallel(SoundInterval(self.warningSfx, node=self, volume=2.0), Wait(3.0)))
ival.append(Parallel(ActorInterval(self, 'Fb_jump'), Sequence(Func(self.setChatAbsolute, random.choice(TTLocalizer.JumpBossTaunts[self.dna.dept]), CFSpeech | CFTimeout), SoundInterval(self.swishSfx, duration=1.1, node=self), SoundInterval(self.boomSfx, duration=1.9)), Sequence(Wait(1.21), Func(self.announceAreaAttack))))
if self.twoFaced:
self.happy = 0
else:
self.happy = 1
self.raised = 1
elif anim == 'Fb_fall':
ival = Parallel(ActorInterval(self, 'Fb_fall'), Sequence(SoundInterval(self.reelSfx, node=self), SoundInterval(self.deathSfx)))
elif isinstance(anim, types.StringType):
ival = ActorInterval(self, anim)
else:
ival = anim
return ival