Beispiel #1
0
    def initOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug('startOrthoWalk')

        def doCollisions(oldPos, newPos, self=self):
            x = bound(newPos[0], self.StageHalfWidth, -self.StageHalfWidth)
            y = bound(newPos[1], self.StageHalfHeight, -self.StageHalfHeight)
            newPos.setX(x)
            newPos.setY(y)
            return newPos

        orthoDrive = OrthoDrive(self.ToonSpeed, instantTurn=True)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)
 def __init__(self, id, toon, game, guiMgr):
     CogdoMazePlayer.__init__(self, id, toon)
     self.disableGagCollision()
     self.game = game
     self.maze = self.game.maze
     self._guiMgr = guiMgr
     self.cameraMgr = CogdoMazeCameraManager(self.toon, self.maze, camera, render)
     self._proximityRadius = self.maze.cellWidth * Globals.CameraRemoteToonRadius
     orthoDrive = OrthoDrive(Globals.ToonRunSpeed, maxFrameMove=self.maze.cellWidth / 2, customCollisionCallback=self.maze.doOrthoCollisions, wantSound=True)
     self.orthoWalk = OrthoWalk(orthoDrive)
     self._audioMgr = base.cogdoGameAudioMgr
     self._getMemoSfx = self._audioMgr.createSfx('getMemo', source=self.toon)
     self._waterCoolerFillSfx = self._audioMgr.createSfx('waterCoolerFill', source=self.toon)
     self._hitByDropSfx = self._audioMgr.createSfx('toonHitByDrop', source=self.toon)
     self._winSfx = self._audioMgr.createSfx('win')
     self._loseSfx = self._audioMgr.createSfx('lose')
     self.enabled = False
     self.pickupCount = 0
     self.numEntered = 0
     self.throwPending = False
     self.coolDownAfterHitInterval = Sequence(Wait(Globals.HitCooldownTime), Func(self.setInvulnerable, False), name='coolDownAfterHitInterval-%i' % self.toon.doId)
     self.invulnerable = False
     self.gagHandler = CollisionHandlerEvent()
     self.gagHandler.addInPattern('%fn-into-%in')
     self.exited = False
     self.hints = {'find': False,
      'throw': False,
      'squashed': False,
      'boss': False,
      'minion': False}
     self.accept(base.JUMP, self.controlKeyPressed)
Beispiel #3
0
 def __init__(self, id, toon, game, guiMgr):
     CogdoMazePlayer.__init__(self, id, toon)
     self.disableGagCollision()
     self.game = game
     self.maze = self.game.maze
     self._guiMgr = guiMgr
     self.cameraMgr = CogdoMazeCameraManager(self.toon, self.maze, camera,
                                             render)
     self._proximityRadius = self.maze.cellWidth * Globals.CameraRemoteToonRadius
     orthoDrive = OrthoDrive(
         Globals.ToonRunSpeed,
         maxFrameMove=self.maze.cellWidth / 2,
         customCollisionCallback=self.maze.doOrthoCollisions,
         wantSound=True)
     self.orthoWalk = OrthoWalk(orthoDrive)
     self._audioMgr = base.cogdoGameAudioMgr
     self._getMemoSfx = self._audioMgr.createSfx('getMemo',
                                                 source=self.toon)
     self._waterCoolerFillSfx = self._audioMgr.createSfx('waterCoolerFill',
                                                         source=self.toon)
     self._hitByDropSfx = self._audioMgr.createSfx('toonHitByDrop',
                                                   source=self.toon)
     self._winSfx = self._audioMgr.createSfx('win')
     self._loseSfx = self._audioMgr.createSfx('lose')
     self.enabled = False
     self.pickupCount = 0
     self.numEntered = 0
     self.throwPending = False
     self.coolDownAfterHitInterval = Sequence(
         Wait(Globals.HitCooldownTime),
         Func(self.setInvulnerable, False),
         name='coolDownAfterHitInterval-%i' % self.toon.doId)
     self.invulnerable = False
     self.gagHandler = CollisionHandlerEvent()
     self.gagHandler.addInPattern('%fn-into-%in')
     self.exited = False
     self.hints = {
         'find': False,
         'throw': False,
         'squashed': False,
         'boss': False,
         'minion': False
     }
     self.accept(base.JUMP, self.controlKeyPressed)
    def initOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug("startOrthoWalk")

        def doCollisions(oldPos, newPos, self=self):
            x = bound(newPos[0], self.StageHalfWidth, -self.StageHalfWidth)
            y = bound(newPos[1], self.StageHalfHeight, -self.StageHalfHeight)
            newPos.setX(x)
            newPos.setY(y)
            return newPos

        orthoDrive = OrthoDrive(self.ToonSpeed, instantTurn=True)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)
Beispiel #5
0
    def initGameWalk(self):
        self.notify.debug('startOrthoWalk')
        if self.useOrthoWalk:

            def doCollisions(oldPos, newPos, self=self):
                x = bound(newPos[0], CTGG.StageHalfWidth, -CTGG.StageHalfWidth)
                y = bound(newPos[1], CTGG.StageHalfHeight,
                          -CTGG.StageHalfHeight)
                newPos.setX(x)
                newPos.setY(y)
                return newPos

            orthoDrive = OrthoDrive(self.ToonSpeed,
                                    customCollisionCallback=doCollisions,
                                    instantTurn=True)
            self.gameWalk = OrthoWalk(orthoDrive,
                                      broadcast=not self.isSinglePlayer())
        else:
            self.gameWalk = CogThiefWalk.CogThiefWalk('walkDone')
            forwardSpeed = self.ToonSpeed / 2.0
            base.mouseInterfaceNode.setForwardSpeed(forwardSpeed)
            multiplier = forwardSpeed / ToontownGlobals.ToonForwardSpeed
            base.mouseInterfaceNode.setRotateSpeed(
                ToontownGlobals.ToonRotateSpeed * 4)
    def initGameWalk(self):
        self.notify.debug('startOrthoWalk')
        if self.useOrthoWalk:

            def doCollisions(oldPos, newPos, self = self):
                x = bound(newPos[0], CTGG.StageHalfWidth, -CTGG.StageHalfWidth)
                y = bound(newPos[1], CTGG.StageHalfHeight, -CTGG.StageHalfHeight)
                newPos.setX(x)
                newPos.setY(y)
                return newPos

            orthoDrive = OrthoDrive(self.ToonSpeed, customCollisionCallback=doCollisions, instantTurn=True)
            self.gameWalk = OrthoWalk(orthoDrive, broadcast=not self.isSinglePlayer())
        else:
            self.gameWalk = CogThiefWalk.CogThiefWalk('walkDone')
            forwardSpeed = self.ToonSpeed / 2.0
            base.mouseInterfaceNode.setForwardSpeed(forwardSpeed)
            multiplier = forwardSpeed / ToontownGlobals.ToonForwardSpeed
            base.mouseInterfaceNode.setRotateSpeed(ToontownGlobals.ToonRotateSpeed * 4)
Beispiel #7
0
class CogdoMazeLocalPlayer(CogdoMazePlayer):
    notify = directNotify.newCategory('CogdoMazeLocalPlayer')

    def __init__(self, id, toon, game, guiMgr):
        CogdoMazePlayer.__init__(self, id, toon)
        self.disableGagCollision()
        self.game = game
        self.maze = self.game.maze
        self._guiMgr = guiMgr
        self.cameraMgr = CogdoMazeCameraManager(self.toon, self.maze, camera,
                                                render)
        self._proximityRadius = self.maze.cellWidth * Globals.CameraRemoteToonRadius
        orthoDrive = OrthoDrive(
            Globals.ToonRunSpeed,
            maxFrameMove=self.maze.cellWidth / 2,
            customCollisionCallback=self.maze.doOrthoCollisions,
            wantSound=True)
        self.orthoWalk = OrthoWalk(orthoDrive)
        self._audioMgr = base.cogdoGameAudioMgr
        self._getMemoSfx = self._audioMgr.createSfx('getMemo',
                                                    source=self.toon)
        self._waterCoolerFillSfx = self._audioMgr.createSfx('waterCoolerFill',
                                                            source=self.toon)
        self._hitByDropSfx = self._audioMgr.createSfx('toonHitByDrop',
                                                      source=self.toon)
        self._winSfx = self._audioMgr.createSfx('win')
        self._loseSfx = self._audioMgr.createSfx('lose')
        self.enabled = False
        self.pickupCount = 0
        self.numEntered = 0
        self.throwPending = False
        self.coolDownAfterHitInterval = Sequence(
            Wait(Globals.HitCooldownTime),
            Func(self.setInvulnerable, False),
            name='coolDownAfterHitInterval-%i' % self.toon.doId)
        self.invulnerable = False
        self.gagHandler = CollisionHandlerEvent()
        self.gagHandler.addInPattern('%fn-into-%in')
        self.exited = False
        self.hints = {
            'find': False,
            'throw': False,
            'squashed': False,
            'boss': False,
            'minion': False
        }
        self.accept(base.JUMP, self.controlKeyPressed)

    def destroy(self):
        self.toon.showName()
        self.ignoreAll()
        self.coolDownAfterHitInterval.clearToInitial()
        del self.coolDownAfterHitInterval
        del self._getMemoSfx
        del self._waterCoolerFillSfx
        del self._hitByDropSfx
        del self._winSfx
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        CogdoMazePlayer.destroy(self)

    def __initCollisions(self):
        collSphere = CollisionSphere(0, 0, 0, Globals.PlayerCollisionRadius)
        collSphere.setTangible(0)
        self.mazeCollisionName = Globals.LocalPlayerCollisionName
        collNode = CollisionNode(self.mazeCollisionName)
        collNode.addSolid(collSphere)
        collNodePath = self.toon.attachNewNode(collNode)
        collNodePath.hide()
        handler = CollisionHandlerEvent()
        handler.addInPattern('%fn-into-%in')
        base.cTrav.addCollider(collNodePath, handler)
        self.handler = handler
        self._collNodePath = collNodePath

    def clearCollisions(self):
        self.handler.clear()

    def __disableCollisions(self):
        self._collNodePath.removeNode()
        del self._collNodePath

    def _isNearPlayer(self, player):
        return self.toon.getDistance(player.toon) <= self._proximityRadius

    def update(self, dt):
        if self.getCurrentOrNextState() != 'Off':
            self._updateCamera(dt)

    def _updateCamera(self, dt):
        numPlayers = 0.0
        for player in self.game.players:
            if player != self and player.toon and self._isNearPlayer(player):
                numPlayers += 1

        d = clamp(
            Globals.CameraMinDistance + numPlayers /
            (CogdoGameConsts.MaxPlayers - 1) *
            (Globals.CameraMaxDistance - Globals.CameraMinDistance),
            Globals.CameraMinDistance, Globals.CameraMaxDistance)
        self.cameraMgr.setCameraTargetDistance(d)
        self.cameraMgr.update(dt)

    def enterOff(self):
        CogdoMazePlayer.enterOff(self)

    def exitOff(self):
        CogdoMazePlayer.exitOff(self)
        self.toon.hideName()

    def enterReady(self):
        CogdoMazePlayer.enterReady(self)
        self.cameraMgr.enable()

    def exitReady(self):
        CogdoMazePlayer.enterReady(self)

    def enterNormal(self):
        CogdoMazePlayer.enterNormal(self)
        self.orthoWalk.start()

    def exitNormal(self):
        CogdoMazePlayer.exitNormal(self)
        self.orthoWalk.stop()

    def enterHit(self, elapsedTime=0.0):
        CogdoMazePlayer.enterHit(self, elapsedTime)
        self.setInvulnerable(True)

    def exitHit(self):
        CogdoMazePlayer.exitHit(self)
        self.coolDownAfterHitInterval.clearToInitial()
        self.coolDownAfterHitInterval.start()

    def enterDone(self):
        CogdoMazePlayer.enterDone(self)
        self._guiMgr.hideQuestArrow()
        self.ignore(base.JUMP)
        self._guiMgr.setMessage('')
        if self.exited == False:
            self.lostMemos()

    def exitDone(self):
        CogdoMazePlayer.exitDone(self)

    def hitByDrop(self):
        if self.equippedGag is not None and not self.hints['squashed']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeSquashHint,
                                             Globals.HintTimeout)
            self.hints['squashed'] = True
        self._hitByDropSfx.play()
        CogdoMazePlayer.hitByDrop(self)
        return

    def equipGag(self):
        CogdoMazePlayer.equipGag(self)
        self._waterCoolerFillSfx.play()
        messenger.send(Globals.WaterCoolerHideEventName, [])
        if not self.hints['throw']:
            self._guiMgr.setMessage(TTLocalizer.CogdoMazeThrowHint)
            self.hints['throw'] = True

    def hitSuit(self, suitType):
        if suitType == Globals.SuitTypes.Boss and not self.hints['boss']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeBossHint,
                                             Globals.HintTimeout)
            self.hints['boss'] = True
        if suitType != Globals.SuitTypes.Boss and not self.hints['minion']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeMinionHint,
                                             Globals.HintTimeout)
            self.hints['minion'] = True

    def createThrowGag(self, gag):
        throwGag = CogdoMazePlayer.createThrowGag(self, gag)
        collSphere = CollisionSphere(0, 0, 0, 0.5)
        collSphere.setTangible(0)
        name = Globals.GagCollisionName
        collNode = CollisionNode(name)
        collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
        collNode.addSolid(collSphere)
        colNp = throwGag.attachNewNode(collNode)
        base.cTrav.addCollider(colNp, self.gagHandler)
        return throwGag

    def showToonThrowingGag(self, heading, pos):
        self._guiMgr.clearMessage()
        return CogdoMazePlayer.showToonThrowingGag(self, heading, pos)

    def removeGag(self):
        if self.equippedGag is None:
            return
        CogdoMazePlayer.removeGag(self)
        self.throwPending = False
        messenger.send(Globals.WaterCoolerShowEventName, [])
        return

    def controlKeyPressed(self):
        if self.game.finished or self.throwPending or self.getCurrentOrNextState(
        ) == 'Hit' or self.equippedGag == None:
            return
        self.throwPending = True
        heading = self.toon.getH()
        pos = self.toon.getPos()
        self.game.requestUseGag(pos.getX(), pos.getY(), heading)
        return

    def completeThrow(self):
        self.clearCollisions()
        CogdoMazePlayer.completeThrow(self)

    def shakeCamera(self, strength):
        self.cameraMgr.shake(strength)

    def getCameraShake(self):
        return self.cameraMgr.shakeStrength

    def setInvulnerable(self, bool):
        self.invulnerable = bool

    def handleGameStart(self):
        self.numEntered = len(self.game.players)
        self.__initCollisions()
        self._guiMgr.startGame(TTLocalizer.CogdoMazeFindHint)
        self.hints['find'] = True
        self.notify.info(
            'toonId:%d laff:%d/%d  %d player(s) started maze game' %
            (self.toon.doId, self.toon.hp, self.toon.maxHp,
             len(self.game.players)))

    def handleGameExit(self):
        self.cameraMgr.disable()
        self.__disableCollisions()

    def handlePickUp(self, toonId):
        if toonId == self.toon.doId:
            self.pickupCount += 1
            self._guiMgr.setPickupCount(self.pickupCount)
            if self.pickupCount == 1:
                self._guiMgr.showPickupCounter()
            self._getMemoSfx.play()

    def handleOpenDoor(self, door):
        self._guiMgr.setMessage(TTLocalizer.CogdoMazeGameDoorOpens)
        self._guiMgr.showQuestArrow(self.toon, door,
                                    Point3(0, 0,
                                           self.toon.getHeight() + 2))

    def handleTimeAlert(self):
        self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeGameTimeAlert)

    def handleToonRevealsDoor(self, toonId, door):
        if toonId == self.toon.doId:
            self._guiMgr.setMessageTemporary(
                TTLocalizer.CogdoMazeGameLocalToonFoundExit)

    def handleToonEntersDoor(self, toonId, door):
        self.exited = True
        message = ''
        if door.getPlayerCount() < len(self.game.players):
            message = TTLocalizer.WaitingForOtherToons
        if toonId == self.toon.doId:
            self._guiMgr.setMessage(message)
            self._winSfx.play()
            self._audioMgr.stopMusic()
        self.notify.info(
            'toonId:%d laff:%d/%d  %d player(s) succeeded in maze game. Going to the executive suit building.'
            % (toonId, self.toon.hp, self.toon.maxHp, len(self.game.players)))
        if self.numEntered > len(self.game.players):
            self.notify.info('%d player(s) failed in maze game' %
                             (self.numEntered - len(self.game.players)))

    def lostMemos(self):
        self.pickupCount = 0
        self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeGameTimeOut)
        self._guiMgr.setPickupCount(self.pickupCount)
        self.notify.info(
            'toonId:%d laff:%d/%d  %d player(s) failed in maze game' %
            (self.toon.doId, self.toon.hp, self.toon.maxHp,
             len(self.game.players)))
Beispiel #8
0
class DistributedCogThiefGame(DistributedMinigame):
    notify = directNotify.newCategory('DistributedCogThiefGame')
    ToonSpeed = CTGG.ToonSpeed
    StageHalfWidth = 200.0
    StageHalfHeight = 100.0
    BarrelScale = 0.25
    TOON_Z = 0
    UPDATE_SUITS_TASK = 'CogThiefGameUpdateSuitsTask'
    REWARD_COUNTDOWN_TASK = 'cogThiefGameRewardCountdown'
    ControlKeyLimitTime = 1.0

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedCogThiefGame', [
            State.State('off', self.enterOff, self.exitOff, ['play']),
            State.State('play', self.enterPlay, self.exitPlay, ['cleanup']),
            State.State('cleanup', self.enterCleanup, self.exitCleanup, [])
        ], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.cameraTopView = (0, 0, 55, 0, -90.0, 0)
        self.barrels = []
        self.cogInfo = {}
        self.lastTimeControlPressed = 0
        self.stolenBarrels = []
        self.useOrthoWalk = base.config.GetBool('cog-thief-ortho', 1)
        self.resultIval = None
        self.gameIsEnding = False
        self.__textGen = TextNode('cogThiefGame')
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        return

    def getTitle(self):
        return TTLocalizer.CogThiefGameTitle

    def getInstructions(self):
        return TTLocalizer.CogThiefGameInstructions

    def getMaxDuration(self):
        return 0

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.music = base.loadMusic('phase_4/audio/bgm/MG_CogThief.ogg')
        self.initCogInfo()
        for barrelIndex in xrange(CTGG.NumBarrels):
            barrel = loader.loadModel(
                'phase_4/models/minigames/cogthief_game_gagTank')
            barrel.setPos(CTGG.BarrelStartingPositions[barrelIndex])
            barrel.setScale(self.BarrelScale)
            barrel.reparentTo(render)
            barrel.setTag('barrelIndex', str(barrelIndex))
            collSphere = CollisionSphere(0, 0, 0, 4)
            collSphere.setTangible(0)
            name = 'BarrelSphere-%d' % barrelIndex
            collSphereName = self.uniqueName(name)
            collNode = CollisionNode(collSphereName)
            collNode.setFromCollideMask(CTGG.BarrelBitmask)
            collNode.addSolid(collSphere)
            colNp = barrel.attachNewNode(collNode)
            handler = CollisionHandlerEvent()
            handler.setInPattern('barrelHit-%fn')
            base.cTrav.addCollider(colNp, handler)
            self.accept('barrelHit-' + collSphereName, self.handleEnterBarrel)
            nodeToHide = '**/gagMoneyTen'
            if barrelIndex % 2:
                nodeToHide = '**/gagMoneyFive'
            iconToHide = barrel.find(nodeToHide)
            if not iconToHide.isEmpty():
                iconToHide.hide()
            self.barrels.append(barrel)

        self.gameBoard = loader.loadModel(
            'phase_4/models/minigames/cogthief_game')
        self.gameBoard.find('**/floor_TT').hide()
        self.gameBoard.find('**/floor_DD').hide()
        self.gameBoard.find('**/floor_DG').hide()
        self.gameBoard.find('**/floor_MM').hide()
        self.gameBoard.find('**/floor_BR').hide()
        self.gameBoard.find('**/floor_DL').hide()
        zone = self.getSafezoneId()
        if zone == ToontownGlobals.ToontownCentral:
            self.gameBoard.find('**/floor_TT').show()
        elif zone == ToontownGlobals.DonaldsDock:
            self.gameBoard.find('**/floor_DD').show()
        elif zone == ToontownGlobals.DaisyGardens:
            self.gameBoard.find('**/floor_DG').show()
        elif zone == ToontownGlobals.MinniesMelodyland:
            self.gameBoard.find('**/floor_MM').show()
        elif zone == ToontownGlobals.TheBrrrgh:
            self.gameBoard.find('**/floor_BR').show()
        elif zone == ToontownGlobals.DonaldsDreamland:
            self.gameBoard.find('**/floor_DL').show()
        else:
            self.gameBoard.find('**/floor_TT').show()
        self.gameBoard.setPosHpr(0, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        self.toonSDs = {}
        avId = self.localAvId
        toonSD = CogThiefGameToonSD.CogThiefGameToonSD(avId, self)
        self.toonSDs[avId] = toonSD
        toonSD.load()
        self.loadCogs()
        self.toonHitTracks = {}
        self.toonPieTracks = {}
        self.sndOof = base.loadSfx('phase_4/audio/sfx/MG_cannon_hit_dirt.ogg')
        self.sndRewardTick = base.loadSfx(
            'phase_3.5/audio/sfx/tick_counter.ogg')
        self.sndPerfect = base.loadSfx('phase_4/audio/sfx/ring_perfect.ogg')
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.hide()
        purchaseModels = loader.loadModel('phase_4/models/gui/purchase_gui')
        self.jarImage = purchaseModels.find('**/Jar')
        self.jarImage.reparentTo(hidden)
        self.rewardPanel = DirectLabel(parent=hidden,
                                       relief=None,
                                       pos=(-0.173, 0.0, -0.55),
                                       scale=0.65,
                                       text='',
                                       text_scale=0.2,
                                       text_fg=(0.95, 0.95, 0, 1),
                                       text_pos=(0, -.13),
                                       text_font=ToontownGlobals.getSignFont(),
                                       image=self.jarImage)
        self.rewardPanelTitle = DirectLabel(parent=self.rewardPanel,
                                            relief=None,
                                            pos=(0, 0, 0.06),
                                            scale=0.08,
                                            text=TTLocalizer.CannonGameReward,
                                            text_fg=(0.95, 0.95, 0, 1),
                                            text_shadow=(0, 0, 0, 1))
        return

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        del self.music
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM
        self.gameBoard.removeNode()
        del self.gameBoard
        for barrel in self.barrels:
            barrel.removeNode()

        del self.barrels
        for avId in self.toonSDs.keys():
            toonSD = self.toonSDs[avId]
            toonSD.unload()

        del self.toonSDs
        self.timer.destroy()
        del self.timer
        self.rewardPanel.destroy()
        del self.rewardPanel
        self.jarImage.removeNode()
        del self.jarImage
        del self.sndRewardTick

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.gameBoard.reparentTo(render)
        lt = base.localAvatar
        lt.reparentTo(render)
        self.__placeToon(self.localAvId)
        lt.setSpeed(0, 0)
        self.moveCameraToTop()
        toonSD = self.toonSDs[self.localAvId]
        toonSD.enter()
        toonSD.fsm.request('normal')
        self.stopGameWalk()
        for cogIndex in xrange(self.getNumCogs()):
            suit = self.cogInfo[cogIndex]['suit'].suit
            pos = self.cogInfo[cogIndex]['pos']
            suit.reparentTo(self.gameBoard)
            suit.setPos(pos)
            suit.nametag3d.stash()
            suit.nametag.destroy()

        for avId in self.avIdList:
            self.toonHitTracks[avId] = Wait(0.1)

        self.toonRNGs = []
        for i in xrange(self.numPlayers):
            self.toonRNGs.append(RandomNumGen.RandomNumGen(self.randomNumGen))

        self.sndTable = {
            'hitBySuit': [None] * self.numPlayers,
            'falling': [None] * self.numPlayers
        }
        for i in xrange(self.numPlayers):
            self.sndTable['hitBySuit'][i] = base.loadSfx(
                'phase_4/audio/sfx/MG_Tag_C.ogg')
            self.sndTable['falling'][i] = base.loadSfx(
                'phase_4/audio/sfx/MG_cannon_whizz.ogg')

        base.playMusic(self.music, looping=1, volume=0.8)
        self.introTrack = self.getIntroTrack()
        self.introTrack.start()
        return

    def offstage(self):
        self.notify.debug('offstage')
        self.gameBoard.hide()
        self.music.stop()
        for barrel in self.barrels:
            barrel.hide()

        for avId in self.toonSDs.keys():
            self.toonSDs[avId].exit()

        for avId in self.avIdList:
            av = self.getAvatar(avId)
            if av:
                av.resetLOD()

        self.timer.reparentTo(hidden)
        self.rewardPanel.reparentTo(hidden)
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        del self.introTrack
        DistributedMinigame.offstage(self)

    def handleDisabledAvatar(self, avId):
        self.notify.debug('handleDisabledAvatar')
        self.notify.debug('avatar ' + str(avId) + ' disabled')
        self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]
        DistributedMinigame.handleDisabledAvatar(self, avId)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self.__placeToon(avId)
                toon.useLOD(1000)
                toonSD = CogThiefGameToonSD.CogThiefGameToonSD(avId, self)
                self.toonSDs[avId] = toonSD
                toonSD.load()
                toonSD.enter()
                toonSD.fsm.request('normal')
                toon.startSmooth()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        if not base.config.GetBool('cog-thief-endless', 0):
            self.timer.show()
            self.timer.countdown(CTGG.GameTime, self.__gameTimerExpired)
        self.clockStopTime = None
        self.rewardPanel.reparentTo(base.a2dTopRight)
        self.scoreMult = MinigameGlobals.getScoreMult(self.cr.playGame.hood.id)
        self.__startRewardCountdown()
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        self.gameFSM.request('play')
        return

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        self.startGameWalk()
        self.spawnUpdateSuitsTask()
        self.accept(base.JUMP, self.controlKeyPressed)
        self.pieHandler = CollisionHandlerEvent()
        self.pieHandler.setInPattern('pieHit-%fn')

    def exitPlay(self):
        self.ignore(base.JUMP)
        if self.resultIval and self.resultIval.isPlaying():
            self.resultIval.finish()
            self.resultIval = None
        return

    def enterCleanup(self):
        self.__killRewardCountdown()
        if hasattr(self, 'jarIval'):
            self.jarIval.finish()
            del self.jarIval
        for key in self.toonHitTracks:
            ival = self.toonHitTracks[key]
            if ival.isPlaying():
                ival.finish()

        self.toonHitTracks = {}
        for key in self.toonPieTracks:
            ival = self.toonPieTracks[key]
            if ival.isPlaying():
                ival.finish()

        self.toonPieTracks = {}
        for key in self.cogInfo:
            cogThief = self.cogInfo[key]['suit']
            cogThief.cleanup()

        self.removeUpdateSuitsTask()
        self.notify.debug('enterCleanup')

    def exitCleanup(self):
        pass

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if toon:
            index = self.avIdList.index(avId)
            toon.setPos(CTGG.ToonStartingPositions[index])
            toon.setHpr(0, 0, 0)

    def moveCameraToTop(self):
        camera.reparentTo(render)
        p = self.cameraTopView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])
        base.camLens.setMinFov(46 / (4. / 3.))
        camera.setZ(camera.getZ() +
                    base.config.GetFloat('cog-thief-z-camera-adjust', 0.0))

    def destroyGameWalk(self):
        self.notify.debug('destroyOrthoWalk')
        if self.useOrthoWalk:
            self.gameWalk.destroy()
            del self.gameWalk
        else:
            self.notify.debug('TODO destroyGameWalk')

    def initGameWalk(self):
        self.notify.debug('startOrthoWalk')
        if self.useOrthoWalk:

            def doCollisions(oldPos, newPos, self=self):
                x = bound(newPos[0], CTGG.StageHalfWidth, -CTGG.StageHalfWidth)
                y = bound(newPos[1], CTGG.StageHalfHeight,
                          -CTGG.StageHalfHeight)
                newPos.setX(x)
                newPos.setY(y)
                return newPos

            orthoDrive = OrthoDrive(self.ToonSpeed,
                                    customCollisionCallback=doCollisions,
                                    instantTurn=True)
            self.gameWalk = OrthoWalk(orthoDrive,
                                      broadcast=not self.isSinglePlayer())
        else:
            self.gameWalk = CogThiefWalk.CogThiefWalk('walkDone')
            forwardSpeed = self.ToonSpeed / 2.0
            base.mouseInterfaceNode.setForwardSpeed(forwardSpeed)
            multiplier = forwardSpeed / ToontownGlobals.ToonForwardSpeed
            base.mouseInterfaceNode.setRotateSpeed(
                ToontownGlobals.ToonRotateSpeed * 4)

    def initCogInfo(self):
        for cogIndex in xrange(self.getNumCogs()):
            self.cogInfo[cogIndex] = {
                'pos': Point3(CTGG.CogStartingPositions[cogIndex]),
                'goal': CTGG.NoGoal,
                'goalId': CTGG.InvalidGoalId,
                'suit': None
            }

        return

    def loadCogs(self):
        suitTypes = ['ds', 'ac', 'bc', 'ms']
        for suitIndex in xrange(self.getNumCogs()):
            st = self.randomNumGen.choice(suitTypes)
            suit = CogThief.CogThief(suitIndex, st, self, self.getCogSpeed())
            self.cogInfo[suitIndex]['suit'] = suit

    def handleEnterSphere(self, colEntry):
        if self.gameIsEnding:
            return
        intoName = colEntry.getIntoNodePath().getName()
        fromName = colEntry.getFromNodePath().getName()
        debugInto = intoName.split('/')
        debugFrom = fromName.split('/')
        self.notify.debug(
            'handleEnterSphere gametime=%s %s into %s' %
            (self.getCurrentGameTime(), debugFrom[-1], debugInto[-1]))
        intoName = colEntry.getIntoNodePath().getName()
        if 'CogThiefSphere' in intoName:
            parts = intoName.split('-')
            suitNum = int(parts[1])
            self.localToonHitBySuit(suitNum)

    def localToonHitBySuit(self, suitNum):
        self.notify.debug('localToonHitBySuit %d' % suitNum)
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime(), bits=32)
        pos = self.cogInfo[suitNum]['suit'].suit.getPos()
        self.sendUpdate(
            'hitBySuit',
            [self.localAvId, timestamp, suitNum, pos[0], pos[1], pos[2]])
        self.showToonHitBySuit(self.localAvId, timestamp)
        self.makeSuitRespondToToonHit(timestamp, suitNum)

    def hitBySuit(self, avId, timestamp, suitNum, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ['play']:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        if self.gameIsEnding:
            return
        self.notify.debug('avatar ' + ` avId ` + ' hit by a suit')
        if avId != self.localAvId:
            self.showToonHitBySuit(avId, timestamp)
            self.makeSuitRespondToToonHit(timestamp, suitNum)

    def showToonHitBySuit(self, avId, timestamp):
        toon = self.getAvatar(avId)
        if toon == None:
            return
        rng = self.toonRNGs[self.avIdList.index(avId)]
        curPos = toon.getPos(render)
        oldTrack = self.toonHitTracks[avId]
        if oldTrack.isPlaying():
            oldTrack.finish()
        toon.setPos(curPos)
        toon.setZ(self.TOON_Z)
        parentNode = render.attachNewNode('mazeFlyToonParent-' + ` avId `)
        parentNode.setPos(toon.getPos())
        toon.reparentTo(parentNode)
        toon.setPos(0, 0, 0)
        startPos = parentNode.getPos()
        dropShadow = toon.dropShadow.copyTo(parentNode)
        dropShadow.setScale(toon.dropShadow.getScale(render))
        trajectory = Trajectory.Trajectory(0,
                                           Point3(0, 0, 0),
                                           Point3(0, 0, 50),
                                           gravMult=1.0)
        oldFlyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        trajectory = Trajectory.Trajectory(0,
                                           Point3(0, 0, 0),
                                           Point3(0, 0, 50),
                                           gravMult=0.55)
        flyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        avIndex = self.avIdList.index(avId)
        endPos = CTGG.ToonStartingPositions[avIndex]

        def flyFunc(t,
                    trajectory,
                    startPos=startPos,
                    endPos=endPos,
                    dur=flyDur,
                    moveNode=parentNode,
                    flyNode=toon):
            u = t / dur
            moveNode.setX(startPos[0] + u * (endPos[0] - startPos[0]))
            moveNode.setY(startPos[1] + u * (endPos[1] - startPos[1]))
            flyNode.setPos(trajectory.getPos(t))

        flyTrack = Sequence(LerpFunctionInterval(flyFunc,
                                                 fromData=0.0,
                                                 toData=flyDur,
                                                 duration=flyDur,
                                                 extraArgs=[trajectory]),
                            name=toon.uniqueName('hitBySuit-fly'))
        geomNode = toon.getGeomNode()
        startHpr = geomNode.getHpr()
        destHpr = Point3(startHpr)
        hRot = rng.randrange(1, 8)
        if rng.choice([0, 1]):
            hRot = -hRot
        destHpr.setX(destHpr[0] + hRot * 360)
        spinHTrack = Sequence(LerpHprInterval(geomNode,
                                              flyDur,
                                              destHpr,
                                              startHpr=startHpr),
                              Func(geomNode.setHpr, startHpr),
                              name=toon.uniqueName('hitBySuit-spinH'))
        parent = geomNode.getParent()
        rotNode = parent.attachNewNode('rotNode')
        geomNode.reparentTo(rotNode)
        rotNode.setZ(toon.getHeight() / 2.0)
        oldGeomNodeZ = geomNode.getZ()
        geomNode.setZ(-toon.getHeight() / 2.0)
        startHpr = rotNode.getHpr()
        destHpr = Point3(startHpr)
        pRot = rng.randrange(1, 3)
        if rng.choice([0, 1]):
            pRot = -pRot
        destHpr.setY(destHpr[1] + pRot * 360)
        spinPTrack = Sequence(LerpHprInterval(rotNode,
                                              flyDur,
                                              destHpr,
                                              startHpr=startHpr),
                              Func(rotNode.setHpr, startHpr),
                              name=toon.uniqueName('hitBySuit-spinP'))
        i = self.avIdList.index(avId)
        soundTrack = Sequence(Func(base.playSfx,
                                   self.sndTable['hitBySuit'][i]),
                              Wait(flyDur * (2.0 / 3.0)),
                              SoundInterval(self.sndTable['falling'][i],
                                            duration=flyDur * (1.0 / 3.0)),
                              name=toon.uniqueName('hitBySuit-soundTrack'))

        def preFunc(self=self, avId=avId, toon=toon, dropShadow=dropShadow):
            forwardSpeed = toon.forwardSpeed
            rotateSpeed = toon.rotateSpeed
            if avId == self.localAvId:
                self.stopGameWalk()
            else:
                toon.stopSmooth()
            if forwardSpeed or rotateSpeed:
                toon.setSpeed(forwardSpeed, rotateSpeed)
            toon.dropShadow.hide()

        def postFunc(self=self,
                     avId=avId,
                     oldGeomNodeZ=oldGeomNodeZ,
                     dropShadow=dropShadow,
                     parentNode=parentNode):
            if avId == self.localAvId:
                base.localAvatar.setPos(endPos)
                if hasattr(self, 'gameWalk'):
                    toon = base.localAvatar
                    toon.setSpeed(0, 0)
                    self.startGameWalk()
            dropShadow.removeNode()
            del dropShadow
            toon = self.getAvatar(avId)
            if toon:
                toon.dropShadow.show()
                geomNode = toon.getGeomNode()
                rotNode = geomNode.getParent()
                baseNode = rotNode.getParent()
                geomNode.reparentTo(baseNode)
                rotNode.removeNode()
                del rotNode
                geomNode.setZ(oldGeomNodeZ)
            if toon:
                toon.reparentTo(render)
                toon.setPos(endPos)
            parentNode.removeNode()
            del parentNode
            if avId != self.localAvId:
                if toon:
                    toon.startSmooth()

        preFunc()
        slipBack = Parallel(
            Sequence(ActorInterval(toon, 'slip-backward', endFrame=24),
                     Wait(CTGG.LyingDownDuration - (flyDur - oldFlyDur)),
                     ActorInterval(toon, 'slip-backward', startFrame=24)))
        if toon.doId == self.localAvId:
            slipBack.append(SoundInterval(self.sndOof))
        hitTrack = Sequence(Parallel(flyTrack, spinHTrack, spinPTrack,
                                     soundTrack),
                            slipBack,
                            Func(postFunc),
                            name=toon.uniqueName('hitBySuit'))
        self.notify.debug('hitTrack duration = %s' % hitTrack.getDuration())
        self.toonHitTracks[avId] = hitTrack
        hitTrack.start(globalClockDelta.localElapsedTime(timestamp))
        return

    def updateSuitGoal(self, timestamp, inResponseToClientStamp, suitNum,
                       goalType, goalId, x, y, z):
        if not self.hasLocalToon:
            return
        self.notify.debug(
            'updateSuitGoal gameTime=%s timeStamp=%s cog=%s goal=%s goalId=%s (%.1f, %.1f,%.1f)'
            % (self.getCurrentGameTime(), timestamp, suitNum,
               CTGG.GoalStr[goalType], goalId, x, y, z))
        cog = self.cogInfo[suitNum]
        cog['goal'] = goalType
        cog['goalId'] = goalId
        newPos = Point3(x, y, z)
        cog['pos'] = newPos
        suit = cog['suit']
        suit.updateGoal(timestamp, inResponseToClientStamp, goalType, goalId,
                        newPos)

    def spawnUpdateSuitsTask(self):
        self.notify.debug('spawnUpdateSuitsTask')
        for cogIndex in self.cogInfo:
            suit = self.cogInfo[cogIndex]['suit']
            suit.gameStart(self.gameStartTime)

        taskMgr.remove(self.UPDATE_SUITS_TASK)
        taskMgr.add(self.updateSuitsTask, self.UPDATE_SUITS_TASK)

    def removeUpdateSuitsTask(self):
        taskMgr.remove(self.UPDATE_SUITS_TASK)

    def updateSuitsTask(self, task):
        if self.gameIsEnding:
            return task.done
        for cogIndex in self.cogInfo:
            suit = self.cogInfo[cogIndex]['suit']
            suit.think()

        return task.cont

    def makeSuitRespondToToonHit(self, timestamp, suitNum):
        cog = self.cogInfo[suitNum]['suit']
        cog.respondToToonHit(timestamp)

    def handleEnterBarrel(self, colEntry):
        if self.gameIsEnding:
            return
        intoName = colEntry.getIntoNodePath().getName()
        fromName = colEntry.getFromNodePath().getName()
        debugInto = intoName.split('/')
        debugFrom = fromName.split('/')
        self.notify.debug(
            'handleEnterBarrel gameTime=%s %s into %s' %
            (self.getCurrentGameTime(), debugFrom[-1], debugInto[-1]))
        if 'CogThiefSphere' in intoName:
            parts = intoName.split('-')
            cogIndex = int(parts[1])
            barrelName = colEntry.getFromNodePath().getName()
            barrelParts = barrelName.split('-')
            barrelIndex = int(barrelParts[1])
            cog = self.cogInfo[cogIndex]['suit']
            if cog.barrel == CTGG.NoBarrelCarried and barrelIndex not in self.stolenBarrels:
                timestamp = globalClockDelta.localToNetworkTime(
                    globalClock.getFrameTime(), bits=32)
                if cog.suit:
                    cogPos = cog.suit.getPos()
                    collisionPos = colEntry.getContactPos(render)
                    self.sendUpdate('cogHitBarrel', [
                        timestamp, cogIndex, barrelIndex, cogPos[0], cogPos[1],
                        cogPos[2]
                    ])

    def makeCogCarryBarrel(self, timestamp, inResponseToClientStamp, cogIndex,
                           barrelIndex, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameIsEnding:
            return
        self.notify.debug(
            'makeCogCarryBarrel gameTime=%s timeStamp=%s cog=%s barrel=%s (%.1f, %.1f,%.1f)'
            % (self.getCurrentGameTime(), timestamp, cogIndex, barrelIndex, x,
               y, z))
        barrel = self.barrels[barrelIndex]
        self.notify.debug('barrelPos= %s' % barrel.getPos())
        cog = self.cogInfo[cogIndex]['suit']
        cogPos = Point3(x, y, z)
        cog.makeCogCarryBarrel(timestamp, inResponseToClientStamp, barrel,
                               barrelIndex, cogPos)

    def makeCogDropBarrel(self, timestamp, inResponseToClientStamp, cogIndex,
                          barrelIndex, x, y, z):
        if not self.hasLocalToon:
            return
        self.notify.debug(
            'makeCogDropBarrel gameTime=%s timeStamp=%s cog=%s barrel=%s (%.1f, %.1f,%.1f)'
            % (self.getCurrentGameTime(), timestamp, cogIndex, barrelIndex, x,
               y, z))
        barrel = self.barrels[barrelIndex]
        self.notify.debug('barrelPos= %s' % barrel.getPos())
        cog = self.cogInfo[cogIndex]['suit']
        cogPos = Point3(x, y, z)
        cog.makeCogDropBarrel(timestamp, inResponseToClientStamp, barrel,
                              barrelIndex, cogPos)

    def controlKeyPressed(self):
        if self.isToonPlayingHitTrack(self.localAvId):
            return
        if self.gameIsEnding:
            return
        if self.getCurrentGameTime(
        ) - self.lastTimeControlPressed > self.ControlKeyLimitTime:
            self.lastTimeControlPressed = self.getCurrentGameTime()
            self.notify.debug('controlKeyPressed')
            toonSD = self.toonSDs[self.localAvId]
            curState = toonSD.fsm.getCurrentState().getName()
            toon = self.getAvatar(self.localAvId)
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime(), bits=32)
            pos = toon.getPos()
            heading = toon.getH()
            self.sendUpdate(
                'throwingPie',
                [self.localAvId, timestamp, heading, pos[0], pos[1], pos[2]])
            self.showToonThrowingPie(self.localAvId, timestamp, heading, pos)

    def throwingPie(self, avId, timestamp, heading, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ['play']:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        self.notify.debug('avatar ' + ` avId ` + ' throwing pie')
        if avId != self.localAvId:
            pos = Point3(x, y, z)
            self.showToonThrowingPie(avId, timestamp, heading, pos)

    def showToonThrowingPie(self, avId, timestamp, heading, pos):
        toon = self.getAvatar(avId)
        if toon:
            tossTrack, pieTrack, flyPie = self.getTossPieInterval(
                toon, pos[0], pos[1], pos[2], heading, 0, 0, 0)

            def removePieFromTraverser(flyPie=flyPie):
                if base.cTrav:
                    if flyPie:
                        base.cTrav.removeCollider(flyPie)

            if avId == self.localAvId:
                flyPie.setTag('throwerId', str(avId))
                collSphere = CollisionSphere(0, 0, 0, 0.5)
                collSphere.setTangible(0)
                name = 'PieSphere-%d' % avId
                collSphereName = self.uniqueName(name)
                collNode = CollisionNode(collSphereName)
                collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
                collNode.addSolid(collSphere)
                colNp = flyPie.attachNewNode(collNode)
                colNp.show()
                base.cTrav.addCollider(colNp, self.pieHandler)
                self.accept('pieHit-' + collSphereName, self.handlePieHitting)

            def matchRunningAnim(toon=toon):
                toon.playingAnim = None
                toon.setSpeed(toon.forwardSpeed, toon.rotateSpeed)
                return

            newTossTrack = Sequence(tossTrack, Func(matchRunningAnim))
            pieTrack = Parallel(newTossTrack, pieTrack)
            elapsedTime = globalClockDelta.localElapsedTime(timestamp)
            if elapsedTime < 16.0 / 24.0:
                elapsedTime = 16.0 / 24.0
            pieTrack.start(elapsedTime)
            self.toonPieTracks[avId] = pieTrack

    def getTossPieInterval(self,
                           toon,
                           x,
                           y,
                           z,
                           h,
                           p,
                           r,
                           power,
                           beginFlyIval=Sequence()):
        from src.toontown.toonbase import ToontownBattleGlobals
        from src.toontown.battle import BattleProps
        pie = toon.getPieModel()
        pie.setScale(0.9)
        flyPie = pie.copyTo(NodePath('a'))
        pieName = ToontownBattleGlobals.pieNames[toon.pieType]
        pieType = BattleProps.globalPropPool.getPropType(pieName)
        animPie = Sequence()
        if pieType == 'actor':
            animPie = ActorInterval(pie, pieName, startFrame=48)
        sound = loader.loadSfx('phase_3.5/audio/sfx/AA_pie_throw_only.ogg')
        t = power / 100.0
        dist = 100 - 70 * t
        time = 1 + 0.5 * t
        proj = ProjectileInterval(None,
                                  startPos=Point3(0, 0, 0),
                                  endPos=Point3(0, dist, 0),
                                  duration=time)
        relVel = proj.startVel

        def getVelocity(toon=toon, relVel=relVel):
            return render.getRelativeVector(toon, relVel) * 0.6

        toss = Track(
            (0,
             Sequence(
                 Func(toon.setPosHpr, x, y, z, h, p, r),
                 Func(pie.reparentTo, toon.rightHand),
                 Func(pie.setPosHpr, 0, 0, 0, 0, 0, 0),
                 Parallel(
                     ActorInterval(
                         toon, 'throw', startFrame=48, partName='torso'),
                     animPie), Func(toon.loop, 'neutral'))),
            (16.0 / 24.0, Func(pie.detachNode)))
        fly = Track(
            (14.0 / 24.0, SoundInterval(sound, node=toon)),
            (16.0 / 24.0,
             Sequence(
                 Func(flyPie.reparentTo, render),
                 Func(flyPie.setPosHpr, toon, 0.52, 0.97, 2.24, 0, -45, 0),
                 beginFlyIval,
                 ProjectileInterval(flyPie, startVel=getVelocity, duration=6),
                 Func(flyPie.detachNode))))
        return (toss, fly, flyPie)

    def handlePieHitting(self, colEntry):
        if self.gameIsEnding:
            return
        into = colEntry.getIntoNodePath()
        intoName = into.getName()
        if 'CogThiefPieSphere' in intoName:
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime(), bits=32)
            parts = intoName.split('-')
            suitNum = int(parts[1])
            pos = self.cogInfo[suitNum]['suit'].suit.getPos()
            if pos in CTGG.CogStartingPositions:
                self.notify.debug('Cog %d hit at starting pos %s, ignoring' %
                                  (suitNum, pos))
            else:
                self.sendUpdate('pieHitSuit', [
                    self.localAvId, timestamp, suitNum, pos[0], pos[1], pos[2]
                ])
                self.makeSuitRespondToPieHit(timestamp, suitNum)

    def pieHitSuit(self, avId, timestamp, suitNum, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ['play']:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        if self.gameIsEnding:
            return
        self.notify.debug('avatar ' + ` avId ` + ' hit by a suit')
        if avId != self.localAvId:
            self.makeSuitRespondToPieHit(timestamp, suitNum)

    def makeSuitRespondToPieHit(self, timestamp, suitNum):
        cog = self.cogInfo[suitNum]['suit']
        cog.respondToPieHit(timestamp)

    def sendCogAtReturnPos(self, cogIndex, barrelIndex):
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime(), bits=32)
        self.sendUpdate('cogAtReturnPos', [timestamp, cogIndex, barrelIndex])

    def markBarrelStolen(self, timestamp, inResponseToClientStamp,
                         barrelIndex):
        if not self.hasLocalToon:
            return
        if barrelIndex not in self.stolenBarrels:
            self.stolenBarrels.append(barrelIndex)
            barrel = self.barrels[barrelIndex]
            barrel.hide()
        if base.config.GetBool('cog-thief-check-barrels', 1):
            if not base.config.GetBool('cog-thief-endless', 0):
                if len(self.stolenBarrels) == len(self.barrels):
                    localStamp = globalClockDelta.networkToLocalTime(timestamp,
                                                                     bits=32)
                    gameTime = self.local2GameTime(localStamp)
                    self.clockStopTime = gameTime
                    self.notify.debug('clockStopTime = %s' % gameTime)
                    score = int(self.scoreMult * CTGG.calcScore(gameTime) +
                                0.5)
                    self.rewardPanel['text'] = str(score)
                    self.showResults()

    def __gameTimerExpired(self):
        self.notify.debug('game timer expired')
        self.showResults()

    def __startRewardCountdown(self):
        taskMgr.remove(self.REWARD_COUNTDOWN_TASK)
        taskMgr.add(self.__updateRewardCountdown, self.REWARD_COUNTDOWN_TASK)

    def __killRewardCountdown(self):
        taskMgr.remove(self.REWARD_COUNTDOWN_TASK)

    def __updateRewardCountdown(self, task):
        curTime = self.getCurrentGameTime()
        if self.clockStopTime is not None:
            if self.clockStopTime < curTime:
                self.notify.debug('self.clockStopTime < curTime %s %s' %
                                  (self.clockStopTime, curTime))
                self.__killRewardCountdown()
                curTime = self.clockStopTime
        if curTime > CTGG.GameTime:
            curTime = CTGG.GameTime
        score = int(self.scoreMult * CTGG.calcScore(curTime) + 0.5)
        if not hasattr(task, 'curScore'):
            task.curScore = score
        result = Task.cont
        if hasattr(self, 'rewardPanel'):
            self.rewardPanel['text'] = str(score)
            if task.curScore != score:
                if hasattr(self, 'jarIval'):
                    self.jarIval.finish()
                s = self.rewardPanel.getScale()
                self.jarIval = Parallel(Sequence(
                    self.rewardPanel.scaleInterval(0.15,
                                                   s * 3.0 / 4.0,
                                                   blendType='easeOut'),
                    self.rewardPanel.scaleInterval(0.15, s,
                                                   blendType='easeIn')),
                                        SoundInterval(self.sndRewardTick),
                                        name='cogThiefGameRewardJarThrob')
                self.jarIval.start()
            task.curScore = score
        else:
            result = Task.done
        return result

    def startGameWalk(self):
        if self.useOrthoWalk:
            self.gameWalk.start()
        else:
            self.gameWalk.enter()
            self.gameWalk.fsm.request('walking')

    def stopGameWalk(self):
        if self.useOrthoWalk:
            self.gameWalk.stop()
        else:
            self.gameWalk.exit()

    def getCogThief(self, cogIndex):
        return self.cogInfo[cogIndex]['suit']

    def isToonPlayingHitTrack(self, avId):
        if avId in self.toonHitTracks:
            track = self.toonHitTracks[avId]
            if track.isPlaying():
                return True
        return False

    def getNumCogs(self):
        result = base.config.GetInt('cog-thief-num-cogs', 0)
        if not result:
            safezone = self.getSafezoneId()
            result = CTGG.calculateCogs(self.numPlayers, safezone)
        return result

    def getCogSpeed(self):
        result = 6.0
        safezone = self.getSafezoneId()
        result = CTGG.calculateCogSpeed(self.numPlayers, safezone)
        return result

    def showResults(self):
        if not self.gameIsEnding:
            self.gameIsEnding = True
            for barrel in self.barrels:
                barrel.wrtReparentTo(render)

            for key in self.cogInfo:
                thief = self.cogInfo[key]['suit']
                thief.suit.setPos(100, 0, 0)
                thief.suit.hide()

            self.__killRewardCountdown()
            self.stopGameWalk()
            numBarrelsSaved = len(self.barrels) - len(self.stolenBarrels)
            resultStr = ''
            if numBarrelsSaved == len(self.barrels):
                resultStr = TTLocalizer.CogThiefPerfect
            elif numBarrelsSaved > 1:
                resultStr = TTLocalizer.CogThiefBarrelsSaved % {
                    'num': numBarrelsSaved
                }
            elif numBarrelsSaved == 1:
                resultStr = TTLocalizer.CogThiefBarrelSaved % {
                    'num': numBarrelsSaved
                }
            else:
                resultStr = TTLocalizer.CogThiefNoBarrelsSaved
            perfectTextSubnode = hidden.attachNewNode(
                self.__genText(resultStr))
            perfectText = hidden.attachNewNode('perfectText')
            perfectTextSubnode.reparentTo(perfectText)
            frame = self.__textGen.getCardActual()
            offsetY = -abs(frame[2] + frame[3]) / 2.0
            perfectTextSubnode.setPos(0, 0, offsetY)
            perfectText.setColor(1, 0.1, 0.1, 1)

            def fadeFunc(t, text=perfectText):
                text.setColorScale(1, 1, 1, t)

            def destroyText(text=perfectText):
                text.removeNode()

            def safeGameOver(self=self):
                if not self.frameworkFSM.isInternalStateInFlux():
                    self.gameOver()

            textTrack = Sequence(
                Func(perfectText.reparentTo, aspect2d),
                Parallel(
                    LerpScaleInterval(perfectText,
                                      duration=0.5,
                                      scale=0.3,
                                      startScale=0.0),
                    LerpFunctionInterval(fadeFunc,
                                         fromData=0.0,
                                         toData=1.0,
                                         duration=0.5)), Wait(2.0),
                Parallel(
                    LerpScaleInterval(perfectText, duration=0.5, scale=1.0),
                    LerpFunctionInterval(fadeFunc,
                                         fromData=1.0,
                                         toData=0.0,
                                         duration=0.5,
                                         blendType='easeIn')),
                Func(destroyText), WaitInterval(0.5), Func(safeGameOver))
            if numBarrelsSaved == len(self.barrels):
                soundTrack = SoundInterval(self.sndPerfect)
            else:
                soundTrack = Sequence()
            self.resultIval = Parallel(textTrack, soundTrack)
            self.resultIval.start()

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def getIntroTrack(self):
        base.camera.setPosHpr(0, -13.66, 13.59, 0, -51.6, 0)
        result = Sequence(
            Wait(2),
            LerpPosHprInterval(base.camera,
                               13,
                               Point3(self.cameraTopView[0],
                                      self.cameraTopView[1],
                                      self.cameraTopView[2]),
                               Point3(self.cameraTopView[3],
                                      self.cameraTopView[4],
                                      self.cameraTopView[5]),
                               blendType='easeIn'))
        return result
Beispiel #9
0
class DistributedPartyCatchActivity(DistributedPartyActivity,
                                    DistributedPartyCatchActivityBase):
    notify = DirectNotifyGlobal.directNotify.newCategory(
        'DistributedPartyCatchActivity')
    DropTaskName = 'dropSomething'
    DropObjectPlurals = {
        'apple': TTLocalizer.PartyCatchActivityApples,
        'orange': TTLocalizer.PartyCatchActivityOranges,
        'pear': TTLocalizer.PartyCatchActivityPears,
        'coconut': TTLocalizer.PartyCatchActivityCoconuts,
        'watermelon': TTLocalizer.PartyCatchActivityWatermelons,
        'pineapple': TTLocalizer.PartyCatchActivityPineapples,
        'anvil': TTLocalizer.PartyCatchActivityAnvils
    }

    class Generation:
        def __init__(self, generation, startTime, startNetworkTime,
                     numPlayers):
            self.generation = generation
            self.startTime = startTime
            self.startNetworkTime = startNetworkTime
            self.numPlayers = numPlayers
            self.hasBeenScheduled = False
            self.droppedObjNames = []
            self.dropSchedule = []
            self.numItemsDropped = 0
            self.droppedObjCaught = {}

    def __init__(self, cr):
        DistributedPartyActivity.__init__(
            self,
            cr,
            PartyGlobals.ActivityIds.PartyCatch,
            PartyGlobals.ActivityTypes.HostInitiated,
            wantRewardGui=True)
        self.setUsesSmoothing()
        self.setUsesLookAround()
        self._sNumGen = SerialNumGen()

    def getTitle(self):
        return TTLocalizer.PartyCatchActivityTitle

    def getInstructions(self):
        return TTLocalizer.PartyCatchActivityInstructions % {
            'badThing': self.DropObjectPlurals['anvil']
        }

    def generate(self):
        DistributedPartyActivity.generate(self)
        self.notify.info('localAvatar doId: %s' % base.localAvatar.doId)
        self.notify.info('generate()')
        self._generateFrame = globalClock.getFrameCount()
        self._id2gen = {}
        self._orderedGenerations = []
        self._orderedGenerationIndex = None
        rng = RandomNumGen(self.doId)
        self._generationSeedBase = rng.randrange(1000)
        self._lastDropTime = 0.0
        return

    def getCurGeneration(self):
        if self._orderedGenerationIndex is None:
            return
        return self._orderedGenerations[self._orderedGenerationIndex]

    def _addGeneration(self, generation, startTime, startNetworkTime,
                       numPlayers):
        self._id2gen[generation] = self.Generation(generation, startTime,
                                                   startNetworkTime,
                                                   numPlayers)
        i = 0
        while 1:
            if i >= len(self._orderedGenerations):
                break
            gen = self._orderedGenerations[i]
            startNetT = self._id2gen[gen].startTime
            genId = self._id2gen[gen].generation
            if startNetT > startNetworkTime:
                break
            if startNetT == startNetworkTime and genId > generation:
                break
            i += 1
        self._orderedGenerations = self._orderedGenerations[:i] + [
            generation
        ] + self._orderedGenerations[i:]
        if self._orderedGenerationIndex is not None:
            if self._orderedGenerationIndex >= i:
                self._orderedGenerationIndex += 1

    def _removeGeneration(self, generation):
        del self._id2gen[generation]
        i = self._orderedGenerations.index(generation)
        self._orderedGenerations = self._orderedGenerations[:
                                                            i] + self._orderedGenerations[
                                                                i + 1:]
        if self._orderedGenerationIndex is not None:
            if len(self._orderedGenerations):
                if self._orderedGenerationIndex >= i:
                    self._orderedGenerationIndex -= 1
            else:
                self._orderedGenerationIndex = None
        return

    def announceGenerate(self):
        self.notify.info('announceGenerate()')
        self.catchTreeZoneEvent = 'fence_floor'
        DistributedPartyActivity.announceGenerate(self)

    def load(self, loadModels=1, arenaModel='partyCatchTree'):
        self.notify.info('load()')
        DistributedPartyCatchActivity.notify.debug('PartyCatch: load')
        self.activityFSM = CatchActivityFSM(self)
        if __dev__:
            for o in xrange(3):
                print {
                    0: 'SPOTS PER PLAYER',
                    1: 'DROPS PER MINUTE PER SPOT DURING NORMAL DROP PERIOD',
                    2: 'DROPS PER MINUTE PER PLAYER DURING NORMAL DROP PERIOD'
                }[o]
                for i in xrange(1, self.FallRateCap_Players + 10):
                    self.defineConstants(forceNumPlayers=i)
                    numDropLocations = self.DropRows * self.DropColumns
                    numDropsPerMin = 60.0 / self.DropPeriod
                    if o == 0:
                        spotsPerPlayer = numDropLocations / float(i)
                        print '%2d PLAYERS: %s' % (i, spotsPerPlayer)
                    elif o == 1:
                        numDropsPerMinPerSpot = numDropsPerMin / numDropLocations
                        print '%2d PLAYERS: %s' % (i, numDropsPerMinPerSpot)
                    elif i > 0:
                        numDropsPerMinPerPlayer = numDropsPerMin / i
                        print '%2d PLAYERS: %s' % (i, numDropsPerMinPerPlayer)

        self.defineConstants()
        self.treesAndFence = loader.loadModel('phase_13/models/parties/%s' %
                                              arenaModel)
        self.treesAndFence.setScale(0.9)
        self.treesAndFence.find('**/fence_floor').setPos(0.0, 0.0, 0.1)
        self.treesAndFence.reparentTo(self.root)
        ground = self.treesAndFence.find('**/groundPlane')
        ground.setBin('ground', 1)
        DistributedPartyActivity.load(self)
        exitText = TextNode('PartyCatchExitText')
        exitText.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
        exitText.setCardDecal(True)
        exitText.setCardColor(1.0, 1.0, 1.0, 0.0)
        exitText.setText(TTLocalizer.PartyCatchActivityExit)
        exitText.setTextColor(0.0, 8.0, 0.0, 0.9)
        exitText.setAlign(exitText.ACenter)
        exitText.setFont(ToontownGlobals.getBuildingNametagFont())
        exitText.setShadowColor(0, 0, 0, 1)
        exitText.setBin('fixed')
        if TTLocalizer.BuildingNametagShadow:
            exitText.setShadow(*TTLocalizer.BuildingNametagShadow)
        exitTextLoc = self.treesAndFence.find('**/loc_exitSignText')
        exitTextNp = exitTextLoc.attachNewNode(exitText)
        exitTextNp.setDepthWrite(0)
        exitTextNp.setScale(4)
        exitTextNp.setZ(-.5)
        self.sign.reparentTo(self.treesAndFence.find('**/loc_eventSign'))
        self.sign.wrtReparentTo(self.root)
        self.avatarNodePath = NodePath('PartyCatchAvatarNodePath')
        self.avatarNodePath.reparentTo(self.root)
        self._avatarNodePathParentToken = 3
        base.cr.parentMgr.registerParent(self._avatarNodePathParentToken,
                                         self.avatarNodePath)
        self.toonSDs = {}
        self.dropShadow = loader.loadModelOnce(
            'phase_3/models/props/drop_shadow')
        self.dropObjModels = {}
        if loadModels:
            self.__loadDropModels()
        self.sndGoodCatch = base.loadSfx(
            'phase_4/audio/sfx/SZ_DD_treasure.ogg')
        self.sndOof = base.loadSfx('phase_4/audio/sfx/MG_cannon_hit_dirt.ogg')
        self.sndAnvilLand = base.loadSfx(
            'phase_4/audio/sfx/AA_drop_anvil_miss.ogg')
        self.sndPerfect = base.loadSfx('phase_4/audio/sfx/ring_perfect.ogg')
        self.__textGen = TextNode('partyCatchActivity')
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        self.activityFSM.request('Idle')

    def __loadDropModels(self):
        for objType in PartyGlobals.DropObjectTypes:
            model = loader.loadModel(objType.modelPath)
            self.dropObjModels[objType.name] = model
            modelScales = {
                'apple': 0.7,
                'orange': 0.7,
                'pear': 0.5,
                'coconut': 0.7,
                'watermelon': 0.6,
                'pineapple': 0.45
            }
            if objType.name in modelScales:
                model.setScale(modelScales[objType.name])
            if objType == PartyGlobals.Name2DropObjectType['pear']:
                model.setZ(-.6)
            if objType == PartyGlobals.Name2DropObjectType['coconut']:
                model.setP(180)
            if objType == PartyGlobals.Name2DropObjectType['watermelon']:
                model.setH(135)
                model.setZ(-.5)
            if objType == PartyGlobals.Name2DropObjectType['pineapple']:
                model.setZ(-1.7)
            if objType == PartyGlobals.Name2DropObjectType['anvil']:
                model.setZ(-self.ObjRadius)
            model.flattenStrong()

    def unload(self):
        DistributedPartyCatchActivity.notify.debug('unload')
        self.finishAllDropIntervals()
        self.destroyOrthoWalk()
        DistributedPartyActivity.unload(self)
        self.stopDropTask()
        del self.activityFSM
        del self.__textGen
        for avId in self.toonSDs:
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.unload()

        del self.toonSDs
        self.treesAndFence.removeNode()
        del self.treesAndFence
        self.dropShadow.removeNode()
        del self.dropShadow
        base.cr.parentMgr.unregisterParent(self._avatarNodePathParentToken)
        for model in self.dropObjModels.values():
            model.removeNode()

        del self.dropObjModels
        del self.sndGoodCatch
        del self.sndOof
        del self.sndAnvilLand
        del self.sndPerfect

    def setStartTimestamp(self, timestamp32):
        self.notify.info('setStartTimestamp(%s)' % (timestamp32, ))
        self._startTimestamp = globalClockDelta.networkToLocalTime(timestamp32,
                                                                   bits=32)

    def getCurrentCatchActivityTime(self):
        return globalClock.getFrameTime() - self._startTimestamp

    def getObjModel(self, objName):
        return self.dropObjModels[objName].copyTo(hidden)

    def joinRequestDenied(self, reason):
        DistributedPartyActivity.joinRequestDenied(self, reason)
        base.cr.playGame.getPlace().fsm.request('walk')

    def handleToonJoined(self, toonId):
        if toonId not in self.toonSDs:
            toonSD = PartyCatchActivityToonSD(toonId, self)
            self.toonSDs[toonId] = toonSD
            toonSD.load()
        self.notify.debug('handleToonJoined : currentState = %s' %
                          self.activityFSM.state)
        self.cr.doId2do[toonId].useLOD(500)
        if self.activityFSM.state == 'Active':
            if toonId in self.toonSDs:
                self.toonSDs[toonId].enter()
            if base.localAvatar.doId == toonId:
                base.localAvatar.b_setParent(self._avatarNodePathParentToken)
                self.putLocalAvatarInActivity()
            if toonId in self.toonSDs:
                self.toonSDs[toonId].fsm.request('rules')

    def handleToonExited(self, toonId):
        self.notify.debug('handleToonExited( toonId=%s )' % toonId)
        if toonId in self.cr.doId2do:
            self.cr.doId2do[toonId].resetLOD()
            if toonId in self.toonSDs:
                self.toonSDs[toonId].fsm.request('notPlaying')
                self.toonSDs[toonId].exit()
                self.toonSDs[toonId].unload()
                del self.toonSDs[toonId]
            if base.localAvatar.doId == toonId:
                base.localAvatar.b_setParent(ToontownGlobals.SPRender)

    def takeLocalAvatarOutOfActivity(self):
        self.notify.debug('localToon has left the circle')
        base.camera.reparentTo(base.localAvatar)
        base.localAvatar.startUpdateSmartCamera()
        base.localAvatar.enableSmartCameraViews()
        base.localAvatar.setCameraPositionByIndex(base.localAvatar.cameraIndex)
        DistributedSmoothNode.activateSmoothing(1, 0)

    def _enableCollisions(self):
        DistributedPartyActivity._enableCollisions(self)
        self._enteredTree = False
        self.accept('enter' + self.catchTreeZoneEvent,
                    self._toonMayHaveEnteredTree)
        self.accept('again' + self.catchTreeZoneEvent,
                    self._toonMayHaveEnteredTree)
        self.accept('exit' + self.catchTreeZoneEvent, self._toonExitedTree)
        self.accept(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT,
                    self._handleCannonLanded)

    def _disableCollisions(self):
        self.ignore(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT)
        self.ignore('enter' + self.catchTreeZoneEvent)
        self.ignore('again' + self.catchTreeZoneEvent)
        self.ignore('exit' + self.catchTreeZoneEvent)
        DistributedPartyActivity._disableCollisions(self)

    def _handleCannonLanded(self):
        x = base.localAvatar.getX()
        y = base.localAvatar.getY()
        if x > self.x - self.StageHalfWidth and x < self.x + self.StageHalfWidth and y > self.y - self.StageHalfHeight and y < self.y + self.StageHalfHeight:
            self._toonEnteredTree(None)
        return

    def _toonMayHaveEnteredTree(self, collEntry):
        if self._enteredTree:
            return
        if base.localAvatar.controlManager.currentControls.getIsAirborne():
            return
        self._toonEnteredTree(collEntry)

    def _toonEnteredTree(self, collEntry):
        self.notify.debug('_toonEnteredTree : avid = %s' %
                          base.localAvatar.doId)
        self.notify.debug('_toonEnteredTree : currentState = %s' %
                          self.activityFSM.state)
        if self.isLocalToonInActivity():
            return
        if self.activityFSM.state == 'Active':
            base.cr.playGame.getPlace().fsm.request('activity')
            self.d_toonJoinRequest()
        elif self.activityFSM.state == 'Idle':
            base.cr.playGame.getPlace().fsm.request('activity')
            self.d_toonJoinRequest()
        self._enteredTree = True

    def _toonExitedTree(self, collEntry):
        self.notify.debug('_toonExitedTree : avid = %s' %
                          base.localAvatar.doId)
        self._enteredTree = False
        if hasattr(
                base.cr.playGame.getPlace(), 'fsm'
        ) and self.activityFSM.state == 'Active' and self.isLocalToonInActivity(
        ):
            if base.localAvatar.doId in self.toonSDs:
                self.takeLocalAvatarOutOfActivity()
                self.toonSDs[base.localAvatar.doId].fsm.request('notPlaying')
            self.d_toonExitDemand()

    def setToonsPlaying(self, toonIds):
        self.notify.info('setToonsPlaying(%s)' % (toonIds, ))
        DistributedPartyActivity.setToonsPlaying(self, toonIds)
        if self.isLocalToonInActivity(
        ) and base.localAvatar.doId not in toonIds:
            if base.localAvatar.doId in self.toonSDs:
                self.takeLocalAvatarOutOfActivity()
                self.toonSDs[base.localAvatar.doId].fsm.request('notPlaying')

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def getNumPlayers(self):
        return len(self.toonIds)

    def defineConstants(self, forceNumPlayers=None):
        DistributedPartyCatchActivity.notify.debug('defineConstants')
        self.ShowObjSpheres = 0
        self.ShowToonSpheres = 0
        self.useGravity = True
        self.trickShadows = True
        if forceNumPlayers is None:
            numPlayers = self.getNumPlayers()
        else:
            numPlayers = forceNumPlayers
        self.calcDifficultyConstants(numPlayers)
        DistributedPartyCatchActivity.notify.debug('ToonSpeed: %s' %
                                                   self.ToonSpeed)
        DistributedPartyCatchActivity.notify.debug('total drops: %s' %
                                                   self.totalDrops)
        DistributedPartyCatchActivity.notify.debug('numFruits: %s' %
                                                   self.numFruits)
        DistributedPartyCatchActivity.notify.debug('numAnvils: %s' %
                                                   self.numAnvils)
        self.ObjRadius = 1.0
        dropRegionTable = PartyRegionDropPlacer.getDropRegionTable(numPlayers)
        self.DropRows, self.DropColumns = len(dropRegionTable), len(
            dropRegionTable[0])
        for objType in PartyGlobals.DropObjectTypes:
            DistributedPartyCatchActivity.notify.debug('*** Object Type: %s' %
                                                       objType.name)
            objType.onscreenDuration = objType.onscreenDurMult * self.BaselineOnscreenDropDuration
            DistributedPartyCatchActivity.notify.debug(
                'onscreenDuration=%s' % objType.onscreenDuration)
            v_0 = 0.0
            t = objType.onscreenDuration
            x_0 = self.MinOffscreenHeight
            x = 0.0
            g = 2.0 * (x - x_0 - v_0 * t) / (t * t)
            DistributedPartyCatchActivity.notify.debug('gravity=%s' % g)
            objType.trajectory = Trajectory(0,
                                            Vec3(0, 0, x_0),
                                            Vec3(0, 0, v_0),
                                            gravMult=abs(g /
                                                         Trajectory.gravity))
            objType.fallDuration = objType.onscreenDuration + self.OffscreenTime

        return

    def grid2world(self, column, row):
        x = column / float(self.DropColumns - 1)
        y = row / float(self.DropRows - 1)
        x = x * 2.0 - 1.0
        y = y * 2.0 - 1.0
        x *= self.StageHalfWidth
        y *= self.StageHalfHeight
        return (x, y)

    def showPosts(self):
        self.hidePosts()
        self.posts = [Toon.Toon(), Toon.Toon(), Toon.Toon(), Toon.Toon()]
        for i in xrange(len(self.posts)):
            tree = self.posts[i]
            tree.reparentTo(render)
            x = self.StageHalfWidth
            y = self.StageHalfHeight
            if i > 1:
                x = -x
            if i % 2:
                y = -y
            tree.setPos(x + self.x, y + self.y, 0)

    def hidePosts(self):
        if hasattr(self, 'posts'):
            for tree in self.posts:
                tree.removeNode()

            del self.posts

    def showDropGrid(self):
        self.hideDropGrid()
        self.dropMarkers = []
        for row in xrange(self.DropRows):
            self.dropMarkers.append([])
            rowList = self.dropMarkers[row]
            for column in xrange(self.DropColumns):
                toon = Toon.Toon()
                toon.setDNA(base.localAvatar.getStyle())
                toon.reparentTo(self.root)
                toon.setScale(1.0 / 3)
                x, y = self.grid2world(column, row)
                toon.setPos(x, y, 0)
                rowList.append(toon)

    def hideDropGrid(self):
        if hasattr(self, 'dropMarkers'):
            for row in self.dropMarkers:
                for marker in row:
                    marker.removeNode()

            del self.dropMarkers

    def handleToonDisabled(self, avId):
        DistributedPartyCatchActivity.notify.debug('handleToonDisabled')
        DistributedPartyCatchActivity.notify.debug('avatar ' + str(avId) +
                                                   ' disabled')
        if avId in self.toonSDs:
            self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]

    def turnOffSmoothingOnGuests(self):
        pass

    def setState(self, newState, timestamp):
        self.notify.info('setState(%s, %s)' % (newState, timestamp))
        DistributedPartyCatchActivity.notify.debug(
            'setState( newState=%s, ... )' % newState)
        DistributedPartyActivity.setState(self, newState, timestamp)
        self.activityFSM.request(newState)
        if newState == 'Active':
            if base.localAvatar.doId != self.party.partyInfo.hostId:
                if globalClock.getFrameCount() > self._generateFrame:
                    if base.localAvatar.getX(
                    ) > self.x - self.StageHalfWidth and base.localAvatar.getX(
                    ) < self.x + self.StageHalfWidth and base.localAvatar.getY(
                    ) > self.y - self.StageHalfHeight and base.localAvatar.getY(
                    ) < self.y + self.StageHalfHeight:
                        self._toonEnteredTree(None)
        return

    def putLocalAvatarInActivity(self):
        if base.cr.playGame.getPlace() and hasattr(base.cr.playGame.getPlace(),
                                                   'fsm'):
            base.cr.playGame.getPlace().fsm.request('activity', [False])
        else:
            self.notify.info(
                "Avoided crash: toontown.parties.DistributedPartyCatchActivity:632, toontown.parties.DistributedPartyCatchActivity:1198, toontown.parties.activityFSMMixins:49, direct.fsm.FSM:423, AttributeError: 'NoneType' object has no attribute 'fsm'"
            )
        base.localAvatar.stopUpdateSmartCamera()
        base.camera.reparentTo(self.treesAndFence)
        base.camera.setPosHpr(0.0, -63.0, 30.0, 0.0, -20.0, 0.0)
        if not hasattr(self, 'ltLegsCollNode'):
            self.createCatchCollisions()

    def createCatchCollisions(self):
        radius = 0.7
        handler = CollisionHandlerEvent()
        handler.setInPattern('ltCatch%in')
        self.ltLegsCollNode = CollisionNode('catchLegsCollNode')
        self.ltLegsCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltHeadCollNode = CollisionNode('catchHeadCollNode')
        self.ltHeadCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltLHandCollNode = CollisionNode('catchLHandCollNode')
        self.ltLHandCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltRHandCollNode = CollisionNode('catchRHandCollNode')
        self.ltRHandCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        legsCollNodepath = base.localAvatar.attachNewNode(self.ltLegsCollNode)
        legsCollNodepath.hide()
        head = base.localAvatar.getHeadParts().getPath(2)
        headCollNodepath = head.attachNewNode(self.ltHeadCollNode)
        headCollNodepath.hide()
        lHand = base.localAvatar.getLeftHands()[0]
        lHandCollNodepath = lHand.attachNewNode(self.ltLHandCollNode)
        lHandCollNodepath.hide()
        rHand = base.localAvatar.getRightHands()[0]
        rHandCollNodepath = rHand.attachNewNode(self.ltRHandCollNode)
        rHandCollNodepath.hide()
        base.localAvatar.cTrav.addCollider(legsCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(headCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(lHandCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(lHandCollNodepath, handler)
        if self.ShowToonSpheres:
            legsCollNodepath.show()
            headCollNodepath.show()
            lHandCollNodepath.show()
            rHandCollNodepath.show()
        self.ltLegsCollNode.addSolid(CollisionSphere(0, 0, radius, radius))
        self.ltHeadCollNode.addSolid(CollisionSphere(0, 0, 0, radius))
        self.ltLHandCollNode.addSolid(
            CollisionSphere(0, 0, 0, 2 * radius / 3.0))
        self.ltRHandCollNode.addSolid(
            CollisionSphere(0, 0, 0, 2 * radius / 3.0))
        self.toonCollNodes = [
            legsCollNodepath, headCollNodepath, lHandCollNodepath,
            rHandCollNodepath
        ]

    def destroyCatchCollisions(self):
        if not hasattr(self, 'ltLegsCollNode'):
            return
        for collNode in self.toonCollNodes:
            while collNode.node().getNumSolids():
                collNode.node().removeSolid(0)

            base.localAvatar.cTrav.removeCollider(collNode)

        del self.toonCollNodes
        del self.ltLegsCollNode
        del self.ltHeadCollNode
        del self.ltLHandCollNode
        del self.ltRHandCollNode

    def timerExpired(self):
        pass

    def __handleCatch(self, generation, objNum):
        DistributedPartyCatchActivity.notify.debug('catch: %s' %
                                                   [generation, objNum])
        if base.localAvatar.doId not in self.toonIds:
            return
        self.showCatch(base.localAvatar.doId, generation, objNum)
        objName = self._id2gen[generation].droppedObjNames[objNum]
        objTypeId = PartyGlobals.Name2DOTypeId[objName]
        self.sendUpdate('claimCatch', [generation, objNum, objTypeId])
        self.finishDropInterval(generation, objNum)

    def showCatch(self, avId, generation, objNum):
        if avId not in self.toonSDs:
            return
        isLocal = avId == base.localAvatar.doId
        if generation not in self._id2gen:
            return
        if not self._id2gen[generation].hasBeenScheduled:
            return
        objName = self._id2gen[generation].droppedObjNames[objNum]
        objType = PartyGlobals.Name2DropObjectType[objName]
        if objType.good:
            if objNum not in self._id2gen[generation].droppedObjCaught:
                if isLocal:
                    base.playSfx(self.sndGoodCatch)
                fruit = self.getObjModel(objName)
                toon = self.getAvatar(avId)
                rHand = toon.getRightHands()[1]
                self.toonSDs[avId].eatFruit(fruit, rHand)
        else:
            self.toonSDs[avId].fsm.request('fallForward')
        self._id2gen[generation].droppedObjCaught[objNum] = 1

    def setObjectCaught(self, avId, generation, objNum):
        self.notify.info('setObjectCaught(%s, %s, %s)' %
                         (avId, generation, objNum))
        if self.activityFSM.state != 'Active':
            DistributedPartyCatchActivity.notify.warning(
                'ignoring msg: object %s caught by %s' % (objNum, avId))
            return
        isLocal = avId == base.localAvatar.doId
        if not isLocal:
            DistributedPartyCatchActivity.notify.debug(
                'AI: avatar %s caught %s' % (avId, objNum))
            self.finishDropInterval(generation, objNum)
            self.showCatch(avId, generation, objNum)
        self._scheduleGenerations()
        gen = self._id2gen[generation]
        if gen.hasBeenScheduled:
            objName = gen.droppedObjNames[objNum]
            if PartyGlobals.Name2DropObjectType[objName].good:
                if hasattr(self, 'fruitsCaught'):
                    self.fruitsCaught += 1

    def finishDropInterval(self, generation, objNum):
        if hasattr(self, 'dropIntervals'):
            if (generation, objNum) in self.dropIntervals:
                self.dropIntervals[generation, objNum].finish()

    def finishAllDropIntervals(self):
        if hasattr(self, 'dropIntervals'):
            for dropInterval in self.dropIntervals.values():
                dropInterval.finish()

    def setGenerations(self, generations):
        self.notify.info('setGenerations(%s)' % (generations, ))
        gen2t = {}
        gen2nt = {}
        gen2np = {}
        for id, timestamp32, numPlayers in generations:
            gen2t[id] = globalClockDelta.networkToLocalTime(
                timestamp32, bits=32) - self._startTimestamp
            gen2nt[id] = timestamp32
            gen2np[id] = numPlayers

        ids = self._id2gen.keys()
        for id in ids:
            if id not in gen2t:
                self._removeGeneration(id)

        for id in gen2t:
            if id not in self._id2gen:
                self._addGeneration(id, gen2t[id], gen2nt[id], gen2np[id])

    def scheduleDrops(self, genId=None):
        if genId is None:
            genId = self.getCurGeneration()
        gen = self._id2gen[genId]
        if gen.hasBeenScheduled:
            return
        fruitIndex = int((gen.startTime + 0.5 * self.DropPeriod) /
                         PartyGlobals.CatchActivityDuration)
        fruitNames = [
            'apple', 'orange', 'pear', 'coconut', 'watermelon', 'pineapple'
        ]
        fruitName = fruitNames[fruitIndex % len(fruitNames)]
        rng = RandomNumGen(genId + self._generationSeedBase)
        gen.droppedObjNames = [fruitName
                               ] * self.numFruits + ['anvil'] * self.numAnvils
        rng.shuffle(gen.droppedObjNames)
        dropPlacer = PartyRegionDropPlacer(self,
                                           gen.numPlayers,
                                           genId,
                                           gen.droppedObjNames,
                                           startTime=gen.startTime)
        gen.numItemsDropped = 0
        tIndex = gen.startTime % PartyGlobals.CatchActivityDuration
        tPercent = float(tIndex) / PartyGlobals.CatchActivityDuration
        gen.numItemsDropped += dropPlacer.skipPercent(tPercent)
        while not dropPlacer.doneDropping(continuous=True):
            nextDrop = dropPlacer.getNextDrop()
            gen.dropSchedule.append(nextDrop)

        gen.hasBeenScheduled = True
        return

    def startDropTask(self):
        taskMgr.add(self.dropTask, self.DropTaskName)

    def stopDropTask(self):
        taskMgr.remove(self.DropTaskName)

    def _scheduleGenerations(self):
        curT = self.getCurrentCatchActivityTime()
        genIndex = self._orderedGenerationIndex
        newGenIndex = genIndex
        while genIndex is None or genIndex < len(self._orderedGenerations) - 1:
            if genIndex is None:
                nextGenIndex = 0
            else:
                nextGenIndex = genIndex + 1
            nextGenId = self._orderedGenerations[nextGenIndex]
            nextGen = self._id2gen[nextGenId]
            startT = nextGen.startTime
            if curT >= startT:
                newGenIndex = nextGenIndex
            if not nextGen.hasBeenScheduled:
                self.defineConstants(forceNumPlayers=nextGen.numPlayers)
                self.scheduleDrops(
                    genId=self._orderedGenerations[nextGenIndex])
            genIndex = nextGenIndex

        self._orderedGenerationIndex = newGenIndex
        return

    def dropTask(self, task):
        self._scheduleGenerations()
        curT = self.getCurrentCatchActivityTime()
        if self._orderedGenerationIndex is not None:
            i = self._orderedGenerationIndex
            genIndex = self._orderedGenerations[i]
            gen = self._id2gen[genIndex]
            while len(gen.dropSchedule) > 0 and gen.dropSchedule[0][0] < curT:
                drop = gen.dropSchedule[0]
                gen.dropSchedule = gen.dropSchedule[1:]
                dropTime, objName, dropCoords = drop
                objNum = gen.numItemsDropped
                x, y = self.grid2world(*dropCoords)
                dropIval = self.getDropIval(x, y, objName, genIndex, objNum)

                def cleanup(generation, objNum, self=self):
                    del self.dropIntervals[generation, objNum]

                dropIval.append(Func(Functor(cleanup, genIndex, objNum)))
                self.dropIntervals[genIndex, objNum] = dropIval
                gen.numItemsDropped += 1
                dropIval.start(curT - dropTime)
                self._lastDropTime = dropTime

        return Task.cont

    def getDropIval(self, x, y, dropObjName, generation, num):
        objType = PartyGlobals.Name2DropObjectType[dropObjName]
        id = (generation, num)
        dropNode = hidden.attachNewNode('catchDropNode%s' % (id, ))
        dropNode.setPos(x, y, 0)
        shadow = self.dropShadow.copyTo(dropNode)
        shadow.setZ(PartyGlobals.CatchDropShadowHeight)
        shadow.setColor(1, 1, 1, 1)
        object = self.getObjModel(dropObjName)
        object.reparentTo(hidden)
        if dropObjName in ['watermelon', 'anvil']:
            objH = object.getH()
            absDelta = {'watermelon': 12, 'anvil': 15}[dropObjName]
            delta = (self.randomNumGen.random() * 2.0 - 1.0) * absDelta
            newH = objH + delta
        else:
            newH = self.randomNumGen.random() * 360.0
        object.setH(newH)
        sphereName = 'FallObj%s' % (id, )
        radius = self.ObjRadius
        if objType.good:
            radius *= lerp(1.0, 1.3, 0.5)
        collSphere = CollisionSphere(0, 0, 0, radius)
        collSphere.setTangible(0)
        collNode = CollisionNode(sphereName)
        collNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        collNode.addSolid(collSphere)
        collNodePath = object.attachNewNode(collNode)
        collNodePath.hide()
        if self.ShowObjSpheres:
            collNodePath.show()
        catchEventName = 'ltCatch' + sphereName

        def eatCollEntry(forward, collEntry):
            forward()

        self.accept(
            catchEventName,
            Functor(eatCollEntry, Functor(self.__handleCatch, id[0], id[1])))

        def cleanup(self=self, dropNode=dropNode, id=id, event=catchEventName):
            self.ignore(event)
            dropNode.removeNode()

        duration = objType.fallDuration
        onscreenDuration = objType.onscreenDuration
        targetShadowScale = 0.3
        if self.trickShadows:
            intermedScale = targetShadowScale * (self.OffscreenTime /
                                                 self.BaselineDropDuration)
            shadowScaleIval = Sequence(
                LerpScaleInterval(shadow,
                                  self.OffscreenTime,
                                  intermedScale,
                                  startScale=0))
            shadowScaleIval.append(
                LerpScaleInterval(shadow,
                                  duration - self.OffscreenTime,
                                  targetShadowScale,
                                  startScale=intermedScale))
        else:
            shadowScaleIval = LerpScaleInterval(shadow,
                                                duration,
                                                targetShadowScale,
                                                startScale=0)
        targetShadowAlpha = 0.4
        shadowAlphaIval = LerpColorScaleInterval(
            shadow,
            self.OffscreenTime,
            Point4(1, 1, 1, targetShadowAlpha),
            startColorScale=Point4(1, 1, 1, 0))
        shadowIval = Parallel(shadowScaleIval, shadowAlphaIval)
        if self.useGravity:

            def setObjPos(t, objType=objType, object=object):
                z = objType.trajectory.calcZ(t)
                object.setZ(z)

            setObjPos(0)
            dropIval = LerpFunctionInterval(setObjPos,
                                            fromData=0,
                                            toData=onscreenDuration,
                                            duration=onscreenDuration)
        else:
            startPos = Point3(0, 0, self.MinOffscreenHeight)
            object.setPos(startPos)
            dropIval = LerpPosInterval(object,
                                       onscreenDuration,
                                       Point3(0, 0, 0),
                                       startPos=startPos,
                                       blendType='easeIn')
        ival = Sequence(Func(Functor(dropNode.reparentTo, self.root)),
                        Parallel(
                            Sequence(
                                WaitInterval(self.OffscreenTime),
                                Func(Functor(object.reparentTo, dropNode)),
                                dropIval), shadowIval),
                        Func(cleanup),
                        name='drop%s' % (id, ))
        if objType == PartyGlobals.Name2DropObjectType['anvil']:
            ival.append(Func(self.playAnvil))
        return ival

    def playAnvil(self):
        if base.localAvatar.doId in self.toonIds:
            base.playSfx(self.sndAnvilLand)

    def initOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug('startOrthoWalk')

        def doCollisions(oldPos, newPos, self=self):
            x = bound(newPos[0], self.StageHalfWidth, -self.StageHalfWidth)
            y = bound(newPos[1], self.StageHalfHeight, -self.StageHalfHeight)
            newPos.setX(x)
            newPos.setY(y)
            return newPos

        orthoDrive = OrthoDrive(self.ToonSpeed, instantTurn=True)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)

    def destroyOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug('destroyOrthoWalk')
        if hasattr(self, 'orthoWalk'):
            self.orthoWalk.stop()
            self.orthoWalk.destroy()
            del self.orthoWalk

    def startIdle(self):
        DistributedPartyCatchActivity.notify.debug('startIdle')

    def finishIdle(self):
        DistributedPartyCatchActivity.notify.debug('finishIdle')

    def startActive(self):
        DistributedPartyCatchActivity.notify.debug('startActive')
        for avId in self.toonIds:
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.enter()
                toonSD.fsm.request('normal')

        self.fruitsCaught = 0
        self.dropIntervals = {}
        self.startDropTask()
        if base.localAvatar.doId in self.toonIds:
            self.putLocalAvatarInActivity()

    def finishActive(self):
        DistributedPartyCatchActivity.notify.debug('finishActive')
        self.stopDropTask()
        if hasattr(self, 'finishIval'):
            self.finishIval.pause()
            del self.finishIval
        if base.localAvatar.doId in self.toonIds:
            self.takeLocalAvatarOutOfActivity()
        for ival in self.dropIntervals.values():
            ival.finish()

        del self.dropIntervals

    def startConclusion(self):
        DistributedPartyCatchActivity.notify.debug('startConclusion')
        for avId in self.toonIds:
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.fsm.request('notPlaying')

        self.destroyCatchCollisions()
        if base.localAvatar.doId not in self.toonIds:
            return
        else:
            self.localToonExiting()
        if self.fruitsCaught >= self.numFruits:
            finishText = TTLocalizer.PartyCatchActivityFinishPerfect
        else:
            finishText = TTLocalizer.PartyCatchActivityFinish
        perfectTextSubnode = hidden.attachNewNode(self.__genText(finishText))
        perfectText = hidden.attachNewNode('perfectText')
        perfectTextSubnode.reparentTo(perfectText)
        frame = self.__textGen.getCardActual()
        offsetY = -abs(frame[2] + frame[3]) / 2.0
        perfectTextSubnode.setPos(0, 0, offsetY)
        perfectText.setColor(1, 0.1, 0.1, 1)

        def fadeFunc(t, text=perfectText):
            text.setColorScale(1, 1, 1, t)

        def destroyText(text=perfectText):
            text.removeNode()

        textTrack = Sequence(
            Func(perfectText.reparentTo, aspect2d),
            Parallel(
                LerpScaleInterval(perfectText,
                                  duration=0.5,
                                  scale=0.3,
                                  startScale=0.0),
                LerpFunctionInterval(fadeFunc,
                                     fromData=0.0,
                                     toData=1.0,
                                     duration=0.5)), Wait(2.0),
            Parallel(
                LerpScaleInterval(perfectText, duration=0.5, scale=1.0),
                LerpFunctionInterval(fadeFunc,
                                     fromData=1.0,
                                     toData=0.0,
                                     duration=0.5,
                                     blendType='easeIn')), Func(destroyText),
            WaitInterval(0.5))
        soundTrack = SoundInterval(self.sndPerfect)
        self.finishIval = Parallel(textTrack, soundTrack)
        self.finishIval.start()

    def finishConclusion(self):
        DistributedPartyCatchActivity.notify.debug('finishConclusion')
        if base.localAvatar.doId in self.toonIds:
            self.takeLocalAvatarOutOfActivity()
            base.cr.playGame.getPlace().fsm.request('walk')

    def showJellybeanReward(self, earnedAmount, jarAmount, message):
        if earnedAmount > 0:
            DistributedPartyActivity.showJellybeanReward(
                self, earnedAmount, jarAmount, message)
        else:
            base.cr.playGame.getPlace().fsm.request('walk')
 def __initOrthoWalk(self):
     self.notify.debug('Initialize Ortho Walk')
     orthoDrive = OrthoDrive(9.778)
     self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)
class CogdoMazeLocalPlayer(CogdoMazePlayer):
    notify = directNotify.newCategory('CogdoMazeLocalPlayer')

    def __init__(self, id, toon, game, guiMgr):
        CogdoMazePlayer.__init__(self, id, toon)
        self.disableGagCollision()
        self.game = game
        self.maze = self.game.maze
        self._guiMgr = guiMgr
        self.cameraMgr = CogdoMazeCameraManager(self.toon, self.maze, camera, render)
        self._proximityRadius = self.maze.cellWidth * Globals.CameraRemoteToonRadius
        orthoDrive = OrthoDrive(Globals.ToonRunSpeed, maxFrameMove=self.maze.cellWidth / 2, customCollisionCallback=self.maze.doOrthoCollisions, wantSound=True)
        self.orthoWalk = OrthoWalk(orthoDrive)
        self._audioMgr = base.cogdoGameAudioMgr
        self._getMemoSfx = self._audioMgr.createSfx('getMemo', source=self.toon)
        self._waterCoolerFillSfx = self._audioMgr.createSfx('waterCoolerFill', source=self.toon)
        self._hitByDropSfx = self._audioMgr.createSfx('toonHitByDrop', source=self.toon)
        self._winSfx = self._audioMgr.createSfx('win')
        self._loseSfx = self._audioMgr.createSfx('lose')
        self.enabled = False
        self.pickupCount = 0
        self.numEntered = 0
        self.throwPending = False
        self.coolDownAfterHitInterval = Sequence(Wait(Globals.HitCooldownTime), Func(self.setInvulnerable, False), name='coolDownAfterHitInterval-%i' % self.toon.doId)
        self.invulnerable = False
        self.gagHandler = CollisionHandlerEvent()
        self.gagHandler.addInPattern('%fn-into-%in')
        self.exited = False
        self.hints = {'find': False,
         'throw': False,
         'squashed': False,
         'boss': False,
         'minion': False}
        self.accept(base.JUMP, self.controlKeyPressed)

    def destroy(self):
        self.toon.showName()
        self.ignoreAll()
        self.coolDownAfterHitInterval.clearToInitial()
        del self.coolDownAfterHitInterval
        del self._getMemoSfx
        del self._waterCoolerFillSfx
        del self._hitByDropSfx
        del self._winSfx
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        CogdoMazePlayer.destroy(self)

    def __initCollisions(self):
        collSphere = CollisionSphere(0, 0, 0, Globals.PlayerCollisionRadius)
        collSphere.setTangible(0)
        self.mazeCollisionName = Globals.LocalPlayerCollisionName
        collNode = CollisionNode(self.mazeCollisionName)
        collNode.addSolid(collSphere)
        collNodePath = self.toon.attachNewNode(collNode)
        collNodePath.hide()
        handler = CollisionHandlerEvent()
        handler.addInPattern('%fn-into-%in')
        base.cTrav.addCollider(collNodePath, handler)
        self.handler = handler
        self._collNodePath = collNodePath

    def clearCollisions(self):
        self.handler.clear()

    def __disableCollisions(self):
        self._collNodePath.removeNode()
        del self._collNodePath

    def _isNearPlayer(self, player):
        return self.toon.getDistance(player.toon) <= self._proximityRadius

    def update(self, dt):
        if self.getCurrentOrNextState() != 'Off':
            self._updateCamera(dt)

    def _updateCamera(self, dt):
        numPlayers = 0.0
        for player in self.game.players:
            if player != self and player.toon and self._isNearPlayer(player):
                numPlayers += 1

        d = clamp(Globals.CameraMinDistance + numPlayers / (CogdoGameConsts.MaxPlayers - 1) * (Globals.CameraMaxDistance - Globals.CameraMinDistance), Globals.CameraMinDistance, Globals.CameraMaxDistance)
        self.cameraMgr.setCameraTargetDistance(d)
        self.cameraMgr.update(dt)

    def enterOff(self):
        CogdoMazePlayer.enterOff(self)

    def exitOff(self):
        CogdoMazePlayer.exitOff(self)
        self.toon.hideName()

    def enterReady(self):
        CogdoMazePlayer.enterReady(self)
        self.cameraMgr.enable()

    def exitReady(self):
        CogdoMazePlayer.enterReady(self)

    def enterNormal(self):
        CogdoMazePlayer.enterNormal(self)
        self.orthoWalk.start()

    def exitNormal(self):
        CogdoMazePlayer.exitNormal(self)
        self.orthoWalk.stop()

    def enterHit(self, elapsedTime = 0.0):
        CogdoMazePlayer.enterHit(self, elapsedTime)
        self.setInvulnerable(True)

    def exitHit(self):
        CogdoMazePlayer.exitHit(self)
        self.coolDownAfterHitInterval.clearToInitial()
        self.coolDownAfterHitInterval.start()

    def enterDone(self):
        CogdoMazePlayer.enterDone(self)
        self._guiMgr.hideQuestArrow()
        self.ignore(base.JUMP)
        self._guiMgr.setMessage('')
        if self.exited == False:
            self.lostMemos()

    def exitDone(self):
        CogdoMazePlayer.exitDone(self)

    def hitByDrop(self):
        if self.equippedGag is not None and not self.hints['squashed']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeSquashHint, Globals.HintTimeout)
            self.hints['squashed'] = True
        self._hitByDropSfx.play()
        CogdoMazePlayer.hitByDrop(self)
        return

    def equipGag(self):
        CogdoMazePlayer.equipGag(self)
        self._waterCoolerFillSfx.play()
        messenger.send(Globals.WaterCoolerHideEventName, [])
        if not self.hints['throw']:
            self._guiMgr.setMessage(TTLocalizer.CogdoMazeThrowHint)
            self.hints['throw'] = True

    def hitSuit(self, suitType):
        if suitType == Globals.SuitTypes.Boss and not self.hints['boss']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeBossHint, Globals.HintTimeout)
            self.hints['boss'] = True
        if suitType != Globals.SuitTypes.Boss and not self.hints['minion']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeMinionHint, Globals.HintTimeout)
            self.hints['minion'] = True

    def createThrowGag(self, gag):
        throwGag = CogdoMazePlayer.createThrowGag(self, gag)
        collSphere = CollisionSphere(0, 0, 0, 0.5)
        collSphere.setTangible(0)
        name = Globals.GagCollisionName
        collNode = CollisionNode(name)
        collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
        collNode.addSolid(collSphere)
        colNp = throwGag.attachNewNode(collNode)
        base.cTrav.addCollider(colNp, self.gagHandler)
        return throwGag

    def showToonThrowingGag(self, heading, pos):
        self._guiMgr.clearMessage()
        return CogdoMazePlayer.showToonThrowingGag(self, heading, pos)

    def removeGag(self):
        if self.equippedGag is None:
            return
        CogdoMazePlayer.removeGag(self)
        self.throwPending = False
        messenger.send(Globals.WaterCoolerShowEventName, [])
        return

    def controlKeyPressed(self):
        if self.game.finished or self.throwPending or self.getCurrentOrNextState() == 'Hit' or self.equippedGag == None:
            return
        self.throwPending = True
        heading = self.toon.getH()
        pos = self.toon.getPos()
        self.game.requestUseGag(pos.getX(), pos.getY(), heading)
        return

    def completeThrow(self):
        self.clearCollisions()
        CogdoMazePlayer.completeThrow(self)

    def shakeCamera(self, strength):
        self.cameraMgr.shake(strength)

    def getCameraShake(self):
        return self.cameraMgr.shakeStrength

    def setInvulnerable(self, bool):
        self.invulnerable = bool

    def handleGameStart(self):
        self.numEntered = len(self.game.players)
        self.__initCollisions()
        self._guiMgr.startGame(TTLocalizer.CogdoMazeFindHint)
        self.hints['find'] = True
        self.notify.info('toonId:%d laff:%d/%d  %d player(s) started maze game' % (self.toon.doId,
         self.toon.hp,
         self.toon.maxHp,
         len(self.game.players)))

    def handleGameExit(self):
        self.cameraMgr.disable()
        self.__disableCollisions()

    def handlePickUp(self, toonId):
        if toonId == self.toon.doId:
            self.pickupCount += 1
            self._guiMgr.setPickupCount(self.pickupCount)
            if self.pickupCount == 1:
                self._guiMgr.showPickupCounter()
            self._getMemoSfx.play()

    def handleOpenDoor(self, door):
        self._guiMgr.setMessage(TTLocalizer.CogdoMazeGameDoorOpens)
        self._guiMgr.showQuestArrow(self.toon, door, Point3(0, 0, self.toon.getHeight() + 2))

    def handleTimeAlert(self):
        self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeGameTimeAlert)

    def handleToonRevealsDoor(self, toonId, door):
        if toonId == self.toon.doId:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeGameLocalToonFoundExit)

    def handleToonEntersDoor(self, toonId, door):
        self.exited = True
        message = ''
        if door.getPlayerCount() < len(self.game.players):
            message = TTLocalizer.WaitingForOtherToons
        if toonId == self.toon.doId:
            self._guiMgr.setMessage(message)
            self._winSfx.play()
            self._audioMgr.stopMusic()
        self.notify.info('toonId:%d laff:%d/%d  %d player(s) succeeded in maze game. Going to the executive suit building.' % (toonId,
         self.toon.hp,
         self.toon.maxHp,
         len(self.game.players)))
        if self.numEntered > len(self.game.players):
            self.notify.info('%d player(s) failed in maze game' % (self.numEntered - len(self.game.players)))

    def lostMemos(self):
        self.pickupCount = 0
        self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeGameTimeOut)
        self._guiMgr.setPickupCount(self.pickupCount)
        self.notify.info('toonId:%d laff:%d/%d  %d player(s) failed in maze game' % (self.toon.doId,
         self.toon.hp,
         self.toon.maxHp,
         len(self.game.players)))
class DistributedCogThiefGame(DistributedMinigame):
    notify = directNotify.newCategory('DistributedCogThiefGame')
    ToonSpeed = CTGG.ToonSpeed
    StageHalfWidth = 200.0
    StageHalfHeight = 100.0
    BarrelScale = 0.25
    TOON_Z = 0
    UPDATE_SUITS_TASK = 'CogThiefGameUpdateSuitsTask'
    REWARD_COUNTDOWN_TASK = 'cogThiefGameRewardCountdown'
    ControlKeyLimitTime = 1.0

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedCogThiefGame', [State.State('off', self.enterOff, self.exitOff, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.cameraTopView = (0, 0, 55, 0, -90.0, 0)
        self.barrels = []
        self.cogInfo = {}
        self.lastTimeControlPressed = 0
        self.stolenBarrels = []
        self.useOrthoWalk = base.config.GetBool('cog-thief-ortho', 1)
        self.resultIval = None
        self.gameIsEnding = False
        self.__textGen = TextNode('cogThiefGame')
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        return

    def getTitle(self):
        return TTLocalizer.CogThiefGameTitle

    def getInstructions(self):
        return TTLocalizer.CogThiefGameInstructions

    def getMaxDuration(self):
        return 0

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.music = base.loadMusic('phase_4/audio/bgm/MG_CogThief.ogg')
        self.initCogInfo()
        for barrelIndex in xrange(CTGG.NumBarrels):
            barrel = loader.loadModel('phase_4/models/minigames/cogthief_game_gagTank')
            barrel.setPos(CTGG.BarrelStartingPositions[barrelIndex])
            barrel.setScale(self.BarrelScale)
            barrel.reparentTo(render)
            barrel.setTag('barrelIndex', str(barrelIndex))
            collSphere = CollisionSphere(0, 0, 0, 4)
            collSphere.setTangible(0)
            name = 'BarrelSphere-%d' % barrelIndex
            collSphereName = self.uniqueName(name)
            collNode = CollisionNode(collSphereName)
            collNode.setFromCollideMask(CTGG.BarrelBitmask)
            collNode.addSolid(collSphere)
            colNp = barrel.attachNewNode(collNode)
            handler = CollisionHandlerEvent()
            handler.setInPattern('barrelHit-%fn')
            base.cTrav.addCollider(colNp, handler)
            self.accept('barrelHit-' + collSphereName, self.handleEnterBarrel)
            nodeToHide = '**/gagMoneyTen'
            if barrelIndex % 2:
                nodeToHide = '**/gagMoneyFive'
            iconToHide = barrel.find(nodeToHide)
            if not iconToHide.isEmpty():
                iconToHide.hide()
            self.barrels.append(barrel)

        self.gameBoard = loader.loadModel('phase_4/models/minigames/cogthief_game')
        self.gameBoard.find('**/floor_TT').hide()
        self.gameBoard.find('**/floor_DD').hide()
        self.gameBoard.find('**/floor_DG').hide()
        self.gameBoard.find('**/floor_MM').hide()
        self.gameBoard.find('**/floor_BR').hide()
        self.gameBoard.find('**/floor_DL').hide()
        zone = self.getSafezoneId()
        if zone == ToontownGlobals.ToontownCentral:
            self.gameBoard.find('**/floor_TT').show()
        elif zone == ToontownGlobals.DonaldsDock:
            self.gameBoard.find('**/floor_DD').show()
        elif zone == ToontownGlobals.DaisyGardens:
            self.gameBoard.find('**/floor_DG').show()
        elif zone == ToontownGlobals.MinniesMelodyland:
            self.gameBoard.find('**/floor_MM').show()
        elif zone == ToontownGlobals.TheBrrrgh:
            self.gameBoard.find('**/floor_BR').show()
        elif zone == ToontownGlobals.DonaldsDreamland:
            self.gameBoard.find('**/floor_DL').show()
        else:
            self.gameBoard.find('**/floor_TT').show()
        self.gameBoard.setPosHpr(0, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        self.toonSDs = {}
        avId = self.localAvId
        toonSD = CogThiefGameToonSD.CogThiefGameToonSD(avId, self)
        self.toonSDs[avId] = toonSD
        toonSD.load()
        self.loadCogs()
        self.toonHitTracks = {}
        self.toonPieTracks = {}
        self.sndOof = base.loadSfx('phase_4/audio/sfx/MG_cannon_hit_dirt.ogg')
        self.sndRewardTick = base.loadSfx('phase_3.5/audio/sfx/tick_counter.ogg')
        self.sndPerfect = base.loadSfx('phase_4/audio/sfx/ring_perfect.ogg')
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.hide()
        purchaseModels = loader.loadModel('phase_4/models/gui/purchase_gui')
        self.jarImage = purchaseModels.find('**/Jar')
        self.jarImage.reparentTo(hidden)
        self.rewardPanel = DirectLabel(parent=hidden, relief=None, pos=(-0.173, 0.0, -0.55), scale=0.65, text='', text_scale=0.2, text_fg=(0.95, 0.95, 0, 1), text_pos=(0, -.13), text_font=ToontownGlobals.getSignFont(), image=self.jarImage)
        self.rewardPanelTitle = DirectLabel(parent=self.rewardPanel, relief=None, pos=(0, 0, 0.06), scale=0.08, text=TTLocalizer.CannonGameReward, text_fg=(0.95, 0.95, 0, 1), text_shadow=(0, 0, 0, 1))
        return

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        del self.music
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM
        self.gameBoard.removeNode()
        del self.gameBoard
        for barrel in self.barrels:
            barrel.removeNode()

        del self.barrels
        for avId in self.toonSDs.keys():
            toonSD = self.toonSDs[avId]
            toonSD.unload()

        del self.toonSDs
        self.timer.destroy()
        del self.timer
        self.rewardPanel.destroy()
        del self.rewardPanel
        self.jarImage.removeNode()
        del self.jarImage
        del self.sndRewardTick

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.gameBoard.reparentTo(render)
        lt = base.localAvatar
        lt.reparentTo(render)
        self.__placeToon(self.localAvId)
        lt.setSpeed(0, 0)
        self.moveCameraToTop()
        toonSD = self.toonSDs[self.localAvId]
        toonSD.enter()
        toonSD.fsm.request('normal')
        self.stopGameWalk()
        for cogIndex in xrange(self.getNumCogs()):
            suit = self.cogInfo[cogIndex]['suit'].suit
            pos = self.cogInfo[cogIndex]['pos']
            suit.reparentTo(self.gameBoard)
            suit.setPos(pos)
            suit.nametag3d.stash()
            suit.nametag.destroy()

        for avId in self.avIdList:
            self.toonHitTracks[avId] = Wait(0.1)

        self.toonRNGs = []
        for i in xrange(self.numPlayers):
            self.toonRNGs.append(RandomNumGen.RandomNumGen(self.randomNumGen))

        self.sndTable = {'hitBySuit': [None] * self.numPlayers,
         'falling': [None] * self.numPlayers}
        for i in xrange(self.numPlayers):
            self.sndTable['hitBySuit'][i] = base.loadSfx('phase_4/audio/sfx/MG_Tag_C.ogg')
            self.sndTable['falling'][i] = base.loadSfx('phase_4/audio/sfx/MG_cannon_whizz.ogg')

        base.playMusic(self.music, looping=1, volume=0.8)
        self.introTrack = self.getIntroTrack()
        self.introTrack.start()
        return

    def offstage(self):
        self.notify.debug('offstage')
        self.gameBoard.hide()
        self.music.stop()
        for barrel in self.barrels:
            barrel.hide()

        for avId in self.toonSDs.keys():
            self.toonSDs[avId].exit()

        for avId in self.avIdList:
            av = self.getAvatar(avId)
            if av:
                av.resetLOD()

        self.timer.reparentTo(hidden)
        self.rewardPanel.reparentTo(hidden)
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        del self.introTrack
        DistributedMinigame.offstage(self)

    def handleDisabledAvatar(self, avId):
        self.notify.debug('handleDisabledAvatar')
        self.notify.debug('avatar ' + str(avId) + ' disabled')
        self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]
        DistributedMinigame.handleDisabledAvatar(self, avId)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self.__placeToon(avId)
                toon.useLOD(1000)
                toonSD = CogThiefGameToonSD.CogThiefGameToonSD(avId, self)
                self.toonSDs[avId] = toonSD
                toonSD.load()
                toonSD.enter()
                toonSD.fsm.request('normal')
                toon.startSmooth()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        if not base.config.GetBool('cog-thief-endless', 0):
            self.timer.show()
            self.timer.countdown(CTGG.GameTime, self.__gameTimerExpired)
        self.clockStopTime = None
        self.rewardPanel.reparentTo(base.a2dTopRight)
        self.scoreMult = MinigameGlobals.getScoreMult(self.cr.playGame.hood.id)
        self.__startRewardCountdown()
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        self.gameFSM.request('play')
        return

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        self.startGameWalk()
        self.spawnUpdateSuitsTask()
        self.accept(base.JUMP, self.controlKeyPressed)
        self.pieHandler = CollisionHandlerEvent()
        self.pieHandler.setInPattern('pieHit-%fn')

    def exitPlay(self):
        self.ignore(base.JUMP)
        if self.resultIval and self.resultIval.isPlaying():
            self.resultIval.finish()
            self.resultIval = None
        return

    def enterCleanup(self):
        self.__killRewardCountdown()
        if hasattr(self, 'jarIval'):
            self.jarIval.finish()
            del self.jarIval
        for key in self.toonHitTracks:
            ival = self.toonHitTracks[key]
            if ival.isPlaying():
                ival.finish()

        self.toonHitTracks = {}
        for key in self.toonPieTracks:
            ival = self.toonPieTracks[key]
            if ival.isPlaying():
                ival.finish()

        self.toonPieTracks = {}
        for key in self.cogInfo:
            cogThief = self.cogInfo[key]['suit']
            cogThief.cleanup()

        self.removeUpdateSuitsTask()
        self.notify.debug('enterCleanup')

    def exitCleanup(self):
        pass

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if toon:
            index = self.avIdList.index(avId)
            toon.setPos(CTGG.ToonStartingPositions[index])
            toon.setHpr(0, 0, 0)

    def moveCameraToTop(self):
        camera.reparentTo(render)
        p = self.cameraTopView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])
        base.camLens.setMinFov(46/(4./3.))
        camera.setZ(camera.getZ() + base.config.GetFloat('cog-thief-z-camera-adjust', 0.0))

    def destroyGameWalk(self):
        self.notify.debug('destroyOrthoWalk')
        if self.useOrthoWalk:
            self.gameWalk.destroy()
            del self.gameWalk
        else:
            self.notify.debug('TODO destroyGameWalk')

    def initGameWalk(self):
        self.notify.debug('startOrthoWalk')
        if self.useOrthoWalk:

            def doCollisions(oldPos, newPos, self = self):
                x = bound(newPos[0], CTGG.StageHalfWidth, -CTGG.StageHalfWidth)
                y = bound(newPos[1], CTGG.StageHalfHeight, -CTGG.StageHalfHeight)
                newPos.setX(x)
                newPos.setY(y)
                return newPos

            orthoDrive = OrthoDrive(self.ToonSpeed, customCollisionCallback=doCollisions, instantTurn=True)
            self.gameWalk = OrthoWalk(orthoDrive, broadcast=not self.isSinglePlayer())
        else:
            self.gameWalk = CogThiefWalk.CogThiefWalk('walkDone')
            forwardSpeed = self.ToonSpeed / 2.0
            base.mouseInterfaceNode.setForwardSpeed(forwardSpeed)
            multiplier = forwardSpeed / ToontownGlobals.ToonForwardSpeed
            base.mouseInterfaceNode.setRotateSpeed(ToontownGlobals.ToonRotateSpeed * 4)

    def initCogInfo(self):
        for cogIndex in xrange(self.getNumCogs()):
            self.cogInfo[cogIndex] = {'pos': Point3(CTGG.CogStartingPositions[cogIndex]),
             'goal': CTGG.NoGoal,
             'goalId': CTGG.InvalidGoalId,
             'suit': None}

        return

    def loadCogs(self):
        suitTypes = ['ds',
         'ac',
         'bc',
         'ms']
        for suitIndex in xrange(self.getNumCogs()):
            st = self.randomNumGen.choice(suitTypes)
            suit = CogThief.CogThief(suitIndex, st, self, self.getCogSpeed())
            self.cogInfo[suitIndex]['suit'] = suit

    def handleEnterSphere(self, colEntry):
        if self.gameIsEnding:
            return
        intoName = colEntry.getIntoNodePath().getName()
        fromName = colEntry.getFromNodePath().getName()
        debugInto = intoName.split('/')
        debugFrom = fromName.split('/')
        self.notify.debug('handleEnterSphere gametime=%s %s into %s' % (self.getCurrentGameTime(), debugFrom[-1], debugInto[-1]))
        intoName = colEntry.getIntoNodePath().getName()
        if 'CogThiefSphere' in intoName:
            parts = intoName.split('-')
            suitNum = int(parts[1])
            self.localToonHitBySuit(suitNum)

    def localToonHitBySuit(self, suitNum):
        self.notify.debug('localToonHitBySuit %d' % suitNum)
        timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
        pos = self.cogInfo[suitNum]['suit'].suit.getPos()
        self.sendUpdate('hitBySuit', [self.localAvId,
         timestamp,
         suitNum,
         pos[0],
         pos[1],
         pos[2]])
        self.showToonHitBySuit(self.localAvId, timestamp)
        self.makeSuitRespondToToonHit(timestamp, suitNum)

    def hitBySuit(self, avId, timestamp, suitNum, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ['play']:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        if self.gameIsEnding:
            return
        self.notify.debug('avatar ' + `avId` + ' hit by a suit')
        if avId != self.localAvId:
            self.showToonHitBySuit(avId, timestamp)
            self.makeSuitRespondToToonHit(timestamp, suitNum)

    def showToonHitBySuit(self, avId, timestamp):
        toon = self.getAvatar(avId)
        if toon == None:
            return
        rng = self.toonRNGs[self.avIdList.index(avId)]
        curPos = toon.getPos(render)
        oldTrack = self.toonHitTracks[avId]
        if oldTrack.isPlaying():
            oldTrack.finish()
        toon.setPos(curPos)
        toon.setZ(self.TOON_Z)
        parentNode = render.attachNewNode('mazeFlyToonParent-' + `avId`)
        parentNode.setPos(toon.getPos())
        toon.reparentTo(parentNode)
        toon.setPos(0, 0, 0)
        startPos = parentNode.getPos()
        dropShadow = toon.dropShadow.copyTo(parentNode)
        dropShadow.setScale(toon.dropShadow.getScale(render))
        trajectory = Trajectory.Trajectory(0, Point3(0, 0, 0), Point3(0, 0, 50), gravMult=1.0)
        oldFlyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        trajectory = Trajectory.Trajectory(0, Point3(0, 0, 0), Point3(0, 0, 50), gravMult=0.55)
        flyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        avIndex = self.avIdList.index(avId)
        endPos = CTGG.ToonStartingPositions[avIndex]

        def flyFunc(t, trajectory, startPos = startPos, endPos = endPos, dur = flyDur, moveNode = parentNode, flyNode = toon):
            u = t / dur
            moveNode.setX(startPos[0] + u * (endPos[0] - startPos[0]))
            moveNode.setY(startPos[1] + u * (endPos[1] - startPos[1]))
            flyNode.setPos(trajectory.getPos(t))

        flyTrack = Sequence(LerpFunctionInterval(flyFunc, fromData=0.0, toData=flyDur, duration=flyDur, extraArgs=[trajectory]), name=toon.uniqueName('hitBySuit-fly'))
        geomNode = toon.getGeomNode()
        startHpr = geomNode.getHpr()
        destHpr = Point3(startHpr)
        hRot = rng.randrange(1, 8)
        if rng.choice([0, 1]):
            hRot = -hRot
        destHpr.setX(destHpr[0] + hRot * 360)
        spinHTrack = Sequence(LerpHprInterval(geomNode, flyDur, destHpr, startHpr=startHpr), Func(geomNode.setHpr, startHpr), name=toon.uniqueName('hitBySuit-spinH'))
        parent = geomNode.getParent()
        rotNode = parent.attachNewNode('rotNode')
        geomNode.reparentTo(rotNode)
        rotNode.setZ(toon.getHeight() / 2.0)
        oldGeomNodeZ = geomNode.getZ()
        geomNode.setZ(-toon.getHeight() / 2.0)
        startHpr = rotNode.getHpr()
        destHpr = Point3(startHpr)
        pRot = rng.randrange(1, 3)
        if rng.choice([0, 1]):
            pRot = -pRot
        destHpr.setY(destHpr[1] + pRot * 360)
        spinPTrack = Sequence(LerpHprInterval(rotNode, flyDur, destHpr, startHpr=startHpr), Func(rotNode.setHpr, startHpr), name=toon.uniqueName('hitBySuit-spinP'))
        i = self.avIdList.index(avId)
        soundTrack = Sequence(Func(base.playSfx, self.sndTable['hitBySuit'][i]), Wait(flyDur * (2.0 / 3.0)), SoundInterval(self.sndTable['falling'][i], duration=flyDur * (1.0 / 3.0)), name=toon.uniqueName('hitBySuit-soundTrack'))

        def preFunc(self = self, avId = avId, toon = toon, dropShadow = dropShadow):
            forwardSpeed = toon.forwardSpeed
            rotateSpeed = toon.rotateSpeed
            if avId == self.localAvId:
                self.stopGameWalk()
            else:
                toon.stopSmooth()
            if forwardSpeed or rotateSpeed:
                toon.setSpeed(forwardSpeed, rotateSpeed)
            toon.dropShadow.hide()

        def postFunc(self = self, avId = avId, oldGeomNodeZ = oldGeomNodeZ, dropShadow = dropShadow, parentNode = parentNode):
            if avId == self.localAvId:
                base.localAvatar.setPos(endPos)
                if hasattr(self, 'gameWalk'):
                    toon = base.localAvatar
                    toon.setSpeed(0, 0)
                    self.startGameWalk()
            dropShadow.removeNode()
            del dropShadow
            toon = self.getAvatar(avId)
            if toon:
                toon.dropShadow.show()
                geomNode = toon.getGeomNode()
                rotNode = geomNode.getParent()
                baseNode = rotNode.getParent()
                geomNode.reparentTo(baseNode)
                rotNode.removeNode()
                del rotNode
                geomNode.setZ(oldGeomNodeZ)
            if toon:
                toon.reparentTo(render)
                toon.setPos(endPos)
            parentNode.removeNode()
            del parentNode
            if avId != self.localAvId:
                if toon:
                    toon.startSmooth()

        preFunc()
        slipBack = Parallel(Sequence(ActorInterval(toon, 'slip-backward', endFrame=24), Wait(CTGG.LyingDownDuration - (flyDur - oldFlyDur)), ActorInterval(toon, 'slip-backward', startFrame=24)))
        if toon.doId == self.localAvId:
            slipBack.append(SoundInterval(self.sndOof))
        hitTrack = Sequence(Parallel(flyTrack, spinHTrack, spinPTrack, soundTrack), slipBack, Func(postFunc), name=toon.uniqueName('hitBySuit'))
        self.notify.debug('hitTrack duration = %s' % hitTrack.getDuration())
        self.toonHitTracks[avId] = hitTrack
        hitTrack.start(globalClockDelta.localElapsedTime(timestamp))
        return

    def updateSuitGoal(self, timestamp, inResponseToClientStamp, suitNum, goalType, goalId, x, y, z):
        if not self.hasLocalToon:
            return
        self.notify.debug('updateSuitGoal gameTime=%s timeStamp=%s cog=%s goal=%s goalId=%s (%.1f, %.1f,%.1f)' % (self.getCurrentGameTime(),
         timestamp,
         suitNum,
         CTGG.GoalStr[goalType],
         goalId,
         x,
         y,
         z))
        cog = self.cogInfo[suitNum]
        cog['goal'] = goalType
        cog['goalId'] = goalId
        newPos = Point3(x, y, z)
        cog['pos'] = newPos
        suit = cog['suit']
        suit.updateGoal(timestamp, inResponseToClientStamp, goalType, goalId, newPos)

    def spawnUpdateSuitsTask(self):
        self.notify.debug('spawnUpdateSuitsTask')
        for cogIndex in self.cogInfo:
            suit = self.cogInfo[cogIndex]['suit']
            suit.gameStart(self.gameStartTime)

        taskMgr.remove(self.UPDATE_SUITS_TASK)
        taskMgr.add(self.updateSuitsTask, self.UPDATE_SUITS_TASK)

    def removeUpdateSuitsTask(self):
        taskMgr.remove(self.UPDATE_SUITS_TASK)

    def updateSuitsTask(self, task):
        if self.gameIsEnding:
            return task.done
        for cogIndex in self.cogInfo:
            suit = self.cogInfo[cogIndex]['suit']
            suit.think()

        return task.cont

    def makeSuitRespondToToonHit(self, timestamp, suitNum):
        cog = self.cogInfo[suitNum]['suit']
        cog.respondToToonHit(timestamp)

    def handleEnterBarrel(self, colEntry):
        if self.gameIsEnding:
            return
        intoName = colEntry.getIntoNodePath().getName()
        fromName = colEntry.getFromNodePath().getName()
        debugInto = intoName.split('/')
        debugFrom = fromName.split('/')
        self.notify.debug('handleEnterBarrel gameTime=%s %s into %s' % (self.getCurrentGameTime(), debugFrom[-1], debugInto[-1]))
        if 'CogThiefSphere' in intoName:
            parts = intoName.split('-')
            cogIndex = int(parts[1])
            barrelName = colEntry.getFromNodePath().getName()
            barrelParts = barrelName.split('-')
            barrelIndex = int(barrelParts[1])
            cog = self.cogInfo[cogIndex]['suit']
            if cog.barrel == CTGG.NoBarrelCarried and barrelIndex not in self.stolenBarrels:
                timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
                if cog.suit:
                    cogPos = cog.suit.getPos()
                    collisionPos = colEntry.getContactPos(render)
                    self.sendUpdate('cogHitBarrel', [timestamp,
                     cogIndex,
                     barrelIndex,
                     cogPos[0],
                     cogPos[1],
                     cogPos[2]])

    def makeCogCarryBarrel(self, timestamp, inResponseToClientStamp, cogIndex, barrelIndex, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameIsEnding:
            return
        self.notify.debug('makeCogCarryBarrel gameTime=%s timeStamp=%s cog=%s barrel=%s (%.1f, %.1f,%.1f)' % (self.getCurrentGameTime(),
         timestamp,
         cogIndex,
         barrelIndex,
         x,
         y,
         z))
        barrel = self.barrels[barrelIndex]
        self.notify.debug('barrelPos= %s' % barrel.getPos())
        cog = self.cogInfo[cogIndex]['suit']
        cogPos = Point3(x, y, z)
        cog.makeCogCarryBarrel(timestamp, inResponseToClientStamp, barrel, barrelIndex, cogPos)

    def makeCogDropBarrel(self, timestamp, inResponseToClientStamp, cogIndex, barrelIndex, x, y, z):
        if not self.hasLocalToon:
            return
        self.notify.debug('makeCogDropBarrel gameTime=%s timeStamp=%s cog=%s barrel=%s (%.1f, %.1f,%.1f)' % (self.getCurrentGameTime(),
         timestamp,
         cogIndex,
         barrelIndex,
         x,
         y,
         z))
        barrel = self.barrels[barrelIndex]
        self.notify.debug('barrelPos= %s' % barrel.getPos())
        cog = self.cogInfo[cogIndex]['suit']
        cogPos = Point3(x, y, z)
        cog.makeCogDropBarrel(timestamp, inResponseToClientStamp, barrel, barrelIndex, cogPos)

    def controlKeyPressed(self):
        if self.isToonPlayingHitTrack(self.localAvId):
            return
        if self.gameIsEnding:
            return
        if self.getCurrentGameTime() - self.lastTimeControlPressed > self.ControlKeyLimitTime:
            self.lastTimeControlPressed = self.getCurrentGameTime()
            self.notify.debug('controlKeyPressed')
            toonSD = self.toonSDs[self.localAvId]
            curState = toonSD.fsm.getCurrentState().getName()
            toon = self.getAvatar(self.localAvId)
            timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
            pos = toon.getPos()
            heading = toon.getH()
            self.sendUpdate('throwingPie', [self.localAvId,
             timestamp,
             heading,
             pos[0],
             pos[1],
             pos[2]])
            self.showToonThrowingPie(self.localAvId, timestamp, heading, pos)

    def throwingPie(self, avId, timestamp, heading, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ['play']:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        self.notify.debug('avatar ' + `avId` + ' throwing pie')
        if avId != self.localAvId:
            pos = Point3(x, y, z)
            self.showToonThrowingPie(avId, timestamp, heading, pos)

    def showToonThrowingPie(self, avId, timestamp, heading, pos):
        toon = self.getAvatar(avId)
        if toon:
            tossTrack, pieTrack, flyPie = self.getTossPieInterval(toon, pos[0], pos[1], pos[2], heading, 0, 0, 0)

            def removePieFromTraverser(flyPie = flyPie):
                if base.cTrav:
                    if flyPie:
                        base.cTrav.removeCollider(flyPie)

            if avId == self.localAvId:
                flyPie.setTag('throwerId', str(avId))
                collSphere = CollisionSphere(0, 0, 0, 0.5)
                collSphere.setTangible(0)
                name = 'PieSphere-%d' % avId
                collSphereName = self.uniqueName(name)
                collNode = CollisionNode(collSphereName)
                collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
                collNode.addSolid(collSphere)
                colNp = flyPie.attachNewNode(collNode)
                colNp.show()
                base.cTrav.addCollider(colNp, self.pieHandler)
                self.accept('pieHit-' + collSphereName, self.handlePieHitting)

            def matchRunningAnim(toon = toon):
                toon.playingAnim = None
                toon.setSpeed(toon.forwardSpeed, toon.rotateSpeed)
                return

            newTossTrack = Sequence(tossTrack, Func(matchRunningAnim))
            pieTrack = Parallel(newTossTrack, pieTrack)
            elapsedTime = globalClockDelta.localElapsedTime(timestamp)
            if elapsedTime < 16.0 / 24.0:
                elapsedTime = 16.0 / 24.0
            pieTrack.start(elapsedTime)
            self.toonPieTracks[avId] = pieTrack

    def getTossPieInterval(self, toon, x, y, z, h, p, r, power, beginFlyIval = Sequence()):
        from src.toontown.toonbase import ToontownBattleGlobals
        from src.toontown.battle import BattleProps
        pie = toon.getPieModel()
        pie.setScale(0.9)
        flyPie = pie.copyTo(NodePath('a'))
        pieName = ToontownBattleGlobals.pieNames[toon.pieType]
        pieType = BattleProps.globalPropPool.getPropType(pieName)
        animPie = Sequence()
        if pieType == 'actor':
            animPie = ActorInterval(pie, pieName, startFrame=48)
        sound = loader.loadSfx('phase_3.5/audio/sfx/AA_pie_throw_only.ogg')
        t = power / 100.0
        dist = 100 - 70 * t
        time = 1 + 0.5 * t
        proj = ProjectileInterval(None, startPos=Point3(0, 0, 0), endPos=Point3(0, dist, 0), duration=time)
        relVel = proj.startVel

        def getVelocity(toon = toon, relVel = relVel):
            return render.getRelativeVector(toon, relVel) * 0.6

        toss = Track((0, Sequence(Func(toon.setPosHpr, x, y, z, h, p, r), Func(pie.reparentTo, toon.rightHand), Func(pie.setPosHpr, 0, 0, 0, 0, 0, 0), Parallel(ActorInterval(toon, 'throw', startFrame=48, partName='torso'), animPie), Func(toon.loop, 'neutral'))), (16.0 / 24.0, Func(pie.detachNode)))
        fly = Track((14.0 / 24.0, SoundInterval(sound, node=toon)), (16.0 / 24.0, Sequence(Func(flyPie.reparentTo, render), Func(flyPie.setPosHpr, toon, 0.52, 0.97, 2.24, 0, -45, 0), beginFlyIval, ProjectileInterval(flyPie, startVel=getVelocity, duration=6), Func(flyPie.detachNode))))
        return (toss, fly, flyPie)

    def handlePieHitting(self, colEntry):
        if self.gameIsEnding:
            return
        into = colEntry.getIntoNodePath()
        intoName = into.getName()
        if 'CogThiefPieSphere' in intoName:
            timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
            parts = intoName.split('-')
            suitNum = int(parts[1])
            pos = self.cogInfo[suitNum]['suit'].suit.getPos()
            if pos in CTGG.CogStartingPositions:
                self.notify.debug('Cog %d hit at starting pos %s, ignoring' % (suitNum, pos))
            else:
                self.sendUpdate('pieHitSuit', [self.localAvId,
                 timestamp,
                 suitNum,
                 pos[0],
                 pos[1],
                 pos[2]])
                self.makeSuitRespondToPieHit(timestamp, suitNum)

    def pieHitSuit(self, avId, timestamp, suitNum, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ['play']:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        if self.gameIsEnding:
            return
        self.notify.debug('avatar ' + `avId` + ' hit by a suit')
        if avId != self.localAvId:
            self.makeSuitRespondToPieHit(timestamp, suitNum)

    def makeSuitRespondToPieHit(self, timestamp, suitNum):
        cog = self.cogInfo[suitNum]['suit']
        cog.respondToPieHit(timestamp)

    def sendCogAtReturnPos(self, cogIndex, barrelIndex):
        timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime(), bits=32)
        self.sendUpdate('cogAtReturnPos', [timestamp, cogIndex, barrelIndex])

    def markBarrelStolen(self, timestamp, inResponseToClientStamp, barrelIndex):
        if not self.hasLocalToon:
            return
        if barrelIndex not in self.stolenBarrels:
            self.stolenBarrels.append(barrelIndex)
            barrel = self.barrels[barrelIndex]
            barrel.hide()
        if base.config.GetBool('cog-thief-check-barrels', 1):
            if not base.config.GetBool('cog-thief-endless', 0):
                if len(self.stolenBarrels) == len(self.barrels):
                    localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
                    gameTime = self.local2GameTime(localStamp)
                    self.clockStopTime = gameTime
                    self.notify.debug('clockStopTime = %s' % gameTime)
                    score = int(self.scoreMult * CTGG.calcScore(gameTime) + 0.5)
                    self.rewardPanel['text'] = str(score)
                    self.showResults()

    def __gameTimerExpired(self):
        self.notify.debug('game timer expired')
        self.showResults()

    def __startRewardCountdown(self):
        taskMgr.remove(self.REWARD_COUNTDOWN_TASK)
        taskMgr.add(self.__updateRewardCountdown, self.REWARD_COUNTDOWN_TASK)

    def __killRewardCountdown(self):
        taskMgr.remove(self.REWARD_COUNTDOWN_TASK)

    def __updateRewardCountdown(self, task):
        curTime = self.getCurrentGameTime()
        if self.clockStopTime is not None:
            if self.clockStopTime < curTime:
                self.notify.debug('self.clockStopTime < curTime %s %s' % (self.clockStopTime, curTime))
                self.__killRewardCountdown()
                curTime = self.clockStopTime
        if curTime > CTGG.GameTime:
            curTime = CTGG.GameTime
        score = int(self.scoreMult * CTGG.calcScore(curTime) + 0.5)
        if not hasattr(task, 'curScore'):
            task.curScore = score
        result = Task.cont
        if hasattr(self, 'rewardPanel'):
            self.rewardPanel['text'] = str(score)
            if task.curScore != score:
                if hasattr(self, 'jarIval'):
                    self.jarIval.finish()
                s = self.rewardPanel.getScale()
                self.jarIval = Parallel(Sequence(self.rewardPanel.scaleInterval(0.15, s * 3.0 / 4.0, blendType='easeOut'), self.rewardPanel.scaleInterval(0.15, s, blendType='easeIn')), SoundInterval(self.sndRewardTick), name='cogThiefGameRewardJarThrob')
                self.jarIval.start()
            task.curScore = score
        else:
            result = Task.done
        return result

    def startGameWalk(self):
        if self.useOrthoWalk:
            self.gameWalk.start()
        else:
            self.gameWalk.enter()
            self.gameWalk.fsm.request('walking')

    def stopGameWalk(self):
        if self.useOrthoWalk:
            self.gameWalk.stop()
        else:
            self.gameWalk.exit()

    def getCogThief(self, cogIndex):
        return self.cogInfo[cogIndex]['suit']

    def isToonPlayingHitTrack(self, avId):
        if avId in self.toonHitTracks:
            track = self.toonHitTracks[avId]
            if track.isPlaying():
                return True
        return False

    def getNumCogs(self):
        result = base.config.GetInt('cog-thief-num-cogs', 0)
        if not result:
            safezone = self.getSafezoneId()
            result = CTGG.calculateCogs(self.numPlayers, safezone)
        return result

    def getCogSpeed(self):
        result = 6.0
        safezone = self.getSafezoneId()
        result = CTGG.calculateCogSpeed(self.numPlayers, safezone)
        return result

    def showResults(self):
        if not self.gameIsEnding:
            self.gameIsEnding = True
            for barrel in self.barrels:
                barrel.wrtReparentTo(render)

            for key in self.cogInfo:
                thief = self.cogInfo[key]['suit']
                thief.suit.setPos(100, 0, 0)
                thief.suit.hide()

            self.__killRewardCountdown()
            self.stopGameWalk()
            numBarrelsSaved = len(self.barrels) - len(self.stolenBarrels)
            resultStr = ''
            if numBarrelsSaved == len(self.barrels):
                resultStr = TTLocalizer.CogThiefPerfect
            elif numBarrelsSaved > 1:
                resultStr = TTLocalizer.CogThiefBarrelsSaved % {'num': numBarrelsSaved}
            elif numBarrelsSaved == 1:
                resultStr = TTLocalizer.CogThiefBarrelSaved % {'num': numBarrelsSaved}
            else:
                resultStr = TTLocalizer.CogThiefNoBarrelsSaved
            perfectTextSubnode = hidden.attachNewNode(self.__genText(resultStr))
            perfectText = hidden.attachNewNode('perfectText')
            perfectTextSubnode.reparentTo(perfectText)
            frame = self.__textGen.getCardActual()
            offsetY = -abs(frame[2] + frame[3]) / 2.0
            perfectTextSubnode.setPos(0, 0, offsetY)
            perfectText.setColor(1, 0.1, 0.1, 1)

            def fadeFunc(t, text = perfectText):
                text.setColorScale(1, 1, 1, t)

            def destroyText(text = perfectText):
                text.removeNode()

            def safeGameOver(self = self):
                if not self.frameworkFSM.isInternalStateInFlux():
                    self.gameOver()

            textTrack = Sequence(Func(perfectText.reparentTo, aspect2d), Parallel(LerpScaleInterval(perfectText, duration=0.5, scale=0.3, startScale=0.0), LerpFunctionInterval(fadeFunc, fromData=0.0, toData=1.0, duration=0.5)), Wait(2.0), Parallel(LerpScaleInterval(perfectText, duration=0.5, scale=1.0), LerpFunctionInterval(fadeFunc, fromData=1.0, toData=0.0, duration=0.5, blendType='easeIn')), Func(destroyText), WaitInterval(0.5), Func(safeGameOver))
            if numBarrelsSaved == len(self.barrels):
                soundTrack = SoundInterval(self.sndPerfect)
            else:
                soundTrack = Sequence()
            self.resultIval = Parallel(textTrack, soundTrack)
            self.resultIval.start()

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def getIntroTrack(self):
        base.camera.setPosHpr(0, -13.66, 13.59, 0, -51.6, 0)
        result = Sequence(Wait(2), LerpPosHprInterval(base.camera, 13, Point3(self.cameraTopView[0], self.cameraTopView[1], self.cameraTopView[2]), Point3(self.cameraTopView[3], self.cameraTopView[4], self.cameraTopView[5]), blendType='easeIn'))
        return result
Beispiel #13
0
 def _initOrthoWalk(self):
     orthoDrive = OrthoDrive(9.778, customCollisionCallback=self.activity.view.checkOrthoDriveCollision)
     self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)
Beispiel #14
0
class PartyCogActivityLocalPlayer(PartyCogActivityPlayer):

    def __init__(self, activity, position, team, exitActivityCallback = None):
        PartyCogActivityPlayer.__init__(self, activity, base.localAvatar, position, team)
        self.input = PartyCogActivityInput(exitActivityCallback)
        self.gui = PartyCogActivityGui()
        self.throwPiePrevTime = 0
        self.lastMoved = 0
        if base.localAvatar:
            self.prevPos = base.localAvatar.getPos()
        self.cameraManager = None
        self.control = None
        self.consecutiveShortThrows = 0
        return

    def destroy(self):
        if self.enabled:
            self.disable()
        if self.cameraManager is not None:
            self.cameraManager.setEnabled(False)
            self.cameraManager.destroy()
        del self.cameraManager
        del self.gui
        del self.input
        if self.control is not None:
            self.control.destroy()
        del self.control
        PartyCogActivityPlayer.destroy(self)
        return

    def _initOrthoWalk(self):
        orthoDrive = OrthoDrive(9.778, customCollisionCallback=self.activity.view.checkOrthoDriveCollision)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)

    def _destroyOrthoWalk(self):
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk

    def getPieThrowingPower(self, time):
        elapsed = max(time - self.input.throwPiePressedStartTime, 0.0)
        w = 1.0 / PartyGlobals.CogActivityPowerMeterTime * 2.0 * math.pi
        power = int(round(-math.cos(w * elapsed) * 50.0 + 50.0))
        return power

    def isShortThrow(self, time):
        elapsed = max(time - self.input.throwPiePressedStartTime, 0.0)
        return elapsed <= PartyGlobals.CogActivityShortThrowTime

    def checkForThrowSpam(self, time):
        if self.isShortThrow(time):
            self.consecutiveShortThrows += 1
        else:
            self.consecutiveShortThrows = 0
        return self.consecutiveShortThrows >= PartyGlobals.CogActivityShortThrowSpam

    def _startUpdateTask(self):
        task = Task(self._updateTask)
        task.lastPositionBroadcastTime = 0.0
        self.throwPiePrevTime = 0
        taskMgr.add(task, UPDATE_TASK_NAME)

    def _stopUpdateTask(self):
        taskMgr.remove(UPDATE_TASK_NAME)

    def _updateTask(self, task):
        self._update()
        if base.localAvatar.getPos() != self.prevPos:
            self.prevPos = base.localAvatar.getPos()
            self.lastMoved = self.activity.getCurrentActivityTime()
        if max(self.activity.getCurrentActivityTime() - self.lastMoved, 0) > PartyGlobals.ToonMoveIdleThreshold:
            self.gui.showMoveControls()
        if max(self.activity.getCurrentActivityTime() - self.throwPiePrevTime, 0) > PartyGlobals.ToonAttackIdleThreshold:
            self.gui.showAttackControls()
        if self.input.throwPieWasReleased:
            if self.checkForThrowSpam(globalClock.getFrameTime()):
                self.gui.showSpamWarning()
            self.input.throwPieWasReleased = False
            self.throwPie(self.getPieThrowingPower(globalClock.getFrameTime()))
        return Task.cont

    def throwPie(self, piePower):
        if not self.activity.isState('Active'):
            return
        if self.activity.getCurrentActivityTime() - self.throwPiePrevTime > THROW_PIE_LIMIT_TIME:
            self.throwPiePrevTime = self.activity.getCurrentActivityTime()
            self.activity.b_pieThrow(self.toon, piePower)

    def _update(self):
        self.control.update()

    def getLookat(self, whosLooking, refNode = None):
        if refNode is None:
            refNode = render
        dist = 5.0
        oldParent = self.tempNP.getParent()
        self.tempNP.reparentTo(whosLooking)
        self.tempNP.setPos(0.0, dist, 0.0)
        pos = self.tempNP.getPos(refNode)
        self.tempNP.reparentTo(oldParent)
        return pos

    def entersActivity(self):
        base.cr.playGame.getPlace().setState('activity')
        PartyCogActivityPlayer.entersActivity(self)
        self.gui.disableToontownHUD()
        self.cameraManager = CameraManager(camera)
        self.tempNP = NodePath('temp')
        self.lookAtMyTeam()
        self.control = StrafingControl(self)

    def exitsActivity(self):
        PartyCogActivityPlayer.exitsActivity(self)
        self.gui.enableToontownHUD()
        self.cameraManager.setEnabled(False)
        self.tempNP.removeNode()
        self.tempNP = None
        if not aspect2d.find('**/JellybeanRewardGui*'):
            base.cr.playGame.getPlace().setState('walk')
        else:
            self.toon.startPosHprBroadcast()
        return

    def getRunToStartPositionIval(self):
        targetH = self.locator.getH()
        travelVec = self.position - self.toon.getPos(self.activity.root)
        duration = travelVec.length() / 9.778
        startH = 0.0
        if travelVec.getY() < 0.0:
            startH = 180.0
        return Sequence(Func(self.toon.startPosHprBroadcast, 0.1), Func(self.toon.b_setAnimState, 'run'), Parallel(self.toon.hprInterval(0.5, VBase3(startH, 0.0, 0.0), other=self.activity.root), self.toon.posInterval(duration, self.position, other=self.activity.root)), Func(self.toon.b_setAnimState, 'neutral'), self.toon.hprInterval(0.25, VBase3(targetH, 0.0, 0.0), other=self.activity.root), Func(self.toon.stopPosHprBroadcast))

    def enable(self):
        if self.enabled:
            return
        PartyCogActivityPlayer.enable(self)
        self.toon.b_setAnimState('Happy')
        self._initOrthoWalk()
        self.orthoWalk.start()
        self.orthoWalking = True
        self.input.enable()
        self.gui.disableToontownHUD()
        self.gui.load()
        self.gui.setScore(0)
        self.gui.showScore()
        self.gui.setTeam(self.team)
        self.gui.startTrackingCogs(self.activity.view.cogManager.cogs)
        self.control.enable()
        self._startUpdateTask()

    def disable(self):
        if not self.enabled:
            return
        self._stopUpdateTask()
        self.toon.b_setAnimState('neutral')
        PartyCogActivityPlayer.disable(self)
        self.orthoWalking = False
        self.orthoWalk.stop()
        self._destroyOrthoWalk()
        self.input.disable()
        self._aimMode = False
        self.cameraManager.setEnabled(False)
        self.gui.hide()
        self.gui.stopTrackingCogs()
        self.gui.unload()

    def updateScore(self):
        self.gui.setScore(self.score)

    def b_updateToonPosition(self):
        self.updateToonPosition()
        self.d_updateToonPosition()

    def d_updateToonPosition(self):
        self.toon.d_setPos(self.toon.getX(), self.toon.getY(), self.toon.getZ())
        self.toon.d_setH(self.toon.getH())

    def lookAtArena(self):
        self.cameraManager.setEnabled(True)
        self.cameraManager.setTargetPos(self.activity.view.arena.find('**/conclusionCamPos_locator').getPos(render))
        self.cameraManager.setTargetLookAtPos(self.activity.view.arena.find('**/conclusionCamAim_locator').getPos(render))

    def lookAtMyTeam(self):
        activityView = self.activity.view
        arena = activityView.arena
        pos = activityView.teamCamPosLocators[self.team].getPos()
        aim = activityView.teamCamAimLocators[self.team].getPos()
        base.camera.wrtReparentTo(arena)
        self.cameraManager.setPos(base.camera.getPos(render))
        self.tempNP.reparentTo(arena)
        self.tempNP.setPos(arena, pos)
        self.cameraManager.setTargetPos(self.tempNP.getPos(render))
        self.cameraManager.setLookAtPos(self.getLookat(camera))
        self.tempNP.reparentTo(arena)
        self.tempNP.setPos(arena, aim)
        self.cameraManager.setTargetLookAtPos(self.tempNP.getPos(render))
        self.cameraManager.setEnabled(True)
        base.camera.setP(0.0)
        base.camera.setR(0.0)
 def initOrthoWalker(self):
     orthoDrive = OrthoDrive(9.778, maxFrameMove=0.5, wantSound=True)
     self.orthoWalk = OrthoWalk(orthoDrive, broadcast=False, collisions=False, broadcastPeriod=Globals.AI.BroadcastPeriod)
class DistributedPartyDanceActivityBase(DistributedPartyActivity):
    notify = directNotify.newCategory('DistributedPartyDanceActivity')

    def __init__(self, cr, actId, dancePatternToAnims, model = 'phase_13/models/parties/danceFloor'):
        DistributedPartyActivity.__init__(self, cr, actId, ActivityTypes.Continuous)
        self.model = model
        self.danceFloor = None
        self.localToonDancing = False
        self.keyCodes = None
        self.gui = None
        self.currentCameraMode = None
        self.orthoWalk = None
        self.cameraParallel = None
        self.localToonDanceSequence = None
        self.localPatternsMatched = []
        self.dancePatternToAnims = dancePatternToAnims
        self.dancingToonFSMs = {}
        return

    def generateInit(self):
        self.notify.debug('generateInit')
        DistributedPartyActivity.generateInit(self)
        self.keyCodes = KeyCodes(patterns=self.dancePatternToAnims.keys())
        self.gui = KeyCodesGui(self.keyCodes)
        self.__initOrthoWalk()
        self.activityFSM = DanceActivityFSM(self)

    def announceGenerate(self):
        DistributedPartyActivity.announceGenerate(self)
        self.activityFSM.request('Active')

    def load(self):
        DistributedPartyActivity.load(self)
        self.danceFloor = loader.loadModel(self.model)
        self.danceFloor.reparentTo(self.getParentNodePath())
        self.danceFloor.setPos(self.x, self.y, 0.0)
        self.danceFloor.setH(self.h)
        self.danceFloor.wrtReparentTo(render)
        self.sign.setPos(22, -22, 0)
        floor = self.danceFloor.find('**/danceFloor_mesh')
        self.danceFloorSequence = Sequence(Wait(0.3), Func(floor.setH, floor, 36))
        discoBall = self.danceFloor.find('**/discoBall_mesh')
        self.discoBallSequence = Parallel(discoBall.hprInterval(6.0, Vec3(360, 0, 0)), Sequence(discoBall.posInterval(3, Point3(0, 0, 1), blendType='easeInOut'), discoBall.posInterval(3, Point3(0, 0, 0), blendType='easeInOut')))

    def unload(self):
        DistributedPartyActivity.unload(self)
        self.activityFSM.request('Disabled')
        if self.localToonDanceSequence is not None:
            self.localToonDanceSequence.finish()
        if self.localToonDancing:
            self.__localStopDancing()
        self.ignoreAll()
        if self.discoBallSequence is not None:
            self.discoBallSequence.finish()
        if self.danceFloorSequence is not None:
            self.danceFloorSequence.finish()
        del self.danceFloorSequence
        del self.discoBallSequence
        del self.localToonDanceSequence
        if self.danceFloor is not None:
            self.danceFloor.removeNode()
            self.danceFloor = None
        self.__destroyOrthoWalk()
        for toonId in self.dancingToonFSMs.keys():
            self.dancingToonFSMs[toonId].destroy()
            del self.dancingToonFSMs[toonId]

        del self.dancingToonFSMs
        del self.cameraParallel
        del self.currentCameraMode
        if self.keyCodes is not None:
            self.keyCodes.destroy()
            del self.keyCodes
        del self.activityFSM
        del self.gui
        del self.localPatternsMatched
        return

    def handleToonDisabled(self, toonId):
        self.notify.debug('handleToonDisabled avatar ' + str(toonId) + ' disabled')
        if toonId in self.dancingToonFSMs:
            self.dancingToonFSMs[toonId].request('cleanup')
            self.dancingToonFSMs[toonId].destroy()
            del self.dancingToonFSMs[toonId]

    def getTitle(self):
        self.notify.warning('define title for this dance activity')
        return TTLocalizer.PartyDanceActivityTitle

    def getInstructions(self):
        self.notify.warning('define instructions for this dance activity')
        return TTLocalizer.PartyDanceActivityInstructions

    def startActive(self):
        self.accept('enter' + DANCE_FLOOR_COLLISION, self.__handleEnterDanceFloor)
        self.accept('exit' + DANCE_FLOOR_COLLISION, self.__handleExitDanceFloor)
        self.danceFloorSequence.loop()
        self.discoBallSequence.loop()

    def finishActive(self):
        pass

    def startDisabled(self):
        self.ignore('enter' + DANCE_FLOOR_COLLISION)
        self.ignore('exit' + DANCE_FLOOR_COLLISION)
        self.discoBallSequence.pause()
        self.danceFloorSequence.pause()

    def finishDisabled(self):
        pass

    def __initOrthoWalk(self):
        self.notify.debug('Initialize Ortho Walk')
        orthoDrive = OrthoDrive(9.778)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)

    def __destroyOrthoWalk(self):
        self.notify.debug('Destroy Ortho Walk')
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk

    def __disableLocalControl(self):
        self.orthoWalk.stop()
        self.keyCodes.disable()
        self.keyCodesGui.disable()

    def __enableLocalControl(self):
        self.orthWalk.start()
        self.keyCodes.enable()
        self.keyCodesGui.enable()
        self.keyCodesGui.hideAll()

    def __handleEnterDanceFloor(self, collEntry):
        if not self.isLocalToonInActivity() and not self.localToonDancing:
            self.notify.debug('Toon enters dance floor collision area.')
            place = base.cr.playGame.getPlace()
            if place and hasattr(place, 'fsm'):
                place.fsm.request('activity')
            self.d_toonJoinRequest()
            place = base.cr.playGame.getPlace()
            if place and hasattr(place, 'fsm'):
                place.fsm.request('activity')

    def joinRequestDenied(self, reason):
        DistributedPartyActivity.joinRequestDenied(self, reason)
        self.showMessage(TTLocalizer.PartyActivityDefaultJoinDeny)
        place = base.cr.playGame.getPlace()
        if place and hasattr(place, 'fsm'):
            place.fsm.request('walk')

    def setToonsPlaying(self, toonIds, toonHeadings):
        self.notify.debug('setToonsPlaying')
        self.notify.debug('\ttoonIds: %s' % toonIds)
        self.notify.debug('\ttoonHeadings: %s' % toonHeadings)
        exitedToons, joinedToons = self.getToonsPlayingChanges(self.toonIds, toonIds)
        self.notify.debug('\texitedToons: %s' % exitedToons)
        self.notify.debug('\tjoinedToons: %s' % joinedToons)
        self.setToonIds(toonIds)
        self._processExitedToons(exitedToons)
        for toonId in joinedToons:
            if toonId != base.localAvatar.doId or toonId == base.localAvatar.doId and self.isLocalToonRequestStatus(PartyGlobals.ActivityRequestStatus.Joining):
                self._enableHandleToonDisabled(toonId)
                self.handleToonJoined(toonId, toonHeadings[toonIds.index(toonId)])
                if toonId == base.localAvatar.doId:
                    self._localToonRequestStatus = None

        return

    def handleToonJoined(self, toonId, h):
        self.notify.debug('handleToonJoined( toonId=%d, h=%.2f )' % (toonId, h))
        if toonId in base.cr.doId2do:
            toonFSM = PartyDanceActivityToonFSM(toonId, self, h)
            toonFSM.request('Init')
            self.dancingToonFSMs[toonId] = toonFSM
            if toonId == base.localAvatar.doId:
                self.__localStartDancing(h)

    def __localStartDancing(self, h):
        if not self.localToonDancing:
            place = base.cr.playGame.getPlace()
            if place and hasattr(place, 'fsm'):
                self.localToonDancing = True
                place.fsm.request('activity')
                self.__updateLocalToonState(ToonDancingStates.Run)
                self.__setViewMode(DanceViews.Dancing)
                self.gui.load()
                self.startRules()
                self.__localEnableControls()
            else:
                self.notify.warning('__localStartDancing, failed in playGame.getPlace()')

    def handleRulesDone(self):
        self.finishRules()

    def __localEnableControls(self):
        if base.localAvatar.doId not in self.dancingToonFSMs:
            self.notify.debug('no dancing FSM for local avatar, not enabling controls')
            return
        self.accept(KeyCodes.PATTERN_MATCH_EVENT, self.__doDanceMove)
        self.accept(KeyCodes.PATTERN_NO_MATCH_EVENT, self.__noDanceMoveMatch)
        self.acceptOnce(KeyCodes.KEY_DOWN_EVENT, self._handleKeyDown)
        self.accept(KeyCodes.KEY_UP_EVENT, self._handleKeyUp)
        self.keyCodes.enable()
        self.orthoWalk.start()
        self.gui.enable()
        self.gui.hideAll()

    def __localDisableControls(self):
        self.orthoWalk.stop()
        self.keyCodes.disable()
        self.gui.disable()
        self.ignore(KeyCodes.PATTERN_MATCH_EVENT)
        self.ignore(KeyCodes.PATTERN_NO_MATCH_EVENT)
        self.ignore(KeyCodes.KEY_DOWN_EVENT)
        self.ignore(KeyCodes.KEY_UP_EVENT)

    def __handleExitDanceFloor(self, collEntry):
        if self.localToonDanceSequence is not None:
            self.notify.debug('finishing %s' % self.localToonDanceSequence)
            self.localToonDanceSequence.finish()
            self.localToonDanceSequence = None
        self.finishRules()
        self.notify.debug('Toon exits dance floor collision area.')
        self.d_toonExitRequest()
        return

    def exitRequestDenied(self, reason):
        DistributedPartyActivity.exitRequestDenied(self, reason)
        if reason != PartyGlobals.DenialReasons.SilentFail:
            self.showMessage(TTLocalizer.PartyActivityDefaultExitDeny)

    def handleToonExited(self, toonId):
        self.notify.debug('exitDanceFloor %s' % toonId)
        if toonId == base.localAvatar.doId:
            self.__localStopDancing()

    def __localStopDancing(self):
        if self.localToonDancing:
            self.__localDisableControls()
            self.gui.unload()
            self.__setViewMode(DanceViews.Normal)
            self.__updateLocalToonState(ToonDancingStates.Cleanup)
            if base.cr.playGame.getPlace():
                if hasattr(base.cr.playGame.getPlace(), 'fsm'):
                    base.cr.playGame.getPlace().fsm.request('walk')
            self.localToonDancing = False

    def __doDanceMove(self, pattern):
        self.notify.debug('Dance move! %s' % pattern)
        anim = self.dancePatternToAnims.get(pattern)
        if anim:
            self.__updateLocalToonState(ToonDancingStates.DanceMove, anim)
            self.gui.setColor(0, 1, 0)
            self.gui.showText(DanceAnimToName.get(anim, anim))
            self.finishRules()
            if pattern not in self.localPatternsMatched:
                camNode = NodePath(self.uniqueName('danceCamNode'))
                camNode.reparentTo(base.localAvatar)
                camNode.lookAt(camera)
                camNode.setHpr(camNode.getH(), 0, 0)
                node2 = NodePath('tempCamNode')
                node2.reparentTo(camNode)
                node2.setPos(Point3(0, 15, 10))
                node2.lookAt(camNode)
                h = node2.getH() * (base.camera.getH(camNode) / abs(base.camera.getH(camNode)))
                node2.removeNode
                del node2
                hpr = base.camera.getHpr()
                pos = base.camera.getPos()
                camParent = base.camera.getParent()
                base.camera.wrtReparentTo(camNode)
                self.localToonDanceSequence = Sequence(Func(self.__localDisableControls), Parallel(base.camera.posInterval(0.5, Point3(0, 15, 10), blendType='easeIn'), base.camera.hprInterval(0.5, Point3(h, -20, 0), blendType='easeIn')), camNode.hprInterval(4.0, Point3(camNode.getH() - 360, 0, 0)), Func(base.camera.wrtReparentTo, camParent), Func(camNode.removeNode), Parallel(base.camera.posInterval(0.5, pos, blendType='easeOut'), base.camera.hprInterval(0.5, hpr, blendType='easeOut')), Func(self.__localEnableControls))
            else:
                self.localToonDanceSequence = Sequence(Func(self.__localDisableControls), Wait(2.0), Func(self.__localEnableControls))
            self.localToonDanceSequence.start()
            self.localPatternsMatched.append(pattern)

    def __noDanceMoveMatch(self):
        self.gui.setColor(1, 0, 0)
        self.gui.showText('No Match!')
        self.__updateLocalToonState(ToonDancingStates.DanceMove)
        self.localToonDanceSequence = Sequence(Func(self.__localDisableControls), Wait(1.0), Func(self.__localEnableControls))
        self.localToonDanceSequence.start()

    def _handleKeyDown(self, key, index):
        self.__updateLocalToonState(ToonDancingStates.Run)

    def _handleKeyUp(self, key):
        if not self.keyCodes.isAnyKeyPressed():
            self.__updateLocalToonState(ToonDancingStates.DanceMove)
            self.acceptOnce(KeyCodes.KEY_DOWN_EVENT, self._handleKeyDown)

    def __updateLocalToonState(self, state, anim = ''):
        self._requestToonState(base.localAvatar.doId, state, anim)
        self.d_updateDancingToon(state, anim)

    def d_updateDancingToon(self, state, anim):
        self.sendUpdate('updateDancingToon', [state, anim])

    def setDancingToonState(self, toonId, state, anim):
        if toonId != base.localAvatar.doId and toonId in self.dancingToonFSMs:
            self._requestToonState(toonId, state, anim)

    def _requestToonState(self, toonId, state, anim):
        if toonId in self.dancingToonFSMs:
            state = ToonDancingStates.getString(state)
            curState = self.dancingToonFSMs[toonId].getCurrentOrNextState()
            try:
                self.dancingToonFSMs[toonId].request(state, anim)
            except FSM.RequestDenied:
                self.notify.warning('could not go from state=%s to state %s' % (curState, state))

            if state == ToonDancingStates.getString(ToonDancingStates.Cleanup):
                self.notify.debug('deleting this fsm %s' % self.dancingToonFSMs[toonId])
                del self.dancingToonFSMs[toonId]
                if self.localToonDanceSequence:
                    self.notify.debug('forcing a finish of localToonDanceSequence')
                    self.localToonDanceSequence.finish()
                    self.localToonDanceSequence = None
        return

    def __setViewMode(self, mode):
        toon = base.localAvatar
        if mode == DanceViews.Normal:
            if self.cameraParallel is not None:
                self.cameraParallel.pause()
                self.cameraParallel = None
            base.camera.reparentTo(toon)
            base.localAvatar.startUpdateSmartCamera()
        elif mode == DanceViews.Dancing:
            base.localAvatar.stopUpdateSmartCamera()
            base.camera.wrtReparentTo(self.danceFloor)
            node = NodePath('temp')
            node.reparentTo(toon.getParent())
            node.setPos(Point3(0, -40, 20))
            node2 = NodePath('temp2')
            node2.reparentTo(self.danceFloor)
            node.reparentTo(node2)
            node2.setH(render, toon.getParent().getH())
            pos = node.getPos(self.danceFloor)
            node2.removeNode()
            node.removeNode()
            self.cameraParallel = Parallel(base.camera.posInterval(0.5, pos, blendType='easeIn'), base.camera.hprInterval(0.5, Point3(0, -27, 0), other=toon.getParent(), blendType='easeIn'))
            self.cameraParallel.start()
        self.currentCameraMode = mode
        return
class CogdoFlyingLocalPlayer(CogdoFlyingPlayer):
    notify = DirectNotifyGlobal.directNotify.newCategory('CogdoFlyingLocalPlayer')
    BroadcastPosTask = 'CogdoFlyingLocalPlayerBroadcastPos'
    PlayWaitingMusicEventName = 'PlayWaitingMusicEvent'
    RanOutOfTimeEventName = 'RanOutOfTimeEvent'
    PropStates = PythonUtil.Enum(('Normal', 'Overdrive', 'Off'))

    def __init__(self, toon, game, level, guiMgr):
        CogdoFlyingPlayer.__init__(self, toon)
        self.defaultTransitions = {'Inactive': ['FreeFly', 'Running'],
         'FreeFly': ['Inactive',
                     'OutOfTime',
                     'Death',
                     'FlyingUp',
                     'Running',
                     'HitWhileFlying',
                     'InWhirlwind'],
         'FlyingUp': ['Inactive',
                      'OutOfTime',
                      'Death',
                      'FreeFly',
                      'Running',
                      'HitWhileFlying',
                      'InWhirlwind',
                      'WaitingForWin'],
         'InWhirlwind': ['Inactive',
                         'OutOfTime',
                         'Death',
                         'FreeFly',
                         'HitWhileFlying'],
         'HitWhileFlying': ['Inactive',
                            'OutOfTime',
                            'Death',
                            'FreeFly',
                            'InWhirlwind'],
         'Death': ['Inactive', 'OutOfTime', 'Spawn'],
         'Running': ['Inactive',
                     'OutOfTime',
                     'FreeFly',
                     'FlyingUp',
                     'Refuel',
                     'WaitingForWin',
                     'HitWhileRunning'],
         'HitWhileRunning': ['Inactive',
                             'OutOfTime',
                             'Death',
                             'Running',
                             'FreeFly'],
         'Spawn': ['Inactive',
                   'OutOfTime',
                   'Running',
                   'WaitingForWin'],
         'OutOfTime': ['Inactive', 'Spawn'],
         'WaitingForWin': ['Inactive', 'Win'],
         'Win': ['Inactive']}
        self.game = game
        self._level = level
        self._guiMgr = guiMgr
        self._inputMgr = CogdoFlyingInputManager()
        self._cameraMgr = CogdoFlyingCameraManager(camera, render, self, self._level)
        self.velocity = Vec3(0.0, 0.0, 0.0)
        self.instantaneousVelocity = Vec3(0.0, 0.0, 0.0)
        self.controlVelocity = Vec3(0.0, 0.0, 0.0)
        self.fanVelocity = Vec3(0.0, 0.0, 0.0)
        self.activeFans = []
        self.fansStillHavingEffect = []
        self.fanIndex2ToonVelocity = {}
        self.legalEagleInterestRequest = {}
        self.activeWhirlwind = None
        self.oldPos = Vec3(0.0, 0.0, 0.0)
        self.checkpointPlatform = None
        self.isHeadInCeiling = False
        self.isToonOnFloor = False
        self.fuel = 0.0
        self.score = 0
        self.postSpawnState = 'Running'
        self.didTimeRunOut = False
        self.hasPressedCtrlYet = False
        self.hasPickedUpFirstPropeller = False
        self.surfacePoint = None
        self.legalEagleHitting = False
        self.propState = None
        self.broadcastPeriod = Globals.AI.BroadcastPeriod
        self.initSfx()
        self.initLocalPlayerIntervals()
        self.initCollisions()
        self.initOrthoWalker()
        self.playerNumber = -1
        self.fuel = 0.0
        self._guiMgr.setFuel(self.fuel)
        self.setCheckpointPlatform(self._level.startPlatform)

    def initSfx(self):
        audioMgr = base.cogdoGameAudioMgr
        self._deathSfx = audioMgr.createSfx('death')
        self._hitByWhirlwindSfx = audioMgr.createSfx('toonInWhirlwind')
        self._bladeBreakSfx = audioMgr.createSfx('bladeBreak')
        self._collideSfx = audioMgr.createSfx('collide')
        self._toonHitSfx = audioMgr.createSfx('toonHit')
        self._getMemoSfx = audioMgr.createSfx('getMemo')
        self._getLaffSfx = audioMgr.createSfx('getLaff')
        self._getRedTapeSfx = audioMgr.createSfx('getRedTape')
        self._refuelSfx = audioMgr.createSfx('refuel')
        self._fanSfx = audioMgr.createSfx('fan')
        self._invulDebuffSfx = audioMgr.createSfx('invulDebuff')
        self._invulBuffSfx = audioMgr.createSfx('invulBuff')
        self._winSfx = audioMgr.createSfx('win')
        self._loseSfx = audioMgr.createSfx('lose')
        self._refuelSpinSfx = audioMgr.createSfx('refuelSpin')
        self._propellerSfx = audioMgr.createSfx('propeller', self.toon)

    def destroySfx(self):
        del self._deathSfx
        del self._hitByWhirlwindSfx
        del self._bladeBreakSfx
        del self._collideSfx
        del self._toonHitSfx
        del self._propellerSfx
        del self._getMemoSfx
        del self._getLaffSfx
        del self._refuelSfx
        del self._fanSfx
        del self._invulBuffSfx
        del self._invulDebuffSfx
        del self._getRedTapeSfx
        del self._refuelSpinSfx

    def setPlayerNumber(self, num):
        self.playerNumber = num

    def getPlayerNumber(self):
        return self.playerNumber

    def initOrthoWalker(self):
        orthoDrive = OrthoDrive(9.778, maxFrameMove=0.5, wantSound=True)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=False, collisions=False, broadcastPeriod=Globals.AI.BroadcastPeriod)

    def initLocalPlayerIntervals(self):
        self.coolDownAfterHitInterval = Sequence(Wait(Globals.Gameplay.HitCooldownTime), Func(self.setEnemyHitting, False), name='coolDownAfterHitInterval-%i' % self.toon.doId)
        self.deathInterval = Sequence(Func(self.resetVelocities), Parallel(Parallel(Func(self._deathSfx.play), LerpHprInterval(self.toon, 1.0, Vec3(720, 0, 0)), LerpFunctionInterval(self.toon.setScale, fromData=1.0, toData=0.1, duration=1.0), self.toon.posInterval(0.5, Vec3(0, 0, -25), other=self.toon)), Sequence(Wait(0.5), Func(base.transitions.irisOut))), Func(self.toon.stash), Wait(1.0), Func(self.toonSpawnFunc), name='%s.deathInterval' % self.__class__.__name__)
        self.outOfTimeInterval = Sequence(Func(messenger.send, CogdoFlyingLocalPlayer.PlayWaitingMusicEventName), Func(self._loseSfx.play), Func(base.transitions.irisOut), Wait(1.0), Func(self.resetVelocities), Func(self._guiMgr.setMessage, '', transition=None), Func(self.toon.stash), Func(self.toonSpawnFunc), name='%s.outOfTimeInterval' % self.__class__.__name__)
        self.spawnInterval = Sequence(Func(self.resetToonFunc), Func(self._cameraMgr.update, 0.0), Func(self._level.update), Func(self.toon.cnode.broadcastPosHprFull), Func(base.transitions.irisIn), Wait(0.5), Func(self.toon.setAnimState, 'TeleportIn'), Func(self.toon.unstash), Wait(1.5), Func(self.requestPostSpawnState), name='%s.spawnInterval' % self.__class__.__name__)
        self.waitingForWinInterval = Sequence(Func(self._guiMgr.setMessage, TTLocalizer.WaitingForOtherToonsDots % '.'), Wait(1.5), Func(self._guiMgr.setMessage, TTLocalizer.WaitingForOtherToonsDots % '..'), Wait(1.5), Func(self._guiMgr.setMessage, TTLocalizer.WaitingForOtherToonsDots % '...'), Wait(1.5), name='%s.waitingForWinInterval' % self.__class__.__name__)
        self.waitingForWinSeq = Sequence(Func(self.setWaitingForWinState), Wait(4.0), Func(self.removeAllMemos), Wait(2.0), Func(self.game.distGame.d_sendRequestAction, Globals.AI.GameActions.LandOnWinPlatform, 0), Func(self.playWaitingForWinInterval), name='%s.waitingForWinSeq' % self.__class__.__name__)
        self.winInterval = Sequence(Func(self._guiMgr.setMessage, ''), Wait(4.0), Func(self.game.distGame.d_sendRequestAction, Globals.AI.GameActions.WinStateFinished, 0), name='%s.winInterval' % self.__class__.__name__)
        self.goSadSequence = Sequence(Wait(2.5), Func(base.transitions.irisOut, 1.5), name='%s.goSadSequence' % self.__class__.__name__)
        self.introGuiSeq = Sequence(Wait(0.5), Parallel(Func(self._guiMgr.setTemporaryMessage, TTLocalizer.CogdoFlyingGameMinimapIntro, duration=5.0), Sequence(Wait(1.0), Func(self._guiMgr.presentProgressGui))), Wait(5.0), Func(self._guiMgr.setMessage, TTLocalizer.CogdoFlyingGamePickUpAPropeller), name='%s.introGuiSeq' % self.__class__.__name__)
        return

    def goSad(self):
        self.goSadSequence.start()

    def setWaitingForWinState(self):
        if self.didTimeRunOut:
            self.toon.b_setAnimState('Sad')
            self._guiMgr.setMessage(TTLocalizer.CogdoFlyingGameOutOfTime, transition='blink')
        else:
            self._winSfx.play()
            messenger.send(CogdoFlyingLocalPlayer.PlayWaitingMusicEventName)
            self.toon.b_setAnimState('victory')
            self._guiMgr.setMessage(TTLocalizer.CogdoFlyingGameYouMadeIt)

    def removeAllMemos(self):
        if self.didTimeRunOut:
            messenger.send(CogdoFlyingLocalPlayer.RanOutOfTimeEventName)

    def playWaitingForWinInterval(self):
        if not self.game.distGame.isSinglePlayer():
            self.waitingForWinInterval.loop()

    def resetToonFunc(self):
        self.resetToon(resetFuel=self.hasPickedUpFirstPropeller)

    def _loopPropellerSfx(self, playRate = 1.0, volume = 1.0):
        self._propellerSfx.loop(playRate=playRate, volume=1.0)

    def initCollisions(self):
        avatarRadius = 2.0
        reach = 4.0
        self.flyerCollisions = CogdoFlyingCollisions()
        self.flyerCollisions.setWallBitMask(OTPGlobals.WallBitmask)
        self.flyerCollisions.setFloorBitMask(OTPGlobals.FloorBitmask)
        self.flyerCollisions.initializeCollisions(base.cTrav, self.toon, avatarRadius, OTPGlobals.FloorOffset, reach)
        self.flyerCollisions.setCollisionsActive(0)
        floorColl = CogdoFlyingPlatform.FloorCollName
        ceilingColl = CogdoFlyingPlatform.CeilingCollName
        self.accept('Flyer.cHeadCollSphere-enter-%s' % ceilingColl, self.__handleHeadCollisionIntoCeiling)
        self.accept('Flyer.cHeadCollSphere-exit-%s' % ceilingColl, self.__handleHeadCollisionExitCeiling)
        self.accept('Flyer.cFloorEventSphere-exit-%s' % floorColl, self.__handleEventCollisionExitFloor)
        self.accept('Flyer.cRayNode-enter-%s' % floorColl, self.__handleRayCollisionEnterFloor)
        self.accept('Flyer.cRayNode-again-%s' % floorColl, self.__handleRayCollisionAgainFloor)

    def enable(self):
        CogdoFlyingPlayer.enable(self)
        self.toon.hideName()

    def disable(self):
        CogdoFlyingPlayer.disable(self)

    def isLegalEagleInterestRequestSent(self, index):
        if index in self.legalEagleInterestRequest:
            return True
        else:
            return False

    def setLegalEagleInterestRequest(self, index):
        if index not in self.legalEagleInterestRequest:
            self.legalEagleInterestRequest[index] = True
        else:
            CogdoFlyingLocalPlayer.notify.warning('Attempting to set an legal eagle interest request when one already exists:%s' % index)

    def clearLegalEagleInterestRequest(self, index):
        if index in self.legalEagleInterestRequest:
            del self.legalEagleInterestRequest[index]

    def setBackpackState(self, state):
        if state == self.backpackState:
            return
        CogdoFlyingPlayer.setBackpackState(self, state)
        if state in Globals.Gameplay.BackpackStates:
            if state == Globals.Gameplay.BackpackStates.Normal:
                messenger.send(CogdoFlyingGuiManager.ClearMessageDisplayEventName)
            elif state == Globals.Gameplay.BackpackStates.Targeted:
                messenger.send(CogdoFlyingGuiManager.EagleTargetingLocalPlayerEventName)
            elif state == Globals.Gameplay.BackpackStates.Attacked:
                messenger.send(CogdoFlyingGuiManager.EagleAttackingLocalPlayerEventName)

    def requestPostSpawnState(self):
        self.request(self.postSpawnState)

    def toonSpawnFunc(self):
        self.game.distGame.b_toonSpawn(self.toon.doId)

    def __handleHeadCollisionIntoCeiling(self, collEntry):
        self.isHeadInCeiling = True
        self.surfacePoint = self.toon.getPos()
        self._collideSfx.play()
        if self.controlVelocity[2] > 0.0:
            self.controlVelocity[2] = -self.controlVelocity[2] / 2.0

    def __handleHeadCollisionExitCeiling(self, collEntry):
        self.isHeadInCeiling = False
        self.surfacePoint = None
        return

    def landOnPlatform(self, collEntry):
        surfacePoint = collEntry.getSurfacePoint(render)
        intoNodePath = collEntry.getIntoNodePath()
        platform = CogdoFlyingPlatform.getFromNode(intoNodePath)
        if platform is not None:
            if not platform.isStartOrEndPlatform():
                taskMgr.doMethodLater(0.5, self.delayedLandOnPlatform, 'delayedLandOnPlatform', extraArgs=[platform])
            elif platform.isEndPlatform():
                taskMgr.doMethodLater(1.0, self.delayedLandOnWinPlatform, 'delayedLandOnWinPlatform', extraArgs=[platform])
        self.isToonOnFloor = True
        self.controlVelocity = Vec3(0.0, 0.0, 0.0)
        self.toon.setPos(render, surfacePoint)
        self.toon.setHpr(0, 0, 0)
        self.request('Running')
        return

    def __handleRayCollisionEnterFloor(self, collEntry):
        fromNodePath = collEntry.getFromNodePath()
        intoNodePath = collEntry.getIntoNodePath()
        intoName = intoNodePath.getName()
        fromName = fromNodePath.getName()
        toonPos = self.toon.getPos(render)
        collPos = collEntry.getSurfacePoint(render)
        if toonPos.getZ() < collPos.getZ() + Globals.Gameplay.RayPlatformCollisionThreshold:
            if not self.isToonOnFloor and self.state in ['FreeFly', 'FlyingUp']:
                self.landOnPlatform(collEntry)

    def __handleRayCollisionAgainFloor(self, collEntry):
        fromNodePath = collEntry.getFromNodePath()
        intoNodePath = collEntry.getIntoNodePath()
        intoName = intoNodePath.getName()
        fromName = fromNodePath.getName()
        toonPos = self.toon.getPos(render)
        collPos = collEntry.getSurfacePoint(render)
        if toonPos.getZ() < collPos.getZ() + Globals.Gameplay.RayPlatformCollisionThreshold:
            if not self.isToonOnFloor and self.state in ['FreeFly', 'FlyingUp']:
                self.landOnPlatform(collEntry)

    def __handleEventCollisionExitFloor(self, collEntry):
        fromNodePath = collEntry.getFromNodePath()
        intoNodePath = collEntry.getIntoNodePath()
        intoName = intoNodePath.getName()
        fromName = fromNodePath.getName()
        if self.isToonOnFloor:
            self.notify.debug('~~~Exit Floor:%s -> %s' % (intoName, fromName))
            self.isToonOnFloor = False
            taskMgr.remove('delayedLandOnPlatform')
            taskMgr.remove('delayedLandOnWinPlatform')
            if self.state not in ['FlyingUp', 'Spawn']:
                self.notify.debug('Exited floor')
                self.request('FreeFly')

    def delayedLandOnPlatform(self, platform):
        self.setCheckpointPlatform(platform)
        return Task.done

    def delayedLandOnWinPlatform(self, platform):
        self.setCheckpointPlatform(self._level.endPlatform)
        self.request('WaitingForWin')
        return Task.done

    def handleTimerExpired(self):
        if self.state not in ['WaitingForWin', 'Win']:
            self.setCheckpointPlatform(self._level.endPlatform)
            self.postSpawnState = 'WaitingForWin'
            self.didTimeRunOut = True
            if self.state not in ['Death']:
                self.request('OutOfTime')

    def ready(self):
        self.resetToon(resetFuel=False)
        self._cameraMgr.enable()
        self._cameraMgr.update()

    def start(self):
        CogdoFlyingPlayer.start(self)
        self.toon.collisionsOff()
        self.flyerCollisions.setAvatar(self.toon)
        self.flyerCollisions.setCollisionsActive(1)
        self._levelBounds = self._level.getBounds()
        self.introGuiSeq.start()
        self.request('Running')

    def exit(self):
        self.request('Inactive')
        CogdoFlyingPlayer.exit(self)
        self._cameraMgr.disable()
        self.flyerCollisions.setCollisionsActive(0)
        self.flyerCollisions.setAvatar(None)
        taskMgr.remove('delayedLandOnFuelPlatform')
        taskMgr.remove('delayedLandOnWinPlatform')
        self.ignoreAll()
        return

    def unload(self):
        self.toon.showName()
        self.toon.collisionsOn()
        self._destroyEventIval()
        self._destroyEnemyHitIval()
        CogdoFlyingPlayer.unload(self)
        self._fanSfx.stop()
        self.flyerCollisions.deleteCollisions()
        del self.flyerCollisions
        self.ignoreAll()
        taskMgr.remove('delayedLandOnPlatform')
        taskMgr.remove('delayedLandOnWinPlatform')
        self.checkpointPlatform = None
        self._cameraMgr.disable()
        del self._cameraMgr
        del self.game
        self._inputMgr.destroy()
        del self._inputMgr
        self.introGuiSeq.clearToInitial()
        del self.introGuiSeq
        if self.goSadSequence:
            self.goSadSequence.clearToInitial()
            del self.goSadSequence
        if self.coolDownAfterHitInterval:
            self.coolDownAfterHitInterval.clearToInitial()
            del self.coolDownAfterHitInterval
        if self.deathInterval:
            self.deathInterval.clearToInitial()
            del self.deathInterval
        if self.spawnInterval:
            self.spawnInterval.clearToInitial()
            del self.spawnInterval
        if self.outOfTimeInterval:
            self.outOfTimeInterval.clearToInitial()
            del self.outOfTimeInterval
        if self.winInterval:
            self.winInterval.clearToInitial()
            del self.winInterval
        if self.waitingForWinInterval:
            self.waitingForWinInterval.clearToInitial()
            del self.waitingForWinInterval
        if self.waitingForWinSeq:
            self.waitingForWinSeq.clearToInitial()
            del self.waitingForWinSeq
        del self.activeFans[:]
        del self.fansStillHavingEffect[:]
        self.fanIndex2ToonVelocity.clear()
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        self.destroySfx()
        return

    def setCheckpointPlatform(self, platform):
        self.checkpointPlatform = platform

    def resetVelocities(self):
        self.fanVelocity = Vec3(0.0, 0.0, 0.0)
        self.controlVelocity = Vec3(0.0, 0.0, 0.0)
        self.velocity = Vec3(0.0, 0.0, 0.0)

    def resetToon(self, resetFuel = True):
        CogdoFlyingPlayer.resetToon(self)
        self.resetVelocities()
        del self.activeFans[:]
        del self.fansStillHavingEffect[:]
        self.fanIndex2ToonVelocity.clear()
        self._fanSfx.stop()
        spawnPos = self.checkpointPlatform.getSpawnPosForPlayer(self.getPlayerNumber(), render)
        self.activeWhirlwind = None
        self.toon.setPos(render, spawnPos)
        self.toon.setHpr(render, 0, 0, 0)
        if resetFuel:
            self.resetFuel()
        self.isHeadInCeiling = False
        self.isToonOnFloor = True
        return

    def activateFlyingBroadcast(self):
        self.timeSinceLastPosBroadcast = 0.0
        self.lastPosBroadcast = self.toon.getPos()
        self.lastHprBroadcast = self.toon.getHpr()
        toon = self.toon
        toon.d_clearSmoothing()
        toon.sendCurrentPosition()
        taskMgr.remove(self.BroadcastPosTask)
        taskMgr.add(self.doBroadcast, self.BroadcastPosTask)

    def shutdownFlyingBroadcast(self):
        taskMgr.remove(self.BroadcastPosTask)

    def doBroadcast(self, task):
        dt = globalClock.getDt()
        self.timeSinceLastPosBroadcast += dt
        if self.timeSinceLastPosBroadcast >= self.broadcastPeriod:
            self.timeSinceLastPosBroadcast = 0.0
            self.toon.cnode.broadcastPosHprFull()
        return Task.cont

    def died(self, timestamp):
        self.request('Death')

    def spawn(self, timestamp):
        self.request('Spawn')

    def updateToonFlyingState(self, dt):
        leftPressed = self._inputMgr.arrowKeys.leftPressed()
        rightPressed = self._inputMgr.arrowKeys.rightPressed()
        upPressed = self._inputMgr.arrowKeys.upPressed()
        downPressed = self._inputMgr.arrowKeys.downPressed()
        jumpPressed = self._inputMgr.arrowKeys.jumpPressed()
        if not self.hasPressedCtrlYet and jumpPressed and self.isFuelLeft():
            self.hasPressedCtrlYet = True
            messenger.send(CogdoFlyingGuiManager.FirstPressOfCtrlEventName)
        if jumpPressed and self.isFuelLeft():
            if self.state == 'FreeFly' and self.isInTransition() == False:
                self.notify.debug('FreeFly -> FlyingUp')
                self.request('FlyingUp')
        elif self.state == 'FlyingUp' and self.isInTransition() == False:
            self.notify.debug('FlyingUp -> FreeFly')
            self.request('FreeFly')
        if leftPressed and not rightPressed:
            self.toon.setH(self.toon, Globals.Gameplay.ToonTurning['turningSpeed'] * dt)
            max = Globals.Gameplay.ToonTurning['maxTurningAngle']
            if self.toon.getH() > max:
                self.toon.setH(max)
        elif rightPressed and not leftPressed:
            self.toon.setH(self.toon, -1.0 * Globals.Gameplay.ToonTurning['turningSpeed'] * dt)
            min = -1.0 * Globals.Gameplay.ToonTurning['maxTurningAngle']
            if self.toon.getH() < min:
                self.toon.setH(min)

    def updateControlVelocity(self, dt):
        leftPressed = self._inputMgr.arrowKeys.leftPressed()
        rightPressed = self._inputMgr.arrowKeys.rightPressed()
        upPressed = self._inputMgr.arrowKeys.upPressed()
        downPressed = self._inputMgr.arrowKeys.downPressed()
        jumpPressed = self._inputMgr.arrowKeys.jumpPressed()
        if leftPressed:
            self.controlVelocity[0] -= Globals.Gameplay.ToonAcceleration['turning'] * dt
        if rightPressed:
            self.controlVelocity[0] += Globals.Gameplay.ToonAcceleration['turning'] * dt
        if upPressed:
            self.controlVelocity[1] += Globals.Gameplay.ToonAcceleration['forward'] * dt
        if downPressed:
            self.controlVelocity[2] -= Globals.Gameplay.ToonAcceleration['activeDropDown'] * dt
            self.controlVelocity[1] -= Globals.Gameplay.ToonAcceleration['activeDropBack'] * dt
        if jumpPressed and self.isFuelLeft():
            self.controlVelocity[2] += Globals.Gameplay.ToonAcceleration['boostUp'] * dt
        minVal = -Globals.Gameplay.ToonVelMax['turning']
        maxVal = Globals.Gameplay.ToonVelMax['turning']
        if not leftPressed and not rightPressed or self.controlVelocity[0] > maxVal or self.controlVelocity[0] < minVal:
            x = self.dampenVelocityVal(self.controlVelocity[0], 'turning', 'turning', minVal, maxVal, dt)
            self.controlVelocity[0] = x
        minVal = -Globals.Gameplay.ToonVelMax['backward']
        maxVal = Globals.Gameplay.ToonVelMax['forward']
        if not upPressed and not downPressed or self.controlVelocity[1] > maxVal or self.controlVelocity[1] < minVal:
            y = self.dampenVelocityVal(self.controlVelocity[1], 'backward', 'forward', minVal, maxVal, dt)
            self.controlVelocity[1] = y
        if self.isFuelLeft():
            minVal = -Globals.Gameplay.ToonVelMax['fall']
        else:
            minVal = -Globals.Gameplay.ToonVelMax['fallNoFuel']
        maxVal = Globals.Gameplay.ToonVelMax['boost']
        if self.controlVelocity[2] > minVal:
            if (not self._inputMgr.arrowKeys.jumpPressed() or not self.isFuelLeft()) and not self.isToonOnFloor:
                self.controlVelocity[2] -= Globals.Gameplay.ToonAcceleration['fall'] * dt
        if self.controlVelocity[2] < 0.0 and self.isToonOnFloor:
            self.controlVelocity[2] = 0.0
        minVal = -Globals.Gameplay.ToonVelMax['turning']
        maxVal = Globals.Gameplay.ToonVelMax['turning']
        self.controlVelocity[0] = clamp(self.controlVelocity[0], minVal, maxVal)
        minVal = -Globals.Gameplay.ToonVelMax['backward']
        maxVal = Globals.Gameplay.ToonVelMax['forward']
        self.controlVelocity[1] = clamp(self.controlVelocity[1], minVal, maxVal)
        if self.isFuelLeft():
            minVal = -Globals.Gameplay.ToonVelMax['fall']
        else:
            minVal = -Globals.Gameplay.ToonVelMax['fallNoFuel']
        maxVal = Globals.Gameplay.ToonVelMax['boost']
        self.controlVelocity[2] = clamp(self.controlVelocity[2], minVal, maxVal)

    def updateFanVelocity(self, dt):
        fanHeight = Globals.Gameplay.FanCollisionTubeHeight
        min = Globals.Gameplay.FanMinPower
        max = Globals.Gameplay.FanMaxPower
        powerRange = max - min
        for fan in self.activeFans:
            blowVec = fan.getBlowDirection()
            blowVec *= Globals.Gameplay.ToonAcceleration['fan'] * dt
            if Globals.Gameplay.UseVariableFanPower:
                distance = fan.model.getDistance(self.toon)
                power = math.fabs(distance / fanHeight - 1.0) * powerRange + min
                power = clamp(power, min, max)
                blowVec *= power
            if fan.index in self.fanIndex2ToonVelocity:
                fanVelocity = self.fanIndex2ToonVelocity[fan.index]
                fanVelocity += blowVec

        removeList = []
        for fan in self.fansStillHavingEffect:
            if fan not in self.activeFans:
                blowVec = fan.getBlowDirection()
                blowVec *= Globals.Gameplay.ToonDeceleration['fan'] * dt
                if fan.index in self.fanIndex2ToonVelocity:
                    fanVelocity = Vec3(self.fanIndex2ToonVelocity[fan.index])
                    lastLen = fanVelocity.length()
                    fanVelocity -= blowVec
                    if fanVelocity.length() > lastLen:
                        removeList.append(fan)
                    else:
                        self.fanIndex2ToonVelocity[fan.index] = fanVelocity

        for fan in removeList:
            self.fansStillHavingEffect.remove(fan)
            if fan.index in self.fanIndex2ToonVelocity:
                del self.fanIndex2ToonVelocity[fan.index]

        self.fanVelocity = Vec3(0.0, 0.0, 0.0)
        for fan in self.fansStillHavingEffect:
            if fan.index in self.fanIndex2ToonVelocity:
                self.fanVelocity += self.fanIndex2ToonVelocity[fan.index]

        minVal = -Globals.Gameplay.ToonVelMax['fan']
        maxVal = Globals.Gameplay.ToonVelMax['fan']
        self.fanVelocity[0] = clamp(self.fanVelocity[0], minVal, maxVal)
        self.fanVelocity[1] = clamp(self.fanVelocity[1], minVal, maxVal)
        self.fanVelocity[2] = clamp(self.fanVelocity[2], minVal, maxVal)

    def dampenVelocityVal(self, velocityVal, typeNeg, typePos, minVal, maxVal, dt):
        if velocityVal > 0.0:
            velocityVal -= Globals.Gameplay.ToonDeceleration[typePos] * dt
            velocityVal = clamp(velocityVal, 0.0, maxVal)
        elif velocityVal < 0.0:
            velocityVal += Globals.Gameplay.ToonDeceleration[typeNeg] * dt
            velocityVal = clamp(velocityVal, minVal, 0.0)
        return velocityVal

    def allowFuelDeath(self):
        if Globals.Gameplay.DoesToonDieWithFuel:
            return True
        else:
            return not self.isFuelLeft()

    def updateToonPos(self, dt):
        toonWorldY = self.toon.getY(render)
        if self.hasPickedUpFirstPropeller == False:
            if toonWorldY > -7.6:
                self.toon.setY(-7.6)
            elif toonWorldY < -35.0:
                self.toon.setY(-35.0)
            return
        self.velocity = self.controlVelocity + self.fanVelocity
        vel = self.velocity * dt
        self.toon.setPos(self.toon, vel[0], vel[1], vel[2])
        toonPos = self.toon.getPos()
        if Globals.Dev.DisableDeath:
            pass
        elif toonPos[2] < 0.0 and self.state in ['FreeFly', 'FlyingUp'] and self.allowFuelDeath():
            self.postSpawnState = 'Running'
            self.game.distGame.b_toonDied(self.toon.doId)
        if toonPos[2] > self._levelBounds[2][1]:
            self.controlVelocity[2] = 0.0
            self.fanVelocity[2] = 0.0
        toonPos = Vec3(clamp(toonPos[0], self._levelBounds[0][0], self._levelBounds[0][1]), clamp(toonPos[1], self._levelBounds[1][0], self._levelBounds[1][1]), clamp(toonPos[2], self._levelBounds[2][0], self._levelBounds[2][1]))
        if self.isHeadInCeiling and toonPos[2] > self.surfacePoint[2]:
            toonPos[2] = self.surfacePoint[2]
        self.toon.setPos(toonPos)
        if self.toon.getY(render) < -10:
            self.toon.setY(-10.0)

    def printFanInfo(self, string):
        if len(self.fanIndex2ToonVelocity) > 0:
            self.notify.info('==AFTER %s==' % string)
            self.notify.info('Fan velocity:%s' % self.fanVelocity)
        if len(self.activeFans) > 0:
            self.notify.info('%s' % self.activeFans)
        if len(self.fanIndex2ToonVelocity) > 0:
            self.notify.info('%s' % self.fanIndex2ToonVelocity)
        if len(self.fansStillHavingEffect) > 0:
            self.notify.info('%s' % self.fansStillHavingEffect)

    def resetFuel(self):
        self.setFuel(Globals.Gameplay.FuelNormalAmt)

    def isFuelLeft(self):
        return self.fuel > 0.0

    def setFuel(self, fuel):
        self.fuel = fuel
        self._guiMgr.setFuel(fuel)
        if self.fuel <= 0.0:
            fuelState = Globals.Gameplay.FuelStates.FuelEmpty
        elif self.fuel < Globals.Gameplay.FuelVeryLowAmt:
            fuelState = Globals.Gameplay.FuelStates.FuelVeryLow
        elif self.fuel < Globals.Gameplay.FuelLowAmt:
            fuelState = Globals.Gameplay.FuelStates.FuelLow
        else:
            fuelState = Globals.Gameplay.FuelStates.FuelNormal
        if fuelState > self.fuelState:
            self.game.distGame.b_toonSetBlades(self.toon.doId, fuelState)
        if fuelState < self.fuelState:
            if self.state in ['FlyingUp', 'FreeFly', 'Running']:
                self.game.distGame.b_toonBladeLost(self.toon.doId)

    def resetBlades(self):
        CogdoFlyingPlayer.resetBlades(self)
        self._guiMgr.resetBlades()

    def setBlades(self, fuelState):
        CogdoFlyingPlayer.setBlades(self, fuelState)
        self._guiMgr.setBlades(fuelState)

    def bladeLost(self):
        CogdoFlyingPlayer.bladeLost(self)
        self._bladeBreakSfx.play(volume=0.35)
        self._guiMgr.bladeLost()

    def updateFuel(self, dt):
        if Globals.Dev.InfiniteFuel:
            self.setFuel(Globals.Gameplay.FuelNormalAmt)
        elif self.state in Globals.Gameplay.DepleteFuelStates and self.fuel > 0.0:
            self.setFuel(self.fuel - Globals.Gameplay.FuelBurnRate * dt)
        elif self.fuel < 0.0:
            self.setFuel(0.0)

    def update(self, dt = 0.0):
        self.instantaneousVelocity = (self.toon.getPos() - self.oldPos) / dt
        self.oldPos = self.toon.getPos()
        self.updateFuel(dt)
        if self.isFlying():
            self.updateToonFlyingState(dt)
        if self.state in ['FreeFly', 'FlyingUp', 'Death']:
            self.updateControlVelocity(dt)
        self.updateFanVelocity(dt)
        self.updateToonPos(dt)
        self._cameraMgr.update(dt)

    def isFlying(self):
        if self.state in ['FreeFly', 'FlyingUp']:
            return True
        else:
            return False

    def pressedControlWhileRunning(self):
        if self.isFuelLeft() and self.state == 'Running':
            self.notify.debug('Pressed Control and have fuel')
            self.request('FlyingUp')
        else:
            self.ignore(base.JUMP)
            self.ignore('lcontrol')
            self.acceptOnce(base.JUMP, self.pressedControlWhileRunning)
            self.acceptOnce('lcontrol', self.pressedControlWhileRunning)

    def setPropellerState(self, propState):
        if not self.hasPickedUpFirstPropeller:
            propState = CogdoFlyingLocalPlayer.PropStates.Off
        if self.propState != propState:
            oldState = self.propState
            self.propState = propState
            if self.propState == CogdoFlyingLocalPlayer.PropStates.Normal:
                if not self.propellerSpinLerp.isPlaying():
                    self.propellerSpinLerp.loop()
                self.setPropellerSpinRate(Globals.Gameplay.NormalPropSpeed)
                self._guiMgr.setPropellerSpinRate(Globals.Gameplay.NormalPropSpeed)
                self._loopPropellerSfx(playRate=0.7, volume=0.8)
            elif self.propState == CogdoFlyingLocalPlayer.PropStates.Overdrive:
                if not self.propellerSpinLerp.isPlaying():
                    self.propellerSpinLerp.loop()
                self.setPropellerSpinRate(Globals.Gameplay.OverdrivePropSpeed)
                self._guiMgr.setPropellerSpinRate(Globals.Gameplay.OverdrivePropSpeed)
                self._loopPropellerSfx(playRate=1.1)
            elif self.propState == CogdoFlyingLocalPlayer.PropStates.Off:
                self.propellerSpinLerp.pause()
                self._propellerSfx.stop()

    def enterInactive(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self._inputMgr.disable()
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Off)
        self.shutdownFlyingBroadcast()

    def filterInactive(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitInactive(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))
        self._inputMgr.enable()
        self.activateFlyingBroadcast()

    def enterSpawn(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self.toon.b_setAnimState('Happy', 1.0)
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)
        self.spawnInterval.start()

    def filterSpawn(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitSpawn(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))

    def enterFreeFly(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)
        if self.oldState in ['Running', 'HitWhileRunning']:
            self.toon.jumpStart()
            self.toon.setHpr(render, 0, 0, 0)

    def filterFreeFly(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitFreeFly(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))

    def enterFlyingUp(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Overdrive)
        if self.oldState in ['Running']:
            self.toon.jumpStart()
            self.toon.setHpr(render, 0, 0, 0)

    def filterFlyingUp(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitFlyingUp(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))

    def enterHitWhileFlying(self, elapsedTime = 0.0):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self.setEnemyHitting(True)
        self._toonHitSfx.play()
        self.startHitFlyingToonInterval()
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)

    def filterHitWhileFlying(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitHitWhileFlying(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))
        self.enemyHitIval.clearToInitial()
        self.coolDownAfterHitInterval.clearToInitial()
        self.coolDownAfterHitInterval.start()

    def enterInWhirlwind(self, elapsedTime = 0.0):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self._hitByWhirlwindSfx.play()
        self.startHitByWhirlwindInterval()
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)

    def filterInWhirlwind(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitInWhirlwind(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))
        self.eventIval.clearToInitial()

    def enterHitWhileRunning(self, elapsedTime = 0.0):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self.setEnemyHitting(True)
        self._toonHitSfx.play()
        self.toon.b_setAnimState('FallDown')
        self.startHitRunningToonInterval()
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)

    def filterHitWhileRunning(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitHitWhileRunning(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))
        self.enemyHitIval.clearToInitial()
        self.coolDownAfterHitInterval.clearToInitial()
        self.coolDownAfterHitInterval.start()

    def enterRunning(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self.toon.b_setAnimState('Happy', 1.0)
        if self.oldState not in ['Spawn', 'HitWhileRunning', 'Inactive']:
            self.toon.jumpHardLand()
            self._collideSfx.play()
        self.orthoWalk.start()
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)
        self.ignore(base.JUMP)
        self.ignore('lcontrol')
        self.acceptOnce(base.JUMP, self.pressedControlWhileRunning)
        self.acceptOnce('lcontrol', self.pressedControlWhileRunning)

    def filterRunning(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitRunning(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))
        self.orthoWalk.stop()
        self.ignore(base.JUMP)
        self.ignore('lcontrol')

    def enterOutOfTime(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        if self.spawnInterval.isPlaying():
            self.spawnInterval.clearToInitial()
        self.ignoreAll()
        self.introGuiSeq.clearToInitial()
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Off)
        if not Globals.Dev.NoLegalEagleAttacks:
            for eagle in self.legalEaglesTargeting:
                messenger.send(CogdoFlyingLegalEagle.RequestRemoveTargetEventName, [eagle.index])

        taskMgr.remove('delayedLandOnPlatform')
        taskMgr.remove('delayedLandOnWinPlatform')
        self.outOfTimeInterval.start()

    def filterOutOfTime(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitOutOfTime(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))

    def enterDeath(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self.propellerSmoke.stop()
        self.deathInterval.start()
        self.toon.b_setAnimState('jumpAirborne', 1.0)
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Off)
        if not Globals.Dev.NoLegalEagleAttacks:
            for eagle in self.legalEaglesTargeting:
                messenger.send(CogdoFlyingLegalEagle.RequestRemoveTargetEventName, [eagle.index])

    def filterDeath(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitDeath(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))
        self.deathInterval.clearToInitial()

    def enterWaitingForWin(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self.resetFuel()
        self._guiMgr.hideRefuelGui()
        self.waitingForWinSeq.start()
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)
        if not Globals.Dev.NoLegalEagleAttacks:
            self.game.forceClearLegalEagleInterestInToon(self.toon.doId)

    def filterWaitingForWin(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitWaitingForWin(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))
        self.waitingForWinSeq.finish()
        self.waitingForWinInterval.clearToInitial()

    def enterWin(self):
        CogdoFlyingLocalPlayer.notify.info("enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState))
        self._guiMgr.stopTimer()
        self.winInterval.start()
        self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)

    def filterWin(self, request, args):
        if request == self.state:
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitWin(self):
        CogdoFlyingLocalPlayer.notify.debug("exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState))

    def _destroyEventIval(self):
        if hasattr(self, 'eventIval'):
            self.eventIval.clearToInitial()
            del self.eventIval

    def startEventIval(self, ival):
        self._destroyEventIval()
        self.eventIval = ival
        self.eventIval.start()

    def _destroyEnemyHitIval(self):
        if hasattr(self, 'enemyHitIval'):
            self.enemyHitIval.clearToInitial()
            del self.enemyHitIval

    def startEnemyHitIval(self, ival):
        self._destroyEnemyHitIval()
        self.enemyHitIval = ival
        self.enemyHitIval.start()

    def isEnemyHitting(self):
        return self.legalEagleHitting

    def setEnemyHitting(self, value):
        self.legalEagleHitting = value

    def shouldLegalEagleBeInFrame(self):
        if not self.isLegalEagleTarget():
            return False
        else:
            index = len(self.legalEaglesTargeting) - 1
            eagle = self.legalEaglesTargeting[index]
            return eagle.shouldBeInFrame()

    def startHitRunningToonInterval(self):
        dur = self.toon.getDuration('slip-backward')
        self.startEnemyHitIval(Sequence(Wait(dur), Func(self.request, 'Running'), name='hitByLegalEagleIval-%i' % self.toon.doId))

    def startHitFlyingToonInterval(self):
        hitByEnemyPos = self.toon.getPos(render)
        collVec = hitByEnemyPos - self.collPos
        collVec[2] = 0.0
        collVec.normalize()
        collVec *= Globals.Gameplay.HitKnockbackDist

        def spinPlayer(t, rand):
            if rand == 0:
                self.toon.setH(-(t * 720.0))
            else:
                self.toon.setH(t * 720.0)

        direction = random.randint(0, 1)
        self.startEnemyHitIval(Sequence(Parallel(LerpFunc(spinPlayer, fromData=0.0, toData=1.0, duration=Globals.Gameplay.HitKnockbackTime, blendType='easeInOut', extraArgs=[direction]), LerpPosInterval(self.toon, duration=Globals.Gameplay.HitKnockbackTime, pos=hitByEnemyPos + collVec, blendType='easeOut')), Func(self.request, 'FreeFly'), name='hitByLegalEagleIval-%i' % self.toon.doId))

    def startHitByWhirlwindInterval(self):

        def spinPlayer(t):
            self.controlVelocity[2] = 1.0
            angle = math.radians(t * (720.0 * 2 - 180))
            self.toon.setPos(self.activeWhirlwind.model.getX(self.game.level.root) + math.cos(angle) * 2, self.activeWhirlwind.model.getY(self.game.level.root) + math.sin(angle) * 2, self.toon.getZ())

        def movePlayerBack(t):
            self.toon.setY(self.activeWhirlwind.model.getY(self.game.level.root) - t * Globals.Gameplay.WhirlwindMoveBackDist)

        self.startEventIval(Sequence(Func(self._cameraMgr.freeze), Func(self.activeWhirlwind.disable), LerpFunc(spinPlayer, fromData=0.0, toData=1.0, duration=Globals.Gameplay.WhirlwindSpinTime), LerpFunc(movePlayerBack, fromData=0.0, toData=1.0, duration=Globals.Gameplay.WhirlwindMoveBackTime, blendType='easeOut'), Func(self.activeWhirlwind.enable), Func(self._cameraMgr.unfreeze), Func(self.request, 'FreeFly'), name='spinPlayerIval-%i' % self.toon.doId))

    def handleEnterWhirlwind(self, whirlwind):
        self.activeWhirlwind = whirlwind
        self.request('InWhirlwind')

    def handleEnterEnemyHit(self, enemy, collPos):
        self.collPos = collPos
        if self.state in ['FlyingUp', 'FreeFly']:
            self.request('HitWhileFlying')
        elif self.state in ['Running']:
            self.request('HitWhileRunning')

    def handleEnterFan(self, fan):
        if fan in self.activeFans:
            return
        if len(self.activeFans) == 0:
            self._fanSfx.loop()
        self.activeFans.append(fan)
        if fan.index not in self.fanIndex2ToonVelocity:
            self.fanIndex2ToonVelocity[fan.index] = Vec3(0.0, 0.0, 0.0)
        if fan not in self.fansStillHavingEffect:
            self.fansStillHavingEffect.append(fan)

    def handleExitFan(self, fan):
        if fan in self.activeFans:
            self.activeFans.remove(fan)
        if len(self.activeFans) == 0:
            self._fanSfx.stop()

    def handleDebuffPowerup(self, pickupType, elapsedTime):
        self._invulDebuffSfx.play()
        CogdoFlyingPlayer.handleDebuffPowerup(self, pickupType, elapsedTime)
        messenger.send(CogdoFlyingGuiManager.ClearMessageDisplayEventName)

    def handleEnterGatherable(self, gatherable, elapsedTime):
        CogdoFlyingPlayer.handleEnterGatherable(self, gatherable, elapsedTime)
        if gatherable.type == Globals.Level.GatherableTypes.Memo:
            self.handleEnterMemo(gatherable)
        elif gatherable.type == Globals.Level.GatherableTypes.Propeller:
            self.handleEnterPropeller(gatherable)
        elif gatherable.type == Globals.Level.GatherableTypes.LaffPowerup:
            self.handleEnterLaffPowerup(gatherable)
        elif gatherable.type == Globals.Level.GatherableTypes.InvulPowerup:
            self.handleEnterInvulPowerup(gatherable)

    def handleEnterMemo(self, gatherable):
        self.score += 1
        if self.score == 1:
            self._guiMgr.presentMemoGui()
            self._guiMgr.setTemporaryMessage(TTLocalizer.CogdoFlyingGameMemoIntro, 4.0)
        self._guiMgr.setMemoCount(self.score)
        self._getMemoSfx.play()

    def handleEnterPropeller(self, gatherable):
        if self.fuel < 1.0:
            if not self.hasPickedUpFirstPropeller:
                messenger.send(CogdoFlyingGuiManager.PickedUpFirstPropellerEventName)
                self.introGuiSeq.clearToInitial()
                self.hasPickedUpFirstPropeller = True
                self.setPropellerState(CogdoFlyingLocalPlayer.PropStates.Normal)
            self.setFuel(1.0)
            self._guiMgr.update()
            self._refuelSfx.play()
            self._refuelSpinSfx.play(volume=0.15)

    def handleEnterLaffPowerup(self, gatherable):
        self._getLaffSfx.play()

    def handleEnterInvulPowerup(self, gatherable):
        messenger.send(CogdoFlyingGuiManager.InvulnerableEventName)
        self._getRedTapeSfx.play()
class DistributedPartyCatchActivity(DistributedPartyActivity, DistributedPartyCatchActivityBase):
    notify = DirectNotifyGlobal.directNotify.newCategory("DistributedPartyCatchActivity")
    DropTaskName = "dropSomething"
    DropObjectPlurals = {
        "apple": TTLocalizer.PartyCatchActivityApples,
        "orange": TTLocalizer.PartyCatchActivityOranges,
        "pear": TTLocalizer.PartyCatchActivityPears,
        "coconut": TTLocalizer.PartyCatchActivityCoconuts,
        "watermelon": TTLocalizer.PartyCatchActivityWatermelons,
        "pineapple": TTLocalizer.PartyCatchActivityPineapples,
        "anvil": TTLocalizer.PartyCatchActivityAnvils,
    }

    class Generation:
        def __init__(self, generation, startTime, startNetworkTime, numPlayers):
            self.generation = generation
            self.startTime = startTime
            self.startNetworkTime = startNetworkTime
            self.numPlayers = numPlayers
            self.hasBeenScheduled = False
            self.droppedObjNames = []
            self.dropSchedule = []
            self.numItemsDropped = 0
            self.droppedObjCaught = {}

    def __init__(self, cr):
        DistributedPartyActivity.__init__(
            self, cr, PartyGlobals.ActivityIds.PartyCatch, PartyGlobals.ActivityTypes.HostInitiated, wantRewardGui=True
        )
        self.setUsesSmoothing()
        self.setUsesLookAround()
        self._sNumGen = SerialNumGen()

    def getTitle(self):
        return TTLocalizer.PartyCatchActivityTitle

    def getInstructions(self):
        return TTLocalizer.PartyCatchActivityInstructions % {"badThing": self.DropObjectPlurals["anvil"]}

    def generate(self):
        DistributedPartyActivity.generate(self)
        self.notify.info("localAvatar doId: %s" % base.localAvatar.doId)
        self.notify.info("generate()")
        self._generateFrame = globalClock.getFrameCount()
        self._id2gen = {}
        self._orderedGenerations = []
        self._orderedGenerationIndex = None
        rng = RandomNumGen(self.doId)
        self._generationSeedBase = rng.randrange(1000)
        self._lastDropTime = 0.0
        return

    def getCurGeneration(self):
        if self._orderedGenerationIndex is None:
            return
        return self._orderedGenerations[self._orderedGenerationIndex]

    def _addGeneration(self, generation, startTime, startNetworkTime, numPlayers):
        self._id2gen[generation] = self.Generation(generation, startTime, startNetworkTime, numPlayers)
        i = 0
        while 1:
            if i >= len(self._orderedGenerations):
                break
            gen = self._orderedGenerations[i]
            startNetT = self._id2gen[gen].startTime
            genId = self._id2gen[gen].generation
            if startNetT > startNetworkTime:
                break
            if startNetT == startNetworkTime and genId > generation:
                break
            i += 1
        self._orderedGenerations = self._orderedGenerations[:i] + [generation] + self._orderedGenerations[i:]
        if self._orderedGenerationIndex is not None:
            if self._orderedGenerationIndex >= i:
                self._orderedGenerationIndex += 1

    def _removeGeneration(self, generation):
        del self._id2gen[generation]
        i = self._orderedGenerations.index(generation)
        self._orderedGenerations = self._orderedGenerations[:i] + self._orderedGenerations[i + 1 :]
        if self._orderedGenerationIndex is not None:
            if len(self._orderedGenerations):
                if self._orderedGenerationIndex >= i:
                    self._orderedGenerationIndex -= 1
            else:
                self._orderedGenerationIndex = None
        return

    def announceGenerate(self):
        self.notify.info("announceGenerate()")
        self.catchTreeZoneEvent = "fence_floor"
        DistributedPartyActivity.announceGenerate(self)

    def load(self, loadModels=1, arenaModel="partyCatchTree"):
        self.notify.info("load()")
        DistributedPartyCatchActivity.notify.debug("PartyCatch: load")
        self.activityFSM = CatchActivityFSM(self)
        if __dev__:
            for o in xrange(3):
                print {
                    0: "SPOTS PER PLAYER",
                    1: "DROPS PER MINUTE PER SPOT DURING NORMAL DROP PERIOD",
                    2: "DROPS PER MINUTE PER PLAYER DURING NORMAL DROP PERIOD",
                }[o]
                for i in xrange(1, self.FallRateCap_Players + 10):
                    self.defineConstants(forceNumPlayers=i)
                    numDropLocations = self.DropRows * self.DropColumns
                    numDropsPerMin = 60.0 / self.DropPeriod
                    if o == 0:
                        spotsPerPlayer = numDropLocations / float(i)
                        print "%2d PLAYERS: %s" % (i, spotsPerPlayer)
                    elif o == 1:
                        numDropsPerMinPerSpot = numDropsPerMin / numDropLocations
                        print "%2d PLAYERS: %s" % (i, numDropsPerMinPerSpot)
                    elif i > 0:
                        numDropsPerMinPerPlayer = numDropsPerMin / i
                        print "%2d PLAYERS: %s" % (i, numDropsPerMinPerPlayer)

        self.defineConstants()
        self.treesAndFence = loader.loadModel("phase_13/models/parties/%s" % arenaModel)
        self.treesAndFence.setScale(0.9)
        self.treesAndFence.find("**/fence_floor").setPos(0.0, 0.0, 0.1)
        self.treesAndFence.reparentTo(self.root)
        ground = self.treesAndFence.find("**/groundPlane")
        ground.setBin("ground", 1)
        DistributedPartyActivity.load(self)
        exitText = TextNode("PartyCatchExitText")
        exitText.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
        exitText.setCardDecal(True)
        exitText.setCardColor(1.0, 1.0, 1.0, 0.0)
        exitText.setText(TTLocalizer.PartyCatchActivityExit)
        exitText.setTextColor(0.0, 8.0, 0.0, 0.9)
        exitText.setAlign(exitText.ACenter)
        exitText.setFont(ToontownGlobals.getBuildingNametagFont())
        exitText.setShadowColor(0, 0, 0, 1)
        exitText.setBin("fixed")
        if TTLocalizer.BuildingNametagShadow:
            exitText.setShadow(*TTLocalizer.BuildingNametagShadow)
        exitTextLoc = self.treesAndFence.find("**/loc_exitSignText")
        exitTextNp = exitTextLoc.attachNewNode(exitText)
        exitTextNp.setDepthWrite(0)
        exitTextNp.setScale(4)
        exitTextNp.setZ(-0.5)
        self.sign.reparentTo(self.treesAndFence.find("**/loc_eventSign"))
        self.sign.wrtReparentTo(self.root)
        self.avatarNodePath = NodePath("PartyCatchAvatarNodePath")
        self.avatarNodePath.reparentTo(self.root)
        self._avatarNodePathParentToken = 3
        base.cr.parentMgr.registerParent(self._avatarNodePathParentToken, self.avatarNodePath)
        self.toonSDs = {}
        self.dropShadow = loader.loadModelOnce("phase_3/models/props/drop_shadow")
        self.dropObjModels = {}
        if loadModels:
            self.__loadDropModels()
        self.sndGoodCatch = base.loadSfx("phase_4/audio/sfx/SZ_DD_treasure.ogg")
        self.sndOof = base.loadSfx("phase_4/audio/sfx/MG_cannon_hit_dirt.ogg")
        self.sndAnvilLand = base.loadSfx("phase_4/audio/sfx/AA_drop_anvil_miss.ogg")
        self.sndPerfect = base.loadSfx("phase_4/audio/sfx/ring_perfect.ogg")
        self.__textGen = TextNode("partyCatchActivity")
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        self.activityFSM.request("Idle")

    def __loadDropModels(self):
        for objType in PartyGlobals.DropObjectTypes:
            model = loader.loadModel(objType.modelPath)
            self.dropObjModels[objType.name] = model
            modelScales = {
                "apple": 0.7,
                "orange": 0.7,
                "pear": 0.5,
                "coconut": 0.7,
                "watermelon": 0.6,
                "pineapple": 0.45,
            }
            if objType.name in modelScales:
                model.setScale(modelScales[objType.name])
            if objType == PartyGlobals.Name2DropObjectType["pear"]:
                model.setZ(-0.6)
            if objType == PartyGlobals.Name2DropObjectType["coconut"]:
                model.setP(180)
            if objType == PartyGlobals.Name2DropObjectType["watermelon"]:
                model.setH(135)
                model.setZ(-0.5)
            if objType == PartyGlobals.Name2DropObjectType["pineapple"]:
                model.setZ(-1.7)
            if objType == PartyGlobals.Name2DropObjectType["anvil"]:
                model.setZ(-self.ObjRadius)
            model.flattenStrong()

    def unload(self):
        DistributedPartyCatchActivity.notify.debug("unload")
        self.finishAllDropIntervals()
        self.destroyOrthoWalk()
        DistributedPartyActivity.unload(self)
        self.stopDropTask()
        del self.activityFSM
        del self.__textGen
        for avId in self.toonSDs:
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.unload()

        del self.toonSDs
        self.treesAndFence.removeNode()
        del self.treesAndFence
        self.dropShadow.removeNode()
        del self.dropShadow
        base.cr.parentMgr.unregisterParent(self._avatarNodePathParentToken)
        for model in self.dropObjModels.values():
            model.removeNode()

        del self.dropObjModels
        del self.sndGoodCatch
        del self.sndOof
        del self.sndAnvilLand
        del self.sndPerfect

    def setStartTimestamp(self, timestamp32):
        self.notify.info("setStartTimestamp(%s)" % (timestamp32,))
        self._startTimestamp = globalClockDelta.networkToLocalTime(timestamp32, bits=32)

    def getCurrentCatchActivityTime(self):
        return globalClock.getFrameTime() - self._startTimestamp

    def getObjModel(self, objName):
        return self.dropObjModels[objName].copyTo(hidden)

    def joinRequestDenied(self, reason):
        DistributedPartyActivity.joinRequestDenied(self, reason)
        base.cr.playGame.getPlace().fsm.request("walk")

    def handleToonJoined(self, toonId):
        if toonId not in self.toonSDs:
            toonSD = PartyCatchActivityToonSD(toonId, self)
            self.toonSDs[toonId] = toonSD
            toonSD.load()
        self.notify.debug("handleToonJoined : currentState = %s" % self.activityFSM.state)
        self.cr.doId2do[toonId].useLOD(500)
        if self.activityFSM.state == "Active":
            if toonId in self.toonSDs:
                self.toonSDs[toonId].enter()
            if base.localAvatar.doId == toonId:
                base.localAvatar.b_setParent(self._avatarNodePathParentToken)
                self.putLocalAvatarInActivity()
            if toonId in self.toonSDs:
                self.toonSDs[toonId].fsm.request("rules")

    def handleToonExited(self, toonId):
        self.notify.debug("handleToonExited( toonId=%s )" % toonId)
        if toonId in self.cr.doId2do:
            self.cr.doId2do[toonId].resetLOD()
            if toonId in self.toonSDs:
                self.toonSDs[toonId].fsm.request("notPlaying")
                self.toonSDs[toonId].exit()
                self.toonSDs[toonId].unload()
                del self.toonSDs[toonId]
            if base.localAvatar.doId == toonId:
                base.localAvatar.b_setParent(ToontownGlobals.SPRender)

    def takeLocalAvatarOutOfActivity(self):
        self.notify.debug("localToon has left the circle")
        base.camera.reparentTo(base.localAvatar)
        base.localAvatar.startUpdateSmartCamera()
        base.localAvatar.enableSmartCameraViews()
        base.localAvatar.setCameraPositionByIndex(base.localAvatar.cameraIndex)
        DistributedSmoothNode.activateSmoothing(1, 0)

    def _enableCollisions(self):
        DistributedPartyActivity._enableCollisions(self)
        self._enteredTree = False
        self.accept("enter" + self.catchTreeZoneEvent, self._toonMayHaveEnteredTree)
        self.accept("again" + self.catchTreeZoneEvent, self._toonMayHaveEnteredTree)
        self.accept("exit" + self.catchTreeZoneEvent, self._toonExitedTree)
        self.accept(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT, self._handleCannonLanded)

    def _disableCollisions(self):
        self.ignore(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT)
        self.ignore("enter" + self.catchTreeZoneEvent)
        self.ignore("again" + self.catchTreeZoneEvent)
        self.ignore("exit" + self.catchTreeZoneEvent)
        DistributedPartyActivity._disableCollisions(self)

    def _handleCannonLanded(self):
        x = base.localAvatar.getX()
        y = base.localAvatar.getY()
        if (
            x > self.x - self.StageHalfWidth
            and x < self.x + self.StageHalfWidth
            and y > self.y - self.StageHalfHeight
            and y < self.y + self.StageHalfHeight
        ):
            self._toonEnteredTree(None)
        return

    def _toonMayHaveEnteredTree(self, collEntry):
        if self._enteredTree:
            return
        if base.localAvatar.controlManager.currentControls.getIsAirborne():
            return
        self._toonEnteredTree(collEntry)

    def _toonEnteredTree(self, collEntry):
        self.notify.debug("_toonEnteredTree : avid = %s" % base.localAvatar.doId)
        self.notify.debug("_toonEnteredTree : currentState = %s" % self.activityFSM.state)
        if self.isLocalToonInActivity():
            return
        if self.activityFSM.state == "Active":
            base.cr.playGame.getPlace().fsm.request("activity")
            self.d_toonJoinRequest()
        elif self.activityFSM.state == "Idle":
            base.cr.playGame.getPlace().fsm.request("activity")
            self.d_toonJoinRequest()
        self._enteredTree = True

    def _toonExitedTree(self, collEntry):
        self.notify.debug("_toonExitedTree : avid = %s" % base.localAvatar.doId)
        self._enteredTree = False
        if (
            hasattr(base.cr.playGame.getPlace(), "fsm")
            and self.activityFSM.state == "Active"
            and self.isLocalToonInActivity()
        ):
            if base.localAvatar.doId in self.toonSDs:
                self.takeLocalAvatarOutOfActivity()
                self.toonSDs[base.localAvatar.doId].fsm.request("notPlaying")
            self.d_toonExitDemand()

    def setToonsPlaying(self, toonIds):
        self.notify.info("setToonsPlaying(%s)" % (toonIds,))
        DistributedPartyActivity.setToonsPlaying(self, toonIds)
        if self.isLocalToonInActivity() and base.localAvatar.doId not in toonIds:
            if base.localAvatar.doId in self.toonSDs:
                self.takeLocalAvatarOutOfActivity()
                self.toonSDs[base.localAvatar.doId].fsm.request("notPlaying")

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def getNumPlayers(self):
        return len(self.toonIds)

    def defineConstants(self, forceNumPlayers=None):
        DistributedPartyCatchActivity.notify.debug("defineConstants")
        self.ShowObjSpheres = 0
        self.ShowToonSpheres = 0
        self.useGravity = True
        self.trickShadows = True
        if forceNumPlayers is None:
            numPlayers = self.getNumPlayers()
        else:
            numPlayers = forceNumPlayers
        self.calcDifficultyConstants(numPlayers)
        DistributedPartyCatchActivity.notify.debug("ToonSpeed: %s" % self.ToonSpeed)
        DistributedPartyCatchActivity.notify.debug("total drops: %s" % self.totalDrops)
        DistributedPartyCatchActivity.notify.debug("numFruits: %s" % self.numFruits)
        DistributedPartyCatchActivity.notify.debug("numAnvils: %s" % self.numAnvils)
        self.ObjRadius = 1.0
        dropRegionTable = PartyRegionDropPlacer.getDropRegionTable(numPlayers)
        self.DropRows, self.DropColumns = len(dropRegionTable), len(dropRegionTable[0])
        for objType in PartyGlobals.DropObjectTypes:
            DistributedPartyCatchActivity.notify.debug("*** Object Type: %s" % objType.name)
            objType.onscreenDuration = objType.onscreenDurMult * self.BaselineOnscreenDropDuration
            DistributedPartyCatchActivity.notify.debug("onscreenDuration=%s" % objType.onscreenDuration)
            v_0 = 0.0
            t = objType.onscreenDuration
            x_0 = self.MinOffscreenHeight
            x = 0.0
            g = 2.0 * (x - x_0 - v_0 * t) / (t * t)
            DistributedPartyCatchActivity.notify.debug("gravity=%s" % g)
            objType.trajectory = Trajectory(0, Vec3(0, 0, x_0), Vec3(0, 0, v_0), gravMult=abs(g / Trajectory.gravity))
            objType.fallDuration = objType.onscreenDuration + self.OffscreenTime

        return

    def grid2world(self, column, row):
        x = column / float(self.DropColumns - 1)
        y = row / float(self.DropRows - 1)
        x = x * 2.0 - 1.0
        y = y * 2.0 - 1.0
        x *= self.StageHalfWidth
        y *= self.StageHalfHeight
        return (x, y)

    def showPosts(self):
        self.hidePosts()
        self.posts = [Toon.Toon(), Toon.Toon(), Toon.Toon(), Toon.Toon()]
        for i in xrange(len(self.posts)):
            tree = self.posts[i]
            tree.reparentTo(render)
            x = self.StageHalfWidth
            y = self.StageHalfHeight
            if i > 1:
                x = -x
            if i % 2:
                y = -y
            tree.setPos(x + self.x, y + self.y, 0)

    def hidePosts(self):
        if hasattr(self, "posts"):
            for tree in self.posts:
                tree.removeNode()

            del self.posts

    def showDropGrid(self):
        self.hideDropGrid()
        self.dropMarkers = []
        for row in xrange(self.DropRows):
            self.dropMarkers.append([])
            rowList = self.dropMarkers[row]
            for column in xrange(self.DropColumns):
                toon = Toon.Toon()
                toon.setDNA(base.localAvatar.getStyle())
                toon.reparentTo(self.root)
                toon.setScale(1.0 / 3)
                x, y = self.grid2world(column, row)
                toon.setPos(x, y, 0)
                rowList.append(toon)

    def hideDropGrid(self):
        if hasattr(self, "dropMarkers"):
            for row in self.dropMarkers:
                for marker in row:
                    marker.removeNode()

            del self.dropMarkers

    def handleToonDisabled(self, avId):
        DistributedPartyCatchActivity.notify.debug("handleToonDisabled")
        DistributedPartyCatchActivity.notify.debug("avatar " + str(avId) + " disabled")
        if avId in self.toonSDs:
            self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]

    def turnOffSmoothingOnGuests(self):
        pass

    def setState(self, newState, timestamp):
        self.notify.info("setState(%s, %s)" % (newState, timestamp))
        DistributedPartyCatchActivity.notify.debug("setState( newState=%s, ... )" % newState)
        DistributedPartyActivity.setState(self, newState, timestamp)
        self.activityFSM.request(newState)
        if newState == "Active":
            if base.localAvatar.doId != self.party.partyInfo.hostId:
                if globalClock.getFrameCount() > self._generateFrame:
                    if (
                        base.localAvatar.getX() > self.x - self.StageHalfWidth
                        and base.localAvatar.getX() < self.x + self.StageHalfWidth
                        and base.localAvatar.getY() > self.y - self.StageHalfHeight
                        and base.localAvatar.getY() < self.y + self.StageHalfHeight
                    ):
                        self._toonEnteredTree(None)
        return

    def putLocalAvatarInActivity(self):
        if base.cr.playGame.getPlace() and hasattr(base.cr.playGame.getPlace(), "fsm"):
            base.cr.playGame.getPlace().fsm.request("activity", [False])
        else:
            self.notify.info(
                "Avoided crash: toontown.parties.DistributedPartyCatchActivity:632, toontown.parties.DistributedPartyCatchActivity:1198, toontown.parties.activityFSMMixins:49, direct.fsm.FSM:423, AttributeError: 'NoneType' object has no attribute 'fsm'"
            )
        base.localAvatar.stopUpdateSmartCamera()
        base.camera.reparentTo(self.treesAndFence)
        base.camera.setPosHpr(0.0, -63.0, 30.0, 0.0, -20.0, 0.0)
        if not hasattr(self, "ltLegsCollNode"):
            self.createCatchCollisions()

    def createCatchCollisions(self):
        radius = 0.7
        handler = CollisionHandlerEvent()
        handler.setInPattern("ltCatch%in")
        self.ltLegsCollNode = CollisionNode("catchLegsCollNode")
        self.ltLegsCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltHeadCollNode = CollisionNode("catchHeadCollNode")
        self.ltHeadCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltLHandCollNode = CollisionNode("catchLHandCollNode")
        self.ltLHandCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltRHandCollNode = CollisionNode("catchRHandCollNode")
        self.ltRHandCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        legsCollNodepath = base.localAvatar.attachNewNode(self.ltLegsCollNode)
        legsCollNodepath.hide()
        head = base.localAvatar.getHeadParts().getPath(2)
        headCollNodepath = head.attachNewNode(self.ltHeadCollNode)
        headCollNodepath.hide()
        lHand = base.localAvatar.getLeftHands()[0]
        lHandCollNodepath = lHand.attachNewNode(self.ltLHandCollNode)
        lHandCollNodepath.hide()
        rHand = base.localAvatar.getRightHands()[0]
        rHandCollNodepath = rHand.attachNewNode(self.ltRHandCollNode)
        rHandCollNodepath.hide()
        base.localAvatar.cTrav.addCollider(legsCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(headCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(lHandCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(lHandCollNodepath, handler)
        if self.ShowToonSpheres:
            legsCollNodepath.show()
            headCollNodepath.show()
            lHandCollNodepath.show()
            rHandCollNodepath.show()
        self.ltLegsCollNode.addSolid(CollisionSphere(0, 0, radius, radius))
        self.ltHeadCollNode.addSolid(CollisionSphere(0, 0, 0, radius))
        self.ltLHandCollNode.addSolid(CollisionSphere(0, 0, 0, 2 * radius / 3.0))
        self.ltRHandCollNode.addSolid(CollisionSphere(0, 0, 0, 2 * radius / 3.0))
        self.toonCollNodes = [legsCollNodepath, headCollNodepath, lHandCollNodepath, rHandCollNodepath]

    def destroyCatchCollisions(self):
        if not hasattr(self, "ltLegsCollNode"):
            return
        for collNode in self.toonCollNodes:
            while collNode.node().getNumSolids():
                collNode.node().removeSolid(0)

            base.localAvatar.cTrav.removeCollider(collNode)

        del self.toonCollNodes
        del self.ltLegsCollNode
        del self.ltHeadCollNode
        del self.ltLHandCollNode
        del self.ltRHandCollNode

    def timerExpired(self):
        pass

    def __handleCatch(self, generation, objNum):
        DistributedPartyCatchActivity.notify.debug("catch: %s" % [generation, objNum])
        if base.localAvatar.doId not in self.toonIds:
            return
        self.showCatch(base.localAvatar.doId, generation, objNum)
        objName = self._id2gen[generation].droppedObjNames[objNum]
        objTypeId = PartyGlobals.Name2DOTypeId[objName]
        self.sendUpdate("claimCatch", [generation, objNum, objTypeId])
        self.finishDropInterval(generation, objNum)

    def showCatch(self, avId, generation, objNum):
        if avId not in self.toonSDs:
            return
        isLocal = avId == base.localAvatar.doId
        if generation not in self._id2gen:
            return
        if not self._id2gen[generation].hasBeenScheduled:
            return
        objName = self._id2gen[generation].droppedObjNames[objNum]
        objType = PartyGlobals.Name2DropObjectType[objName]
        if objType.good:
            if objNum not in self._id2gen[generation].droppedObjCaught:
                if isLocal:
                    base.playSfx(self.sndGoodCatch)
                fruit = self.getObjModel(objName)
                toon = self.getAvatar(avId)
                rHand = toon.getRightHands()[1]
                self.toonSDs[avId].eatFruit(fruit, rHand)
        else:
            self.toonSDs[avId].fsm.request("fallForward")
        self._id2gen[generation].droppedObjCaught[objNum] = 1

    def setObjectCaught(self, avId, generation, objNum):
        self.notify.info("setObjectCaught(%s, %s, %s)" % (avId, generation, objNum))
        if self.activityFSM.state != "Active":
            DistributedPartyCatchActivity.notify.warning("ignoring msg: object %s caught by %s" % (objNum, avId))
            return
        isLocal = avId == base.localAvatar.doId
        if not isLocal:
            DistributedPartyCatchActivity.notify.debug("AI: avatar %s caught %s" % (avId, objNum))
            self.finishDropInterval(generation, objNum)
            self.showCatch(avId, generation, objNum)
        self._scheduleGenerations()
        gen = self._id2gen[generation]
        if gen.hasBeenScheduled:
            objName = gen.droppedObjNames[objNum]
            if PartyGlobals.Name2DropObjectType[objName].good:
                if hasattr(self, "fruitsCaught"):
                    self.fruitsCaught += 1

    def finishDropInterval(self, generation, objNum):
        if hasattr(self, "dropIntervals"):
            if (generation, objNum) in self.dropIntervals:
                self.dropIntervals[generation, objNum].finish()

    def finishAllDropIntervals(self):
        if hasattr(self, "dropIntervals"):
            for dropInterval in self.dropIntervals.values():
                dropInterval.finish()

    def setGenerations(self, generations):
        self.notify.info("setGenerations(%s)" % (generations,))
        gen2t = {}
        gen2nt = {}
        gen2np = {}
        for id, timestamp32, numPlayers in generations:
            gen2t[id] = globalClockDelta.networkToLocalTime(timestamp32, bits=32) - self._startTimestamp
            gen2nt[id] = timestamp32
            gen2np[id] = numPlayers

        ids = self._id2gen.keys()
        for id in ids:
            if id not in gen2t:
                self._removeGeneration(id)

        for id in gen2t:
            if id not in self._id2gen:
                self._addGeneration(id, gen2t[id], gen2nt[id], gen2np[id])

    def scheduleDrops(self, genId=None):
        if genId is None:
            genId = self.getCurGeneration()
        gen = self._id2gen[genId]
        if gen.hasBeenScheduled:
            return
        fruitIndex = int((gen.startTime + 0.5 * self.DropPeriod) / PartyGlobals.CatchActivityDuration)
        fruitNames = ["apple", "orange", "pear", "coconut", "watermelon", "pineapple"]
        fruitName = fruitNames[fruitIndex % len(fruitNames)]
        rng = RandomNumGen(genId + self._generationSeedBase)
        gen.droppedObjNames = [fruitName] * self.numFruits + ["anvil"] * self.numAnvils
        rng.shuffle(gen.droppedObjNames)
        dropPlacer = PartyRegionDropPlacer(self, gen.numPlayers, genId, gen.droppedObjNames, startTime=gen.startTime)
        gen.numItemsDropped = 0
        tIndex = gen.startTime % PartyGlobals.CatchActivityDuration
        tPercent = float(tIndex) / PartyGlobals.CatchActivityDuration
        gen.numItemsDropped += dropPlacer.skipPercent(tPercent)
        while not dropPlacer.doneDropping(continuous=True):
            nextDrop = dropPlacer.getNextDrop()
            gen.dropSchedule.append(nextDrop)

        gen.hasBeenScheduled = True
        return

    def startDropTask(self):
        taskMgr.add(self.dropTask, self.DropTaskName)

    def stopDropTask(self):
        taskMgr.remove(self.DropTaskName)

    def _scheduleGenerations(self):
        curT = self.getCurrentCatchActivityTime()
        genIndex = self._orderedGenerationIndex
        newGenIndex = genIndex
        while genIndex is None or genIndex < len(self._orderedGenerations) - 1:
            if genIndex is None:
                nextGenIndex = 0
            else:
                nextGenIndex = genIndex + 1
            nextGenId = self._orderedGenerations[nextGenIndex]
            nextGen = self._id2gen[nextGenId]
            startT = nextGen.startTime
            if curT >= startT:
                newGenIndex = nextGenIndex
            if not nextGen.hasBeenScheduled:
                self.defineConstants(forceNumPlayers=nextGen.numPlayers)
                self.scheduleDrops(genId=self._orderedGenerations[nextGenIndex])
            genIndex = nextGenIndex

        self._orderedGenerationIndex = newGenIndex
        return

    def dropTask(self, task):
        self._scheduleGenerations()
        curT = self.getCurrentCatchActivityTime()
        if self._orderedGenerationIndex is not None:
            i = self._orderedGenerationIndex
            genIndex = self._orderedGenerations[i]
            gen = self._id2gen[genIndex]
            while len(gen.dropSchedule) > 0 and gen.dropSchedule[0][0] < curT:
                drop = gen.dropSchedule[0]
                gen.dropSchedule = gen.dropSchedule[1:]
                dropTime, objName, dropCoords = drop
                objNum = gen.numItemsDropped
                x, y = self.grid2world(*dropCoords)
                dropIval = self.getDropIval(x, y, objName, genIndex, objNum)

                def cleanup(generation, objNum, self=self):
                    del self.dropIntervals[generation, objNum]

                dropIval.append(Func(Functor(cleanup, genIndex, objNum)))
                self.dropIntervals[genIndex, objNum] = dropIval
                gen.numItemsDropped += 1
                dropIval.start(curT - dropTime)
                self._lastDropTime = dropTime

        return Task.cont

    def getDropIval(self, x, y, dropObjName, generation, num):
        objType = PartyGlobals.Name2DropObjectType[dropObjName]
        id = (generation, num)
        dropNode = hidden.attachNewNode("catchDropNode%s" % (id,))
        dropNode.setPos(x, y, 0)
        shadow = self.dropShadow.copyTo(dropNode)
        shadow.setZ(PartyGlobals.CatchDropShadowHeight)
        shadow.setColor(1, 1, 1, 1)
        object = self.getObjModel(dropObjName)
        object.reparentTo(hidden)
        if dropObjName in ["watermelon", "anvil"]:
            objH = object.getH()
            absDelta = {"watermelon": 12, "anvil": 15}[dropObjName]
            delta = (self.randomNumGen.random() * 2.0 - 1.0) * absDelta
            newH = objH + delta
        else:
            newH = self.randomNumGen.random() * 360.0
        object.setH(newH)
        sphereName = "FallObj%s" % (id,)
        radius = self.ObjRadius
        if objType.good:
            radius *= lerp(1.0, 1.3, 0.5)
        collSphere = CollisionSphere(0, 0, 0, radius)
        collSphere.setTangible(0)
        collNode = CollisionNode(sphereName)
        collNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        collNode.addSolid(collSphere)
        collNodePath = object.attachNewNode(collNode)
        collNodePath.hide()
        if self.ShowObjSpheres:
            collNodePath.show()
        catchEventName = "ltCatch" + sphereName

        def eatCollEntry(forward, collEntry):
            forward()

        self.accept(catchEventName, Functor(eatCollEntry, Functor(self.__handleCatch, id[0], id[1])))

        def cleanup(self=self, dropNode=dropNode, id=id, event=catchEventName):
            self.ignore(event)
            dropNode.removeNode()

        duration = objType.fallDuration
        onscreenDuration = objType.onscreenDuration
        targetShadowScale = 0.3
        if self.trickShadows:
            intermedScale = targetShadowScale * (self.OffscreenTime / self.BaselineDropDuration)
            shadowScaleIval = Sequence(LerpScaleInterval(shadow, self.OffscreenTime, intermedScale, startScale=0))
            shadowScaleIval.append(
                LerpScaleInterval(shadow, duration - self.OffscreenTime, targetShadowScale, startScale=intermedScale)
            )
        else:
            shadowScaleIval = LerpScaleInterval(shadow, duration, targetShadowScale, startScale=0)
        targetShadowAlpha = 0.4
        shadowAlphaIval = LerpColorScaleInterval(
            shadow, self.OffscreenTime, Point4(1, 1, 1, targetShadowAlpha), startColorScale=Point4(1, 1, 1, 0)
        )
        shadowIval = Parallel(shadowScaleIval, shadowAlphaIval)
        if self.useGravity:

            def setObjPos(t, objType=objType, object=object):
                z = objType.trajectory.calcZ(t)
                object.setZ(z)

            setObjPos(0)
            dropIval = LerpFunctionInterval(setObjPos, fromData=0, toData=onscreenDuration, duration=onscreenDuration)
        else:
            startPos = Point3(0, 0, self.MinOffscreenHeight)
            object.setPos(startPos)
            dropIval = LerpPosInterval(object, onscreenDuration, Point3(0, 0, 0), startPos=startPos, blendType="easeIn")
        ival = Sequence(
            Func(Functor(dropNode.reparentTo, self.root)),
            Parallel(
                Sequence(WaitInterval(self.OffscreenTime), Func(Functor(object.reparentTo, dropNode)), dropIval),
                shadowIval,
            ),
            Func(cleanup),
            name="drop%s" % (id,),
        )
        if objType == PartyGlobals.Name2DropObjectType["anvil"]:
            ival.append(Func(self.playAnvil))
        return ival

    def playAnvil(self):
        if base.localAvatar.doId in self.toonIds:
            base.playSfx(self.sndAnvilLand)

    def initOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug("startOrthoWalk")

        def doCollisions(oldPos, newPos, self=self):
            x = bound(newPos[0], self.StageHalfWidth, -self.StageHalfWidth)
            y = bound(newPos[1], self.StageHalfHeight, -self.StageHalfHeight)
            newPos.setX(x)
            newPos.setY(y)
            return newPos

        orthoDrive = OrthoDrive(self.ToonSpeed, instantTurn=True)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)

    def destroyOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug("destroyOrthoWalk")
        if hasattr(self, "orthoWalk"):
            self.orthoWalk.stop()
            self.orthoWalk.destroy()
            del self.orthoWalk

    def startIdle(self):
        DistributedPartyCatchActivity.notify.debug("startIdle")

    def finishIdle(self):
        DistributedPartyCatchActivity.notify.debug("finishIdle")

    def startActive(self):
        DistributedPartyCatchActivity.notify.debug("startActive")
        for avId in self.toonIds:
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.enter()
                toonSD.fsm.request("normal")

        self.fruitsCaught = 0
        self.dropIntervals = {}
        self.startDropTask()
        if base.localAvatar.doId in self.toonIds:
            self.putLocalAvatarInActivity()

    def finishActive(self):
        DistributedPartyCatchActivity.notify.debug("finishActive")
        self.stopDropTask()
        if hasattr(self, "finishIval"):
            self.finishIval.pause()
            del self.finishIval
        if base.localAvatar.doId in self.toonIds:
            self.takeLocalAvatarOutOfActivity()
        for ival in self.dropIntervals.values():
            ival.finish()

        del self.dropIntervals

    def startConclusion(self):
        DistributedPartyCatchActivity.notify.debug("startConclusion")
        for avId in self.toonIds:
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.fsm.request("notPlaying")

        self.destroyCatchCollisions()
        if base.localAvatar.doId not in self.toonIds:
            return
        else:
            self.localToonExiting()
        if self.fruitsCaught >= self.numFruits:
            finishText = TTLocalizer.PartyCatchActivityFinishPerfect
        else:
            finishText = TTLocalizer.PartyCatchActivityFinish
        perfectTextSubnode = hidden.attachNewNode(self.__genText(finishText))
        perfectText = hidden.attachNewNode("perfectText")
        perfectTextSubnode.reparentTo(perfectText)
        frame = self.__textGen.getCardActual()
        offsetY = -abs(frame[2] + frame[3]) / 2.0
        perfectTextSubnode.setPos(0, 0, offsetY)
        perfectText.setColor(1, 0.1, 0.1, 1)

        def fadeFunc(t, text=perfectText):
            text.setColorScale(1, 1, 1, t)

        def destroyText(text=perfectText):
            text.removeNode()

        textTrack = Sequence(
            Func(perfectText.reparentTo, aspect2d),
            Parallel(
                LerpScaleInterval(perfectText, duration=0.5, scale=0.3, startScale=0.0),
                LerpFunctionInterval(fadeFunc, fromData=0.0, toData=1.0, duration=0.5),
            ),
            Wait(2.0),
            Parallel(
                LerpScaleInterval(perfectText, duration=0.5, scale=1.0),
                LerpFunctionInterval(fadeFunc, fromData=1.0, toData=0.0, duration=0.5, blendType="easeIn"),
            ),
            Func(destroyText),
            WaitInterval(0.5),
        )
        soundTrack = SoundInterval(self.sndPerfect)
        self.finishIval = Parallel(textTrack, soundTrack)
        self.finishIval.start()

    def finishConclusion(self):
        DistributedPartyCatchActivity.notify.debug("finishConclusion")
        if base.localAvatar.doId in self.toonIds:
            self.takeLocalAvatarOutOfActivity()
            base.cr.playGame.getPlace().fsm.request("walk")

    def showJellybeanReward(self, earnedAmount, jarAmount, message):
        if earnedAmount > 0:
            DistributedPartyActivity.showJellybeanReward(self, earnedAmount, jarAmount, message)
        else:
            base.cr.playGame.getPlace().fsm.request("walk")