""" DistributedBuildingMgrAI module: contains the DistributedBuildingMgrAI class, the server side handler of all buildings in a neighborhood.""" # AI code should not import ShowBaseGlobal because it creates a graphics window # Use AIBaseGlobal instead # from ShowBaseGlobal import * import os from direct.task.Task import Task import pickle from otp.ai.AIBaseGlobal import * from . import DistributedBuildingAI from . import HQBuildingAI from . import GagshopBuildingAI from . import PetshopBuildingAI from toontown.building.KartShopBuildingAI import KartShopBuildingAI from toontown.building import DistributedAnimBuildingAI #import DistributedDoorAI from direct.directnotify import DirectNotifyGlobal from toontown.hood import ZoneUtil import time import random class DistributedBuildingMgrAI: """ DistributedBuildingMgrAI class: a server side object, keeps track of all buildings within a single neighborhood (street), handles converting them from good to bad, and hands out information about buildings to whoever asks. Landmark data will be saved to an AI Server local file. *How landmark building info gets loaded: load list from dna; look for backup .buildings file; if present: load from backup buildings file; #if buildings file is present: # remove buildings file; else: load .buildings file; compare dna list with saved list; if they are different: make reasonable matches for suit blocks; create the building AI dictionary *Saving building data: check for backup buildings file; if present: remove buildings file; else: move buildings file to backup file; write new buildings file; remove backup buildings file; """ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBuildingMgrAI') serverDatafolder = simbase.config.GetString('server-data-folder', "") def __init__(self, air, branchID, dnaStore, trophyMgr): """ branchID: The street number. Such as 2200. """ self.branchID = branchID self.canonicalBranchID = ZoneUtil.getCanonicalZoneId(branchID) assert(self.debugPrint("__init__(air, branchID, dnaStore, trophyMgr)")) self.air = air self.__buildings = {} self.dnaStore = dnaStore self.trophyMgr = trophyMgr self.shard = str(air.districtId) self.backupExtension = '.bu' self.findAllLandmarkBuildings() self.doLaterTask = None def cleanup(self): taskMgr.remove(str(self.branchID)+'_delayed_save-timer') for building in self.__buildings.values(): building.cleanup() self.__buildings = {} def isValidBlockNumber(self, blockNumber): """return true if that block refers to a real block""" assert(self.debugPrint("isValidBlockNumber(blockNumber="+str(blockNumber)+")")) return blockNumber in self.__buildings def delayedSaveTask(self, task): assert(self.debugPrint("delayedSaveTask()")) self.save() self.doLaterTask=None return Task.done def isSuitBlock(self, blockNumber): """return true if that block is a suit block/building""" assert(self.debugPrint("isSuitBlock(blockNumber="+str(blockNumber)+")")) assert(blockNumber in self.__buildings) return self.__buildings[blockNumber].isSuitBlock() def getSuitBlocks(self): assert(self.debugPrint("getSuitBlocks()")) blocks=[] for i in self.__buildings.values(): if i.isSuitBlock(): blocks.append(i.getBlock()[0]) return blocks def isCogdoBlock(self, blockNumber): return self.__buildings[blockNumber].isCogdo() def getCogdoBlocks(self): blocks = [] for i in list(self.__buildings.values()): if i.isCogdo(): blocks.append(i.getBlock()[0]) return blocks def getEstablishedSuitBlocks(self): assert(self.debugPrint("getEstablishedSuitBlocks()")) blocks=[] for i in self.__buildings.values(): if i.isEstablishedSuitBlock(): blocks.append(i.getBlock()[0]) return blocks def getToonBlocks(self): assert(self.debugPrint("getToonBlocks()")) blocks=[] for i in self.__buildings.values(): if isinstance(i, HQBuildingAI.HQBuildingAI): continue if not i.isSuitBlock(): blocks.append(i.getBlock()[0]) return blocks def getBuildings(self): return self.__buildings.values() def getFrontDoorPoint(self, blockNumber): """get any associated path point for the specified building, useful for suits to know where to go when exiting from a building""" assert(self.debugPrint("getFrontDoorPoint(blockNumber="+str(blockNumber)+")")) assert(blockNumber in self.__buildings) return self.__buildings[blockNumber].getFrontDoorPoint() def getBuildingTrack(self, blockNumber): """get any associated path point for the specified building, useful for suits to know where to go when exiting from a building""" assert(self.debugPrint("getBuildingTrack(blockNumber="+str(blockNumber)+")")) assert(blockNumber in self.__buildings) return self.__buildings[blockNumber].track def getBuilding( self, blockNumber ): assert(self.debugPrint("getBuilding(%s)" %(str(blockNumber),))) assert(blockNumber in self.__buildings) return self.__buildings[blockNumber] def setFrontDoorPoint(self, blockNumber, point): """get any associated path point for the specified building, useful for suits to know where to go when exiting from a building""" assert(self.debugPrint("setFrontDoorPoint(blockNumber="+str(blockNumber) +", point="+str(point)+")")) assert(blockNumber in self.__buildings) return self.__buildings[blockNumber].setFrontDoorPoint(point) def getDNABlockLists(self): blocks=[] hqBlocks=[] gagshopBlocks=[] petshopBlocks=[] kartshopBlocks = [] animBldgBlocks = [] for i in range(self.dnaStore.getNumBlockNumbers()): blockNumber = self.dnaStore.getBlockNumberAt(i) buildingType = self.dnaStore.getBlockBuildingType(blockNumber) if (buildingType == 'hq'): hqBlocks.append(blockNumber) elif (buildingType == 'gagshop'): gagshopBlocks.append(blockNumber) elif (buildingType == 'petshop'): petshopBlocks.append(blockNumber) elif( buildingType == 'kartshop' ): kartshopBlocks.append( blockNumber ) elif( buildingType == 'animbldg' ): animBldgBlocks.append( blockNumber ) else: blocks.append(blockNumber) return blocks, hqBlocks, gagshopBlocks, petshopBlocks, kartshopBlocks, animBldgBlocks def findAllLandmarkBuildings(self): assert(self.debugPrint("findAllLandmarkBuildings()")) # Load the saved buildings: buildings=self.load() # Create the distributed buildings: blocks, hqBlocks, gagshopBlocks, petshopBlocks, kartshopBlocks, animBldgBlocks = self.getDNABlockLists() for block in blocks: # Used saved data, if appropriate: self.newBuilding(block, buildings.get(block, None)) for block in animBldgBlocks: # Used saved data, if appropriate: self.newAnimBuilding(block, buildings.get(block, None)) for block in hqBlocks: self.newHQBuilding(block) for block in gagshopBlocks: self.newGagshopBuilding(block) if simbase.wantPets: for block in petshopBlocks: self.newPetshopBuilding(block) if( simbase.wantKarts ): for block in kartshopBlocks: self.newKartShopBuilding( block ) def newBuilding(self, blockNumber, blockData=None): """Create a new building and keep track of it.""" assert(self.debugPrint("newBuilding(blockNumber="+str(blockNumber) +", blockData="+str(blockData)+")")) assert(blockNumber not in self.__buildings) building=DistributedBuildingAI.DistributedBuildingAI( self.air, blockNumber, self.branchID, self.trophyMgr) building.generateWithRequired(self.branchID) if blockData: building.track = blockData.get("track", "c") building.difficulty = int(blockData.get("difficulty", 1)) building.numFloors = int(blockData.get("numFloors", 1)) building.numFloors = max(1, min(5, building.numFloors)) if not ZoneUtil.isWelcomeValley(building.zoneId): building.updateSavedBy(blockData.get("savedBy")) else: self.notify.warning('we had a cog building in welcome valley %d' % building.zoneId) building.becameSuitTime = blockData.get("becameSuitTime", time.time()) # Double check the state becuase we have seen the building # saved out with other states (like waitForVictor). If we # get one of these weird states, just make it a toon bldg if blockData["state"] == "suit": building.setState("suit") elif blockData['state'] == 'cogdo': if simbase.air.wantCogdominiums: building.numFloors = DistributedBuildingAI.DistributedBuildingAI.FieldOfficeNumFloors building.setState("cogdo") else: building.setState("toon") else: building.setState("toon") self.__buildings[blockNumber] = building return building def newAnimBuilding(self, blockNumber, blockData=None): """Create a new building and keep track of it.""" assert(self.debugPrint("newBuilding(blockNumber="+str(blockNumber) +", blockData="+str(blockData)+")")) assert(blockNumber not in self.__buildings) building=DistributedAnimBuildingAI.DistributedAnimBuildingAI( self.air, blockNumber, self.branchID, self.trophyMgr) building.generateWithRequired(self.branchID) if blockData: building.track = blockData.get("track", "c") building.difficulty = int(blockData.get("difficulty", 1)) building.numFloors = int(blockData.get("numFloors", 1)) if not ZoneUtil.isWelcomeValley(building.zoneId): building.updateSavedBy(blockData.get("savedBy")) else: self.notify.warning('we had a cog building in welcome valley %d' % building.zoneId) building.becameSuitTime = blockData.get("becameSuitTime", time.time()) # Double check the state becuase we have seen the building # saved out with other states (like waitForVictor). If we # get one of these weird states, just make it a toon bldg if blockData["state"] == "suit": building.setState("suit") else: building.setState("toon") else: building.setState("toon") self.__buildings[blockNumber] = building return building def newHQBuilding(self, blockNumber): """Create a new HQ building and keep track of it.""" assert(blockNumber not in self.__buildings) dnaStore = self.air.dnaStoreMap[self.canonicalBranchID] exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber) exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID) interiorZoneId = (self.branchID-self.branchID%100)+500+blockNumber assert(self.debugPrint("newHQBuilding(blockNumber=%s exteriorZoneId=%s interiorZoneId=%s" % (blockNumber, exteriorZoneId, interiorZoneId))) building=HQBuildingAI.HQBuildingAI(self.air, exteriorZoneId, interiorZoneId, blockNumber) self.__buildings[blockNumber] = building return building def newGagshopBuilding(self, blockNumber): """Create a new Gagshop building and keep track of it.""" assert(self.debugPrint("newGagshopBuilding(blockNumber="+str(blockNumber)+")")) assert(blockNumber not in self.__buildings) dnaStore = self.air.dnaStoreMap[self.canonicalBranchID] exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber) exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID) interiorZoneId = (self.branchID-self.branchID%100)+500+blockNumber building=GagshopBuildingAI.GagshopBuildingAI(self.air, exteriorZoneId, interiorZoneId, blockNumber) self.__buildings[blockNumber] = building return building def newPetshopBuilding(self, blockNumber): """Create a new Petshop building and keep track of it.""" assert(self.debugPrint("newPetshopBuilding(blockNumber="+str(blockNumber)+")")) assert(blockNumber not in self.__buildings) dnaStore = self.air.dnaStoreMap[self.canonicalBranchID] exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber) exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID) interiorZoneId = (self.branchID-self.branchID%100)+500+blockNumber building=PetshopBuildingAI.PetshopBuildingAI(self.air, exteriorZoneId, interiorZoneId, blockNumber) self.__buildings[blockNumber] = building return building def newKartShopBuilding( self, blockNumber ): """ Purpose: The newKartShopBuilding Method creates a new KartShop building and keeps track of it. Params: blockNumber - block that the shop is on. Return: None """ assert( self.debugPrint( "newKartShopBuilding(blockNumber=" + str( blockNumber ) + ")" ) ) assert( blockNumber not in self.__buildings ) dnaStore = self.air.dnaStoreMap[ self.canonicalBranchID ] # Retrieve the Exterior and Interior ZoneIds exteriorZoneId = dnaStore.getZoneFromBlockNumber( blockNumber ) exteriorZoneId = ZoneUtil.getTrueZoneId( exteriorZoneId, self.branchID ) interiorZoneId = ( self.branchID - self.branchID%100 ) + 500 + blockNumber building = KartShopBuildingAI( self.air, exteriorZoneId, interiorZoneId, blockNumber ) self.__buildings[ blockNumber ] = building return building def getFileName(self): """Figure out the path to the saved state""" f = "%s%s_%d.buildings" % (self.serverDatafolder, self.shard, self.branchID) assert(self.debugPrint("getFileName() returning \""+str(f)+"\"")) return f def saveTo(self, file, block=None): """Save data to specified file""" assert(self.debugPrint("saveTo(file="+str(file)+", block="+str(block)+")")) if block: # Save just this one block to the file: pickleData=block.getPickleData() pickle.dump(pickleData, file) else: # Save them all: for i in self.__buildings.values(): # HQs do not need to be saved if isinstance(i, HQBuildingAI.HQBuildingAI): continue pickleData=i.getPickleData() pickle.dump(pickleData, file) def fastSave(self, block): """Save data to default location""" return # This code has not been tested or connected. If the normal save takes # too long on the AI server, this fastSave should be considered. assert(0) assert(self.debugPrint("fastSave(block="+str(block)+")")) try: fileName=self.getFileName()+'.delta' working=fileName+'.temp' # Change the name to flag the work in progress: if os.path.exists(working): os.remove(working) os.rename(fileName, working) file=open(working, 'wb') file.seek(0, 2) self.saveTo(file, block) file.close() # Change the name to flag the work complete: os.rename(working, fileName) except IOError: self.notify.error(str(sys.exc_info()[1])) # Even if it's just the rename that failed, we don't want to # clobber the prior file. def save(self): """Save data to default location""" assert(self.debugPrint("save()")) try: fileName=self.getFileName() backup=fileName+self.backupExtension # Move current file as the backup file: if os.path.exists(fileName): os.rename(fileName, backup) file=open(fileName, 'wb') file.seek(0) self.saveTo(file) file.close() if os.path.exists(backup): os.remove(backup) except EnvironmentError: self.notify.warning(str(sys.exc_info()[1])) # Even if it's just the rename that failed, we don't want to # clobber the prior file. def loadFrom(self, file): """Load data from specified file""" assert(self.debugPrint("loadFrom(file="+str(file)+")")) blocks={} try: while 1: pickleData=pickle.load(file) blocks[int(pickleData['block'])]=pickleData except EOFError: pass return blocks def load(self): """Load data from default location""" assert(self.debugPrint("load()")) fileName=self.getFileName() try: # Try to open the backup file: file=open(fileName+self.backupExtension, 'rb') # Remove the (assumed) broken file: if os.path.exists(fileName): os.remove(fileName) except IOError: # OK, there's no backup file, good. try: # Open the real file: file=open(fileName, 'rb') except IOError: # OK, there's no file. Start new list: return {} file.seek(0) blocks=self.loadFrom(file) file.close() return blocks if __debug__: def debugPrint(self, message): """for debugging""" return self.notify.debug( str(self.__dict__.get('branchID', '?'))+' '+message)