oldschool-toontown/toontown/pets/Pet.py
Little Cat 1801d2b9fb
all: replace pandac.PandaModules imports.
UD/AI + Client boots up.
2022-12-16 20:40:57 -04:00

672 lines
24 KiB
Python

from panda3d.core import *
from direct.directnotify import DirectNotifyGlobal
from direct.interval.IntervalGlobal import *
from direct.fsm.ClassicFSM import *
from direct.fsm.State import *
from direct.distributed.ClockDelta import globalClockDelta
from otp.avatar import Avatar
from direct.actor import Actor
from direct.task import Task
from toontown.pets import PetDNA
from .PetDNA import HeadParts, EarParts, NoseParts, TailParts, BodyTypes, BodyTextures, AllPetColors, getColors, ColorScales, PetEyeColors, EarTextures, TailTextures, getFootTexture, getEarTexture, GiraffeTail, LeopardTail, PetGenders
from toontown.toonbase import TTLocalizer
from toontown.toonbase import ToontownGlobals
from enum import IntEnum
import random
Component2IconDict = {'boredom': 'Bored',
'restlessness': None,
'playfulness': 'Play',
'loneliness': 'Lonely',
'sadness': 'Sad',
'fatigue': 'Sleepy',
'hunger': 'Hungry',
'confusion': 'Confused',
'excitement': 'Surprised',
'anger': 'Angry',
'surprise': 'Surprised',
'affection': 'Love'}
class Pet(Avatar.Avatar):
notify = DirectNotifyGlobal.directNotify.newCategory('Pet')
SerialNum = 0
Interactions = IntEnum('Interactions', ('SCRATCH', 'BEG', 'EAT', 'NEUTRAL'), start=0)
InteractAnims = {Interactions.SCRATCH: ('toPet', 'pet', 'fromPet'),
Interactions.BEG: ('toBeg', 'beg', 'fromBeg'),
Interactions.EAT: ('eat', 'swallow', 'neutral'),
Interactions.NEUTRAL: 'neutral'}
def __init__(self, forGui = 0):
Avatar.Avatar.__init__(self)
self.serialNum = Pet.SerialNum
Pet.SerialNum += 1
self.lockedDown = 0
self.setPickable(1)
self.setPlayerType(NametagGroup.CCNonPlayer)
self.animFSM = ClassicFSM('petAnimFSM', [State('off', self.enterOff, self.exitOff),
State('neutral', self.enterNeutral, self.exitNeutral),
State('neutralHappy', self.enterNeutralHappy, self.exitNeutralHappy),
State('neutralSad', self.enterNeutralSad, self.exitNeutralSad),
State('run', self.enterRun, self.exitRun),
State('swim', self.enterSwim, self.exitSwim),
State('teleportIn', self.enterTeleportIn, self.exitTeleportOut),
State('teleportOut', self.enterTeleportOut, self.exitTeleportOut),
State('walk', self.enterWalk, self.exitWalk),
State('walkHappy', self.enterWalkHappy, self.exitWalkHappy),
State('walkSad', self.enterWalkSad, self.exitWalkSad)], 'off', 'off')
self.animFSM.enterInitialState()
self.forGui = forGui
self.moodModel = None
self.__blinkName = 'petblink-' + str(self.this)
self.track = None
self.soundBackflip = None
self.soundRollover = None
self.soundPlaydead = None
self.soundTeleportIn = None
self.soundTeleportOut = None
self.teleportHole = None
return
def isPet(self):
return True
def stopAnimations(self):
if self.track:
self.track.pause()
self.stopBlink()
self.animFSM.request('off')
def delete(self):
self.stopAnimations()
self.track = None
self.soundBackflip = None
self.soundRollover = None
self.soundPlaydead = None
self.soundTeleportIn = None
self.soundTeleportOut = None
if self.teleportHole:
self.teleportHole.cleanup()
self.teleportHole = None
self.eyesOpenTexture = None
self.eyesClosedTexture = None
self.animFSM = None
self.eyes = None
self.rightPupil = None
self.rightHighlight = None
self.rightBrow = None
self.leftPupil = None
self.leftHighlight = None
self.leftBrow = None
self.color = None
Avatar.Avatar.delete(self)
return
def getDNA(self):
return self.style
def setDNA(self, dna):
if self.style:
pass
else:
self.style = dna
self.generatePet()
self.generateMoods()
self.initializeDropShadow()
self.initializeNametag3d()
self.dropShadow.setScale(0.75)
def generatePet(self):
self.loadModel('phase_4/models/char/TT_pets-mod')
self.loadAnims({'toBeg': 'phase_5/models/char/TT_pets-intoBeg',
'beg': 'phase_5/models/char/TT_pets-beg',
'fromBeg': 'phase_5/models/char/TT_pets-begOut',
'backflip': 'phase_5/models/char/TT_pets-backflip',
'dance': 'phase_5/models/char/TT_pets-heal',
'toDig': 'phase_5/models/char/TT_pets-intoDig',
'dig': 'phase_5/models/char/TT_pets-dig',
'fromDig': 'phase_5/models/char/TT_pets-digToNeutral',
'disappear': 'phase_5/models/char/TT_pets-disappear',
'eat': 'phase_5.5/models/char/TT_pets-eat',
'jump': 'phase_5/models/char/TT_pets-jump',
'neutral': 'phase_4/models/char/TT_pets-neutral',
'neutralHappy': 'phase_4/models/char/TT_pets-neutralHappy',
'neutralSad': 'phase_4/models/char/TT_pets-neutral_sad',
'toPet': 'phase_5.5/models/char/TT_pets-petin',
'pet': 'phase_5.5/models/char/TT_pets-petloop',
'fromPet': 'phase_5.5/models/char/TT_pets-petend',
'playDead': 'phase_5/models/char/TT_pets-playdead',
'fromPlayDead': 'phase_5/models/char/TT_pets-deadend',
'reappear': 'phase_5/models/char/TT_pets-reappear',
'run': 'phase_5.5/models/char/TT_pets-run',
'rollover': 'phase_5/models/char/TT_pets-rollover',
'walkSad': 'phase_5.5/models/char/TT_pets-sadwalk',
'speak': 'phase_5/models/char/TT_pets-speak',
'swallow': 'phase_5.5/models/char/TT_pets-swallow',
'swim': 'phase_5.5/models/char/TT_pets-swim',
'toBall': 'phase_5.5/models/char/TT_pets-toBall',
'walk': 'phase_5.5/models/char/TT_pets-walk',
'walkHappy': 'phase_5.5/models/char/TT_pets-walkHappy'})
self.setHeight(2)
color = None
colorIndex = self.style[5]
color = AllPetColors[colorIndex]
self.color = color
bodyType = self.style[4]
body = self.find('**/body')
tex = loader.loadTexture(BodyTextures[BodyTypes[bodyType]])
tex.setMinfilter(Texture.FTLinear)
tex.setMagfilter(Texture.FTLinear)
body.setTexture(tex, 1)
body.setColor(color)
leftFoot = self.find('**/leftFoot')
rightFoot = self.find('**/rightFoot')
texName = getFootTexture(bodyType)
tex = loader.loadTexture(texName)
tex.setMinfilter(Texture.FTLinear)
tex.setMagfilter(Texture.FTLinear)
leftFoot.setTexture(tex, 1)
rightFoot.setTexture(tex, 1)
leftFoot.setColor(color)
rightFoot.setColor(color)
for part in HeadParts + EarParts + NoseParts + TailParts:
self.find('**/' + part).stash()
colorScale = ColorScales[self.style[6]]
partColor = self.amplifyColor(color, colorScale)
headIndex = self.style[0]
if headIndex != -1:
head = self.find('**/@@' + HeadParts[headIndex])
head.setColor(partColor)
head.unstash()
earsIndex = self.style[1]
if earsIndex != -1:
ears = self.find('**/@@' + EarParts[earsIndex])
ears.setColor(partColor)
texName = getEarTexture(bodyType, EarParts[earsIndex])
if texName:
tex = loader.loadTexture(texName)
tex.setMinfilter(Texture.FTLinear)
tex.setMagfilter(Texture.FTLinear)
ears.setTexture(tex, 1)
ears.unstash()
noseIndex = self.style[2]
if noseIndex != -1:
nose = self.find('**/@@' + NoseParts[noseIndex])
nose.setColor(partColor)
nose.unstash()
tailIndex = self.style[3]
if tailIndex != -1:
tail = self.find('**/@@' + TailParts[tailIndex])
tail.setColor(partColor)
texName = TailTextures[TailParts[tailIndex]]
if texName:
if BodyTypes[bodyType] == 'giraffe':
texName = GiraffeTail
elif BodyTypes[bodyType] == 'leopard':
texName = LeopardTail
tex = loader.loadTexture(texName)
tex.setMinfilter(Texture.FTLinear)
tex.setMagfilter(Texture.FTLinear)
tail.setTexture(tex, 1)
tail.unstash()
if not self.forGui:
self.drawInFront('eyeWhites', 'body', 1)
self.drawInFront('rightPupil', 'eyeWhites', 2)
self.drawInFront('leftPupil', 'eyeWhites', 2)
self.drawInFront('rightHighlight', 'rightPupil', 3)
self.drawInFront('leftHighlight', 'leftPupil', 3)
else:
self.drawInFront('eyeWhites', 'body', -2)
self.drawInFront('rightPupil', 'eyeWhites', -2)
self.drawInFront('leftPupil', 'eyeWhites', -2)
self.find('**/rightPupil').adjustAllPriorities(1)
self.find('**/leftPupil').adjustAllPriorities(1)
eyes = self.style[7]
eyeColor = PetEyeColors[eyes]
self.eyes = self.find('**/eyeWhites')
self.rightPupil = self.find('**/rightPupil')
self.leftPupil = self.find('**/leftPupil')
self.rightHighlight = self.find('**/rightHighlight')
self.leftHighlight = self.find('**/leftHighlight')
self.rightBrow = self.find('**/rightBrow')
self.leftBrow = self.find('**/leftBrow')
self.eyes.setColor(1, 1, 1, 1)
self.rightPupil.setColor(eyeColor, 2)
self.leftPupil.setColor(eyeColor, 2)
self.rightHighlight.setColor(1, 1, 1, 1)
self.leftHighlight.setColor(1, 1, 1, 1)
self.rightBrow.setColor(0, 0, 0, 1)
self.leftBrow.setColor(0, 0, 0, 1)
self.eyes.setTwoSided(1, 1)
self.rightPupil.setTwoSided(1, 1)
self.leftPupil.setTwoSided(1, 1)
self.rightHighlight.setTwoSided(1, 1)
self.leftHighlight.setTwoSided(1, 1)
self.rightBrow.setTwoSided(1, 1)
self.leftBrow.setTwoSided(1, 1)
if self.forGui:
self.rightHighlight.hide()
self.leftHighlight.hide()
if self.style[8]:
self.eyesOpenTexture = loader.loadTexture('phase_4/maps/BeanEyeBoys2.jpg', 'phase_4/maps/BeanEyeBoys2_a.rgb')
self.eyesClosedTexture = loader.loadTexture('phase_4/maps/BeanEyeBoysBlink.jpg', 'phase_4/maps/BeanEyeBoysBlink_a.rgb')
else:
self.eyesOpenTexture = loader.loadTexture('phase_4/maps/BeanEyeGirlsNew.jpg', 'phase_4/maps/BeanEyeGirlsNew_a.rgb')
self.eyesClosedTexture = loader.loadTexture('phase_4/maps/BeanEyeGirlsBlinkNew.jpg', 'phase_4/maps/BeanEyeGirlsBlinkNew_a.rgb')
self.eyesOpenTexture.setMinfilter(Texture.FTLinear)
self.eyesOpenTexture.setMagfilter(Texture.FTLinear)
self.eyesClosedTexture.setMinfilter(Texture.FTLinear)
self.eyesClosedTexture.setMagfilter(Texture.FTLinear)
self.eyesOpen()
return None
def initializeBodyCollisions(self, collIdStr):
Avatar.Avatar.initializeBodyCollisions(self, collIdStr)
if not self.ghostMode:
self.collNode.setCollideMask(self.collNode.getIntoCollideMask() | ToontownGlobals.PieBitmask)
def amplifyColor(self, color, scale):
color = color * scale
for i in (0, 1, 2):
if color[i] > 1.0:
color.setCell(i, 1.0)
return color
def generateMoods(self):
moodIcons = loader.loadModel('phase_4/models/char/petEmotes')
self.moodIcons = self.attachNewNode('moodIcons')
self.moodIcons.setScale(2.0)
self.moodIcons.setZ(3.65)
moods = moodIcons.findAllMatches('**/+GeomNode')
for moodNum in range(0, moods.getNumPaths()):
mood = moods.getPath(moodNum)
mood.reparentTo(self.moodIcons)
mood.setBillboardPointEye()
mood.hide()
def clearMood(self):
if self.moodModel:
self.moodModel.hide()
self.moodModel = None
return
def showMood(self, mood):
if hasattr(base.cr, 'newsManager') and base.cr.newsManager:
holidayIds = base.cr.newsManager.getHolidayIdList()
if (ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds or ToontownGlobals.SILLYMETER_EXT_HOLIDAY in holidayIds) and not mood == 'confusion':
self.speakMood(mood)
return
else:
self.clearChat()
else:
self.clearChat()
mood = Component2IconDict[mood]
if mood is None:
moodModel = None
else:
moodModel = self.moodIcons.find('**/*' + mood + '*')
if moodModel.isEmpty():
self.notify.warning('No such mood!: %s' % mood)
return
if self.moodModel == moodModel:
return
if self.moodModel:
self.moodModel.hide()
self.moodModel = moodModel
if self.moodModel:
self.moodModel.show()
return
def speakMood(self, mood):
if self.moodModel:
self.moodModel.hide()
if base.config.GetBool('want-speech-bubble', 1):
self.nametag.setChat(random.choice(TTLocalizer.SpokenMoods[mood]), CFSpeech)
else:
self.nametag.setChat(random.choice(TTLocalizer.SpokenMoods[mood]), CFThought)
def getGenderString(self):
if self.style:
if self.style[8]:
return TTLocalizer.GenderShopBoyButtonText
else:
return TTLocalizer.GenderShopGirlButtonText
def getShadowJoint(self):
if hasattr(self, 'shadowJoint'):
return self.shadowJoint
shadowJoint = self.find('**/attachShadow')
if shadowJoint.isEmpty():
self.shadowJoint = self
else:
self.shadowJoint = shadowJoint
return self.shadowJoint
def getNametagJoints(self):
joints = []
bundle = self.getPartBundle('modelRoot')
joint = bundle.findChild('attachNametag')
if joint:
joints.append(joint)
return joints
def fitAndCenterHead(self, maxDim, forGui = 0):
p1 = Point3()
p2 = Point3()
self.calcTightBounds(p1, p2)
if forGui:
h = 180
t = p1[0]
p1.setX(-p2[0])
p2.setX(-t)
self.getGeomNode().setDepthWrite(1)
self.getGeomNode().setDepthTest(1)
else:
h = 0
d = p2 - p1
biggest = max(d[0], d[2])
s = (maxDim + 0.0) / biggest
mid = (p1 + d / 2.0) * s
self.setPosHprScale(-mid[0], -mid[1] + 1, -mid[2], h, 0, 0, s, s, s)
def makeRandomPet(self):
dna = PetDNA.getRandomPetDNA()
self.setDNA(dna)
def enterOff(self):
self.stop()
def exitOff(self):
pass
def enterBall(self):
self.setPlayRate(1, 'toBall')
self.play('toBall')
def exitBall(self):
self.setPlayRate(-1, 'toBall')
self.play('toBall')
def enterBackflip(self):
self.play('backflip')
def exitBackflip(self):
self.stop('backflip')
def enterBeg(self):
delay = self.getDuration('toBeg')
self.track = Sequence(Func(self.play, 'toBeg'), Wait(delay), Func(self.loop, 'beg'))
self.track.start()
def exitBeg(self):
self.track.pause()
self.play('fromBeg')
def enterEat(self):
self.loop('eat')
def exitEat(self):
self.stop('swallow')
def enterDance(self):
self.loop('dance')
def exitDance(self):
self.stop('dance')
def enterNeutral(self):
anim = 'neutral'
self.pose(anim, random.choice(list(range(0, self.getNumFrames(anim)))))
self.loop(anim, restart=0)
def exitNeutral(self):
self.stop('neutral')
def enterNeutralHappy(self):
anim = 'neutralHappy'
self.pose(anim, random.choice(list(range(0, self.getNumFrames(anim)))))
self.loop(anim, restart=0)
def exitNeutralHappy(self):
self.stop('neutralHappy')
def enterNeutralSad(self):
anim = 'neutralSad'
self.pose(anim, random.choice(list(range(0, self.getNumFrames(anim)))))
self.loop(anim, restart=0)
def exitNeutralSad(self):
self.stop('neutralSad')
def enterRun(self):
self.loop('run')
def exitRun(self):
self.stop('run')
def enterSwim(self):
self.loop('swim')
def exitSwim(self):
self.stop('swim')
def getTeleportInTrack(self):
if not self.teleportHole:
self.teleportHole = Actor.Actor('phase_3.5/models/props/portal-mod', {'hole': 'phase_3.5/models/props/portal-chan'})
track = Sequence(Wait(1.0), Parallel(self.getTeleportInSoundInterval(), Sequence(Func(self.showHole), ActorInterval(self.teleportHole, 'hole', startFrame=81, endFrame=71), ActorInterval(self, 'reappear'), ActorInterval(self.teleportHole, 'hole', startFrame=71, endFrame=81), Func(self.cleanupHole), Func(self.loop, 'neutral')), Sequence(Func(self.dropShadow.hide), Wait(1.0), Func(self.dropShadow.show))))
return track
def enterTeleportIn(self, timestamp):
self.track = self.getTeleportInTrack()
self.track.start(globalClockDelta.localElapsedTime(timestamp))
def exitTeleportIn(self):
self.track.pause()
def getTeleportOutTrack(self):
if not self.teleportHole:
self.teleportHole = Actor.Actor('phase_3.5/models/props/portal-mod', {'hole': 'phase_3.5/models/props/portal-chan'})
track = Sequence(Wait(1.0), Parallel(self.getTeleportOutSoundInterval(), Sequence(ActorInterval(self, 'toDig'), Parallel(ActorInterval(self, 'dig'), Func(self.showHole), ActorInterval(self.teleportHole, 'hole', startFrame=81, endFrame=71)), ActorInterval(self, 'disappear'), ActorInterval(self.teleportHole, 'hole', startFrame=71, endFrame=81), Func(self.cleanupHole)), Sequence(Wait(1.0), Func(self.dropShadow.hide))))
return track
def enterTeleportOut(self, timestamp):
self.track = self.getTeleportOutTrack()
self.track.start(globalClockDelta.localElapsedTime(timestamp))
def exitTeleportOut(self):
self.track.pause()
def showHole(self):
if self.teleportHole:
self.teleportHole.setBin('shadow', 0)
self.teleportHole.setDepthTest(0)
self.teleportHole.setDepthWrite(0)
self.teleportHole.reparentTo(self)
self.teleportHole.setScale(0.75)
self.teleportHole.setPos(0, -1, 0)
def cleanupHole(self):
if self.teleportHole:
self.teleportHole.reparentTo(hidden)
self.teleportHole.clearBin()
self.teleportHole.clearDepthTest()
self.teleportHole.clearDepthWrite()
def getTeleportInSoundInterval(self):
if not self.soundTeleportIn:
self.soundTeleportIn = loader.loadSfx('phase_5/audio/sfx/teleport_reappear.ogg')
return SoundInterval(self.soundTeleportIn)
def getTeleportOutSoundInterval(self):
if not self.soundTeleportOut:
self.soundTeleportOut = loader.loadSfx('phase_5/audio/sfx/teleport_disappear.ogg')
return SoundInterval(self.soundTeleportOut)
def enterWalk(self):
self.loop('walk')
def exitWalk(self):
self.stop('walk')
def enterWalkHappy(self):
self.loop('walkHappy')
def exitWalkHappy(self):
self.stop('walkHappy')
def enterWalkSad(self):
self.loop('walkSad')
def exitWalkSad(self):
self.stop('walkSad')
def trackAnimToSpeed(self, forwardVel, rotVel, inWater = 0):
action = 'neutral'
if self.isInWater():
action = 'swim'
elif forwardVel > 0.1 or abs(rotVel) > 0.1:
action = 'walk'
self.setAnimWithMood(action)
def setAnimWithMood(self, action):
how = ''
if self.isExcited():
how = 'Happy'
elif self.isSad():
how = 'Sad'
if action == 'swim':
anim = action
else:
anim = '%s%s' % (action, how)
if anim != self.animFSM.getCurrentState().getName():
self.animFSM.request(anim)
def isInWater(self):
return 0
def isExcited(self):
return 0
def isSad(self):
return 0
def startTrackAnimToSpeed(self):
self.lastPos = self.getPos(render)
self.lastH = self.getH(render)
taskMgr.add(self._trackAnimTask, self.getTrackAnimTaskName())
def stopTrackAnimToSpeed(self):
taskMgr.remove(self.getTrackAnimTaskName())
del self.lastPos
del self.lastH
def getTrackAnimTaskName(self):
return 'trackPetAnim-%s' % self.serialNum
def _trackAnimTask(self, task):
curPos = self.getPos(render)
curH = self.getH(render)
self.trackAnimToSpeed(curPos - self.lastPos, curH - self.lastH)
self.lastPos = curPos
self.lastH = curH
return Task.cont
def __blinkOpen(self, task):
self.eyesOpen()
r = random.random()
if r < 0.1:
t = 0.2
else:
t = r * 4.0 + 1
taskMgr.doMethodLater(t, self.__blinkClosed, self.__blinkName)
return Task.done
def __blinkClosed(self, task):
self.eyesClose()
taskMgr.doMethodLater(0.125, self.__blinkOpen, self.__blinkName)
return Task.done
def startBlink(self):
taskMgr.remove(self.__blinkName)
self.eyesOpen()
t = random.random() * 4.0 + 1
taskMgr.doMethodLater(t, self.__blinkClosed, self.__blinkName)
def stopBlink(self):
taskMgr.remove(self.__blinkName)
self.eyesOpen()
def eyesOpen(self):
self.eyes.setColor(1, 1, 1, 1)
self.eyes.setTexture(self.eyesOpenTexture, 1)
self.rightPupil.show()
self.leftPupil.show()
if not self.forGui:
self.rightHighlight.show()
self.leftHighlight.show()
def eyesClose(self):
self.eyes.setColor(self.color)
self.eyes.setTexture(self.eyesClosedTexture, 1)
self.rightPupil.hide()
self.leftPupil.hide()
if not self.forGui:
self.rightHighlight.hide()
self.leftHighlight.hide()
def lockPet(self):
if not self.lockedDown:
self.prevAnimState = self.animFSM.getCurrentState().getName()
self.animFSM.request('neutral')
self.lockedDown += 1
def isLockedDown(self):
return self.lockedDown != 0
def unlockPet(self):
self.lockedDown -= 1
if not self.lockedDown:
self.animFSM.request(self.prevAnimState)
self.prevAnimState = None
return
def getInteractIval(self, interactId):
anims = self.InteractAnims[interactId]
if type(anims) == str:
animIval = ActorInterval(self, anims)
else:
animIval = Sequence()
for anim in anims:
animIval.append(ActorInterval(self, anim))
return animIval
def gridPets():
pets = []
offsetX = 0
offsetY = 0
startPos = base.localAvatar.getPos()
for body in range(0, len(BodyTypes)):
colors = getColors(body)
for color in colors:
p = Pet()
p.setDNA([random.choice(list(range(-1, len(HeadParts)))),
random.choice(list(range(-1, len(EarParts)))),
random.choice(list(range(-1, len(NoseParts)))),
random.choice(list(range(-1, len(TailParts)))),
body,
color,
random.choice(list(range(-1, len(ColorScales)))),
random.choice(list(range(0, len(PetEyeColors)))),
random.choice(list(range(0, len(PetGenders))))])
p.setPos(startPos[0] + offsetX, startPos[1] + offsetY, startPos[2])
p.animFSM.request('neutral')
p.reparentTo(render)
pets.append(p)
offsetX += 3
offsetY += 3
offsetX = 0
return pets