Ejemplo n.º 1
0
class DistributedTwoDGame(DistributedMinigame):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTwoDGame')
    UpdateLocalToonTask = 'ToonBlitzUpdateLocalToonTask'
    EndGameTaskName = 'endTwoDGame'

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedTwoDGame', [State.State('off', self.enterOff, self.exitOff, ['play']),
         State.State('play', self.enterPlay, self.exitPlay, ['cleanup', 'pause', 'showScores']),
         State.State('pause', self.enterPause, self.exitPause, ['cleanup', 'play', 'showScores']),
         State.State('showScores', self.enterShowScores, self.exitShowScores, ['cleanup']),
         State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.reportedDone = False
        self.showCollSpheres = False

    def getTitle(self):
        return TTLocalizer.TwoDGameTitle

    def getInstructions(self):
        return TTLocalizer.TwoDGameInstructions

    def getMaxDuration(self):
        return 200

    def __defineConstants(self):
        self.TOON_SPEED = 12.0
        self.MAX_FRAME_MOVE = 1
        self.isHeadInFloor = False
        self.timeToRunToElevator = 1.5

    def setSectionsSelected(self, sectionsSelected):
        self.sectionsSelected = sectionsSelected

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.__defineConstants()
        self.assetMgr = ToonBlitzAssetMgr.ToonBlitzAssetMgr(self)
        self.cameraMgr = TwoDCamera.TwoDCamera(camera)
        self.sectionMgr = TwoDSectionMgr.TwoDSectionMgr(self, self.sectionsSelected)
        self.gameStartX = -40.0
        endSection = self.sectionMgr.sections[-1]
        self.gameEndX = endSection.spawnPointMgr.gameEndX
        self.gameLength = self.gameEndX - self.gameStartX
        self.toonSDs = {}
        avId = self.localAvId
        toonSD = TwoDGameToonSD.TwoDGameToonSD(avId, self)
        self.toonSDs[avId] = toonSD
        self.toonSDs[avId].createHeadFrame(0)

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        taskMgr.remove(self.UpdateLocalToonTask)
        for avId in self.toonSDs.keys():
            toonSD = self.toonSDs[avId]
            toonSD.destroy()

        del self.toonSDs
        self.cameraMgr.destroy()
        del self.cameraMgr
        self.sectionMgr.destroy()
        del self.sectionMgr
        for panel in self.scorePanels:
            panel.cleanup()

        del self.scorePanels
        self.assetMgr.destroy()
        del self.assetMgr
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.scorePanels = []
        self.assetMgr.onstage()
        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        base.localAvatar.collisionsOn()
        base.localAvatar.setTransparency(1)
        self.setupHeadCollision()
        self.cameraMgr.onstage()
        toonSD = self.toonSDs[self.localAvId]
        toonSD.enter()
        toonSD.fsm.request('normal')
        self.twoDDrive = TwoDDrive(self, self.TOON_SPEED, maxFrameMove=self.MAX_FRAME_MOVE)

    def offstage(self):
        self.notify.debug('offstage')
        self.assetMgr.offstage()
        for avId in self.toonSDs.keys():
            self.toonSDs[avId].exit()

        base.localAvatar.setTransparency(0)
        self.ignore('enterheadCollSphere-into-floor1')
        base.localAvatar.controlManager.currentControls.cTrav.removeCollider(self.headCollNP)
        self.headCollNP.removeNode()
        del self.headCollNP
        base.localAvatar.laffMeter.start()
        DistributedMinigame.offstage(self)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        drawNum = 0
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                drawNum += 1
                toon.reparentTo(render)
                toon.setAnimState('Happy', 1.0)
                toon.hideName()
                toon.startSmooth()
                toon.startLookAround()
                distCNP = toon.find('**/distAvatarCollNode*')
                distCNP.node().setIntoCollideMask(BitMask32.allOff())
                toonSD = TwoDGameToonSD.TwoDGameToonSD(avId, self)
                self.toonSDs[avId] = toonSD
                toonSD.enter()
                toonSD.fsm.request('normal')
                self.toonSDs[avId].createHeadFrame(drawNum)

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        self.twoDWalk = TwoDWalk(self.twoDDrive, broadcast=not self.isSinglePlayer())
        self.scores = [0] * self.numPlayers
        spacing = 0.4
        for i in xrange(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = MinigameAvatarScorePanel.MinigameAvatarScorePanel(avId, avName)
            scorePanel.setScale(0.9)
            scorePanel.setPos(-0.583 - spacing * (self.numPlayers - 1 - i), 0.0, -0.15)
            scorePanel.reparentTo(base.a2dTopRight)
            scorePanel.makeTransparent(0.75)
            self.scorePanels.append(scorePanel)

        self.gameFSM.request('play', [timestamp])

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

    def exitOff(self):
        pass

    def enterPlay(self, timestamp):
        self.notify.debug('enterPlay')
        elapsedTime = globalClockDelta.localElapsedTime(timestamp)
        self.sectionMgr.enterPlay(elapsedTime)
        handlers = [None,
         None,
         None,
         None,
         self.shootKeyHandler]
        self.twoDDrive.arrowKeys.setPressHandlers(handlers)
        self.twoDWalk.start()
        self.accept('jumpStart', self.startJump)
        self.accept('enemyHit', self.localToonHitByEnemy)
        self.accept('twoDTreasureGrabbed', self.__treasureGrabbed)
        self.accept('enemyShot', self.__enemyShot)
        taskMgr.remove(self.UpdateLocalToonTask)
        taskMgr.add(self.__updateLocalToonTask, self.UpdateLocalToonTask, priority=1)
        base.localAvatar.laffMeter.stop()
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(ToonBlitzGlobals.GameDuration[self.getSafezoneId()])
        self.timer.countdown(ToonBlitzGlobals.GameDuration[self.getSafezoneId()], self.timerExpired)
        return

    def exitPlay(self):
        handlers = [None,
         None,
         None,
         None,
         None]
        self.twoDDrive.arrowKeys.setPressHandlers(handlers)
        if self.toonSDs[self.localAvId].fsm.getCurrentState().getName() != 'victory':
            base.localAvatar.b_setAnimState('Happy', 1.0)
        self.ignore('jumpStart')
        self.ignore('enemyHit')
        self.ignore('twoDTreasureGrabbed')
        return

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

    def exitPause(self):
        pass

    def enterShowScores(self):
        self.notify.debug('enterShowScores')
        lerpTrack = Parallel()
        lerpDur = 0.5
        tY = 0.6
        bY = -.6
        lX = -.7
        cX = 0
        rX = 0.7
        scorePanelLocs = (((cX, bY),),
         ((lX, bY), (rX, bY)),
         ((cX, bY), (lX, bY), (rX, bY)),
         ((lX, tY),
          (rX, tY),
          (lX, bY),
          (rX, bY)))
        scorePanelLocs = scorePanelLocs[self.numPlayers - 1]
        for i in xrange(self.numPlayers):
            panel = self.scorePanels[i]
            pos = scorePanelLocs[i]
            panel.wrtReparentTo(aspect2d)
            lerpTrack.append(Parallel(LerpPosInterval(panel, lerpDur, Point3(pos[0], 0, pos[1]), blendType='easeInOut'), LerpScaleInterval(panel, lerpDur, Vec3(panel.getScale()) * 1.5, blendType='easeInOut')))

        self.showScoreTrack = Parallel(lerpTrack, self.getElevatorCloseTrack(), Sequence(Wait(ToonBlitzGlobals.ShowScoresDuration), Func(self.gameOver)))
        self.showScoreTrack.start()

        #For the Alpha Blueprint ARG
        if base.config.GetBool('want-blueprint4-ARG', False):
            MinigameGlobals.generateDebugARGPhrase()

    def exitShowScores(self):
        self.showScoreTrack.pause()
        del self.showScoreTrack

    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        self.timer.stop()
        self.timer.destroy()
        del self.timer
        taskMgr.remove(self.EndGameTaskName)
        self.twoDWalk.stop()
        self.twoDWalk.destroy()
        del self.twoDWalk
        self.twoDDrive = None
        del self.twoDDrive
        return

    def exitCleanup(self):
        pass

    def acceptInputs(self):
        if hasattr(self, 'twoDDrive'):
            handlers = [None,
             None,
             None,
             None,
             self.shootKeyHandler]
            self.twoDDrive.arrowKeys.setPressHandlers(handlers)
            self.twoDDrive.start()
        return

    def ignoreInputs(self):
        if hasattr(self, 'twoDDrive'):
            handlers = [None,
             None,
             None,
             None,
             None]
            self.twoDDrive.arrowKeys.setPressHandlers(handlers)
            self.twoDDrive.lastAction = None
            self.twoDDrive.stop()
        return

    def __updateLocalToonTask(self, task):
        dt = globalClock.getDt()
        self.cameraMgr.update()
        if self.gameFSM.getCurrentState().getName() == 'play':
            if not self.toonSDs[self.localAvId].fsm.getCurrentState().getName() == 'victory':
                if not base.localAvatar.getY() == 0:
                    base.localAvatar.setY(0)
        if base.localAvatar.getZ() < -2.0:
            self.localToonFellDown()
        for avId in self.toonSDs.keys():
            self.toonSDs[avId].update()

        return task.cont

    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 setupHeadCollision(self):
        collSphere = CollisionSphere(0, 0, 0, 1)
        collSphere.setTangible(1)
        collNode = CollisionNode('headCollSphere')
        collNode.setFromCollideMask(ToontownGlobals.WallBitmask)
        collNode.setIntoCollideMask(BitMask32.allOff())
        collNode.addSolid(collSphere)
        head = base.localAvatar.getPart('head', '1000')
        self.headCollNP = head.attachNewNode(collNode)
        self.headCollNP.setPos(0, 0, 0.0)
        animal = base.localAvatar.style.getAnimal()
        if animal == 'dog' or animal == 'bear' or animal == 'horse':
            torso = base.localAvatar.style.torso
            legs = base.localAvatar.style.legs
            if (torso == 'ls' or torso == 'ld') and legs == 'l':
                self.headCollNP.setZ(-1.3)
            else:
                self.headCollNP.setZ(-0.7)
        elif animal == 'mouse' or animal == 'duck':
            self.headCollNP.setZ(0.5)
        elif animal == 'cat':
            self.headCollNP.setZ(-0.3)
        elif animal == 'rabbit':
            self.headCollNP.setZ(-0.5)
        elif animal == 'monkey':
            self.headCollNP.setZ(0.3)
        elif animal == 'pig':
            self.headCollNP.setZ(-0.7)
        self.headCollNP.hide()
        if self.showCollSpheres:
            self.headCollNP.show()
        headCollisionEvent = CollisionHandlerEvent()
        headCollisionEvent.addInPattern('enter%fn-into-%in')
        headCollisionEvent.addOutPattern('%fn-exit-%in')
        cTrav = base.localAvatar.controlManager.currentControls.cTrav
        cTrav.addCollider(self.headCollNP, headCollisionEvent)
        self.accept('enterheadCollSphere-into-floor1', self.__handleHeadCollisionIntoFloor)
        self.accept('headCollSphere-exit-floor1', self.__handleHeadCollisionExitFloor)

    def __handleHeadCollisionIntoFloor(self, cevent):
        self.isHeadInFloor = True
        if base.localAvatar.controlManager.currentControls.lifter.getVelocity() > 0:
            base.localAvatar.controlManager.currentControls.lifter.setVelocity(0.0)
            self.assetMgr.playHeadCollideSound()

    def __handleHeadCollisionExitFloor(self, cevent):
        self.isHeadInFloor = False

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        i = self.avIdList.index(avId)
        pos = Point3(ToonBlitzGlobals.ToonStartingPosition[0] + i, ToonBlitzGlobals.ToonStartingPosition[1], ToonBlitzGlobals.ToonStartingPosition[2])
        toon.setPos(pos)
        toon.setHpr(-90, 0, 0)

    def startJump(self):
        self.assetMgr.playJumpSound()

    def checkValidity(self, avId):
        if not self.hasLocalToon:
            return False
        if self.gameFSM.getCurrentState() == None or self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring msg: av %s performing some action.' % avId)
            return False
        toon = self.getAvatar(avId)
        if toon == None:
            return False
        return True

    def shootKeyHandler(self):
        self.toonSDs[self.localAvId].shootGun()
        timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime())
        self.sendUpdate('showShootGun', [self.localAvId, timestamp])

    def showShootGun(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s is shooting water gun' % avId)
            if avId != self.localAvId:
                self.toonSDs[avId].shootGun()

    def localToonFellDown(self):
        if self.toonSDs[self.localAvId].fsm.getCurrentState().getName() != 'fallDown':
            self.toonSDs[self.localAvId].fsm.request('fallDown')
            timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime())
            self.updateScore(self.localAvId, ToonBlitzGlobals.ScoreLossPerFallDown[self.getSafezoneId()])
            self.sendUpdate('toonFellDown', [self.localAvId, timestamp])

    def toonFellDown(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s fell down.' % avId)
            if avId != self.localAvId:
                self.updateScore(avId, ToonBlitzGlobals.ScoreLossPerFallDown[self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('fallDown')

    def localToonHitByEnemy(self):
        currToonState = self.toonSDs[self.localAvId].fsm.getCurrentState().getName()
        if not (currToonState == 'fallBack' or currToonState == 'squish'):
            self.toonSDs[self.localAvId].fsm.request('fallBack')
            timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime())
            self.updateScore(self.localAvId, ToonBlitzGlobals.ScoreLossPerEnemyCollision[self.getSafezoneId()])
            self.sendUpdate('toonHitByEnemy', [self.localAvId, timestamp])

    def toonHitByEnemy(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s hit by a suit' % avId)
            if avId != self.localAvId:
                self.updateScore(avId, ToonBlitzGlobals.ScoreLossPerEnemyCollision[self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('fallBack')

    def localToonSquished(self):
        currToonState = self.toonSDs[self.localAvId].fsm.getCurrentState().getName()
        if not (currToonState == 'fallBack' or currToonState == 'squish'):
            self.toonSDs[self.localAvId].fsm.request('squish')
            timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime())
            self.updateScore(self.localAvId, ToonBlitzGlobals.ScoreLossPerStomperSquish[self.getSafezoneId()])
            self.sendUpdate('toonSquished', [self.localAvId, timestamp])

    def toonSquished(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s is squished.' % avId)
            if avId != self.localAvId:
                self.updateScore(avId, ToonBlitzGlobals.ScoreLossPerStomperSquish[self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('squish')

    def localToonVictory(self):
        if not ToonBlitzGlobals.EndlessGame:
            self.ignoreInputs()
        if not self.toonSDs[self.localAvId].fsm.getCurrentState().getName() == 'victory':
            self.toonSDs[self.localAvId].fsm.request('victory')
            timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime())
            self.sendUpdate('toonVictory', [self.localAvId, timestamp])

    def toonVictory(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s achieves victory' % avId)
            if avId != self.localAvId:
                self.toonSDs[avId].fsm.request('victory')

    def addVictoryScore(self, avId, score):
        if not self.hasLocalToon:
            return
        self.updateScore(avId, score)
        if avId == self.localAvId:
            self.assetMgr.threeSparkles.play()

    def __treasureGrabbed(self, sectionIndex, treasureIndex):
        self.notify.debug('treasure %s-%s grabbed' % (sectionIndex, treasureIndex))
        section = self.sectionMgr.sections[sectionIndex]
        section.treasureMgr.treasures[treasureIndex].hideTreasure()
        self.assetMgr.treasureGrabSound.play()
        self.sendUpdate('claimTreasure', [sectionIndex, treasureIndex])

    def setTreasureGrabbed(self, avId, sectionIndex, treasureIndex):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() == 'play':
            self.notify.debug('treasure %s-%s grabbed by %s' % (sectionIndex, treasureIndex, avId))
            numSections = len(self.sectionMgr.sections)
            if sectionIndex < numSections:
                section = self.sectionMgr.sections[sectionIndex]
                numTreasures = len(section.treasureMgr.treasures)
                if treasureIndex < numTreasures:
                    treasure = section.treasureMgr.treasures[treasureIndex]
                    if avId != self.localAvId:
                        treasure.hideTreasure()
                    self.updateScore(avId, ToonBlitzGlobals.ScoreGainPerTreasure * treasure.value)
                else:
                    self.notify.error('WHOA!! treasureIndex %s is out of range; numTreasures = %s' % (treasureIndex, numTreasures))
                    base.localAvatar.sendLogMessage('treasureIndex %s is out of range; numTreasures = %s' % (treasureIndex, numTreasures))
            else:
                self.notify.error('WHOA!! sectionIndex %s is out of range; numSections = %s' % (sectionIndex, numSections))
                base.localAvatar.sendLogMessage('sectionIndex %s is out of range; numSections = %s' % (sectionIndex, numSections))

    def __enemyShot(self, sectionIndex, enemyIndex):
        self.sectionMgr.sections[sectionIndex].enemyMgr.enemies[enemyIndex].doShotTrack()
        self.sendUpdate('claimEnemyShot', [sectionIndex, enemyIndex])

    def setEnemyShot(self, avId, sectionIndex, enemyIndex, enemyHealth):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() == 'play':
            self.notify.debug('enemy %s is shot by %s. Health left %s' % (enemyIndex, avId, enemyHealth))
            if enemyHealth > 0:
                if not avId == self.localAvId:
                    self.sectionMgr.sections[sectionIndex].enemyMgr.enemies[enemyIndex].doShotTrack()
            else:
                enemy = self.sectionMgr.sections[sectionIndex].enemyMgr.enemies[enemyIndex]
                treasureSpawnPoint = Point3(enemy.suit.getX(), enemy.suit.getY(), enemy.suit.getZ() + enemy.suit.height / 2.0)
                self.spawnTreasure(sectionIndex, enemyIndex, treasureSpawnPoint)
                enemy.doDeathTrack()

    def updateScore(self, avId, deltaScore):
        i = self.avIdList.index(avId)
        self.scores[i] += deltaScore
        self.scorePanels[i].setScore(self.scores[i])
        self.toonSDs[avId].showScoreText(deltaScore)

    def spawnTreasure(self, sectionIndex, enemyIndex, pos):
        if self.gameFSM.getCurrentState().getName() == 'play':
            section = self.sectionMgr.sections[sectionIndex]
            treasure = section.treasureMgr.enemyTreasures[enemyIndex]
            treasure.setTreasurePos(pos)
            treasure.popupEnemyTreasure()

    def timerExpired(self):
        self.notify.debug('timer expired')
        if not self.reportedDone:
            if not ToonBlitzGlobals.EndlessGame:
                self.ignoreInputs()
            self.reportedDone = True
            self.sendUpdate('reportDone')

    def setEveryoneDone(self):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring setEveryoneDone msg')
            return
        self.notify.debug('setEveryoneDone')

        def endGame(task, self = self):
            if not ToonBlitzGlobals.EndlessGame:
                self.gameFSM.request('showScores')
            return Task.done

        self.timer.hide()
        taskMgr.doMethodLater(1, endGame, self.EndGameTaskName)

    def getElevatorCloseTrack(self):
        leftDoor = self.sectionMgr.exitElevator.find('**/doorL')
        rightDoor = self.sectionMgr.exitElevator.find('**/doorR')
        leftDoorClose = LerpPosInterval(leftDoor, 2, Point3(3.02, 0, 0))
        rightDoorClose = LerpPosInterval(rightDoor, 2, Point3(-3.02, 0, 0))
        return Sequence(Wait(self.timeToRunToElevator), Parallel(leftDoorClose, rightDoorClose))

    def debugStartPause(self):
        self.sectionMgr.enterPause()

    def debugEndPause(self):
        self.sectionMgr.exitPause()
class DistributedTwoDGame(DistributedMinigame):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTwoDGame')
    UpdateLocalToonTask = 'ToonBlitzUpdateLocalToonTask'
    EndGameTaskName = 'endTwoDGame'

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedTwoDGame', [
            State.State('off', self.enterOff, self.exitOff, ['play']),
            State.State('play', self.enterPlay, self.exitPlay,
                        ['cleanup', 'pause', 'showScores']),
            State.State('pause', self.enterPause, self.exitPause,
                        ['cleanup', 'play', 'showScores']),
            State.State('showScores', self.enterShowScores,
                        self.exitShowScores, ['cleanup']),
            State.State('cleanup', self.enterCleanup, self.exitCleanup, [])
        ], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.reportedDone = False
        self.showCollSpheres = False

    def getTitle(self):
        return TTLocalizer.TwoDGameTitle

    def getInstructions(self):
        return TTLocalizer.TwoDGameInstructions

    def getMaxDuration(self):
        return 200

    def __defineConstants(self):
        self.TOON_SPEED = 12.0
        self.MAX_FRAME_MOVE = 1
        self.isHeadInFloor = False
        self.timeToRunToElevator = 1.5

    def setSectionsSelected(self, sectionsSelected):
        self.sectionsSelected = sectionsSelected

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.__defineConstants()
        self.assetMgr = ToonBlitzAssetMgr.ToonBlitzAssetMgr(self)
        self.cameraMgr = TwoDCamera.TwoDCamera(camera)
        self.sectionMgr = TwoDSectionMgr.TwoDSectionMgr(
            self, self.sectionsSelected)
        self.gameStartX = -40.0
        endSection = self.sectionMgr.sections[-1]
        self.gameEndX = endSection.spawnPointMgr.gameEndX
        self.gameLength = self.gameEndX - self.gameStartX
        self.toonSDs = {}
        avId = self.localAvId
        toonSD = TwoDGameToonSD.TwoDGameToonSD(avId, self)
        self.toonSDs[avId] = toonSD
        self.toonSDs[avId].createHeadFrame(0)

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        taskMgr.remove(self.UpdateLocalToonTask)
        for avId in self.toonSDs.keys():
            toonSD = self.toonSDs[avId]
            toonSD.destroy()

        del self.toonSDs
        self.cameraMgr.destroy()
        del self.cameraMgr
        self.sectionMgr.destroy()
        del self.sectionMgr
        for panel in self.scorePanels:
            panel.cleanup()

        del self.scorePanels
        self.assetMgr.destroy()
        del self.assetMgr
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.scorePanels = []
        self.assetMgr.onstage()
        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        base.localAvatar.collisionsOn()
        base.localAvatar.setTransparency(1)
        self.setupHeadCollision()
        self.cameraMgr.onstage()
        toonSD = self.toonSDs[self.localAvId]
        toonSD.enter()
        toonSD.fsm.request('normal')
        self.twoDDrive = TwoDDrive(self,
                                   self.TOON_SPEED,
                                   maxFrameMove=self.MAX_FRAME_MOVE)

    def offstage(self):
        self.notify.debug('offstage')
        self.assetMgr.offstage()
        for avId in self.toonSDs.keys():
            self.toonSDs[avId].exit()

        base.localAvatar.setTransparency(0)
        self.ignore('enterheadCollSphere-into-floor1')
        base.localAvatar.controlManager.currentControls.cTrav.removeCollider(
            self.headCollNP)
        self.headCollNP.removeNode()
        del self.headCollNP
        base.localAvatar.laffMeter.start()
        DistributedMinigame.offstage(self)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        drawNum = 0
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                drawNum += 1
                toon.reparentTo(render)
                toon.setAnimState('Happy', 1.0)
                toon.hideName()
                toon.startSmooth()
                toon.startLookAround()
                distCNP = toon.find('**/distAvatarCollNode*')
                distCNP.node().setIntoCollideMask(BitMask32.allOff())
                toonSD = TwoDGameToonSD.TwoDGameToonSD(avId, self)
                self.toonSDs[avId] = toonSD
                toonSD.enter()
                toonSD.fsm.request('normal')
                self.toonSDs[avId].createHeadFrame(drawNum)

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        self.twoDWalk = TwoDWalk(self.twoDDrive,
                                 broadcast=not self.isSinglePlayer())
        self.scores = [0] * self.numPlayers
        spacing = 0.4
        for i in range(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = MinigameAvatarScorePanel.MinigameAvatarScorePanel(
                avId, avName)
            scorePanel.setScale(0.9)
            scorePanel.setPos(0.75 - spacing * (self.numPlayers - 1 - i), 0.0,
                              0.85)
            scorePanel.makeTransparent(0.75)
            self.scorePanels.append(scorePanel)

        self.gameFSM.request('play', [timestamp])

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

    def exitOff(self):
        pass

    def enterPlay(self, timestamp):
        self.notify.debug('enterPlay')
        elapsedTime = globalClockDelta.localElapsedTime(timestamp)
        self.sectionMgr.enterPlay(elapsedTime)
        handlers = [None, None, None, None, self.shootKeyHandler]
        self.twoDDrive.arrowKeys.setPressHandlers(handlers)
        self.twoDWalk.start()
        self.accept('jumpStart', self.startJump)
        self.accept('enemyHit', self.localToonHitByEnemy)
        self.accept('twoDTreasureGrabbed', self.__treasureGrabbed)
        self.accept('enemyShot', self.__enemyShot)
        taskMgr.remove(self.UpdateLocalToonTask)
        taskMgr.add(self.__updateLocalToonTask,
                    self.UpdateLocalToonTask,
                    priority=1)
        base.localAvatar.laffMeter.stop()
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(ToonBlitzGlobals.GameDuration[self.getSafezoneId()])
        self.timer.countdown(
            ToonBlitzGlobals.GameDuration[self.getSafezoneId()],
            self.timerExpired)
        return

    def exitPlay(self):
        handlers = [None, None, None, None, None]
        self.twoDDrive.arrowKeys.setPressHandlers(handlers)
        if self.toonSDs[
                self.localAvId].fsm.getCurrentState().getName() != 'victory':
            base.localAvatar.b_setAnimState('Happy', 1.0)
        self.ignore('jumpStart')
        self.ignore('enemyHit')
        self.ignore('twoDTreasureGrabbed')
        return

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

    def exitPause(self):
        pass

    def enterShowScores(self):
        self.notify.debug('enterShowScores')
        lerpTrack = Parallel()
        lerpDur = 0.5
        tY = 0.6
        bY = -.6
        lX = -.7
        cX = 0
        rX = 0.7
        scorePanelLocs = (((cX, bY), ), ((lX, bY), (rX, bY)),
                          ((cX, bY), (lX, bY), (rX, bY)), ((lX, tY), (rX, tY),
                                                           (lX, bY), (rX, bY)))
        scorePanelLocs = scorePanelLocs[self.numPlayers - 1]
        for i in range(self.numPlayers):
            panel = self.scorePanels[i]
            pos = scorePanelLocs[i]
            lerpTrack.append(
                Parallel(
                    LerpPosInterval(panel,
                                    lerpDur,
                                    Point3(pos[0], 0, pos[1]),
                                    blendType='easeInOut'),
                    LerpScaleInterval(panel,
                                      lerpDur,
                                      Vec3(panel.getScale()) * 1.5,
                                      blendType='easeInOut')))

        self.showScoreTrack = Parallel(
            lerpTrack, self.getElevatorCloseTrack(),
            Sequence(Wait(ToonBlitzGlobals.ShowScoresDuration),
                     Func(self.gameOver)))
        self.showScoreTrack.start()

    def exitShowScores(self):
        self.showScoreTrack.pause()
        del self.showScoreTrack

    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        self.timer.stop()
        self.timer.destroy()
        del self.timer
        taskMgr.remove(self.EndGameTaskName)
        self.twoDWalk.stop()
        self.twoDWalk.destroy()
        del self.twoDWalk
        self.twoDDrive = None
        del self.twoDDrive
        return

    def exitCleanup(self):
        pass

    def acceptInputs(self):
        if hasattr(self, 'twoDDrive'):
            handlers = [None, None, None, None, self.shootKeyHandler]
            self.twoDDrive.arrowKeys.setPressHandlers(handlers)
            self.twoDDrive.start()
        return

    def ignoreInputs(self):
        if hasattr(self, 'twoDDrive'):
            handlers = [None, None, None, None, None]
            self.twoDDrive.arrowKeys.setPressHandlers(handlers)
            self.twoDDrive.lastAction = None
            self.twoDDrive.stop()
        return

    def __updateLocalToonTask(self, task):
        dt = globalClock.getDt()
        self.cameraMgr.update()
        if self.gameFSM.getCurrentState().getName() == 'play':
            if not self.toonSDs[self.localAvId].fsm.getCurrentState().getName(
            ) == 'victory':
                if not base.localAvatar.getY() == 0:
                    base.localAvatar.setY(0)
        if base.localAvatar.getZ() < -2.0:
            self.localToonFellDown()
        for avId in self.toonSDs.keys():
            self.toonSDs[avId].update()

        return task.cont

    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 setupHeadCollision(self):
        collSphere = CollisionSphere(0, 0, 0, 1)
        collSphere.setTangible(1)
        collNode = CollisionNode('headCollSphere')
        collNode.setFromCollideMask(ToontownGlobals.WallBitmask)
        collNode.setIntoCollideMask(BitMask32.allOff())
        collNode.addSolid(collSphere)
        head = base.localAvatar.getPart('head', '1000')
        self.headCollNP = head.attachNewNode(collNode)
        self.headCollNP.setPos(0, 0, 0.0)
        animal = base.localAvatar.style.getAnimal()
        if animal == 'dog' or animal == 'bear' or animal == 'horse':
            torso = base.localAvatar.style.torso
            legs = base.localAvatar.style.legs
            if (torso == 'ls' or torso == 'ld') and legs == 'l':
                self.headCollNP.setZ(-1.3)
            else:
                self.headCollNP.setZ(-0.7)
        elif animal == 'mouse' or animal == 'duck':
            self.headCollNP.setZ(0.5)
        elif animal == 'cat':
            self.headCollNP.setZ(-0.3)
        elif animal == 'rabbit':
            self.headCollNP.setZ(-0.5)
        elif animal == 'monkey':
            self.headCollNP.setZ(0.3)
        elif animal == 'pig':
            self.headCollNP.setZ(-0.7)
        self.headCollNP.hide()
        if self.showCollSpheres:
            self.headCollNP.show()
        headCollisionEvent = CollisionHandlerEvent()
        headCollisionEvent.addInPattern('enter%fn-into-%in')
        headCollisionEvent.addOutPattern('%fn-exit-%in')
        cTrav = base.localAvatar.controlManager.currentControls.cTrav
        cTrav.addCollider(self.headCollNP, headCollisionEvent)
        self.accept('enterheadCollSphere-into-floor1',
                    self.__handleHeadCollisionIntoFloor)
        self.accept('headCollSphere-exit-floor1',
                    self.__handleHeadCollisionExitFloor)

    def __handleHeadCollisionIntoFloor(self, cevent):
        self.isHeadInFloor = True
        if base.localAvatar.controlManager.currentControls.lifter.getVelocity(
        ) > 0:
            base.localAvatar.controlManager.currentControls.lifter.setVelocity(
                0.0)
            self.assetMgr.playHeadCollideSound()

    def __handleHeadCollisionExitFloor(self, cevent):
        self.isHeadInFloor = False

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        i = self.avIdList.index(avId)
        pos = Point3(ToonBlitzGlobals.ToonStartingPosition[0] + i,
                     ToonBlitzGlobals.ToonStartingPosition[1],
                     ToonBlitzGlobals.ToonStartingPosition[2])
        toon.setPos(pos)
        toon.setHpr(-90, 0, 0)

    def startJump(self):
        self.assetMgr.playJumpSound()

    def checkValidity(self, avId):
        if not self.hasLocalToon:
            return False
        if self.gameFSM.getCurrentState(
        ) == None or self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring msg: av %s performing some action.' %
                                avId)
            return False
        toon = self.getAvatar(avId)
        if toon == None:
            return False
        return True

    def shootKeyHandler(self):
        self.toonSDs[self.localAvId].shootGun()
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime())
        self.sendUpdate('showShootGun', [self.localAvId, timestamp])

    def showShootGun(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s is shooting water gun' % avId)
            if avId != self.localAvId:
                self.toonSDs[avId].shootGun()

    def localToonFellDown(self):
        if self.toonSDs[
                self.localAvId].fsm.getCurrentState().getName() != 'fallDown':
            self.toonSDs[self.localAvId].fsm.request('fallDown')
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime())
            self.updateScore(
                self.localAvId,
                ToonBlitzGlobals.ScoreLossPerFallDown[self.getSafezoneId()])
            self.sendUpdate('toonFellDown', [self.localAvId, timestamp])

    def toonFellDown(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s fell down.' % avId)
            if avId != self.localAvId:
                self.updateScore(
                    avId, ToonBlitzGlobals.ScoreLossPerFallDown[
                        self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('fallDown')

    def localToonHitByEnemy(self):
        currToonState = self.toonSDs[
            self.localAvId].fsm.getCurrentState().getName()
        if not (currToonState == 'fallBack' or currToonState == 'squish'):
            self.toonSDs[self.localAvId].fsm.request('fallBack')
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime())
            self.updateScore(
                self.localAvId, ToonBlitzGlobals.ScoreLossPerEnemyCollision[
                    self.getSafezoneId()])
            self.sendUpdate('toonHitByEnemy', [self.localAvId, timestamp])

    def toonHitByEnemy(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s hit by a suit' % avId)
            if avId != self.localAvId:
                self.updateScore(
                    avId, ToonBlitzGlobals.ScoreLossPerEnemyCollision[
                        self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('fallBack')

    def localToonSquished(self):
        currToonState = self.toonSDs[
            self.localAvId].fsm.getCurrentState().getName()
        if not (currToonState == 'fallBack' or currToonState == 'squish'):
            self.toonSDs[self.localAvId].fsm.request('squish')
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime())
            self.updateScore(
                self.localAvId, ToonBlitzGlobals.ScoreLossPerStomperSquish[
                    self.getSafezoneId()])
            self.sendUpdate('toonSquished', [self.localAvId, timestamp])

    def toonSquished(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s is squished.' % avId)
            if avId != self.localAvId:
                self.updateScore(
                    avId, ToonBlitzGlobals.ScoreLossPerStomperSquish[
                        self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('squish')

    def localToonVictory(self):
        if not ToonBlitzGlobals.EndlessGame:
            self.ignoreInputs()
        if not self.toonSDs[
                self.localAvId].fsm.getCurrentState().getName() == 'victory':
            self.toonSDs[self.localAvId].fsm.request('victory')
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime())
            self.sendUpdate('toonVictory', [self.localAvId, timestamp])

    def toonVictory(self, avId, timestamp):
        if self.checkValidity(avId):
            self.notify.debug('avatar %s achieves victory' % avId)
            if avId != self.localAvId:
                self.toonSDs[avId].fsm.request('victory')

    def addVictoryScore(self, avId, score):
        if not self.hasLocalToon:
            return
        self.updateScore(avId, score)
        if avId == self.localAvId:
            self.assetMgr.threeSparkles.play()

    def __treasureGrabbed(self, sectionIndex, treasureIndex):
        self.notify.debug('treasure %s-%s grabbed' %
                          (sectionIndex, treasureIndex))
        section = self.sectionMgr.sections[sectionIndex]
        section.treasureMgr.treasures[treasureIndex].hideTreasure()
        self.assetMgr.treasureGrabSound.play()
        self.sendUpdate('claimTreasure', [sectionIndex, treasureIndex])

    def setTreasureGrabbed(self, avId, sectionIndex, treasureIndex):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() == 'play':
            self.notify.debug('treasure %s-%s grabbed by %s' %
                              (sectionIndex, treasureIndex, avId))
            numSections = len(self.sectionMgr.sections)
            if sectionIndex < numSections:
                section = self.sectionMgr.sections[sectionIndex]
                numTreasures = len(section.treasureMgr.treasures)
                if treasureIndex < numTreasures:
                    treasure = section.treasureMgr.treasures[treasureIndex]
                    if avId != self.localAvId:
                        treasure.hideTreasure()
                    self.updateScore(
                        avId,
                        ToonBlitzGlobals.ScoreGainPerTreasure * treasure.value)
                else:
                    self.notify.error(
                        'WHOA!! treasureIndex %s is out of range; numTreasures = %s'
                        % (treasureIndex, numTreasures))
                    base.localAvatar.sendLogMessage(
                        'treasureIndex %s is out of range; numTreasures = %s' %
                        (treasureIndex, numTreasures))
            else:
                self.notify.error(
                    'WHOA!! sectionIndex %s is out of range; numSections = %s'
                    % (sectionIndex, numSections))
                base.localAvatar.sendLogMessage(
                    'sectionIndex %s is out of range; numSections = %s' %
                    (sectionIndex, numSections))

    def __enemyShot(self, sectionIndex, enemyIndex):
        self.sectionMgr.sections[sectionIndex].enemyMgr.enemies[
            enemyIndex].doShotTrack()
        self.sendUpdate('claimEnemyShot', [sectionIndex, enemyIndex])

    def setEnemyShot(self, avId, sectionIndex, enemyIndex, enemyHealth):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() == 'play':
            self.notify.debug('enemy %s is shot by %s. Health left %s' %
                              (enemyIndex, avId, enemyHealth))
            if enemyHealth > 0:
                if not avId == self.localAvId:
                    self.sectionMgr.sections[sectionIndex].enemyMgr.enemies[
                        enemyIndex].doShotTrack()
            else:
                enemy = self.sectionMgr.sections[
                    sectionIndex].enemyMgr.enemies[enemyIndex]
                treasureSpawnPoint = Point3(
                    enemy.suit.getX(), enemy.suit.getY(),
                    enemy.suit.getZ() + enemy.suit.height / 2.0)
                self.spawnTreasure(sectionIndex, enemyIndex,
                                   treasureSpawnPoint)
                enemy.doDeathTrack()

    def updateScore(self, avId, deltaScore):
        i = self.avIdList.index(avId)
        self.scores[i] += deltaScore
        self.scorePanels[i].setScore(self.scores[i])
        self.toonSDs[avId].showScoreText(deltaScore)

    def spawnTreasure(self, sectionIndex, enemyIndex, pos):
        if self.gameFSM.getCurrentState().getName() == 'play':
            section = self.sectionMgr.sections[sectionIndex]
            treasure = section.treasureMgr.enemyTreasures[enemyIndex]
            treasure.setTreasurePos(pos)
            treasure.popupEnemyTreasure()

    def timerExpired(self):
        self.notify.debug('timer expired')
        if not self.reportedDone:
            if not ToonBlitzGlobals.EndlessGame:
                self.ignoreInputs()
            self.reportedDone = True
            self.sendUpdate('reportDone')

    def setEveryoneDone(self):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring setEveryoneDone msg')
            return
        self.notify.debug('setEveryoneDone')

        def endGame(task, self=self):
            if not ToonBlitzGlobals.EndlessGame:
                self.gameFSM.request('showScores')
            return Task.done

        self.timer.hide()
        taskMgr.doMethodLater(1, endGame, self.EndGameTaskName)

    def getElevatorCloseTrack(self):
        leftDoor = self.sectionMgr.exitElevator.find('**/doorL')
        rightDoor = self.sectionMgr.exitElevator.find('**/doorR')
        leftDoorClose = LerpPosInterval(leftDoor, 2, Point3(3.02, 0, 0))
        rightDoorClose = LerpPosInterval(rightDoor, 2, Point3(-3.02, 0, 0))
        return Sequence(Wait(self.timeToRunToElevator),
                        Parallel(leftDoorClose, rightDoorClose))

    def debugStartPause(self):
        self.sectionMgr.enterPause()

    def debugEndPause(self):
        self.sectionMgr.exitPause()
Ejemplo n.º 3
0
class DistributedTwoDGame(DistributedMinigame):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTwoDGame')
    # define constants that you won't want to tweak here

    UpdateLocalToonTask = "ToonBlitzUpdateLocalToonTask"  # name of the update task called each frame
    EndGameTaskName = 'endTwoDGame'

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM(
            'DistributedTwoDGame',
            [
                State.State('off', self.enterOff, self.exitOff, ['play']),
                State.State('play', self.enterPlay, self.exitPlay,
                            ['cleanup', 'pause', 'showScores']),
                State.State('pause', self.enterPause, self.exitPause,
                            ['cleanup', 'play', 'showScores']),
                State.State('showScores', self.enterShowScores,
                            self.exitShowScores, ['cleanup']),
                State.State('cleanup', self.enterCleanup, self.exitCleanup,
                            []),
            ],
            # Initial State
            'off',
            # Final State
            'cleanup',
        )

        # it's important for the final state to do cleanup;
        # on disconnect, the ClassicFSM will be forced into the
        # final state. All states (except 'off') should
        # be prepared to transition to 'cleanup' at any time.

        # Add our game ClassicFSM to the framework ClassicFSM
        self.addChildGameFSM(self.gameFSM)

        if __debug__:
            base.mg = self
            self.accept('p', self.debugStartPause)
            self.accept('o', self.debugEndPause)
##            self.accept('y', self.localToonVictory)

        self.reportedDone = False
        self.showCollSpheres = False

    def getTitle(self):
        return TTLocalizer.TwoDGameTitle

    def getInstructions(self):
        return TTLocalizer.TwoDGameInstructions

    def getMaxDuration(self):
        # how many seconds can this minigame possibly last (within reason)?
        # this is for debugging only
        return 200

    def __defineConstants(self):
        '''Define all the game contants here.'''
        self.TOON_SPEED = 12.0
        self.MAX_FRAME_MOVE = 1
        self.isHeadInFloor = False
        self.timeToRunToElevator = 1.5

    def setSectionsSelected(self, sectionsSelected):
        """ Set the sectionsSelected as dictated by the AI."""
        self.sectionsSelected = sectionsSelected

    def load(self):
        self.notify.debug("load")
        DistributedMinigame.load(self)
        self.__defineConstants()
        # Load resources and create objects here
        # Create Asset Manager and load assets
        self.assetMgr = ToonBlitzAssetMgr.ToonBlitzAssetMgr(self)
        # Create camera
        self.cameraMgr = TwoDCamera.TwoDCamera(camera)
        # Create a Section Manager
        self.sectionMgr = TwoDSectionMgr.TwoDSectionMgr(
            self, self.sectionsSelected)
        # Get the start and end X values of the game. Required for the progress line.
        self.gameStartX = -40.
        endSection = self.sectionMgr.sections[-1]
        # This is just to confirm that the last section is actually the endSection
        assert endSection.sectionTypeNum == 'end'
        self.gameEndX = endSection.spawnPointMgr.gameEndX
        self.gameLength = self.gameEndX - self.gameStartX

        # make a dictionary of TwoDGameToonSDs; they will track
        # toons' states and animate them appropriately
        self.toonSDs = {}
        # add the local toon now, add remote toons as they join
        avId = self.localAvId
        toonSD = TwoDGameToonSD.TwoDGameToonSD(avId, self)
        self.toonSDs[avId] = toonSD
        # The toon with the higher drawNum is rendered closer to the camera.
        # Local toon is rendered with a drawNum of 0. In the progress line the
        # other toons appear in front of the localToon.
        self.toonSDs[avId].createHeadFrame(0)

    def unload(self):
        self.notify.debug("unload")
        DistributedMinigame.unload(self)
        taskMgr.remove(self.UpdateLocalToonTask)

        # unload resources and delete objects from load() here
        for avId in self.toonSDs.keys():
            toonSD = self.toonSDs[avId]
            toonSD.destroy()
        del self.toonSDs

        self.cameraMgr.destroy()
        del self.cameraMgr

        self.sectionMgr.destroy()
        del self.sectionMgr

        for panel in self.scorePanels:
            panel.cleanup()
        del self.scorePanels

        self.assetMgr.destroy()
        del self.assetMgr

        # remove our game ClassicFSM from the framework ClassicFSM
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM

    def onstage(self):
        self.notify.debug("onstage")
        DistributedMinigame.onstage(self)

        self.scorePanels = []

        # start up the minigame; parent things to render, start playing music...
        self.assetMgr.onstage()

        # Displaying the local avatar
        lt = base.localAvatar
        lt.reparentTo(render)
        ##        lt.setBin('fixed', 10)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        base.localAvatar.collisionsOn()
        base.localAvatar.setTransparency(1)
        # Create Head Collision for the local avatar
        self.setupHeadCollision()

        self.cameraMgr.onstage()

        toonSD = self.toonSDs[self.localAvId]
        toonSD.enter()
        toonSD.fsm.request('normal')

        self.twoDDrive = TwoDDrive(self,
                                   self.TOON_SPEED,
                                   maxFrameMove=self.MAX_FRAME_MOVE)
        # at this point we cannot yet show the remote players' toons

    def offstage(self):
        self.notify.debug("offstage")
        # stop the minigame; parent things to hidden, stop the music...
        self.assetMgr.offstage()

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

        base.localAvatar.setTransparency(0)

        self.ignore('enterheadCollSphere-into-floor1')
        base.localAvatar.controlManager.currentControls.cTrav.removeCollider(
            self.headCollNP)
        self.headCollNP.removeNode()
        del self.headCollNP

        # Show the laff meter
        base.localAvatar.laffMeter.start()

        #@TODO: setIntoBitMask of distAvatarCollNode of all the toons to 0x1
        #@TODO: setBin to default for all toons

        # the base class parents the toons to hidden, so consider
        # calling it last
        DistributedMinigame.offstage(self)

    def setGameReady(self):
        if not self.hasLocalToon: return
        self.notify.debug("setGameReady")
        if DistributedMinigame.setGameReady(self):
            return
        # all of the remote toons have joined the game; it's safe to show them now.

        drawNum = 0
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                drawNum += 1
                toon.reparentTo(render)
                ##                toon.setBin('fixed', drawNum)
                toon.setAnimState('Happy', 1.0)
                toon.hideName()
                # Start a smoothing task
                toon.startSmooth()
                toon.startLookAround()
                # Switching off the Bitmask for distAvatarCollNode
                # so that the toons don't collide against each other
                distCNP = toon.find('**/distAvatarCollNode*')
                distCNP.node().setIntoCollideMask(BitMask32.allOff())

                # create the toonSD for this toon
                toonSD = TwoDGameToonSD.TwoDGameToonSD(avId, self)
                self.toonSDs[avId] = toonSD
                toonSD.enter()
                toonSD.fsm.request('normal')
                # The toon with the higher drawNum is rendered closer to the camera
                self.toonSDs[avId].createHeadFrame(drawNum)

    def setGameStart(self, timestamp):
        if not self.hasLocalToon: return
        self.notify.debug("setGameStart")
        # base class will cause gameFSM to enter initial state
        DistributedMinigame.setGameStart(self, timestamp)
        # all players have finished reading the rules, and are ready to start playing.

        ##        self.twoDDrive = TwoDDrive(self, self.TOON_SPEED, maxFrameMove=self.MAX_FRAME_MOVE)

        self.twoDWalk = TwoDWalk(self.twoDDrive,
                                 broadcast=not self.isSinglePlayer())

        # Initialize the scoreboard
        self.scores = [0] * self.numPlayers
        spacing = .4
        for i in xrange(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = \
                       MinigameAvatarScorePanel.MinigameAvatarScorePanel(avId,
                                                                         avName)
            scorePanel.setScale(.9)
            scorePanel.setPos(.75 - spacing * ((self.numPlayers - 1) - i), 0.0,
                              .85)
            # make the panels slightly transparent
            scorePanel.makeTransparent(.75)
            self.scorePanels.append(scorePanel)

        # Transition to the appropriate state
        self.gameFSM.request("play", [timestamp])

    # these are enter and exit functions for the game's
    # fsm (finite state machine)

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

    def exitOff(self):
        pass

    def enterPlay(self, timestamp):
        self.notify.debug("enterPlay")

        # Calculated to make sure all Lerps will start at the same time
        elapsedTime = globalClockDelta.localElapsedTime(timestamp)

        self.sectionMgr.enterPlay(elapsedTime)
        # Enable shootKeyHandler() to handle Ctrl
        handlers = [None, None, None, None, self.shootKeyHandler]
        self.twoDDrive.arrowKeys.setPressHandlers(handlers)
        # Start twoDWalk so that left-right walking works
        self.twoDWalk.start()

        # Listen to 'jumpStart' to play jump sound
        self.accept('jumpStart', self.startJump)
        # Listen for enemy collision
        self.accept('enemyHit', self.localToonHitByEnemy)
        # Listen for treasure collision
        self.accept('twoDTreasureGrabbed', self.__treasureGrabbed)
        # Listen for for enemy shot
        self.accept('enemyShot', self.__enemyShot)

        # This is the local toon task (game loop for the local toon)
        taskMgr.remove(self.UpdateLocalToonTask)
        # Making the priority of this task 1 because this controls the camera.
        # We want the camera pos to be calculated after the toon pos has been
        # calculated, which happens in TwoDDrive.__update().
        taskMgr.add(self.__updateLocalToonTask,
                    self.UpdateLocalToonTask,
                    priority=1)

        # Hide the laff meter
        base.localAvatar.laffMeter.stop()

        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(ToonBlitzGlobals.GameDuration[self.getSafezoneId()])
        self.timer.countdown(
            ToonBlitzGlobals.GameDuration[self.getSafezoneId()],
            self.timerExpired)

    def exitPlay(self):
        # Remove all handles so that we don't track Ctrl
        handlers = [None, None, None, None, None]
        self.twoDDrive.arrowKeys.setPressHandlers(handlers)

        # Loop idle animation so that the toon is standing while showing showing the score.
        # Do this only if player is not in victory state.
        if (self.toonSDs[self.localAvId].fsm.getCurrentState().getName() !=
                'victory'):
            base.localAvatar.b_setAnimState('Happy', 1.0)

        # Stop listening to 'jumpStart' because we don't need to play jump sound
        self.ignore('jumpStart')
        self.ignore('enemyHit')
        self.ignore('twoDTreasureGrabbed')

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

    def exitPause(self):
        pass

    def enterShowScores(self):
        self.notify.debug("enterShowScores")

        # lerp up the goal bar, score panels
        lerpTrack = Parallel()
        lerpDur = .5

        # score panels
        # top/bottom Y
        tY = .6
        bY = -.6
        # left/center/right X
        lX = -.7
        cX = 0
        rX = .7
        scorePanelLocs = (
            ((cX, bY), ),
            ((lX, bY), (rX, bY)),
            ((cX, bY), (lX, bY), (rX, bY)),
            ((lX, tY), (rX, tY), (lX, bY), (rX, bY)),
        )
        scorePanelLocs = scorePanelLocs[self.numPlayers - 1]
        for i in xrange(self.numPlayers):
            panel = self.scorePanels[i]
            pos = scorePanelLocs[i]
            lerpTrack.append(
                Parallel(
                    LerpPosInterval(panel,
                                    lerpDur,
                                    Point3(pos[0], 0, pos[1]),
                                    blendType='easeInOut'),
                    LerpScaleInterval(panel,
                                      lerpDur,
                                      Vec3(panel.getScale()) * 1.5,
                                      blendType='easeInOut'),
                ))

        self.showScoreTrack = Parallel(
            lerpTrack,
            self.getElevatorCloseTrack(),
            Sequence(
                Wait(ToonBlitzGlobals.ShowScoresDuration),
                Func(self.gameOver),
            ),
        )

        self.showScoreTrack.start()

    def exitShowScores(self):
        # calling finish() here would cause problems if we're
        # exiting abnormally, because of the gameOver() call
        self.showScoreTrack.pause()
        del self.showScoreTrack

    def enterCleanup(self):
        self.notify.debug("enterCleanup")

        self.timer.stop()
        self.timer.destroy()
        del self.timer

        taskMgr.remove(self.EndGameTaskName)
        # Stop twoDWalk to stop left-right movement
        self.twoDWalk.stop()
        self.twoDWalk.destroy()
        del self.twoDWalk
        self.twoDDrive = None
        del self.twoDDrive

    def exitCleanup(self):
        pass

    def acceptInputs(self):
        # Enable shootKeyHandler() to handle Ctrl
        if hasattr(self, 'twoDDrive'):
            handlers = [None, None, None, None, self.shootKeyHandler]
            self.twoDDrive.arrowKeys.setPressHandlers(handlers)
            # Start twoDDrive so that left-right walking works
            self.twoDDrive.start()

    def ignoreInputs(self):
        # Remove all handles so that we don't track Ctrl
        if hasattr(self, 'twoDDrive'):
            handlers = [None, None, None, None, None]
            self.twoDDrive.arrowKeys.setPressHandlers(handlers)
            # Stop twoDDrive to stop left-right movement
            self.twoDDrive.lastAction = None
            self.twoDDrive.stop()

    def __updateLocalToonTask(self, task):
        dt = globalClock.getDt()
        self.cameraMgr.update()

        # ALWAYS keep the toon at y=0
        if self.gameFSM.getCurrentState().getName() == 'play':
            # Only exception is when the local toon has reached the victory state.
            if not self.toonSDs[self.localAvId].fsm.getCurrentState().getName(
            ) == 'victory':
                if not (base.localAvatar.getY() == 0):
                    base.localAvatar.setY(0)

        # Handle if toon falls into a pit
        if (base.localAvatar.getZ() < -2.):
            self.localToonFellDown()

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

        return task.cont

    def handleDisabledAvatar(self, avId):
        """This will be called if an avatar exits unexpectedly"""
        self.notify.debug("handleDisabledAvatar")
        self.notify.debug("avatar " + str(avId) + " disabled")
        # clean up any references to the disabled avatar before he disappears
        self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]
        # then call the base class
        DistributedMinigame.handleDisabledAvatar(self, avId)

    def setupHeadCollision(self):
        collSphere = CollisionSphere(0, 0, 0, 1)
        collSphere.setTangible(1)
        collNode = CollisionNode('headCollSphere')
        collNode.setFromCollideMask(ToontownGlobals.WallBitmask)
        collNode.setIntoCollideMask(BitMask32.allOff())
        collNode.addSolid(collSphere)
        head = base.localAvatar.getPart('head', '1000')
        self.headCollNP = head.attachNewNode(collNode)
        self.headCollNP.setPos(0, 0, 0.0)
        # I'm positioning the headCollNP at different Z values because the different animal
        # species have different heights. The headCollNP prevents the toon from going through
        # a floor. For tall toons, I bring the headCollNP lower so that the toon can complete
        # a jump onto a 2nd platform without banging it's head on the 3rd platform.
        animal = base.localAvatar.style.getAnimal()
        if (animal == 'dog') or (animal == 'bear') or (animal == 'horse'):
            torso = base.localAvatar.style.torso
            legs = base.localAvatar.style.legs
            if ((torso == 'ls') or (torso == 'ld')) and (legs == 'l'):
                self.headCollNP.setZ(-1.3)
            else:
                self.headCollNP.setZ(-0.7)
        elif (animal == 'mouse') or (animal == 'duck'):
            self.headCollNP.setZ(0.5)
        elif (animal == 'cat'):
            self.headCollNP.setZ(-0.3)
        elif (animal == 'rabbit'):
            self.headCollNP.setZ(-0.5)
        elif (animal == 'monkey'):
            self.headCollNP.setZ(0.3)
        elif (animal == 'pig'):
            self.headCollNP.setZ(-0.7)

        self.headCollNP.hide()
        if self.showCollSpheres:
            self.headCollNP.show()

        # Setting up the collision event handler
        headCollisionEvent = CollisionHandlerEvent()
        headCollisionEvent.addInPattern("enter%fn-into-%in")
        headCollisionEvent.addOutPattern("%fn-exit-%in")
        # Adding this as a collider to the main local avatar's traverser
        cTrav = base.localAvatar.controlManager.currentControls.cTrav
        cTrav.addCollider(self.headCollNP, headCollisionEvent)
        self.accept('enterheadCollSphere-into-floor1',
                    self.__handleHeadCollisionIntoFloor)
        self.accept('headCollSphere-exit-floor1',
                    self.__handleHeadCollisionExitFloor)

    def __handleHeadCollisionIntoFloor(self, cevent):
        # Toon's head is hitting a floor.
        # Making the up velocity 0 when you hit a floor. Only while going up.
        self.isHeadInFloor = True
        if (base.localAvatar.controlManager.currentControls.lifter.getVelocity(
        ) > 0):
            base.localAvatar.controlManager.currentControls.lifter.setVelocity(
                0.)
            # Play collision sound
            self.assetMgr.playHeadCollideSound()

    def __handleHeadCollisionExitFloor(self, cevent):
        # Toon's head is exiting the floor.
        # Check this flag before jumping in TwoDDrive.
        self.isHeadInFloor = False

    def __placeToon(self, avId):
        """Places a toon in its starting position."""
        toon = self.getAvatar(avId)
        i = self.avIdList.index(avId)
        pos = Point3(ToonBlitzGlobals.ToonStartingPosition[0] + i,
                     ToonBlitzGlobals.ToonStartingPosition[1],
                     ToonBlitzGlobals.ToonStartingPosition[2])
        toon.setPos(pos)
        toon.setHpr(-90, 0, 0)

    def startJump(self):
        """
        This function does not control the start of the jump.
        GravityWalker controls jump and sends a msg "jumpStart".
        This function is only called because we are listening to the "jumpStart" msg.
        """
        ##        self.notify.debug('startJump')
        # Play start jump sound here
        self.assetMgr.playJumpSound()

    def checkValidity(self, avId):
        if not self.hasLocalToon: return False

        if self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring msg: av %s performing some action.' %
                                avId)
            return False

        # try to see if the avId is valid, return if not
        toon = self.getAvatar(avId)
        if toon == None:
            return False

        return True

    def shootKeyHandler(self):
        """Handle pressing the Ctrl key."""
        self.toonSDs[self.localAvId].shootGun()
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime())
        self.sendUpdate('showShootGun', [self.localAvId, timestamp])

    def showShootGun(self, avId, timestamp):
        """ called when remote toon shoots gun """
        if self.checkValidity(avId):
            self.notify.debug("avatar %s is shooting water gun" % avId)
            if avId != self.localAvId:
                self.toonSDs[avId].shootGun()

    def localToonFellDown(self):
        """ Called when the local toon falls through a hole."""
        if (self.toonSDs[self.localAvId].fsm.getCurrentState().getName() !=
                'fallDown'):
            self.toonSDs[self.localAvId].fsm.request('fallDown')
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime())
            # Update local toon's score
            self.updateScore(
                self.localAvId,
                ToonBlitzGlobals.ScoreLossPerFallDown[self.getSafezoneId()])
            self.sendUpdate('toonFellDown', [self.localAvId, timestamp])

    def toonFellDown(self, avId, timestamp):
        """ Called when a remote toon falls through a hole."""
        if self.checkValidity(avId):
            self.notify.debug("avatar %s fell down." % avId)
            if avId != self.localAvId:
                # Update remote toon's score
                self.updateScore(
                    avId, ToonBlitzGlobals.ScoreLossPerFallDown[
                        self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('fallDown')

    def localToonHitByEnemy(self):
        """ Called when a suit collides with localToon."""
        # If not already in fallBack or squished state
        currToonState = self.toonSDs[
            self.localAvId].fsm.getCurrentState().getName()
        if not (currToonState == 'fallBack' or currToonState == 'squish'):
            self.toonSDs[self.localAvId].fsm.request('fallBack')
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime())
            # Update local toon's score
            self.updateScore(
                self.localAvId, ToonBlitzGlobals.ScoreLossPerEnemyCollision[
                    self.getSafezoneId()])
            self.sendUpdate('toonHitByEnemy', [self.localAvId, timestamp])

    def toonHitByEnemy(self, avId, timestamp):
        """ Called when a remote toon is hit by suit."""
        if self.checkValidity(avId):
            self.notify.debug("avatar %s hit by a suit" % avId)
            if avId != self.localAvId:
                # Update remote toon's score
                self.updateScore(
                    avId, ToonBlitzGlobals.ScoreLossPerEnemyCollision[
                        self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('fallBack')

    def localToonSquished(self):
        """ Called when the local toon gets squished by a stomper."""
        # If not already in fallBack or squished state
        currToonState = self.toonSDs[
            self.localAvId].fsm.getCurrentState().getName()
        if not (currToonState == 'fallBack' or currToonState == 'squish'):
            self.toonSDs[self.localAvId].fsm.request('squish')
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime())
            # Update local toon's score
            self.updateScore(
                self.localAvId, ToonBlitzGlobals.ScoreLossPerStomperSquish[
                    self.getSafezoneId()])
            # Tell others that the local toon got squished
            self.sendUpdate('toonSquished', [self.localAvId, timestamp])

    def toonSquished(self, avId, timestamp):
        """ Called whan a remote toon is squished by a stomper."""
        if self.checkValidity(avId):
            self.notify.debug("avatar %s is squished." % avId)
            if (avId != self.localAvId):
                # Update remote toon's score
                self.updateScore(
                    avId, ToonBlitzGlobals.ScoreLossPerStomperSquish[
                        self.getSafezoneId()])
                self.toonSDs[avId].fsm.request('squish')

    def localToonVictory(self):
        """ Called when localToon reaches the end of tunnel. """
        if not ToonBlitzGlobals.EndlessGame:
            self.ignoreInputs()
        if not (self.toonSDs[self.localAvId].fsm.getCurrentState().getName()
                == 'victory'):
            self.toonSDs[self.localAvId].fsm.request('victory')
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime())
            self.sendUpdate('toonVictory', [self.localAvId, timestamp])

    def toonVictory(self, avId, timestamp):
        """ Called when a remote toon reaches the end of tunnel. """
        if self.checkValidity(avId):
            self.notify.debug("avatar %s achieves victory" % avId)
            if avId != self.localAvId:
                self.toonSDs[avId].fsm.request('victory')

    def addVictoryScore(self, avId, score):
        """ Called by the AI to set the victory score on the toon who has completed the game."""
        if not self.hasLocalToon: return
        # Update the toon's score
        self.updateScore(avId, score)
        # Play sound for the local player
        if (avId == self.localAvId):
            self.assetMgr.threeSparkles.play()

    def __treasureGrabbed(self, sectionIndex, treasureIndex):
        """ Handle the local toon grabbing this treasure.
        Another toon may actually get the credit, proceed as if we got it
        """
        self.notify.debug('treasure %s-%s grabbed' %
                          (sectionIndex, treasureIndex))
        # make the treasure react
        section = self.sectionMgr.sections[sectionIndex]
        section.treasureMgr.treasures[treasureIndex].hideTreasure()
        # play a sound
        self.assetMgr.treasureGrabSound.play()
        # tell the AI we're claiming this treasure
        self.sendUpdate("claimTreasure", [sectionIndex, treasureIndex])

    def setTreasureGrabbed(self, avId, sectionIndex, treasureIndex):
        """Update a treasure being grabbed by a toon."""
        if not self.hasLocalToon: return
        # Grab treasure only if we're still playing
        if (self.gameFSM.getCurrentState().getName() == 'play'):
            self.notify.debug("treasure %s-%s grabbed by %s" %
                              (sectionIndex, treasureIndex, avId))
            numSections = len(self.sectionMgr.sections)
            # numSections includes the end section also.
            ##            assert (sectionIndex < numSections)
            if (sectionIndex < numSections):
                section = self.sectionMgr.sections[sectionIndex]

                numTreasures = len(section.treasureMgr.treasures)
                ##                assert (treasureIndex < numTreasures)
                if (treasureIndex < numTreasures):
                    treasure = section.treasureMgr.treasures[treasureIndex]
                    if avId != self.localAvId:
                        # destroy the treasure
                        treasure.hideTreasure()

                    # Update the toon's score
                    self.updateScore(
                        avId,
                        ToonBlitzGlobals.ScoreGainPerTreasure * treasure.value)
                else:
                    self.notify.error(
                        'WHOA!! treasureIndex %s is out of range; numTreasures = %s'
                        % (treasureIndex, numTreasures))
                    base.localAvatar.sendLogMessage(
                        'treasureIndex %s is out of range; numTreasures = %s' %
                        (treasureIndex, numTreasures))
            else:
                self.notify.error(
                    'WHOA!! sectionIndex %s is out of range; numSections = %s'
                    % (sectionIndex, numSections))
                base.localAvatar.sendLogMessage(
                    'sectionIndex %s is out of range; numSections = %s' %
                    (sectionIndex, numSections))

    def __enemyShot(self, sectionIndex, enemyIndex):
        """
        Handle the local toon shooting the enemy of enemyIndex.
        Send a message to the AI.
        """
        ##        self.notify.debug('enemy %s-%s shot' %(sectionIndex, enemyIndex))
        # Play cog hit effect for the local client
        self.sectionMgr.sections[sectionIndex].enemyMgr.enemies[
            enemyIndex].doShotTrack()
        # play a sound

        # tall the AI we're claiming the shot
        self.sendUpdate('claimEnemyShot', [sectionIndex, enemyIndex])

    def setEnemyShot(self, avId, sectionIndex, enemyIndex, enemyHealth):
        """
        This method is called by the AI to all the clients.
        enemyIndex has been shot by avId.
        Update effects on the enemyIndex
        If avId is local toon update score.
        """
        if not self.hasLocalToon: return

        # Enemy is hit only if we're still playing
        if (self.gameFSM.getCurrentState().getName() == 'play'):
            self.notify.debug('enemy %s is shot by %s. Health left %s' %
                              (enemyIndex, avId, enemyHealth))
            if (enemyHealth > 0):
                # Enemy is still alive.
                # Don't play the shotTrack for the local client, because we already played it.
                if not (avId == self.localAvId):
                    self.sectionMgr.sections[sectionIndex].enemyMgr.enemies[
                        enemyIndex].doShotTrack()
            else:
                enemy = self.sectionMgr.sections[
                    sectionIndex].enemyMgr.enemies[enemyIndex]
                # Show the spawned treasure
                treasureSpawnPoint = Point3(
                    enemy.suit.getX(), enemy.suit.getY(),
                    enemy.suit.getZ() + enemy.suit.height / 2.)
                self.spawnTreasure(sectionIndex, enemyIndex,
                                   treasureSpawnPoint)
                # Enemy just died. Play Death animation.
                enemy.doDeathTrack()

    def updateScore(self, avId, deltaScore):
        i = self.avIdList.index(avId)
        self.scores[i] += deltaScore
        self.scorePanels[i].setScore(self.scores[i])
        self.toonSDs[avId].showScoreText(deltaScore)

    def spawnTreasure(self, sectionIndex, enemyIndex, pos):
        # Spawn a treasure if we're still playing
        if (self.gameFSM.getCurrentState().getName() == 'play'):
            # Place the correct treasure in the right position and show it
            section = self.sectionMgr.sections[sectionIndex]
            treasure = section.treasureMgr.enemyTreasures[enemyIndex]
            treasure.setTreasurePos(pos)
            treasure.popupEnemyTreasure()

    def timerExpired(self):
        self.notify.debug('timer expired')
        if not self.reportedDone:
            if not ToonBlitzGlobals.EndlessGame:
                self.ignoreInputs()
            self.reportedDone = True
            self.sendUpdate('reportDone')

    def setEveryoneDone(self):
        if not self.hasLocalToon: return
        if self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring setEveryoneDone msg')
            return

        self.notify.debug('setEveryoneDone')

        def endGame(task, self=self):
            if not ToonBlitzGlobals.EndlessGame:
                self.gameFSM.request('showScores')
            return Task.done

        # hide the timer
        self.timer.hide()

        taskMgr.doMethodLater(1, endGame, self.EndGameTaskName)

    def getElevatorCloseTrack(self):
        leftDoor = self.sectionMgr.exitElevator.find('**/doorL')
        rightDoor = self.sectionMgr.exitElevator.find('**/doorR')
        leftDoorClose = LerpPosInterval(leftDoor, 2, Point3(3.02, 0, 0))
        rightDoorClose = LerpPosInterval(rightDoor, 2, Point3(-3.02, 0, 0))
        return Sequence(Wait(self.timeToRunToElevator),
                        Parallel(leftDoorClose, rightDoorClose))

    def debugStartPause(self):
        """ This function is called when the minigame is paused in the debug mode."""
        self.sectionMgr.enterPause()

    def debugEndPause(self):
        """ This function is called when the minigame is unpaused in the debug mode."""
        self.sectionMgr.exitPause()