mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-23 11:42:39 -06:00
499 lines
17 KiB
Python
499 lines
17 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
|
||
|
from toontown.toon import ToonDNA
|
||
|
import HouseGlobals
|
||
|
import functools
|
||
|
|
||
|
class LoadHouseFSM(FSM):
|
||
|
notify = DirectNotifyGlobal.directNotify.newCategory('LoadHouseFSM')
|
||
|
|
||
|
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):
|
||
|
style = ToonDNA.ToonDNA()
|
||
|
style.makeFromNetString(self.toon['setDNAString'][0])
|
||
|
|
||
|
self.mgr.air.dbInterface.createObject(
|
||
|
self.mgr.air.dbId,
|
||
|
self.mgr.air.dclassesByName['DistributedHouseAI'],
|
||
|
{
|
||
|
'setName' : [self.toon['setName'][0]],
|
||
|
'setAvatarId' : [self.toon['ID']],
|
||
|
'setGender': [0 if style.getGender() == 'm' else 1]
|
||
|
},
|
||
|
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
|
||
|
house.initializeInterior()
|
||
|
|
||
|
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 LoadPetFSM(FSM):
|
||
|
def __init__(self, mgr, estate, toon, callback):
|
||
|
FSM.__init__(self, 'LoadPetFSM')
|
||
|
self.mgr = mgr
|
||
|
self.estate = estate
|
||
|
self.toon = toon
|
||
|
self.callback = callback
|
||
|
|
||
|
self.done = False
|
||
|
|
||
|
def start(self):
|
||
|
self.petId = self.toon['setPetId'][0]
|
||
|
if not self.petId in self.mgr.air.doId2do:
|
||
|
self.mgr.air.sendActivate(self.petId, self.mgr.air.districtId, self.estate.zoneId)
|
||
|
self.acceptOnce('generate-%d' % self.petId, self.__generated)
|
||
|
else:
|
||
|
self.__generated(self.mgr.air.doId2do[self.petId])
|
||
|
|
||
|
def __generated(self, pet):
|
||
|
self.pet = pet
|
||
|
self.estate.pets.append(pet)
|
||
|
self.demand('Off')
|
||
|
|
||
|
def enterOff(self):
|
||
|
self.done = True
|
||
|
self.callback(self.pet)
|
||
|
|
||
|
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
|
||
|
self.demand('StoreEstate')
|
||
|
|
||
|
def enterStoreEstate(self):
|
||
|
# store the estate in account
|
||
|
# congrats however wrote this for forgetting it!
|
||
|
|
||
|
self.mgr.air.dbInterface.updateObject(
|
||
|
self.mgr.air.dbId,
|
||
|
self.accountId,
|
||
|
self.mgr.air.dclassesByName['AccountAI'],
|
||
|
{'ESTATE_ID': self.estateId},
|
||
|
{'ESTATE_ID': 0},
|
||
|
self.__handleStoreEstate)
|
||
|
|
||
|
def __handleStoreEstate(self, fields):
|
||
|
if fields:
|
||
|
self.notify.warning("Failed to associate Estate %d with account %d, loading anyway." % (self.estateId, self.accountId))
|
||
|
|
||
|
self.demand('LoadEstate')
|
||
|
|
||
|
def enterLoadEstate(self):
|
||
|
# Activate the estate:
|
||
|
fields = {}
|
||
|
for i, toon in enumerate(self.toonIds):
|
||
|
fields['setSlot%dToonId' % i] = (toon,)
|
||
|
|
||
|
self.mgr.air.sendActivate(self.estateId, self.mgr.air.districtId, self.zoneId,
|
||
|
self.mgr.air.dclassesByName['DistributedEstateAI'], fields)
|
||
|
|
||
|
# 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
|
||
|
estate.pets = []
|
||
|
|
||
|
# 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('LoadPets')
|
||
|
|
||
|
def enterLoadPets(self):
|
||
|
self.petFSMs = []
|
||
|
for houseIndex in xrange(6):
|
||
|
toon = self.toons[houseIndex]
|
||
|
if toon and toon['setPetId'][0] != 0:
|
||
|
fsm = LoadPetFSM(self.mgr, self.estate, toon, self.__petDone)
|
||
|
self.petFSMs.append(fsm)
|
||
|
fsm.start()
|
||
|
|
||
|
if not self.petFSMs:
|
||
|
taskMgr.doMethodLater(0, lambda: self.demand('Finished'), 'nopets', extraArgs=[])
|
||
|
|
||
|
def __petDone(self, pet):
|
||
|
if self.state != 'LoadPets':
|
||
|
pet.requestDelete()
|
||
|
return
|
||
|
|
||
|
# A petFSM just finished! Let's see if all of them are done:
|
||
|
if all(petFSM.done for petFSM in self.petFSMs):
|
||
|
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 = {}
|
||
|
self.zoneId2owner = {}
|
||
|
|
||
|
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)
|
||
|
del self.zoneId2owner[zoneId]
|
||
|
|
||
|
toon.loadEstateFSM = None
|
||
|
|
||
|
self.acceptOnce(self.air.getAvatarExitEvent(toon.doId), self._unloadEstate, extraArgs=[toon])
|
||
|
self.zoneId2owner[zoneId] = avId
|
||
|
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
|
||
|
|
||
|
# Destroy pets:
|
||
|
for pet in estate.pets:
|
||
|
pet.requestDelete()
|
||
|
|
||
|
estate.pets = []
|
||
|
|
||
|
# Free estate's zone:
|
||
|
self.air.deallocateZone(estate.zoneId)
|
||
|
del self.zoneId2owner[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
|
||
|
|
||
|
if hasattr(toon, 'enterEstate'):
|
||
|
toon.enterEstate(estate.owner.doId, estate.zoneId)
|
||
|
|
||
|
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
|
||
|
|
||
|
if hasattr(toon, 'exitEstate'):
|
||
|
toon.exitEstate()
|
||
|
|
||
|
def _lookupEstate(self, toon):
|
||
|
return self.toon2estate.get(toon)
|
||
|
|
||
|
def getOwnerFromZone(self, zoneId):
|
||
|
return self.zoneId2owner.get(zoneId, 0)
|
||
|
|
||
|
def getEstateZones(self, ownerId):
|
||
|
estate = self._lookupEstate(self.air.doId2do.get(ownerId))
|
||
|
if estate:
|
||
|
return [estate.zoneId]
|
||
|
|
||
|
return []
|