Пример #1
0
class Pet(Avatar.Avatar):
    """Toontown pet"""

    notify = DirectNotifyGlobal.directNotify.newCategory("Pet")

    SerialNum = 0

    Interactions = PythonUtil.Enum('SCRATCH, BEG, EAT, NEUTRAL')
    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),
            ],
            # init
            'off',
            # final
            'off',
        )
        self.animFSM.enterInitialState()
        self.forGui = forGui
        self.moodModel = None
        # probably don't have a name yet
        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

    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)

    def getDNA(self):
        return self.style

    def setDNA(self, dna):
        # dna format [head, ears, nose, tail, body, color, eyes, gender]
        if self.style:
            pass
        else:
            # make sure the dna is valid
            assert (len(dna) == PetDNA.NumFields)

            # store the dna
            self.style = dna
            self.generatePet()
            self.generateMoods()

            # this no longer works in the Avatar init!
            # I moved it here for lack of a better place
            # make the drop shadow
            self.initializeDropShadow()
            self.initializeNametag3d()

            # looks a little big
            self.dropShadow.setScale(0.75)

    def generatePet(self):
        """
        create a pet from dna
        """
        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)
        # name is set by user or subclass

        # determine the color
        color = None
        colorIndex = self.style[5]
        color = AllPetColors[colorIndex]
        # remember this for blinks
        self.color = color

        # set the body and foot texture
        bodyType = self.style[4]
        assert (bodyType < len(BodyTypes))
        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)

        # set the appropriate foot texture
        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)

        # stash all parts
        for part in HeadParts + EarParts + NoseParts + TailParts:
            self.find("**/" + part).stash()

        #
        # unstash the appropriate parts
        #

        # scale the color slightly to accent misc. body parts
        colorScale = ColorScales[self.style[6]]
        partColor = self.amplifyColor(color, colorScale)

        headIndex = self.style[0]
        # if we have a head decoration
        if headIndex != -1:
            assert (headIndex < len(HeadParts))
            head = self.find("**/@@" + HeadParts[headIndex])
            head.setColor(partColor)
            head.unstash()

        earsIndex = self.style[1]
        # if we have ears
        if earsIndex != -1:
            assert (earsIndex < len(EarParts))
            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 we have a nose
        if noseIndex != -1:
            assert (noseIndex < len(NoseParts))
            nose = self.find("**/@@" + NoseParts[noseIndex])
            nose.setColor(partColor)
            nose.unstash()

        tailIndex = self.style[3]
        # if we have a tail
        if tailIndex != -1:
            assert (tailIndex < len(TailParts))
            tail = self.find("**/@@" + TailParts[tailIndex])
            tail.setColor(partColor)
            texName = TailTextures[TailParts[tailIndex]]
            if texName:
                # check to see if we have a special tail texture
                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()

        # Reorder the eyes so they don't do any Z-fighting with the
        # head or with each other.
        if not self.forGui:
            # In the 3-D world, we can fix the eyes by putting them
            # all in the fixed bin.
            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:
            # In the 2-D panel, we have to reparent them.  This also
            # means we have to elevate the priorities on the pupils so
            # they don't inherit the wrong texture from above.
            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)

        # set eye color
        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)
        # we need to increase priority on eyeColor so pupils show
        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:
            # HACK: highlights look awful in the GUI
            # There is probably a way to make them look better
            # but for know. Bye bye...
            self.rightHighlight.hide()
            self.leftHighlight.hide()

        # set eye texture based on gender
        if self.style[8]:
            self.eyesOpenTexture = loader.loadTexture(
                'phase_4/maps/BeanEyeBoys2.png')
            self.eyesClosedTexture = loader.loadTexture(
                'phase_4/maps/BeanEyeBoysBlink.png')
        else:
            self.eyesOpenTexture = loader.loadTexture(
                'phase_4/maps/BeanEyeGirlsNew.png')
            self.eyesClosedTexture = loader.loadTexture(
                'phase_4/maps/BeanEyeGirlsBlinkNew.png')

        self.eyesOpenTexture.setMinfilter(Texture.FTLinear)
        self.eyesOpenTexture.setMagfilter(Texture.FTLinear)

        self.eyesClosedTexture.setMinfilter(Texture.FTLinear)
        self.eyesClosedTexture.setMagfilter(Texture.FTLinear)
        self.eyesOpen()

    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):
        # load the emoticon models
        moodIcons = loader.loadModel('phase_4/models/char/petEmotes')
        self.moodIcons = self.attachNewNode('moodIcons')
        # set Z
        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)
            # set billboard
            mood.setBillboardPointEye()
            mood.hide()

    def clearMood(self):
        if self.moodModel:
            self.moodModel.hide()
        self.moodModel = None

    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 and (
                    not mood == "confusion"):
                self.speakMood(mood)
                return
            else:
                self.clearChat()
        else:
            self.clearChat()

        # the model uses caps
        mood = Component2IconDict[mood]
        if mood is None:
            moodModel = None
        else:
            moodModel = self.moodIcons.find("**/*" + mood + "*")
            # make sure we found a model
            if moodModel.isEmpty():
                self.notify.warning("No such mood!: %s" % (mood))
                return
        # make sure the mood has actually changed
        if self.moodModel == moodModel:
            return
        # hide the old mood
        if self.moodModel:
            self.moodModel.hide()
        # set the new mood
        self.moodModel = moodModel

        if self.moodModel:
            self.moodModel.show()

    def speakMood(self, mood):
        """
        The Doodle speaks for April Toons' Week.
        """
        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)  # Note: Use CFTimeout?

    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):
        """
        Return the CharacterJoint that animates the nametag, in a list.
        """
        joints = []
        bundle = self.getPartBundle('modelRoot')
        joint = bundle.findChild('attachNametag')
        if joint:
            joints.append(joint)
        return joints

    def fitAndCenterHead(self, maxDim, forGui=0):
        # Compute an xform which centers geometry on origin and scales it
        # to max +/- maxDim/2.0 in size
        p1 = Point3()
        p2 = Point3()
        self.calcTightBounds(p1, p2)
        # Take into account rotation by 180 degrees if necessary
        if forGui:
            h = 180
            # Need to flip max and min
            t = p1[0]
            p1.setX(-p2[0])
            p2.setX(-t)
            # Turn on depth write and test.
            self.getGeomNode().setDepthWrite(1)
            self.getGeomNode().setDepthTest(1)
        else:
            h = 0
        # Find dimension
        d = p2 - p1
        biggest = max(d[0], d[2])
        s = (maxDim + 0.0) / biggest
        # find midpoint
        mid = (p1 + d / 2.0) * s
        # We must push the head a distance forward in Y so it doesn't
        # intersect the near plane, which is incorrectly set to 0 in
        # DX for some reason.
        self.setPosHprScale(-mid[0], -mid[1] + 1, -mid[2], h, 0, 0, s, s, s)

    def makeRandomPet(self):
        dna = PetDNA.getRandomPetDNA()
        self.setDNA(dna)

    # animFSM states

    # Note there are not states for all anim cycles. Just the ones trackAnim2Speed might trigger

    def enterOff(self):
        self.stop()

    def exitOff(self):
        pass

    def enterBall(self):
        self.setPlayRate(1, 'toBall')
        self.play('toBall')
        # TODO: ball neutral

    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):
        # make the neutral start at a random frame
        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):
        # make the happy neutral start at a random frame
        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):
        # make the sad neutral start at a random frame
        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()

    # teleport utility methods
    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.mp3')
        return SoundInterval(self.soundTeleportIn)

    def getTeleportOutSoundInterval(self):
        if not self.soundTeleportOut:
            self.soundTeleportOut = loader.loadSfx(
                'phase_5/audio/sfx/teleport_disappear.mp3')
        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')

    # make animation match motion of character
    def trackAnimToSpeed(self, forwardVel, rotVel, inWater=0):
        # call this with a forward and rotational velocity every frame
        # to automatically update the pet's animation

        # what is the pet doing?
        action = 'neutral'
        if self.isInWater():
            # tread water
            action = 'swim'
        elif (forwardVel > .1) or (abs(rotVel) > .1):
            action = 'walk'

        self.setAnimWithMood(action)

    def setAnimWithMood(self, action):
        # how are they doing the 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)

    # override if desired
    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

    # blinks
    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):
        # call this when you need to lock the pet down in order to play
        # a movie on him
        if not self.lockedDown:
            self.prevAnimState = self.animFSM.getCurrentState().getName()
            # if the movie doesn't do anything, the pet is going to stop moving.
            # put him in neutral
            self.animFSM.request('neutral')
        self.lockedDown += 1

    def isLockedDown(self):
        return self.lockedDown != 0

    def unlockPet(self):
        # call this when you're done playing the movie
        assert (self.lockedDown)
        self.lockedDown -= 1
        if not self.lockedDown:
            # make sure the pet is playing the same animation that it was
            # playing when we locked it down
            self.animFSM.request(self.prevAnimState)
            self.prevAnimState = None

    def getInteractIval(self, interactId):
        anims = self.InteractAnims[interactId]
        #print "getInteractIval: anims = ", anims
        if type(anims) == bytes:
            animIval = ActorInterval(self, anims)
        else:
            animIval = Sequence()
            for anim in anims:
                animIval.append(ActorInterval(self, anim))
        return animIval
Пример #2
0
class Pet(Avatar.Avatar):
    notify = DirectNotifyGlobal.directNotify.newCategory('Pet')
    SerialNum = 0
    Interactions = PythonUtil.Enum('SCRATCH, BEG, EAT, NEUTRAL')
    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.CCSpeedChat)
        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.initializeDropShadow()
            self.initializeNametag3d()
            self.generateMoods()
            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):
        nodePath = NodePath(self.nametag.getNameIcon())

        if not nodePath:
            return

        moodIcons = loader.loadModel('phase_4/models/char/petEmotes')
        self.moodIcons = nodePath.attachNewNode('moodIcons')
        self.moodIcons.setScale(6.0)
        self.moodIcons.setZ(3.5)

        moods = moodIcons.findAllMatches('**/+GeomNode')
        for moodNum in xrange(0, moods.getNumPaths()):
            mood = moods.getPath(moodNum)
            mood.reparentTo(self.moodIcons)
            mood.setBillboardPointEye()
            mood.hide()

        moodIcons.removeNode()

    def clearMood(self):
        if self.moodModel:
            self.moodModel.hide()
        self.moodModel = None
        return

    def showMood(self, mood):
        if base.cr.newsManager.isHolidayRunning(
                ToontownGlobals.APRIL_TOONS_WEEK) and mood != 'confusion':
            self.speakMood(mood)
        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):
        self.setChatAbsolute(random.choice(TTLocalizer.SpokenMoods[mood]),
                             CFSpeech)

    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(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(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(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) == types.StringType:
            animIval = ActorInterval(self, anims)
        else:
            animIval = Sequence()
            for anim in anims:
                animIval.append(ActorInterval(self, anim))

        return animIval
Пример #3
0
class Pet(Avatar.Avatar):
    notify = DirectNotifyGlobal.directNotify.newCategory('Pet')
    SerialNum = 0
    Interactions = PythonUtil.Enum('SCRATCH, BEG, EAT, NEUTRAL')
    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(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(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(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) == types.StringType:
            animIval = ActorInterval(self, anims)
        else:
            animIval = Sequence()
            for anim in anims:
                animIval.append(ActorInterval(self, anim))

        return animIval