Poodletooth-iLand/toontown/estate/EstateManagerAI.py

407 lines
14 KiB
Python
Raw Normal View History

2015-03-03 16:10:12 -06:00
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI
from direct.fsm.FSM import FSM
from toontown.estate.DistributedEstateAI import DistributedEstateAI
from toontown.estate.DistributedHouseAI import DistributedHouseAI
import HouseGlobals
import functools
class LoadHouseFSM(FSM):
def __init__(self, mgr, estate, houseIndex, toon, callback):
FSM.__init__(self, 'LoadHouseFSM')
self.mgr = mgr
self.estate = estate
self.houseIndex = houseIndex
self.toon = toon
self.callback = callback
self.done = False
def start(self):
# We have a few different cases here:
if self.toon is None:
# Case #1: There isn't a Toon in that estate slot. Make a blank house.
# Because this state completes so fast, we'll use taskMgr to delay
# it until the next iteration. This solves re-entrancy problems.
taskMgr.doMethodLater(0.0, self.demand,
'makeBlankHouse-%s' % id(self),
extraArgs=['MakeBlankHouse'])
return
self.houseId = self.toon.get('setHouseId', [0])[0]
if self.houseId == 0:
# Case #2: There is a Toon, but no setHouseId. Gotta make one.
self.demand('CreateHouse')
else:
# Case #3: Toon with a setHouseId. Load it.
self.demand('LoadHouse')
def enterMakeBlankHouse(self):
self.house = DistributedHouseAI(self.mgr.air)
self.house.setHousePos(self.houseIndex)
self.house.setColor(self.houseIndex)
self.house.generateWithRequired(self.estate.zoneId)
self.estate.houses[self.houseIndex] = self.house
self.demand('Off')
def enterCreateHouse(self):
self.mgr.air.dbInterface.createObject(
self.mgr.air.dbId,
self.mgr.air.dclassesByName['DistributedHouseAI'],
{
'setName' : [self.toon['setName'][0]],
'setAvatarId' : [self.toon['ID']],
},
self.__handleCreate)
def __handleCreate(self, doId):
if self.state != 'CreateHouse':
return
# Update the avatar's houseId:
av = self.mgr.air.doId2do.get(self.toon['ID'])
if av:
av.b_setHouseId(doId)
else:
self.mgr.air.dbInterface.updateObject(
self.mgr.air.dbId,
self.toon['ID'],
self.mgr.air.dclassesByName['DistributedToonAI'],
{'setHouseId': [doId]})
self.houseId = doId
self.demand('LoadHouse')
def enterLoadHouse(self):
# Activate the house:
self.mgr.air.sendActivate(self.houseId, self.mgr.air.districtId, self.estate.zoneId,
self.mgr.air.dclassesByName['DistributedHouseAI'],
{'setHousePos': [self.houseIndex],
'setColor': [self.houseIndex],
'setName': [self.toon['setName'][0]],
'setAvatarId': [self.toon['ID']]})
# Now we wait for the house to show up... We do this by hanging a messenger
# hook which the DistributedHouseAI throws once it spawns.
self.acceptOnce('generate-%d' % self.houseId, self.__gotHouse)
def __gotHouse(self, house):
self.house = house
self.estate.houses[self.houseIndex] = self.house
self.demand('Off')
def exitLoadHouse(self):
self.ignore('generate-%d' % self.houseId)
def enterOff(self):
self.done = True
self.callback(self.house)
class LoadEstateFSM(FSM):
def __init__(self, mgr, callback):
FSM.__init__(self, 'LoadEstateFSM')
self.mgr = mgr
self.callback = callback
self.estate = None
def start(self, accountId, zoneId):
self.accountId = accountId
self.zoneId = zoneId
self.demand('QueryAccount')
def enterQueryAccount(self):
self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, self.accountId,
self.__gotAccount)
def __gotAccount(self, dclass, fields):
if self.state != 'QueryAccount':
return # We must have aborted or something...
if dclass != self.mgr.air.dclassesByName['AccountAI']:
self.mgr.notify.warning('Account %d has non-account dclass %d!' %
(self.accountId, dclass))
self.demand('Failure')
return
self.accountFields = fields
self.estateId = fields.get('ESTATE_ID', 0)
self.demand('QueryToons')
def enterQueryToons(self):
self.toonIds = self.accountFields.get('ACCOUNT_AV_SET', [0]*6)
self.toons = {}
for index, toonId in enumerate(self.toonIds):
if toonId == 0:
self.toons[index] = None
continue
self.mgr.air.dbInterface.queryObject(
self.mgr.air.dbId, toonId,
functools.partial(self.__gotToon, index=index))
def __gotToon(self, dclass, fields, index):
if self.state != 'QueryToons':
return # We must have aborted or something...
if dclass != self.mgr.air.dclassesByName['DistributedToonAI']:
self.mgr.notify.warning('Account %d has avatar %d with non-Toon dclass %d!' %
(self.accountId, self.toonIds[index], dclass))
self.demand('Failure')
return
fields['ID'] = self.toonIds[index]
self.toons[index] = fields
if len(self.toons) == 6:
self.__gotAllToons()
def __gotAllToons(self):
# Okay, we have all of our Toons, now we can proceed with estate!
if self.estateId:
# We already have an estate, load it!
self.demand('LoadEstate')
else:
# We don't have one yet, make one!
self.demand('CreateEstate')
def enterCreateEstate(self):
# We have to ask the DB server to construct a blank estate object...
self.mgr.air.dbInterface.createObject(
self.mgr.air.dbId,
self.mgr.air.dclassesByName['DistributedEstateAI'],
{},
self.__handleEstateCreate)
def __handleEstateCreate(self, estateId):
if self.state != 'CreateEstate':
return # We must have aborted or something...
self.estateId = estateId
# Update our account so we can store this new estate object.
self.mgr.air.dbInterface.updateObject(
self.mgr.air.dbId,
self.accountId,
self.mgr.air.dclassesByName['AccountAI'],
{ 'ESTATE_ID': estateId }
)
self.demand('LoadEstate')
def enterLoadEstate(self):
# Activate the estate:
self.mgr.air.sendActivate(self.estateId, self.mgr.air.districtId, self.zoneId)
# Now we wait for the estate to show up... We do this by hanging a messenger
# hook which the DistributedEstateAI throws once it spawns.
self.acceptOnce('generate-%d' % self.estateId, self.__gotEstate)
def __gotEstate(self, estate):
self.estate = estate
self.estate.toons = self.toonIds
self.estate.updateToons()
# Gotcha! Now we need to load houses:
self.demand('LoadHouses')
def exitLoadEstate(self):
self.ignore('generate-%d' % self.estateId)
def enterLoadHouses(self):
self.houseFSMs = []
for houseIndex in xrange(6):
fsm = LoadHouseFSM(self.mgr, self.estate, houseIndex,
self.toons[houseIndex], self.__houseDone)
self.houseFSMs.append(fsm)
fsm.start()
def __houseDone(self, house):
if self.state != 'LoadHouses':
# We aren't loading houses, so we probably got cancelled. Therefore,
# the only sensible thing to do is simply destroy the house.
house.requestDelete()
return
# A houseFSM just finished! Let's see if all of them are done:
if all(houseFSM.done for houseFSM in self.houseFSMs):
self.demand('Finished')
def enterFinished(self):
self.callback(True)
def enterFailure(self):
self.cancel()
self.callback(False)
def cancel(self):
if self.estate:
self.estate.destroy()
self.estate = None
self.demand('Off')
class EstateManagerAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory("EstateManagerAI")
def __init__(self, air):
DistributedObjectAI.__init__(self, air)
self.estate2toons = {}
self.toon2estate = {}
self.estate2timeout = {}
def getEstateZone(self, avId):
senderId = self.air.getAvatarIdFromSender()
accId = self.air.getAccountIdFromSender()
toon = self.air.doId2do.get(senderId)
if not toon:
self.air.writeServerEvent('suspicious', senderId, 'Sent getEstateZone() but not on district!')
return
# If there's an avId included, then the Toon is interested in visiting a
# friend. We do NOT load the estate, we simply see if it's already up...
if avId and avId != senderId:
av = self.air.doId2do.get(avId)
if av and av.dclass == self.air.dclassesByName['DistributedToonAI']:
estate = self._lookupEstate(av)
if estate:
# Yep, there it is!
avId = estate.owner.doId
zoneId = estate.zoneId
self._mapToEstate(toon, estate)
self._unloadEstate(toon) # In case they're doing estate->estate TP.
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [avId, zoneId])
# Bummer, couldn't find avId at an estate...
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [0, 0])
return
# The Toon definitely wants to go to his own estate...
estate = getattr(toon, 'estate', None)
if estate:
# They already have an estate loaded, so let's just return it:
self._mapToEstate(toon, toon.estate)
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [senderId, estate.zoneId])
# If a timeout is active, cancel it:
if estate in self.estate2timeout:
self.estate2timeout[estate].remove()
del self.estate2timeout[estate]
return
if getattr(toon, 'loadEstateFSM', None):
# We already have a loading operation underway; ignore this second
# request since the first operation will setEstateZone() when it
# finishes anyway.
return
zoneId = self.air.allocateZone()
def estateLoaded(success):
if success:
toon.estate = toon.loadEstateFSM.estate
toon.estate.owner = toon
self._mapToEstate(toon, toon.estate)
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [senderId, zoneId])
else:
# Estate loading failed??!
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [0, 0])
# And I guess we won't need our zoneId anymore...
self.air.deallocateZone(zoneId)
toon.loadEstateFSM = None
self.acceptOnce(self.air.getAvatarExitEvent(toon.doId), self._unloadEstate, extraArgs=[toon])
toon.loadEstateFSM = LoadEstateFSM(self, estateLoaded)
toon.loadEstateFSM.start(accId, zoneId)
def exitEstate(self):
senderId = self.air.getAvatarIdFromSender()
toon = self.air.doId2do.get(senderId)
if not toon:
self.air.writeServerEvent('suspicious', senderId, 'Sent exitEstate() but not on district!')
return
self._unmapFromEstate(toon)
self._unloadEstate(toon)
def _unloadEstate(self, toon):
if getattr(toon, 'estate', None):
estate = toon.estate
if estate not in self.estate2timeout:
self.estate2timeout[estate] = \
taskMgr.doMethodLater(HouseGlobals.BOOT_GRACE_PERIOD,
self._cleanupEstate,
estate.uniqueName('emai-cleanup-task'),
extraArgs=[estate])
self._sendToonsToPlayground(toon.estate, 0) # This is a warning only...
if getattr(toon, 'loadEstateFSM', None):
self.air.deallocateZone(toon.loadEstateFSM.zoneId)
toon.loadEstateFSM.cancel()
toon.loadEstateFSM = None
self.ignore(self.air.getAvatarExitEvent(toon.doId))
def _cleanupEstate(self, estate):
# Boot all Toons from estate:
self._sendToonsToPlayground(estate, 1)
# Clean up toon<->estate mappings...
for toon in self.estate2toons.get(estate, []):
try:
del self.toon2estate[toon]
except KeyError:
pass
try:
del self.estate2toons[estate]
except KeyError:
pass
# Clean up timeout, if it exists:
if estate in self.estate2timeout:
del self.estate2timeout[estate]
# Destroy estate and unmap from owner:
estate.destroy()
estate.owner.estate = None
# Free estate's zone:
self.air.deallocateZone(estate.zoneId)
def _sendToonsToPlayground(self, estate, reason):
for toon in self.estate2toons.get(estate, []):
self.sendUpdateToAvatarId(toon.doId, 'sendAvToPlayground',
[toon.doId, reason])
def _mapToEstate(self, toon, estate):
self._unmapFromEstate(toon)
self.estate2toons.setdefault(estate, []).append(toon)
self.toon2estate[toon] = estate
def _unmapFromEstate(self, toon):
estate = self.toon2estate.get(toon)
if not estate: return
del self.toon2estate[toon]
try:
self.estate2toons[estate].remove(toon)
except (KeyError, ValueError):
pass
def _lookupEstate(self, toon):
return self.toon2estate.get(toon)