From e289553726c30b0adfe25d5fb6989015d9d78281 Mon Sep 17 00:00:00 2001 From: Open Toontown <57279094+opentoontown@users.noreply.github.com> Date: Sat, 17 Dec 2022 01:15:36 -0500 Subject: [PATCH] ai: Getting somewhere --- otp/ai/AIRepository.py | 2 +- otp/distributed/DistributedDistrictAI.py | 64 +- otp/distributed/DistributedDistrictUD.py | 10 +- toontown/ai/AIStart.py | 6 +- toontown/ai/ToontownAIRepository.py | 2 +- toontown/building/DistributedBuildingMgrAI.py | 408 ++++++--- toontown/distributed/ToontownDistrictAI.py | 29 +- .../distributed/ToontownDistrictStatsAI.py | 99 ++- toontown/racing/RaceManagerAI.py | 804 +++++++++++++----- 9 files changed, 1020 insertions(+), 404 deletions(-) diff --git a/otp/ai/AIRepository.py b/otp/ai/AIRepository.py index edf1a00..561b2e6 100644 --- a/otp/ai/AIRepository.py +++ b/otp/ai/AIRepository.py @@ -1016,7 +1016,7 @@ class AIRepository(ConnectionRepository): datagram.addChannel(CONTROL_MESSAGE) datagram.addUint16(CONTROL_ADD_POST_REMOVE) - datagram.addString(themessage.getMessage()) + datagram.addBlob(themessage.getMessage()) self.send(datagram) def addPostSocketCloseUD(self, dclassName, fieldName, doId, args): diff --git a/otp/distributed/DistributedDistrictAI.py b/otp/distributed/DistributedDistrictAI.py index faedb7f..4f02211 100644 --- a/otp/distributed/DistributedDistrictAI.py +++ b/otp/distributed/DistributedDistrictAI.py @@ -1,37 +1,49 @@ -from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.directnotify.DirectNotifyGlobal import directNotify + +from direct.task import Task + class DistributedDistrictAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedDistrictAI') - - def __init__(self, air): + notify = directNotify.newCategory("DistributedDistrictAI") + + def __init__(self, air, name="untitled"): DistributedObjectAI.__init__(self, air) - self.name = '' - self.available = False + self.air = air + self.name=name + self.available = 0 + + def delete(self): + self.ignoreAll() + self.b_setAvailable(0) + DistributedObjectAI.delete(self) + + def getAvailable(self): + return self.available + + def getName(self): + return self.name + + # available value + def setAvailable(self, available): + self.available=available + + def d_setAvailable(self, available): + self.sendUpdate("setAvailable", [available]) + def b_setAvailable(self, available): + self.setAvailable(available) + self.d_setAvailable(available) + + # set name def setName(self, name): - self.name = name - + self.name=name + def d_setName(self, name): - self.sendUpdate('setName', [name]) + self.sendUpdate("setName", [name]) def b_setName(self, name): self.setName(name) self.d_setName(name) - - def getName(self): - return self.name - - def setAvailable(self, available): - self.available = available - - def d_setAvailable(self, available): - self.sendUpdate('setAvailable', [available]) - - def b_setAvailable(self, available): - self.setAvailable(available) - self.d_setAvailable(available) - - def getAvailable(self): - return self.available + diff --git a/otp/distributed/DistributedDistrictUD.py b/otp/distributed/DistributedDistrictUD.py index 0d2dca7..e540a99 100644 --- a/otp/distributed/DistributedDistrictUD.py +++ b/otp/distributed/DistributedDistrictUD.py @@ -1,6 +1,8 @@ -from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectUD import DistributedObjectUD -class DistributedDistrictUD(DistributedObjectUD): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedDistrictUD') +class DistributedDistrictUD: + def __init__(self, air, name="untitled"): + assert 0, "Hey, This assert is here to let you know that a DistributedDistrict should not be on a UD server." + # There is probably a bug somewhere if you are getting a district + # on an uberDog server, districts are for AI district servers and + # clients. diff --git a/toontown/ai/AIStart.py b/toontown/ai/AIStart.py index 213bdfa..a38a88a 100644 --- a/toontown/ai/AIStart.py +++ b/toontown/ai/AIStart.py @@ -21,19 +21,19 @@ print("Initializing...") from otp.ai.AIBaseGlobal import * from . import ToontownAIRepository -from direct.showbase import PythonUtil +from otp.otpbase import PythonUtil # Clear the default model extension for AI developers, so they'll know # when they screw up and omit it. from panda3d.core import loadPrcFileData loadPrcFileData("AIStart.py", "default-model-extension") -simbase.mdip = simbase.config.GetString("msg-director-ip", "localhost") +simbase.mdip = simbase.config.GetString("msg-director-ip", "127.0.0.1") # Now the AI connects directly to the state server instead of the msg director simbase.mdport = simbase.config.GetInt("msg-director-port", 6666) -simbase.esip = simbase.config.GetString("event-server-ip", "localhost") +simbase.esip = simbase.config.GetString("event-server-ip", "127.0.0.1") simbase.esport = simbase.config.GetInt("event-server-port", 4343) diff --git a/toontown/ai/ToontownAIRepository.py b/toontown/ai/ToontownAIRepository.py index 59a13b1..fab2a5d 100644 --- a/toontown/ai/ToontownAIRepository.py +++ b/toontown/ai/ToontownAIRepository.py @@ -536,7 +536,7 @@ class ToontownAIRepository(AIDistrict): # them can be used to fill the world with requests for suit # buildings. if self.suitPlanners: - self.suitPlanners.values()[0].assignInitialSuitBuildings() + list(self.suitPlanners.values())[0].assignInitialSuitBuildings() # mark district as avaliable self.district.b_setAvailable(1) diff --git a/toontown/building/DistributedBuildingMgrAI.py b/toontown/building/DistributedBuildingMgrAI.py index d506656..c7f8239 100644 --- a/toontown/building/DistributedBuildingMgrAI.py +++ b/toontown/building/DistributedBuildingMgrAI.py @@ -1,20 +1,73 @@ +""" 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 json +import pickle from otp.ai.AIBaseGlobal import * -from . import DistributedBuildingAI, HQBuildingAI, GagshopBuildingAI, PetshopBuildingAI +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, random +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 @@ -23,32 +76,38 @@ class DistributedBuildingMgrAI: self.backupExtension = '.bu' self.findAllLandmarkBuildings() self.doLaterTask = None - return + def cleanup(self): - taskMgr.remove(str(self.branchID) + '_delayed_save-timer') - for building in list(self.__buildings.values()): + taskMgr.remove(str(self.branchID)+'_delayed_save-timer') + + for building in self.__buildings.values(): building.cleanup() - self.__buildings = {} - + def isValidBlockNumber(self, blockNumber): - return blockNumber in self.__buildings + """return true if that block refers to a real block""" + assert(self.debugPrint("isValidBlockNumber(blockNumber="+str(blockNumber)+")")) + return self.__buildings.has_key(blockNumber) def delayedSaveTask(self, task): + assert(self.debugPrint("delayedSaveTask()")) self.save() - self.doLaterTask = None + 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(self.__buildings.has_key(blockNumber)) return self.__buildings[blockNumber].isSuitBlock() def getSuitBlocks(self): - blocks = [] - for i in list(self.__buildings.values()): + 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): @@ -63,193 +122,289 @@ class DistributedBuildingMgrAI: return blocks def getEstablishedSuitBlocks(self): - blocks = [] - for i in list(self.__buildings.values()): + assert(self.debugPrint("getEstablishedSuitBlocks()")) + blocks=[] + for i in self.__buildings.values(): if i.isEstablishedSuitBlock(): blocks.append(i.getBlock()[0]) - return blocks def getToonBlocks(self): - blocks = [] - for i in list(self.__buildings.values()): + 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 list(self.__buildings.values()) + 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(self.__buildings.has_key(blockNumber)) 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(self.__buildings.has_key(blockNumber)) return self.__buildings[blockNumber].track - def getBuilding(self, blockNumber): + def getBuilding( self, blockNumber ): + assert(self.debugPrint("getBuilding(%s)" %(str(blockNumber),))) + assert(self.__buildings.has_key(blockNumber)) 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(self.__buildings.has_key(blockNumber)) return self.__buildings[blockNumber].setFrontDoorPoint(point) - + def getDNABlockLists(self): - blocks = [] - hqBlocks = [] - gagshopBlocks = [] - petshopBlocks = [] + 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': + if (buildingType == 'hq'): + hqBlocks.append(blockNumber) + elif (buildingType == 'gagshop'): gagshopBlocks.append(blockNumber) - elif buildingType == 'petshop': + elif (buildingType == 'petshop'): petshopBlocks.append(blockNumber) - elif buildingType == 'kartshop': - kartshopBlocks.append(blockNumber) - elif buildingType == 'animbldg': - animBldgBlocks.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) - + return blocks, hqBlocks, gagshopBlocks, petshopBlocks, kartshopBlocks, animBldgBlocks + def findAllLandmarkBuildings(self): - buildings = self.load() + 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: + if( simbase.wantKarts ): for block in kartshopBlocks: - self.newKartShopBuilding(block) - - return + self.newKartShopBuilding( block ) def newBuilding(self, blockNumber, blockData=None): - building = DistributedBuildingAI.DistributedBuildingAI(self.air, blockNumber, self.branchID, self.trophyMgr) + """Create a new building and keep track of it.""" + assert(self.debugPrint("newBuilding(blockNumber="+str(blockNumber) + +", blockData="+str(blockData)+")")) + assert(not self.__buildings.has_key(blockNumber)) + + 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.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')) + 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()) - if blockData['state'] == 'suit': - building.setState('suit') + 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') + building.setState("cogdo") else: - building.setState('toon') + building.setState("toon") else: - building.setState('toon') + building.setState("toon") self.__buildings[blockNumber] = building return building def newAnimBuilding(self, blockNumber, blockData=None): - building = DistributedAnimBuildingAI.DistributedAnimBuildingAI(self.air, blockNumber, self.branchID, self.trophyMgr) + """Create a new building and keep track of it.""" + assert(self.debugPrint("newBuilding(blockNumber="+str(blockNumber) + +", blockData="+str(blockData)+")")) + assert(not self.__buildings.has_key(blockNumber)) + + 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')) + 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()) - if blockData['state'] == 'suit': - building.setState('suit') + 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') + building.setState("toon") else: - building.setState('toon') + building.setState("toon") self.__buildings[blockNumber] = building - return building + return building def newHQBuilding(self, blockNumber): + """Create a new HQ building and keep track of it.""" + assert(not self.__buildings.has_key(blockNumber)) 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 = HQBuildingAI.HQBuildingAI(self.air, exteriorZoneId, interiorZoneId, blockNumber) + 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(not self.__buildings.has_key(blockNumber)) 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) + 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(not self.__buildings.has_key(blockNumber)) 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) + 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): - 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 = KartShopBuildingAI(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( not self.__buildings.has_key( blockNumber ) ) + + 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): - f = '%s%s_%d_buildings.json' % (self.air.dataFolder, self.shard, self.branchID) + """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): - buildings = {} - for i in list(self.__buildings.values()): - if isinstance(i, HQBuildingAI.HQBuildingAI): - continue - buildingData = i.getBuildingData() - buildings[buildingData['block']] = buildingData - - json.dump(buildings, file, indent=2) - - def save(self): + + 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() - backup = fileName + self.backupExtension + 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, 'w') + 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, 'w') + file=open(fileName, 'w') file.seek(0) self.saveTo(file) file.close() @@ -257,28 +412,47 @@ class DistributedBuildingMgrAI: 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): - blocks = {} - buildingData = json.load(file) - for block in buildingData: - blocks[int(block)] = buildingData[block] - - return blocks - - def load(self): - fileName = self.getFileName() + """Load data from specified file""" + assert(self.debugPrint("loadFrom(file="+str(file)+")")) + blocks={} try: - file = open(fileName + self.backupExtension, 'r') + 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, 'r') + # Remove the (assumed) broken file: if os.path.exists(fileName): os.remove(fileName) except IOError: + # OK, there's no backup file, good. try: - file = open(fileName, 'r') + # Open the real file: + file=open(fileName, 'r') except IOError: + # OK, there's no file. Start new list: return {} - file.seek(0) - blocks = self.loadFrom(file) + 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) + diff --git a/toontown/distributed/ToontownDistrictAI.py b/toontown/distributed/ToontownDistrictAI.py index d9be1a2..4dfe15f 100644 --- a/toontown/distributed/ToontownDistrictAI.py +++ b/toontown/distributed/ToontownDistrictAI.py @@ -1,12 +1,31 @@ -from direct.directnotify import DirectNotifyGlobal +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.task import Task +from otp.distributed.OtpDoGlobals import * from otp.distributed.DistributedDistrictAI import DistributedDistrictAI +from toontown.distributed import ToontownDistrictStatsAI class ToontownDistrictAI(DistributedDistrictAI): - notify = DirectNotifyGlobal.directNotify.newCategory('ToontownDistrictAI') - - def __init__(self, air): - DistributedDistrictAI.__init__(self, air) + """ + See Also: "toontown/src/distributed/DistributedDistrict.py" + """ + notify = directNotify.newCategory("ToontownDistrictAI") + + def __init__(self, air, name="untitled"): + DistributedDistrictAI.__init__(self, air, name) + self.stats = None self.ahnnLog = False + + def generate(self): + DistributedDistrictAI.generate(self) + self.stats = ToontownDistrictStatsAI.ToontownDistrictStatsAI(self.air) + self.stats.toontownDistrictId = self.doId + self.stats.generateOtpObject(self.stats.defaultParent, self.stats.defaultZone) + + def delete(self): + DistributedDistrictAI.delete(self) + if(self.stats is not None): + self.stats.requestDelete() + self.stats = None def allowAHNNLog(self, ahnnLog): self.ahnnLog = ahnnLog diff --git a/toontown/distributed/ToontownDistrictStatsAI.py b/toontown/distributed/ToontownDistrictStatsAI.py index 86d4c6b..4e9a2f3 100644 --- a/toontown/distributed/ToontownDistrictStatsAI.py +++ b/toontown/distributed/ToontownDistrictStatsAI.py @@ -1,50 +1,89 @@ +from direct.distributed import DistributedObjectAI from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.task import Task -class ToontownDistrictStatsAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('ToontownDistrictStatsAI') +from otp.distributed.OtpDoGlobals import * + +class ToontownDistrictStatsAI(DistributedObjectAI.DistributedObjectAI): + """ + See Also: "toontown/src/distributed/DistributedDistrictAi.py" + """ + notify = DirectNotifyGlobal.directNotify.newCategory("ToontownDistrictStatsAI") + + defaultParent = OTP_DO_ID_TOONTOWN + defaultZone = OTP_ZONE_ID_DISTRICTS_STATS + + + def __init__(self, air): - DistributedObjectAI.__init__(self, air) + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.updateFreq = 5 self.toontownDistrictId = 0 - self.avatarCount = 0 - self.newAvatarCount = 0 - - def settoontownDistrictId(self, toontownDistrictId): - self.toontownDistrictId = toontownDistrictId - - def d_settoontownDistrictId(self, toontownDistrictId): - self.sendUpdate('settoontownDistrictId', [toontownDistrictId]) - - def b_settoontownDistrictId(self, toontownDistrictId): - self.settoontownDistrictId(toontownDistrictId) - self.d_settoontownDistrictId(toontownDistrictId) - + def gettoontownDistrictId(self): - return self.toontownDistrictId + return self.toontownDistrictId + + def generate(self): + DistributedObjectAI.DistributedObjectAI.generate(self) + self.pushDistrictStats(firstTime=True) + + def delete(self): + taskMgr.remove("DistributedToonDistrictAIStatsUpdate") + DistributedObjectAI.DistributedObjectAI.delete(self) + + # set avatar count def setAvatarCount(self, avatarCount): - self.avatarCount = avatarCount - + pass + def d_setAvatarCount(self, avatarCount): - self.sendUpdate('setAvatarCount', [avatarCount]) - + self.sendUpdate("setAvatarCount", [avatarCount]) + def b_setAvatarCount(self, avatarCount): - self.setAvatarCount(avatarCount) - self.d_setAvatarCount(avatarCount) + self.setAvatarCount(avatarCount) + self.d_setAvatarCount(avatarCount) def getAvatarCount(self): - return self.avatarCount - + return 0 + + ## avatars in bewbe zone... def setNewAvatarCount(self, newAvatarCount): - self.newAvatarCount = newAvatarCount + pass def d_setNewAvatarCount(self, newAvatarCount): - self.sendUpdate('setNewAvatarCount', [newAvatarCount]) - + self.sendUpdate("setAvatarCount", [newAvatarCount]) + def b_setNewAvatarCount(self, newAvatarCount): self.setNewAvatarCount(newAvatarCount) self.d_setNewAvatarCount(newAvatarCount) def getNewAvatarCount(self): - return self.newAvatarCount + return 0 + + ## stat fields... + + def setStats(self, avatarCount, newAvatarCount): + self.setAvatarCount(avatarCount) + self.setNewAvatarCount(newAvatarCount) + + def d_setStats(self, avatarCount, newAvatarCount): + self.sendUpdate("setStats", [avatarCount, newAvatarCount]) + + def b_setStats(self, avatarCount, newAvatarCount): + self.setStats(avatarCount, newAvatarCount) + self.d_setStats(avatarCount, newAvatarCount) + + + def pushDistrictStats(self, task=None, firstTime=False): + if self.isDeleted(): + return + # the first time we're called, the AIR doesn't have a welcomeValleyManager yet + if firstTime: + wvCount = 0 + else: + wvCount = self.air.getWelcomeValleyCount() + avatar_count = self.air.getPopulation() + self.b_setStats(avatar_count, wvCount) + taskMgr.doMethodLater(self.updateFreq, self.pushDistrictStats, "DistributedDistrictUpdate") + self.air.writeServerStatus("", avatar_count, len(self.air.doId2do)) diff --git a/toontown/racing/RaceManagerAI.py b/toontown/racing/RaceManagerAI.py index b19e54f..fc8cf1b 100644 --- a/toontown/racing/RaceManagerAI.py +++ b/toontown/racing/RaceManagerAI.py @@ -1,15 +1,29 @@ from direct.directnotify import DirectNotifyGlobal -from toontown.racing import DistributedRaceAI +from . import DistributedRaceAI from toontown.toonbase import ToontownGlobals, TTLocalizer from toontown.coghq import MintLayout from toontown.ai import HolidayBaseAI from direct.showbase import DirectObject -from toontown.racing import RaceGlobals -import os, pickle +from . import RaceGlobals +import random +import os +import pickle + +def getDefaultRecord(trackId): + """ + Get the default record for this track. Used for reseting records. + """ + # format: time, race type, num racers, racer name + return (RaceGlobals.getDefaultRecordTime(trackId), 0, 1, TTLocalizer.Goofy) class RaceManagerAI(DirectObject.DirectObject): - notify = DirectNotifyGlobal.directNotify.newCategory('RaceManagerAI') + notify = DirectNotifyGlobal.directNotify.newCategory('RaceManagerAI') + + serverDataFolder = simbase.config.GetString('server-data-folder', "") + + SuspiciousPercent = 0.15 # what percent of qualifying time counts as suspicious + def __init__(self, air): DirectObject.DirectObject.__init__(self) self.air = air @@ -17,122 +31,205 @@ class RaceManagerAI(DirectObject.DirectObject): self.shard = str(air.districtId) self.filename = self.getFilename() self.trackRecords = self.loadRecords() - + def getDoId(self): + # DistributedElevatorAI needs this return 0 - def createRace(self, trackId, raceType, laps, players, circuitLoop, circuitPoints, circuitTimes, qualTimes=[], circuitTimeList={}, circuitTotalBonusTickets={}): + def createRace(self, trackId, raceType, laps, players, circuitLoop, circuitPoints, circuitTimes, qualTimes = [], circuitTimeList = {}, circuitTotalBonusTickets = {}): + #print('Creating Race: circuit Points %s' % circuitPoints) + assert self.notify.debug("createRace: players = %s" % players) raceZone = self.air.allocateZone() - race = DistributedRaceAI.DistributedRaceAI(self.air, trackId, raceZone, players, laps, raceType, self.exitedRace, self.raceOver, circuitLoop, circuitPoints, circuitTimes, qualTimes, circuitTimeList, circuitTotalBonusTickets) + race = DistributedRaceAI.DistributedRaceAI( + self.air, trackId, raceZone, players, laps, raceType, + self.exitedRace, self.raceOver, + circuitLoop, circuitPoints, circuitTimes, qualTimes, circuitTimeList, circuitTotalBonusTickets) race.generateWithRequired(raceZone) + # TODO: where should this be stored? race.playersFinished = [] race.lastTotalTime = 0 self.races.append(race) return raceZone def exitedRace(self, race, playerInfo): - self.notify.debug('exited race: %s' % playerInfo.avId) - totalTime = playerInfo.totalTime + # The Object returned here, playerInfo, is a Racer object, + # and can be found in Racer.py + + self.notify.debug("exited race: %s" % playerInfo.avId) + + # calculate total race time + totalTime=playerInfo.totalTime + entryFee = 0 bonus = 0 - placeMultiplier = 0 + placeMultiplier=0 qualify = 0 winnings = 0 trophies = [] points = [] newHistory = None + suspicious = False # if True, his time is suspiciously low + + # determine their place race.playersFinished.append(playerInfo.avId) place = len(race.playersFinished) - self.notify.debug('place: %s of %s' % (place, race.toonCount)) - self.notify.debug('pre-tie totalTime: %s' % totalTime) + self.notify.debug( "place: %s of %s" % (place, race.toonCount) ) + + self.notify.debug("pre-tie totalTime: %s" % totalTime) + + # HACK: make sure they don't tie if totalTime <= race.lastTotalTime: totalTime = race.lastTotalTime + 0.01 race.lastTotalTime = totalTime - self.notify.debug('totalTime: %s, qualify: %s' % (totalTime, RaceGlobals.getQualifyingTime(race.trackId))) + + self.notify.debug("totalTime: %s, qualify: %s" % (totalTime, RaceGlobals.getQualifyingTime(race.trackId) )) + + # update this player's total time for this set of races circuitTime = totalTime + race.circuitTimes.get(playerInfo.avId, 0) race.circuitTimes[playerInfo.avId] = circuitTime + + # keep track of the players time if not race.circuitTimeList.get(playerInfo.avId): race.circuitTimeList[playerInfo.avId] = [] race.circuitTimeList[playerInfo.avId].append([totalTime, 0]) - self.notify.debug('CircuitTimeList %s' % race.circuitTimeList) + self.notify.debug("CircuitTimeList %s" % (race.circuitTimeList)) + + # update circuit points if race.raceType == RaceGlobals.Circuit: points = race.circuitPoints.get(playerInfo.avId, []) - points.append(RaceGlobals.CircuitPoints[place - 1]) + points.append(RaceGlobals.CircuitPoints[place-1]) race.circuitPoints[playerInfo.avId] = points - currentTimeIndex = len(race.circuitTimeList[playerInfo.avId]) - 1 + + currentTimeIndex = len(race.circuitTimeList[playerInfo.avId]) -1 + # if they qualified if totalTime <= RaceGlobals.getQualifyingTime(race.trackId): race.circuitTimeList[playerInfo.avId][currentTimeIndex][1] = 1 - self.notify.debug('Racer Qualified time: %s required: %s' % (totalTime, RaceGlobals.getQualifyingTime(race.trackId))) + self.notify.debug("Racer Qualified time: %s required: %s" % (totalTime, RaceGlobals.getQualifyingTime(race.trackId))) + # set the flag for the gui qualify = 1 + + # see if the broke a personal record self.checkPersonalBest(race.trackId, totalTime, race.raceType, race.toonCount, playerInfo.avId) + + # check for suspiciousTime + qualTime = RaceGlobals.getQualifyingTime(race.trackId) + threshold = qualTime * self.SuspiciousPercent + if totalTime < threshold: + self.air.writeServerEvent("kartingSuspiciousRacer", playerInfo.avId, "%s|%s" % (race.trackId,totalTime)) + suspicious = True + + # you can only place if you beat qualifying time and you are NOT practicing if race.raceType == RaceGlobals.Practice: winnings = RaceGlobals.PracticeWinnings - self.notify.debug('GrandTouring: Checking from branch: practice %s' % playerInfo.avId) + self.notify.debug("GrandTouring: Checking from branch: practice %s" % (playerInfo.avId)) trophies = self.checkForNonRaceTrophies(playerInfo.avId) + if trophies: + # need to update the history from a list of trophiesWon self.updateTrophiesFromList(playerInfo.avId, trophies) else: - self.air.writeServerEvent('kartingPlaced', playerInfo.avId, '%s|%s' % (place, race.toonCount)) + # keep track of placemnet for contests + self.air.writeServerEvent("kartingPlaced", playerInfo.avId, "%s|%s" % (place, race.toonCount)) + if race.raceType != RaceGlobals.Circuit: + # calculate their winnings entryFee = RaceGlobals.getEntryFee(race.trackId, race.raceType) - placeMultiplier = RaceGlobals.Winnings[place - 1 + (RaceGlobals.MaxRacers - race.toonCount)] + placeMultiplier = RaceGlobals.Winnings[(place - 1) + (RaceGlobals.MaxRacers - race.toonCount)] winnings = int(entryFee * placeMultiplier) + + # win any trophies? + #trophies = self.checkForTrophies(place, race.trackId, race.raceType, race.toonCount, playerInfo.avId) + # update history newHistory = self.getNewSingleRaceHistory(race, playerInfo.avId, place) av = self.air.doId2do.get(playerInfo.avId) if newHistory: - self.notify.debug('history %s' % newHistory) + self.notify.debug("history %s" % (newHistory)) av.b_setKartingHistory(newHistory) + # win any trophies? trophies = self.checkForRaceTrophies(race, playerInfo.avId) else: trophies = self.checkForNonRaceTrophies(playerInfo.avId) + if trophies: + # need to update the history from a list of trophiesWon self.updateTrophiesFromList(playerInfo.avId, trophies) - bonus = self.checkTimeRecord(race.trackId, totalTime, race.raceType, race.toonCount, playerInfo.avId) - if playerInfo.avId in race.circuitTotalBonusTickets: - race.circuitTotalBonusTickets[playerInfo.avId] += bonus - else: - race.circuitTotalBonusTickets[playerInfo.avId] = bonus + + + # see if they broke a server record + if not suspicious: + bonus = self.checkTimeRecord(race.trackId, totalTime, race.raceType, race.toonCount, playerInfo.avId) + if (race.circuitTotalBonusTickets.has_key(playerInfo.avId)): + race.circuitTotalBonusTickets[playerInfo.avId] += bonus + else: + race.circuitTotalBonusTickets[playerInfo.avId] = bonus + + # set the tickets on the toon av = self.air.doId2do.get(playerInfo.avId) if av: oldTickets = av.getTickets() - self.notify.debug('old tickets: %s' % oldTickets) + self.notify.debug( "old tickets: %s" % oldTickets ) newTickets = oldTickets + winnings + entryFee + bonus - self.air.writeServerEvent('kartingTicketsWon', playerInfo.avId, '%s' % (newTickets - oldTickets)) - self.notify.debug('entry fee: %s' % entryFee) - self.notify.debug('place mult: %s' % placeMultiplier) - self.notify.debug('winnings: %s' % winnings) - self.notify.debug('bonus: %s' % bonus) - self.notify.debug('new tickets: %s' % newTickets) - self.notify.debug('circuit points: %s' % points) - self.notify.debug('circuit time: %s' % circuitTime) - self.notify.debug('circuitTotalBonusTickets: %s' % race.circuitTotalBonusTickets) + # Write to the server log + self.air.writeServerEvent("kartingTicketsWon", playerInfo.avId, "%s" % (newTickets - oldTickets)) + self.notify.debug( "entry fee: %s" % entryFee ) + self.notify.debug( "place mult: %s" % placeMultiplier ) + self.notify.debug( "winnings: %s" % winnings ) + self.notify.debug( "bonus: %s" % bonus ) + self.notify.debug( "new tickets: %s" % newTickets ) + self.notify.debug( "circuit points: %s" % points ) + self.notify.debug( "circuit time: %s" % circuitTime ) + self.notify.debug( "circuitTotalBonusTickets: %s" % race.circuitTotalBonusTickets) av.b_setTickets(newTickets) + else: race.circuitTimeList[playerInfo.avId][currentTimeIndex][1] = -1 - self.notify.debug('GrandTouring: Checking from branch: Not Qualified %s' % playerInfo.avId) + self.notify.debug("GrandTouring: Checking from branch: Not Qualified %s" % (playerInfo.avId)) trophies = self.checkForNonRaceTrophies(playerInfo.avId) + if trophies: + # need to update the history from a list of trophiesWon self.updateTrophiesFromList(playerInfo.avId, trophies) - if race in self.races: - race.d_setPlace(playerInfo.avId, totalTime, place, entryFee, qualify, winnings, bonus, trophies, points, circuitTime) + + # report the good news! + if(race in self.races): + race.d_setPlace(playerInfo.avId, + totalTime, + place, + entryFee, + qualify, + winnings, + bonus, + trophies, + points, + circuitTime) + + # handle the end of a circuit race if race.isCircuit(): - self.notify.debug('isCircuit') + self.notify.debug("isCircuit") if race.everyoneDone(): - self.notify.debug('everyoneDone') - if race.isLastRace(): - taskMgr.doMethodLater(10, self.endCircuitRace, 'DelayEndCircuitRace=%d' % race.doId, (race, bonus)) + self.notify.debug("everyoneDone") + #doing this immediately kills showing the breaking a record sequence. + #it's a problem only when the last player in the 3rd race breaks a record + if race.isLastRace(): + taskMgr.doMethodLater(10,self.endCircuitRace,"DelayEndCircuitRace=%d" % race.doId , (race,bonus)) else: self.endCircuitRace(race, bonus) else: - self.notify.debug('not isCircuit') - return + self.notify.debug("not isCircuit") + + + #trophies.extend(self.checkForNonRaceTrophies(playerInfo.avId, newHistory)) - def endCircuitRace(self, race, bonus=0): - self.notify.debug('endCircuitRace') + + + def endCircuitRace(self, race, bonus = 0): + self.notify.debug("endCircuitRace") + # look at all the points and decide the standings pointTotals = [] for avId in race.circuitPoints: pointTotals.append([avId, sum(race.circuitPoints[avId])]) + # do a quick n dirty bubble sort (first place -> last place) numVals = len(pointTotals) finalStandings = [] @@ -141,66 +238,88 @@ class RaceManagerAI(DirectObject.DirectObject): pointTotals[x] = pointTotals[y] pointTotals[y] = t - for i in range(numVals - 1, 0, -1): + # the old bubble sort seemed wonky - grabbed this one from wikisource! =] + + for i in range(numVals-1,0,-1): for j in range(i): - if pointTotals[j][1] < pointTotals[j + 1][1]: - swap(j, j + 1) - elif pointTotals[j][1] == pointTotals[j + 1][1]: + if pointTotals[j][1] < pointTotals[j+1][1]: + swap(j, j+1) + elif pointTotals[j][1] == pointTotals[j+1][1]: + # ties are resolved via totalTimes avId1 = pointTotals[j][0] - avId2 = pointTotals[j + 1][0] + avId2 = pointTotals[j+1][0] if race.circuitTimes[avId1] > race.circuitTimes[avId2]: - swap(j, j + 1) + swap(j,j+1) + # don't trust grabbing them out while sorting for i in range(numVals): finalStandings.append(pointTotals[i][0]) - self.notify.debug('Final standings %s' % finalStandings) + self.notify.debug("Final standings %s" % (finalStandings)) + # check for trophies, apply winnings, etc. for avId in finalStandings: - self.notify.debug('avId %s' % avId) + self.notify.debug("avId %s" % (avId)) + place = finalStandings.index(avId) + 1 places = len(finalStandings) winnings = 0 trophies = [] + if race.isLastRace(): - self.notify.debug('isLastRace') + self.notify.debug("isLastRace") + # make sure we don't reward any disconnected racers av = self.air.doId2do.get(avId) - if av and avId in race.playersFinished: - self.air.writeServerEvent('kartingCircuitFinished', avId, '%s|%s' % (place, places)) - print('kartingCircuitFinished', avId, '%s|%s' % (place, places)) + if av and (avId in race.playersFinished): + # keep track of placement for contests + self.air.writeServerEvent("kartingCircuitFinished", avId, "%s|%s|%s|%s" % (place, places, race.circuitTimes[avId], race.trackId)) + print("kartingCircuitFinished", avId, "%s|%s|%s|%s" % (place, places, race.circuitTimes[avId], race.trackId)) + + # calculate their winnings entryFee = RaceGlobals.getEntryFee(race.trackId, race.raceType) - placeMultiplier = RaceGlobals.Winnings[place - 1 + (RaceGlobals.MaxRacers - places)] + placeMultiplier = RaceGlobals.Winnings[(place - 1) + (RaceGlobals.MaxRacers - places)] winnings = int(entryFee * placeMultiplier) + + # update history newHistory = self.getNewCircuitHistory(race, avId, place) if newHistory: - self.notify.debug('history %s' % newHistory) + self.notify.debug("history %s" % (newHistory)) + # win any trophies? trophies = self.checkForCircuitTrophies(race, avId, newHistory) av.b_setKartingHistory(newHistory) - if trophies: - self.updateTrophiesFromList(avId, trophies) + else: - self.notify.debug('no new history') + self.notify.debug("no new history") trophies = self.checkForNonRaceTrophies(avId) + + if trophies: + # need to update the history from a list of trophiesWon + self.updateTrophiesFromList(avId, trophies) + oldTickets = av.getTickets() - self.notify.debug('endCircuitRace: old tickets: %s' % oldTickets) + self.notify.debug( "endCircuitRace: old tickets: %s" % oldTickets ) newTickets = oldTickets + winnings + entryFee - self.air.writeServerEvent('kartingTicketsWonCircuit', avId, '%s' % (newTickets - oldTickets)) - self.notify.debug('entry fee: %s' % entryFee) - self.notify.debug('place mult: %s' % placeMultiplier) - self.notify.debug('winnings: %s' % winnings) - self.notify.debug('new tickets: %s' % newTickets) - self.notify.debug('trophies: %s' % trophies) - self.notify.debug('bonus: %s' % bonus) + # Write to the server log + self.air.writeServerEvent("kartingTicketsWonCircuit", avId, "%s" % (newTickets - oldTickets)) + self.notify.debug( "entry fee: %s" % entryFee ) + self.notify.debug( "place mult: %s" % placeMultiplier ) + self.notify.debug( "winnings: %s" % winnings ) + self.notify.debug( "new tickets: %s" % newTickets ) + self.notify.debug( "trophies: %s" % trophies ) + self.notify.debug( "bonus: %s" % bonus) av.b_setTickets(newTickets) + finalBonus = 0 - if avId in race.circuitTotalBonusTickets: + if (race.circuitTotalBonusTickets.has_key(avId)): finalBonus = race.circuitTotalBonusTickets[avId] - race.d_setCircuitPlace(avId, place, entryFee, winnings, finalBonus, trophies) + + #race.d_setCircuitPlace(avId, place, entryFee, winnings, bonus, trophies) + race.d_setCircuitPlace(avId, place, entryFee, winnings, finalBonus, trophies) race.playersFinished = finalStandings race.d_endCircuitRace() - + def updateTrophiesFromList(self, avId, trophies): - self.air.writeServerEvent('Writing Trophies: Updating trophies from list', avId, '%s' % trophies) + self.air.writeServerEvent("Writing Trophies: Updating trophies from list", avId, "%s" % (trophies)) av = self.air.doId2do.get(avId) if not av: return @@ -209,272 +328,404 @@ class RaceManagerAI(DirectObject.DirectObject): trophyField = av.getKartingTrophies() for trophy in trophies: trophyField[trophy] = 1 - av.b_setKartingTrophies(trophyField) + + + def checkForCircuitTrophies(self, race, avId, inHistory = None): + #checks for qualify, win, and sweep trophies for curcuit races + #returns a list of new trophies + + #check and retrieve the needed data - def checkForCircuitTrophies(self, race, avId, inHistory=None): av = self.air.doId2do.get(avId) if not av: return [] + trophies = av.getKartingTrophies() if inHistory: history = inHistory else: history = av.getKartingHistory() + + #trackGenre = RaceGlobals.getTrackGenre(race.trackId) + + #setup the test groups: wins, sweeps, and qualifies + winIndex = RaceGlobals.CircuitWins winReqList = RaceGlobals.WonCircuitRaces winIndices = RaceGlobals.CircuitWinsList + sweepIndex = RaceGlobals.CircuitSweeps sweepReqList = RaceGlobals.SweptCircuitRaces sweepIndices = RaceGlobals.CircuitSweepsList + qualIndex = RaceGlobals.CircuitQuals qualReqList = RaceGlobals.QualifiedCircuitRaces qualIndices = RaceGlobals.CircuitQualList + trophies = av.getKartingTrophies() newTrophies = [] + + #check to see which win trophies are won newTrophies.extend(self.checkHistoryForTrophy(trophies, history, winIndex, winReqList, winIndices)) + + #check to see which sweep trophies are won newTrophies.extend(self.checkHistoryForTrophy(trophies, history, sweepIndex, sweepReqList, sweepIndices)) + + #check to see which qualify trophies are won newTrophies.extend(self.checkHistoryForTrophy(trophies, history, qualIndex, qualReqList, qualIndices)) - self.notify.debug('GrandTouring: Checking from branch: Circuit %s' % avId) + self.notify.debug("GrandTouring: Checking from branch: Circuit %s" % (avId)) newTrophies.extend(self.checkForNonRaceTrophies(avId, history)) newTrophies.sort() - return newTrophies - def checkForRaceTrophies(self, race, avId, inHistory=None): + return newTrophies + + def checkForRaceTrophies(self, race, avId, inHistory = None): + #checks for single race win and qualified trophies + + #check and retrieve the needed data + av = self.air.doId2do.get(avId) if not av: return [] + trophies = av.getKartingTrophies() if inHistory: history = inHistory else: history = av.getKartingHistory() + newTrophies = [] + + #check for genre wins trophies numTrackGenres = len(RaceGlobals.WinsList) for genre in range(numTrackGenres): - singleGenreNewTrophies = self.checkHistoryForTrophy(trophies, history, RaceGlobals.WinsList[genre], RaceGlobals.WonRaces, RaceGlobals.AllWinsList[genre]) + singleGenreNewTrophies = self.checkHistoryForTrophy(trophies, + history, + RaceGlobals.WinsList[genre], + RaceGlobals.WonRaces, + RaceGlobals.AllWinsList[genre]) newTrophies.extend(singleGenreNewTrophies) - + + #check for genre quals trophies numTrackGenres = len(RaceGlobals.QualsList) for genre in range(numTrackGenres): - singleGenreNewTrophies = self.checkHistoryForTrophy(trophies, history, RaceGlobals.QualsList[genre], RaceGlobals.QualifiedRaces, RaceGlobals.AllQualsList[genre]) + singleGenreNewTrophies = self.checkHistoryForTrophy(trophies, + history, + RaceGlobals.QualsList[genre], + RaceGlobals.QualifiedRaces, + RaceGlobals.AllQualsList[genre]) newTrophies.extend(singleGenreNewTrophies) - + + #check for total wins trophy totalWins = 0 for genre in range(numTrackGenres): totalWins += history[RaceGlobals.WinsList[genre]] - - singleGenreNewTrophies = self.checkHistoryForTrophyByValue(trophies, history, totalWins, [ - RaceGlobals.TotalWonRaces], [ - RaceGlobals.TotalWins]) + + singleGenreNewTrophies = self.checkHistoryForTrophyByValue(trophies, + history, + totalWins, + [RaceGlobals.TotalWonRaces], + [RaceGlobals.TotalWins]) newTrophies.extend(singleGenreNewTrophies) + + #check for total quals trophy totalQuals = 0 for genre in range(numTrackGenres): totalQuals += history[RaceGlobals.QualsList[genre]] - - singleGenreNewTrophies = self.checkHistoryForTrophyByValue(trophies, history, totalQuals, [ - RaceGlobals.TotalQualifiedRaces], [ - RaceGlobals.TotalQuals]) + + singleGenreNewTrophies = self.checkHistoryForTrophyByValue(trophies, + history, + totalQuals, + [RaceGlobals.TotalQualifiedRaces], + [RaceGlobals.TotalQuals]) newTrophies.extend(singleGenreNewTrophies) - self.notify.debug('GrandTouring: Checking from branch: Race %s ' % avId) + self.notify.debug("GrandTouring: Checking from branch: Race %s " % (avId)) newTrophies.extend(self.checkForNonRaceTrophies(avId, history)) newTrophies.sort() + return newTrophies - - def checkForNonRaceTrophies(self, avId, inHistory=None): - self.notify.debug('CHECKING FOR NONRACE TROPHIES') - self.notify.debug('GrandTouring: Checking for non-race trophies %s' % avId) + + def checkForNonRaceTrophies(self, avId, inHistory = None): + #checks for race independent trophies like laff cups + self.notify.debug("CHECKING FOR NONRACE TROPHIES") + self.notify.debug("GrandTouring: Checking for non-race trophies %s" % (avId)) + + #check and retrieve the needed data + av = self.air.doId2do.get(avId) if not av: - self.notify.debug('NO AV %s' % avId) - self.notify.debug("GrandTouring: can't convert avId to Av %s" % avId) + self.notify.debug("NO AV %s" % (avId)) + self.notify.debug("GrandTouring: can't convert avId to Av %s" % (avId)) return [] + trophies = av.getKartingTrophies() if inHistory: history = inHistory else: history = av.getKartingHistory() + newTrophies = [] - self.notify.debug('GrandTouring: history-- %s' % history) - self.notify.debug('GrandTouring: trophies- %s' % trophies) + self.notify.debug("GrandTouring: history-- %s" % (history)) + self.notify.debug("GrandTouring: trophies- %s" % (trophies)) + addTrophyCount = 0 + + #check for grandtouring if not trophies[RaceGlobals.GrandTouring]: - self.notify.debug('checking for grand touring') - self.notify.debug('GrandTouring: checking for grand touring %s' % trophies[RaceGlobals.GrandTouring]) + self.notify.debug("checking for grand touring") + self.notify.debug("GrandTouring: checking for grand touring %s" % (trophies[RaceGlobals.GrandTouring])) + #import pdb; pdb.set_trace() best = av.getKartingPersonalBestAll() - self.notify.debug('personal best %s' % best) - self.notify.debug('GrandTouring: checking personal best %s' % best) + self.notify.debug("personal best %s" % (best)) + self.notify.debug("GrandTouring: checking personal best %s" % (best)) counter = 0 for time in best: if not time == 0: - counter += 1 - - self.notify.debug('counter %s tracks %s' % (counter, len(RaceGlobals.TrackDict))) - self.notify.debug('GrandTouring: bests comparison counter: %s tracks: %s' % (counter, len(RaceGlobals.TrackDict))) + counter +=1 + self.notify.debug("counter %s tracks %s" % (counter, len(RaceGlobals.TrackDict))) + self.notify.debug("GrandTouring: bests comparison counter: %s tracks: %s" % (counter, len(RaceGlobals.TrackDict))) if counter >= len(RaceGlobals.TrackDict): newTrophies.append(RaceGlobals.GrandTouring) addTrophyCount += 1 - self.air.writeServerEvent('kartingTrophy', avId, '%s' % RaceGlobals.GrandTouring) - self.notify.debug('trophy: ' + TTLocalizer.KartTrophyDescriptions[RaceGlobals.GrandTouring]) - self.notify.debug('GrandTouring: awarding grand touring new trophies %s' % newTrophies) + self.air.writeServerEvent("kartingTrophy", avId, "%s" % (RaceGlobals.GrandTouring)) + self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[RaceGlobals.GrandTouring] ) + self.notify.debug("GrandTouring: awarding grand touring new trophies %s" % (newTrophies)) else: - self.notify.debug('already has grandtouring') - self.notify.debug('trophies %s' % trophies) - self.notify.debug('GrandTouring: already has grand touring %s' % trophies[RaceGlobals.GrandTouring]) - for i in range(1, RaceGlobals.NumTrophies // RaceGlobals.TrophiesPerCup + 1): - cupNum = (trophies[:RaceGlobals.NumTrophies].count(1) + addTrophyCount) // (i * RaceGlobals.TrophiesPerCup) - self.notify.debug('cupNum: %s' % cupNum) + self.notify.debug("already has grandtouring") + self.notify.debug("trophies %s" % (trophies)) + self.notify.debug("GrandTouring: already has grand touring %s" % (trophies[RaceGlobals.GrandTouring])) + + # check to see if we need a laff point + for i in range(1, RaceGlobals.NumTrophies/RaceGlobals.TrophiesPerCup + 1): + cupNum = (trophies[:RaceGlobals.NumTrophies].count(1) + addTrophyCount) / (i * RaceGlobals.TrophiesPerCup) + self.notify.debug( "cupNum: %s" % cupNum) trophyIndex = RaceGlobals.TrophyCups[i - 1] if cupNum and not trophies[trophyIndex]: + # award the cup! + #trophies[trophyIndex] = 1 newTrophies.append(trophyIndex) + # laff point boost! oldMaxHp = av.getMaxHp() newMaxHp = min(ToontownGlobals.MaxHpLimit, oldMaxHp + 1) - self.notify.debug('cup awarded! new max laff : %s' % newMaxHp) + self.notify.debug( "cup awarded! new max laff : %s" % newMaxHp) av.b_setMaxHp(newMaxHp) + # Also, give them a full heal av.toonUp(newMaxHp) - self.air.writeServerEvent('kartingTrophy', avId, '%s' % trophyIndex) - self.notify.debug('trophy: ' + TTLocalizer.KartTrophyDescriptions[trophyIndex]) - - self.notify.debug('NONRACE TROPHIES %s' % newTrophies) + # Write to the server log + self.air.writeServerEvent("kartingTrophy", avId, "%s" % (trophyIndex)) + self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[trophyIndex] ) + + self.notify.debug("NONRACE TROPHIES %s" % (newTrophies)) return newTrophies - + def checkHistoryForTrophyByValue(self, trophies, history, historyIndexValue, trophyReqList, trophyIndices): + #takes the index of the history we are interested in "number of wins etc.." + #compares it to a list of required wins for each stage + #and returns the new trophy indices won at each stage + newTrophies = [] - self.notify.debug('Checking History for Trophy') - self.notify.debug('Index %s Num %s ReqList %s Indices %s' % (0, historyIndexValue, trophyReqList, trophyIndices)) + self.notify.debug("Checking History for Trophy") + self.notify.debug("Index %s Num %s ReqList %s Indices %s" % (0, historyIndexValue, trophyReqList, trophyIndices)) for index in range(len(trophyIndices)): if not trophies[trophyIndices[index]]: if historyIndexValue >= trophyReqList[index]: trophies[trophyIndices[index]] = 1 newTrophies.append(trophyIndices[index]) - return newTrophies - + + def checkHistoryForTrophy(self, trophies, history, historyIndex, trophyReqList, trophyIndices): + #takes the index of the history we are interested in "number of wins etc.." + #compares it to a list of required wins for each stage + #and returns the new trophy indices won at each stage + newTrophies = [] - self.notify.debug('Checking History for Trophy') - self.notify.debug('Index %s Num %s ReqList %s Indices %s' % (historyIndex, history[historyIndex], trophyReqList, trophyIndices)) + self.notify.debug("Checking History for Trophy") + self.notify.debug("Index %s Num %s ReqList %s Indices %s" % (historyIndex, history[historyIndex], trophyReqList, trophyIndices)) for index in range(len(trophyIndices)): if not trophies[trophyIndices[index]]: if history[historyIndex] >= trophyReqList[index]: trophies[trophyIndices[index]] = 1 newTrophies.append(trophyIndices[index]) - return newTrophies - + def mergeHistories(self, historyA, historyB): newHistorySize = len(historyA) if len(historyB) > len(historyA): newHistorySize = historyB + mergedHistory = [] + for index in range(newHistorySize): mergedHistory[index] = 0 + if len(historyA) > index: if historyA[index] > mergedHistory[index]: historyA[index] = mergedHistory[index] + if len(historyB) > index: if historyB[index] > mergedHistory[index]: historyB[index] = mergedHistory[index] - + return mergedHistory - + def getNewSingleRaceHistory(self, race, avId, positionFinished): newHistory = 0 + av = self.air.doId2do.get(avId) if not av: return [] history = av.getKartingHistory() trackGenre = RaceGlobals.getTrackGenre(race.trackId) + winIndex = RaceGlobals.WinsList[trackGenre] winReqList = RaceGlobals.WonRaces + qualIndex = RaceGlobals.QualsList[trackGenre] qualReqList = RaceGlobals.QualifiedRaces + + #win history if history[winIndex] < winReqList[-1] and positionFinished == 1: history[winIndex] += 1 - self.notify.debug('New History Won!') + self.notify.debug("New History Won!") newHistory = 1 + + #qual history if history[qualIndex] < qualReqList[-1]: history[qualIndex] += 1 - self.notify.debug('New History Qualified!') + self.notify.debug("New History Qualified!") newHistory = 1 + if newHistory: return history - + def getNewCircuitHistory(self, race, avId, positionFinished): newHistory = 0 + av = self.air.doId2do.get(avId) if not av: return [] history = av.getKartingHistory() + trackGenre = RaceGlobals.getTrackGenre(race.trackId) historyIndex = RaceGlobals.CircuitWins trophyReqList = RaceGlobals.WonCircuitRaces sweepIndices = RaceGlobals.CircuitSweepsList sweepReqList = RaceGlobals.SweptCircuitRaces - self.notify.debug('getNewCircuitHistory: avId=%d positionFinished=%d history =%s' % (avId, positionFinished, history)) + + self.notify.debug("getNewCircuitHistory: avId=%d positionFinished=%d history =%s" % (avId, positionFinished, history)) + + #win history if history[historyIndex] < trophyReqList[-1] and positionFinished == 1: history[historyIndex] += 1 - self.notify.debug('New History Won!') + self.notify.debug("New History Won!") newHistory = 1 + + + #sweep history + #first determine if a sweep occured ~ if the player has maximum points swept = 0 totalPoints = sum(race.circuitPoints[avId]) if totalPoints == len(race.circuitPoints[avId]) * RaceGlobals.CircuitPoints[0]: swept = 1 + + #increment the sweep history if it's not maxed ~ reached the highest requirement if swept: if history[RaceGlobals.CircuitSweeps] < sweepReqList[-1]: if not history[RaceGlobals.CircuitSweeps]: history[RaceGlobals.CircuitSweeps] = 0 history[RaceGlobals.CircuitSweeps] += 1 - self.notify.debug('New History Swept!') + self.notify.debug("New History Swept!") newHistory = 1 + + #qual history + #first determine if the player qualified for all their circuit races qualified = 0 - self.notify.debug('qual times %s' % race.qualTimes) - self.notify.debug('avatar times %s' % race.circuitTimeList[avId]) + #check to see if the avatar has a time for each race + self.notify.debug("qual times %s" % (race.qualTimes)) + self.notify.debug("avatar times %s" % (race.circuitTimeList[avId])) + #if len(race.qualTimes) <= len(race.circuitTimeList[avId]): qualified = 1 - self.notify.debug('End Race Circuit Time List %s' % race.circuitTimeList) - self.notify.debug('check for qualify') + self.notify.debug("End Race Circuit Time List %s" % (race.circuitTimeList)) + self.notify.debug("check for qualify") for qual in race.circuitTimeList[avId]: - self.notify.debug('qual %s' % qual) + self.notify.debug("qual %s" % (qual)) if qual[1] == -1: qualified = 0 - self.notify.debug('not qualified') + self.notify.debug("not qualified") + #increment the qualify history if it's not maxed ~ reached the highest requirement if qualified: - self.notify.debug('qualified has %s needs %s' % (history[RaceGlobals.CircuitQuals], RaceGlobals.QualifiedCircuitRaces[-1])) + self.notify.debug("qualified has %s needs %s" % (history[RaceGlobals.CircuitQuals], RaceGlobals.QualifiedCircuitRaces[-1])) if history[RaceGlobals.CircuitQuals] < RaceGlobals.QualifiedCircuitRaces[-1]: history[RaceGlobals.CircuitQuals] += 1 - self.notify.debug('New History qualified!') + self.notify.debug("New History qualified!") newHistory = 1 + if newHistory: return history + - def raceOver(self, race, normalExit=True): - if race in self.races: + def raceOver(self, race, normalExit = True): + assert self.notify.debug("raceOver: race.doId=%d normalExit=%s" % (race.doId, normalExit)) + if(race in self.races): if normalExit and race.isCircuit() and not race.isLastRace(): nextTrackId = race.circuitLoop[0] + assert self.notify.debug("raceOver: creating a new race %d" % nextTrackId) continuingAvs = [] for avId in race.avIds: - if avId not in race.kickedAvIds: + if not avId in race.kickedAvIds: continuingAvs.append(avId) + assert self.notify.debug("race.kickedAvIds=%s continuingAvs = %s" % (race.kickedAvIds,continuingAvs)) lastRace = False if len(continuingAvs) > 0: - raceZone = self.createRace(nextTrackId, race.raceType, race.lapCount, continuingAvs, race.circuitLoop[1:], race.circuitPoints, race.circuitTimes, race.qualTimes, race.circuitTimeList, race.circuitTotalBonusTickets) + raceZone = self.createRace( nextTrackId, + race.raceType, + race.lapCount, + continuingAvs, + race.circuitLoop[1:], + race.circuitPoints, + race.circuitTimes, + race.qualTimes, + race.circuitTimeList, + race.circuitTotalBonusTickets) + race.sendToonsToNextCircuitRace(raceZone, nextTrackId) else: lastRace = True + assert self.notify.debug("no avatars to continue race, not creating a new one") + + + assert self.notify.debug("removing race %d" % race.doId) self.races.remove(race) + + race.requestDelete(lastRace) else: + assert self.notify.debug("removing race %d" % race.doId) self.races.remove(race) race.requestDelete() def checkForTrophies(self, place, trackId, raceType, numRacers, avId): + """Update race history and award trophies if necessary""" + + # I have tried my best to abstract this so that checking for all trophies can be done + # by this one for loop. By maintaining some strategic lists of indices in RaceGlobals + # I avoided a ridiculously long cascade of if-else statements. I apologize if it + # seems obtuse - grw + + + av = self.air.doId2do.get(avId) outHistory = av.getKartingHistory() trophies = av.getKartingTrophies() trophiesWon = [] + + # determine track type trackGenre = RaceGlobals.getTrackGenre(trackId) + # win if place == 1: historyIndex = RaceGlobals.WinsList[trackGenre] trophyIndices = RaceGlobals.AllWinsList[trackGenre] @@ -482,37 +733,56 @@ class RaceManagerAI(DirectObject.DirectObject): historyTotalList = RaceGlobals.WinsList totalTrophyIndex = RaceGlobals.TotalWins totalReq = RaceGlobals.TotalWonRaces - trophiesWon += self.checkForTrophy(place, trackId, raceType, numRacers, avId, historyIndex, trophyIndices, trophyReqList, historyTotalList, totalTrophyIndex, totalReq) + # check for win trophies + trophiesWon += self.checkForTrophy(place, trackId, raceType, numRacers, avId, + historyIndex, trophyIndices, trophyReqList, + historyTotalList, totalTrophyIndex, totalReq) + + # quals + #circuit quals cannot use checkfortrophy because it assumes you get a qual if you get anything + historyIndex = RaceGlobals.QualsList[trackGenre] trophyIndices = RaceGlobals.AllQualsList[trackGenre] trophyReqList = RaceGlobals.QualifiedRaces historyTotalList = RaceGlobals.QualsList totalTrophyIndex = RaceGlobals.TotalQuals totalReq = RaceGlobals.TotalQualifiedRaces - trophiesWon += self.checkForTrophy(place, trackId, raceType, numRacers, avId, historyIndex, trophyIndices, trophyReqList, historyTotalList, totalTrophyIndex, totalReq) + trophiesWon += self.checkForTrophy(place, trackId, raceType, numRacers, avId, + historyIndex, trophyIndices, trophyReqList, + historyTotalList, totalTrophyIndex, totalReq) + + # check for qual trophies + + #check for grandtouring if not trophies[RaceGlobals.GrandTouring]: - self.notify.debug('checking for grand touring') + self.notify.debug("checking for grand touring") + #import pdb; pdb.set_trace() best = av.getKartingPersonalBestAll() - self.notify.debug('personal best %s' % best) + self.notify.debug("personal best %s" % (best)) counter = 0 for time in best: if not time == 0: - counter += 1 - - self.notify.debug('counter %s tracks %s' % (counter, len(RaceGlobals.TrackDict))) + counter +=1 + self.notify.debug("counter %s tracks %s" % (counter, len(RaceGlobals.TrackDict))) if counter >= len(RaceGlobals.TrackDict): - trophiesWon.append(RaceGlobals.GrandTouring) + trophiesWon.append(RaceGlobals.GrandTouring) + + # set the new trophies and history if changed + if outHistory: av.b_setKartingHistory(outHistory) if len(trophiesWon): for trophy in trophiesWon: trophies[trophy] = 1 - av.b_setKartingTrophies(trophies) + trophiesWon.sort() return trophiesWon + + def checkForTrophy(self, place, trackId, raceType, numRacers, avId, historyIndex, trophyIndices, trophyReqList, + historyTotalList, totalTrophyIndex, totalReq): + """Update race history and award trophies if necessary""" - def checkForTrophy(self, place, trackId, raceType, numRacers, avId, historyIndex, trophyIndices, trophyReqList, historyTotalList, totalTrophyIndex, totalReq): av = self.air.doId2do.get(avId) if not av: return [] @@ -520,96 +790,142 @@ class RaceManagerAI(DirectObject.DirectObject): newHistory = 0 trophies = av.getKartingTrophies() newTrophies = [] + + # If the number of races the player has completed is less than the most needed for any trophy, + # then add this race to players race history (keeps us from adding up to infinity) if history[historyIndex] < trophyReqList[-1]: history[historyIndex] += 1 newHistory = 1 + + # check for all three laff point trophies for i in range(0, len(trophyReqList)): if not trophies[trophyIndices[i]]: + #self.notify.debug( "checking for trophy: %s == %s" % (history[historyIndex], trophyReqList[i])) if history[historyIndex] == trophyReqList[i]: trophies[trophyIndices[i]] = 1 newTrophies.append(trophyIndices[i]) - self.air.writeServerEvent('kartingTrophy', avId, '%s' % trophyIndices[i]) - self.notify.debug('trophy: ' + TTLocalizer.KartTrophyDescriptions[trophyIndices[i]]) + # Write to the server log + self.air.writeServerEvent("kartingTrophy", avId, "%s" % (trophyIndices[i])) + self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[trophyIndices[i]] ) break + # if this is not tourney, count toward total wins if not raceType == RaceGlobals.Circuit: + # now check for total wins style trophies if not trophies[totalTrophyIndex]: total = 0 + # calculate total since its not stored explicitly for i in historyTotalList: total += history[i] - - self.notify.debug('checking for total trophy: %s >= %s' % (total, totalReq)) + self.notify.debug( "checking for total trophy: %s >= %s" % (total, totalReq)) if total >= totalReq: trophies[totalTrophyIndex] = 1 newTrophies.append(totalTrophyIndex) - self.air.writeServerEvent('kartingTrophy', avId, '%s' % totalTrophyIndex) - self.notify.debug('trophy: ' + TTLocalizer.KartTrophyDescriptions[totalTrophyIndex]) - for i in range(1, RaceGlobals.NumTrophies // RaceGlobals.TrophiesPerCup + 1): - cupNum = trophies[:RaceGlobals.NumTrophies].count(1) // (i * RaceGlobals.TrophiesPerCup) - self.notify.debug('cupNum: %s' % cupNum) + + # Write to the server log + self.air.writeServerEvent("kartingTrophy", avId, "%s" % (totalTrophyIndex)) + self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[totalTrophyIndex] ) + + # check to see if we need a laff point + for i in range(1, RaceGlobals.NumTrophies/RaceGlobals.TrophiesPerCup + 1): + cupNum = trophies[:RaceGlobals.NumTrophies].count(1) / (i * RaceGlobals.TrophiesPerCup) + self.notify.debug( "cupNum: %s" % cupNum) trophyIndex = RaceGlobals.TrophyCups[i - 1] if cupNum and not trophies[trophyIndex]: + # award the cup! trophies[trophyIndex] = 1 newTrophies.append(trophyIndex) + # laff point boost! oldMaxHp = av.getMaxHp() newMaxHp = min(ToontownGlobals.MaxHpLimit, oldMaxHp + 1) - self.notify.debug('cup awarded! new max laff : %s' % newMaxHp) + self.notify.debug( "cup awarded! new max laff : %s" % newMaxHp) av.b_setMaxHp(newMaxHp) + # Also, give them a full heal av.toonUp(newMaxHp) - self.air.writeServerEvent('kartingTrophy', avId, '%s' % trophyIndex) - self.notify.debug('trophy: ' + TTLocalizer.KartTrophyDescriptions[trophyIndex]) - - self.notify.debug('newTrophies: %s' % newTrophies) - self.notify.debug('trophies: %s' % trophies) - self.notify.debug('kartingHistory: %s' % history) + # Write to the server log + self.air.writeServerEvent("kartingTrophy", avId, "%s" % (trophyIndex)) + self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[trophyIndex] ) + + self.notify.debug( "newTrophies: %s" % newTrophies ) + self.notify.debug( "trophies: %s" % trophies ) + self.notify.debug( "kartingHistory: %s" % history ) + + ## set the new trophies and history if changed if newHistory: av.b_setKartingHistory(history) + return newTrophies + # + # personal best methods + # def checkPersonalBest(self, trackId, time, raceType, numRacers, avId): + """Check time against top three personal best times for each track.""" av = simbase.air.doId2do.get(avId) + # TODO: what do we do if the av goes away? if av: bestTimes = av.getKartingPersonalBestAll() trackIndex = RaceGlobals.TrackIds.index(trackId) bestTime = bestTimes[trackIndex] - self.notify.debug('thisTime: %s, bestTime: %s' % (time, bestTime)) - if bestTime == 0.0 or time < bestTime: + self.notify.debug( "thisTime: %s, bestTime: %s" % (time, bestTime) ) + if (bestTime == 0.0) or (time < bestTime): bestTimes[trackIndex] = time - self.notify.debug('new personal best!') + self.notify.debug( "new personal best!" ) av.b_setKartingPersonalBest(bestTimes) - self.notify.debug('personal best: %s' % bestTimes) - + self.notify.debug( "personal best: %s" % bestTimes ) + + # + # high score file writing/reading methods + # + def checkTimeRecord(self, trackId, time, raceType, numRacers, avId): + """Check a time against current shard record. Returns bonus tickets won if any.""" bonus = 0 newRecord = 0 + for period in RaceGlobals.PeriodIds: for record in range(0, RaceGlobals.NumRecordsPerPeriod): recordTime = self.trackRecords[trackId][period][record][0] if time < recordTime: newRecord = 1 - self.notify.debug('new %s record!' % TTLocalizer.RecordPeriodStrings[period]) + # new record! + self.notify.debug( "new %s record!" % TTLocalizer.RecordPeriodStrings[period] ) + # TODO: alert player + # format: time, race type, num racers, racer name av = simbase.air.doId2do.get(avId) + # TODO: what do we do if the av goes away? if av: name = av.name + # insert new record in this spot self.trackRecords[trackId][period].insert(record, (time, raceType, numRacers, name)) + # make sure we haven't gone over record list length self.trackRecords[trackId][period] = self.trackRecords[trackId][period][:RaceGlobals.NumRecordsPerPeriod] + # notify the leaderboards of the update self.updateLeaderboards(trackId, period) bonus = RaceGlobals.PeriodDict[period] - self.air.writeServerEvent('kartingRecord', avId, '%s|%s|%s' % (period, trackId, time)) + + # keep a log for contests, etc. + self.air.writeServerEvent("kartingRecord", avId, "%s|%s|%s" % (period, trackId, time)) else: - self.notify.warning('warning: av not logged in!') + self.notify.warning("warning: av not logged in!") + # no need to keep checking times break if newRecord: + # write new record out to disk + # TODO: does this need to be cached? self.updateRecordFile() - return bonus + return bonus + def updateRecordFile(self): + """Update current track record in this shard's record file""" + # notify the leader boards that there has been an update try: backup = self.filename + '.bu' if os.path.exists(self.filename): os.rename(self.filename, backup) - file = open(self.filename, 'wb') + file = open(self.filename, 'w') file.seek(0) pickle.dump(self.trackRecords, file) file.close() @@ -617,122 +933,176 @@ class RaceManagerAI(DirectObject.DirectObject): os.remove(backup) except EnvironmentError: self.notify.warning(str(sys.exc_info()[1])) - + def getFilename(self): - return '%s%s.trackRecords' % (self.air.dataFolder, self.shard) + """Compose the track record filename""" + return "%s%s.trackRecords" % (self.serverDataFolder, self.shard) def loadRecords(self): + """Load track record data from default location""" try: - file = open(self.filename + '.bu', 'rb') + # Try to open the backup file: + file = open(self.filename + '.bu', 'r') + # Remove the (assumed) broken file: if os.path.exists(self.filename): os.remove(self.filename) except IOError: + # OK, there's no backup file, good. try: - file = open(self.filename, 'rb') + # Open the real file: + file = open(self.filename, 'r') except IOError: + # OK, there's no file. Grab the default times. return self.getRecordTimes() - file.seek(0) records = self.loadFrom(file) file.close() + + #check for new tracks for trackId in RaceGlobals.TrackIds: - if trackId not in records: + if not records.has_key(trackId): records[trackId] = {} + # for each recording period (daily, etc.) for i in RaceGlobals.PeriodIds: records[trackId][i] = [] + # for each top score for j in range(0, RaceGlobals.NumRecordsPerPeriod): - records[trackId][i].append(RaceGlobals.getDefaultRecord(trackId)) + records[trackId][i].append(getDefaultRecord(trackId)) + # update all leaderboards self.resetLeaderboards() return records def loadFrom(self, file): + """Load track record data from specified file""" records = {} try: while 1: records = pickle.load(file) - except EOFError: pass - return records def getRecordTimes(self): + """ + Create a set of track records from default times. + Used when no record file can be found. + """ records = {} + # for each track for trackId in RaceGlobals.TrackIds: records[trackId] = {} + # for each recording period (daily, etc.) for i in RaceGlobals.PeriodIds: records[trackId][i] = [] + # for each top score for j in range(0, RaceGlobals.NumRecordsPerPeriod): - records[trackId][i].append(RaceGlobals.getDefaultRecord(trackId)) - + records[trackId][i].append(getDefaultRecord(trackId)) return records + # TODO: who will call this? HolidayMgrAI? def resetRecordPeriod(self, period): + """ + Reset the records for a given period. + """ + # for each track for trackId in RaceGlobals.TrackIds: + # for each top score for i in range(0, RaceGlobals.NumRecordsPerPeriod): - self.trackRecords[trackId][period][i] = RaceGlobals.getDefaultRecord(trackId) - + self.trackRecords[trackId][period][i] = getDefaultRecord(trackId) + # notify the leaderboards that the records have changed self.updateLeaderboards(trackId, period) + # save new records to disk self.updateRecordFile() + # + # leaderboard methods + # + def getRecords(self, trackId, period): return self.trackRecords[trackId][period] def updateLeaderboards(self, trackId, period): - messenger.send('UpdateRaceRecord', [(trackId, period)]) + # notify the leaderboards of the update + messenger.send("UpdateRaceRecord", [(trackId, period)]) def resetLeaderboards(self): + # usually done at startup for track in RaceGlobals.TrackIds: for period in RaceGlobals.PeriodIds: self.updateLeaderboards(track, period) +# classes for the HolidayManagerAI class KartRecordDailyResetter(HolidayBaseAI.HolidayBaseAI): - notify = DirectNotifyGlobal.directNotify.newCategory('ResistanceEventMgrAI') + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'ResistanceEventMgrAI') + PostName = 'kertRecordDailyReset' def __init__(self, air, holidayId): - HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) - + HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) + def start(self): + # let the holiday system know we started bboard.post(KartRecordDailyResetter.PostName) simbase.air.raceMgr.resetRecordPeriod(RaceGlobals.Daily) - + def stop(self): + # let the holiday system know we stopped bboard.remove(KartRecordDailyResetter.PostName) - class KartRecordWeeklyResetter(HolidayBaseAI.HolidayBaseAI): - notify = DirectNotifyGlobal.directNotify.newCategory('ResistanceEventMgrAI') + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'ResistanceEventMgrAI') + PostName = 'kartRecordWeeklyReset' def __init__(self, air, holidayId): HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) def start(self): + # let the holiday system know we started bboard.post(KartRecordWeeklyResetter.PostName) simbase.air.raceMgr.resetRecordPeriod(RaceGlobals.Weekly) def stop(self): + # let the holiday system know we stopped bboard.remove(KartRecordWeeklyResetter.PostName) - class CircuitRaceHolidayMgr(HolidayBaseAI.HolidayBaseAI): - notify = DirectNotifyGlobal.directNotify.newCategory('CircuitRaceHolidayMgr') + + notify = DirectNotifyGlobal.directNotify.newCategory( + 'CircuitRaceHolidayMgr') + PostName = 'CircuitRaceHoliday' StartStopMsg = 'CircuitRaceHolidayStartStop' def __init__(self, air, holidayId): + # Because of Silly Saturday and the Circuit Racing Event, these holidays can overlap. + # We will use a counter to determine when to actually stop and start these events so + # the two holidays don't stomp on each other. HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId) def start(self): - bboard.post(CircuitRaceHolidayMgr.PostName, True) - simbase.air.newsManager.circuitRaceStart() - messenger.send(CircuitRaceHolidayMgr.StartStopMsg) + # let the holiday system know we started + # note: the circuit race holidays can overlap, so we use a counter to control start/stop + bboard.post(CircuitRaceHolidayMgr.PostName, bboard.get(CircuitRaceHolidayMgr.PostName, 0) + 1) + + if bboard.get(CircuitRaceHolidayMgr.PostName) == 1: + # tell everyone race night is starting + simbase.air.newsManager.circuitRaceStart() + messenger.send(CircuitRaceHolidayMgr.StartStopMsg) def stop(self): - bboard.remove(CircuitRaceHolidayMgr.PostName) - simbase.air.newsManager.circuitRaceEnd() - messenger.send(CircuitRaceHolidayMgr.StartStopMsg) + # let the holiday system know we stopped + # note: the circuit race holidays can overlap, so we use a counter to control start/stop + bboard.post(CircuitRaceHolidayMgr.PostName, bboard.get(CircuitRaceHolidayMgr.PostName) - 1) + + if bboard.get(CircuitRaceHolidayMgr.PostName) == 0: + # tell everyone race night is stopping + simbase.air.newsManager.circuitRaceEnd() + messenger.send(CircuitRaceHolidayMgr.StartStopMsg)