class DistributedMazeGame(DistributedMinigame):
    notify = directNotify.newCategory('DistributedMazeGame')
    CAMERA_TASK = 'MazeGameCameraTask'
    UPDATE_SUITS_TASK = 'MazeGameUpdateSuitsTask'
    TREASURE_GRAB_EVENT_NAME = 'MazeTreasureGrabbed'

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedMazeGame', [
            State.State('off', self.enterOff, self.exitOff, ['play']),
            State.State('play', self.enterPlay, self.exitPlay,
                        ['cleanup', 'showScores']),
            State.State('showScores', self.enterShowScores,
                        self.exitShowScores, ['cleanup']),
            State.State('cleanup', self.enterCleanup, self.exitCleanup, [])
        ], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.usesLookAround = 1

    def getTitle(self):
        return TTLocalizer.MazeGameTitle

    def getInstructions(self):
        return TTLocalizer.MazeGameInstructions

    def getMaxDuration(self):
        return MazeGameGlobals.GAME_DURATION

    def __defineConstants(self):
        self.TOON_SPEED = 8.0
        self.TOON_Z = 0
        self.MinSuitSpeedRange = [0.8 * self.TOON_SPEED, 0.6 * self.TOON_SPEED]
        self.MaxSuitSpeedRange = [1.1 * self.TOON_SPEED, 2.0 * self.TOON_SPEED]
        self.FASTER_SUIT_CURVE = 1
        self.SLOWER_SUIT_CURVE = self.getDifficulty() < 0.5
        self.slowerSuitPeriods = {
            2000: {
                4: [128, 76],
                8: [128, 99, 81, 68],
                12: [128, 108, 93, 82, 74, 67],
                16: [128, 112, 101, 91, 83, 76, 71, 66]
            },
            1000: {
                4: [110, 69],
                8: [110, 88, 73, 62],
                12: [110, 95, 83, 74, 67, 61],
                16: [110, 98, 89, 81, 75, 69, 64, 60]
            },
            5000: {
                4: [96, 63],
                8: [96, 79, 66, 57],
                12: [96, 84, 75, 67, 61, 56],
                16: [96, 87, 80, 73, 68, 63, 59, 55]
            },
            4000: {
                4: [86, 58],
                8: [86, 71, 61, 53],
                12: [86, 76, 68, 62, 56, 52],
                16: [86, 78, 72, 67, 62, 58, 54, 51]
            },
            3000: {
                4: [78, 54],
                8: [78, 65, 56, 49],
                12: [78, 69, 62, 57, 52, 48],
                16: [78, 71, 66, 61, 57, 54, 51, 48]
            },
            9000: {
                4: [71, 50],
                8: [71, 60, 52, 46],
                12: [71, 64, 58, 53, 49, 45],
                16: [71, 65, 61, 57, 53, 50, 47, 45]
            }
        }
        self.slowerSuitPeriodsCurve = {
            2000: {
                4: [128, 65],
                8: [128, 78, 66, 64],
                12: [128, 88, 73, 67, 64, 64],
                16: [128, 94, 79, 71, 67, 65, 64, 64]
            },
            1000: {
                4: [110, 59],
                8: [110, 70, 60, 58],
                12: [110, 78, 66, 61, 59, 58],
                16: [110, 84, 72, 65, 61, 59, 58, 58]
            },
            5000: {
                4: [96, 55],
                8: [96, 64, 56, 54],
                12: [96, 71, 61, 56, 54, 54],
                16: [96, 76, 65, 59, 56, 55, 54, 54]
            },
            4000: {
                4: [86, 51],
                8: [86, 59, 52, 50],
                12: [86, 65, 56, 52, 50, 50],
                16: [86, 69, 60, 55, 52, 51, 50, 50]
            },
            3000: {
                4: [78, 47],
                8: [78, 55, 48, 47],
                12: [78, 60, 52, 48, 47, 47],
                16: [78, 63, 55, 51, 49, 47, 47, 47]
            },
            9000: {
                4: [71, 44],
                8: [71, 51, 45, 44],
                12: [71, 55, 48, 45, 44, 44],
                16: [71, 58, 51, 48, 45, 44, 44, 44]
            }
        }
        self.fasterSuitPeriods = {
            2000: {
                4: [54, 42],
                8: [59, 52, 47, 42],
                12: [61, 56, 52, 48, 45, 42],
                16: [61, 58, 54, 51, 49, 46, 44, 42]
            },
            1000: {
                4: [50, 40],
                8: [55, 48, 44, 40],
                12: [56, 52, 48, 45, 42, 40],
                16: [56, 53, 50, 48, 45, 43, 41, 40]
            },
            5000: {
                4: [47, 37],
                8: [51, 45, 41, 37],
                12: [52, 48, 45, 42, 39, 37],
                16: [52, 49, 47, 44, 42, 40, 39, 37]
            },
            4000: {
                4: [44, 35],
                8: [47, 42, 38, 35],
                12: [48, 45, 42, 39, 37, 35],
                16: [49, 46, 44, 42, 40, 38, 37, 35]
            },
            3000: {
                4: [41, 33],
                8: [44, 40, 36, 33],
                12: [45, 42, 39, 37, 35, 33],
                16: [45, 43, 41, 39, 38, 36, 35, 33]
            },
            9000: {
                4: [39, 32],
                8: [41, 37, 34, 32],
                12: [42, 40, 37, 35, 33, 32],
                16: [43, 41, 39, 37, 35, 34, 33, 32]
            }
        }
        self.fasterSuitPeriodsCurve = {
            2000: {
                4: [62, 42],
                8: [63, 61, 54, 42],
                12: [63, 63, 61, 56, 50, 42],
                16: [63, 63, 62, 60, 57, 53, 48, 42]
            },
            1000: {
                4: [57, 40],
                8: [58, 56, 50, 40],
                12: [58, 58, 56, 52, 46, 40],
                16: [58, 58, 57, 56, 53, 49, 45, 40]
            },
            5000: {
                4: [53, 37],
                8: [54, 52, 46, 37],
                12: [54, 53, 52, 48, 43, 37],
                16: [54, 54, 53, 51, 49, 46, 42, 37]
            },
            4000: {
                4: [49, 35],
                8: [50, 48, 43, 35],
                12: [50, 49, 48, 45, 41, 35],
                16: [50, 50, 49, 48, 46, 43, 39, 35]
            },
            3000: {
                4: [46, 33],
                8: [47, 45, 41, 33],
                12: [47, 46, 45, 42, 38, 33],
                16: [47, 46, 46, 45, 43, 40, 37, 33]
            },
            9000: {
                4: [43, 32],
                8: [44, 42, 38, 32],
                12: [44, 43, 42, 40, 36, 32],
                16: [44, 44, 43, 42, 40, 38, 35, 32]
            }
        }
        self.CELL_WIDTH = MazeData.CELL_WIDTH
        self.MAX_FRAME_MOVE = self.CELL_WIDTH / 2
        startOffset = 3
        self.startPosHTable = [[Point3(0, startOffset, self.TOON_Z), 0],
                               [Point3(0, -startOffset, self.TOON_Z), 180],
                               [Point3(startOffset, 0, self.TOON_Z), 270],
                               [Point3(-startOffset, 0, self.TOON_Z), 90]]
        self.camOffset = Vec3(0, -19, 45)

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.__defineConstants()
        mazeName = MazeGameGlobals.getMazeName(self.doId, self.numPlayers,
                                               MazeData.mazeNames)
        self.maze = Maze.Maze(mazeName)
        model = loader.loadModel('phase_3.5/models/props/mickeySZ')
        self.treasureModel = model.find('**/mickeySZ')
        model.removeNode()
        self.treasureModel.setScale(1.6)
        self.treasureModel.setP(-90)
        self.music = base.loader.loadMusic('phase_4/audio/bgm/MG_toontag.ogg')
        self.toonHitTracks = {}
        self.scorePanels = []

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        del self.toonHitTracks
        self.maze.destroy()
        del self.maze
        self.treasureModel.removeNode()
        del self.treasureModel
        del self.music
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.maze.onstage()
        self.randomNumGen.shuffle(self.startPosHTable)
        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        self.camParent = render.attachNewNode('mazeGameCamParent')
        self.camParent.reparentTo(base.localAvatar)
        self.camParent.setPos(0, 0, 0)
        self.camParent.setHpr(render, 0, 0, 0)
        camera.reparentTo(self.camParent)
        camera.setPos(self.camOffset)
        self.__spawnCameraTask()
        self.toonRNGs = []
        for i in range(self.numPlayers):
            self.toonRNGs.append(RandomNumGen.RandomNumGen(self.randomNumGen))

        self.treasures = []
        for i in range(self.maze.numTreasures):
            self.treasures.append(
                MazeTreasure.MazeTreasure(self.treasureModel,
                                          self.maze.treasurePosList[i], i,
                                          self.doId))

        self.__loadSuits()
        for suit in self.suits:
            suit.onstage()

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

        self.grabSounds = []
        for i in range(5):
            self.grabSounds.append(
                base.loader.loadSfx('phase_4/audio/sfx/MG_maze_pickup.ogg'))

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

        self.scores = [0] * self.numPlayers
        self.goalBar = DirectWaitBar(parent=render2d,
                                     relief=DGG.SUNKEN,
                                     frameSize=(-0.35, 0.35, -0.15, 0.15),
                                     borderWidth=(0.02, 0.02),
                                     scale=0.42,
                                     pos=(0.84, 0,
                                          0.5 - 0.28 * self.numPlayers + 0.05),
                                     barColor=(0, 0.7, 0, 1))
        self.goalBar.setBin('unsorted', 0)
        self.goalBar.hide()
        self.introTrack = self.getIntroTrack()
        self.introTrack.start()
        return

    def offstage(self):
        self.notify.debug('offstage')
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        del self.introTrack
        for avId in list(self.toonHitTracks.keys()):
            track = self.toonHitTracks[avId]
            if track.isPlaying():
                track.finish()

        self.__killCameraTask()
        camera.wrtReparentTo(render)
        self.camParent.removeNode()
        del self.camParent
        for panel in self.scorePanels:
            panel.cleanup()

        self.scorePanels = []
        self.goalBar.destroy()
        del self.goalBar
        base.setCellsAvailable(base.rightCells, 1)
        for suit in self.suits:
            suit.offstage()

        self.__unloadSuits()
        for treasure in self.treasures:
            treasure.destroy()

        del self.treasures
        del self.sndTable
        del self.grabSounds
        del self.toonRNGs
        self.maze.offstage()
        base.localAvatar.showName()
        DistributedMinigame.offstage(self)

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if self.numPlayers == 1:
            toon.setPos(0, 0, self.TOON_Z)
            toon.setHpr(180, 0, 0)
        else:
            posIndex = self.avIdList.index(avId)
            toon.setPos(self.startPosHTable[posIndex][0])
            toon.setHpr(self.startPosHTable[posIndex][1], 0, 0)

    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.setAnimState('Happy', 1.0)
                toon.startSmooth()
                toon.startLookAround()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.stopLookAround()

        self.gameFSM.request('play')

    def handleDisabledAvatar(self, avId):
        hitTrack = self.toonHitTracks[avId]
        if hitTrack.isPlaying():
            hitTrack.finish()
        DistributedMinigame.handleDisabledAvatar(self, avId)

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

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        for i in range(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = MinigameAvatarScorePanel.MinigameAvatarScorePanel(
                avId, avName)
            scorePanel.setPos(1.12, 0.0, 0.5 - 0.28 * i)
            self.scorePanels.append(scorePanel)

        self.goalBar.show()
        self.goalBar['value'] = 0.0
        base.setCellsAvailable(base.rightCells, 0)
        self.__spawnUpdateSuitsTask()
        orthoDrive = OrthoDrive(
            self.TOON_SPEED,
            maxFrameMove=self.MAX_FRAME_MOVE,
            customCollisionCallback=self.__doMazeCollisions,
            priority=1)
        self.orthoWalk = OrthoWalk(orthoDrive,
                                   broadcast=not self.isSinglePlayer())
        self.orthoWalk.start()
        self.accept(MazeSuit.COLLISION_EVENT_NAME, self.__hitBySuit)
        self.accept(self.TREASURE_GRAB_EVENT_NAME, self.__treasureGrabbed)
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(MazeGameGlobals.GAME_DURATION)
        self.timer.countdown(MazeGameGlobals.GAME_DURATION, self.timerExpired)
        self.accept('resetClock', self.__resetClock)
        base.playMusic(self.music, looping=0, volume=0.8)

    def exitPlay(self):
        self.notify.debug('exitPlay')
        self.ignore('resetClock')
        self.ignore(MazeSuit.COLLISION_EVENT_NAME)
        self.ignore(self.TREASURE_GRAB_EVENT_NAME)
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        self.__killUpdateSuitsTask()
        self.timer.stop()
        self.timer.destroy()
        del self.timer
        for avId in self.avIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.loop('neutral')

    def __resetClock(self, tOffset):
        self.notify.debug('resetClock')
        self.gameStartTime += tOffset
        self.timer.countdown(self.timer.currentTime + tOffset,
                             self.timerExpired)

    def __treasureGrabbed(self, treasureNum):
        self.treasures[treasureNum].showGrab()
        self.grabSounds[self.grabSoundIndex].play()
        self.grabSoundIndex = (self.grabSoundIndex + 1) % len(self.grabSounds)
        self.sendUpdate('claimTreasure', [treasureNum])

    def setTreasureGrabbed(self, avId, treasureNum):
        if not self.hasLocalToon:
            return
        if avId != self.localAvId:
            self.treasures[treasureNum].showGrab()
        i = self.avIdList.index(avId)
        self.scores[i] += 1
        self.scorePanels[i].setScore(self.scores[i])
        total = 0
        for score in self.scores:
            total += score

        self.goalBar['value'] = 100.0 * (float(total) /
                                         float(self.maze.numTreasures))

    def __hitBySuit(self, suitNum):
        self.notify.debug('hitBySuit')
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime())
        self.sendUpdate('hitBySuit', [self.localAvId, timestamp])
        self.__showToonHitBySuit(self.localAvId, timestamp)

    def hitBySuit(self, avId, timestamp):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in [
                'play', 'showScores'
        ]:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        self.notify.debug('avatar ' + repr(avId) + ' hit by a suit')
        if avId != self.localAvId:
            self.__showToonHitBySuit(avId, timestamp)

    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-' + repr(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)
        flyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        while 1:
            endTile = [
                rng.randint(2, self.maze.width - 1),
                rng.randint(2, self.maze.height - 1)
            ]
            if self.maze.isWalkable(endTile[0], endTile[1]):
                break
        endWorldCoords = self.maze.tile2world(endTile[0], endTile[1])
        endPos = Point3(endWorldCoords[0], endWorldCoords[1], startPos[2])

        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'))
        if avId != self.localAvId:
            cameraTrack = Sequence()
        else:
            self.camParent.reparentTo(parentNode)
            startCamPos = camera.getPos()
            destCamPos = camera.getPos()
            zenith = trajectory.getPos(flyDur / 2.0)[2]
            destCamPos.setZ(zenith * 1.3)
            destCamPos.setY(destCamPos[1] * 0.3)

            def camTask(task,
                        zenith=zenith,
                        flyNode=toon,
                        startCamPos=startCamPos,
                        camOffset=destCamPos - startCamPos):
                u = flyNode.getZ() / zenith
                camera.setPos(startCamPos + camOffset * u)
                camera.lookAt(toon)
                return Task.cont

            camTaskName = 'mazeToonFlyCam-' + repr(avId)
            taskMgr.add(camTask, camTaskName, priority=20)

            def cleanupCamTask(self=self,
                               toon=toon,
                               camTaskName=camTaskName,
                               startCamPos=startCamPos):
                taskMgr.remove(camTaskName)
                self.camParent.reparentTo(toon)
                camera.setPos(startCamPos)
                camera.lookAt(toon)

            cameraTrack = Sequence(Wait(flyDur),
                                   Func(cleanupCamTask),
                                   name='hitBySuit-cameraLerp')

        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.orthoWalk.stop()
            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, 'orthoWalk'):
                    if self.gameFSM.getCurrentState().getName() == 'play':
                        self.orthoWalk.start()
            dropShadow.removeNode()
            del dropShadow
            toon.dropShadow.show()
            geomNode = toon.getGeomNode()
            rotNode = geomNode.getParent()
            baseNode = rotNode.getParent()
            geomNode.reparentTo(baseNode)
            rotNode.removeNode()
            del rotNode
            geomNode.setZ(oldGeomNodeZ)
            toon.reparentTo(render)
            toon.setPos(endPos)
            parentNode.removeNode()
            del parentNode
            if avId != self.localAvId:
                toon.startSmooth()

        preFunc()

        hitTrack = Sequence(Parallel(flyTrack, cameraTrack, spinHTrack,
                                     spinPTrack, soundTrack),
                            Func(postFunc),
                            name=toon.uniqueName('hitBySuit'))

        self.toonHitTracks[avId] = hitTrack

        hitTrack.start(globalClockDelta.localElapsedTime(timestamp))

    def allTreasuresTaken(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('all treasures taken')
        if not MazeGameGlobals.ENDLESS_GAME:
            self.gameFSM.request('showScores')

    def timerExpired(self):
        self.notify.debug('local timer expired')
        if not MazeGameGlobals.ENDLESS_GAME:
            self.gameFSM.request('showScores')

    def __doMazeCollisions(self, oldPos, newPos):
        offset = newPos - oldPos
        WALL_OFFSET = 1.0
        curX = oldPos[0]
        curY = oldPos[1]
        curTX, curTY = self.maze.world2tile(curX, curY)

        def calcFlushCoord(curTile, newTile, centerTile):
            EPSILON = 0.01
            if newTile > curTile:
                return (newTile -
                        centerTile) * self.CELL_WIDTH - EPSILON - WALL_OFFSET
            else:
                return (curTile - centerTile) * self.CELL_WIDTH + WALL_OFFSET

        offsetX = offset[0]
        offsetY = offset[1]
        WALL_OFFSET_X = WALL_OFFSET
        if offsetX < 0:
            WALL_OFFSET_X = -WALL_OFFSET_X
        WALL_OFFSET_Y = WALL_OFFSET
        if offsetY < 0:
            WALL_OFFSET_Y = -WALL_OFFSET_Y
        newX = curX + offsetX + WALL_OFFSET_X
        newY = curY
        newTX, newTY = self.maze.world2tile(newX, newY)
        if newTX != curTX:
            if self.maze.collisionTable[newTY][newTX]:
                offset.setX(
                    calcFlushCoord(curTX, newTX, self.maze.originTX) - curX)
        newX = curX
        newY = curY + offsetY + WALL_OFFSET_Y
        newTX, newTY = self.maze.world2tile(newX, newY)
        if newTY != curTY:
            if self.maze.collisionTable[newTY][newTX]:
                offset.setY(
                    calcFlushCoord(curTY, newTY, self.maze.originTY) - curY)
        offsetX = offset[0]
        offsetY = offset[1]
        newX = curX + offsetX + WALL_OFFSET_X
        newY = curY + offsetY + WALL_OFFSET_Y
        newTX, newTY = self.maze.world2tile(newX, newY)
        if self.maze.collisionTable[newTY][newTX]:
            cX = calcFlushCoord(curTX, newTX, self.maze.originTX)
            cY = calcFlushCoord(curTY, newTY, self.maze.originTY)
            if abs(cX - curX) < abs(cY - curY):
                offset.setX(cX - curX)
            else:
                offset.setY(cY - curY)
        return oldPos + offset

    def __spawnCameraTask(self):
        self.notify.debug('spawnCameraTask')
        camera.lookAt(base.localAvatar)
        taskMgr.remove(self.CAMERA_TASK)
        taskMgr.add(self.__cameraTask, self.CAMERA_TASK, priority=45)

    def __killCameraTask(self):
        self.notify.debug('killCameraTask')
        taskMgr.remove(self.CAMERA_TASK)

    def __cameraTask(self, task):
        self.camParent.setHpr(render, 0, 0, 0)
        return Task.cont

    def __loadSuits(self):
        self.notify.debug('loadSuits')
        self.suits = []
        self.numSuits = 4 * self.numPlayers
        safeZone = self.getSafezoneId()
        slowerTable = self.slowerSuitPeriods
        if self.SLOWER_SUIT_CURVE:
            slowerTable = self.slowerSuitPeriodsCurve
        slowerPeriods = slowerTable[safeZone][self.numSuits]
        fasterTable = self.fasterSuitPeriods
        if self.FASTER_SUIT_CURVE:
            fasterTable = self.fasterSuitPeriodsCurve
        fasterPeriods = fasterTable[safeZone][self.numSuits]
        suitPeriods = slowerPeriods + fasterPeriods
        self.notify.debug('suit periods: ' + repr(suitPeriods))
        self.randomNumGen.shuffle(suitPeriods)
        for i in range(self.numSuits):
            self.suits.append(
                MazeSuit(i, self.maze, self.randomNumGen, suitPeriods[i],
                         self.getDifficulty()))

    def __unloadSuits(self):
        self.notify.debug('unloadSuits')
        for suit in self.suits:
            suit.destroy()

        del self.suits

    def __spawnUpdateSuitsTask(self):
        self.notify.debug('spawnUpdateSuitsTask')
        for suit in self.suits:
            suit.gameStart(self.gameStartTime)

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

    def __killUpdateSuitsTask(self):
        self.notify.debug('killUpdateSuitsTask')
        taskMgr.remove(self.UPDATE_SUITS_TASK)
        for suit in self.suits:
            suit.gameEnd()

    def __updateSuitsTask(self, task):
        curT = globalClock.getFrameTime() - self.gameStartTime
        curTic = int(curT * float(MazeGameGlobals.SUIT_TIC_FREQ))
        suitUpdates = []
        for i in range(len(self.suits)):
            updateTics = self.suits[i].getThinkTimestampTics(curTic)
            suitUpdates.extend(list(zip(updateTics, [i] * len(updateTics))))

        suitUpdates.sort(key=functools.cmp_to_key(lambda a, b: a[0] - b[0]))
        if len(suitUpdates) > 0:
            curTic = 0
            for i in range(len(suitUpdates)):
                update = suitUpdates[i]
                tic = update[0]
                suitIndex = update[1]
                suit = self.suits[suitIndex]
                if tic > curTic:
                    curTic = tic
                    j = i + 1
                    while j < len(suitUpdates):
                        if suitUpdates[j][0] > tic:
                            break
                        self.suits[suitUpdates[j][1]].prepareToThink()
                        j += 1

                unwalkables = []
                for si in range(suitIndex):
                    unwalkables.extend(self.suits[si].occupiedTiles)

                for si in range(suitIndex + 1, len(self.suits)):
                    unwalkables.extend(self.suits[si].occupiedTiles)

                suit.think(curTic, curT, unwalkables)

        return Task.cont

    def enterShowScores(self):
        self.notify.debug('enterShowScores')
        lerpTrack = Parallel()
        lerpDur = 0.5
        lerpTrack.append(
            Parallel(
                LerpPosInterval(self.goalBar,
                                lerpDur,
                                Point3(0, 0, -.6),
                                blendType='easeInOut'),
                LerpScaleInterval(self.goalBar,
                                  lerpDur,
                                  Vec3(self.goalBar.getScale()) * 2.0,
                                  blendType='easeInOut')))
        tY = 0.6
        bY = -.05
        lX = -.5
        cX = 0
        rX = 0.5
        scorePanelLocs = (((cX, bY), ), ((lX, bY), (rX, bY)),
                          ((cX, tY), (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()) * 2.0,
                                      blendType='easeInOut')))

        self.showScoreTrack = Parallel(
            lerpTrack,
            Sequence(Wait(MazeGameGlobals.SHOWSCORES_DURATION),
                     Func(self.gameOver)))
        self.showScoreTrack.start()

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

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

    def exitCleanup(self):
        pass

    def getIntroTrack(self):
        self.__cameraTask(None)
        origCamParent = camera.getParent()
        origCamPos = camera.getPos()
        origCamHpr = camera.getHpr()
        iCamParent = base.localAvatar.attachNewNode('iCamParent')
        iCamParent.setH(180)
        camera.reparentTo(iCamParent)
        toonHeight = base.localAvatar.getHeight()
        camera.setPos(0, -15, toonHeight * 3)
        camera.lookAt(0, 0, toonHeight / 2.0)
        iCamParent.wrtReparentTo(origCamParent)
        waitDur = 5.0
        lerpDur = 4.5
        lerpTrack = Parallel()
        startHpr = iCamParent.getHpr()
        startHpr.setX(PythonUtil.reduceAngle(startHpr[0]))
        lerpTrack.append(
            LerpPosHprInterval(iCamParent,
                               lerpDur,
                               pos=Point3(0, 0, 0),
                               hpr=Point3(0, 0, 0),
                               startHpr=startHpr,
                               name=self.uniqueName('introLerpParent')))
        lerpTrack.append(
            LerpPosHprInterval(camera,
                               lerpDur,
                               pos=origCamPos,
                               hpr=origCamHpr,
                               blendType='easeInOut',
                               name=self.uniqueName('introLerpCameraPos')))
        base.localAvatar.startLookAround()

        def cleanup(origCamParent=origCamParent,
                    origCamPos=origCamPos,
                    origCamHpr=origCamHpr,
                    iCamParent=iCamParent):
            camera.reparentTo(origCamParent)
            camera.setPos(origCamPos)
            camera.setHpr(origCamHpr)
            iCamParent.removeNode()
            del iCamParent
            base.localAvatar.stopLookAround()

        return Sequence(Wait(waitDur), lerpTrack, Func(cleanup))
Пример #2
0
class DistributedPartyTugOfWarActivity(DistributedPartyTeamActivity):
    notify = directNotify.newCategory('DistributedPartyTugOfWarActivity')

    def __init__(self, cr):
        DistributedPartyTeamActivity.__init__(self, cr, PartyGlobals.ActivityIds.PartyTugOfWar, startDelay=PartyGlobals.TugOfWarStartDelay)
        self.buttons = [0, 1]
        self.arrowKeys = None
        self.keyTTL = []
        self.idealRate = 0.0
        self.keyRate = 0
        self.allOutMode = False
        self.rateMatchAward = 0.0
        self.toonIdsToStartPositions = {}
        self.toonIdsToIsPullingFlags = {}
        self.toonIdsToRightHands = {}
        self.fallenToons = []
        self.fallenPositions = []
        self.unusedFallenPositionsIndices = [0,
         1,
         2,
         3]
        self.toonIdsToAnimIntervals = {}
        self.tugRopes = []
        return

    def generate(self):
        DistributedPartyTeamActivity.generate(self)
        self._hopOffFinishedSV = StateVar(True)
        self._rewardFinishedSV = StateVar(True)
        self._isWalkStateReadyFC = FunctionCall(self._testWalkStateReady, self._hopOffFinishedSV, self._rewardFinishedSV)

    def delete(self):
        self._isWalkStateReadyFC.destroy()
        self._hopOffFinishedSV.destroy()
        self._rewardFinishedSV.destroy()
        DistributedPartyTeamActivity.delete(self)

    def handleToonJoined(self, toonId):
        DistributedPartyTeamActivity.handleToonJoined(self, toonId)
        self.toonIdsToAnimIntervals[toonId] = None
        if toonId == base.localAvatar.doId:
            base.cr.playGame.getPlace().fsm.request('activity')
            camera.wrtReparentTo(self.root)
            self.cameraMoveIval = LerpPosHprInterval(camera, 1.5, PartyGlobals.TugOfWarCameraPos, PartyGlobals.TugOfWarCameraInitialHpr, other=self.root)
            self.cameraMoveIval.start()
            self.localToonPosIndex = self.getIndex(base.localAvatar.doId, self.localToonTeam)
            self.notify.debug('posIndex: %d' % self.localToonPosIndex)
            toon = self.getAvatar(toonId)
            targetPos = self.dockPositions[self.localToonTeam][self.localToonPosIndex]
            if toon.getZ(self.root) < PartyGlobals.TugOfWarToonPositionZ:
                toon.setZ(self.root, PartyGlobals.TugOfWarToonPositionZ)
            targetH = fitDestAngle2Src(toon.getH(self.root), PartyGlobals.TugOfWarHeadings[self.localToonTeam])
            travelVector = targetPos - toon.getPos(self.root)
            duration = travelVector.length() / 5.0
            if self.toonIdsToAnimIntervals[toonId] is not None:
                self.toonIdsToAnimIntervals[toonId].finish()
            self.toonIdsToAnimIntervals[toonId] = Sequence(Func(toon.startPosHprBroadcast, 0.1), Func(toon.b_setAnimState, 'run'), LerpPosHprInterval(toon, duration, targetPos, VBase3(targetH, 0.0, 0.0), other=self.root), Func(toon.stopPosHprBroadcast), Func(toon.b_setAnimState, 'neutral'))
            self.toonIdsToAnimIntervals[toonId].start()
        return

    def handleToonExited(self, toonId):
        DistributedPartyTeamActivity.handleToonExited(self, toonId)
        if toonId == base.localAvatar.doId:
            self.cameraMoveIval.pause()
            if toonId not in self.fallenToons:
                if toonId in self.toonIdsToAnimIntervals and self.toonIdsToAnimIntervals[toonId] is not None:
                    self.toonIdsToAnimIntervals[toonId].finish()
                toon = self.getAvatar(toonId)
                targetH = fitDestAngle2Src(toon.getH(self.root), 180.0)
                targetPos = self.hopOffPositions[self.getTeam(toonId)][self.getIndex(toonId, self.getTeam(toonId))]
                hopOffAnim = Sequence(Func(toon.startPosHprBroadcast, 0.1), toon.hprInterval(0.2, VBase3(targetH, 0.0, 0.0), other=self.root), Func(toon.b_setAnimState, 'jump', 1.0), Wait(0.4), PartyUtils.arcPosInterval(0.75, toon, targetPos, 5.0, self.root), Func(toon.stopPosHprBroadcast), Func(toon.sendCurrentPosition), Func(self.hopOffFinished, toonId))
                self.toonIdsToAnimIntervals[toonId] = hopOffAnim
                self._hopOffFinishedSV.set(False)
                self.toonIdsToAnimIntervals[toonId].start()
            else:
                self._hopOffFinishedSV.set(True)
                del self.toonIdsToAnimIntervals[toonId]
        return

    def handleRewardDone(self):
        self._rewardFinishedSV.set(True)

    def _testWalkStateReady(self, hoppedOff, rewardFinished):
        if hoppedOff and rewardFinished:
            DistributedPartyTeamActivity.handleRewardDone(self)

    def hopOffFinished(self, toonId):
        if hasattr(self, 'toonIdsToAnimIntervals') and toonId in self.toonIdsToAnimIntervals:
            del self.toonIdsToAnimIntervals[toonId]
        if toonId == base.localAvatar.doId:
            if hasattr(self._hopOffFinishedSV, '_value'):
                self._hopOffFinishedSV.set(True)

    def handleToonShifted(self, toonId):
        if toonId == base.localAvatar.doId:
            self.localToonPosIndex = self.getIndex(base.localAvatar.doId, self.localToonTeam)
            if self.toonIdsToAnimIntervals[toonId] is not None:
                self.toonIdsToAnimIntervals[toonId].finish()
            toon = self.getAvatar(toonId)
            targetPos = self.dockPositions[self.localToonTeam][self.localToonPosIndex]
            self.toonIdsToAnimIntervals[toonId] = Sequence(Wait(0.6), Func(toon.startPosHprBroadcast, 0.1), Func(toon.b_setAnimState, 'run'), toon.posInterval(0.5, targetPos, other=self.root), Func(toon.stopPosHprBroadcast), Func(toon.b_setAnimState, 'neutral'))
            self.toonIdsToAnimIntervals[toonId].start()
        return

    def handleToonDisabled(self, toonId):
        if toonId in self.toonIdsToAnimIntervals:
            if self.toonIdsToAnimIntervals[toonId]:
                if self.toonIdsToAnimIntervals[toonId].isPlaying():
                    self.toonIdsToAnimIntervals[toonId].finish()
            else:
                self.notify.debug('self.toonIdsToAnimIntervals[%d] is none' % toonId)

    def setToonsPlaying(self, leftTeamToonIds, rightTeamToonIds):
        DistributedPartyTeamActivity.setToonsPlaying(self, leftTeamToonIds, rightTeamToonIds)
        self.toonIdsToRightHands.clear()
        for toonId in self.getToonIdsAsList():
            toon = self.getAvatar(toonId)
            if toon:
                self.toonIdsToRightHands[toonId] = toon.getRightHands()[0]

    def load(self):
        DistributedPartyTeamActivity.load(self)
        self.loadModels()
        self.loadGuiElements()
        self.loadSounds()
        self.loadIntervals()
        self.arrowKeys = ArrowKeys()

    def loadModels(self):
        self.playArea = loader.loadModel('phase_13/models/parties/partyTugOfWar')
        self.playArea.reparentTo(self.root)
        self.sign.reparentTo(self.playArea.find('**/TugOfWar_sign_locator'))
        self.dockPositions = [[], []]
        for i in range(4):
            self.dockPositions[0].append(Point3(-PartyGlobals.TugOfWarInitialToonPositionsXOffset - PartyGlobals.TugOfWarToonPositionXSeparation * i, 0.0, PartyGlobals.TugOfWarToonPositionZ))

        for i in range(4):
            self.dockPositions[1].append(Point3(PartyGlobals.TugOfWarInitialToonPositionsXOffset + PartyGlobals.TugOfWarToonPositionXSeparation * i, 0.0, PartyGlobals.TugOfWarToonPositionZ))

        self.hopOffPositions = [[], []]
        for i in range(1, 5):
            self.hopOffPositions[PartyGlobals.TeamActivityTeams.LeftTeam].append(self.playArea.find('**/leftTeamHopOff%d_locator' % i).getPos())
            self.hopOffPositions[PartyGlobals.TeamActivityTeams.RightTeam].append(self.playArea.find('**/rightTeamHopOff%d_locator' % i).getPos())

        for i in range(1, 5):
            pos = self.playArea.find('**/fallenToon%d_locator' % i).getPos()
            self.fallenPositions.append(pos)

        self.joinCollision = []
        self.joinCollisionNodePaths = []
        for i in range(len(PartyGlobals.TeamActivityTeams)):
            collShape = CollisionTube(PartyGlobals.TugOfWarJoinCollisionEndPoints[0], PartyGlobals.TugOfWarJoinCollisionEndPoints[1], PartyGlobals.TugOfWarJoinCollisionRadius)
            collShape.setTangible(True)
            self.joinCollision.append(CollisionNode('TugOfWarJoinCollision%d' % i))
            self.joinCollision[i].addSolid(collShape)
            tubeNp = self.playArea.attachNewNode(self.joinCollision[i])
            tubeNp.node().setCollideMask(ToontownGlobals.WallBitmask)
            self.joinCollisionNodePaths.append(tubeNp)
            self.joinCollisionNodePaths[i].setPos(PartyGlobals.TugOfWarJoinCollisionPositions[i])

        self.__enableCollisions()
        ropeModel = loader.loadModel('phase_4/models/minigames/tug_of_war_rope')
        self.ropeTexture = ropeModel.findTexture('*')
        ropeModel.removeNode()
        for i in range(PartyGlobals.TugOfWarMaximumPlayersPerTeam * 2 - 1):
            rope = Rope(self.uniqueName('TugRope%d' % i))
            if rope.showRope:
                rope.ropeNode.setRenderMode(RopeNode.RMBillboard)
                rope.ropeNode.setThickness(0.2)
                rope.setTexture(self.ropeTexture)
                rope.ropeNode.setUvMode(RopeNode.UVDistance)
                rope.ropeNode.setUvDirection(1)
                rope.setTransparency(1)
                rope.setColor(0.89, 0.89, 0.6, 1.0)
                rope.reparentTo(self.root)
                rope.stash()
            self.tugRopes.append(rope)

        self.splash = Splash.Splash(self.root)
        self.splash.setScale(2.0, 4.0, 1.0)
        pos = self.fallenPositions[0]
        self.splash.setPos(pos[0], pos[1], PartyGlobals.TugOfWarSplashZOffset)
        self.splash.hide()

    def loadGuiElements(self):
        self.powerMeter = MinigamePowerMeter(PartyGlobals.TugOfWarPowerMeterSize)
        self.powerMeter.reparentTo(aspect2d)
        self.powerMeter.setPos(0.0, 0.0, 0.6)
        self.powerMeter.hide()
        self.arrows = [None] * 2
        for x in range(len(self.arrows)):
            self.arrows[x] = loader.loadModel('phase_3/models/props/arrow')
            self.arrows[x].reparentTo(self.powerMeter)
            self.arrows[x].setScale(0.2 - 0.4 * x, 0.2, 0.2)
            self.arrows[x].setPos(0.12 - 0.24 * x, 0, -.26)

        return

    def loadSounds(self):
        self.splashSound = base.loader.loadSfx('phase_4/audio/sfx/MG_cannon_splash.ogg')
        self.whistleSound = base.loader.loadSfx('phase_4/audio/sfx/AA_sound_whistle.ogg')

    def loadIntervals(self):
        self.updateIdealRateInterval = Sequence()
        self.updateIdealRateInterval.append(Wait(PartyGlobals.TugOfWarTargetRateList[0][0]))
        for i in range(1, len(PartyGlobals.TugOfWarTargetRateList)):
            duration = PartyGlobals.TugOfWarTargetRateList[i][0]
            idealRate = PartyGlobals.TugOfWarTargetRateList[i][1]
            self.updateIdealRateInterval.append(Func(self.setIdealRate, idealRate))
            if i == len(PartyGlobals.TugOfWarTargetRateList) - 1:
                self.updateIdealRateInterval.append(Func(setattr, self, 'allOutMode', True))
            else:
                self.updateIdealRateInterval.append(Wait(duration))

        self.updateKeyPressRateInterval = Sequence(Wait(PartyGlobals.TugOfWarKeyPressUpdateRate), Func(self.updateKeyPressRate))
        self.reportToServerInterval = Sequence(Wait(PartyGlobals.TugOfWarKeyPressReportRate), Func(self.reportToServer))
        self.setupInterval = Parallel()
        self.globalSetupInterval = Sequence(Wait(PartyGlobals.TugOfWarReadyDuration + PartyGlobals.TugOfWarGoDuration), Func(self.tightenRopes))
        self.localSetupInterval = Sequence(Func(self.setStatus, TTLocalizer.PartyTugOfWarReady), Func(self.showStatus), Wait(PartyGlobals.TugOfWarReadyDuration), Func(base.playSfx, self.whistleSound), Func(self.setStatus, TTLocalizer.PartyTugOfWarGo), Wait(PartyGlobals.TugOfWarGoDuration), Func(self.enableKeys), Func(self.hideStatus), Func(self.updateIdealRateInterval.start), Func(self.updateKeyPressRateInterval.loop), Func(self.reportToServerInterval.loop))
        self.splashInterval = Sequence(Func(base.playSfx, self.splashSound), Func(self.splash.play))

    def unload(self):
        DistributedPartyTeamActivity.unload(self)
        self.arrowKeys.destroy()
        self.unloadIntervals()
        self.unloadModels()
        self.unloadGuiElements()
        self.unloadSounds()
        if hasattr(self, 'toonIds'):
            del self.toonIds
        del self.buttons
        del self.arrowKeys
        del self.keyTTL
        del self.idealRate
        del self.keyRate
        del self.allOutMode
        del self.rateMatchAward
        del self.toonIdsToStartPositions
        del self.toonIdsToIsPullingFlags
        del self.toonIdsToRightHands
        del self.fallenToons
        del self.fallenPositions
        del self.unusedFallenPositionsIndices
        self.toonIdsToAnimIntervals.clear()
        del self.toonIdsToAnimIntervals

    def unloadModels(self):
        self.playArea.removeNode()
        del self.playArea
        del self.dockPositions
        del self.hopOffPositions
        self.__disableCollisions()
        while len(self.joinCollision) > 0:
            collNode = self.joinCollision.pop()
            del collNode

        while len(self.joinCollisionNodePaths) > 0:
            collNodePath = self.joinCollisionNodePaths.pop()
            collNodePath.removeNode()
            del collNodePath

        while len(self.tugRopes) > 0:
            rope = self.tugRopes.pop()
            if rope is not None:
                rope.removeNode()
            del rope

        del self.tugRopes
        self.splash.destroy()
        del self.splash
        return

    def unloadGuiElements(self):
        for arrow in self.arrows:
            if arrow is not None:
                arrow.removeNode()
                del arrow

        del self.arrows
        if self.powerMeter is not None:
            self.powerMeter.cleanup()
            del self.powerMeter
        return

    def unloadSounds(self):
        del self.splashSound
        del self.whistleSound

    def unloadIntervals(self):
        self.updateIdealRateInterval.pause()
        del self.updateIdealRateInterval
        self.updateKeyPressRateInterval.pause()
        del self.updateKeyPressRateInterval
        self.reportToServerInterval.pause()
        del self.reportToServerInterval
        self.setupInterval.pause()
        del self.setupInterval
        self.globalSetupInterval.pause()
        del self.globalSetupInterval
        self.localSetupInterval.pause()
        del self.localSetupInterval
        self.splashInterval.pause()
        del self.splashInterval

    def __enableCollisions(self):
        for i in range(len(PartyGlobals.TeamActivityTeams)):
            self.accept('enterTugOfWarJoinCollision%d' % i, getattr(self, '_join%s' % PartyGlobals.TeamActivityTeams.getString(i)))

    def __disableCollisions(self):
        for i in range(len(PartyGlobals.TeamActivityTeams)):
            self.ignore('enterTugOfWarJoinCollision%d' % i)

    def startWaitForEnough(self):
        DistributedPartyTeamActivity.startWaitForEnough(self)
        self.__enableCollisions()

    def finishWaitForEnough(self):
        DistributedPartyTeamActivity.finishWaitForEnough(self)
        self.__disableCollisions()

    def startWaitToStart(self, waitStartTimestamp):
        DistributedPartyTeamActivity.startWaitToStart(self, waitStartTimestamp)
        self.__enableCollisions()

    def finishWaitToStart(self):
        DistributedPartyTeamActivity.finishWaitToStart(self)
        self.__disableCollisions()

    def startRules(self):
        DistributedPartyTeamActivity.startRules(self)
        self.setUpRopes()
        if self.isLocalToonPlaying:
            self.showControls()

    def finishRules(self):
        DistributedPartyTeamActivity.finishRules(self)
        if self.activityFSM.getCurrentOrNextState() == 'WaitForEnough':
            self.hideRopes()
            self.hideControls()

    def finishWaitForServer(self):
        DistributedPartyTeamActivity.finishWaitForServer(self)
        if self.activityFSM.getCurrentOrNextState() == 'WaitForEnough':
            self.hideRopes()
            self.hideControls()

    def startActive(self):
        DistributedPartyTeamActivity.startActive(self)
        self.toonIdsToStartPositions.clear()
        self.toonIdsToIsPullingFlags.clear()
        for toonId in self.getToonIdsAsList():
            self.toonIdsToIsPullingFlags[toonId] = False
            toon = self.getAvatar(toonId)
            if toon:
                self.toonIdsToStartPositions[toonId] = toon.getPos(self.root)
            else:
                self.notify.warning("couldn't find toon %d assigning 0,0,0 to startPos" % toonId)
                self.toonIdsToStartPositions[toonId] = Point3(0, 0, 0)

        self.unusedFallenPositionsIndices = [0,
         1,
         2,
         3]
        self.setupInterval = Parallel(self.globalSetupInterval)
        if self.isLocalToonPlaying:
            self.keyTTL = []
            self.idealForce = 0.0
            self.keyRate = 0
            self.rateMatchAward = 0.0
            self.allOutMode = False
            self.setIdealRate(PartyGlobals.TugOfWarTargetRateList[0][1])
            self.setupInterval.append(self.localSetupInterval)
        self.setupInterval.start()

    def finishActive(self):
        DistributedPartyTeamActivity.finishActive(self)
        self.hideControls()
        self.disableKeys()
        self.setupInterval.pause()
        self.reportToServerInterval.pause()
        self.updateKeyPressRateInterval.pause()
        self.updateIdealRateInterval.pause()
        self.hideRopes()

    def startConclusion(self, losingTeam):
        DistributedPartyTeamActivity.startConclusion(self, losingTeam)
        if self.isLocalToonPlaying:
            self._rewardFinishedSV.set(False)
            if losingTeam == PartyGlobals.TeamActivityNeitherTeam:
                self.setStatus(TTLocalizer.PartyTeamActivityGameTie)
            else:
                self.setStatus(TTLocalizer.PartyTugOfWarGameEnd)
            self.showStatus()
        if losingTeam == PartyGlobals.TeamActivityNeitherTeam:
            for toonId in self.getToonIdsAsList():
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop('neutral')

        else:
            for toonId in self.toonIds[losingTeam]:
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop('neutral')

            for toonId in self.toonIds[1 - losingTeam]:
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop('victory')

        for ival in list(self.toonIdsToAnimIntervals.values()):
            if ival is not None:
                ival.finish()

        return

    def finishConclusion(self):
        DistributedPartyTeamActivity.finishConclusion(self)
        self.fallenToons = []

    def getTitle(self):
        return TTLocalizer.PartyTugOfWarTitle

    def getInstructions(self):
        return TTLocalizer.TugOfWarInstructions

    def showControls(self):
        for arrow in self.arrows:
            arrow.setColor(PartyGlobals.TugOfWarDisabledArrowColor)

        self.powerMeter.setTarget(PartyGlobals.TugOfWarTargetRateList[0][1])
        self.powerMeter.setPower(PartyGlobals.TugOfWarTargetRateList[0][1])
        self.powerMeter.setBarColor((0.0, 1.0, 0.0, 0.5))
        self.powerMeter.clearTooSlowTooFast()
        self.powerMeter.show()

    def hideControls(self):
        self.powerMeter.hide()

    def setUpRopes(self):
        self.notify.debug('setUpRopes')
        ropeIndex = 0
        leftToonId = -1
        if self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam]:
            leftToonId = self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam][0]
        rightToonId = -1
        if self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam]:
            rightToonId = self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam][0]
        if leftToonId in self.toonIdsToRightHands and rightToonId in self.toonIdsToRightHands:
            self.tugRopes[ropeIndex].setup(3, ((self.toonIdsToRightHands[leftToonId], (0, 0, 0)), (self.root, (0.0, 0.0, 2.5)), (self.toonIdsToRightHands[rightToonId], (0, 0, 0))), [0,
             0,
             0,
             1,
             1,
             1])
            self.tugRopes[ropeIndex].unstash()
            ropeIndex += 1
        teams = [PartyGlobals.TeamActivityTeams.LeftTeam, PartyGlobals.TeamActivityTeams.RightTeam]
        for currTeam in teams:
            numToons = len(self.toonIds[currTeam])
            if numToons > 1:
                for i in range(numToons - 1, 0, -1):
                    toon1 = self.toonIds[currTeam][i]
                    toon2 = self.toonIds[currTeam][i - 1]
                    if toon1 not in self.toonIdsToRightHands:
                        self.notify.warning('Toon in tug of war activity but not properly setup:  %s' % toon1)
                    elif toon2 not in self.toonIdsToRightHands:
                        self.notify.warning('Toon in tug of war activity but not properly setup:  %s' % toon2)
                    else:
                        self.notify.debug('Connecting rope between toon %d and toon %d of team %d.' % (i, i - 1, currTeam))
                        self.tugRopes[ropeIndex].setup(3, ((self.toonIdsToRightHands[toon1], (0, 0, 0)), (self.toonIdsToRightHands[toon1], (0, 0, 0)), (self.toonIdsToRightHands[toon2], (0, 0, 0))), [0,
                         0,
                         0,
                         1,
                         1,
                         1])
                        self.tugRopes[ropeIndex].unstash()
                        ropeIndex += 1

    def tightenRopes(self):
        self.notify.debug('tightenRopes')
        self.tugRopes[0].setup(3, ((self.toonIdsToRightHands[self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam][0]], (0, 0, 0)), (self.toonIdsToRightHands[self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam][0]], (0, 0, 0)), (self.toonIdsToRightHands[self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam][0]], (0, 0, 0))), [0,
         0,
         0,
         1,
         1,
         1])

    def hideRopes(self):
        self.notify.debug('hideRopes')
        for rope in self.tugRopes:
            rope.stash()

    def handleGameTimerExpired(self):
        self.disableKeys()

    def setIdealRate(self, idealRate):
        self.notify.debug('setIdealRate( %d )' % idealRate)
        self.idealRate = idealRate
        self.idealForce = self.advantage * (4 + 0.4 * self.idealRate)

    def updateKeyPressRate(self):
        for i in range(len(self.keyTTL)):
            self.keyTTL[i] -= PartyGlobals.TugOfWarKeyPressUpdateRate

        for i in range(len(self.keyTTL)):
            if self.keyTTL[i] <= 0.0:
                a = self.keyTTL[0:i]
                del self.keyTTL
                self.keyTTL = a
                break

        self.keyRate = len(self.keyTTL)
        if self.keyRate == self.idealRate or self.keyRate == self.idealRate + 1:
            self.rateMatchAward += 0.3
        else:
            self.rateMatchAward = 0.0

    def reportToServer(self):
        self.currentForce = self.computeForce(self.keyRate)
        self.sendUpdate('reportKeyRateForce', [self.keyRate, self.currentForce])
        self.setSpeedGauge()
        self.setAnimState(base.localAvatar.doId, self.keyRate)

    def computeForce(self, keyRate):
        F = 0
        if self.allOutMode:
            F = 0.75 * keyRate
        else:
            stdDev = 0.25 * self.idealRate
            F = self.advantage * (self.rateMatchAward + 4 + 0.4 * self.idealRate) * math.pow(math.e, -math.pow(keyRate - self.idealRate, 2) / (2.0 * math.pow(stdDev, 2)))
        return F

    def setSpeedGauge(self):
        self.powerMeter.setPower(self.keyRate)
        self.powerMeter.setTarget(self.idealRate)
        if not self.allOutMode:
            self.powerMeter.updateTooSlowTooFast()
            index = float(self.currentForce) / self.idealForce
            bonus = 0.0
            if index > 1.0:
                bonus = max(1.0, index - 1.0)
                index = 1.0
            color = (0,
             0.75 * index + 0.25 * bonus,
             0.75 * (1 - index),
             0.5)
            self.powerMeter.setBarColor(color)
        else:
            self.powerMeter.setBarColor((0.0, 1.0, 0.0, 0.5))

    def updateToonKeyRate(self, toonId, keyRate):
        if toonId != base.localAvatar.doId:
            self.setAnimState(toonId, keyRate)

    def setAnimState(self, toonId, keyRate):
        if self.activityFSM.state != 'Active':
            return
        toon = self.getAvatar(toonId)
        if toonId not in self.toonIdsToIsPullingFlags:
            if self.getTeam(toonId) == None:
                self.notify.warning("setAnimState called with toonId (%d) that wasn't in self.toonIds" % toonId)
                return
            else:
                self.notify.warning('setAnimState called with toonId (%d) that was in self.toonIds but not in self.toonIdsToIsPullingFlags. Adding it.' % toonId)
                self.toonIdsToIsPullingFlags[toonId] = False
        if keyRate > 0 and not self.toonIdsToIsPullingFlags[toonId]:
            if toon:
                toon.loop('tug-o-war')
            else:
                self.notify.warning('toon %d is None, skipping toon.loop(tugowar)' % toonId)
            self.toonIdsToIsPullingFlags[toonId] = True
        if keyRate <= 0 and self.toonIdsToIsPullingFlags[toonId]:
            if toon:
                toon.pose('tug-o-war', 3)
                toon.startLookAround()
            else:
                self.notify.warning('toon %d is None, skipping toon.startLookAround' % toonId)
            self.toonIdsToIsPullingFlags[toonId] = False
        return

    def enableKeys(self):
        self.notify.debug('enableKeys')
        self.arrowKeys.setPressHandlers([lambda : self.__pressHandler(2),
         lambda : self.__pressHandler(3),
         lambda : self.__pressHandler(1),
         lambda : self.__pressHandler(0)])
        self.arrowKeys.setReleaseHandlers([lambda : self.__releaseHandler(2),
         lambda : self.__releaseHandler(3),
         lambda : self.__releaseHandler(1),
         lambda : self.__releaseHandler(0)])
        for arrow in self.arrows:
            arrow.setColor(PartyGlobals.TugOfWarEnabledArrowColor)

    def disableKeys(self):
        self.arrowKeys.setPressHandlers(self.arrowKeys.NULL_HANDLERS)
        self.arrowKeys.setReleaseHandlers(self.arrowKeys.NULL_HANDLERS)

    def __pressHandler(self, index):
        if index == self.buttons[0]:
            self.arrows[index].setColor(PartyGlobals.TugOfWarHilightedArrowColor)
            self.keyTTL.insert(0, PartyGlobals.TugOfWarKeyPressTimeToLive)
            self.buttons.reverse()

    def __releaseHandler(self, index):
        if index in self.buttons:
            self.arrows[index].setColor(PartyGlobals.TugOfWarEnabledArrowColor)

    def updateToonPositions(self, offset):
        if self.activityFSM.state != 'Active':
            return
        if self.isLocalToonPlaying:
            camera.lookAt(self.root, offset, 0.0, PartyGlobals.TugOfWarCameraLookAtHeightOffset)
        for toonId in self.getToonIdsAsList():
            if hasattr(self, 'fallenToons') and toonId not in self.fallenToons:
                toon = self.getAvatar(toonId)
                if toon is not None:
                    origPos = self.toonIdsToStartPositions[toonId]
                    curPos = toon.getPos(self.root)
                    newPos = Point3(origPos[0] + offset, curPos[1], curPos[2])
                    if self.toonIdsToAnimIntervals[toonId] != None:
                        if self.toonIdsToAnimIntervals[toonId].isPlaying():
                            self.toonIdsToAnimIntervals[toonId].finish()
                            self.checkIfFallen(toonId)
                    if toonId not in self.fallenToons:
                        self.toonIdsToAnimIntervals[toonId] = Sequence(LerpPosInterval(toon, duration=PartyGlobals.TugOfWarKeyPressReportRate, pos=newPos, other=self.root), Func(self.checkIfFallen, toonId))
                        self.toonIdsToAnimIntervals[toonId].start()

        return

    def checkIfFallen(self, toonId):
        if hasattr(self, 'fallenToons') and toonId not in self.fallenToons:
            toon = self.getAvatar(toonId)
            if toon:
                curPos = toon.getPos(self.root)
                team = self.getTeam(toonId)
                if team == PartyGlobals.TeamActivityTeams.LeftTeam and curPos[0] > -2.0 or team == PartyGlobals.TeamActivityTeams.RightTeam and curPos[0] < 2.0:
                    losingTeam = self.getTeam(toonId)
                    self.throwTeamInWater(losingTeam)
                    self.sendUpdate('reportFallIn', [losingTeam])

    def throwTeamInWater(self, losingTeam):
        self.notify.debug('throwTeamInWater( %s )' % PartyGlobals.TeamActivityTeams.getString(losingTeam))
        splashSet = False
        for toonId in self.toonIds[losingTeam]:
            self.fallenToons.append(toonId)
            toon = self.getAvatar(toonId)
            fallenPosIndex = self.toonIds[losingTeam].index(toonId)
            if fallenPosIndex < 0 or fallenPosIndex >= 4:
                fallenPosIndex = 0
            newPos = self.fallenPositions[fallenPosIndex]
            if toonId in self.toonIdsToAnimIntervals and self.toonIdsToAnimIntervals[toonId] is not None:
                if self.toonIdsToAnimIntervals[toonId].isPlaying():
                    self.toonIdsToAnimIntervals[toonId].finish()
            if toon:
                parallel = Parallel(ActorInterval(actor=toon, animName='slip-forward', duration=2.0), LerpPosInterval(toon, duration=2.0, pos=newPos, other=self.root))
            else:
                self.notify.warning('toon %d is none, skipping slip-forward' % toonId)
                parallel = Parallel()
            if not splashSet:
                splashSet = True
                parallel.append(self.splashInterval)
            if toon:
                self.toonIdsToAnimIntervals[toonId] = Sequence(parallel, Func(toon.loop, 'neutral'))
            else:
                self.notify.warning('toon %d is none, skipping toon.loop(neutral)' % toonId)
                self.toonIdsToAnimIntervals[toonId] = parallel
            self.toonIdsToAnimIntervals[toonId].start()

        return

    def setAdvantage(self, advantage):
        DistributedPartyTeamActivity.setAdvantage(self, advantage)
        if self.isLocalToonPlaying:
            self.setIdealRate(PartyGlobals.TugOfWarTargetRateList[0][1])
Пример #3
0
class MazeSuit(DirectObject):
    COLL_SPHERE_NAME = 'MazeSuitSphere'
    COLLISION_EVENT_NAME = 'MazeSuitCollision'
    MOVE_IVAL_NAME = 'moveMazeSuit'
    DIR_UP = 0
    DIR_DOWN = 1
    DIR_LEFT = 2
    DIR_RIGHT = 3
    oppositeDirections = [DIR_DOWN, DIR_UP, DIR_RIGHT, DIR_LEFT]
    directionHs = [0, 180, 90, 270]
    DEFAULT_SPEED = 4.0
    SUIT_Z = 0.1

    def __init__(
            self,
            serialNum,
            maze,
            randomNumGen,
            cellWalkPeriod,
            difficulty,
            suitDnaName='f',
            startTile=None,
            ticFreq=MazeGameGlobals.SUIT_TIC_FREQ,
            walkSameDirectionProb=MazeGameGlobals.WALK_SAME_DIRECTION_PROB,
            walkTurnAroundProb=MazeGameGlobals.WALK_TURN_AROUND_PROB,
            uniqueRandomNumGen=True,
            walkAnimName=None):
        self.serialNum = serialNum
        self.maze = maze
        if uniqueRandomNumGen:
            self.rng = RandomNumGen(randomNumGen)
        else:
            self.rng = randomNumGen
        self.difficulty = difficulty
        self._walkSameDirectionProb = walkSameDirectionProb
        self._walkTurnAroundProb = walkTurnAroundProb
        self._walkAnimName = walkAnimName or 'walk'
        self.suit = Suit.Suit()
        d = SuitDNA.SuitDNA()
        d.newSuit(suitDnaName)
        self.suit.setDNA(d)
        self.suit.nametag3d.stash()
        self.suit.nametag.destroy()
        if startTile is None:
            defaultStartPos = MazeGameGlobals.SUIT_START_POSITIONS[
                self.serialNum]
            self.startTile = (defaultStartPos[0] * self.maze.width,
                              defaultStartPos[1] * self.maze.height)
        else:
            self.startTile = startTile
        self.ticFreq = ticFreq
        self.ticPeriod = int(cellWalkPeriod)
        self.cellWalkDuration = float(self.ticPeriod) / float(self.ticFreq)
        self.turnDuration = 0.6 * self.cellWalkDuration
        return

    def destroy(self):
        self.suit.delete()

    def uniqueName(self, str):
        return str + ` (self.serialNum) `

    def gameStart(self, gameStartTime):
        self.gameStartTime = gameStartTime
        self.initCollisions()
        self.startWalkAnim()
        self.occupiedTiles = [(self.nextTX, self.nextTY)]
        n = 20
        self.nextThinkTic = self.serialNum * self.ticFreq / n
        self.fromPos = Point3(0, 0, 0)
        self.toPos = Point3(0, 0, 0)
        self.fromHpr = Point3(0, 0, 0)
        self.toHpr = Point3(0, 0, 0)
        self.moveIval = WaitInterval(1.0)

    def gameEnd(self):
        self.moveIval.pause()
        del self.moveIval
        self.shutdownCollisions()
        self.suit.loop('neutral')

    def initCollisions(self):
        self.collSphere = CollisionSphere(0, 0, 0, 2.0)
        self.collSphere.setTangible(0)
        self.collNode = CollisionNode(self.uniqueName(self.COLL_SPHERE_NAME))
        self.collNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.collNode.addSolid(self.collSphere)
        self.collNodePath = self.suit.attachNewNode(self.collNode)
        self.collNodePath.hide()
        self.accept(self.uniqueName('enter' + self.COLL_SPHERE_NAME),
                    self.handleEnterSphere)

    def shutdownCollisions(self):
        self.ignore(self.uniqueName('enter' + self.COLL_SPHERE_NAME))
        del self.collSphere
        self.collNodePath.removeNode()
        del self.collNodePath
        del self.collNode

    def handleEnterSphere(self, collEntry):
        messenger.send(self.COLLISION_EVENT_NAME, [self.serialNum])

    def __getWorldPos(self, sTX, sTY):
        wx, wy = self.maze.tile2world(sTX, sTY)
        return Point3(wx, wy, self.SUIT_Z)

    def onstage(self):
        sTX = int(self.startTile[0])
        sTY = int(self.startTile[1])
        c = 0
        lim = 0
        toggle = 0
        direction = 0
        while not self.maze.isWalkable(sTX, sTY):
            if 0 == direction:
                sTX -= 1
            elif 1 == direction:
                sTY -= 1
            elif 2 == direction:
                sTX += 1
            elif 3 == direction:
                sTY += 1
            c += 1
            if c > lim:
                c = 0
                direction = (direction + 1) % 4
                toggle += 1
                if not toggle & 1:
                    lim += 1

        self.TX = sTX
        self.TY = sTY
        self.direction = self.DIR_DOWN
        self.lastDirection = self.direction
        self.nextTX = self.TX
        self.nextTY = self.TY
        self.suit.setPos(self.__getWorldPos(self.TX, self.TY))
        self.suit.setHpr(self.directionHs[self.direction], 0, 0)
        self.suit.reparentTo(render)
        self.suit.pose(self._walkAnimName, 0)
        self.suit.loop('neutral')

    def offstage(self):
        self.suit.reparentTo(hidden)

    def startWalkAnim(self):
        self.suit.loop(self._walkAnimName)
        speed = float(self.maze.cellWidth) / self.cellWalkDuration
        self.suit.setPlayRate(speed / self.DEFAULT_SPEED, self._walkAnimName)

    def __applyDirection(self, dir, TX, TY):
        if self.DIR_UP == dir:
            TY += 1
        elif self.DIR_DOWN == dir:
            TY -= 1
        elif self.DIR_LEFT == dir:
            TX -= 1
        elif self.DIR_RIGHT == dir:
            TX += 1
        return (TX, TY)

    def __chooseNewWalkDirection(self, unwalkables):
        if not self.rng.randrange(self._walkSameDirectionProb):
            newTX, newTY = self.__applyDirection(self.direction, self.TX,
                                                 self.TY)
            if self.maze.isWalkable(newTX, newTY, unwalkables):
                return self.direction
        if self.difficulty >= 0.5:
            if not self.rng.randrange(self._walkTurnAroundProb):
                oppositeDir = self.oppositeDirections[self.direction]
                newTX, newTY = self.__applyDirection(oppositeDir, self.TX,
                                                     self.TY)
                if self.maze.isWalkable(newTX, newTY, unwalkables):
                    return oppositeDir
        candidateDirs = [
            self.DIR_UP, self.DIR_DOWN, self.DIR_LEFT, self.DIR_RIGHT
        ]
        candidateDirs.remove(self.oppositeDirections[self.direction])
        while len(candidateDirs):
            dir = self.rng.choice(candidateDirs)
            newTX, newTY = self.__applyDirection(dir, self.TX, self.TY)
            if self.maze.isWalkable(newTX, newTY, unwalkables):
                return dir
            candidateDirs.remove(dir)

        return self.oppositeDirections[self.direction]

    def getThinkTimestampTics(self, curTic):
        if curTic < self.nextThinkTic:
            return []
        else:
            r = range(self.nextThinkTic, curTic + 1, self.ticPeriod)
            self.lastTicBeforeRender = r[-1]
            return r

    def prepareToThink(self):
        self.occupiedTiles = [(self.nextTX, self.nextTY)]

    def think(self, curTic, curT, unwalkables):
        self.TX = self.nextTX
        self.TY = self.nextTY
        self.lastDirection = self.direction
        self.direction = self.__chooseNewWalkDirection(unwalkables)
        self.nextTX, self.nextTY = self.__applyDirection(
            self.direction, self.TX, self.TY)
        self.occupiedTiles = [(self.TX, self.TY), (self.nextTX, self.nextTY)]
        if curTic == self.lastTicBeforeRender:
            fromCoords = self.maze.tile2world(self.TX, self.TY)
            toCoords = self.maze.tile2world(self.nextTX, self.nextTY)
            self.fromPos.set(fromCoords[0], fromCoords[1], self.SUIT_Z)
            self.toPos.set(toCoords[0], toCoords[1], self.SUIT_Z)
            self.moveIval = LerpPosInterval(self.suit,
                                            self.cellWalkDuration,
                                            self.toPos,
                                            startPos=self.fromPos,
                                            name=self.uniqueName(
                                                self.MOVE_IVAL_NAME))
            if self.direction != self.lastDirection:
                self.fromH = self.directionHs[self.lastDirection]
                toH = self.directionHs[self.direction]
                if self.fromH == 270 and toH == 0:
                    self.fromH = -90
                elif self.fromH == 0 and toH == 270:
                    self.fromH = 360
                self.fromHpr.set(self.fromH, 0, 0)
                self.toHpr.set(toH, 0, 0)
                turnIval = LerpHprInterval(
                    self.suit,
                    self.turnDuration,
                    self.toHpr,
                    startHpr=self.fromHpr,
                    name=self.uniqueName('turnMazeSuit'))
                self.moveIval = Parallel(self.moveIval,
                                         turnIval,
                                         name=self.uniqueName(
                                             self.MOVE_IVAL_NAME))
            else:
                self.suit.setH(self.directionHs[self.direction])
            moveStartT = float(self.nextThinkTic) / float(self.ticFreq)
            self.moveIval.start(curT - (moveStartT + self.gameStartTime))
        self.nextThinkTic += self.ticPeriod

    @staticmethod
    def thinkSuits(suitList, startTime, ticFreq=MazeGameGlobals.SUIT_TIC_FREQ):
        curT = globalClock.getFrameTime() - startTime
        curTic = int(curT * float(ticFreq))
        suitUpdates = []
        for i in xrange(len(suitList)):
            updateTics = suitList[i].getThinkTimestampTics(curTic)
            suitUpdates.extend(zip(updateTics, [i] * len(updateTics)))

        suitUpdates.sort(lambda a, b: a[0] - b[0])
        if len(suitUpdates) > 0:
            curTic = 0
            for i in xrange(len(suitUpdates)):
                update = suitUpdates[i]
                tic = update[0]
                suitIndex = update[1]
                suit = suitList[suitIndex]
                if tic > curTic:
                    curTic = tic
                    j = i + 1
                    while j < len(suitUpdates):
                        if suitUpdates[j][0] > tic:
                            break
                        suitList[suitUpdates[j][1]].prepareToThink()
                        j += 1

                unwalkables = []
                for si in xrange(suitIndex):
                    unwalkables.extend(suitList[si].occupiedTiles)

                for si in xrange(suitIndex + 1, len(suitList)):
                    unwalkables.extend(suitList[si].occupiedTiles)

                suit.think(curTic, curT, unwalkables)
class DistributedPartyTugOfWarActivity(DistributedPartyTeamActivity):
    notify = directNotify.newCategory('DistributedPartyTugOfWarActivity')

    def __init__(self, cr):
        DistributedPartyTeamActivity.__init__(self, cr, PartyGlobals.ActivityIds.PartyTugOfWar, startDelay=PartyGlobals.TugOfWarStartDelay)
        self.buttons = [0, 1]
        self.arrowKeys = None
        self.keyTTL = []
        self.idealRate = 0.0
        self.keyRate = 0
        self.allOutMode = False
        self.rateMatchAward = 0.0
        self.toonIdsToStartPositions = {}
        self.toonIdsToIsPullingFlags = {}
        self.toonIdsToRightHands = {}
        self.fallenToons = []
        self.fallenPositions = []
        self.unusedFallenPositionsIndices = [0,
         1,
         2,
         3]
        self.toonIdsToAnimIntervals = {}
        self.tugRopes = []
        return

    def generate(self):
        DistributedPartyTeamActivity.generate(self)
        self._hopOffFinishedSV = StateVar(True)
        self._rewardFinishedSV = StateVar(True)
        self._isWalkStateReadyFC = FunctionCall(self._testWalkStateReady, self._hopOffFinishedSV, self._rewardFinishedSV)

    def delete(self):
        self._isWalkStateReadyFC.destroy()
        self._hopOffFinishedSV.destroy()
        self._rewardFinishedSV.destroy()
        DistributedPartyTeamActivity.delete(self)

    def handleToonJoined(self, toonId):
        DistributedPartyTeamActivity.handleToonJoined(self, toonId)
        self.toonIdsToAnimIntervals[toonId] = None
        if toonId == base.localAvatar.doId:
            base.cr.playGame.getPlace().fsm.request('activity')
            camera.wrtReparentTo(self.root)
            self.cameraMoveIval = LerpPosHprInterval(camera, 1.5, PartyGlobals.TugOfWarCameraPos, PartyGlobals.TugOfWarCameraInitialHpr, other=self.root)
            self.cameraMoveIval.start()
            self.localToonPosIndex = self.getIndex(base.localAvatar.doId, self.localToonTeam)
            self.notify.debug('posIndex: %d' % self.localToonPosIndex)
            toon = self.getAvatar(toonId)
            targetPos = self.dockPositions[self.localToonTeam][self.localToonPosIndex]
            if toon.getZ(self.root) < PartyGlobals.TugOfWarToonPositionZ:
                toon.setZ(self.root, PartyGlobals.TugOfWarToonPositionZ)
            targetH = fitDestAngle2Src(toon.getH(self.root), PartyGlobals.TugOfWarHeadings[self.localToonTeam])
            travelVector = targetPos - toon.getPos(self.root)
            duration = travelVector.length() / 5.0
            if self.toonIdsToAnimIntervals[toonId] is not None:
                self.toonIdsToAnimIntervals[toonId].finish()
            self.toonIdsToAnimIntervals[toonId] = Sequence(Func(toon.startPosHprBroadcast, 0.1), Func(toon.b_setAnimState, 'run'), LerpPosHprInterval(toon, duration, targetPos, VBase3(targetH, 0.0, 0.0), other=self.root), Func(toon.stopPosHprBroadcast), Func(toon.b_setAnimState, 'neutral'))
            self.toonIdsToAnimIntervals[toonId].start()
        return

    def handleToonExited(self, toonId):
        DistributedPartyTeamActivity.handleToonExited(self, toonId)
        if toonId == base.localAvatar.doId:
            self.cameraMoveIval.pause()
            if toonId not in self.fallenToons:
                if toonId in self.toonIdsToAnimIntervals and self.toonIdsToAnimIntervals[toonId] is not None:
                    self.toonIdsToAnimIntervals[toonId].finish()
                toon = self.getAvatar(toonId)
                targetH = fitDestAngle2Src(toon.getH(self.root), 180.0)
                targetPos = self.hopOffPositions[self.getTeam(toonId)][self.getIndex(toonId, self.getTeam(toonId))]
                hopOffAnim = Sequence(Func(toon.startPosHprBroadcast, 0.1), toon.hprInterval(0.2, VBase3(targetH, 0.0, 0.0), other=self.root), Func(toon.b_setAnimState, 'jump', 1.0), Wait(0.4), PartyUtils.arcPosInterval(0.75, toon, targetPos, 5.0, self.root), Func(toon.stopPosHprBroadcast), Func(toon.sendCurrentPosition), Func(self.hopOffFinished, toonId))
                self.toonIdsToAnimIntervals[toonId] = hopOffAnim
                self._hopOffFinishedSV.set(False)
                self.toonIdsToAnimIntervals[toonId].start()
            else:
                self._hopOffFinishedSV.set(True)
                del self.toonIdsToAnimIntervals[toonId]
        return

    def handleRewardDone(self):
        self._rewardFinishedSV.set(True)

    def _testWalkStateReady(self, hoppedOff, rewardFinished):
        if hoppedOff and rewardFinished:
            DistributedPartyTeamActivity.handleRewardDone(self)

    def hopOffFinished(self, toonId):
        if hasattr(self, 'toonIdsToAnimIntervals') and toonId in self.toonIdsToAnimIntervals:
            del self.toonIdsToAnimIntervals[toonId]
        if toonId == base.localAvatar.doId:
            if hasattr(self._hopOffFinishedSV, '_value'):
                self._hopOffFinishedSV.set(True)

    def handleToonShifted(self, toonId):
        if toonId == base.localAvatar.doId:
            self.localToonPosIndex = self.getIndex(base.localAvatar.doId, self.localToonTeam)
            if self.toonIdsToAnimIntervals[toonId] is not None:
                self.toonIdsToAnimIntervals[toonId].finish()
            toon = self.getAvatar(toonId)
            targetPos = self.dockPositions[self.localToonTeam][self.localToonPosIndex]
            self.toonIdsToAnimIntervals[toonId] = Sequence(Wait(0.6), Func(toon.startPosHprBroadcast, 0.1), Func(toon.b_setAnimState, 'run'), toon.posInterval(0.5, targetPos, other=self.root), Func(toon.stopPosHprBroadcast), Func(toon.b_setAnimState, 'neutral'))
            self.toonIdsToAnimIntervals[toonId].start()
        return

    def handleToonDisabled(self, toonId):
        if self.toonIdsToAnimIntervals.has_key(toonId):
            if self.toonIdsToAnimIntervals[toonId]:
                if self.toonIdsToAnimIntervals[toonId].isPlaying():
                    self.toonIdsToAnimIntervals[toonId].finish()
            else:
                self.notify.debug('self.toonIdsToAnimIntervals[%d] is none' % toonId)

    def setToonsPlaying(self, leftTeamToonIds, rightTeamToonIds):
        DistributedPartyTeamActivity.setToonsPlaying(self, leftTeamToonIds, rightTeamToonIds)
        self.toonIdsToRightHands.clear()
        for toonId in self.getToonIdsAsList():
            toon = self.getAvatar(toonId)
            if toon:
                self.toonIdsToRightHands[toonId] = toon.getRightHands()[0]

    def load(self):
        DistributedPartyTeamActivity.load(self)
        self.loadModels()
        self.loadGuiElements()
        self.loadSounds()
        self.loadIntervals()
        self.arrowKeys = ArrowKeys()

    def loadModels(self):
        self.playArea = loader.loadModel('phase_13/models/parties/partyTugOfWar')
        self.playArea.reparentTo(self.root)
        self.sign.reparentTo(self.playArea.find('**/TugOfWar_sign_locator'))
        self.dockPositions = [[], []]
        for i in xrange(4):
            self.dockPositions[0].append(Point3(-PartyGlobals.TugOfWarInitialToonPositionsXOffset - PartyGlobals.TugOfWarToonPositionXSeparation * i, 0.0, PartyGlobals.TugOfWarToonPositionZ))

        for i in xrange(4):
            self.dockPositions[1].append(Point3(PartyGlobals.TugOfWarInitialToonPositionsXOffset + PartyGlobals.TugOfWarToonPositionXSeparation * i, 0.0, PartyGlobals.TugOfWarToonPositionZ))

        self.hopOffPositions = [[], []]
        for i in xrange(1, 5):
            self.hopOffPositions[PartyGlobals.TeamActivityTeams.LeftTeam].append(self.playArea.find('**/leftTeamHopOff%d_locator' % i).getPos())
            self.hopOffPositions[PartyGlobals.TeamActivityTeams.RightTeam].append(self.playArea.find('**/rightTeamHopOff%d_locator' % i).getPos())

        for i in xrange(1, 5):
            pos = self.playArea.find('**/fallenToon%d_locator' % i).getPos()
            self.fallenPositions.append(pos)

        self.joinCollision = []
        self.joinCollisionNodePaths = []
        for i in xrange(len(PartyGlobals.TeamActivityTeams)):
            collShape = CollisionTube(PartyGlobals.TugOfWarJoinCollisionEndPoints[0], PartyGlobals.TugOfWarJoinCollisionEndPoints[1], PartyGlobals.TugOfWarJoinCollisionRadius)
            collShape.setTangible(True)
            self.joinCollision.append(CollisionNode('TugOfWarJoinCollision%d' % i))
            self.joinCollision[i].addSolid(collShape)
            tubeNp = self.playArea.attachNewNode(self.joinCollision[i])
            tubeNp.node().setCollideMask(ToontownGlobals.WallBitmask)
            self.joinCollisionNodePaths.append(tubeNp)
            self.joinCollisionNodePaths[i].setPos(PartyGlobals.TugOfWarJoinCollisionPositions[i])

        self.__enableCollisions()
        ropeModel = loader.loadModel('phase_4/models/minigames/tug_of_war_rope')
        self.ropeTexture = ropeModel.findTexture('*')
        ropeModel.removeNode()
        for i in xrange(PartyGlobals.TugOfWarMaximumPlayersPerTeam * 2 - 1):
            rope = Rope(self.uniqueName('TugRope%d' % i))
            if rope.showRope:
                rope.ropeNode.setRenderMode(RopeNode.RMBillboard)
                rope.ropeNode.setThickness(0.2)
                rope.setTexture(self.ropeTexture)
                rope.ropeNode.setUvMode(RopeNode.UVDistance)
                rope.ropeNode.setUvDirection(1)
                rope.setTransparency(1)
                rope.setColor(0.89, 0.89, 0.6, 1.0)
                rope.reparentTo(self.root)
                rope.stash()
            self.tugRopes.append(rope)

        self.splash = Splash.Splash(self.root)
        self.splash.setScale(2.0, 4.0, 1.0)
        pos = self.fallenPositions[0]
        self.splash.setPos(pos[0], pos[1], PartyGlobals.TugOfWarSplashZOffset)
        self.splash.hide()

    def loadGuiElements(self):
        self.powerMeter = MinigamePowerMeter(PartyGlobals.TugOfWarPowerMeterSize)
        self.powerMeter.reparentTo(aspect2d)
        self.powerMeter.setPos(0.0, 0.0, 0.6)
        self.powerMeter.hide()
        self.arrows = [None] * 2
        for x in xrange(len(self.arrows)):
            self.arrows[x] = loader.loadModel('phase_3/models/props/arrow')
            self.arrows[x].reparentTo(self.powerMeter)
            self.arrows[x].setScale(0.2 - 0.4 * x, 0.2, 0.2)
            self.arrows[x].setPos(0.12 - 0.24 * x, 0, -.26)

        return

    def loadSounds(self):
        self.splashSound = base.loadSfx('phase_4/audio/sfx/MG_cannon_splash.ogg')
        self.whistleSound = base.loadSfx('phase_4/audio/sfx/AA_sound_whistle.ogg')

    def loadIntervals(self):
        self.updateIdealRateInterval = Sequence()
        self.updateIdealRateInterval.append(Wait(PartyGlobals.TugOfWarTargetRateList[0][0]))
        for i in xrange(1, len(PartyGlobals.TugOfWarTargetRateList)):
            duration = PartyGlobals.TugOfWarTargetRateList[i][0]
            idealRate = PartyGlobals.TugOfWarTargetRateList[i][1]
            self.updateIdealRateInterval.append(Func(self.setIdealRate, idealRate))
            if i == len(PartyGlobals.TugOfWarTargetRateList) - 1:
                self.updateIdealRateInterval.append(Func(setattr, self, 'allOutMode', True))
            else:
                self.updateIdealRateInterval.append(Wait(duration))

        self.updateKeyPressRateInterval = Sequence(Wait(PartyGlobals.TugOfWarKeyPressUpdateRate), Func(self.updateKeyPressRate))
        self.reportToServerInterval = Sequence(Wait(PartyGlobals.TugOfWarKeyPressReportRate), Func(self.reportToServer))
        self.setupInterval = Parallel()
        self.globalSetupInterval = Sequence(Wait(PartyGlobals.TugOfWarReadyDuration + PartyGlobals.TugOfWarGoDuration), Func(self.tightenRopes))
        self.localSetupInterval = Sequence(Func(self.setStatus, TTLocalizer.PartyTugOfWarReady), Func(self.showStatus), Wait(PartyGlobals.TugOfWarReadyDuration), Func(base.playSfx, self.whistleSound), Func(self.setStatus, TTLocalizer.PartyTugOfWarGo), Wait(PartyGlobals.TugOfWarGoDuration), Func(self.enableKeys), Func(self.hideStatus), Func(self.updateIdealRateInterval.start), Func(self.updateKeyPressRateInterval.loop), Func(self.reportToServerInterval.loop))
        self.splashInterval = Sequence(Func(base.playSfx, self.splashSound), Func(self.splash.play))

    def unload(self):
        DistributedPartyTeamActivity.unload(self)
        self.arrowKeys.destroy()
        self.unloadIntervals()
        self.unloadModels()
        self.unloadGuiElements()
        self.unloadSounds()
        if hasattr(self, 'toonIds'):
            del self.toonIds
        del self.buttons
        del self.arrowKeys
        del self.keyTTL
        del self.idealRate
        del self.keyRate
        del self.allOutMode
        del self.rateMatchAward
        del self.toonIdsToStartPositions
        del self.toonIdsToIsPullingFlags
        del self.toonIdsToRightHands
        del self.fallenToons
        del self.fallenPositions
        del self.unusedFallenPositionsIndices
        self.toonIdsToAnimIntervals.clear()
        del self.toonIdsToAnimIntervals

    def unloadModels(self):
        self.playArea.removeNode()
        del self.playArea
        del self.dockPositions
        del self.hopOffPositions
        self.__disableCollisions()
        while len(self.joinCollision) > 0:
            collNode = self.joinCollision.pop()
            del collNode

        while len(self.joinCollisionNodePaths) > 0:
            collNodePath = self.joinCollisionNodePaths.pop()
            collNodePath.removeNode()
            del collNodePath

        while len(self.tugRopes) > 0:
            rope = self.tugRopes.pop()
            if rope is not None:
                rope.removeNode()
            del rope

        del self.tugRopes
        self.splash.destroy()
        del self.splash
        return

    def unloadGuiElements(self):
        for arrow in self.arrows:
            if arrow is not None:
                arrow.removeNode()
                del arrow

        del self.arrows
        if self.powerMeter is not None:
            self.powerMeter.cleanup()
            del self.powerMeter
        return

    def unloadSounds(self):
        del self.splashSound
        del self.whistleSound

    def unloadIntervals(self):
        self.updateIdealRateInterval.pause()
        del self.updateIdealRateInterval
        self.updateKeyPressRateInterval.pause()
        del self.updateKeyPressRateInterval
        self.reportToServerInterval.pause()
        del self.reportToServerInterval
        self.setupInterval.pause()
        del self.setupInterval
        self.globalSetupInterval.pause()
        del self.globalSetupInterval
        self.localSetupInterval.pause()
        del self.localSetupInterval
        self.splashInterval.pause()
        del self.splashInterval

    def __enableCollisions(self):
        for i in xrange(len(PartyGlobals.TeamActivityTeams)):
            self.accept('enterTugOfWarJoinCollision%d' % i, getattr(self, '_join%s' % PartyGlobals.TeamActivityTeams.getString(i)))

    def __disableCollisions(self):
        for i in xrange(len(PartyGlobals.TeamActivityTeams)):
            self.ignore('enterTugOfWarJoinCollision%d' % i)

    def startWaitForEnough(self):
        DistributedPartyTeamActivity.startWaitForEnough(self)
        self.__enableCollisions()

    def finishWaitForEnough(self):
        DistributedPartyTeamActivity.finishWaitForEnough(self)
        self.__disableCollisions()

    def startWaitToStart(self, waitStartTimestamp):
        DistributedPartyTeamActivity.startWaitToStart(self, waitStartTimestamp)
        self.__enableCollisions()

    def finishWaitToStart(self):
        DistributedPartyTeamActivity.finishWaitToStart(self)
        self.__disableCollisions()

    def startRules(self):
        DistributedPartyTeamActivity.startRules(self)
        self.setUpRopes()
        if self.isLocalToonPlaying:
            self.showControls()

    def finishRules(self):
        DistributedPartyTeamActivity.finishRules(self)
        if self.activityFSM.getCurrentOrNextState() == 'WaitForEnough':
            self.hideRopes()
            self.hideControls()

    def finishWaitForServer(self):
        DistributedPartyTeamActivity.finishWaitForServer(self)
        if self.activityFSM.getCurrentOrNextState() == 'WaitForEnough':
            self.hideRopes()
            self.hideControls()

    def startActive(self):
        DistributedPartyTeamActivity.startActive(self)
        self.toonIdsToStartPositions.clear()
        self.toonIdsToIsPullingFlags.clear()
        for toonId in self.getToonIdsAsList():
            self.toonIdsToIsPullingFlags[toonId] = False
            toon = self.getAvatar(toonId)
            if toon:
                self.toonIdsToStartPositions[toonId] = toon.getPos(self.root)
            else:
                self.notify.warning("couldn't find toon %d assigning 0,0,0 to startPos" % toonId)
                self.toonIdsToStartPositions[toonId] = Point3(0, 0, 0)

        self.unusedFallenPositionsIndices = [0,
         1,
         2,
         3]
        self.setupInterval = Parallel(self.globalSetupInterval)
        if self.isLocalToonPlaying:
            self.keyTTL = []
            self.idealForce = 0.0
            self.keyRate = 0
            self.rateMatchAward = 0.0
            self.allOutMode = False
            self.setIdealRate(PartyGlobals.TugOfWarTargetRateList[0][1])
            self.setupInterval.append(self.localSetupInterval)
        self.setupInterval.start()

    def finishActive(self):
        DistributedPartyTeamActivity.finishActive(self)
        self.hideControls()
        self.disableKeys()
        self.setupInterval.pause()
        self.reportToServerInterval.pause()
        self.updateKeyPressRateInterval.pause()
        self.updateIdealRateInterval.pause()
        self.hideRopes()

    def startConclusion(self, losingTeam):
        DistributedPartyTeamActivity.startConclusion(self, losingTeam)
        if self.isLocalToonPlaying:
            self._rewardFinishedSV.set(False)
            if losingTeam == PartyGlobals.TeamActivityNeitherTeam:
                self.setStatus(TTLocalizer.PartyTeamActivityGameTie)
            else:
                self.setStatus(TTLocalizer.PartyTugOfWarGameEnd)
            self.showStatus()
        if losingTeam == PartyGlobals.TeamActivityNeitherTeam:
            for toonId in self.getToonIdsAsList():
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop('neutral')

        else:
            for toonId in self.toonIds[losingTeam]:
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop('neutral')

            for toonId in self.toonIds[1 - losingTeam]:
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop('victory')

        for ival in self.toonIdsToAnimIntervals.values():
            if ival is not None:
                ival.finish()

        return

    def finishConclusion(self):
        DistributedPartyTeamActivity.finishConclusion(self)
        self.fallenToons = []

    def getTitle(self):
        return TTLocalizer.PartyTugOfWarTitle

    def getInstructions(self):
        return TTLocalizer.TugOfWarInstructions

    def showControls(self):
        for arrow in self.arrows:
            arrow.setColor(PartyGlobals.TugOfWarDisabledArrowColor)

        self.powerMeter.setTarget(PartyGlobals.TugOfWarTargetRateList[0][1])
        self.powerMeter.setPower(PartyGlobals.TugOfWarTargetRateList[0][1])
        self.powerMeter.setBarColor((0.0, 1.0, 0.0, 0.5))
        self.powerMeter.clearTooSlowTooFast()
        self.powerMeter.show()

    def hideControls(self):
        self.powerMeter.hide()

    def setUpRopes(self):
        self.notify.debug('setUpRopes')
        ropeIndex = 0
        leftToonId = -1
        if self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam]:
            leftToonId = self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam][0]
        rightToonId = -1
        if self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam]:
            rightToonId = self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam][0]
        if leftToonId in self.toonIdsToRightHands and rightToonId in self.toonIdsToRightHands:
            self.tugRopes[ropeIndex].setup(3, ((self.toonIdsToRightHands[leftToonId], (0, 0, 0)), (self.root, (0.0, 0.0, 2.5)), (self.toonIdsToRightHands[rightToonId], (0, 0, 0))), [0,
             0,
             0,
             1,
             1,
             1])
            self.tugRopes[ropeIndex].unstash()
            ropeIndex += 1
        teams = [PartyGlobals.TeamActivityTeams.LeftTeam, PartyGlobals.TeamActivityTeams.RightTeam]
        for currTeam in teams:
            numToons = len(self.toonIds[currTeam])
            if numToons > 1:
                for i in xrange(numToons - 1, 0, -1):
                    toon1 = self.toonIds[currTeam][i]
                    toon2 = self.toonIds[currTeam][i - 1]
                    if not self.toonIdsToRightHands.has_key(toon1):
                        self.notify.warning('Toon in tug of war activity but not properly setup:  %s' % toon1)
                    elif not self.toonIdsToRightHands.has_key(toon2):
                        self.notify.warning('Toon in tug of war activity but not properly setup:  %s' % toon2)
                    else:
                        self.notify.debug('Connecting rope between toon %d and toon %d of team %d.' % (i, i - 1, currTeam))
                        self.tugRopes[ropeIndex].setup(3, ((self.toonIdsToRightHands[toon1], (0, 0, 0)), (self.toonIdsToRightHands[toon1], (0, 0, 0)), (self.toonIdsToRightHands[toon2], (0, 0, 0))), [0,
                         0,
                         0,
                         1,
                         1,
                         1])
                        self.tugRopes[ropeIndex].unstash()
                        ropeIndex += 1

    def tightenRopes(self):
        self.notify.debug('tightenRopes')
        self.tugRopes[0].setup(3, ((self.toonIdsToRightHands[self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam][0]], (0, 0, 0)), (self.toonIdsToRightHands[self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam][0]], (0, 0, 0)), (self.toonIdsToRightHands[self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam][0]], (0, 0, 0))), [0,
         0,
         0,
         1,
         1,
         1])

    def hideRopes(self):
        self.notify.debug('hideRopes')
        for rope in self.tugRopes:
            rope.stash()

    def handleGameTimerExpired(self):
        self.disableKeys()

    def setIdealRate(self, idealRate):
        self.notify.debug('setIdealRate( %d )' % idealRate)
        self.idealRate = idealRate
        self.idealForce = self.advantage * (4 + 0.4 * self.idealRate)

    def updateKeyPressRate(self):
        for i in xrange(len(self.keyTTL)):
            self.keyTTL[i] -= PartyGlobals.TugOfWarKeyPressUpdateRate

        for i in xrange(len(self.keyTTL)):
            if self.keyTTL[i] <= 0.0:
                a = self.keyTTL[0:i]
                del self.keyTTL
                self.keyTTL = a
                break

        self.keyRate = len(self.keyTTL)
        if self.keyRate == self.idealRate or self.keyRate == self.idealRate + 1:
            self.rateMatchAward += 0.3
        else:
            self.rateMatchAward = 0.0

    def reportToServer(self):
        self.currentForce = self.computeForce(self.keyRate)
        self.sendUpdate('reportKeyRateForce', [self.keyRate, self.currentForce])
        self.setSpeedGauge()
        self.setAnimState(base.localAvatar.doId, self.keyRate)

    def computeForce(self, keyRate):
        F = 0
        if self.allOutMode:
            F = 0.75 * keyRate
        else:
            stdDev = 0.25 * self.idealRate
            F = self.advantage * (self.rateMatchAward + 4 + 0.4 * self.idealRate) * math.pow(math.e, -math.pow(keyRate - self.idealRate, 2) / (2.0 * math.pow(stdDev, 2)))
        return F

    def setSpeedGauge(self):
        self.powerMeter.setPower(self.keyRate)
        self.powerMeter.setTarget(self.idealRate)
        if not self.allOutMode:
            self.powerMeter.updateTooSlowTooFast()
            index = float(self.currentForce) / self.idealForce
            bonus = 0.0
            if index > 1.0:
                bonus = max(1.0, index - 1.0)
                index = 1.0
            color = (0,
             0.75 * index + 0.25 * bonus,
             0.75 * (1 - index),
             0.5)
            self.powerMeter.setBarColor(color)
        else:
            self.powerMeter.setBarColor((0.0, 1.0, 0.0, 0.5))

    def updateToonKeyRate(self, toonId, keyRate):
        if toonId != base.localAvatar.doId:
            self.setAnimState(toonId, keyRate)

    def setAnimState(self, toonId, keyRate):
        if self.activityFSM.state != 'Active':
            return
        toon = self.getAvatar(toonId)
        if not self.toonIdsToIsPullingFlags.has_key(toonId):
            if self.getTeam(toonId) == None:
                self.notify.warning("setAnimState called with toonId (%d) that wasn't in self.toonIds" % toonId)
                return
            else:
                self.notify.warning('setAnimState called with toonId (%d) that was in self.toonIds but not in self.toonIdsToIsPullingFlags. Adding it.' % toonId)
                self.toonIdsToIsPullingFlags[toonId] = False
        if keyRate > 0 and not self.toonIdsToIsPullingFlags[toonId]:
            if toon:
                toon.loop('tug-o-war')
            else:
                self.notify.warning('toon %d is None, skipping toon.loop(tugowar)' % toonId)
            self.toonIdsToIsPullingFlags[toonId] = True
        if keyRate <= 0 and self.toonIdsToIsPullingFlags[toonId]:
            if toon:
                toon.pose('tug-o-war', 3)
                toon.startLookAround()
            else:
                self.notify.warning('toon %d is None, skipping toon.startLookAround' % toonId)
            self.toonIdsToIsPullingFlags[toonId] = False
        return

    def enableKeys(self):
        self.notify.debug('enableKeys')
        self.arrowKeys.setPressHandlers([lambda : self.__pressHandler(2),
         lambda : self.__pressHandler(3),
         lambda : self.__pressHandler(1),
         lambda : self.__pressHandler(0)])
        self.arrowKeys.setReleaseHandlers([lambda : self.__releaseHandler(2),
         lambda : self.__releaseHandler(3),
         lambda : self.__releaseHandler(1),
         lambda : self.__releaseHandler(0)])
        for arrow in self.arrows:
            arrow.setColor(PartyGlobals.TugOfWarEnabledArrowColor)

    def disableKeys(self):
        self.arrowKeys.setPressHandlers(self.arrowKeys.NULL_HANDLERS)
        self.arrowKeys.setReleaseHandlers(self.arrowKeys.NULL_HANDLERS)

    def __pressHandler(self, index):
        if index == self.buttons[0]:
            self.arrows[index].setColor(PartyGlobals.TugOfWarHilightedArrowColor)
            self.keyTTL.insert(0, PartyGlobals.TugOfWarKeyPressTimeToLive)
            self.buttons.reverse()

    def __releaseHandler(self, index):
        if index in self.buttons:
            self.arrows[index].setColor(PartyGlobals.TugOfWarEnabledArrowColor)

    def updateToonPositions(self, offset):
        if self.activityFSM.state != 'Active':
            return
        if self.isLocalToonPlaying:
            camera.lookAt(self.root, offset, 0.0, PartyGlobals.TugOfWarCameraLookAtHeightOffset)
        for toonId in self.getToonIdsAsList():
            if hasattr(self, 'fallenToons') and toonId not in self.fallenToons:
                toon = self.getAvatar(toonId)
                if toon is not None:
                    origPos = self.toonIdsToStartPositions[toonId]
                    curPos = toon.getPos(self.root)
                    newPos = Point3(origPos[0] + offset, curPos[1], curPos[2])
                    if self.toonIdsToAnimIntervals[toonId] != None:
                        if self.toonIdsToAnimIntervals[toonId].isPlaying():
                            self.toonIdsToAnimIntervals[toonId].finish()
                            self.checkIfFallen(toonId)
                    if toonId not in self.fallenToons:
                        self.toonIdsToAnimIntervals[toonId] = Sequence(LerpPosInterval(toon, duration=PartyGlobals.TugOfWarKeyPressReportRate, pos=newPos, other=self.root), Func(self.checkIfFallen, toonId))
                        self.toonIdsToAnimIntervals[toonId].start()

        return

    def checkIfFallen(self, toonId):
        if hasattr(self, 'fallenToons') and toonId not in self.fallenToons:
            toon = self.getAvatar(toonId)
            if toon:
                curPos = toon.getPos(self.root)
                team = self.getTeam(toonId)
                if team == PartyGlobals.TeamActivityTeams.LeftTeam and curPos[0] > -2.0 or team == PartyGlobals.TeamActivityTeams.RightTeam and curPos[0] < 2.0:
                    losingTeam = self.getTeam(toonId)
                    self.throwTeamInWater(losingTeam)
                    self.sendUpdate('reportFallIn', [losingTeam])

    def throwTeamInWater(self, losingTeam):
        self.notify.debug('throwTeamInWater( %s )' % PartyGlobals.TeamActivityTeams.getString(losingTeam))
        splashSet = False
        for toonId in self.toonIds[losingTeam]:
            self.fallenToons.append(toonId)
            toon = self.getAvatar(toonId)
            fallenPosIndex = self.toonIds[losingTeam].index(toonId)
            if fallenPosIndex < 0 or fallenPosIndex >= 4:
                fallenPosIndex = 0
            newPos = self.fallenPositions[fallenPosIndex]
            if self.toonIdsToAnimIntervals.has_key(toonId) and self.toonIdsToAnimIntervals[toonId] is not None:
                if self.toonIdsToAnimIntervals[toonId].isPlaying():
                    self.toonIdsToAnimIntervals[toonId].finish()
            if toon:
                parallel = Parallel(ActorInterval(actor=toon, animName='slip-forward', duration=2.0), LerpPosInterval(toon, duration=2.0, pos=newPos, other=self.root))
            else:
                self.notify.warning('toon %d is none, skipping slip-forward' % toonId)
                parallel = Parallel()
            if not splashSet:
                splashSet = True
                parallel.append(self.splashInterval)
            if toon:
                self.toonIdsToAnimIntervals[toonId] = Sequence(parallel, Func(toon.loop, 'neutral'))
            else:
                self.notify.warning('toon %d is none, skipping toon.loop(neutral)' % toonId)
                self.toonIdsToAnimIntervals[toonId] = parallel
            self.toonIdsToAnimIntervals[toonId].start()

        return

    def setAdvantage(self, advantage):
        DistributedPartyTeamActivity.setAdvantage(self, advantage)
        if self.isLocalToonPlaying:
            self.setIdealRate(PartyGlobals.TugOfWarTargetRateList[0][1])
Пример #5
0
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() * (camera.getH(camNode) /
                                    abs(camera.getH(camNode)))
                node2.removeNode
                del node2
                hpr = camera.getHpr()
                pos = camera.getPos()
                camParent = camera.getParent()
                camera.wrtReparentTo(camNode)
                self.localToonDanceSequence = Sequence(
                    Func(self.__localDisableControls),
                    Parallel(
                        camera.posInterval(0.5,
                                           Point3(0, 15, 10),
                                           blendType='easeIn'),
                        camera.hprInterval(0.5,
                                           Point3(h, -20, 0),
                                           blendType='easeIn')),
                    camNode.hprInterval(4.0, Point3(camNode.getH() - 360, 0,
                                                    0)),
                    Func(camera.wrtReparentTo, camParent),
                    Func(camNode.removeNode),
                    Parallel(camera.posInterval(0.5, pos, blendType='easeOut'),
                             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
            camera.reparentTo(toon)
            base.localAvatar.startUpdateSmartCamera()
        elif mode == DanceViews.Dancing:
            base.localAvatar.stopUpdateSmartCamera()
            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(
                camera.posInterval(0.5, pos, blendType='easeIn'),
                camera.hprInterval(0.5,
                                   Point3(0, -27, 0),
                                   other=toon.getParent(),
                                   blendType='easeIn'))
            self.cameraParallel.start()
        self.currentCameraMode = mode
        return
Пример #6
0
class DistributedPartyTugOfWarActivity(DistributedPartyTeamActivity):

    notify = directNotify.newCategory("DistributedPartyTugOfWarActivity")

    def __init__(self, cr):
        """
        cr: instance of ClientRepository
        """
        DistributedPartyTeamActivity.__init__(
            self,
            cr,
            PartyGlobals.ActivityIds.PartyTugOfWar,
            startDelay=PartyGlobals.TugOfWarStartDelay)
        assert (self.notify.debug("__init__"))

        # these are the indices of the active buttons
        self.buttons = [0, 1]

        # these variables are used for calculation how fast the player is pressing the keys
        self.arrowKeys = None
        self.keyTTL = []
        self.idealRate = 0.0
        self.keyRate = 0
        self.allOutMode = False
        self.rateMatchAward = 0.0  # bonus for consistently hitting the ideal rate

        self.toonIdsToStartPositions = {}  # initial positions of toons
        self.toonIdsToIsPullingFlags = {}  # whether or not a toon is pulling
        self.toonIdsToRightHands = {}  # used for setting up ropes
        self.fallenToons = []  # toons in the water
        self.fallenPositions = []
        self.unusedFallenPositionsIndices = [0, 1, 2, 3]
        self.toonIdsToAnimIntervals = {}
        self.tugRopes = []

    def generate(self):
        DistributedPartyTeamActivity.generate(self)
        assert (self.notify.debug("generate"))

        self._hopOffFinishedSV = StateVar(True)
        self._rewardFinishedSV = StateVar(True)
        self._isWalkStateReadyFC = FunctionCall(self._testWalkStateReady,
                                                self._hopOffFinishedSV,
                                                self._rewardFinishedSV)

    def delete(self):
        self._isWalkStateReadyFC.destroy()
        self._hopOffFinishedSV.destroy()
        self._rewardFinishedSV.destroy()

        DistributedPartyTeamActivity.delete(self)

    def handleToonJoined(self, toonId):
        DistributedPartyTeamActivity.handleToonJoined(self, toonId)

        self.toonIdsToAnimIntervals[toonId] = None

        if toonId == base.localAvatar.doId:
            base.cr.playGame.getPlace().fsm.request("activity")

            # set camera to a 3rd person view of play area
            camera.wrtReparentTo(self.root)
            self.cameraMoveIval = LerpPosHprInterval(
                camera,
                1.5,
                PartyGlobals.TugOfWarCameraPos,
                PartyGlobals.TugOfWarCameraInitialHpr,
                other=self.root,
            )
            self.cameraMoveIval.start()

            self.localToonPosIndex = self.getIndex(base.localAvatar.doId,
                                                   self.localToonTeam)
            self.notify.debug("posIndex: %d" % self.localToonPosIndex)

            toon = self.getAvatar(toonId)
            targetPos = self.dockPositions[self.localToonTeam][
                self.localToonPosIndex]
            # prevent toons from clipping through the dock by warping them to dock height
            if toon.getZ(self.root) < PartyGlobals.TugOfWarToonPositionZ:
                toon.setZ(self.root, PartyGlobals.TugOfWarToonPositionZ)
            targetH = fitDestAngle2Src(
                toon.getH(self.root),
                PartyGlobals.TugOfWarHeadings[self.localToonTeam])
            travelVector = targetPos - toon.getPos(self.root)
            duration = travelVector.length() / 5.0
            if self.toonIdsToAnimIntervals[toonId] is not None:
                self.toonIdsToAnimIntervals[toonId].finish()
            self.toonIdsToAnimIntervals[toonId] = Sequence(
                Func(toon.startPosHprBroadcast, 0.1),
                Func(toon.b_setAnimState, "run"),
                LerpPosHprInterval(toon,
                                   duration,
                                   targetPos,
                                   VBase3(targetH, 0.0, 0.0),
                                   other=self.root),
                Func(toon.stopPosHprBroadcast),
                Func(toon.b_setAnimState, "neutral"),
            )
            self.toonIdsToAnimIntervals[toonId].start()

    def handleToonExited(self, toonId):
        DistributedPartyTeamActivity.handleToonExited(self, toonId)

        # clean up local toon stuff
        if toonId == base.localAvatar.doId:
            self.cameraMoveIval.pause()

            # make toon jump off the dock if needed
            if toonId not in self.fallenToons:
                # finish any existing interval for that toon
                if toonId in self.toonIdsToAnimIntervals and \
                   self.toonIdsToAnimIntervals[toonId] is not None:
                    self.toonIdsToAnimIntervals[toonId].finish()
                toon = self.getAvatar(toonId)
                # clamp targetHeading to minimize spin
                targetH = fitDestAngle2Src(toon.getH(self.root), 180.0)
                targetPos = self.hopOffPositions[self.getTeam(toonId)][
                    self.getIndex(toonId, self.getTeam(toonId))]
                hopOffAnim = Sequence(
                    Func(toon.startPosHprBroadcast, 0.1),
                    toon.hprInterval(0.2,
                                     VBase3(targetH, 0.0, 0.0),
                                     other=self.root),
                    Func(toon.b_setAnimState, "jump", 1.0),
                    Wait(0.4),
                    PartyUtils.arcPosInterval(0.75, toon, targetPos, 5.0,
                                              self.root),
                    Func(toon.stopPosHprBroadcast),
                    # make sure toon ends up on the ground on remote clients
                    Func(toon.sendCurrentPosition),
                    Func(self.hopOffFinished, toonId),
                )
                self.toonIdsToAnimIntervals[toonId] = hopOffAnim
                self._hopOffFinishedSV.set(False)
                self.toonIdsToAnimIntervals[toonId].start()
            # local toons not on the dock are put back into the walk state
            else:
                self._hopOffFinishedSV.set(True)
                del self.toonIdsToAnimIntervals[toonId]

    def handleRewardDone(self):
        # don't call down, it puts the toon in a bad state because it interferes with
        # the 'hopOffAnim'
        self._rewardFinishedSV.set(True)

    def _testWalkStateReady(self, hoppedOff, rewardFinished):
        assert (self.notify.debug("_testWalkStateReady %d %d" %
                                  (hoppedOff, rewardFinished)))

        if hoppedOff and rewardFinished:
            DistributedPartyTeamActivity.handleRewardDone(self)

    def hopOffFinished(self, toonId):
        assert (self.notify.debug("hopOffFinished( toonId=%d )" % toonId))

        if hasattr(self,"toonIdsToAnimIntervals") and \
           toonId in self.toonIdsToAnimIntervals:
            del self.toonIdsToAnimIntervals[toonId]  # clean up anim dictionary

        if toonId == base.localAvatar.doId:
            if hasattr(self._hopOffFinishedSV, '_value'):
                self._hopOffFinishedSV.set(True)

    def handleToonShifted(self, toonId):
        assert (self.notify.debug("handleToonShifted( toonId=%d )" % toonId))

        if toonId == base.localAvatar.doId:
            # update local toon's position on the dock if they got shifted
            self.localToonPosIndex = self.getIndex(base.localAvatar.doId,
                                                   self.localToonTeam)
            if self.toonIdsToAnimIntervals[toonId] is not None:
                self.toonIdsToAnimIntervals[toonId].finish()
            toon = self.getAvatar(toonId)
            targetPos = self.dockPositions[self.localToonTeam][
                self.localToonPosIndex]
            self.toonIdsToAnimIntervals[toonId] = Sequence(
                Wait(0.6),  # give leaving toon time to jump off dock
                Func(toon.startPosHprBroadcast, 0.1),
                Func(toon.b_setAnimState, "run"),
                toon.posInterval(0.5, targetPos, other=self.root),
                Func(toon.stopPosHprBroadcast),
                Func(toon.b_setAnimState, "neutral"),
            )
            self.toonIdsToAnimIntervals[toonId].start()

    def handleToonDisabled(self, toonId):
        """
        A toon dropped unexpectedly from the game. Handle it!
        """
        assert (self.notify.debug("handleToonDisabled( toonId:%d )" % toonId))

        if self.toonIdsToAnimIntervals.has_key(toonId):
            if self.toonIdsToAnimIntervals[toonId]:
                if self.toonIdsToAnimIntervals[toonId].isPlaying():
                    self.toonIdsToAnimIntervals[toonId].finish()
            else:
                self.notify.debug("self.toonIdsToAnimIntervals[%d] is none" %
                                  toonId)

    def setToonsPlaying(self, leftTeamToonIds, rightTeamToonIds):
        """Overrides DistributedPartyActivity's setToonsPlaying"""
        DistributedPartyTeamActivity.setToonsPlaying(self, leftTeamToonIds,
                                                     rightTeamToonIds)

        # update table of right hands
        self.toonIdsToRightHands.clear()
        for toonId in self.getToonIdsAsList():
            toon = self.getAvatar(toonId)
            if toon:
                self.toonIdsToRightHands[toonId] = toon.getRightHands()[0]

    def load(self):
        """
        Load the necessary assets
        """
        DistributedPartyTeamActivity.load(self)

        assert (self.notify.debug("load"))

        self.loadModels()
        self.loadGuiElements()
        self.loadSounds()
        self.loadIntervals()
        self.arrowKeys = ArrowKeys()

    def loadModels(self):
        # load the tug of war play area
        self.playArea = loader.loadModel(
            "phase_13/models/parties/partyTugOfWar")
        # reparent to the party ground root
        self.playArea.reparentTo(self.root)

        # place the activity sign
        self.sign.reparentTo(self.playArea.find("**/TugOfWar_sign_locator"))

        # define initial positions, with index 0 being closest to the other team
        self.dockPositions = [
            [],  # left team positions
            [],  # right team positions
        ]
        for i in range(4):
            self.dockPositions[0].append(
                Point3(
                    -PartyGlobals.TugOfWarInitialToonPositionsXOffset -
                    PartyGlobals.TugOfWarToonPositionXSeparation * i,
                    0.0,
                    PartyGlobals.TugOfWarToonPositionZ,
                ))
        for i in range(4):
            self.dockPositions[1].append(
                Point3(
                    PartyGlobals.TugOfWarInitialToonPositionsXOffset +
                    PartyGlobals.TugOfWarToonPositionXSeparation * i,
                    0.0,
                    PartyGlobals.TugOfWarToonPositionZ,
                ))
        self.hopOffPositions = [
            [],  # left team positions
            [],  # right team positions
        ]
        for i in range(1, 5):
            self.hopOffPositions[
                PartyGlobals.TeamActivityTeams.LeftTeam].append(
                    self.playArea.find("**/leftTeamHopOff%d_locator" %
                                       i).getPos())
            self.hopOffPositions[
                PartyGlobals.TeamActivityTeams.RightTeam].append(
                    self.playArea.find("**/rightTeamHopOff%d_locator" %
                                       i).getPos())

        # load positions for when toons fall into the water
        for i in range(1, 5):
            pos = self.playArea.find("**/fallenToon%d_locator" % i).getPos()
            self.fallenPositions.append(pos)

        # load collision that allows toons to play the game
        # create one for each dock that lets toons join a particular team
        self.joinCollision = []
        self.joinCollisionNodePaths = []
        for i in range(len(PartyGlobals.TeamActivityTeams)):
            collShape = CollisionTube(
                PartyGlobals.TugOfWarJoinCollisionEndPoints[0],
                PartyGlobals.TugOfWarJoinCollisionEndPoints[1],
                PartyGlobals.TugOfWarJoinCollisionRadius)
            collShape.setTangible(True)

            self.joinCollision.append(
                CollisionNode("TugOfWarJoinCollision%d" % i))
            self.joinCollision[i].addSolid(collShape)
            tubeNp = self.playArea.attachNewNode(self.joinCollision[i])
            tubeNp.node().setCollideMask(ToontownGlobals.WallBitmask)
            self.joinCollisionNodePaths.append(tubeNp)
            self.joinCollisionNodePaths[i].setPos(
                PartyGlobals.TugOfWarJoinCollisionPositions[i])
        self.__enableCollisions()

        # Get the rope texture by extracting it from its model.
        ropeModel = loader.loadModel(
            "phase_4/models/minigames/tug_of_war_rope")
        self.ropeTexture = ropeModel.findTexture("*")
        ropeModel.removeNode()

        # create as many ropes as we will ever need
        for i in range(PartyGlobals.TugOfWarMaximumPlayersPerTeam * 2 - 1):
            rope = Rope(self.uniqueName("TugRope%d" % i))
            if rope.showRope:
                rope.ropeNode.setRenderMode(RopeNode.RMBillboard)
                rope.ropeNode.setThickness(0.2)
                rope.setTexture(self.ropeTexture)
                rope.ropeNode.setUvMode(RopeNode.UVDistance)
                rope.ropeNode.setUvDirection(1)
                rope.setTransparency(1)
                rope.setColor(0.89, 0.89, 0.6, 1.0)
                rope.reparentTo(self.root)
                rope.stash()
            self.tugRopes.append(rope)

        # Splash object for when toon hits the water
        self.splash = Splash.Splash(self.root)
        self.splash.setScale(2.0, 4.0, 1.0)
        pos = self.fallenPositions[0]
        self.splash.setPos(pos[0], pos[1], PartyGlobals.TugOfWarSplashZOffset)
        self.splash.hide()

    def loadGuiElements(self):
        # load gui power meter
        self.powerMeter = MinigamePowerMeter(
            PartyGlobals.TugOfWarPowerMeterSize)
        self.powerMeter.reparentTo(aspect2d)
        self.powerMeter.setPos(0.0, 0.0, 0.6)
        self.powerMeter.hide()

        # Load the arrows for button indicator
        self.arrows = [None] * 2
        for x in range(len(self.arrows)):
            self.arrows[x] = loader.loadModel('phase_3/models/props/arrow')
            self.arrows[x].reparentTo(self.powerMeter)
            self.arrows[x].setScale(.2 - .4 * x, .2, .2)
            self.arrows[x].setPos(.12 - .24 * x, 0, -.26)

    def loadSounds(self):
        self.splashSound = base.loadSfx(
            "phase_4/audio/sfx/MG_cannon_splash.mp3")
        self.whistleSound = base.loadSfx(
            "phase_4/audio/sfx/AA_sound_whistle.mp3")

    def loadIntervals(self):
        # create an interval that updates the ideal key press rate for each stage
        self.updateIdealRateInterval = Sequence()
        # other code handles setting the initial ideal rate, so only add the
        # wait for the first state
        self.updateIdealRateInterval.append(
            Wait(PartyGlobals.TugOfWarTargetRateList[0][0]), )
        # for each stage after the first
        for i in range(1, len(PartyGlobals.TugOfWarTargetRateList)):
            duration = PartyGlobals.TugOfWarTargetRateList[i][0]
            idealRate = PartyGlobals.TugOfWarTargetRateList[i][1]
            # set ideal speed
            self.updateIdealRateInterval.append(
                Func(self.setIdealRate, idealRate))
            # add delay for stage's length or set last stage flag
            if i == (len(PartyGlobals.TugOfWarTargetRateList) - 1):
                self.updateIdealRateInterval.append(
                    Func(setattr, self, "allOutMode", True))
            else:
                self.updateIdealRateInterval.append(Wait(duration), )
        # create an interval that updates the local player's key press rate
        self.updateKeyPressRateInterval = Sequence(
            Wait(PartyGlobals.TugOfWarKeyPressUpdateRate),
            Func(self.updateKeyPressRate),
        )
        # create an interval that updates the local player's force and tells the
        # server
        self.reportToServerInterval = Sequence(
            Wait(PartyGlobals.TugOfWarKeyPressReportRate),
            Func(self.reportToServer),
        )

        self.setupInterval = Parallel()
        # run this even if the local toon is not playing
        self.globalSetupInterval = Sequence(
            Wait(PartyGlobals.TugOfWarReadyDuration +
                 PartyGlobals.TugOfWarGoDuration),
            Func(self.tightenRopes),
        )
        # only run this when a local toon is playing
        self.localSetupInterval = Sequence(
            Func(self.setStatus, TTLocalizer.PartyTugOfWarReady),
            Func(self.showStatus),
            Wait(PartyGlobals.TugOfWarReadyDuration),
            Func(base.playSfx, self.whistleSound),
            Func(self.setStatus, TTLocalizer.PartyTugOfWarGo),
            Wait(PartyGlobals.TugOfWarGoDuration),
            Func(self.enableKeys),
            Func(self.hideStatus),
            Func(self.updateIdealRateInterval.start),
            Func(self.updateKeyPressRateInterval.loop),
            Func(self.reportToServerInterval.loop),
        )

        # interval for playing the splash sound and showing the splash visual effect
        self.splashInterval = Sequence(
            Func(base.playSfx, self.splashSound),
            Func(self.splash.play),
        )

    def unload(self):
        DistributedPartyTeamActivity.unload(self)

        self.arrowKeys.destroy()
        self.unloadIntervals()
        self.unloadModels()
        self.unloadGuiElements()
        self.unloadSounds()

        # delete variables
        if hasattr(self, "toonIds"):
            del self.toonIds
        del self.buttons
        del self.arrowKeys
        del self.keyTTL
        del self.idealRate
        del self.keyRate
        del self.allOutMode
        del self.rateMatchAward

        del self.toonIdsToStartPositions
        del self.toonIdsToIsPullingFlags
        del self.toonIdsToRightHands
        del self.fallenToons
        del self.fallenPositions
        del self.unusedFallenPositionsIndices
        self.toonIdsToAnimIntervals.clear()
        del self.toonIdsToAnimIntervals

    def unloadModels(self):
        self.playArea.removeNode()
        del self.playArea

        del self.dockPositions
        del self.hopOffPositions

        self.__disableCollisions()
        while len(self.joinCollision) > 0:
            collNode = self.joinCollision.pop()
            del collNode
        while len(self.joinCollisionNodePaths) > 0:
            collNodePath = self.joinCollisionNodePaths.pop()
            collNodePath.removeNode()
            del collNodePath

        while len(self.tugRopes) > 0:
            rope = self.tugRopes.pop()
            if rope is not None:
                rope.removeNode()
            del rope
        del self.tugRopes

        self.splash.destroy()
        del self.splash

    def unloadGuiElements(self):
        for arrow in self.arrows:
            if arrow is not None:
                arrow.removeNode()
                del arrow
        del self.arrows

        if self.powerMeter is not None:
            self.powerMeter.cleanup()
            del self.powerMeter

    def unloadSounds(self):
        del self.splashSound
        del self.whistleSound

    def unloadIntervals(self):
        self.updateIdealRateInterval.pause()
        del self.updateIdealRateInterval

        self.updateKeyPressRateInterval.pause()
        del self.updateKeyPressRateInterval

        self.reportToServerInterval.pause()
        del self.reportToServerInterval

        self.setupInterval.pause()
        del self.setupInterval

        self.globalSetupInterval.pause()
        del self.globalSetupInterval

        self.localSetupInterval.pause()
        del self.localSetupInterval

        self.splashInterval.pause()
        del self.splashInterval

    def __enableCollisions(self):
        assert (self.notify.debug("__enableCollisions"))

        for i in range(len(PartyGlobals.TeamActivityTeams)):
            self.accept(
                "enterTugOfWarJoinCollision%d" % i,
                getattr(
                    self,
                    "_join%s" % PartyGlobals.TeamActivityTeams.getString(i)))

    def __disableCollisions(self):
        assert (self.notify.debug("__disableCollisions"))

        for i in range(len(PartyGlobals.TeamActivityTeams)):
            self.ignore("enterTugOfWarJoinCollision%d" % i)

    # FSM transition methods
    def startWaitForEnough(self):
        DistributedPartyTeamActivity.startWaitForEnough(self)

        self.__enableCollisions()

    def finishWaitForEnough(self):
        DistributedPartyTeamActivity.finishWaitForEnough(self)

        self.__disableCollisions()

    def startWaitToStart(self, waitStartTimestamp):
        DistributedPartyTeamActivity.startWaitToStart(self, waitStartTimestamp)

        self.__enableCollisions()

    def finishWaitToStart(self):
        DistributedPartyTeamActivity.finishWaitToStart(self)

        self.__disableCollisions()

    def startRules(self):
        DistributedPartyTeamActivity.startRules(self)

        self.setUpRopes()
        # display rules to the local toon if we have one
        if self.isLocalToonPlaying:
            self.showControls()

    def finishRules(self):
        DistributedPartyTeamActivity.finishRules(self)

        # check for a non-standard transition and do additional cleanup as needed
        if self.activityFSM.getCurrentOrNextState() == "WaitForEnough":
            self.hideRopes()
            self.hideControls()

    def finishWaitForServer(self):
        DistributedPartyTeamActivity.finishWaitForServer(self)

        # check for a non-standard transition and do additional cleanup as needed
        if self.activityFSM.getCurrentOrNextState() == "WaitForEnough":
            self.hideRopes()
            self.hideControls()

    def startActive(self):
        DistributedPartyTeamActivity.startActive(self)

        # reset active variables
        self.toonIdsToStartPositions.clear()
        self.toonIdsToIsPullingFlags.clear()

        for toonId in self.getToonIdsAsList():
            self.toonIdsToIsPullingFlags[toonId] = False
            toon = self.getAvatar(toonId)

            if toon:
                self.toonIdsToStartPositions[toonId] = toon.getPos(self.root)
            else:
                # what the heck do we do at this point? lets try 0,0,0
                self.notify.warning(
                    "couldn't find toon %d assigning 0,0,0 to startPos" %
                    toonId)
                self.toonIdsToStartPositions[toonId] = Point3(0, 0, 0)

        self.unusedFallenPositionsIndices = [0, 1, 2, 3]
        self.setupInterval = Parallel(self.globalSetupInterval)

        if self.isLocalToonPlaying:
            self.keyTTL = []
            self.idealForce = 0.0
            self.keyRate = 0
            self.rateMatchAward = 0.0
            self.allOutMode = False
            self.setIdealRate(PartyGlobals.TugOfWarTargetRateList[0][1])
            self.setupInterval.append(self.localSetupInterval)

        self.setupInterval.start()

    def finishActive(self):
        DistributedPartyTeamActivity.finishActive(self)

        self.hideControls()
        self.disableKeys()
        self.setupInterval.pause()
        self.reportToServerInterval.pause()
        self.updateKeyPressRateInterval.pause()
        self.updateIdealRateInterval.pause()
        self.hideRopes()

    def startConclusion(self, losingTeam):
        DistributedPartyTeamActivity.startConclusion(self, losingTeam)

        if self.isLocalToonPlaying:
            self._rewardFinishedSV.set(False)

            if losingTeam == PartyGlobals.TeamActivityNeitherTeam:
                self.setStatus(TTLocalizer.PartyTeamActivityGameTie)
            else:
                self.setStatus(TTLocalizer.PartyTugOfWarGameEnd)

            self.showStatus()

        if losingTeam == PartyGlobals.TeamActivityNeitherTeam:
            # tie
            for toonId in self.getToonIdsAsList():
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop("neutral")
        else:
            # winning and losing team
            for toonId in self.toonIds[losingTeam]:
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop("neutral")
            for toonId in self.toonIds[1 - losingTeam]:
                if self.getAvatar(toonId):
                    self.getAvatar(toonId).loop("victory")

        for ival in self.toonIdsToAnimIntervals.values():
            if ival is not None:
                ival.finish()

    def finishConclusion(self):
        DistributedPartyTeamActivity.finishConclusion(self)

        self.fallenToons = []

    def getTitle(self):
        return TTLocalizer.PartyTugOfWarTitle

    def getInstructions(self):
        return TTLocalizer.TugOfWarInstructions

    def showControls(self):
        # show the power meter and arrows so player can see them while they
        # read the rules
        for arrow in self.arrows:
            arrow.setColor(PartyGlobals.TugOfWarDisabledArrowColor)
        # set meter to first stage values
        self.powerMeter.setTarget(PartyGlobals.TugOfWarTargetRateList[0][1])
        self.powerMeter.setPower(PartyGlobals.TugOfWarTargetRateList[0][1])
        self.powerMeter.setBarColor((0.0, 1.0, 0.0, 0.5))
        self.powerMeter.clearTooSlowTooFast()
        self.powerMeter.show()

    def hideControls(self):
        self.powerMeter.hide()

    def setUpRopes(self):
        self.notify.debug("setUpRopes")
        ropeIndex = 0
        # setup rope linking the left team to the right team
        leftToonId = -1
        if self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam]:
            leftToonId = self.toonIds[
                PartyGlobals.TeamActivityTeams.LeftTeam][0]
        rightToonId = -1
        if self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam]:
            rightToonId = self.toonIds[
                PartyGlobals.TeamActivityTeams.RightTeam][0]
        if leftToonId in self.toonIdsToRightHands and \
           rightToonId in self.toonIdsToRightHands:
            self.tugRopes[ropeIndex].setup(
                3,
                (
                    (self.toonIdsToRightHands[self.toonIds[
                        PartyGlobals.TeamActivityTeams.LeftTeam][0]],
                     (0, 0, 0)),
                    (self.root, (0.0, 0.0, 2.5)),
                    (self.toonIdsToRightHands[self.toonIds[
                        PartyGlobals.TeamActivityTeams.RightTeam][0]],
                     (0, 0, 0)),
                ),
                [0, 0, 0, 1, 1, 1],
            )
            self.tugRopes[ropeIndex].unstash()
            ropeIndex += 1

        # setup ropes linking toons on the left team
        if len(self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam]) > 1:
            for i in range(
                    len(self.toonIds[PartyGlobals.TeamActivityTeams.LeftTeam])
                    - 1, 0, -1):
                self.notify.debug(
                    "Connecting rope between toon %d and toon %d of left team."
                    % (i, i - 1))
                self.tugRopes[ropeIndex].setup(
                    3,
                    (
                        (self.toonIdsToRightHands[self.toonIds[
                            PartyGlobals.TeamActivityTeams.LeftTeam][i]],
                         (0, 0, 0)),
                        (self.toonIdsToRightHands[self.toonIds[
                            PartyGlobals.TeamActivityTeams.LeftTeam][i]],
                         (0, 0, 0)),
                        (self.toonIdsToRightHands[self.toonIds[
                            PartyGlobals.TeamActivityTeams.LeftTeam][i - 1]],
                         (0, 0, 0)),
                    ),
                    [0, 0, 0, 1, 1, 1],
                )
                self.tugRopes[ropeIndex].unstash()
                ropeIndex += 1

        # setup ropes linking toons on the right team
        if len(self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam]) > 1:
            for i in range(
                    len(self.toonIds[PartyGlobals.TeamActivityTeams.RightTeam])
                    - 1):
                self.notify.debug(
                    "Connecting rope between toon %d and toon %d of left team."
                    % (i, i + 1))
                self.tugRopes[ropeIndex].setup(
                    3,
                    (
                        (self.toonIdsToRightHands[self.toonIds[
                            PartyGlobals.TeamActivityTeams.RightTeam][i]],
                         (0, 0, 0)),
                        (self.toonIdsToRightHands[self.toonIds[
                            PartyGlobals.TeamActivityTeams.RightTeam][i]],
                         (0, 0, 0)),
                        (self.toonIdsToRightHands[self.toonIds[
                            PartyGlobals.TeamActivityTeams.RightTeam][i + 1]],
                         (0, 0, 0)),
                    ),
                    [0, 0, 0, 1, 1, 1],
                )
                self.tugRopes[ropeIndex].unstash()
                ropeIndex += 1

    def tightenRopes(self):
        """
        The pulling part has started. Make the rope between the teams taut.
        """
        self.notify.debug("tightenRopes")
        self.tugRopes[0].setup(
            3,
            (
                (self.toonIdsToRightHands[self.toonIds[
                    PartyGlobals.TeamActivityTeams.LeftTeam][0]], (0, 0, 0)),
                (self.toonIdsToRightHands[self.toonIds[
                    PartyGlobals.TeamActivityTeams.LeftTeam][0]], (0, 0, 0)),
                (self.toonIdsToRightHands[self.toonIds[
                    PartyGlobals.TeamActivityTeams.RightTeam][0]], (0, 0, 0)),
            ),
            [0, 0, 0, 1, 1, 1],
        )

    def hideRopes(self):
        self.notify.debug("hideRopes")
        for rope in self.tugRopes:
            rope.stash()

    def handleGameTimerExpired(self):
        assert (self.notify.debug("game timer expired"))

        self.disableKeys()  # do not allow any more input

    def setIdealRate(self, idealRate):
        self.notify.debug("setIdealRate( %d )" % idealRate)
        self.idealRate = idealRate
        self.idealForce = self.advantage * (4 + 0.4 * self.idealRate)

    def updateKeyPressRate(self):
        # decrement times to live for each key press entry in keyTTL
        for i in range(len(self.keyTTL)):
            self.keyTTL[i] -= PartyGlobals.TugOfWarKeyPressUpdateRate

        # remove all key presses that have run out of time to live
        # I think this only removes at most 1 item from the list, which is not
        # what we want, but worked for Trolley tug of war so I'm afraid to "fix" it
        for i in range(len(self.keyTTL)):
            if self.keyTTL[i] <= 0.0:
                a = self.keyTTL[0:i]
                del self.keyTTL
                self.keyTTL = a
                break

        self.keyRate = len(self.keyTTL)

        # if the user has matched the idealRate several times in a row, add
        # a little bit to their power
        if self.keyRate == self.idealRate or self.keyRate == self.idealRate + 1:
            self.rateMatchAward += 0.3
        else:
            self.rateMatchAward = 0.0

    def reportToServer(self):
        self.currentForce = self.computeForce(self.keyRate)
        self.sendUpdate("reportKeyRateForce",
                        [self.keyRate, self.currentForce])
        self.setSpeedGauge()
        self.setAnimState(base.localAvatar.doId, self.keyRate)

    def computeForce(self, keyRate):
        # return a force in the range 0-self.idealRate
        F = 0
        # if this is the last stage, make force directly proportional to keyrate
        if self.allOutMode:
            F = 0.75 * keyRate
        # otherwise, make force proportional to how close you are to ideal key rate
        else:
            stdDev = 0.25 * self.idealRate
            F = self.advantage * (
                self.rateMatchAward + 4 + 0.4 * self.idealRate) * math.pow(
                    math.e, -math.pow(keyRate - self.idealRate, 2) /
                    (2.0 * math.pow(stdDev, 2)))
        return F

    def setSpeedGauge(self):
        # update the power meter to show the toon's speed and the target speed
        self.powerMeter.setPower(self.keyRate)
        self.powerMeter.setTarget(self.idealRate)

        # change the color of the power meter to indicate how well the toon is doing
        # the color should be dark if you are doing badly, and green if you are doing well
        if not self.allOutMode:
            # tell the toon if he is pulling too fast or too slow
            self.powerMeter.updateTooSlowTooFast()
            index = float(self.currentForce) / self.idealForce
            bonus = 0.0
            if index > 1.0:
                bonus = max(1.0, index - 1.0)
                index = 1.0
            color = (0, 0.75 * index + 0.25 * bonus, 0.75 * (1 - index), 0.5)
            self.powerMeter.setBarColor(color)
        else:
            self.powerMeter.setBarColor((0.0, 1.0, 0.0, 0.5))

    def updateToonKeyRate(self, toonId, keyRate):
        # since we set the local toon's pulling animation locally, don't do it
        # here
        if toonId != base.localAvatar.doId:
            self.setAnimState(toonId, keyRate)

    def setAnimState(self, toonId, keyRate):
        if self.activityFSM.state != "Active":
            return
        toon = self.getAvatar(toonId)
        if not self.toonIdsToIsPullingFlags.has_key(toonId):
            if self.getTeam(toonId) == None:
                self.notify.warning(
                    "setAnimState called with toonId (%d) that wasn't in self.toonIds"
                    % toonId)
                return
            else:
                self.notify.warning(
                    "setAnimState called with toonId (%d) that was in self.toonIds but not in self.toonIdsToIsPullingFlags. Adding it."
                    % toonId)
                self.toonIdsToIsPullingFlags[toonId] = False

        if keyRate > 0 and not self.toonIdsToIsPullingFlags[toonId]:
            if toon:
                toon.loop('tug-o-war')
            else:
                self.notify.warning(
                    "toon %d is None, skipping toon.loop(tugowar)" % toonId)
            self.toonIdsToIsPullingFlags[toonId] = True
        if keyRate <= 0 and self.toonIdsToIsPullingFlags[toonId]:
            if toon:
                toon.pose('tug-o-war', 3)
                toon.startLookAround()
            else:
                self.notify.warning(
                    "toon %d is None, skipping toon.startLookAround" % toonId)
            self.toonIdsToIsPullingFlags[toonId] = False

    def enableKeys(self):
        self.notify.debug("enableKeys")
        # Change the order of the press handlers because we are only using 2 keys
        self.arrowKeys.setPressHandlers([
            lambda: self.__pressHandler(2),
            lambda: self.__pressHandler(3),
            lambda: self.__pressHandler(1),
            lambda: self.__pressHandler(0),
        ])
        self.arrowKeys.setReleaseHandlers([
            lambda: self.__releaseHandler(2),
            lambda: self.__releaseHandler(3),
            lambda: self.__releaseHandler(1),
            lambda: self.__releaseHandler(0),
        ])
        for arrow in self.arrows:
            arrow.setColor(PartyGlobals.TugOfWarEnabledArrowColor)

    def disableKeys(self):
        self.arrowKeys.setPressHandlers(self.arrowKeys.NULL_HANDLERS)
        self.arrowKeys.setReleaseHandlers(self.arrowKeys.NULL_HANDLERS)

    # callbacks for when the buttons are pressed and released
    def __pressHandler(self, index):
        if index == self.buttons[0]:
            self.arrows[index].setColor(
                PartyGlobals.TugOfWarHilightedArrowColor)
            self.keyTTL.insert(0, PartyGlobals.TugOfWarKeyPressTimeToLive)
            self.buttons.reverse()

    def __releaseHandler(self, index):
        if index in self.buttons:
            self.arrows[index].setColor(PartyGlobals.TugOfWarEnabledArrowColor)

    def updateToonPositions(self, offset):
        # Since the timer expires locally, we may still get a few
        # messages from the AI that were on the wire when we left
        # the play state, just ignore it
        if self.activityFSM.state != "Active":
            return

        # adjust the camera angle
        if self.isLocalToonPlaying:
            camera.lookAt(self.root, offset, 0.0,
                          PartyGlobals.TugOfWarCameraLookAtHeightOffset)

        # this client sets the position of all toons playing
        for toonId in self.getToonIdsAsList():
            if hasattr(self,"fallenToons") and \
               toonId not in self.fallenToons:
                toon = self.getAvatar(toonId)
                if toon is not None:
                    origPos = self.toonIdsToStartPositions[toonId]
                    curPos = toon.getPos(self.root)
                    newPos = Point3(origPos[0] + offset, curPos[1], curPos[2])
                    # finish any existing animation interval
                    if self.toonIdsToAnimIntervals[toonId] != None:
                        if self.toonIdsToAnimIntervals[toonId].isPlaying():
                            self.toonIdsToAnimIntervals[toonId].finish()
                            self.checkIfFallen(toonId)

                    if toonId not in self.fallenToons:
                        self.toonIdsToAnimIntervals[toonId] = Sequence(
                            LerpPosInterval(
                                toon,
                                duration=PartyGlobals.
                                TugOfWarKeyPressReportRate,
                                pos=newPos,
                                other=self.root,
                            ), Func(self.checkIfFallen, toonId))
                        self.toonIdsToAnimIntervals[toonId].start()

    def checkIfFallen(self, toonId):
        # check if toon has fallen
        if hasattr(self,"fallenToons") and \
           toonId not in self.fallenToons:
            toon = self.getAvatar(toonId)
            if toon:
                curPos = toon.getPos(self.root)
                team = self.getTeam(toonId)
                if ((team == PartyGlobals.TeamActivityTeams.LeftTeam
                     and curPos[0] > -2.0)
                        or (team == PartyGlobals.TeamActivityTeams.RightTeam
                            and curPos[0] < 2.0)):
                    # throw all toons from this side in the water
                    losingTeam = self.getTeam(toonId)
                    self.throwTeamInWater(losingTeam)
                    # tell AI that a team fell in the water
                    self.sendUpdate("reportFallIn", [losingTeam])

    def throwTeamInWater(self, losingTeam):
        self.notify.debug("throwTeamInWater( %s )" %
                          PartyGlobals.TeamActivityTeams.getString(losingTeam))
        splashSet = False
        for toonId in self.toonIds[losingTeam]:
            # throw toon in water
            self.fallenToons.append(toonId)
            toon = self.getAvatar(toonId)
            # getting a a crash of popping from empty list
            #fallenPosIndex = self.unusedFallenPositionsIndices.pop(0)
            fallenPosIndex = self.toonIds[losingTeam].index(toonId)
            if (fallenPosIndex < 0) or (fallenPosIndex >= 4):
                fallenPosIndex = 0
            newPos = self.fallenPositions[fallenPosIndex]

            # animate the toons falling into the water
            if self.toonIdsToAnimIntervals[toonId] is not None:
                if self.toonIdsToAnimIntervals[toonId].isPlaying():
                    self.toonIdsToAnimIntervals[toonId].finish()

            # Fall into water
            if toon:
                parallel = Parallel(
                    ActorInterval(actor=toon,
                                  animName='slip-forward',
                                  duration=2.0),
                    LerpPosInterval(toon,
                                    duration=2.0,
                                    pos=newPos,
                                    other=self.root),
                )
            else:
                self.notify.warning("toon %d is none, skipping slip-forward" %
                                    toonId)
                parallel = Parallel()

            # only setup splash for the first toon
            if not splashSet:
                splashSet = True
                parallel.append(self.splashInterval)

            if toon:
                self.toonIdsToAnimIntervals[toonId] = Sequence(
                    parallel,
                    Func(toon.loop, 'neutral'),
                )
            else:
                self.notify.warning(
                    "toon %d is none, skipping toon.loop(neutral)" % toonId)
                self.toonIdsToAnimIntervals[toonId] = parallel
            self.toonIdsToAnimIntervals[toonId].start()

    def setAdvantage(self, advantage):
        DistributedPartyTeamActivity.setAdvantage(self, advantage)

        if self.isLocalToonPlaying:
            self.setIdealRate(PartyGlobals.TugOfWarTargetRateList[0][1])
Пример #7
0
class DistributedPartyDanceActivityBase(DistributedPartyActivity):
    notify = directNotify.newCategory("DistributedPartyDanceActivity")

    def __init__(self, cr, actId, dancePatternToAnims):
        DistributedPartyActivity.__init__(self, cr, actId,
                                          ActivityTypes.Continuous)
        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
        # Map of toonIds to PartyDanceActivityToonFSMs
        self.dancingToonFSMs = {}

    def generateInit(self):
        self.notify.debug("generateInit")
        DistributedPartyActivity.generateInit(self)

        self.keyCodes = KeyCodes(
            patterns=list(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):
        """
        Loads the dance floor and place it in the right spot.
        """
        DistributedPartyActivity.load(self)

        self.danceFloor = loader.loadModel(
            "phase_13/models/parties/danceFloor")
        self.danceFloor.reparentTo(self.getParentNodePath())
        self.danceFloor.setPos(self.x, self.y, 0.0)
        self.danceFloor.setH(self.h)

        # Reparent to render so that when the fireworks are on, it "glows" in the dark
        self.danceFloor.wrtReparentTo(render)

        self.sign.setPos(22, -22, 0)

        # Initialize programatic animation sequences
        floor = self.danceFloor.find("**/danceFloor_mesh")
        self.danceFloorSequence = Sequence(Wait(0.3),
                                           Func(floor.setH, floor, 36))

        # Spin the ball around while bobbing up and down
        # (since it's being held by balloons!)
        # spinning the disco ball moved to the child classes,
        # to deal with 10 and 20 on the ball
        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):
        """
        Unloads the dance floor.
        """
        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 list(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

    def handleToonDisabled(self, toonId):
        """This will be called if an avatar exits unexpectedly"""
        self.notify.debug("handleToonDisabled avatar " + str(toonId) +
                          " disabled")
        # clean up any references to the disabled avatar before he disappears
        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

#===============================================================================
# FSM States
#===============================================================================

    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

#===============================================================================
# Ortho movement
#===============================================================================

# Orthowalk init/shutdown

    def __initOrthoWalk(self):
        """
        Initializes ortho walk movement for the local toon.
        Orthowalk is movement where up is +y and right is +x in relation to the toon's parent
        """
        self.notify.debug("Initialize Ortho Walk")

        orthoDrive = OrthoDrive(
            9.778
        )  # run speed = run frames (15) / fps (24fps) * avg. run speed (14.667 ft./s)
        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()

#===============================================================================
# Enter / Exit Dance Floor
#===============================================================================

    def __handleEnterDanceFloor(self, collEntry):
        """
        Triggered when the local toon enters the dance floor collision.
        """
        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")

    # Distributed (broadcast ram)
    def setToonsPlaying(self, toonIds, toonHeadings):
        """
        Overrides DistributedPartyActivity's setToonsPlaying because it needs
        heading information for each toon.
        """
        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)

        # Handle the joining toons
        for toonId in joinedToons:

            # Only trigger handleToonJoined if it isn't the local Toon
            # or if the local Toon is joining this activity.
            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

    def handleToonJoined(self, toonId, h):
        """
        Called when toon is allowed to enter dance floor.
        """
        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):
        """
        Local toon is entering dance floor. Listen for extra events and enable
        ortho movement.
        """
        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):
        """
        Triggered when the local toon exits the dance floor collision.
        """
        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()

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

    def handleToonExited(self, toonId):
        """
        Stops the local toon from dancing.
        Called when when the client gets an exit response from the server.
        """
        self.notify.debug("exitDanceFloor %s" % toonId)
        if toonId == base.localAvatar.doId:
            self.__localStopDancing()

    def __localStopDancing(self):
        """
        Cleans up local dancing toon and broadcasts final state to all clients
        """
        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

#===============================================================================
# Dance!
#===============================================================================

    def __doDanceMove(self, pattern):
        """
        Handler called when there is a pattern match
        """
        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))

            # Local toon just matched this pattern for the first time
            # play fancier animation.
            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() * (camera.getH(camNode) /
                                    abs(camera.getH(camNode)))
                node2.removeNode
                del node2

                hpr = camera.getHpr()
                pos = camera.getPos()
                camParent = camera.getParent()

                camera.wrtReparentTo(camNode)
                self.localToonDanceSequence = Sequence(
                    Func(self.__localDisableControls),
                    Parallel(
                        camera.posInterval(0.5,
                                           Point3(0, 15, 10),
                                           blendType="easeIn"),
                        camera.hprInterval(0.5,
                                           Point3(h, -20, 0),
                                           blendType="easeIn"),
                    ),
                    camNode.hprInterval(4.0, Point3(camNode.getH() - 360, 0,
                                                    0)),
                    Func(camera.wrtReparentTo, camParent),
                    Func(camNode.removeNode),
                    Parallel(camera.posInterval(0.5, pos, blendType="easeOut"),
                             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):
        """
        Called when a match fails.
        """
        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):
        """
        Called when a key in KeyCodes is pressed down.
        """
        self.__updateLocalToonState(ToonDancingStates.Run)

    def _handleKeyUp(self, key):
        """
        Called when a key in KeyCodes is pressed up.
        """
        if not self.keyCodes.isAnyKeyPressed():
            self.__updateLocalToonState(ToonDancingStates.DanceMove)
            self.acceptOnce(KeyCodes.KEY_DOWN_EVENT, self._handleKeyDown)

#===============================================================================
# Dancing Toon State
#===============================================================================

    def __updateLocalToonState(self, state, anim=""):
        """
        Sets the dancing toon's local and remote fsm. This is done immediately
        in the local side, while the state is sent to the AI for the other clients.
        """
        self._requestToonState(base.localAvatar.doId, state, anim)
        self.d_updateDancingToon(state, anim)

    # Distributed (clsend airecv)
    def d_updateDancingToon(self, state, anim):
        self.sendUpdate("updateDancingToon", [state, anim])

    # Distributed (broadcast ram)
    def setDancingToonState(self, toonId, state, anim):
        """
        From AI, it sets a dancing toon's FSM
        """
        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()
            assert (self.notify.debug(
                "requestToonState toonId=%s, state=%s, anim=%s" %
                (toonId, state, anim)))
            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]
                # the local Toon dance sequence has camera reparents, which is bad if
                # we're not in the dance floor anymore.
                if self.localToonDanceSequence:
                    self.notify.debug(
                        "forcing a finish of localToonDanceSequence")
                    self.localToonDanceSequence.finish()
                    self.localToonDanceSequence = None

#===============================================================================
# Camera
#===============================================================================

    def __setViewMode(self, mode):
        """
        Changes the camera mode and controls according to the camera.
        Called typically when toon enters/exits the dance floor.
        """
        assert (self.notify.debug("Set camera mode to %d" % mode))
        toon = base.localAvatar

        if mode == DanceViews.Normal:
            """
            if self.currentCameraMode == DanceViews.Isometric:
                base.cam.node().setLens(self.clens)
            """

            if self.cameraParallel is not None:
                self.cameraParallel.pause()
                self.cameraParallel = None

            camera.reparentTo(toon)
            base.localAvatar.startUpdateSmartCamera()

        elif mode == DanceViews.Dancing:
            base.localAvatar.stopUpdateSmartCamera()
            camera.wrtReparentTo(self.danceFloor)

            # Get the destination of the camera
            # based on the orientation of the toon parent dance node.
            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(
                camera.posInterval(0.5, pos, blendType="easeIn"),
                camera.hprInterval(0.5,
                                   Point3(0, -27, 0),
                                   other=toon.getParent(),
                                   blendType="easeIn"))
            self.cameraParallel.start()
        """
        if mode == DanceViews.Isometric:
            if not hasattr(self, 'olens'):
                self.olens = OrthographicLens()
                self.olens.setFilmSize(20, 15)  # or whatever is appropriate for your scene
                self.clens = base.cam.node().getLens()
            base.cam.node().setLens(self.olens)

            def handleF1Pressed(self):
                if base.cam.node().getLens() == self.clens:
                    base.cam.node().setLens(self.olens)
                else:
                    base.cam.node().setLens(self.clens)
            self.accept('f1', handleF1Pressed)
        """

        self.currentCameraMode = mode
class DistributedMazeGame(DistributedMinigame):
    notify = directNotify.newCategory('DistributedMazeGame')
    CAMERA_TASK = 'MazeGameCameraTask'
    UPDATE_SUITS_TASK = 'MazeGameUpdateSuitsTask'
    TREASURE_GRAB_EVENT_NAME = 'MazeTreasureGrabbed'

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedMazeGame', [State.State('off', self.enterOff, self.exitOff, ['play']),
         State.State('play', self.enterPlay, self.exitPlay, ['cleanup', 'showScores']),
         State.State('showScores', self.enterShowScores, self.exitShowScores, ['cleanup']),
         State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.usesLookAround = 1

    def getTitle(self):
        return TTLocalizer.MazeGameTitle

    def getInstructions(self):
        return TTLocalizer.MazeGameInstructions

    def getMaxDuration(self):
        return MazeGameGlobals.GAME_DURATION

    def __defineConstants(self):
        self.TOON_SPEED = 8.0
        self.TOON_Z = 0
        self.MinSuitSpeedRange = [0.8 * self.TOON_SPEED, 0.6 * self.TOON_SPEED]
        self.MaxSuitSpeedRange = [1.1 * self.TOON_SPEED, 2.0 * self.TOON_SPEED]
        self.FASTER_SUIT_CURVE = 1
        self.SLOWER_SUIT_CURVE = self.getDifficulty() < 0.5
        self.slowerSuitPeriods = {2000: {4: [128, 76],
                8: [128,
                    99,
                    81,
                    68],
                12: [128,
                     108,
                     93,
                     82,
                     74,
                     67],
                16: [128,
                     112,
                     101,
                     91,
                     83,
                     76,
                     71,
                     66]},
         1000: {4: [110, 69],
                8: [110,
                    88,
                    73,
                    62],
                12: [110,
                     95,
                     83,
                     74,
                     67,
                     61],
                16: [110,
                     98,
                     89,
                     81,
                     75,
                     69,
                     64,
                     60]},
         5000: {4: [96, 63],
                8: [96,
                    79,
                    66,
                    57],
                12: [96,
                     84,
                     75,
                     67,
                     61,
                     56],
                16: [96,
                     87,
                     80,
                     73,
                     68,
                     63,
                     59,
                     55]},
         4000: {4: [86, 58],
                8: [86,
                    71,
                    61,
                    53],
                12: [86,
                     76,
                     68,
                     62,
                     56,
                     52],
                16: [86,
                     78,
                     72,
                     67,
                     62,
                     58,
                     54,
                     51]},
         3000: {4: [78, 54],
                8: [78,
                    65,
                    56,
                    49],
                12: [78,
                     69,
                     62,
                     57,
                     52,
                     48],
                16: [78,
                     71,
                     66,
                     61,
                     57,
                     54,
                     51,
                     48]},
         9000: {4: [71, 50],
                8: [71,
                    60,
                    52,
                    46],
                12: [71,
                     64,
                     58,
                     53,
                     49,
                     45],
                16: [71,
                     65,
                     61,
                     57,
                     53,
                     50,
                     47,
                     45]}}
        self.slowerSuitPeriodsCurve = {2000: {4: [128, 65],
                8: [128,
                    78,
                    66,
                    64],
                12: [128,
                     88,
                     73,
                     67,
                     64,
                     64],
                16: [128,
                     94,
                     79,
                     71,
                     67,
                     65,
                     64,
                     64]},
         1000: {4: [110, 59],
                8: [110,
                    70,
                    60,
                    58],
                12: [110,
                     78,
                     66,
                     61,
                     59,
                     58],
                16: [110,
                     84,
                     72,
                     65,
                     61,
                     59,
                     58,
                     58]},
         5000: {4: [96, 55],
                8: [96,
                    64,
                    56,
                    54],
                12: [96,
                     71,
                     61,
                     56,
                     54,
                     54],
                16: [96,
                     76,
                     65,
                     59,
                     56,
                     55,
                     54,
                     54]},
         4000: {4: [86, 51],
                8: [86,
                    59,
                    52,
                    50],
                12: [86,
                     65,
                     56,
                     52,
                     50,
                     50],
                16: [86,
                     69,
                     60,
                     55,
                     52,
                     51,
                     50,
                     50]},
         3000: {4: [78, 47],
                8: [78,
                    55,
                    48,
                    47],
                12: [78,
                     60,
                     52,
                     48,
                     47,
                     47],
                16: [78,
                     63,
                     55,
                     51,
                     49,
                     47,
                     47,
                     47]},
         9000: {4: [71, 44],
                8: [71,
                    51,
                    45,
                    44],
                12: [71,
                     55,
                     48,
                     45,
                     44,
                     44],
                16: [71,
                     58,
                     51,
                     48,
                     45,
                     44,
                     44,
                     44]}}
        self.fasterSuitPeriods = {2000: {4: [54, 42],
                8: [59,
                    52,
                    47,
                    42],
                12: [61,
                     56,
                     52,
                     48,
                     45,
                     42],
                16: [61,
                     58,
                     54,
                     51,
                     49,
                     46,
                     44,
                     42]},
         1000: {4: [50, 40],
                8: [55,
                    48,
                    44,
                    40],
                12: [56,
                     52,
                     48,
                     45,
                     42,
                     40],
                16: [56,
                     53,
                     50,
                     48,
                     45,
                     43,
                     41,
                     40]},
         5000: {4: [47, 37],
                8: [51,
                    45,
                    41,
                    37],
                12: [52,
                     48,
                     45,
                     42,
                     39,
                     37],
                16: [52,
                     49,
                     47,
                     44,
                     42,
                     40,
                     39,
                     37]},
         4000: {4: [44, 35],
                8: [47,
                    42,
                    38,
                    35],
                12: [48,
                     45,
                     42,
                     39,
                     37,
                     35],
                16: [49,
                     46,
                     44,
                     42,
                     40,
                     38,
                     37,
                     35]},
         3000: {4: [41, 33],
                8: [44,
                    40,
                    36,
                    33],
                12: [45,
                     42,
                     39,
                     37,
                     35,
                     33],
                16: [45,
                     43,
                     41,
                     39,
                     38,
                     36,
                     35,
                     33]},
         9000: {4: [39, 32],
                8: [41,
                    37,
                    34,
                    32],
                12: [42,
                     40,
                     37,
                     35,
                     33,
                     32],
                16: [43,
                     41,
                     39,
                     37,
                     35,
                     34,
                     33,
                     32]}}
        self.fasterSuitPeriodsCurve = {2000: {4: [62, 42],
                8: [63,
                    61,
                    54,
                    42],
                12: [63,
                     63,
                     61,
                     56,
                     50,
                     42],
                16: [63,
                     63,
                     62,
                     60,
                     57,
                     53,
                     48,
                     42]},
         1000: {4: [57, 40],
                8: [58,
                    56,
                    50,
                    40],
                12: [58,
                     58,
                     56,
                     52,
                     46,
                     40],
                16: [58,
                     58,
                     57,
                     56,
                     53,
                     49,
                     45,
                     40]},
         5000: {4: [53, 37],
                8: [54,
                    52,
                    46,
                    37],
                12: [54,
                     53,
                     52,
                     48,
                     43,
                     37],
                16: [54,
                     54,
                     53,
                     51,
                     49,
                     46,
                     42,
                     37]},
         4000: {4: [49, 35],
                8: [50,
                    48,
                    43,
                    35],
                12: [50,
                     49,
                     48,
                     45,
                     41,
                     35],
                16: [50,
                     50,
                     49,
                     48,
                     46,
                     43,
                     39,
                     35]},
         3000: {4: [46, 33],
                8: [47,
                    45,
                    41,
                    33],
                12: [47,
                     46,
                     45,
                     42,
                     38,
                     33],
                16: [47,
                     46,
                     46,
                     45,
                     43,
                     40,
                     37,
                     33]},
         9000: {4: [43, 32],
                8: [44,
                    42,
                    38,
                    32],
                12: [44,
                     43,
                     42,
                     40,
                     36,
                     32],
                16: [44,
                     44,
                     43,
                     42,
                     40,
                     38,
                     35,
                     32]}}
        self.CELL_WIDTH = MazeData.CELL_WIDTH
        self.MAX_FRAME_MOVE = self.CELL_WIDTH / 2
        startOffset = 3
        self.startPosHTable = [[Point3(0, startOffset, self.TOON_Z), 0],
         [Point3(0, -startOffset, self.TOON_Z), 180],
         [Point3(startOffset, 0, self.TOON_Z), 270],
         [Point3(-startOffset, 0, self.TOON_Z), 90]]
        self.camOffset = Vec3(0, -19, 45)

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.__defineConstants()
        mazeName = MazeGameGlobals.getMazeName(self.doId, self.numPlayers, MazeData.mazeNames)
        self.maze = Maze.Maze(mazeName)
        model = loader.loadModel('phase_3.5/models/props/mickeySZ')
        self.treasureModel = model.find('**/mickeySZ')
        model.removeNode()
        self.treasureModel.setScale(1.6)
        self.treasureModel.setP(-90)
        self.music = base.loadMusic('phase_4/audio/bgm/MG_toontag.ogg')
        self.toonHitTracks = {}
        self.scorePanels = []

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        del self.toonHitTracks
        self.maze.destroy()
        del self.maze
        self.treasureModel.removeNode()
        del self.treasureModel
        del self.music
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.maze.onstage()
        self.randomNumGen.shuffle(self.startPosHTable)
        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        self.camParent = render.attachNewNode('mazeGameCamParent')
        self.camParent.reparentTo(base.localAvatar)
        self.camParent.setPos(0, 0, 0)
        self.camParent.setHpr(render, 0, 0, 0)
        camera.reparentTo(self.camParent)
        camera.setPos(self.camOffset)
        self.__spawnCameraTask()
        self.toonRNGs = []
        for i in xrange(self.numPlayers):
            self.toonRNGs.append(RandomNumGen.RandomNumGen(self.randomNumGen))

        self.treasures = []
        for i in xrange(self.maze.numTreasures):
            self.treasures.append(MazeTreasure.MazeTreasure(self.treasureModel, self.maze.treasurePosList[i], i, self.doId))

        self.__loadSuits()
        for suit in self.suits:
            suit.onstage()

        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')

        self.grabSounds = []
        for i in xrange(5):
            self.grabSounds.append(base.loadSfx('phase_4/audio/sfx/MG_maze_pickup.ogg'))

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

        self.scores = [0] * self.numPlayers
        self.goalBar = DirectWaitBar(parent=render2d, relief=DGG.SUNKEN, frameSize=(-0.35,
         0.35,
         -0.15,
         0.15), borderWidth=(0.02, 0.02), scale=0.42, pos=(0.84, 0, 0.5 - 0.28 * self.numPlayers + 0.05), barColor=(0, 0.7, 0, 1))
        self.goalBar.setBin('unsorted', 0)
        self.goalBar.hide()
        self.introTrack = self.getIntroTrack()
        self.introTrack.start()
        return

    def offstage(self):
        self.notify.debug('offstage')
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        del self.introTrack
        for avId in self.toonHitTracks.keys():
            track = self.toonHitTracks[avId]
            if track.isPlaying():
                track.finish()

        self.__killCameraTask()
        camera.wrtReparentTo(render)
        self.camParent.removeNode()
        del self.camParent
        for panel in self.scorePanels:
            panel.cleanup()

        self.scorePanels = []
        self.goalBar.destroy()
        del self.goalBar
        base.setCellsAvailable(base.rightCells, 1)
        for suit in self.suits:
            suit.offstage()

        self.__unloadSuits()
        for treasure in self.treasures:
            treasure.destroy()

        del self.treasures
        del self.sndTable
        del self.grabSounds
        del self.toonRNGs
        self.maze.offstage()
        base.localAvatar.showName()
        DistributedMinigame.offstage(self)

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if self.numPlayers == 1:
            toon.setPos(0, 0, self.TOON_Z)
            toon.setHpr(180, 0, 0)
        else:
            posIndex = self.avIdList.index(avId)
            toon.setPos(self.startPosHTable[posIndex][0])
            toon.setHpr(self.startPosHTable[posIndex][1], 0, 0)

    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.setAnimState('Happy', 1.0)
                toon.startSmooth()
                toon.startLookAround()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.stopLookAround()

        self.gameFSM.request('play')

    def handleDisabledAvatar(self, avId):
        hitTrack = self.toonHitTracks[avId]
        if hitTrack.isPlaying():
            hitTrack.finish()
        DistributedMinigame.handleDisabledAvatar(self, avId)

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

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        for i in xrange(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = MinigameAvatarScorePanel.MinigameAvatarScorePanel(avId, avName)
            scorePanel.reparentTo(base.a2dTopRight)
            scorePanel.setPos(-0.213, 0.0, -0.5 - 0.28 * i)
            self.scorePanels.append(scorePanel)

        self.goalBar.show()
        self.goalBar['value'] = 0.0
        base.setCellsAvailable(base.rightCells, 0)
        self.__spawnUpdateSuitsTask()
        orthoDrive = OrthoDrive(self.TOON_SPEED, maxFrameMove=self.MAX_FRAME_MOVE, customCollisionCallback=self.__doMazeCollisions, priority=1)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=not self.isSinglePlayer())
        self.orthoWalk.start()
        self.accept(MazeSuit.COLLISION_EVENT_NAME, self.__hitBySuit)
        self.accept(self.TREASURE_GRAB_EVENT_NAME, self.__treasureGrabbed)
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(MazeGameGlobals.GAME_DURATION)
        self.timer.countdown(MazeGameGlobals.GAME_DURATION, self.timerExpired)
        self.accept('resetClock', self.__resetClock)
        base.playMusic(self.music, looping=0, volume=0.8)

    def exitPlay(self):
        self.notify.debug('exitPlay')
        self.ignore('resetClock')
        self.ignore(MazeSuit.COLLISION_EVENT_NAME)
        self.ignore(self.TREASURE_GRAB_EVENT_NAME)
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        self.__killUpdateSuitsTask()
        self.timer.stop()
        self.timer.destroy()
        del self.timer
        for avId in self.avIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.loop('neutral')

    def __resetClock(self, tOffset):
        self.notify.debug('resetClock')
        self.gameStartTime += tOffset
        self.timer.countdown(self.timer.currentTime + tOffset, self.timerExpired)

    def __treasureGrabbed(self, treasureNum):
        self.treasures[treasureNum].showGrab()
        self.grabSounds[self.grabSoundIndex].play()
        self.grabSoundIndex = (self.grabSoundIndex + 1) % len(self.grabSounds)
        self.sendUpdate('claimTreasure', [treasureNum])

    def setTreasureGrabbed(self, avId, treasureNum):
        if not self.hasLocalToon:
            return
        if avId != self.localAvId:
            self.treasures[treasureNum].showGrab()
        i = self.avIdList.index(avId)
        self.scores[i] += 1
        self.scorePanels[i].setScore(self.scores[i])
        total = 0
        for score in self.scores:
            total += score

        self.goalBar['value'] = 100.0 * (float(total) / float(self.maze.numTreasures))

    def __hitBySuit(self, suitNum):
        self.notify.debug('hitBySuit')
        timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime())
        self.sendUpdate('hitBySuit', [self.localAvId, timestamp])
        self.__showToonHitBySuit(self.localAvId, timestamp)

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

    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)
        flyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        while 1:
            endTile = [rng.randint(2, self.maze.width-1), rng.randint(2, self.maze.height-1)]
            if self.maze.isWalkable(endTile[0], endTile[1]):
                break
        endWorldCoords = self.maze.tile2world(endTile[0], endTile[1])
        endPos = Point3(endWorldCoords[0], endWorldCoords[1], startPos[2])
        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'))
        if avId != self.localAvId:
            cameraTrack = Sequence()
        else:
            self.camParent.reparentTo(parentNode)
            startCamPos = camera.getPos()
            destCamPos = camera.getPos()
            zenith = trajectory.getPos(flyDur/2.0)[2]
            destCamPos.setZ(zenith*1.3)
            destCamPos.setY(destCamPos[1]*0.3)
            def camTask(task, zenith = zenith, flyNode = toon, startCamPos = startCamPos, camOffset = destCamPos - startCamPos):
                u = flyNode.getZ()/zenith
                camera.setPos(startCamPos + camOffset*u)
                camera.lookAt(toon)
                return Task.cont
            camTaskName = 'mazeToonFlyCam-' + `avId`
            taskMgr.add(camTask, camTaskName, priority=20)
            def cleanupCamTask(self = self, toon = toon, camTaskName = camTaskName, startCamPos = startCamPos):
                taskMgr.remove(camTaskName)
                self.camParent.reparentTo(toon)
                camera.setPos(startCamPos)
                camera.lookAt(toon)

            cameraTrack = Sequence(
                Wait(flyDur),
                Func(cleanupCamTask),
                name='hitBySuit-cameraLerp')

        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.orthoWalk.stop()
            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, 'orthoWalk'):
                    if self.gameFSM.getCurrentState().getName() == 'play':
                        self.orthoWalk.start()
            dropShadow.removeNode()
            del dropShadow
            toon.dropShadow.show()
            geomNode = toon.getGeomNode()
            rotNode = geomNode.getParent()
            baseNode = rotNode.getParent()
            geomNode.reparentTo(baseNode)
            rotNode.removeNode()
            del rotNode
            geomNode.setZ(oldGeomNodeZ)
            toon.reparentTo(render)
            toon.setPos(endPos)
            parentNode.removeNode()
            del parentNode
            if avId != self.localAvId:
                toon.startSmooth()

        preFunc()

        hitTrack = Sequence(Parallel(flyTrack, cameraTrack,
                                     spinHTrack, spinPTrack, soundTrack),
                            Func(postFunc),
                            name=toon.uniqueName('hitBySuit'))

        self.toonHitTracks[avId] = hitTrack

        hitTrack.start(globalClockDelta.localElapsedTime(timestamp))

    def allTreasuresTaken(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('all treasures taken')
        if not MazeGameGlobals.ENDLESS_GAME:
            self.gameFSM.request('showScores')

    def timerExpired(self):
        self.notify.debug('local timer expired')
        if not MazeGameGlobals.ENDLESS_GAME:
            self.gameFSM.request('showScores')

    def __doMazeCollisions(self, oldPos, newPos):
        offset = newPos - oldPos
        WALL_OFFSET = 1.0
        curX = oldPos[0]
        curY = oldPos[1]
        curTX, curTY = self.maze.world2tile(curX, curY)

        def calcFlushCoord(curTile, newTile, centerTile):
            EPSILON = 0.01
            if newTile > curTile:
                return (newTile - centerTile) * self.CELL_WIDTH - EPSILON - WALL_OFFSET
            else:
                return (curTile - centerTile) * self.CELL_WIDTH + WALL_OFFSET

        offsetX = offset[0]
        offsetY = offset[1]
        WALL_OFFSET_X = WALL_OFFSET
        if offsetX < 0:
            WALL_OFFSET_X = -WALL_OFFSET_X
        WALL_OFFSET_Y = WALL_OFFSET
        if offsetY < 0:
            WALL_OFFSET_Y = -WALL_OFFSET_Y
        newX = curX + offsetX + WALL_OFFSET_X
        newY = curY
        newTX, newTY = self.maze.world2tile(newX, newY)
        if newTX != curTX:
            if self.maze.collisionTable[newTY][newTX]:
                offset.setX(calcFlushCoord(curTX, newTX, self.maze.originTX) - curX)
        newX = curX
        newY = curY + offsetY + WALL_OFFSET_Y
        newTX, newTY = self.maze.world2tile(newX, newY)
        if newTY != curTY:
            if self.maze.collisionTable[newTY][newTX]:
                offset.setY(calcFlushCoord(curTY, newTY, self.maze.originTY) - curY)
        offsetX = offset[0]
        offsetY = offset[1]
        newX = curX + offsetX + WALL_OFFSET_X
        newY = curY + offsetY + WALL_OFFSET_Y
        newTX, newTY = self.maze.world2tile(newX, newY)
        if self.maze.collisionTable[newTY][newTX]:
            cX = calcFlushCoord(curTX, newTX, self.maze.originTX)
            cY = calcFlushCoord(curTY, newTY, self.maze.originTY)
            if abs(cX - curX) < abs(cY - curY):
                offset.setX(cX - curX)
            else:
                offset.setY(cY - curY)
        return oldPos + offset

    def __spawnCameraTask(self):
        self.notify.debug('spawnCameraTask')
        camera.lookAt(base.localAvatar)
        taskMgr.remove(self.CAMERA_TASK)
        taskMgr.add(self.__cameraTask, self.CAMERA_TASK, priority=45)

    def __killCameraTask(self):
        self.notify.debug('killCameraTask')
        taskMgr.remove(self.CAMERA_TASK)

    def __cameraTask(self, task):
        self.camParent.setHpr(render, 0, 0, 0)
        return Task.cont

    def __loadSuits(self):
        self.notify.debug('loadSuits')
        self.suits = []
        self.numSuits = 4 * self.numPlayers
        safeZone = self.getSafezoneId()
        slowerTable = self.slowerSuitPeriods
        if self.SLOWER_SUIT_CURVE:
            slowerTable = self.slowerSuitPeriodsCurve
        slowerPeriods = slowerTable[safeZone][self.numSuits]
        fasterTable = self.fasterSuitPeriods
        if self.FASTER_SUIT_CURVE:
            fasterTable = self.fasterSuitPeriodsCurve
        fasterPeriods = fasterTable[safeZone][self.numSuits]
        suitPeriods = slowerPeriods + fasterPeriods
        self.notify.debug('suit periods: ' + `suitPeriods`)
        self.randomNumGen.shuffle(suitPeriods)
        for i in xrange(self.numSuits):
            self.suits.append(MazeSuit(i, self.maze, self.randomNumGen, suitPeriods[i], self.getDifficulty()))

    def __unloadSuits(self):
        self.notify.debug('unloadSuits')
        for suit in self.suits:
            suit.destroy()

        self.suits = []

    def __spawnUpdateSuitsTask(self):
        self.notify.debug('spawnUpdateSuitsTask')
        for suit in self.suits:
            suit.gameStart(self.gameStartTime)

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

    def __killUpdateSuitsTask(self):
        self.notify.debug('killUpdateSuitsTask')
        taskMgr.remove(self.UPDATE_SUITS_TASK)
        for suit in self.suits:
            suit.gameEnd()

    def __updateSuitsTask(self, task):
        curT = globalClock.getFrameTime() - self.gameStartTime
        curTic = int(curT * float(MazeGameGlobals.SUIT_TIC_FREQ))
        suitUpdates = []
        for i in xrange(len(self.suits)):
            updateTics = self.suits[i].getThinkTimestampTics(curTic)
            suitUpdates.extend(zip(updateTics, [i] * len(updateTics)))

        suitUpdates.sort(lambda a, b: a[0] - b[0])
        if len(suitUpdates) > 0:
            curTic = 0
            for i in xrange(len(suitUpdates)):
                update = suitUpdates[i]
                tic = update[0]
                suitIndex = update[1]
                suit = self.suits[suitIndex]
                if tic > curTic:
                    curTic = tic
                    j = i + 1
                    while j < len(suitUpdates):
                        if suitUpdates[j][0] > tic:
                            break
                        self.suits[suitUpdates[j][1]].prepareToThink()
                        j += 1

                unwalkables = []
                for si in xrange(suitIndex):
                    unwalkables.extend(self.suits[si].occupiedTiles)

                for si in xrange(suitIndex + 1, len(self.suits)):
                    unwalkables.extend(self.suits[si].occupiedTiles)

                suit.think(curTic, curT, unwalkables)

        return Task.cont

    def enterShowScores(self):
        self.notify.debug('enterShowScores')
        lerpTrack = Parallel()
        lerpDur = 0.5
        lerpTrack.append(Parallel(LerpPosInterval(self.goalBar, lerpDur, Point3(0, 0, -.6), blendType='easeInOut'), LerpScaleInterval(self.goalBar, lerpDur, Vec3(self.goalBar.getScale()) * 2.0, blendType='easeInOut')))
        tY = 0.6
        bY = -.05
        lX = -.5
        cX = 0
        rX = 0.5
        scorePanelLocs = (((cX, bY),),
         ((lX, bY), (rX, bY)),
         ((cX, tY), (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()) * 2.0, blendType='easeInOut')))

        self.showScoreTrack = Parallel(lerpTrack, Sequence(Wait(MazeGameGlobals.SHOWSCORES_DURATION), Func(self.gameOver)))
        self.showScoreTrack.start()

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

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

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

    def exitCleanup(self):
        pass

    def getIntroTrack(self):
        self.__cameraTask(None)
        origCamParent = camera.getParent()
        origCamPos = camera.getPos()
        origCamHpr = camera.getHpr()
        iCamParent = base.localAvatar.attachNewNode('iCamParent')
        iCamParent.setH(180)
        camera.reparentTo(iCamParent)
        toonHeight = base.localAvatar.getHeight()
        camera.setPos(0, -15, toonHeight * 3)
        camera.lookAt(0, 0, toonHeight / 2.0)
        iCamParent.wrtReparentTo(origCamParent)
        waitDur = 5.0
        lerpDur = 4.5
        lerpTrack = Parallel()
        startHpr = iCamParent.getHpr()
        startHpr.setX(PythonUtil.reduceAngle(startHpr[0]))
        lerpTrack.append(LerpPosHprInterval(iCamParent, lerpDur, pos=Point3(0, 0, 0), hpr=Point3(0, 0, 0), startHpr=startHpr, name=self.uniqueName('introLerpParent')))
        lerpTrack.append(LerpPosHprInterval(camera, lerpDur, pos=origCamPos, hpr=origCamHpr, blendType='easeInOut', name=self.uniqueName('introLerpCameraPos')))
        base.localAvatar.startLookAround()

        def cleanup(origCamParent = origCamParent, origCamPos = origCamPos, origCamHpr = origCamHpr, iCamParent = iCamParent):
            camera.reparentTo(origCamParent)
            camera.setPos(origCamPos)
            camera.setHpr(origCamHpr)
            iCamParent.removeNode()
            del iCamParent
            base.localAvatar.stopLookAround()

        return Sequence(Wait(waitDur),
                        lerpTrack,
                        Func(cleanup))
Пример #9
0
class MazeSuit(DirectObject):
    COLL_SPHERE_NAME = 'MazeSuitSphere'
    COLLISION_EVENT_NAME = 'MazeSuitCollision'
    MOVE_IVAL_NAME = 'moveMazeSuit'
    DIR_UP = 0
    DIR_DOWN = 1
    DIR_LEFT = 2
    DIR_RIGHT = 3
    oppositeDirections = [
        DIR_DOWN,
        DIR_UP,
        DIR_RIGHT,
        DIR_LEFT]
    directionHs = [
        0,
        180,
        90,
        270]
    DEFAULT_SPEED = 4.0
    SUIT_Z = 0.10000000000000001
    
    def __init__(self, serialNum, maze, randomNumGen, cellWalkPeriod, difficulty, suitDnaName = 'f', startTile = None, ticFreq = MazeGameGlobals.SUIT_TIC_FREQ, walkSameDirectionProb = MazeGameGlobals.WALK_SAME_DIRECTION_PROB, walkTurnAroundProb = MazeGameGlobals.WALK_TURN_AROUND_PROB, uniqueRandomNumGen = True, walkAnimName = None):
        self.serialNum = serialNum
        self.maze = maze
        if uniqueRandomNumGen:
            self.rng = RandomNumGen(randomNumGen)
        else:
            self.rng = randomNumGen
        self.difficulty = difficulty
        self._walkSameDirectionProb = walkSameDirectionProb
        self._walkTurnAroundProb = walkTurnAroundProb
        if not walkAnimName:
            pass
        self._walkAnimName = 'walk'
        self.suit = Suit.Suit()
        d = SuitDNA.SuitDNA()
        d.newSuit(suitDnaName)
        self.suit.setDNA(d)
        if startTile is None:
            defaultStartPos = MazeGameGlobals.SUIT_START_POSITIONS[self.serialNum]
            self.startTile = (defaultStartPos[0] * self.maze.width, defaultStartPos[1] * self.maze.height)
        else:
            self.startTile = startTile
        self.ticFreq = ticFreq
        self.ticPeriod = int(cellWalkPeriod)
        self.cellWalkDuration = float(self.ticPeriod) / float(self.ticFreq)
        self.turnDuration = 0.59999999999999998 * self.cellWalkDuration

    
    def destroy(self):
        self.suit.delete()

    
    def uniqueName(self, str):
        return str + `self.serialNum`

    
    def gameStart(self, gameStartTime):
        self.gameStartTime = gameStartTime
        self.initCollisions()
        self.startWalkAnim()
        self.occupiedTiles = [
            (self.nextTX, self.nextTY)]
        n = 20
        self.nextThinkTic = self.serialNum * self.ticFreq / n
        self.fromPos = Point3(0, 0, 0)
        self.toPos = Point3(0, 0, 0)
        self.fromHpr = Point3(0, 0, 0)
        self.toHpr = Point3(0, 0, 0)
        self.moveIval = WaitInterval(1.0)

    
    def gameEnd(self):
        self.moveIval.pause()
        del self.moveIval
        self.shutdownCollisions()
        self.suit.loop('neutral')

    
    def initCollisions(self):
        self.collSphere = CollisionSphere(0, 0, 0, 2.0)
        self.collSphere.setTangible(0)
        self.collNode = CollisionNode(self.uniqueName(self.COLL_SPHERE_NAME))
        self.collNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.collNode.addSolid(self.collSphere)
        self.collNodePath = self.suit.attachNewNode(self.collNode)
        self.collNodePath.hide()
        self.accept(self.uniqueName('enter' + self.COLL_SPHERE_NAME), self.handleEnterSphere)

    
    def shutdownCollisions(self):
        self.ignore(self.uniqueName('enter' + self.COLL_SPHERE_NAME))
        del self.collSphere
        self.collNodePath.removeNode()
        del self.collNodePath
        del self.collNode

    
    def handleEnterSphere(self, collEntry):
        messenger.send(self.COLLISION_EVENT_NAME, [
            self.serialNum])

    
    def _MazeSuit__getWorldPos(self, sTX, sTY):
        (wx, wy) = self.maze.tile2world(sTX, sTY)
        return Point3(wx, wy, self.SUIT_Z)

    
    def onstage(self):
        sTX = int(self.startTile[0])
        sTY = int(self.startTile[1])
        c = 0
        lim = 0
        toggle = 0
        direction = 0
        while not self.maze.isWalkable(sTX, sTY):
            if 0 == direction:
                sTX -= 1
            elif 1 == direction:
                sTY -= 1
            elif 2 == direction:
                sTX += 1
            elif 3 == direction:
                sTY += 1
            
            c += 1
            if c > lim:
                c = 0
                direction = (direction + 1) % 4
                toggle += 1
                if not toggle & 1:
                    lim += 1
                
        self.TX = sTX
        self.TY = sTY
        self.direction = self.DIR_DOWN
        self.lastDirection = self.direction
        self.nextTX = self.TX
        self.nextTY = self.TY
        self.suit.setPos(self._MazeSuit__getWorldPos(self.TX, self.TY))
        self.suit.setHpr(self.directionHs[self.direction], 0, 0)
        self.suit.reparentTo(render)
        self.suit.pose(self._walkAnimName, 0)
        self.suit.loop('neutral')

    
    def offstage(self):
        self.suit.reparentTo(hidden)

    
    def startWalkAnim(self):
        self.suit.loop(self._walkAnimName)
        speed = float(self.maze.cellWidth) / self.cellWalkDuration
        self.suit.setPlayRate(speed / self.DEFAULT_SPEED, self._walkAnimName)

    
    def _MazeSuit__applyDirection(self, dir, TX, TY):
        if self.DIR_UP == dir:
            TY += 1
        elif self.DIR_DOWN == dir:
            TY -= 1
        elif self.DIR_LEFT == dir:
            TX -= 1
        elif self.DIR_RIGHT == dir:
            TX += 1
        
        return (TX, TY)

    
    def _MazeSuit__chooseNewWalkDirection(self, unwalkables):
        if not self.rng.randrange(self._walkSameDirectionProb):
            (newTX, newTY) = self._MazeSuit__applyDirection(self.direction, self.TX, self.TY)
            if self.maze.isWalkable(newTX, newTY, unwalkables):
                return self.direction
            
        
        if self.difficulty >= 0.5:
            if not self.rng.randrange(self._walkTurnAroundProb):
                oppositeDir = self.oppositeDirections[self.direction]
                (newTX, newTY) = self._MazeSuit__applyDirection(oppositeDir, self.TX, self.TY)
                if self.maze.isWalkable(newTX, newTY, unwalkables):
                    return oppositeDir
                
            
        
        candidateDirs = [
            self.DIR_UP,
            self.DIR_DOWN,
            self.DIR_LEFT,
            self.DIR_RIGHT]
        candidateDirs.remove(self.oppositeDirections[self.direction])
        while len(candidateDirs):
            dir = self.rng.choice(candidateDirs)
            (newTX, newTY) = self._MazeSuit__applyDirection(dir, self.TX, self.TY)
            if self.maze.isWalkable(newTX, newTY, unwalkables):
                return dir
            
            candidateDirs.remove(dir)
        return self.oppositeDirections[self.direction]

    
    def getThinkTimestampTics(self, curTic):
        if curTic < self.nextThinkTic:
            return []
        else:
            r = range(self.nextThinkTic, curTic + 1, self.ticPeriod)
            self.lastTicBeforeRender = r[-1]
            return r

    
    def prepareToThink(self):
        self.occupiedTiles = [
            (self.nextTX, self.nextTY)]

    
    def think(self, curTic, curT, unwalkables):
        self.TX = self.nextTX
        self.TY = self.nextTY
        self.lastDirection = self.direction
        self.direction = self._MazeSuit__chooseNewWalkDirection(unwalkables)
        (self.nextTX, self.nextTY) = self._MazeSuit__applyDirection(self.direction, self.TX, self.TY)
        self.occupiedTiles = [
            (self.TX, self.TY),
            (self.nextTX, self.nextTY)]
        if curTic == self.lastTicBeforeRender:
            fromCoords = self.maze.tile2world(self.TX, self.TY)
            toCoords = self.maze.tile2world(self.nextTX, self.nextTY)
            self.fromPos.set(fromCoords[0], fromCoords[1], self.SUIT_Z)
            self.toPos.set(toCoords[0], toCoords[1], self.SUIT_Z)
            self.moveIval = LerpPosInterval(self.suit, self.cellWalkDuration, self.toPos, startPos = self.fromPos, name = self.uniqueName(self.MOVE_IVAL_NAME))
            if self.direction != self.lastDirection:
                self.fromH = self.directionHs[self.lastDirection]
                toH = self.directionHs[self.direction]
                if self.fromH == 270 and toH == 0:
                    self.fromH = -90
                elif self.fromH == 0 and toH == 270:
                    self.fromH = 360
                
                self.fromHpr.set(self.fromH, 0, 0)
                self.toHpr.set(toH, 0, 0)
                turnIval = LerpHprInterval(self.suit, self.turnDuration, self.toHpr, startHpr = self.fromHpr, name = self.uniqueName('turnMazeSuit'))
                self.moveIval = Parallel(self.moveIval, turnIval, name = self.uniqueName(self.MOVE_IVAL_NAME))
            else:
                self.suit.setH(self.directionHs[self.direction])
            moveStartT = float(self.nextThinkTic) / float(self.ticFreq)
            self.moveIval.start(curT - moveStartT + self.gameStartTime)
        
        self.nextThinkTic += self.ticPeriod

    
    def thinkSuits(suitList, startTime, ticFreq = MazeGameGlobals.SUIT_TIC_FREQ):
        curT = globalClock.getFrameTime() - startTime
        curTic = int(curT * float(ticFreq))
        suitUpdates = []
        for i in xrange(len(suitList)):
            updateTics = suitList[i].getThinkTimestampTics(curTic)
            suitUpdates.extend(zip(updateTics, [
                i] * len(updateTics)))
        
        suitUpdates.sort(lambda a, b: a[0] - b[0])
        if len(suitUpdates) > 0:
            curTic = 0
            for i in xrange(len(suitUpdates)):
                update = suitUpdates[i]
                tic = update[0]
                suitIndex = update[1]
                suit = suitList[suitIndex]
                if tic > curTic:
                    curTic = tic
                    j = i + 1
                    while j < len(suitUpdates):
                        if suitUpdates[j][0] > tic:
                            break
                        
                        suitList[suitUpdates[j][1]].prepareToThink()
                        j += 1
                
                unwalkables = []
                for si in xrange(suitIndex):
                    unwalkables.extend(suitList[si].occupiedTiles)
                
                for si in xrange(suitIndex + 1, len(suitList)):
                    unwalkables.extend(suitList[si].occupiedTiles)
                
                suit.think(curTic, curT, unwalkables)
            
        

    thinkSuits = staticmethod(thinkSuits)
Пример #10
0
class MusicBox(DirectObject):
    def __init__(self):
        # Our standard title and instructions text
        self.title = OnscreenText(text="Panda3D: Tutorial - Music Box",
                                  parent=base.a2dBottomCenter,
                                  pos=(0, 0.08), scale=0.08,
                                  fg=(1, 1, 1, 1), shadow=(0, 0, 0, .5))
        self.escapeText = OnscreenText(text="ESC: Quit", parent=base.a2dTopLeft,
                                       fg=(1, 1, 1, 1), pos=(0.06, -0.1),
                                       align=TextNode.ALeft, scale=.05)

        # Set up the key input
        self.accept('escape', sys.exit)

        # Fix the camera position
        base.disableMouse()

        # Loading sounds is done in a similar way to loading other things
        # Loading the main music box song
        self.musicBoxSound = loader.loadMusic('music/musicbox.ogg')
        self.musicBoxSound.setVolume(.5)  # Volume is a percentage from 0 to 1
        # 0 means loop forever, 1 (default) means
        # play once. 2 or higher means play that many times
        self.musicBoxSound.setLoopCount(0)

        # Set up a simple light.
        self.plight = PointLight("light")
        self.plight.setColor((0.7, 0.7, 0.5, 1))
        light_path = base.render.attachNewNode(self.plight)
        light_path.setPos(0, 0, 20)
        base.render.setLight(light_path)

        alight = AmbientLight("ambient")
        alight.setColor((0.3, 0.3, 0.4, 1))
        base.render.setLight(base.render.attachNewNode(alight))

        # Enable per-pixel lighting
        base.render.setShaderAuto()

        # Sound objects do not have a pause function, just play and stop. So we will
        # Use this variable to keep track of where the sound is at when it was stoped
        # to impliment pausing
        self.musicTime = 0

        # Loading the open/close effect
        # loadSFX and loadMusic are identical. They are often used for organization
        #(loadMusic is used for background music, loadSfx is used for other effects)
        self.lidSfx = loader.loadSfx('music/openclose.ogg')
        # The open/close file has both effects in it. Fortunatly we can use intervals
        # to easily define parts of a sound file to play
        self.lidOpenSfx = SoundInterval(self.lidSfx, duration=2, startTime=0)
        self.lidCloseSfx = SoundInterval(self.lidSfx, startTime=5)

        # For this tutorial, it seemed appropriate to have on screen controls.
        # The following code creates them.
        # This is a label for a slider
        self.sliderText = OnscreenText("Volume", pos=(-0.1, 0.87), scale=.07,
                                       fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
        # The slider itself. It calls self.setMusicBoxVolume when changed
        self.slider = DirectSlider(pos=(-0.1, 0, .75), scale=0.8, value=.50,
                                   command=self.setMusicBoxVolume)
        # A button that calls self.toggleMusicBox when pressed
        self.button = DirectButton(pos=(.9, 0, .75), text="Open",
                                   scale=.1, pad=(.2, .2),
                                   rolloverSound=None, clickSound=None,
                                   command=self.toggleMusicBox)

        # A variable to represent the state of the simulation. It starts closed
        self.boxOpen = False

        # Here we load and set up the music box. It was modeled in a complex way, so
        # setting it up will be complicated
        self.musicBox = loader.loadModel('models/MusicBox')
        self.musicBox.setPos(0, 60, -9)
        self.musicBox.reparentTo(render)
        # Just like the scene graph contains hierarchies of nodes, so can
        # models. You can get the NodePath for the node using the find
        # function, and then you can animate the model by moving its parts
        # To see the hierarchy of a model, use, the ls function
        # self.musicBox.ls() prints out the entire hierarchy of the model

        # Finding pieces of the model
        self.Lid = self.musicBox.find('**/lid')
        self.Panda = self.musicBox.find('**/turningthing')

        # This model was made with the hinge in the wrong place
        # this is here so we have something to turn
        self.HingeNode = self.musicBox.find(
            '**/box').attachNewNode('nHingeNode')
        self.HingeNode.setPos(.8659, 6.5, 5.4)
        # WRT - ie with respect to. Reparents the object without changing
        # its position, size, or orientation
        self.Lid.wrtReparentTo(self.HingeNode)
        self.HingeNode.setHpr(0, 90, 0)

        # This sets up an interval to play the close sound and actually close the box
        # at the same time.
        self.lidClose = Parallel(
            self.lidCloseSfx,
            LerpHprInterval(self.HingeNode, 2.0, (0, 90, 0), blendType='easeInOut'))

        # Same thing for opening the box
        self.lidOpen = Parallel(
            self.lidOpenSfx,
            LerpHprInterval(self.HingeNode, 2.0, (0, 0, 0), blendType='easeInOut'))

        # The interval for turning the panda
        self.PandaTurn = self.Panda.hprInterval(7, (360, 0, 0))
        # Do a quick loop and pause to set it as a looping interval so it can be
        # started with resume and loop properly
        self.PandaTurn.loop()
        self.PandaTurn.pause()

    def setMusicBoxVolume(self):
        # Simply reads the current value from the slider and sets it in the
        # sound
        newVol = self.slider.guiItem.getValue()
        self.musicBoxSound.setVolume(newVol)

    def toggleMusicBox(self):
        #if self.lidOpen.isPlaying() or self.lidClose.isPlaying():
        #    # It's currently already opening or closing
        #    return

        if self.boxOpen:
            self.lidOpen.pause()

            self.lidClose.start()  # Start the close box interval
            self.PandaTurn.pause()  # Pause the figurine turning
            # Save the current time of the music
            self.musicTime = self.musicBoxSound.getTime()
            self.musicBoxSound.stop()  # Stop the music
            self.button['text'] = "Open"  # Prepare to change button label
        else:
            self.lidClose.pause()

            self.lidOpen.start()  # Start the open box interval
            self.PandaTurn.resume()  # Resume the figuring turning
            # Reset the time of the music so it starts where it left off
            self.musicBoxSound.setTime(self.musicTime)
            self.musicBoxSound.play()  # Play the music
            self.button['text'] = "Close"  # Prepare to change button label

        self.button.setText()  # Actually change the button label
        # Set our state to opposite what it was
        self.boxOpen = not self.boxOpen
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 self.dancingToonFSMs.has_key(toonId):
            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 base.cr.doId2do.has_key(toonId):
            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 not self.dancingToonFSMs.has_key(base.localAvatar.doId):
            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() * (camera.getH(camNode) / abs(camera.getH(camNode)))
                node2.removeNode
                del node2
                hpr = camera.getHpr()
                pos = camera.getPos()
                camParent = camera.getParent()
                camera.wrtReparentTo(camNode)
                self.localToonDanceSequence = Sequence(Func(self.__localDisableControls), Parallel(camera.posInterval(0.5, Point3(0, 15, 10), blendType='easeIn'), camera.hprInterval(0.5, Point3(h, -20, 0), blendType='easeIn')), camNode.hprInterval(4.0, Point3(camNode.getH() - 360, 0, 0)), Func(camera.wrtReparentTo, camParent), Func(camNode.removeNode), Parallel(camera.posInterval(0.5, pos, blendType='easeOut'), 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 self.dancingToonFSMs.has_key(toonId):
            self._requestToonState(toonId, state, anim)

    def _requestToonState(self, toonId, state, anim):
        if self.dancingToonFSMs.has_key(toonId):
            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
            camera.reparentTo(toon)
            base.localAvatar.startUpdateSmartCamera()
        elif mode == DanceViews.Dancing:
            base.localAvatar.stopUpdateSmartCamera()
            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(camera.posInterval(0.5, pos, blendType='easeIn'), camera.hprInterval(0.5, Point3(0, -27, 0), other=toon.getParent(), blendType='easeIn'))
            self.cameraParallel.start()
        self.currentCameraMode = mode
        return