class DistributedCogdoInteriorAI(DistributedObjectAI.DistributedObjectAI):

    def __init__(self, air, elevator):
        self.air = air
        DistributedObjectAI.DistributedObjectAI.__init__(self, air)
        self.extZoneId, self.zoneId = elevator.bldg.getExteriorAndInteriorZoneId()
        self._numFloors = elevator.bldg.planner.numFloors
        self.layout = elevator.bldg._cogdoLayout
        self.avatarExitEvents = []
        self.toons = []
        self.toonSkillPtsGained = {}
        self.toonExp = {}
        self.toonOrigQuests = {}
        self.toonItems = {}
        self.toonOrigMerits = {}
        self.toonMerits = {}
        self.toonParts = {}
        self.helpfulToons = []
        self.currentFloor = 0
        self.bldg = elevator.bldg
        self.elevator = elevator
        self._game = None
        self.suits = []
        self.activeSuits = []
        self.reserveSuits = []
        self.joinedReserves = []
        self.suitsKilled = []
        self.suitsKilledPerFloor = []
        self.battle = None
        self.timer = Timer.Timer()
        self.responses = {}
        self.ignoreResponses = 0
        self.ignoreElevatorDone = 0
        self.ignoreReserveJoinDone = 0
        self.toonIds = copy.copy(elevator.seats)
        for toonId in self.toonIds:
            if toonId != None:
                self.__addToon(toonId)

        self.savedByMap = {}
        self.fsm = ClassicFSM.ClassicFSM('DistributedCogdoInteriorAI', [State.State('WaitForAllToonsInside', self.enterWaitForAllToonsInside, self.exitWaitForAllToonsInside, ['Elevator']),
         State.State('Elevator', self.enterElevator, self.exitElevator, ['Game']),
         State.State('Game', self.enterGame, self.exitGame, ['Battle']),
         State.State('Battle', self.enterBattle, self.exitBattle, ['ReservesJoining', 'BattleDone']),
         State.State('ReservesJoining', self.enterReservesJoining, self.exitReservesJoining, ['Battle']),
         State.State('BattleDone', self.enterBattleDone, self.exitBattleDone, ['Resting', 'Reward']),
         State.State('Resting', self.enterResting, self.exitResting, ['Elevator']),
         State.State('Reward', self.enterReward, self.exitReward, ['Off']),
         State.State('Off', self.enterOff, self.exitOff, ['WaitForAllToonsInside'])], 'Off', 'Off', onUndefTransition=ClassicFSM.ClassicFSM.ALLOW)
        self.fsm.enterInitialState()
        return

    def delete(self):
        self.ignoreAll()
        self.toons = []
        self.toonIds = []
        self.fsm.requestFinalState()
        del self.fsm
        del self.bldg
        del self.elevator
        self.timer.stop()
        del self.timer
        self._cogdoLayout = None
        self.__cleanupFloorBattle()
        taskName = self.taskName('deleteInterior')
        taskMgr.remove(taskName)
        DistributedObjectAI.DistributedObjectAI.delete(self)
        return

    def requestDelete(self):
        if self._game:
            self._game.requestDelete()
        DistributedObjectAI.DistributedObjectAI.requestDelete(self)

    def addGameFSM(self, gameFSM):
        self.fsm.getStateNamed('Game').addChild(gameFSM)

    def removeGameFSM(self, gameFSM):
        self.fsm.getStateNamed('Game').removeChild(gameFSM)

    def __handleUnexpectedExit(self, toonId):
        self.notify.warning('toon: %d exited unexpectedly' % toonId)
        if self._game:
            self._game.handleToonDisconnected(toonId)
        self.__removeToon(toonId)
        if len(self.toons) == 0:
            self.timer.stop()
            if self.fsm.getCurrentState().getName() == 'Resting':
                pass
            elif self.battle == None:
                self.bldg.deleteCogdoInterior()
        return

    def _handleToonWentSad(self, toonId):
        self.notify.info('toon: %d went sad' % toonId)
        toon = self.air.getDo(toonId)
        if toon:
            self.ignore(toon.getGoneSadMessage())
        if self._game:
            self._game.handleToonWentSad(toonId)
        self.__removeToon(toonId)
        if len(self.toons) == 0:
            self.timer.stop()
            if self.fsm.getCurrentState().getName() == 'Resting':
                pass
            elif self.battle == None:
                self._sadCleanupTask = taskMgr.doMethodLater(20, self._cleanupAfterLastToonWentSad, self.uniqueName('sadcleanup'))
        return

    def _cleanupAfterLastToonWentSad(self, task):
        self._sadCleanupTask = None
        self.bldg.deleteCogdoInterior()
        return task.done

    def __addToon(self, toonId):
        if not self.air.doId2do.has_key(toonId):
            self.notify.warning('addToon() - no toon for doId: %d' % toonId)
            return
        event = self.air.getAvatarExitEvent(toonId)
        self.avatarExitEvents.append(event)
        self.accept(event, self.__handleUnexpectedExit, extraArgs=[toonId])
        self.toons.append(toonId)
        self.responses[toonId] = 0

    def __removeToon(self, toonId):
        if self.toons.count(toonId):
            self.toons.remove(toonId)
        if self.toonIds.count(toonId):
            self.toonIds[self.toonIds.index(toonId)] = None
        if self.responses.has_key(toonId):
            del self.responses[toonId]
        event = self.air.getAvatarExitEvent(toonId)
        if self.avatarExitEvents.count(event):
            self.avatarExitEvents.remove(event)
        self.ignore(event)
        return

    def __resetResponses(self):
        self.responses = {}
        for toon in self.toons:
            self.responses[toon] = 0

        self.ignoreResponses = 0

    def __allToonsResponded(self):
        for toon in self.toons:
            if self.responses[toon] == 0:
                return 0

        self.ignoreResponses = 1
        return 1

    def getZoneId(self):
        return self.zoneId

    def getExtZoneId(self):
        return self.extZoneId

    def getDistBldgDoId(self):
        return self.bldg.getDoId()

    def getNumFloors(self):
        return self._numFloors

    def d_setToons(self):
        self.sendUpdate('setToons', self.getToons())

    def getToons(self):
        sendIds = []
        for toonId in self.toonIds:
            if toonId == None:
                sendIds.append(0)
            else:
                sendIds.append(toonId)

        return [sendIds, 0]

    def d_setSuits(self):
        self.sendUpdate('setSuits', self.getSuits())

    def getSuits(self):
        suitIds = []
        for suit in self.activeSuits:
            suitIds.append(suit.doId)

        reserveIds = []
        values = []
        for info in self.reserveSuits:
            reserveIds.append(info[0].doId)
            values.append(info[1])

        return [suitIds, reserveIds, values]

    def b_setState(self, state):
        self.d_setState(state)
        self.setState(state)

    def d_setState(self, state):
        stime = globalClock.getRealTime() + BattleBase.SERVER_BUFFER_TIME
        self.sendUpdate('setState', [state, globalClockDelta.localToNetworkTime(stime)])

    def setState(self, state):
        self.fsm.request(state)

    def getState(self):
        return [self.fsm.getCurrentState().getName(), globalClockDelta.getRealNetworkTime()]

    def setAvatarJoined(self):
        avId = self.air.getAvatarIdFromSender()
        if self.toons.count(avId) == 0:
            self.air.writeServerEvent('suspicious', avId, 'DistributedCogdoInteriorAI.setAvatarJoined from toon not in %s.' % self.toons)
            self.notify.warning('setAvatarJoined() - av: %d not in list' % avId)
            return
        avatar = self.air.doId2do.get(avId)
        if avatar != None:
            self.savedByMap[avId] = (avatar.getName(), avatar.dna.asTuple())
        self.responses[avId] += 1
        if self.__allToonsResponded():
            self.fsm.request('Elevator')
        return

    def elevatorDone(self):
        toonId = self.air.getAvatarIdFromSender()
        if self.ignoreResponses == 1:
            return
        elif self.fsm.getCurrentState().getName() != 'Elevator':
            self.notify.warning('elevatorDone() - in state: %s' % self.fsm.getCurrentState().getName())
            return
        elif self.toons.count(toonId) == 0:
            self.notify.warning('elevatorDone() - toon not in toon list: %d' % toonId)
            return
        self.responses[toonId] += 1
        if self.__allToonsResponded() and self.ignoreElevatorDone == 0:
            self.b_setState('Game')

    def reserveJoinDone(self):
        toonId = self.air.getAvatarIdFromSender()
        if self.ignoreResponses == 1:
            return
        elif self.fsm.getCurrentState().getName() != 'ReservesJoining':
            self.notify.warning('reserveJoinDone() - in state: %s' % self.fsm.getCurrentState().getName())
            return
        elif self.toons.count(toonId) == 0:
            self.notify.warning('reserveJoinDone() - toon not in list: %d' % toonId)
            return
        self.responses[toonId] += 1
        if self.__allToonsResponded() and self.ignoreReserveJoinDone == 0:
            self.b_setState('Battle')

    def isBossFloor(self, floorNum):
        if self.layout.hasBossBattle():
            if self.layout.getBossBattleFloor() == floorNum:
                return True
        return False

    def isTopFloor(self, floorNum):
        return self.layout.getNumFloors() - 1 == floorNum

    def enterOff(self):
        return None

    def exitOff(self):
        return None

    def enterWaitForAllToonsInside(self):
        self.__resetResponses()
        return None

    def exitWaitForAllToonsInside(self):
        self.__resetResponses()
        return None

    def enterElevator(self):
        if self.isBossFloor(self.currentFloor):
            self._populateFloorSuits()
        else:
            self.d_setToons()
        self.__resetResponses()
        self._game = self._createGame()
        self.d_setState('Elevator')
        self.timer.startCallback(BattleBase.ELEVATOR_T + ElevatorData[ELEVATOR_NORMAL]['openTime'] + BattleBase.SERVER_BUFFER_TIME, self.__serverElevatorDone)
        return None

    def _createGame(self):
        game = None
        if not self.isBossFloor(self.currentFloor):
            for toonId in self.toonIds:
                if toonId:
                    toon = self.air.getDo(toonId)
                    if toon:
                        self.accept(toon.getGoneSadMessage(), Functor(self._handleToonWentSad, toonId))

            game = DistCogdoCraneGameAI(self.air, self)
            game.generateWithRequired(self.zoneId)
        return game

    def __serverElevatorDone(self):
        self.ignoreElevatorDone = 1
        self.b_setState('Game')

    def exitElevator(self):
        self.timer.stop()
        self.__resetResponses()
        return None

    def enterGame(self):
        self.d_setState('Game')
        self.elevator.d_setFloor(self.currentFloor)
        if self._game:
            self._game.start()
        else:
            self._gameDone()

    def _populateFloorSuits(self):
        suitHandles = self.bldg.planner.genFloorSuits(self.currentFloor)
        self.suits = suitHandles['activeSuits']
        self.activeSuits = []
        for suit in self.suits:
            self.activeSuits.append(suit)

        self.reserveSuits = suitHandles['reserveSuits']
        self.d_setToons()
        self.d_setSuits()

    def _gameDone(self):
        if len(self.toons) == 0:
            return
        if not self.isBossFloor(self.currentFloor):
            self._populateFloorSuits()
        self.b_setState('Battle')
        if self._game:
            self._game.requestDelete()
            self._game = None
            for toonId in self.toonIds:
                if toonId:
                    toon = self.air.getDo(toonId)
                    if toon:
                        self.ignore(toon.getGoneSadMessage())

        return

    def exitGame(self):
        pass

    def __createFloorBattle(self):
        if self.isBossFloor(self.currentFloor):
            bossBattle = 1
        else:
            bossBattle = 0
        self.battle = DistributedBattleBldgAI.DistributedBattleBldgAI(self.air, self.zoneId, self.__handleRoundDone, self.__handleBattleDone, bossBattle=bossBattle)
        self.battle.suitsKilled = self.suitsKilled
        self.battle.suitsKilledPerFloor = self.suitsKilledPerFloor
        self.battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained
        self.battle.toonExp = self.toonExp
        self.battle.toonOrigQuests = self.toonOrigQuests
        self.battle.toonItems = self.toonItems
        self.battle.toonOrigMerits = self.toonOrigMerits
        self.battle.toonMerits = self.toonMerits
        self.battle.toonParts = self.toonParts
        self.battle.helpfulToons = self.helpfulToons
        self.battle.setInitialMembers(self.toons, self.suits)
        self.battle.generateWithRequired(self.zoneId)
        mult = getCreditMultiplier(self.currentFloor)
        if self.air.suitInvasionManager.getInvading():
            mult *= getInvasionMultiplier()
        self.battle.battleCalc.setSkillCreditMultiplier(mult)

    def __cleanupFloorBattle(self):
        for suit in self.suits:
            self.notify.debug('cleaning up floor suit: %d' % suit.doId)
            if suit.isDeleted():
                self.notify.debug('whoops, suit %d is deleted.' % suit.doId)
            else:
                suit.requestDelete()

        self.suits = []
        self.reserveSuits = []
        self.activeSuits = []
        if self.battle != None:
            self.battle.requestDelete()
        self.battle = None
        return

    def __handleRoundDone(self, toonIds, totalHp, deadSuits):
        totalMaxHp = 0
        for suit in self.suits:
            totalMaxHp += suit.maxHP

        for suit in deadSuits:
            self.activeSuits.remove(suit)

        if len(self.reserveSuits) > 0 and len(self.activeSuits) < 4:
            self.joinedReserves = []
            hpPercent = 100 - totalHp / totalMaxHp * 100.0
            for info in self.reserveSuits:
                if info[1] <= hpPercent and len(self.activeSuits) < 4:
                    self.suits.append(info[0])
                    self.activeSuits.append(info[0])
                    self.joinedReserves.append(info)

            for info in self.joinedReserves:
                self.reserveSuits.remove(info)

            if len(self.joinedReserves) > 0:
                self.fsm.request('ReservesJoining')
                self.d_setSuits()
                return
        if len(self.activeSuits) == 0:
            self.fsm.request('BattleDone', [toonIds])
        else:
            self.battle.resume()

    def __handleBattleDone(self, zoneId, toonIds):
        if len(toonIds) == 0:
            taskName = self.taskName('deleteInterior')
            taskMgr.doMethodLater(10, self.__doDeleteInterior, taskName)
        elif self.isTopFloor(self.currentFloor):
            self.setState('Reward')
        else:
            self.b_setState('Resting')

    def __doDeleteInterior(self, task):
        self.bldg.deleteCogdoInterior()

    def enterBattle(self):
        if self.battle == None:
            self.__createFloorBattle()
        return

    def exitBattle(self):
        return None

    def enterReservesJoining(self):
        self.__resetResponses()
        self.timer.startCallback(ElevatorData[ELEVATOR_NORMAL]['openTime'] + SUIT_HOLD_ELEVATOR_TIME + BattleBase.SERVER_BUFFER_TIME, self.__serverReserveJoinDone)
        return None

    def __serverReserveJoinDone(self):
        self.ignoreReserveJoinDone = 1
        self.b_setState('Battle')

    def exitReservesJoining(self):
        self.timer.stop()
        self.__resetResponses()
        for info in self.joinedReserves:
            self.battle.suitRequestJoin(info[0])

        self.battle.resume()
        self.joinedReserves = []
        return None

    def enterBattleDone(self, toonIds):
        if len(toonIds) != len(self.toons):
            deadToons = []
            for toon in self.toons:
                if toonIds.count(toon) == 0:
                    deadToons.append(toon)

            for toon in deadToons:
                self.__removeToon(toon)

        self.d_setToons()
        if len(self.toons) == 0:
            self.bldg.deleteCogdoInterior()
        elif self.isTopFloor(self.currentFloor):
            self.battle.resume(self.currentFloor, topFloor=1)
        else:
            self.battle.resume(self.currentFloor, topFloor=0)
        return None

    def exitBattleDone(self):
        self.__cleanupFloorBattle()
        self.d_setSuits()
        return None

    def __handleEnterElevator(self):
        self.fsm.request('Elevator')

    def enterResting(self):
        self.intElevator = DistributedCogdoElevatorIntAI(self.air, self, self.toons)
        self.intElevator.generateWithRequired(self.zoneId)
        return None

    def handleAllAboard(self, seats):
        if not hasattr(self, 'fsm'):
            return
        numOfEmptySeats = seats.count(None)
        if numOfEmptySeats == 4:
            self.bldg.deleteCogdoInterior()
            return
        elif numOfEmptySeats >= 0 and numOfEmptySeats <= 3:
            pass
        else:
            self.error('Bad number of empty seats: %s' % numOfEmptySeats)
        for toon in self.toons:
            if seats.count(toon) == 0:
                self.__removeToon(toon)

        self.toonIds = copy.copy(seats)
        self.toons = []
        for toonId in self.toonIds:
            if toonId != None:
                self.toons.append(toonId)

        self.d_setToons()
        self.currentFloor += 1
        self.fsm.request('Elevator')
        return

    def exitResting(self):
        self.intElevator.requestDelete()
        del self.intElevator
        return None

    def enterReward(self):
        victors = self.toonIds[:]
        savedBy = []
        for v in victors:
            tuple = self.savedByMap.get(v)
            if tuple:
                savedBy.append([v, tuple[0], tuple[1]])

        self.bldg.fsm.request('waitForVictorsFromCogdo', [victors, savedBy])
        self.d_setState('Reward')
        return None

    def exitReward(self):
        return None
示例#2
0
class DistributedCogdoInteriorAI(DistributedObjectAI.DistributedObjectAI):
    def __init__(self, air, elevator):
        self.air = air
        DistributedObjectAI.DistributedObjectAI.__init__(self, air)
        (self.extZoneId,
         self.zoneId) = elevator.bldg.getExteriorAndInteriorZoneId()
        self._numFloors = elevator.bldg.planner.numFloors
        self.layout = elevator.bldg._cogdoLayout
        self.avatarExitEvents = []
        self.toons = []
        self.toonSkillPtsGained = {}
        self.toonExp = {}
        self.toonOrigQuests = {}
        self.toonItems = {}
        self.toonOrigMerits = {}
        self.toonMerits = {}
        self.toonParts = {}
        self.helpfulToons = []
        self.currentFloor = 0
        self.bldg = elevator.bldg
        self.elevator = elevator
        self._game = None
        self.suits = []
        self.activeSuits = []
        self.reserveSuits = []
        self.joinedReserves = []
        self.suitsKilled = []
        self.suitsKilledPerFloor = []
        self.battle = None
        self.timer = Timer.Timer()
        self.responses = {}
        self.ignoreResponses = 0
        self.ignoreElevatorDone = 0
        self.ignoreReserveJoinDone = 0
        self.toonIds = copy.copy(elevator.seats)
        for toonId in self.toonIds:
            if toonId != None:
                self._DistributedCogdoInteriorAI__addToon(toonId)
                continue

        self.savedByMap = {}
        self.fsm = ClassicFSM.ClassicFSM(
            'DistributedCogdoInteriorAI', [
                State.State('WaitForAllToonsInside',
                            self.enterWaitForAllToonsInside,
                            self.exitWaitForAllToonsInside, ['Elevator']),
                State.State('Elevator', self.enterElevator, self.exitElevator,
                            ['Game']),
                State.State('Game', self.enterGame, self.exitGame, ['Battle']),
                State.State('Battle', self.enterBattle, self.exitBattle,
                            ['ReservesJoining', 'BattleDone']),
                State.State('ReservesJoining', self.enterReservesJoining,
                            self.exitReservesJoining, ['Battle']),
                State.State('BattleDone', self.enterBattleDone,
                            self.exitBattleDone, ['Resting', 'Reward']),
                State.State('Resting', self.enterResting, self.exitResting,
                            ['Elevator']),
                State.State('Reward', self.enterReward, self.exitReward,
                            ['Off']),
                State.State('Off', self.enterOff, self.exitOff,
                            ['WaitForAllToonsInside'])
            ],
            'Off',
            'Off',
            onUndefTransition=ClassicFSM.ClassicFSM.ALLOW)
        self.fsm.enterInitialState()

    def delete(self):
        self.ignoreAll()
        self.toons = []
        self.toonIds = []
        self.fsm.requestFinalState()
        del self.fsm
        del self.bldg
        del self.elevator
        self.timer.stop()
        del self.timer
        self._cogdoLayout = None
        self._DistributedCogdoInteriorAI__cleanupFloorBattle()
        taskName = self.taskName('deleteInterior')
        taskMgr.remove(taskName)
        DistributedObjectAI.DistributedObjectAI.delete(self)

    def requestDelete(self):
        if self._game:
            self._game.requestDelete()

        DistributedObjectAI.DistributedObjectAI.requestDelete(self)

    def addGameFSM(self, gameFSM):
        self.fsm.getStateNamed('Game').addChild(gameFSM)

    def removeGameFSM(self, gameFSM):
        self.fsm.getStateNamed('Game').removeChild(gameFSM)

    def _DistributedCogdoInteriorAI__handleUnexpectedExit(self, toonId):
        self.notify.warning('toon: %d exited unexpectedly' % toonId)
        if self._game:
            self._game.handleToonDisconnected(toonId)

        self._DistributedCogdoInteriorAI__removeToon(toonId)
        if len(self.toons) == 0:
            self.timer.stop()
            if self.fsm.getCurrentState().getName() == 'Resting':
                pass
            elif self.battle == None:
                self.bldg.deleteCogdoInterior()

    def _handleToonWentSad(self, toonId):
        self.notify.info('toon: %d went sad' % toonId)
        toon = self.air.getDo(toonId)
        if toon:
            self.ignore(toon.getGoneSadMessage())

        if self._game:
            self._game.handleToonWentSad(toonId)

        self._DistributedCogdoInteriorAI__removeToon(toonId)
        if len(self.toons) == 0:
            self.timer.stop()
            if self.fsm.getCurrentState().getName() == 'Resting':
                pass
            elif self.battle == None:
                self._sadCleanupTask = taskMgr.doMethodLater(
                    20, self._cleanupAfterLastToonWentSad,
                    self.uniqueName('sadcleanup'))

    def _cleanupAfterLastToonWentSad(self, task):
        self._sadCleanupTask = None
        self.bldg.deleteCogdoInterior()
        return task.done

    def _DistributedCogdoInteriorAI__addToon(self, toonId):
        if not self.air.doId2do.has_key(toonId):
            self.notify.warning('addToon() - no toon for doId: %d' % toonId)
            return None

        event = self.air.getAvatarExitEvent(toonId)
        self.avatarExitEvents.append(event)
        self.accept(event,
                    self._DistributedCogdoInteriorAI__handleUnexpectedExit,
                    extraArgs=[toonId])
        self.toons.append(toonId)
        self.responses[toonId] = 0

    def _DistributedCogdoInteriorAI__removeToon(self, toonId):
        if self.toons.count(toonId):
            self.toons.remove(toonId)

        if self.toonIds.count(toonId):
            self.toonIds[self.toonIds.index(toonId)] = None

        if self.responses.has_key(toonId):
            del self.responses[toonId]

        event = self.air.getAvatarExitEvent(toonId)
        if self.avatarExitEvents.count(event):
            self.avatarExitEvents.remove(event)

        self.ignore(event)

    def _DistributedCogdoInteriorAI__resetResponses(self):
        self.responses = {}
        for toon in self.toons:
            self.responses[toon] = 0

        self.ignoreResponses = 0

    def _DistributedCogdoInteriorAI__allToonsResponded(self):
        for toon in self.toons:
            if self.responses[toon] == 0:
                return 0
                continue

        self.ignoreResponses = 1
        return 1

    def getZoneId(self):
        return self.zoneId

    def getExtZoneId(self):
        return self.extZoneId

    def getDistBldgDoId(self):
        return self.bldg.getDoId()

    def getNumFloors(self):
        return self._numFloors

    def d_setToons(self):
        self.sendUpdate('setToons', self.getToons())

    def getToons(self):
        sendIds = []
        for toonId in self.toonIds:
            if toonId == None:
                sendIds.append(0)
                continue
            sendIds.append(toonId)

        return [sendIds, 0]

    def d_setSuits(self):
        self.sendUpdate('setSuits', self.getSuits())

    def getSuits(self):
        suitIds = []
        for suit in self.activeSuits:
            suitIds.append(suit.doId)

        reserveIds = []
        values = []
        for info in self.reserveSuits:
            reserveIds.append(info[0].doId)
            values.append(info[1])

        return [suitIds, reserveIds, values]

    def b_setState(self, state):
        self.d_setState(state)
        self.setState(state)

    def d_setState(self, state):
        stime = globalClock.getRealTime() + BattleBase.SERVER_BUFFER_TIME
        self.sendUpdate(
            'setState',
            [state, globalClockDelta.localToNetworkTime(stime)])

    def setState(self, state):
        self.fsm.request(state)

    def getState(self):
        return [
            self.fsm.getCurrentState().getName(),
            globalClockDelta.getRealNetworkTime()
        ]

    def setAvatarJoined(self):
        avId = self.air.getAvatarIdFromSender()
        if self.toons.count(avId) == 0:
            self.air.writeServerEvent(
                'suspicious', avId,
                'DistributedCogdoInteriorAI.setAvatarJoined from toon not in %s.'
                % self.toons)
            self.notify.warning('setAvatarJoined() - av: %d not in list' %
                                avId)
            return None

        avatar = self.air.doId2do.get(avId)
        if avatar != None:
            self.savedByMap[avId] = (avatar.getName(), avatar.dna.asTuple())

        self.responses[avId] += 1
        if self._DistributedCogdoInteriorAI__allToonsResponded():
            self.fsm.request('Elevator')

    def elevatorDone(self):
        toonId = self.air.getAvatarIdFromSender()
        if self.ignoreResponses == 1:
            return None
        elif self.fsm.getCurrentState().getName() != 'Elevator':
            self.notify.warning('elevatorDone() - in state: %s' %
                                self.fsm.getCurrentState().getName())
            return None
        elif self.toons.count(toonId) == 0:
            self.notify.warning('elevatorDone() - toon not in toon list: %d' %
                                toonId)
            return None

        self.responses[toonId] += 1
        if self._DistributedCogdoInteriorAI__allToonsResponded(
        ) and self.ignoreElevatorDone == 0:
            self.b_setState('Game')

    def reserveJoinDone(self):
        toonId = self.air.getAvatarIdFromSender()
        if self.ignoreResponses == 1:
            return None
        elif self.fsm.getCurrentState().getName() != 'ReservesJoining':
            self.notify.warning('reserveJoinDone() - in state: %s' %
                                self.fsm.getCurrentState().getName())
            return None
        elif self.toons.count(toonId) == 0:
            self.notify.warning('reserveJoinDone() - toon not in list: %d' %
                                toonId)
            return None

        self.responses[toonId] += 1
        if self._DistributedCogdoInteriorAI__allToonsResponded(
        ) and self.ignoreReserveJoinDone == 0:
            self.b_setState('Battle')

    def isBossFloor(self, floorNum):
        if self.layout.hasBossBattle():
            if self.layout.getBossBattleFloor() == floorNum:
                return True

        return False

    def isTopFloor(self, floorNum):
        return self.layout.getNumFloors() - 1 == floorNum

    def enterOff(self):
        pass

    def exitOff(self):
        pass

    def enterWaitForAllToonsInside(self):
        self._DistributedCogdoInteriorAI__resetResponses()

    def exitWaitForAllToonsInside(self):
        self._DistributedCogdoInteriorAI__resetResponses()

    def enterElevator(self):
        if self.isBossFloor(self.currentFloor):
            self._populateFloorSuits()
        else:
            self.d_setToons()
        self._DistributedCogdoInteriorAI__resetResponses()
        self._game = self._createGame()
        self.d_setState('Elevator')
        self.timer.startCallback(
            BattleBase.ELEVATOR_T + ElevatorData[ELEVATOR_NORMAL]['openTime'] +
            BattleBase.SERVER_BUFFER_TIME,
            self._DistributedCogdoInteriorAI__serverElevatorDone)

    def _createGame(self):
        game = None
        if not self.isBossFloor(self.currentFloor):
            for toonId in self.toonIds:
                if toonId:
                    toon = self.air.getDo(toonId)
                    if toon:
                        self.accept(toon.getGoneSadMessage(),
                                    Functor(self._handleToonWentSad, toonId))

            game = DistCogdoCraneGameAI(self.air, self)
            game.generateWithRequired(self.zoneId)

        return game

    def _DistributedCogdoInteriorAI__serverElevatorDone(self):
        self.ignoreElevatorDone = 1
        self.b_setState('Game')

    def exitElevator(self):
        self.timer.stop()
        self._DistributedCogdoInteriorAI__resetResponses()

    def enterGame(self):
        self.d_setState('Game')
        self.elevator.d_setFloor(self.currentFloor)
        if self._game:
            self._game.start()
        else:
            self._gameDone()

    def _populateFloorSuits(self):
        suitHandles = self.bldg.planner.genFloorSuits(self.currentFloor)
        self.suits = suitHandles['activeSuits']
        self.activeSuits = []
        for suit in self.suits:
            self.activeSuits.append(suit)

        self.reserveSuits = suitHandles['reserveSuits']
        self.d_setToons()
        self.d_setSuits()

    def _gameDone(self):
        if len(self.toons) == 0:
            return None

        if not self.isBossFloor(self.currentFloor):
            self._populateFloorSuits()

        self.b_setState('Battle')
        if self._game:
            self._game.requestDelete()
            self._game = None
            for toonId in self.toonIds:
                if toonId:
                    toon = self.air.getDo(toonId)
                    if toon:
                        self.ignore(toon.getGoneSadMessage())

    def exitGame(self):
        pass

    def _DistributedCogdoInteriorAI__createFloorBattle(self):
        if self.isBossFloor(self.currentFloor):
            bossBattle = 1
        else:
            bossBattle = 0
        self.battle = DistributedBattleBldgAI.DistributedBattleBldgAI(
            self.air,
            self.zoneId,
            self._DistributedCogdoInteriorAI__handleRoundDone,
            self._DistributedCogdoInteriorAI__handleBattleDone,
            bossBattle=bossBattle)
        self.battle.suitsKilled = self.suitsKilled
        self.battle.suitsKilledPerFloor = self.suitsKilledPerFloor
        self.battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained
        self.battle.toonExp = self.toonExp
        self.battle.toonOrigQuests = self.toonOrigQuests
        self.battle.toonItems = self.toonItems
        self.battle.toonOrigMerits = self.toonOrigMerits
        self.battle.toonMerits = self.toonMerits
        self.battle.toonParts = self.toonParts
        self.battle.helpfulToons = self.helpfulToons
        self.battle.setInitialMembers(self.toons, self.suits)
        self.battle.generateWithRequired(self.zoneId)
        mult = getCreditMultiplier(self.currentFloor)
        if self.air.suitInvasionManager.getInvading():
            mult *= getInvasionMultiplier()

        self.battle.battleCalc.setSkillCreditMultiplier(mult)

    def _DistributedCogdoInteriorAI__cleanupFloorBattle(self):
        for suit in self.suits:
            self.notify.debug('cleaning up floor suit: %d' % suit.doId)
            if suit.isDeleted():
                self.notify.debug('whoops, suit %d is deleted.' % suit.doId)
                continue
            suit.requestDelete()

        self.suits = []
        self.reserveSuits = []
        self.activeSuits = []
        if self.battle != None:
            self.battle.requestDelete()

        self.battle = None

    def _DistributedCogdoInteriorAI__handleRoundDone(self, toonIds, totalHp,
                                                     deadSuits):
        totalMaxHp = 0
        for suit in self.suits:
            totalMaxHp += suit.maxHP

        for suit in deadSuits:
            self.activeSuits.remove(suit)

        if len(self.reserveSuits) > 0 and len(self.activeSuits) < 4:
            self.joinedReserves = []
            hpPercent = 100 - (totalHp / totalMaxHp) * 100.0
            for info in self.reserveSuits:
                if info[1] <= hpPercent and len(self.activeSuits) < 4:
                    self.suits.append(info[0])
                    self.activeSuits.append(info[0])
                    self.joinedReserves.append(info)
                    continue

            for info in self.joinedReserves:
                self.reserveSuits.remove(info)

            if len(self.joinedReserves) > 0:
                self.fsm.request('ReservesJoining')
                self.d_setSuits()
                return None

        if len(self.activeSuits) == 0:
            self.fsm.request('BattleDone', [toonIds])
        else:
            self.battle.resume()

    def _DistributedCogdoInteriorAI__handleBattleDone(self, zoneId, toonIds):
        if len(toonIds) == 0:
            taskName = self.taskName('deleteInterior')
            taskMgr.doMethodLater(
                10, self._DistributedCogdoInteriorAI__doDeleteInterior,
                taskName)
        elif self.isTopFloor(self.currentFloor):
            self.setState('Reward')
        else:
            self.b_setState('Resting')

    def _DistributedCogdoInteriorAI__doDeleteInterior(self, task):
        self.bldg.deleteCogdoInterior()

    def enterBattle(self):
        if self.battle == None:
            self._DistributedCogdoInteriorAI__createFloorBattle()

    def exitBattle(self):
        pass

    def enterReservesJoining(self):
        self._DistributedCogdoInteriorAI__resetResponses()
        self.timer.startCallback(
            ElevatorData[ELEVATOR_NORMAL]['openTime'] +
            SUIT_HOLD_ELEVATOR_TIME + BattleBase.SERVER_BUFFER_TIME,
            self._DistributedCogdoInteriorAI__serverReserveJoinDone)

    def _DistributedCogdoInteriorAI__serverReserveJoinDone(self):
        self.ignoreReserveJoinDone = 1
        self.b_setState('Battle')

    def exitReservesJoining(self):
        self.timer.stop()
        self._DistributedCogdoInteriorAI__resetResponses()
        for info in self.joinedReserves:
            self.battle.suitRequestJoin(info[0])

        self.battle.resume()
        self.joinedReserves = []

    def enterBattleDone(self, toonIds):
        if len(toonIds) != len(self.toons):
            deadToons = []
            for toon in self.toons:
                if toonIds.count(toon) == 0:
                    deadToons.append(toon)
                    continue

            for toon in deadToons:
                self._DistributedCogdoInteriorAI__removeToon(toon)

        self.d_setToons()
        if len(self.toons) == 0:
            self.bldg.deleteCogdoInterior()
        elif self.isTopFloor(self.currentFloor):
            self.battle.resume(self.currentFloor, topFloor=1)
        else:
            self.battle.resume(self.currentFloor, topFloor=0)

    def exitBattleDone(self):
        self._DistributedCogdoInteriorAI__cleanupFloorBattle()
        self.d_setSuits()

    def _DistributedCogdoInteriorAI__handleEnterElevator(self):
        self.fsm.request('Elevator')

    def enterResting(self):
        self.intElevator = DistributedCogdoElevatorIntAI(
            self.air, self, self.toons)
        self.intElevator.generateWithRequired(self.zoneId)

    def handleAllAboard(self, seats):
        if not hasattr(self, 'fsm'):
            return None

        numOfEmptySeats = seats.count(None)
        if numOfEmptySeats == 4:
            self.bldg.deleteCogdoInterior()
            return None
        elif numOfEmptySeats >= 0 and numOfEmptySeats <= 3:
            pass
        else:
            self.error('Bad number of empty seats: %s' % numOfEmptySeats)
        for toon in self.toons:
            if seats.count(toon) == 0:
                self._DistributedCogdoInteriorAI__removeToon(toon)
                continue

        self.toonIds = copy.copy(seats)
        self.toons = []
        for toonId in self.toonIds:
            if toonId != None:
                self.toons.append(toonId)
                continue

        self.d_setToons()
        self.currentFloor += 1
        self.fsm.request('Elevator')

    def exitResting(self):
        self.intElevator.requestDelete()
        del self.intElevator

    def enterReward(self):
        victors = self.toonIds[:]
        savedBy = []
        for v in victors:
            tuple = self.savedByMap.get(v)
            if tuple:
                savedBy.append([v, tuple[0], tuple[1]])
                continue

        self.bldg.fsm.request('waitForVictorsFromCogdo', [victors, savedBy])
        self.d_setState('Reward')

    def exitReward(self):
        pass
class DistributedCogdoInteriorAI(DistributedObjectAI.DistributedObjectAI):
    """
    DistributedCogdoInteriorAI class:  
    """

    if __debug__:
        notify = DirectNotifyGlobal.directNotify.newCategory(
            'DistributedCogdoInteriorAI')

    def __init__(self, air, elevator):
        self.air = air
        DistributedObjectAI.DistributedObjectAI.__init__(self, air)
        self.extZoneId, self.zoneId = elevator.bldg.getExteriorAndInteriorZoneId(
        )
        self._numFloors = elevator.bldg.planner.numFloors
        self.layout = elevator.bldg._cogdoLayout
        assert (len(elevator.seats) == 4)

        self.avatarExitEvents = []
        self.toons = []
        self.toonSkillPtsGained = {}
        self.toonExp = {}
        self.toonOrigQuests = {}
        self.toonItems = {}
        self.toonOrigMerits = {}
        self.toonMerits = {}
        self.toonParts = {}
        self.helpfulToons = []

        self.currentFloor = 0
        self.bldg = elevator.bldg
        self.elevator = elevator

        self._game = None

        self.suits = []
        self.activeSuits = []
        self.reserveSuits = []
        self.joinedReserves = []
        self.suitsKilled = []
        self.suitsKilledPerFloor = []
        self.battle = None

        self.timer = Timer.Timer()

        self.responses = {}
        self.ignoreResponses = 0
        self.ignoreElevatorDone = 0
        self.ignoreReserveJoinDone = 0

        # Register all the toons
        self.toonIds = copy.copy(elevator.seats)
        for toonId in self.toonIds:
            if (toonId != None):
                self.__addToon(toonId)
        assert (len(self.toons) > 0)

        # Build a map of id:(name, style) pairs to have around for the
        # end in case the toons are successful.  These elements are
        # filled in as each toon registers with the building.
        self.savedByMap = {}

        self.fsm = ClassicFSM.ClassicFSM(
            'DistributedCogdoInteriorAI',
            [
                State.State('WaitForAllToonsInside',
                            self.enterWaitForAllToonsInside,
                            self.exitWaitForAllToonsInside, ['Elevator']),
                State.State('Elevator', self.enterElevator, self.exitElevator,
                            ['Game']),
                State.State('Game', self.enterGame, self.exitGame, ['Battle']),
                State.State('Battle', self.enterBattle, self.exitBattle,
                            ['ReservesJoining', 'BattleDone']),
                State.State('ReservesJoining', self.enterReservesJoining,
                            self.exitReservesJoining, ['Battle']),
                State.State('BattleDone', self.enterBattleDone,
                            self.exitBattleDone, ['Resting', 'Reward']),
                State.State('Resting', self.enterResting, self.exitResting,
                            ['Elevator']),
                State.State('Reward', self.enterReward, self.exitReward,
                            ['Off']),
                State.State('Off', self.enterOff, self.exitOff,
                            ['WaitForAllToonsInside'])
            ],
            # Initial state
            'Off',
            # Final state
            'Off',
            onUndefTransition=ClassicFSM.ClassicFSM.ALLOW)
        self.fsm.enterInitialState()

    def delete(self):
        assert (self.notify.debug('delete()'))
        self.ignoreAll()
        self.toons = []
        self.toonIds = []
        self.fsm.requestFinalState()
        del self.fsm
        del self.bldg
        del self.elevator
        self.timer.stop()
        del self.timer
        self._cogdoLayout = None
        self.__cleanupFloorBattle()

        taskName = self.taskName('deleteInterior')
        taskMgr.remove(taskName)

        DistributedObjectAI.DistributedObjectAI.delete(self)

    def requestDelete(self):
        if self._game:
            self._game.requestDelete()
        DistributedObjectAI.DistributedObjectAI.requestDelete(self)

    def addGameFSM(self, gameFSM):
        """ games should call this with their game ClassicFSM """
        self.fsm.getStateNamed('Game').addChild(gameFSM)

    def removeGameFSM(self, gameFSM):
        """ games should call this with their game ClassicFSM """
        self.fsm.getStateNamed('Game').removeChild(gameFSM)

    def __handleUnexpectedExit(self, toonId):
        self.notify.warning('toon: %d exited unexpectedly' % toonId)
        if self._game:
            self._game.handleToonDisconnected(toonId)
        self.__removeToon(toonId)
        if (len(self.toons) == 0):
            assert (self.notify.debug('last toon is gone!'))
            self.timer.stop()
            # The last toon exited unexpectedly - if we're in a battle, let
            # the battle clean up first, if we're in Resting state, let
            # the interior elevator clean up first, otherwise, just
            # reset the suit interior.
            if (self.fsm.getCurrentState().getName() == 'Resting'):
                pass
            elif (self.battle == None):
                self.bldg.deleteCogdoInterior()

    def _handleToonWentSad(self, toonId):
        # for the game only, the battle handles this differently
        self.notify.info('toon: %d went sad' % toonId)
        toon = self.air.getDo(toonId)
        if toon:
            self.ignore(toon.getGoneSadMessage())
        if self._game:
            self._game.handleToonWentSad(toonId)
        self.__removeToon(toonId)
        if (len(self.toons) == 0):
            assert (self.notify.debug('last toon is gone!'))
            self.timer.stop()
            # The last toon exited unexpectedly - if we're in a battle, let
            # the battle clean up first, if we're in Resting state, let
            # the interior elevator clean up first, otherwise, just
            # reset the suit interior.
            if (self.fsm.getCurrentState().getName() == 'Resting'):
                pass
            elif (self.battle == None):
                # give the last client out time to play out the sad animation sequence
                self._sadCleanupTask = taskMgr.doMethodLater(
                    20, self._cleanupAfterLastToonWentSad,
                    self.uniqueName('sadcleanup'))

    def _cleanupAfterLastToonWentSad(self, task):
        self._sadCleanupTask = None
        self.bldg.deleteCogdoInterior()
        return task.done

    def __addToon(self, toonId):
        assert (self.notify.debug('addToon(%d)' % toonId))
        if (not self.air.doId2do.has_key(toonId)):
            self.notify.warning('addToon() - no toon for doId: %d' % toonId)
            return

        # Handle unexpected exits for the toon
        event = self.air.getAvatarExitEvent(toonId)
        self.avatarExitEvents.append(event)
        self.accept(event, self.__handleUnexpectedExit, extraArgs=[toonId])

        self.toons.append(toonId)
        assert (not self.responses.has_key(toonId))
        self.responses[toonId] = 0

    def __removeToon(self, toonId):
        assert (self.notify.debug('removeToon(%d)' % toonId))

        if self.toons.count(toonId):
            self.toons.remove(toonId)

        if self.toonIds.count(toonId):
            self.toonIds[self.toonIds.index(toonId)] = None

        if self.responses.has_key(toonId):
            del self.responses[toonId]

        # Ignore future exit events for the toon
        event = self.air.getAvatarExitEvent(toonId)
        if self.avatarExitEvents.count(event):
            self.avatarExitEvents.remove(event)
        self.ignore(event)

    def __resetResponses(self):
        self.responses = {}
        for toon in self.toons:
            self.responses[toon] = 0
        self.ignoreResponses = 0

    def __allToonsResponded(self):
        for toon in self.toons:
            assert (self.responses.has_key(toon))
            if (self.responses[toon] == 0):
                return 0
        self.ignoreResponses = 1
        return 1

    # Distributed Messages

    # setZoneIdAndBlock

    def getZoneId(self):
        assert (self.notify.debug('network:getZoneId()'))
        return self.zoneId

    def getExtZoneId(self):
        return self.extZoneId

    def getDistBldgDoId(self):
        return self.bldg.getDoId()

    def getNumFloors(self):
        return self._numFloors

    # setToons()

    def d_setToons(self):
        assert (self.notify.debug('network:setToons()'))
        self.sendUpdate('setToons', self.getToons())

    def getToons(self):
        sendIds = []
        for toonId in self.toonIds:
            if (toonId == None):
                sendIds.append(0)
            else:
                sendIds.append(toonId)
        assert (self.notify.debug('getToons(): %s' % sendIds))
        return [sendIds, 0]

    # setSuits()

    def d_setSuits(self):
        assert (self.notify.debug('network:setSuits()'))
        self.sendUpdate('setSuits', self.getSuits())

    def getSuits(self):
        suitIds = []
        for suit in self.activeSuits:
            suitIds.append(suit.doId)
        reserveIds = []
        values = []
        for info in self.reserveSuits:
            reserveIds.append(info[0].doId)
            values.append(info[1])
        return [suitIds, reserveIds, values]

    # setState()

    def b_setState(self, state):
        self.d_setState(state)
        self.setState(state)

    def d_setState(self, state):
        assert (self.notify.debug('network:setState(%s)' % state))
        stime = globalClock.getRealTime() + BattleBase.SERVER_BUFFER_TIME
        self.sendUpdate(
            'setState',
            [state, globalClockDelta.localToNetworkTime(stime)])

    def setState(self, state):
        self.fsm.request(state)

    def getState(self):
        return [
            self.fsm.getCurrentState().getName(),
            globalClockDelta.getRealNetworkTime()
        ]

    ##### Messages from the clients #####

    def setAvatarJoined(self):
        """setAvatarJoined(self)
        This message is sent from a client toon to indicate that it
        has finished loading the interior.
        """
        avId = self.air.getAvatarIdFromSender()
        if (self.toons.count(avId) == 0):
            self.air.writeServerEvent(
                'suspicious', avId,
                'DistributedCogdoInteriorAI.setAvatarJoined from toon not in %s.'
                % (self.toons))
            self.notify.warning('setAvatarJoined() - av: %d not in list' % \
                avId)
            return

        avatar = self.air.doId2do.get(avId)
        if avatar != None:
            self.savedByMap[avId] = (avatar.getName(), avatar.dna.asTuple())

        assert (self.responses.has_key(avId))
        self.responses[avId] += 1
        assert (self.notify.debug('toon: %d in suit interior' % avId))
        if (self.__allToonsResponded()):
            self.fsm.request('Elevator')

    def elevatorDone(self):
        """elevatorDone(self)
        This message is sent from a client toon to indicate that it has
        finished viewing the elevator movie.
        """
        toonId = self.air.getAvatarIdFromSender()
        if (self.ignoreResponses == 1):
            assert(self.notify.debug('elevatorDone() ignoring toon: %d' % \
                toonId))
            return
        elif (self.fsm.getCurrentState().getName() != 'Elevator'):
            self.notify.warning('elevatorDone() - in state: %s' % \
                self.fsm.getCurrentState().getName())
            return
        elif (self.toons.count(toonId) == 0):
            self.notify.warning('elevatorDone() - toon not in toon list: %d' \
                % toonId)
            assert(self.notify.debug('toons: %s toonIds: %s' % \
                        (self.toons, self.toonIds)))
            return
        assert (self.responses.has_key(toonId))
        self.responses[toonId] += 1
        assert (self.notify.debug('toon: %d done with elevator' % toonId))
        if (self.__allToonsResponded() and self.ignoreElevatorDone == 0):
            self.b_setState('Game')

    def reserveJoinDone(self):
        """reserveJoinDone(self)
        This message is sent from a client toon to indicate that it has
        finished viewing the ReservesJoining movie.
        """
        toonId = self.air.getAvatarIdFromSender()
        if (self.ignoreResponses == 1):
            assert(self.notify.debug('reserveJoinDone() ignoring toon: %d' % \
                toonId))
            return
        elif (self.fsm.getCurrentState().getName() != 'ReservesJoining'):
            self.notify.warning('reserveJoinDone() - in state: %s' % \
                self.fsm.getCurrentState().getName())
            return
        elif (self.toons.count(toonId) == 0):
            self.notify.warning('reserveJoinDone() - toon not in list: %d' \
                % toonId)
            assert(self.notify.debug('toons: %s toonIds: %s' % \
                        (self.toons, self.toonIds)))
            return
        assert (self.responses.has_key(toonId))
        self.responses[toonId] += 1
        assert(self.notify.debug('toon: %d done with joining reserves' % \
                                toonId))
        if (self.__allToonsResponded() and self.ignoreReserveJoinDone == 0):
            self.b_setState('Battle')

    def isBossFloor(self, floorNum):
        if self.layout.hasBossBattle():
            if self.layout.getBossBattleFloor() == floorNum:
                return True
        return False

    def isTopFloor(self, floorNum):
        return (self.layout.getNumFloors() - 1) == floorNum

    # Specific State Functions

    ##### Off state #####

    def enterOff(self):
        assert (self.notify.debug('enterOff()'))
        return None

    def exitOff(self):
        return None

    ##### WaitForAllToonsInside state #####

    def enterWaitForAllToonsInside(self):
        assert (self.notify.debug('enterWaitForAllToonsInside()'))
        self.__resetResponses()
        return None

    def exitWaitForAllToonsInside(self):
        self.__resetResponses()
        return None

    ##### Elevator state #####

    def enterElevator(self):
        assert (self.notify.debug('enterElevator()'))

        if self.isBossFloor(self.currentFloor):
            self._populateFloorSuits()
        else:
            self.d_setToons()

        self.__resetResponses()

        # create the game here so the players see something when the doors open
        self._game = self._createGame()

        self.d_setState('Elevator')

        self.timer.startCallback(BattleBase.ELEVATOR_T + \
                       ElevatorData[ELEVATOR_NORMAL]['openTime'] + \
                       BattleBase.SERVER_BUFFER_TIME, self.__serverElevatorDone)
        return None

    def _createGame(self):
        game = None
        if not self.isBossFloor(self.currentFloor):
            for toonId in self.toonIds:
                if toonId:
                    toon = self.air.getDo(toonId)
                    if toon:
                        self.accept(toon.getGoneSadMessage(),
                                    Functor(self._handleToonWentSad, toonId))

            game = DistCogdoCraneGameAI(self.air, self)
            game.generateWithRequired(self.zoneId)
        return game

    def __serverElevatorDone(self):
        assert (self.notify.debug('serverElevatorDone()'))
        self.ignoreElevatorDone = 1
        self.b_setState('Game')

    def exitElevator(self):
        self.timer.stop()
        self.__resetResponses()
        return None

    ##### Game state #####

    def enterGame(self):
        assert (self.notify.debug('enterGame()'))

        self.d_setState('Game')
        self.elevator.d_setFloor(self.currentFloor)

        # no game on the top floor, breeze on through
        if self._game:
            self._game.start()
        else:
            self._gameDone()

    def _populateFloorSuits(self):
        # Create the suits and place them in their initial positions on
        # the floor
        assert (self.currentFloor < self.layout.getNumFloors())
        suitHandles = self.bldg.planner.genFloorSuits(self.currentFloor)
        self.suits = suitHandles['activeSuits']
        assert (len(self.suits) > 0)
        self.activeSuits = []
        for suit in self.suits:
            self.activeSuits.append(suit)
        self.reserveSuits = suitHandles['reserveSuits']

        self.d_setToons()
        # do this before setting state to 'battle' otherwise client show will get messed up
        self.d_setSuits()

    def _gameDone(self):
        if len(self.toons) == 0:
            # all toons went sad but game is still running so that the game doesn't disappear
            # on the player's client as he watches sad animation. ignore this event
            return
        if not self.isBossFloor(self.currentFloor):
            self._populateFloorSuits()
        self.b_setState('Battle')
        if self._game:
            self._game.requestDelete()
            self._game = None

            for toonId in self.toonIds:
                if toonId:
                    toon = self.air.getDo(toonId)
                    if toon:
                        self.ignore(toon.getGoneSadMessage())

    def exitGame(self):
        pass

    ##### Battle state #####

    def __createFloorBattle(self):
        assert (len(self.toons) > 0)
        if self.isBossFloor(self.currentFloor):
            assert (self.notify.debug('createFloorBattle() - boss battle'))
            bossBattle = 1
        else:
            bossBattle = 0
        # Create the battle for the floor
        self.battle = DistributedBattleBldgAI.DistributedBattleBldgAI(
            self.air,
            self.zoneId,
            self.__handleRoundDone,
            self.__handleBattleDone,
            bossBattle=bossBattle)

        # We store the lists of experience gained and suits killed in
        # the DistributedCogdoInterior object, and share these pointers
        # in all battles created for this building.  This way, each
        # battle will actually be modifying the same objects, these,
        # and will thus accumulate the experience from previous
        # battles.
        self.battle.suitsKilled = self.suitsKilled
        self.battle.suitsKilledPerFloor = self.suitsKilledPerFloor
        self.battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained
        self.battle.toonExp = self.toonExp
        self.battle.toonOrigQuests = self.toonOrigQuests
        self.battle.toonItems = self.toonItems
        self.battle.toonOrigMerits = self.toonOrigMerits
        self.battle.toonMerits = self.toonMerits
        self.battle.toonParts = self.toonParts
        self.battle.helpfulToons = self.helpfulToons

        # We must set the members of a building battle before we
        # generate it.
        self.battle.setInitialMembers(self.toons, self.suits)
        self.battle.generateWithRequired(self.zoneId)

        # We get a bonus factor applied toward each attack's experience credit.
        mult = getCreditMultiplier(self.currentFloor)

        # If there is an invasion, multiply the exp for the duration of this battle
        # Now, if the invasion ends midway through this battle, the players will
        # continue getting credit. This is ok I guess.
        if self.air.suitInvasionManager.getInvading():
            mult *= getInvasionMultiplier()

        self.battle.battleCalc.setSkillCreditMultiplier(mult)

    def __cleanupFloorBattle(self):
        for suit in self.suits:
            self.notify.debug('cleaning up floor suit: %d' % suit.doId)
            if suit.isDeleted():
                self.notify.debug('whoops, suit %d is deleted.' % suit.doId)
            else:
                suit.requestDelete()
        self.suits = []
        self.reserveSuits = []
        self.activeSuits = []
        if (self.battle != None):
            self.battle.requestDelete()
        self.battle = None

    def __handleRoundDone(self, toonIds, totalHp, deadSuits):
        # Determine if any reserves need to join
        assert (self.notify.debug('handleRoundDone() - hp: %d' % totalHp))
        # Calculate the total max HP for all the suits currently on the floor
        totalMaxHp = 0
        for suit in self.suits:
            totalMaxHp += suit.maxHP

        for suit in deadSuits:
            self.activeSuits.remove(suit)

        # Determine if any reserve suits need to join
        if (len(self.reserveSuits) > 0 and len(self.activeSuits) < 4):
            assert(self.notify.debug('potential reserve suits: %d' % \
                len(self.reserveSuits)))
            self.joinedReserves = []
            assert (totalHp <= totalMaxHp)
            hpPercent = 100 - (totalHp / totalMaxHp * 100.0)
            assert(self.notify.debug('totalHp: %d totalMaxHp: %d percent: %f' \
                % (totalHp, totalMaxHp, hpPercent)))
            for info in self.reserveSuits:
                if (info[1] <= hpPercent and len(self.activeSuits) < 4):
                    assert(self.notify.debug('reserve: %d joining percent: %f' \
                        % (info[0].doId, info[1])))
                    self.suits.append(info[0])
                    self.activeSuits.append(info[0])
                    self.joinedReserves.append(info)
            for info in self.joinedReserves:
                self.reserveSuits.remove(info)
            if (len(self.joinedReserves) > 0):
                # setSuits() triggers the state change on the client
                self.fsm.request('ReservesJoining')
                self.d_setSuits()
                return

        # See if the battle is done
        if (len(self.activeSuits) == 0):
            self.fsm.request('BattleDone', [toonIds])
        else:
            # No reserve suits to join - tell the battle to continue
            self.battle.resume()

    def __handleBattleDone(self, zoneId, toonIds):
        assert (self.notify.debug('%s.handleBattleDone(%s, %s)' %
                                  (self.doId, zoneId, toonIds)))
        #self.fsm.request('BattleDone', [toonIds])
        if (len(toonIds) == 0):
            assert (self.notify.debug('handleBattleDone() - last toon gone'))

            # Rather than shutting down immediately, we give the last
            # toon a few seconds to finish playing his teleport-out
            # animation before the world goes away.

            taskName = self.taskName('deleteInterior')
            taskMgr.doMethodLater(10, self.__doDeleteInterior, taskName)

        elif self.isTopFloor(self.currentFloor):
            # This is not b_setState, because enterReward has to do
            # some things before the broadcast takes place. enterReward
            # will call d_setState when it is ready.
            self.setState('Reward')
        else:
            self.b_setState('Resting')

    def __doDeleteInterior(self, task):
        self.bldg.deleteCogdoInterior()

    def enterBattle(self):
        assert (self.notify.debug('enterBattle()'))

        if (self.battle == None):
            self.__createFloorBattle()
        return None

    def exitBattle(self):
        return None

    ##### ReservesJoining state #####

    def enterReservesJoining(self):
        assert (self.notify.debug('enterReservesJoining()'))
        self.__resetResponses()
        self.timer.startCallback(
            ElevatorData[ELEVATOR_NORMAL]['openTime'] + \
            SUIT_HOLD_ELEVATOR_TIME + \
            BattleBase.SERVER_BUFFER_TIME,
            self.__serverReserveJoinDone)
        return None

    def __serverReserveJoinDone(self):
        """__serverReserveJoinDone()
        This callback is made only if some of the toons don't send
        their reserveJoinDone() message in a reasonable time--rather
        than waiting for everyone, we simply carry on without them.
        """
        assert (self.notify.debug('serverReserveJoinDone()'))
        self.ignoreReserveJoinDone = 1
        self.b_setState('Battle')

    def exitReservesJoining(self):
        self.timer.stop()
        self.__resetResponses()
        # Join the suits to the battle and tell it to resume
        for info in self.joinedReserves:
            self.battle.suitRequestJoin(info[0])
        self.battle.resume()
        self.joinedReserves = []
        return None

    ##### BattleDone state #####

    def enterBattleDone(self, toonIds):
        assert (self.notify.debug('enterBattleDone()'))
        # Find out if any toons are gone
        if (len(toonIds) != len(self.toons)):
            deadToons = []
            for toon in self.toons:
                if (toonIds.count(toon) == 0):
                    deadToons.append(toon)
            for toon in deadToons:
                self.__removeToon(toon)
        self.d_setToons()

        if (len(self.toons) == 0):
            self.bldg.deleteCogdoInterior()
        else:
            if self.isTopFloor(self.currentFloor):
                # Toons beat the building
                self.battle.resume(self.currentFloor, topFloor=1)
            else:
                # The building isn't finished yet - gather up experience and
                # activate the elevator
                self.battle.resume(self.currentFloor, topFloor=0)
        return None

    def exitBattleDone(self):
        self.__cleanupFloorBattle()
        self.d_setSuits()
        return None

    ##### Resting state #####

    def __handleEnterElevator(self):
        self.fsm.request('Elevator')

    def enterResting(self):
        assert (self.notify.debug('enterResting()'))
        # Tell the elevator to start accepting entrants
        self.intElevator = DistributedCogdoElevatorIntAI(
            self.air, self, self.toons)
        self.intElevator.generateWithRequired(self.zoneId)
        return None

    def handleAllAboard(self, seats):
        if not hasattr(self, "fsm"):
            # If we've already been cleaned up, never mind.
            return

        assert (self.fsm.getCurrentState().getName() == "Resting")
        assert (self.notify.debug('handleAllAboard() - toons: %s' %
                                  self.toons))

        # Make sure the number of empty seats is correct. If it is empty,
        # reset and get us out of here.
        numOfEmptySeats = seats.count(None)
        if (numOfEmptySeats == 4):
            self.bldg.deleteCogdoInterior()
            return
        elif (numOfEmptySeats >= 0) and (numOfEmptySeats <= 3):
            pass
        else:
            self.error("Bad number of empty seats: %s" % numOfEmptySeats)

        for toon in self.toons:
            if seats.count(toon) == 0:
                self.__removeToon(toon)
        self.toonIds = copy.copy(seats)
        self.toons = []
        for toonId in self.toonIds:
            if (toonId != None):
                self.toons.append(toonId)

        self.d_setToons()

        # Increment the floor number
        self.currentFloor += 1
        self.fsm.request('Elevator')
        return

    def exitResting(self):
        self.intElevator.requestDelete()
        del self.intElevator
        return None

    ##### Reward state #####

    def enterReward(self):
        assert (self.notify.debug('enterReward()'))
        # Tell the building to get ready for the victors to come outside.

        # We pass in a *copy* of the toonIds list, not the list
        # itself, so that when we pull toons out of our list (for
        # instance, if they disconnect unexpectedly), it won't affect
        # the building's victor list.
        victors = self.toonIds[:]

        # Build a new savedBy list that includes only the Toons that
        # made it to the end.
        savedBy = []
        for v in victors:
            tuple = self.savedByMap.get(v)
            if tuple:
                savedBy.append([v, tuple[0], tuple[1]])

        # Going to waitForVictors deletes the elevator
        self.bldg.fsm.request("waitForVictorsFromCogdo", [victors, savedBy])
        # Tell the players to go back outside.
        self.d_setState('Reward')
        return None

    def exitReward(self):
        return None