def load(self, cogdoMazeFactory, numSuits, bossCode):
        self._initAudio()
        self.maze = cogdoMazeFactory.createCogdoMaze()
        suitSpawnSpot = self.maze.createRandomSpotsList(numSuits, self.distGame.randomNumGen)
        self.guiMgr = CogdoMazeGuiManager(self.maze, bossCode)
        self.suits = []
        self.suitsById = {}
        self.shakers = []
        self.toonsThatRevealedDoor = []
        self.quake = 0
        self.dropCounter = 0
        self.drops = {}
        self.gagCounter = 0
        self.gags = []
        self.hackTemp = False
        self.dropGen = RandomNumGen(self.distGame.doId)
        self.gagTimeoutTasks = []
        self.finished = False
        self.lastBalloonTimestamp = None
        difficulty = self.distGame.getDifficulty()
        serialNum = 0
        for i in range(numSuits[0]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeBossSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[0][i])
            self.addSuit(suit)
            self.guiMgr.mazeMapGui.addSuit(suit.suit)
            serialNum += 1

        for i in range(numSuits[1]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeFastMinionSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[1][i])
            self.addSuit(suit)
            serialNum += 1

        for i in range(numSuits[2]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeSlowMinionSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[2][i])
            self.addSuit(suit)
            serialNum += 1

        self.toonId2Door = {}
        self.keyIdToKey = {}
        self.players = []
        self.toonId2Player = {}
        cellPos = (int(self.maze.width / 2), self.maze.height - 1)
        pos = self.maze.tile2world(*cellPos)
        self._exit = CogdoMazeExit()
        self._exit.reparentTo(render)
        self._exit.setPos(self.maze.exitPos)
        self._exit.stash()
        self.guiMgr.mazeMapGui.placeExit(*cellPos)
        self._collNode2waterCooler = {}
        for waterCooler in self.maze.getWaterCoolers():
            pos = waterCooler.getPos(render)
            tpos = self.maze.world2tile(pos[0], pos[1])
            self.guiMgr.mazeMapGui.addWaterCooler(*tpos)
            self._collNode2waterCooler[waterCooler.collNode] = waterCooler

        self.pickups = []
        self.gagModel = CogdoUtil.loadMazeModel('waterBalloon')
        self._movie = CogdoMazeGameIntro(self.maze, self._exit, self.distGame.randomNumGen)
        self._movie.load()
        return
예제 #2
0
class CogdoMazeGame(DirectObject):
    notify = directNotify.newCategory('CogdoMazeGame')
    UpdateTaskName = 'CogdoMazeGameUpdateTask'
    RemoveGagTaskName = 'CogdoMazeGameRemoveGag'
    PlayerCoolerCollision = '%s-into-%s' % (Globals.LocalPlayerCollisionName,
                                            Globals.WaterCoolerCollisionName)
    PlayerDropCollision = '%s-into-%s' % (Globals.LocalPlayerCollisionName,
                                          Globals.DropCollisionName)

    def __init__(self, distGame):
        self.distGame = distGame
        self._allowSuitsHitToons = base.config.GetBool(
            'cogdomaze-suits-hit-toons', True)

    def load(self, cogdoMazeFactory, numSuits, bossCode):
        self._initAudio()
        self.maze = cogdoMazeFactory.createCogdoMaze()
        suitSpawnSpot = self.maze.createRandomSpotsList(
            numSuits, self.distGame.randomNumGen)
        self.guiMgr = CogdoMazeGuiManager(self.maze, bossCode)
        self.suits = []
        self.suitsById = {}
        self.shakers = []
        self.toonsThatRevealedDoor = []
        self.quake = 0
        self.dropCounter = 0
        self.drops = {}
        self.gagCounter = 0
        self.gags = []
        self.hackTemp = False
        self.dropGen = RandomNumGen(self.distGame.doId)
        self.gagTimeoutTasks = []
        self.finished = False
        self.lastBalloonTimestamp = None
        difficulty = self.distGame.getDifficulty()
        serialNum = 0
        for i in xrange(numSuits[0]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeBossSuit(serialNum,
                                     self.maze,
                                     suitRng,
                                     difficulty,
                                     startTile=suitSpawnSpot[0][i])
            self.addSuit(suit)
            self.guiMgr.mazeMapGui.addSuit(suit.suit)
            serialNum += 1

        for i in xrange(numSuits[1]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeFastMinionSuit(serialNum,
                                           self.maze,
                                           suitRng,
                                           difficulty,
                                           startTile=suitSpawnSpot[1][i])
            self.addSuit(suit)
            serialNum += 1

        for i in xrange(numSuits[2]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeSlowMinionSuit(serialNum,
                                           self.maze,
                                           suitRng,
                                           difficulty,
                                           startTile=suitSpawnSpot[2][i])
            self.addSuit(suit)
            serialNum += 1

        self.toonId2Door = {}
        self.keyIdToKey = {}
        self.players = []
        self.toonId2Player = {}
        cellPos = (int(self.maze.width / 2), self.maze.height - 1)
        pos = self.maze.tile2world(*cellPos)
        self._exit = CogdoMazeExit()
        self._exit.reparentTo(render)
        self._exit.setPos(self.maze.exitPos)
        self._exit.stash()
        self.guiMgr.mazeMapGui.placeExit(*cellPos)
        self._collNode2waterCooler = {}
        for waterCooler in self.maze.getWaterCoolers():
            pos = waterCooler.getPos(render)
            tpos = self.maze.world2tile(pos[0], pos[1])
            self.guiMgr.mazeMapGui.addWaterCooler(*tpos)
            self._collNode2waterCooler[waterCooler.collNode] = waterCooler

        self.pickups = []
        self.gagModel = CogdoUtil.loadMazeModel('waterBalloon')
        self._movie = CogdoMazeGameIntro(self.maze, self._exit,
                                         self.distGame.randomNumGen)
        self._movie.load()
        return

    def _initAudio(self):
        self._audioMgr = CogdoGameAudioManager(Globals.MusicFiles,
                                               Globals.SfxFiles,
                                               base.camera,
                                               cutoff=Globals.AudioCutoff)
        self._quakeSfx1 = self._audioMgr.createSfx('quake')
        self._quakeSfx2 = self._audioMgr.createSfx('quake')

    def _destroyAudio(self):
        self._quakeSfx1.destroy()
        self._quakeSfx2.destroy()
        del self._quakeSfx1
        del self._quakeSfx2
        self._audioMgr.destroy()
        del self._audioMgr

    def addSuit(self, suit):
        id = suit.serialNum
        self.suits.append(suit)
        if suit.type == Globals.SuitTypes.Boss:
            self.shakers.append(suit)
        self.suitsById[id] = suit

    def removeSuit(self, suit):
        id = suit.serialNum
        del self.suitsById[id]
        if suit.type == Globals.SuitTypes.Boss:
            self.shakers.remove(suit)
            self.guiMgr.showBossCode(id)
            self.guiMgr.mazeMapGui.removeSuit(suit.suit)
        self.suits.remove(suit)
        suit.destroy()

    def unload(self):
        self.toonsThatRevealedDoor = []
        for suit in self.suits:
            suit.destroy()

        del self.suits
        for id in self.drops.keys():
            self.cleanupDrop(id)

        self.__stopUpdateTask()
        self.ignoreAll()
        self.maze.destroy()
        del self.maze
        self._exit.destroy()
        del self._exit
        self.guiMgr.destroy()
        del self.guiMgr
        for player in self.players:
            player.destroy()

        del self.players
        del self.toonId2Player
        del self.localPlayer
        for pickup in self.pickups:
            pickup.destroy()

        self._destroyAudio()
        del self.distGame

    def onstage(self):
        self._exit.onstage()
        self.maze.onstage()

    def offstage(self):
        self.maze.offstage()
        self._exit.offstage()
        for suit in self.suits:
            suit.offstage()

    def startIntro(self):
        self._movie.play()
        self._audioMgr.playMusic('normal')

    def endIntro(self):
        self._movie.end()
        self._movie.unload()
        del self._movie
        for player in self.players:
            self.placePlayer(player)
            if player.toon is localAvatar:
                localAvatar.sendCurrentPosition()
            player.request('Ready')

    def startFinish(self):
        self._movie = CogdoMazeGameFinish(self.localPlayer, self._exit)
        self._movie.load()
        self._movie.play()

    def endFinish(self):
        self._movie.end()
        self._movie.unload()
        del self._movie

    def placeEntranceElevator(self, elevator):
        pos = self.maze.elevatorPos
        elevator.setPos(pos)
        tpos = self.maze.world2tile(pos[0], pos[1])
        self.guiMgr.mazeMapGui.placeEntrance(*tpos)

    def initPlayers(self):
        for toonId in self.distGame.getToonIds():
            toon = self.distGame.getToon(toonId)
            if toon is not None:
                if toon.isLocal():
                    player = CogdoMazeLocalPlayer(len(self.players),
                                                  base.localAvatar, self,
                                                  self.guiMgr)
                    self.localPlayer = player
                else:
                    player = CogdoMazePlayer(len(self.players), toon)
                self._addPlayer(player)

        return

    def start(self):
        self.accept(self.PlayerDropCollision, self.handleLocalToonMeetsDrop)
        self.accept(self.PlayerCoolerCollision,
                    self.handleLocalToonMeetsWaterCooler)
        self.accept(CogdoMazeExit.EnterEventName,
                    self.handleLocalToonEntersDoor)
        self.accept(CogdoMemo.EnterEventName, self.handleLocalToonMeetsPickup)
        if self._allowSuitsHitToons:
            self.accept(CogdoMazeSuit.COLLISION_EVENT_NAME,
                        self.handleLocalToonMeetsSuit)
        self.accept(CogdoMazePlayer.GagHitEventName, self.handleToonMeetsGag)
        self.accept(CogdoMazeSuit.GagHitEventName,
                    self.handleLocalSuitMeetsGag)
        self.accept(CogdoMazeSuit.DeathEventName, self.handleSuitDeath)
        self.accept(CogdoMazeSuit.ThinkEventName, self.handleSuitThink)
        self.accept(CogdoMazeBossSuit.ShakeEventName, self.handleBossShake)
        self.accept(CogdoMazePlayer.RemovedEventName, self._removePlayer)
        self.__startUpdateTask()
        for player in self.players:
            player.handleGameStart()
            player.request('Normal')

        for suit in self.suits:
            suit.onstage()
            suit.gameStart(self.distGame.getStartTime())

    def exit(self):
        self._quakeSfx1.stop()
        self._quakeSfx2.stop()
        for suit in self.suits:
            suit.gameEnd()

        self.ignore(self.PlayerDropCollision)
        self.ignore(self.PlayerCoolerCollision)
        self.ignore(CogdoMazeExit.EnterEventName)
        self.ignore(CogdoMemo.EnterEventName)
        self.ignore(CogdoMazeSuit.COLLISION_EVENT_NAME)
        self.ignore(CogdoMazePlayer.GagHitEventName)
        self.ignore(CogdoMazeSuit.GagHitEventName)
        self.ignore(CogdoMazeSuit.DeathEventName)
        self.ignore(CogdoMazeSuit.ThinkEventName)
        self.ignore(CogdoMazeBossSuit.ShakeEventName)
        self.ignore(CogdoMazePlayer.RemovedEventName)
        self.__stopUpdateTask()
        for timeoutTask in self.gagTimeoutTasks:
            taskMgr.remove(timeoutTask)

        self.finished = True
        self.localPlayer.handleGameExit()
        for player in self.players:
            player.request('Done')

        self.guiMgr.hideTimer()
        self.guiMgr.hideMazeMap()
        self.guiMgr.hideBossGui()

    def _addPlayer(self, player):
        self.players.append(player)
        self.toonId2Player[player.toon.doId] = player

    def _removePlayer(self, player):
        if player in self.players:
            self.players.remove(player)
        else:
            for cPlayer in self.players:
                if cPlayer.toon == player.toon:
                    self.players.remove(cPlayer)
                    break

        if player.toon.doId in self.toonId2Player:
            del self.toonId2Player[player.toon.doId]
        self.guiMgr.mazeMapGui.removeToon(player.toon)

    def handleToonLeft(self, toonId):
        self._removePlayer(self.toonId2Player[toonId])

    def __startUpdateTask(self):
        self.__stopUpdateTask()
        taskMgr.add(self.__updateTask, CogdoMazeGame.UpdateTaskName, 45)

    def __stopUpdateTask(self):
        taskMgr.remove(CogdoMazeGame.UpdateTaskName)
        taskMgr.remove('loopSecondQuakeSound')

    def __updateTask(self, task):
        dt = globalClock.getDt()
        self.localPlayer.update(dt)
        for player in self.players:
            curTX, curTY = self.maze.world2tileClipped(player.toon.getX(),
                                                       player.toon.getY())
            self.guiMgr.mazeMapGui.updateToon(player.toon, curTX, curTY)

        self.__updateGags()
        if Globals.QuakeSfxEnabled:
            self.__updateQuakeSound()
        for pickup in self.pickups:
            pickup.update(dt)

        MazeSuit.thinkSuits(self.suits, self.distGame.getStartTime())
        return Task.cont

    def __updateGags(self):
        remove = []
        for i in xrange(len(self.gags)):
            balloon = self.gags[i]
            if balloon.isSingleton():
                remove.append(i)
            elif balloon.getParent() == render:
                loc = self.maze.world2tile(balloon.getX(), balloon.getY())
                if not self.maze.isAccessible(loc[0], loc[1]):
                    self.removeGag(balloon)

        remove.reverse()
        for i in remove:
            self.gags.pop(i)

    def __updateQuakeSound(self):
        shake = self.localPlayer.getCameraShake()
        if shake < 1.0:
            shake = 1.0
        volume = 3.0 * shake / Globals.CameraShakeMax
        if volume > 3.0:
            volume = 3.0
        if self._quakeSfx1.getAudioSound().status(
        ) != self._quakeSfx1.getAudioSound().PLAYING:
            self._quakeSfx1.loop(volume=volume)
        else:
            self._quakeSfx1.getAudioSound().setVolume(volume)
        volume = shake * shake / Globals.CameraShakeMax
        if not self.hackTemp and self._quakeSfx2.getAudioSound().status(
        ) != self._quakeSfx2.getAudioSound().PLAYING:
            taskMgr.doMethodLater(1.5,
                                  self._quakeSfx2.loop,
                                  'loopSecondQuakeSound',
                                  extraArgs=[])
            self.hackTemp = True
        else:
            self._quakeSfx2.getAudioSound().setVolume(volume)

    def handleLocalToonMeetsSuit(self, suitType, suitNum):
        if self.localPlayer.state == 'Normal' and not self.localPlayer.invulnerable:
            self.distGame.b_toonHitBySuit(suitType, suitNum)

    def toonHitBySuit(self, toonId, suitType, suitNum, elapsedTime=0.0):
        player = self.toonId2Player[toonId]
        if player.state == 'Normal':
            player.request('Hit', elapsedTime)

    def handleSuitDeath(self, suitType, suitNum):
        suit = self.suitsById[suitNum]
        self.dropMemos(suit)
        self.removeSuit(suit)

    def dropMemos(self, suit):
        numDrops = suit.memos
        if numDrops > 0:
            start = math.radians(random.randint(0, 360))
            step = math.radians(360.0 / numDrops)
            radius = 2.0
            for i in xrange(numDrops):
                angle = start + i * step
                x = radius * math.cos(angle) + suit.suit.getX()
                y = radius * math.sin(angle) + suit.suit.getY()
                self.generatePickup(x, y)

    def handleSuitThink(self, suit, TX, TY):
        if suit in self.shakers:
            self.guiMgr.mazeMapGui.updateSuit(suit.suit, TX, TY)
            suit.dropTimer += 1
            if suit.dropTimer >= Globals.DropFrequency:
                self.doDrop(suit)
                suit.dropTimer = 0

    def doDrop(self, boss):
        dropLoc = boss.pickRandomValidSpot()
        self.generateDrop(dropLoc[0], dropLoc[1])

    def handleBossShake(self, suit, strength):
        if Globals.BossShakeEnabled:
            self.shakeCamera(suit.suit, strength, Globals.BossMaxDistance)

    def randomDrop(self, centerTX, centerTY, radius):
        dropArray = []
        for i in xrange(1, distance):
            dropArray.append(i)
            dropArray.append(-1 * i)

        offsetTX = self.distGame.randomNumGen.choice(dropArray)
        offsetTY = self.distGame.randomNumGen.choice(dropArray)
        dropTX = sourceTX + offsetTX
        dropTY = sourceTY + offsetTY
        if self.maze.isWalkable(dropTX, dropTY):
            self.generateDrop(dropTX, dropTY)

    def generateDrop(self, TX, TY):
        drop = self.maze.tile2world(TX, TY)
        ival = self.createDrop(drop[0], drop[1])
        ival.start()

    def createDrop(self, x, y):
        self.dropCounter = self.dropCounter + 1
        id = self.dropCounter
        drop = CogdoMazeDrop(self, id, x, y)
        self.drops[id] = drop
        return drop.getDropIval()

    def cleanupDrop(self, id):
        if id in self.drops.keys():
            drop = self.drops[id]
            drop.destroy()
            del self.drops[id]

    def dropHit(self, node, id):
        if self.finished:
            return
        if Globals.DropShakeEnabled:
            self.shakeCamera(node, Globals.DropShakeStrength,
                             Globals.DropMaxDistance)

    def shakeCamera(self, node, strength, distanceCutoff=60.0):
        distance = self.localPlayer.toon.getDistance(node)
        shake = strength * (1 - distance / distanceCutoff)
        if shake > 0:
            self.localPlayer.shakeCamera(shake)

    def handleLocalToonMeetsDrop(self, collEntry):
        fcabinet = collEntry.getIntoNodePath()
        if fcabinet.getTag('isFalling') == str('False'):
            return
        if self.localPlayer.state == 'Normal' and not self.localPlayer.invulnerable:
            self.distGame.b_toonHitByDrop()

    def toonHitByDrop(self, toonId):
        player = self.toonId2Player[toonId]
        player.hitByDrop()

    def handleLocalToonMeetsGagPickup(self, collEntry):
        if self.localPlayer.equippedGag != None:
            return
        into = collEntry.getIntoNodePath()
        if into.hasPythonTag('id'):
            id = into.getPythonTag('id')
            self.distGame.d_sendRequestGagPickUp(id)
        return

    def hasGag(self, toonId, elapsedTime=0.0):
        player = self.toonId2Player[toonId]
        player.equipGag()

    def handleLocalToonMeetsWaterCooler(self, collEntry):
        if self.localPlayer.equippedGag != None:
            return
        if self.lastBalloonTimestamp and globalClock.getFrameTime(
        ) - self.lastBalloonTimestamp < Globals.BalloonDelay:
            return
        collNode = collEntry.getIntoNode()
        waterCooler = self._collNode2waterCooler[collNode]
        self.lastBalloonTimestamp = globalClock.getFrameTime()
        self.distGame.d_sendRequestGag(waterCooler.serialNum)
        return

    def requestUseGag(self, x, y, h):
        self.distGame.b_toonUsedGag(x, y, h)

    def toonUsedGag(self, toonId, x, y, h, elapsedTime=0.0):
        player = self.toonId2Player[toonId]
        heading = h
        pos = Point3(x, y, 0)
        gag = player.showToonThrowingGag(heading, pos)
        if gag is not None:
            self.gags.append(gag)
        return

    def handleToonMeetsGag(self, playerId, gag):
        self.removeGag(gag)
        self.distGame.b_toonHitByGag(playerId)

    def toonHitByGag(self, toonId, hitToon, elapsedTime=0.0):
        if toonId not in self.toonId2Player.keys(
        ) or hitToon not in self.toonId2Player.keys():
            return
        player = self.toonId2Player[hitToon]
        player.hitByGag()

    def handleLocalSuitMeetsGag(self, suitType, suitNum, gag):
        self.removeGag(gag)
        self.localPlayer.hitSuit(suitType)
        self.distGame.b_suitHitByGag(suitType, suitNum)

    def suitHitByGag(self, toonId, suitType, suitNum, elapsedTime=0.0):
        if suitType == Globals.SuitTypes.Boss:
            self.guiMgr.showBossHit(suitNum)
        if suitNum in self.suitsById.keys():
            suit = self.suitsById[suitNum]
            suit.hitByGag()

    def removeGag(self, gag):
        gag.detachNode()

    def toonRevealsDoor(self, toonId):
        self._exit.revealed = True

    def openDoor(self, timeLeft):
        self._audioMgr.playMusic('timeRunningOut')
        if not self._exit.isOpen():
            self._exit.open()
            self.localPlayer.handleOpenDoor(self._exit)
            self.guiMgr.showTimer(timeLeft, self._handleTimerExpired)
            self.guiMgr.mazeMapGui.showExit()
            self.guiMgr.mazeMapGui.revealAll()

    def countdown(self, timeLeft):
        self.guiMgr.showTimer(timeLeft, self._handleTimerExpired)

    def timeAlert(self):
        self._audioMgr.playMusic('timeRunningOut')

    def _handleTimerExpired(self):
        self.localPlayer.request('Done')

    def toonEntersDoor(self, toonId):
        player = self.toonId2Player[toonId]
        self.guiMgr.mazeMapGui.removeToon(player.toon)
        self._exit.playerEntersDoor(player)
        self.localPlayer.handleToonEntersDoor(toonId, self._exit)
        player.request('Done')

    def generatePickup(self, x, y):
        pickup = CogdoMemo(len(self.pickups), pitch=-90)
        self.pickups.append(pickup)
        pickup.reparentTo(self.maze.maze)
        pickup.setPos(x, y, 1)
        pickup.enable()

    def handleLocalToonMeetsPickup(self, pickup):
        pickup.disable()
        self.distGame.d_sendRequestPickUp(pickup.serialNum)

    def pickUp(self, toonId, pickupNum, elapsedTime=0.0):
        self.notify.debugCall()
        player = self.toonId2Player[toonId]
        pickup = self.pickups[pickupNum]
        if not pickup.wasPickedUp():
            pickup.pickUp(player.toon, elapsedTime)
            self.localPlayer.handlePickUp(toonId)

    def placePlayer(self, player):
        toonIds = self.distGame.getToonIds()
        if player.toon.doId not in toonIds:
            return
        i = toonIds.index(player.toon.doId)
        x = int(self.maze.width / 2.0) - int(len(toonIds) / 2.0) + i
        y = 2
        while not self.maze.isAccessible(x, y):
            y += 1

        pos = self.maze.tile2world(x, y)
        player.toon.setPos(pos[0], pos[1], 0)
        self.guiMgr.mazeMapGui.addToon(player.toon, x, y)

    def handleLocalToonEntersDoor(self, door):
        localToonId = self.localPlayer.toon.doId
        if self._exit.isOpen():
            self.distGame.d_sendRequestAction(Globals.GameActions.EnterDoor, 0)
        else:
            if localToonId not in self.toonsThatRevealedDoor:
                self.toonsThatRevealedDoor.append(localToonId)
                self.localPlayer.handleToonRevealsDoor(localToonId, self._exit)
            if not self._exit.revealed:
                self.toonRevealsDoor(localToonId)
                self.distGame.d_sendRequestAction(
                    Globals.GameActions.RevealDoor, 0)

    def handleToonWentSad(self, toonId):
        if toonId == self.localPlayer.toon.doId:
            for player in self.players:
                player.removeGag()

        elif toonId in self.toonId2Player.keys():
            player = self.toonId2Player[toonId]
            player.removeGag()

    def handleToonDisconnected(self, toonId):
        if toonId == self.localPlayer.toon.doId:
            pass
        elif toonId in self.toonId2Player.keys():
            player = self.toonId2Player[toonId]
            self._removePlayer(player)
class CogdoMazeGame(DirectObject):
    notify = directNotify.newCategory('CogdoMazeGame')
    UpdateTaskName = 'CogdoMazeGameUpdateTask'
    RemoveGagTaskName = 'CogdoMazeGameRemoveGag'
    PlayerCoolerCollision = '%s-into-%s' % (Globals.LocalPlayerCollisionName, Globals.WaterCoolerCollisionName)
    PlayerDropCollision = '%s-into-%s' % (Globals.LocalPlayerCollisionName, Globals.DropCollisionName)

    def __init__(self, distGame):
        self.distGame = distGame
        self._allowSuitsHitToons = base.config.GetBool('cogdomaze-suits-hit-toons', True)

    def load(self, cogdoMazeFactory, numSuits, bossCode):
        self._initAudio()
        self.maze = cogdoMazeFactory.createCogdoMaze()
        suitSpawnSpot = self.maze.createRandomSpotsList(numSuits, self.distGame.randomNumGen)
        self.guiMgr = CogdoMazeGuiManager(self.maze, bossCode)
        self.suits = []
        self.suitsById = {}
        self.shakers = []
        self.toonsThatRevealedDoor = []
        self.quake = 0
        self.dropCounter = 0
        self.drops = {}
        self.gagCounter = 0
        self.gags = []
        self.hackTemp = False
        self.dropGen = RandomNumGen(self.distGame.doId)
        self.gagTimeoutTasks = []
        self.finished = False
        self.lastBalloonTimestamp = None
        difficulty = self.distGame.getDifficulty()
        serialNum = 0
        for i in range(numSuits[0]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeBossSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[0][i])
            self.addSuit(suit)
            self.guiMgr.mazeMapGui.addSuit(suit.suit)
            serialNum += 1

        for i in range(numSuits[1]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeFastMinionSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[1][i])
            self.addSuit(suit)
            serialNum += 1

        for i in range(numSuits[2]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeSlowMinionSuit(serialNum, self.maze, suitRng, difficulty, startTile=suitSpawnSpot[2][i])
            self.addSuit(suit)
            serialNum += 1

        self.toonId2Door = {}
        self.keyIdToKey = {}
        self.players = []
        self.toonId2Player = {}
        cellPos = (int(self.maze.width / 2), self.maze.height - 1)
        pos = self.maze.tile2world(*cellPos)
        self._exit = CogdoMazeExit()
        self._exit.reparentTo(render)
        self._exit.setPos(self.maze.exitPos)
        self._exit.stash()
        self.guiMgr.mazeMapGui.placeExit(*cellPos)
        self._collNode2waterCooler = {}
        for waterCooler in self.maze.getWaterCoolers():
            pos = waterCooler.getPos(render)
            tpos = self.maze.world2tile(pos[0], pos[1])
            self.guiMgr.mazeMapGui.addWaterCooler(*tpos)
            self._collNode2waterCooler[waterCooler.collNode] = waterCooler

        self.pickups = []
        self.gagModel = CogdoUtil.loadMazeModel('waterBalloon')
        self._movie = CogdoMazeGameIntro(self.maze, self._exit, self.distGame.randomNumGen)
        self._movie.load()
        return

    def _initAudio(self):
        self._audioMgr = CogdoGameAudioManager(Globals.MusicFiles, Globals.SfxFiles, camera, cutoff=Globals.AudioCutoff)
        self._quakeSfx1 = self._audioMgr.createSfx('quake')
        self._quakeSfx2 = self._audioMgr.createSfx('quake')

    def _destroyAudio(self):
        self._quakeSfx1.destroy()
        self._quakeSfx2.destroy()
        del self._quakeSfx1
        del self._quakeSfx2
        self._audioMgr.destroy()
        del self._audioMgr

    def addSuit(self, suit):
        id = suit.serialNum
        self.suits.append(suit)
        if suit.type == Globals.SuitTypes.Boss:
            self.shakers.append(suit)
        self.suitsById[id] = suit

    def removeSuit(self, suit):
        id = suit.serialNum
        del self.suitsById[id]
        if suit.type == Globals.SuitTypes.Boss:
            self.shakers.remove(suit)
            self.guiMgr.showBossCode(id)
            self.guiMgr.mazeMapGui.removeSuit(suit.suit)
        self.suits.remove(suit)
        suit.destroy()

    def unload(self):
        self.toonsThatRevealedDoor = []
        for suit in self.suits:
            suit.destroy()

        del self.suits
        for id in self.drops.keys():
            self.cleanupDrop(id)

        self.__stopUpdateTask()
        self.ignoreAll()
        self.maze.destroy()
        del self.maze
        self._exit.destroy()
        del self._exit
        self.guiMgr.destroy()
        del self.guiMgr
        for player in self.players:
            player.destroy()

        del self.players
        del self.toonId2Player
        del self.localPlayer
        for pickup in self.pickups:
            pickup.destroy()

        self._destroyAudio()
        del self.distGame

    def onstage(self):
        self._exit.onstage()
        self.maze.onstage()

    def offstage(self):
        self.maze.offstage()
        self._exit.offstage()
        for suit in self.suits:
            suit.offstage()

    def startIntro(self):
        self._movie.play()
        self._audioMgr.playMusic('normal')

    def endIntro(self):
        self._movie.end()
        self._movie.unload()
        del self._movie
        for player in self.players:
            self.placePlayer(player)
            if player.toon is localAvatar:
                localAvatar.sendCurrentPosition()
            player.request('Ready')

    def startFinish(self):
        self._movie = CogdoMazeGameFinish(self.localPlayer, self._exit)
        self._movie.load()
        self._movie.play()

    def endFinish(self):
        self._movie.end()
        self._movie.unload()
        del self._movie

    def placeEntranceElevator(self, elevator):
        pos = self.maze.elevatorPos
        elevator.setPos(pos)
        tpos = self.maze.world2tile(pos[0], pos[1])
        self.guiMgr.mazeMapGui.placeEntrance(*tpos)

    def initPlayers(self):
        for toonId in self.distGame.getToonIds():
            toon = self.distGame.getToon(toonId)
            if toon is not None:
                if toon.isLocal():
                    player = CogdoMazeLocalPlayer(len(self.players), base.localAvatar, self, self.guiMgr)
                    self.localPlayer = player
                else:
                    player = CogdoMazePlayer(len(self.players), toon)
                self._addPlayer(player)

        return

    def start(self):
        self.accept(self.PlayerDropCollision, self.handleLocalToonMeetsDrop)
        self.accept(self.PlayerCoolerCollision, self.handleLocalToonMeetsWaterCooler)
        self.accept(CogdoMazeExit.EnterEventName, self.handleLocalToonEntersDoor)
        self.accept(CogdoMemo.EnterEventName, self.handleLocalToonMeetsPickup)
        if self._allowSuitsHitToons:
            self.accept(CogdoMazeSuit.COLLISION_EVENT_NAME, self.handleLocalToonMeetsSuit)
        self.accept(CogdoMazePlayer.GagHitEventName, self.handleToonMeetsGag)
        self.accept(CogdoMazeSuit.GagHitEventName, self.handleLocalSuitMeetsGag)
        self.accept(CogdoMazeSuit.DeathEventName, self.handleSuitDeath)
        self.accept(CogdoMazeSuit.ThinkEventName, self.handleSuitThink)
        self.accept(CogdoMazeBossSuit.ShakeEventName, self.handleBossShake)
        self.accept(CogdoMazePlayer.RemovedEventName, self._removePlayer)
        self.__startUpdateTask()
        for player in self.players:
            player.handleGameStart()
            player.request('Normal')

        for suit in self.suits:
            suit.onstage()
            suit.gameStart(self.distGame.getStartTime())

    def exit(self):
        self._quakeSfx1.stop()
        self._quakeSfx2.stop()
        for suit in self.suits:
            suit.gameEnd()

        self.ignore(self.PlayerDropCollision)
        self.ignore(self.PlayerCoolerCollision)
        self.ignore(CogdoMazeExit.EnterEventName)
        self.ignore(CogdoMemo.EnterEventName)
        self.ignore(CogdoMazeSuit.COLLISION_EVENT_NAME)
        self.ignore(CogdoMazePlayer.GagHitEventName)
        self.ignore(CogdoMazeSuit.GagHitEventName)
        self.ignore(CogdoMazeSuit.DeathEventName)
        self.ignore(CogdoMazeSuit.ThinkEventName)
        self.ignore(CogdoMazeBossSuit.ShakeEventName)
        self.ignore(CogdoMazePlayer.RemovedEventName)
        self.__stopUpdateTask()
        for timeoutTask in self.gagTimeoutTasks:
            taskMgr.remove(timeoutTask)

        self.finished = True
        self.localPlayer.handleGameExit()
        for player in self.players:
            player.request('Done')

        self.guiMgr.hideTimer()
        self.guiMgr.hideMazeMap()
        self.guiMgr.hideBossGui()

    def _addPlayer(self, player):
        self.players.append(player)
        self.toonId2Player[player.toon.doId] = player

    def _removePlayer(self, player):
        if player in self.players:
            self.players.remove(player)
        else:
            for cPlayer in self.players:
                if cPlayer.toon == player.toon:
                    self.players.remove(cPlayer)
                    break

        if self.toonId2Player.has_key(player.toon.doId):
            del self.toonId2Player[player.toon.doId]
        self.guiMgr.mazeMapGui.removeToon(player.toon)

    def handleToonLeft(self, toonId):
        self._removePlayer(self.toonId2Player[toonId])

    def __startUpdateTask(self):
        self.__stopUpdateTask()
        taskMgr.add(self.__updateTask, CogdoMazeGame.UpdateTaskName, 45)

    def __stopUpdateTask(self):
        taskMgr.remove(CogdoMazeGame.UpdateTaskName)
        taskMgr.remove('loopSecondQuakeSound')

    def __updateTask(self, task):
        dt = globalClock.getDt()
        self.localPlayer.update(dt)
        for player in self.players:
            curTX, curTY = self.maze.world2tileClipped(player.toon.getX(), player.toon.getY())
            self.guiMgr.mazeMapGui.updateToon(player.toon, curTX, curTY)

        self.__updateGags()
        if Globals.QuakeSfxEnabled:
            self.__updateQuakeSound()
        for pickup in self.pickups:
            pickup.update(dt)

        MazeSuit.thinkSuits(self.suits, self.distGame.getStartTime())
        return Task.cont

    def __updateGags(self):
        remove = []
        for i in range(len(self.gags)):
            balloon = self.gags[i]
            if balloon.isSingleton():
                remove.append(i)
            elif balloon.getParent() == render:
                loc = self.maze.world2tile(balloon.getX(), balloon.getY())
                if not self.maze.isAccessible(loc[0], loc[1]):
                    self.removeGag(balloon)

        remove.reverse()
        for i in remove:
            self.gags.pop(i)

    def __updateQuakeSound(self):
        shake = self.localPlayer.getCameraShake()
        if shake < 1.0:
            shake = 1.0
        volume = 3.0 * shake / Globals.CameraShakeMax
        if volume > 3.0:
            volume = 3.0
        if self._quakeSfx1.getAudioSound().status() != self._quakeSfx1.getAudioSound().PLAYING:
            self._quakeSfx1.loop(volume=volume)
        else:
            self._quakeSfx1.getAudioSound().setVolume(volume)
        volume = shake * shake / Globals.CameraShakeMax
        if not self.hackTemp and self._quakeSfx2.getAudioSound().status() != self._quakeSfx2.getAudioSound().PLAYING:
            taskMgr.doMethodLater(1.5, self._quakeSfx2.loop, 'loopSecondQuakeSound', extraArgs=[])
            self.hackTemp = True
        else:
            self._quakeSfx2.getAudioSound().setVolume(volume)

    def handleLocalToonMeetsSuit(self, suitType, suitNum):
        if self.localPlayer.state == 'Normal' and not self.localPlayer.invulnerable:
            self.distGame.b_toonHitBySuit(suitType, suitNum)

    def toonHitBySuit(self, toonId, suitType, suitNum, elapsedTime = 0.0):
        player = self.toonId2Player[toonId]
        if player.state == 'Normal':
            player.request('Hit', elapsedTime)

    def handleSuitDeath(self, suitType, suitNum):
        suit = self.suitsById[suitNum]
        self.dropMemos(suit)
        self.removeSuit(suit)

    def dropMemos(self, suit):
        numDrops = suit.memos
        if numDrops > 0:
            start = math.radians(random.randint(0, 360))
            step = math.radians(360.0 / numDrops)
            radius = 2.0
            for i in range(numDrops):
                angle = start + i * step
                x = radius * math.cos(angle) + suit.suit.getX()
                y = radius * math.sin(angle) + suit.suit.getY()
                self.generatePickup(x, y)

    def handleSuitThink(self, suit, TX, TY):
        if suit in self.shakers:
            self.guiMgr.mazeMapGui.updateSuit(suit.suit, TX, TY)
            suit.dropTimer += 1
            if suit.dropTimer >= Globals.DropFrequency:
                self.doDrop(suit)
                suit.dropTimer = 0

    def doDrop(self, boss):
        dropLoc = boss.pickRandomValidSpot()
        self.generateDrop(dropLoc[0], dropLoc[1])

    def handleBossShake(self, suit, strength):
        if Globals.BossShakeEnabled:
            self.shakeCamera(suit.suit, strength, Globals.BossMaxDistance)

    def randomDrop(self, centerTX, centerTY, radius):
        dropArray = []
        for i in xrange(1, distance):
            dropArray.append(i)
            dropArray.append(-1 * i)

        offsetTX = self.distGame.randomNumGen.choice(dropArray)
        offsetTY = self.distGame.randomNumGen.choice(dropArray)
        dropTX = sourceTX + offsetTX
        dropTY = sourceTY + offsetTY
        if self.maze.isWalkable(dropTX, dropTY):
            self.generateDrop(dropTX, dropTY)

    def generateDrop(self, TX, TY):
        drop = self.maze.tile2world(TX, TY)
        ival = self.createDrop(drop[0], drop[1])
        ival.start()

    def createDrop(self, x, y):
        self.dropCounter = self.dropCounter + 1
        id = self.dropCounter
        drop = CogdoMazeDrop(self, id, x, y)
        self.drops[id] = drop
        return drop.getDropIval()

    def cleanupDrop(self, id):
        if id in self.drops.keys():
            drop = self.drops[id]
            drop.destroy()
            del self.drops[id]

    def dropHit(self, node, id):
        if self.finished:
            return
        if Globals.DropShakeEnabled:
            self.shakeCamera(node, Globals.DropShakeStrength, Globals.DropMaxDistance)

    def shakeCamera(self, node, strength, distanceCutoff = 60.0):
        distance = self.localPlayer.toon.getDistance(node)
        shake = strength * (1 - distance / distanceCutoff)
        if shake > 0:
            self.localPlayer.shakeCamera(shake)

    def handleLocalToonMeetsDrop(self, collEntry):
        fcabinet = collEntry.getIntoNodePath()
        if fcabinet.getTag('isFalling') == str('False'):
            return
        if self.localPlayer.state == 'Normal' and not self.localPlayer.invulnerable:
            self.distGame.b_toonHitByDrop()

    def toonHitByDrop(self, toonId):
        player = self.toonId2Player[toonId]
        player.hitByDrop()

    def handleLocalToonMeetsGagPickup(self, collEntry):
        if self.localPlayer.equippedGag != None:
            return
        into = collEntry.getIntoNodePath()
        if into.hasPythonTag('id'):
            id = into.getPythonTag('id')
            self.distGame.d_sendRequestGagPickUp(id)
        return

    def hasGag(self, toonId, elapsedTime = 0.0):
        player = self.toonId2Player[toonId]
        player.equipGag()

    def handleLocalToonMeetsWaterCooler(self, collEntry):
        if self.localPlayer.equippedGag != None:
            return
        if self.lastBalloonTimestamp and globalClock.getFrameTime() - self.lastBalloonTimestamp < Globals.BalloonDelay:
            return
        collNode = collEntry.getIntoNode()
        waterCooler = self._collNode2waterCooler[collNode]
        self.lastBalloonTimestamp = globalClock.getFrameTime()
        self.distGame.d_sendRequestGag(waterCooler.serialNum)
        return

    def requestUseGag(self, x, y, h):
        self.distGame.b_toonUsedGag(x, y, h)

    def toonUsedGag(self, toonId, x, y, h, elapsedTime = 0.0):
        player = self.toonId2Player[toonId]
        heading = h
        pos = Point3(x, y, 0)
        gag = player.showToonThrowingGag(heading, pos)
        if gag is not None:
            self.gags.append(gag)
        return

    def handleToonMeetsGag(self, playerId, gag):
        self.removeGag(gag)
        self.distGame.b_toonHitByGag(playerId)

    def toonHitByGag(self, toonId, hitToon, elapsedTime = 0.0):
        if toonId not in self.toonId2Player.keys() or hitToon not in self.toonId2Player.keys():
            return
        player = self.toonId2Player[hitToon]
        player.hitByGag()

    def handleLocalSuitMeetsGag(self, suitType, suitNum, gag):
        self.removeGag(gag)
        self.localPlayer.hitSuit(suitType)
        self.distGame.b_suitHitByGag(suitType, suitNum)

    def suitHitByGag(self, toonId, suitType, suitNum, elapsedTime = 0.0):
        if suitType == Globals.SuitTypes.Boss:
            self.guiMgr.showBossHit(suitNum)
        if suitNum in self.suitsById.keys():
            suit = self.suitsById[suitNum]
            suit.hitByGag()

    def removeGag(self, gag):
        gag.detachNode()

    def toonRevealsDoor(self, toonId):
        self._exit.revealed = True

    def openDoor(self, timeLeft):
        self._audioMgr.playMusic('timeRunningOut')
        if not self._exit.isOpen():
            self._exit.open()
            self.localPlayer.handleOpenDoor(self._exit)
            self.guiMgr.showTimer(timeLeft, self._handleTimerExpired)
            self.guiMgr.mazeMapGui.showExit()
            self.guiMgr.mazeMapGui.revealAll()

    def countdown(self, timeLeft):
        self.guiMgr.showTimer(timeLeft, self._handleTimerExpired)

    def timeAlert(self):
        self._audioMgr.playMusic('timeRunningOut')

    def _handleTimerExpired(self):
        self.localPlayer.request('Done')

    def toonEntersDoor(self, toonId):
        player = self.toonId2Player[toonId]
        self.guiMgr.mazeMapGui.removeToon(player.toon)
        self._exit.playerEntersDoor(player)
        self.localPlayer.handleToonEntersDoor(toonId, self._exit)
        player.request('Done')

    def generatePickup(self, x, y):
        pickup = CogdoMemo(len(self.pickups), pitch=-90)
        self.pickups.append(pickup)
        pickup.reparentTo(self.maze.maze)
        pickup.setPos(x, y, 1)
        pickup.enable()

    def handleLocalToonMeetsPickup(self, pickup):
        pickup.disable()
        self.distGame.d_sendRequestPickUp(pickup.serialNum)

    def pickUp(self, toonId, pickupNum, elapsedTime = 0.0):
        self.notify.debugCall()
        player = self.toonId2Player[toonId]
        pickup = self.pickups[pickupNum]
        if not pickup.wasPickedUp():
            pickup.pickUp(player.toon, elapsedTime)
            self.localPlayer.handlePickUp(toonId)

    def placePlayer(self, player):
        toonIds = self.distGame.getToonIds()
        if player.toon.doId not in toonIds:
            return
        i = toonIds.index(player.toon.doId)
        x = int(self.maze.width / 2.0) - int(len(toonIds) / 2.0) + i
        y = 2
        while not self.maze.isAccessible(x, y):
            y += 1

        pos = self.maze.tile2world(x, y)
        player.toon.setPos(pos[0], pos[1], 0)
        self.guiMgr.mazeMapGui.addToon(player.toon, x, y)

    def handleLocalToonEntersDoor(self, door):
        localToonId = self.localPlayer.toon.doId
        if self._exit.isOpen():
            self.distGame.d_sendRequestAction(Globals.GameActions.EnterDoor, 0)
        else:
            if localToonId not in self.toonsThatRevealedDoor:
                self.toonsThatRevealedDoor.append(localToonId)
                self.localPlayer.handleToonRevealsDoor(localToonId, self._exit)
            if not self._exit.revealed:
                self.toonRevealsDoor(localToonId)
                self.distGame.d_sendRequestAction(Globals.GameActions.RevealDoor, 0)

    def handleToonWentSad(self, toonId):
        if toonId == self.localPlayer.toon.doId:
            for player in self.players:
                player.removeGag()

        elif toonId in self.toonId2Player.keys():
            player = self.toonId2Player[toonId]
            player.removeGag()

    def handleToonDisconnected(self, toonId):
        if toonId == self.localPlayer.toon.doId:
            pass
        elif toonId in self.toonId2Player.keys():
            player = self.toonId2Player[toonId]
            self._removePlayer(player)
    def load(self, cogdoMazeFactory, numSuits, bossCode):
        self._initAudio()
        self.maze = cogdoMazeFactory.createCogdoMaze()
        suitSpawnSpot = self.maze.createRandomSpotsList(
            numSuits, self.distGame.randomNumGen)
        self.guiMgr = CogdoMazeGuiManager(self.maze, bossCode)
        self.suits = []
        self.suitsById = {}
        self.shakers = []
        self.toonsThatRevealedDoor = []
        self.quake = 0
        self.dropCounter = 0
        self.drops = {}
        self.gagCounter = 0
        self.gags = []
        self.hackTemp = False
        self.dropGen = RandomNumGen(self.distGame.doId)
        self.gagTimeoutTasks = []
        self.finished = False
        self.lastBalloonTimestamp = None
        difficulty = self.distGame.getDifficulty()
        serialNum = 0
        for i in range(numSuits[0]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeBossSuit(serialNum,
                                     self.maze,
                                     suitRng,
                                     difficulty,
                                     startTile=suitSpawnSpot[0][i])
            self.addSuit(suit)
            self.guiMgr.mazeMapGui.addSuit(suit.suit)
            serialNum += 1

        for i in range(numSuits[1]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeFastMinionSuit(serialNum,
                                           self.maze,
                                           suitRng,
                                           difficulty,
                                           startTile=suitSpawnSpot[1][i])
            self.addSuit(suit)
            serialNum += 1

        for i in range(numSuits[2]):
            suitRng = RandomNumGen(self.distGame.doId + serialNum * 10)
            suit = CogdoMazeSlowMinionSuit(serialNum,
                                           self.maze,
                                           suitRng,
                                           difficulty,
                                           startTile=suitSpawnSpot[2][i])
            self.addSuit(suit)
            serialNum += 1

        self.toonId2Door = {}
        self.keyIdToKey = {}
        self.players = []
        self.toonId2Player = {}
        cellPos = (int(self.maze.width / 2), self.maze.height - 1)
        pos = self.maze.tile2world(*cellPos)
        self._exit = CogdoMazeExit()
        self._exit.reparentTo(render)
        self._exit.setPos(self.maze.exitPos)
        self._exit.stash()
        self.guiMgr.mazeMapGui.placeExit(*cellPos)
        self._collNode2waterCooler = {}
        for waterCooler in self.maze.getWaterCoolers():
            pos = waterCooler.getPos(render)
            tpos = self.maze.world2tile(pos[0], pos[1])
            self.guiMgr.mazeMapGui.addWaterCooler(*tpos)
            self._collNode2waterCooler[waterCooler.collNode] = waterCooler

        self.pickups = []
        self.gagModel = CogdoUtil.loadMazeModel('waterBalloon')
        self._movie = CogdoMazeGameIntro(self.maze, self._exit,
                                         self.distGame.randomNumGen)
        self._movie.load()
        return