import functools from direct.directnotify import DirectNotifyGlobal from direct.distributed.DistributedObjectAI import DistributedObjectAI from direct.fsm.FSM import FSM from toontown.estate import HouseGlobals from toontown.estate.DistributedHouseAI import DistributedHouseAI from toontown.toon import ToonDNA class LoadHouseOperation(FSM): def __init__(self, mgr, estate, index, avatar, callback): FSM.__init__(self, 'LoadHouseOperation') self.mgr = mgr self.estate = estate self.index = index self.avatar = avatar self.callback = callback self.done = False self.houseId = None self.house = None self.gender = None def start(self): # We have a few different cases here: if self.avatar is None: # Case #1: There isn't an avatar 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 reentrancy problems. taskMgr.doMethodLater(0.0, self.demand, 'makeBlankHouse-%s' % id(self), extraArgs=['MakeBlankHouse']) return style = ToonDNA.ToonDNA() style.makeFromNetString(self.avatar.get('setDNAString')[0]) self.houseId = self.avatar.get('setHouseId', [0])[0] self.gender = style.gender if self.houseId == 0: # Case #2: There is an avatar, but no setHouseId. Make a new house: self.demand('CreateHouse') else: # Case #3: Avatar with a setHouseId. Load it: self.demand('LoadHouse') def enterMakeBlankHouse(self): self.house = DistributedHouseAI(self.mgr.air) self.house.setHousePos(self.index) self.house.setColor(self.index) self.house.generateWithRequired(self.estate.zoneId) self.estate.houses[self.index] = self.house self.demand('Off') def enterCreateHouse(self): self.mgr.air.dbInterface.createObject(self.mgr.air.dbId, self.mgr.air.dclassesByName['DistributedHouseAI'], {'setName': [self.avatar['setName'][0]], 'setAvatarId': [self.avatar['avId']]}, self.__handleHouseCreated) def __handleHouseCreated(self, houseId): if self.state != 'CreateHouse': # This operation was likely aborted. return # Update the avatar's houseId: av = self.mgr.air.doId2do.get(self.avatar['avId']) if av: av.b_setHouseId(houseId) else: self.mgr.air.dbInterface.updateObject(self.mgr.air.dbId, self.avatar['avId'], self.mgr.air.dclassesByName['DistributedToonAI'], {'setHouseId': [houseId]}) self.houseId = houseId 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.index], 'setColor': [self.index], 'setName': [self.avatar['setName'][0]], 'setAvatarId': [self.avatar['avId']]}) # Wait for the house to generate: self.acceptOnce('generate-%d' % self.houseId, self.__handleHouseGenerated) def __handleHouseGenerated(self, house): # The house will need to be able to reference # the estate for setting up gardens, so: house.estate = self.estate # Initialize our interior: house.interior.gender = self.gender house.interior.start() self.house = house self.estate.houses[self.index] = self.house if config.GetBool('want-gardening', False): # Initialize our garden: self.house.createGardenManager() self.demand('Off') def exitLoadHouse(self): self.ignore('generate-%d' % self.houseId) def enterOff(self): self.done = True self.callback(self.house) class LoadEstateOperation(FSM): def __init__(self, mgr, callback): FSM.__init__(self, 'LoadEstateOperation') self.mgr = mgr self.callback = callback self.estate = None self.accId = None self.zoneId = None self.avIds = None self.avatars = None self.houseOperations = None self.petOperations = None def start(self, accId, zoneId): self.accId = accId self.zoneId = zoneId self.demand('QueryAccount') def enterQueryAccount(self): self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, self.accId, self.__handleQueryAccount) def __handleQueryAccount(self, dclass, fields): if self.state != 'QueryAccount': # This operation was likely aborted. return if dclass != self.mgr.air.dclassesByName['AccountAI']: self.mgr.notify.warning('Account %d has non-account dclass %d!' % (self.accId, dclass)) self.demand('Failure') return self.accFields = fields self.estateId = fields.get('ESTATE_ID', 0) self.demand('QueryAvatars') def enterQueryAvatars(self): self.avIds = self.accFields.get('ACCOUNT_AV_SET', [0] * 6) self.avatars = {} for index, avId in enumerate(self.avIds): if avId == 0: self.avatars[index] = None continue self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, avId, functools.partial(self.__handleQueryAvatar, index=index)) def __handleQueryAvatar(self, dclass, fields, index): if self.state != 'QueryAvatars': # This operation was likely aborted. return if dclass != self.mgr.air.dclassesByName['DistributedToonAI']: self.mgr.notify.warning( 'Account %d has avatar %d with non-Toon dclass %d!' % (self.accId, self.avIds[index], dclass)) self.demand('Failure') return fields['avId'] = self.avIds[index] self.avatars[index] = fields if len(self.avatars) == 6: self.__gotAllAvatars() def __gotAllAvatars(self): # We have all of our avatars, so now we can handle the estate. if self.estateId: # We already have an estate, so let's load that: self.demand('LoadEstate') else: # We don't yet have an estate, so let's make one: self.demand('CreateEstate') def enterCreateEstate(self): # Create a blank estate object: self.mgr.air.dbInterface.createObject(self.mgr.air.dbId, self.mgr.air.dclassesByName['DistributedEstateAI'], {}, self.__handleEstateCreated) def __handleEstateCreated(self, estateId): if self.state != 'CreateEstate': # This operation was likely aborted. return self.estateId = estateId # Store the new estate object on our account: self.mgr.air.dbInterface.updateObject(self.mgr.air.dbId, self.accId, self.mgr.air.dclassesByName['AccountAI'], {'ESTATE_ID': estateId}) self.demand('LoadEstate') def enterLoadEstate(self): # Set the estate fields: fields = {'setSlot%dToonId' % i: (avId,) for i, avId in enumerate(self.avIds)} # Activate the estate: self.mgr.air.sendActivate(self.estateId, self.mgr.air.districtId, self.zoneId, self.mgr.air.dclassesByName['DistributedEstateAI'], fields) # Wait for the estate to generate: self.acceptOnce('generate-%d' % self.estateId, self.__handleEstateGenerated) def __handleEstateGenerated(self, estate): # Get the estate: self.estate = estate # For keeping track of pets in this estate: self.estate.pets = [] # Map the owner to the estate: ownerId = self.mgr.getOwnerFromZone(self.estate.zoneId) owner = self.mgr.air.doId2do.get(ownerId) if owner: self.mgr.toon2estate[owner] = self.estate # Set the estate's ID list: self.estate.b_setIdList(self.avIds) # Load houses: self.demand('LoadHouses') def exitLoadEstate(self): self.ignore('generate-%d' % self.estateId) def enterLoadHouses(self): self.houseOperations = [] for houseIndex in xrange(6): houseOperation = LoadHouseOperation(self.mgr, self.estate, houseIndex, self.avatars[houseIndex], self.__handleHouseLoaded) self.houseOperations.append(houseOperation) houseOperation.start() def __handleHouseLoaded(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 house operation just finished! Let's see if all of them are done: if all(houseOperation.done for houseOperation in self.houseOperations): # Load our pets: self.demand('LoadPets') def enterLoadPets(self): self.petOperations = [] for houseIndex in xrange(6): av = self.avatars[houseIndex] if av and av['setPetId'][0] != 0: petOperation = LoadPetOperation(self.mgr, self.estate, av, self.__handlePetLoaded) self.petOperations.append(petOperation) petOperation.start() if not self.petOperations: taskMgr.doMethodLater(0, lambda: self.demand('Finished'), 'no-pets', extraArgs=[]) def __handlePetLoaded(self, pet): if self.state != 'LoadPets': pet.requestDelete() return # A pet operation just finished! Let's see if all of them are done: if all(petOperation.done for petOperation in self.petOperations): self.demand('Finished') def enterFinished(self): self.petOperations = [] 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 LoadPetOperation(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 self.petId = 0 def start(self): if type(self.toon) == dict: self.petId = self.toon['setPetId'][0] else: self.petId = self.toon.getPetId() if self.petId not 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.ignore('generate-%d' % self.petId) self.done = True self.callback(self.pet) class EstateManagerAI(DistributedObjectAI): notify = DirectNotifyGlobal.directNotify.newCategory('EstateManagerAI') def __init__(self, air): DistributedObjectAI.__init__(self, air) self.toon2estate = {} self.estate = {} self.estate2toons = {} self.estate2timeout = {} self.zone2toons = {} self.zone2owner = {} self.petOperations = [] def getEstateZone(self, avId, name): # Thank you name, very cool! senderId = self.air.getAvatarIdFromSender() accId = self.air.getAccountIdFromSender() senderAv = self.air.doId2do.get(senderId) if not senderAv: self.air.writeServerEvent('suspicious', senderId, 'Sent getEstateZone() but not on district!') return # If an avId has been provided, then the sender wants to visit a friend. # In this case, we do not need to load the estate, we only need to check # to see if it already exists. if avId and avId != senderId: av = self.air.doId2do.get(avId) if av and av.dclass == self.air.dclassesByName['DistributedToonAI']: estate = self.toon2estate.get(av) if estate: # Found an estate! avId = estate.owner.doId zoneId = estate.zoneId self._mapToEstate(senderAv, estate) # In case the sender is teleporting from their estate # to another estate, we want to unload their estate. self._unloadEstate(senderAv) if senderAv and senderAv.getPetId() != 0: pet = self.air.doId2do.get(senderAv.getPetId()) if pet: self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadPet, extraArgs=[estate, senderAv]) pet.requestDelete() else: self.__handleLoadPet(estate, senderAv) # Now we want to send the sender to the estate. if hasattr(senderAv, 'enterEstate'): senderAv.enterEstate(avId, zoneId) self.sendUpdateToAvatarId(senderId, 'setEstateZone', [avId, zoneId]) # We weren't able to find the given avId at an estate, that's pretty sad. self.sendUpdateToAvatarId(senderId, 'setEstateZone', [0, 0]) return # Otherwise, the sender wants to go to their own estate. estate = getattr(senderAv, 'estate', None) if estate: # The sender already has an estate loaded, so let's send them there. self._mapToEstate(senderAv, senderAv.estate) if senderAv and senderAv.getPetId() != 0: pet = self.air.doId2do.get(senderAv.getPetId()) if pet: self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadPet, extraArgs=[estate, senderAv]) pet.requestDelete() else: self.__handleLoadPet(estate, senderAv) if hasattr(senderAv, 'enterEstate'): senderAv.enterEstate(senderId, estate.zoneId) 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(senderAv, 'loadEstateOperation', 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() self.zone2owner[zoneId] = avId def estateLoaded(success): if success: senderAv.estate = senderAv.loadEstateOperation.estate senderAv.estate.owner = senderAv self._mapToEstate(senderAv, senderAv.estate) if hasattr(senderAv, 'enterEstate'): senderAv.enterEstate(senderId, zoneId) self.sendUpdateToAvatarId(senderId, 'setEstateZone', [senderId, zoneId]) else: # Estate loading failed. Sad! self.sendUpdateToAvatarId(senderId, 'setEstateZone', [0, 0]) # Might as well free up the zoneId as well. self.air.deallocateZone(zoneId) del self.zone2owner[zoneId] senderAv.loadEstateOperation = None self.acceptOnce(self.air.getAvatarExitEvent(senderAv.doId), self.__handleUnexpectedExit, extraArgs=[senderAv]) if senderAv and senderAv.getPetId() != 0: pet = self.air.doId2do.get(senderAv.getPetId()) if pet: self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadEstate, extraArgs=[senderAv, estateLoaded, accId, zoneId]) pet.requestDelete() return self.__handleLoadEstate(senderAv, estateLoaded, accId, zoneId) def __handleUnexpectedExit(self, senderAv): self._unmapFromEstate(senderAv) self._unloadEstate(senderAv) def exitEstate(self): senderId = self.air.getAvatarIdFromSender() senderAv = self.air.doId2do.get(senderId) if not senderAv: self.air.writeServerEvent('suspicious', senderId, 'Sent exitEstate() but not on district!') return self._unmapFromEstate(senderAv) self._unloadEstate(senderAv) def removeFriend(self, ownerId, avId): if not (ownerId or avId): return owner = self.air.doId2do.get(ownerId) if not owner: return friend = self.air.doId2do.get(avId) if not friend: return estate = self.estate.get(ownerId) if not estate: return if ownerId not in estate.getIdList(): return toons = self.estate2toons.get(estate, []) if owner not in toons and friend not in toons: return friendInList = False for friendPair in owner.getFriendsList(): if type(friendPair) == tuple: friendId = friendPair[0] else: friendId = friendPair if friendId == avId: friendInList = True break if not friendInList: self.sendUpdateToAvatarId(friend.doId, 'sendAvToPlayground', [friend.doId, 1]) def _unloadEstate(self, av): if getattr(av, 'estate', None): estate = av.estate if estate not in self.estate2timeout: self.estate2timeout[estate] = taskMgr.doMethodLater(HouseGlobals.BOOT_GRACE_PERIOD, self._cleanupEstate, estate.uniqueName('unload-estate'), extraArgs=[estate]) # Send warning: self._sendToonsToPlayground(av.estate, 0) if getattr(av, 'loadEstateOperation', None): self.air.deallocateZone(av.loadEstateOperation.zoneId) av.loadEstateOperation.cancel() av.loadEstateOperation = None if av and hasattr(av, 'exitEstate') and hasattr(av, 'isInEstate') and av.isInEstate(): av.exitEstate() if av and av.getPetId() != 0: self.ignore(self.air.getAvatarExitEvent(av.getPetId())) pet = self.air.doId2do.get(av.getPetId()) if pet: pet.requestDelete() self.ignore(self.air.getAvatarExitEvent(av.doId)) def _mapToEstate(self, av, estate): self._unmapFromEstate(av) self.estate[av.doId] = estate self.estate2toons.setdefault(estate, []).append(av) if av not in self.toon2estate: self.toon2estate[av] = estate self.zone2toons.setdefault(estate.zoneId, []).append(av.doId) def _unmapFromEstate(self, av): estate = self.toon2estate.get(av) if not estate: return try: del self.estate[av.doId] except KeyError: pass del self.toon2estate[av] try: self.estate2toons[estate].remove(av) except (KeyError, ValueError): pass try: self.zone2toons[estate.zoneId].remove(av.doId) except (KeyError, ValueError): pass def _cleanupEstate(self, estate): # Boot all avatars from estate: self._sendToonsToPlayground(estate, 1) # Clean up avatar <-> estate mappings: for av in self.estate2toons.get(estate, []): try: del self.estate[av.doId] del self.toon2estate[av] except KeyError: pass try: del self.estate2toons[estate] except KeyError: pass try: del self.zone2toons[estate.zoneId] 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.zone2owner[estate.zoneId] def _sendToonsToPlayground(self, estate, reason): for toon in self.estate2toons.get(estate, []): self.sendUpdateToAvatarId(toon.doId, 'sendAvToPlayground', [toon.doId, reason]) def getEstateZones(self, ownerId): toon = self.air.doId2do.get(ownerId) if not toon: return [] estate = self.toon2estate.get(toon) if not estate: return [] return [estate.zoneId] def getEstateHouseZones(self, ownerId): houseZones = [] toon = self.air.doId2do.get(ownerId) if not toon: return houseZones estate = self.toon2estate.get(toon) if not estate: return houseZones houses = estate.houses for house in houses: houseZones.append(house.interiorZone) return houseZones def getOwnerFromZone(self, zoneId): return self.zone2owner.get(zoneId, 0) def __handleLoadPet(self, estate, av): petOperation = LoadPetOperation(self, estate, av, self.__handlePetLoaded) self.petOperations.append(petOperation) petOperation.start() def __handlePetLoaded(self, _): # A pet operation just finished! Let's see if all of them are done: if all(petOperation.done for petOperation in self.petOperations): self.petOperations = [] def __handleLoadEstate(self, av, callback, accId, zoneId): self._unmapFromEstate(av) av.loadEstateOperation = LoadEstateOperation(self, callback) av.loadEstateOperation.start(accId, zoneId)