533 lines
18 KiB
Python
533 lines
18 KiB
Python
from direct.directnotify import DirectNotifyGlobal
|
|
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
|
from direct.distributed.ClockDelta import *
|
|
from direct.fsm import FSM
|
|
from direct.task import Timer
|
|
from toontown.battle import BattleBase
|
|
from toontown.building.ElevatorConstants import *
|
|
from toontown.toonbase.ToontownGlobals import *
|
|
from toontown.toonbase.ToontownBattleGlobals import *
|
|
import DistCogdoMazeGameAI, CogdoMazeGameGlobals, DistributedCogdoElevatorIntAI
|
|
import DistCogdoFlyingGameAI, DistributedCogdoBarrelAI
|
|
from DistributedCogdoBattleBldgAI import DistributedCogdoBattleBldgAI
|
|
from SuitPlannerCogdoInteriorAI import SuitPlannerCogdoInteriorAI
|
|
from toontown.cogdominium import CogdoBarrelRoomConsts
|
|
|
|
from toontown.toon import NPCToons
|
|
from toontown.quest import Quests
|
|
import random, math
|
|
|
|
NUM_FLOORS_DICT = {
|
|
's': 1,
|
|
'l': 2,
|
|
'm':1,
|
|
'c': 1
|
|
}
|
|
|
|
BATTLE_INTRO_DURATION = 10
|
|
BARREL_INTRO_DURATION = 12
|
|
BARREL_ROOM_DURATION = 30
|
|
BARREL_ROOM_REWARD_DURATION = 7
|
|
|
|
class DistributedCogdoInteriorAI(DistributedObjectAI, FSM.FSM):
|
|
notify = DirectNotifyGlobal.directNotify.newCategory("DistributedCogdoInteriorAI")
|
|
|
|
def __init__(self, air, exterior):
|
|
DistributedObjectAI.__init__(self, air)
|
|
FSM.FSM.__init__(self, 'CogdoInteriorAIFSM')
|
|
self.toons = filter(None, exterior.elevator.seats[:])
|
|
self.responses = {}
|
|
self.bldgDoId = exterior.doId
|
|
self.numFloors = NUM_FLOORS_DICT[exterior.track]
|
|
self.sosNPC = self.__generateSOS(exterior.difficulty)
|
|
self.shopOwnerNpcId = 0
|
|
self.extZoneId, self.zoneId = exterior.getExteriorAndInteriorZoneId()
|
|
npcIdList = NPCToons.zone2NpcDict.get(self.zoneId, [])
|
|
|
|
if len(npcIdList) == 0:
|
|
self.notify.info('No NPC in taken cogdo at %s' % self.zoneId)
|
|
else:
|
|
if len(npcIdList) > 1:
|
|
self.notify.warning('Multiple NPCs in taken cogdo at %s' % self.zoneId)
|
|
|
|
self.shopOwnerNpcId = npcIdList[0]
|
|
|
|
self.gameDone = 0
|
|
self.bossBattleDone = 0
|
|
self.curFloor = 0
|
|
self.topFloor = 2
|
|
self.timer = Timer.Timer()
|
|
self.exterior = exterior
|
|
self.planner = self.exterior.planner
|
|
self.savedByMap = { }
|
|
self.battle = None
|
|
self.FOType = exterior.track
|
|
self.gameFloor = 1
|
|
self.battleFloor = 2
|
|
self.barrelFloor = -1
|
|
|
|
if self.FOType == 'l':
|
|
self.battleFloor = 3
|
|
self.barrelFloor = 2
|
|
self.topFloor += 1
|
|
|
|
self.toonSkillPtsGained = { }
|
|
self.toonExp = { }
|
|
self.toonOrigQuests = { }
|
|
self.toonItems = { }
|
|
self.toonOrigMerits = { }
|
|
self.toonMerits = { }
|
|
self.toonParts = { }
|
|
self.helpfulToons = []
|
|
self.barrels = []
|
|
self.suits = []
|
|
self.activeSuits = []
|
|
self.reserveSuits = []
|
|
self.joinedReserves = []
|
|
self.suitsKilled = []
|
|
self.suitsKilledPerFloor = []
|
|
self.ignoreResponses = 0
|
|
self.ignoreElevatorDone = 0
|
|
self.ignoreReserveJoinDone = 0
|
|
|
|
def __generateSOS(self, difficulty):
|
|
g = lambda: random.choice(NPCToons.FOnpcFriends.keys())
|
|
v = g()
|
|
|
|
getStars = lambda x: NPCToons.getNPCTrackLevelHpRarity(x)[-1]
|
|
|
|
maxStars = min(2, int(math.ceil(difficulty / 5.)))
|
|
minStars = max(0, maxStars - 1)
|
|
|
|
while not (minStars <= getStars(v) <= maxStars):
|
|
v = g()
|
|
|
|
self.notify.info('selected SOS %s (stars = %s)' % (v, getStars(v)))
|
|
return v
|
|
|
|
def setZoneId(self, zoneId):
|
|
self.zoneId = zoneId
|
|
|
|
def getZoneId(self):
|
|
return self.zoneId
|
|
|
|
def setExtZoneId(self, extZoneId):
|
|
self.extZoneId = extZoneId
|
|
|
|
def getExtZoneId(self):
|
|
return self.extZoneId
|
|
|
|
def setDistBldgDoId(self, bldgDoId):
|
|
self.bldgDoId = bldgDoId
|
|
|
|
def getDistBldgDoId(self):
|
|
return self.bldgDoId
|
|
|
|
def setNumFloors(self, numFloors):
|
|
self.numFloors = numFloors
|
|
|
|
def getNumFloors(self):
|
|
return self.numFloors
|
|
|
|
def setShopOwnerNpcId(self, id):
|
|
self.shopOwnerNpcId = id
|
|
|
|
def getShopOwnerNpcId(self):
|
|
return self.shopOwnerNpcId
|
|
|
|
def setState(self, state, timestamp):
|
|
self.request(state)
|
|
|
|
def getState(self):
|
|
timestamp = globalClockDelta.getRealNetworkTime()
|
|
return [self.state, timestamp]
|
|
|
|
def b_setState(self, state):
|
|
self.setState(state, 0)
|
|
self.d_setState(state)
|
|
|
|
def d_setState(self, state):
|
|
timestamp = globalClockDelta.getRealNetworkTime()
|
|
self.sendUpdate('setState', [state, timestamp])
|
|
|
|
def reserveJoinDone(self):
|
|
toonId = self.air.getAvatarIdFromSender()
|
|
if self.ignoreResponses == 1:
|
|
return None
|
|
elif self.toons.count(toonId) == 0:
|
|
self.notify.warning('reserveJoinDone() - toon not in list: %d' % toonId)
|
|
return None
|
|
self.b_setState('Battle')
|
|
|
|
def elevatorDone(self):
|
|
toonId = self.air.getAvatarIdFromSender()
|
|
if self.ignoreResponses == 1:
|
|
return None
|
|
elif self.toons.count(toonId) == 0:
|
|
self.notify.warning('elevatorDone() - toon not in toon list: %d' % toonId)
|
|
|
|
def enterWaitForAllToonsInside(self):
|
|
self.resetResponses()
|
|
|
|
if self.FOType == "s":
|
|
self.game = DistCogdoMazeGameAI.DistCogdoMazeGameAI(self.air)
|
|
self.game.setNumSuits(CogdoMazeGameGlobals.NumSuits)
|
|
elif self.FOType == "l":
|
|
self.game = DistCogdoFlyingGameAI.DistCogdoFlyingGameAI(self.air)
|
|
elif self.FOType == "m":
|
|
self.game = DistCogdoCraneGameAI.DistCogdoCraneGameAI(self.air)
|
|
|
|
self.sendUpdate("setSOSNpcId", [self.sosNPC])
|
|
self.sendUpdate("setFOType", [ord(self.FOType)])
|
|
|
|
def resetResponses(self):
|
|
for toon in self.toons:
|
|
self.responses[toon] = 0
|
|
|
|
def setAvatarJoined(self):
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.responses[avId] = 1
|
|
avatar = self.air.doId2do.get(avId)
|
|
if avatar != None:
|
|
self.savedByMap[avId] = (avatar.getName(), avatar.dna.asTuple())
|
|
self.addToon(avId)
|
|
if self.allToonsJoined():
|
|
self.request('Elevator')
|
|
|
|
def addToon(self, avId):
|
|
if not avId in self.toons:
|
|
self.toons.append(avId)
|
|
|
|
if avId in self.air.doId2do:
|
|
event = self.air.getAvatarExitEvent(avId)
|
|
self.accept(event, self.__handleUnexpectedExit, [avId])
|
|
|
|
def __handleUnexpectedExit(self, avId):
|
|
self.removeToon(avId)
|
|
if len(self.toons) == 0:
|
|
self.exterior.deleteSuitInterior()
|
|
if self.battle:
|
|
self.battle.requestDelete()
|
|
self.battle = None
|
|
|
|
def removeToon(self, avId):
|
|
if avId in self.toons: self.toons.pop(avId)
|
|
|
|
def enterElevator(self):
|
|
self.curFloor += 1
|
|
self.d_setToons()
|
|
self.resetResponses()
|
|
|
|
if self.curFloor == self.gameFloor:
|
|
self.enterGame()
|
|
|
|
self.d_setState('Elevator')
|
|
self.timer.stop()
|
|
self.timer.startCallback(BattleBase.ELEVATOR_T + ElevatorData[ELEVATOR_FIELD]['openTime'], self.serverElevatorDone)
|
|
|
|
if self.curFloor == self.battleFloor:
|
|
self.planner.myPrint()
|
|
suitHandles = self.planner.genFloorSuits(0)
|
|
self.suits = suitHandles['activeSuits']
|
|
self.activeSuits = self.suits[:]
|
|
self.reserveSuits = suitHandles['reserveSuits']
|
|
self.d_setSuits()
|
|
|
|
def exitElevator(self):
|
|
self.timer.stop()
|
|
|
|
def serverElevatorDone(self):
|
|
if self.curFloor == self.gameFloor:
|
|
self.d_setState('Game')
|
|
elif self.curFloor == self.battleFloor:
|
|
self.b_setState('BattleIntro')
|
|
self.timer.startCallback(BATTLE_INTRO_DURATION, self.battleIntroDone)
|
|
else:
|
|
self.notify.warning('Unknown floor %s (track=%s)' % (self.curFloor, self.FOType))
|
|
|
|
def battleIntroDone(self):
|
|
if self.air:
|
|
self.createBattle()
|
|
self.b_setState('Battle')
|
|
|
|
def barrelIntroDone(self):
|
|
if not self.air:
|
|
return
|
|
|
|
self.b_setState('CollectBarrels')
|
|
for i in xrange(len(CogdoBarrelRoomConsts.BarrelProps)):
|
|
barrel = DistributedCogdoBarrelAI.DistributedCogdoBarrelAI(self.air, i)
|
|
barrel.generateWithRequired(self.zoneId)
|
|
self.barrels.append(barrel)
|
|
self.timer.startCallback(BARREL_ROOM_DURATION, self.barrelReward)
|
|
|
|
def barrelReward(self):
|
|
if not self.air:
|
|
return
|
|
|
|
self.b_setState('BarrelRoomReward')
|
|
for i in self.barrels:
|
|
i.requestDelete()
|
|
self.timer.startCallback(BARREL_ROOM_REWARD_DURATION, self.barrelRewardDone)
|
|
|
|
def barrelRewardDone(self):
|
|
if not self.air:
|
|
return
|
|
barrelPlanner = SuitPlannerCogdoInteriorAI(self.exterior._cogdoLayout, max(0, self.exterior.difficulty - 5),
|
|
self.FOType, self.exterior.getExteriorAndInteriorZoneId()[1])
|
|
barrelPlanner.myPrint()
|
|
suitHandles = barrelPlanner.genFloorSuits(0)
|
|
self.suits = suitHandles['activeSuits']
|
|
self.activeSuits = self.suits[:]
|
|
self.reserveSuits = suitHandles['reserveSuits']
|
|
self.d_setSuits()
|
|
self.battleIntroDone()
|
|
|
|
def handleAllAboard(self, seats):
|
|
if not hasattr(self, 'air') or not self.air:
|
|
return None
|
|
|
|
numOfEmptySeats = seats.count(None)
|
|
if numOfEmptySeats == 4:
|
|
self.exterior.deleteSuitInterior()
|
|
return
|
|
elif not 0 <= numOfEmptySeats <= 3:
|
|
self.notify.error('Bad number of empty seats: %s' % numOfEmptySeats)
|
|
|
|
for toon in self.toons:
|
|
if toon not in seats:
|
|
self.removeToon(toon)
|
|
|
|
self.toons = filter(None, seats)
|
|
self.d_setToons()
|
|
self.request('Elevator')
|
|
|
|
def enterGame(self):
|
|
self.game.setToons(self.toons)
|
|
self.game.setInteriorId(self.doId)
|
|
self.game.setExteriorZone(self.exterior.zoneId)
|
|
self.game.setDifficultyOverrides(2147483647, -1)
|
|
self.game.generateWithRequired(self.zoneId)
|
|
self.game.d_startIntro()
|
|
self.accept(self.game.finishEvent, self.__handleGameDone)
|
|
self.accept(self.game.gameOverEvent, self.__handleGameOver)
|
|
|
|
def __handleGameDone(self, toons):
|
|
self.game.requestDelete()
|
|
self.gameDone = 1
|
|
self.toons = toons
|
|
if self.curFloor == self.barrelFloor - 1:
|
|
self.curFloor += 1
|
|
self.d_setToons()
|
|
self.resetResponses()
|
|
self.b_setState('BarrelRoomIntro')
|
|
self.timer.startCallback(BARREL_INTRO_DURATION, self.barrelIntroDone)
|
|
else:
|
|
self.request('Elevator')
|
|
|
|
def __handleGameOver(self):
|
|
self.game.requestDelete()
|
|
self.exterior.deleteSuitInterior()
|
|
|
|
def createBattle(self):
|
|
isBoss = self.curFloor == self.topFloor
|
|
self.battle = DistributedCogdoBattleBldgAI(self.air, self.zoneId, self.__handleRoundDone, self.__handleBattleDone, bossBattle = isBoss)
|
|
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.curFloor)
|
|
self.battle.battleCalc.setSkillCreditMultiplier(self.battle.battleCalc.getSkillCreditMultiplier() * mult)
|
|
|
|
def enterBattleDone(self, toonIds):
|
|
toonIds = toonIds[0]
|
|
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.removeToon(toon)
|
|
|
|
self.d_setToons()
|
|
if len(self.toons) == 0:
|
|
self.exterior.deleteSuitInterior()
|
|
elif self.curFloor == self.topFloor:
|
|
self.battle.resume(self.curFloor, topFloor = 1)
|
|
else:
|
|
self.battle.resume(self.curFloor, topFloor = 0)
|
|
|
|
def __doDeleteInterior(self, task):
|
|
self.exterior.deleteSuitInterior()
|
|
return task.done
|
|
|
|
def exitBattleDone(self):
|
|
self.cleanupFloorBattle()
|
|
|
|
def cleanupFloorBattle(self):
|
|
for suit in self.suits:
|
|
if suit.isDeleted():
|
|
continue
|
|
suit.requestDelete()
|
|
|
|
self.suits = []
|
|
self.reserveSuits = []
|
|
self.activeSuits = []
|
|
if self.battle != None:
|
|
self.battle.requestDelete()
|
|
|
|
self.battle = None
|
|
|
|
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)
|
|
continue
|
|
|
|
for info in self.joinedReserves:
|
|
self.reserveSuits.remove(info)
|
|
|
|
if len(self.joinedReserves) > 0:
|
|
self.d_setSuits()
|
|
self.request('ReservesJoining')
|
|
return
|
|
|
|
if len(self.activeSuits) == 0:
|
|
self.request('BattleDone', [
|
|
toonIds])
|
|
else:
|
|
self.battle.resume()
|
|
|
|
def enterReservesJoining(self):
|
|
self.resetResponses()
|
|
self.timer.startCallback(ElevatorData[ELEVATOR_FIELD]['openTime'] + SUIT_HOLD_ELEVATOR_TIME + BattleBase.SERVER_BUFFER_TIME, self.serverReserveJoinDone)
|
|
|
|
def exitReservesJoining(self):
|
|
self.timer.stop()
|
|
self.resetResponses()
|
|
for info in self.joinedReserves:
|
|
self.battle.suitRequestJoin(info[0])
|
|
|
|
self.battle.resume()
|
|
self.joinedReserves = []
|
|
|
|
def serverReserveJoinDone(self):
|
|
self.ignoreReserveJoinDone = 1
|
|
self.b_setState('Battle')
|
|
|
|
def __handleBattleDone(self, zoneId, toonIds):
|
|
if len(toonIds) == 0:
|
|
taskMgr.doMethodLater(10, self.__doDeleteInterior, self.taskName('deleteInterior'))
|
|
elif self.curFloor == self.topFloor:
|
|
self.request('Reward')
|
|
else:
|
|
self.b_setState('Resting')
|
|
|
|
def enterResting(self):
|
|
self.intElevator = DistributedCogdoElevatorIntAI.DistributedCogdoElevatorIntAI(self.air, self, self.toons)
|
|
self.intElevator.generateWithRequired(self.zoneId)
|
|
|
|
def exitResting(self):
|
|
self.intElevator.requestDelete()
|
|
|
|
def enterReward(self):
|
|
victors = self.toons[:]
|
|
savedBy = []
|
|
for v in victors:
|
|
tuple = self.savedByMap.get(v)
|
|
if tuple:
|
|
savedBy.append([
|
|
v,
|
|
tuple[0],
|
|
tuple[1]])
|
|
|
|
toon = self.air.doId2do.get(v)
|
|
if toon:
|
|
if self.FOType == 's':
|
|
if not toon.attemptAddNPCFriend(self.sosNPC, Quests.InFO):
|
|
self.notify.info('%s unable to add NPCFriend %s to %s.' % (self.doId, self.sosNPC, v))
|
|
elif self.FOType == 'l':
|
|
reward = self.getEmblemsReward()
|
|
toon.addEmblems(reward)
|
|
else:
|
|
self.notify.warning('%s unable to reward %s: unknown reward for track %s' % (self.doId, v, self.FOType))
|
|
|
|
self.exterior.fsm.request('waitForVictorsFromCogdo', [
|
|
victors,
|
|
savedBy])
|
|
self.d_setState('Reward')
|
|
|
|
def removeToon(self, toonId):
|
|
if self.toons.count(toonId):
|
|
self.toons.remove(toonId)
|
|
|
|
def d_setToons(self):
|
|
self.sendUpdate('setToons', self.getToons())
|
|
|
|
def getToons(self):
|
|
return [self.toons, 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 allToonsJoined(self):
|
|
for toon in self.toons:
|
|
if self.responses[toon] == 0:
|
|
return 0
|
|
return 1
|
|
|
|
def delete(self):
|
|
DistributedObjectAI.delete(self)
|
|
self.timer.stop()
|
|
|
|
def getEmblemsReward(self):
|
|
hoodIdMap = {2: .5, # Toontown Central
|
|
1: 1., # Donald's Dock
|
|
5: 1.5, # Daisy Gardens
|
|
4: 2., # Minnie's Melodyland
|
|
3: 2.7, # The Brrrgh
|
|
9: 3.5 # Donald's Dreamland
|
|
}
|
|
|
|
hoodValue = hoodIdMap[int(self.exterior.zoneId // 1000)]
|
|
diff = max(self.exterior.difficulty, 1)
|
|
memos = self.game.getTotalMemos()
|
|
E = (hoodValue * max(memos, 1) * diff) / 2.5
|
|
return divmod(E, 100)[::-1]
|