Poodletooth-iLand/toontown/estate/EstateManagerAI.py

407 lines
14 KiB
Python

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):
print(self.toon2estate)
return self.toon2estate.get(toon)