From bf9cb79078e2fa7509af45a06f2f05158fd02199 Mon Sep 17 00:00:00 2001 From: Open Toontown <57279094+opentoontown@users.noreply.github.com> Date: Sat, 17 Dec 2022 01:58:59 -0500 Subject: [PATCH] ai: No longer crashes immediately --- toontown/ai/ToontownAIRepository.py | 40 +- toontown/ai/WelcomeValleyManagerAI.py | 508 ++++++++++++++- toontown/building/DistributedBuildingMgrAI.py | 24 +- .../building/DistributedToonHallInteriorAI.py | 45 +- .../building/DistributedToonInteriorAI.py | 118 ++-- toontown/fishing/DistributedFishingPondAI.py | 161 ++++- .../fishing/DistributedFishingTargetAI.py | 76 ++- toontown/racing/DistributedGagAI.py | 65 +- toontown/racing/DistributedKartPadAI.py | 172 ++++- toontown/racing/DistributedLeaderBoardAI.py | 200 +++++- toontown/racing/DistributedProjectileAI.py | 77 ++- toontown/racing/DistributedRaceAI.py | 595 ++++++++++++------ toontown/racing/DistributedRacePadAI.py | 498 +++++++++++---- toontown/racing/DistributedStartingBlockAI.py | 265 +++++--- toontown/racing/DistributedVehicleAI.py | 259 ++++++-- toontown/racing/DistributedViewPadAI.py | 161 +++-- toontown/racing/Racer.py | 62 +- toontown/safezone/DistributedFishingSpotAI.py | 220 ++++++- 18 files changed, 2827 insertions(+), 719 deletions(-) diff --git a/toontown/ai/ToontownAIRepository.py b/toontown/ai/ToontownAIRepository.py index fab2a5d..b9ffeff 100644 --- a/toontown/ai/ToontownAIRepository.py +++ b/toontown/ai/ToontownAIRepository.py @@ -167,25 +167,15 @@ class ToontownAIRepository(AIDistrict): self.setTimeWarning(simbase.config.GetFloat('aimsg-time-warning', 4)) self.dnaSearchPath = DSearchPath() - if os.getenv('TTMODELS'): - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_3.5/dna')) - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_4/dna')) - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_5/dna')) - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_5.5/dna')) - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_6/dna')) - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_8/dna')) - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_9/dna')) - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_10/dna')) - self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_11/dna')) - - # In the publish environment, TTMODELS won't be on the model - # path by default, so we always add it there. In the dev - # environment, it'll be on the model path already, but it - # doesn't hurt to add it again. - getModelPath().appendDirectory(Filename.expandFrom("$TTMODELS")) - else: - self.dnaSearchPath.appendDirectory(Filename('.')) - self.dnaSearchPath.appendDirectory(Filename('ttmodels/src/dna')) + self.dnaSearchPath.appendDirectory('resources/phase_3.5/dna') + self.dnaSearchPath.appendDirectory('resources/phase_4/dna') + self.dnaSearchPath.appendDirectory('resources/phase_5/dna') + self.dnaSearchPath.appendDirectory('resources/phase_5.5/dna') + self.dnaSearchPath.appendDirectory('resources/phase_6/dna') + self.dnaSearchPath.appendDirectory('resources/phase_8/dna') + self.dnaSearchPath.appendDirectory('resources/phase_9/dna') + self.dnaSearchPath.appendDirectory('resources/phase_10/dna') + self.dnaSearchPath.appendDirectory('resources/phase_11/dna') # Initialize our query context. self.__queryEstateContext = 0 @@ -636,7 +626,7 @@ class ToontownAIRepository(AIDistrict): if ((isinstance(dnaGroup, DNAGroup)) and # If it is a DNAGroup, and the name has party_gate, count it - (string.find(dnaGroup.getName(), 'party_gate') >= 0)): + (dnaGroup.getName().find('party_gate') >= 0)): # Here's a party hat! ph = DistributedPartyGateAI.DistributedPartyGateAI(self) ph.generateWithRequired(zoneId) @@ -670,7 +660,7 @@ class ToontownAIRepository(AIDistrict): if ((isinstance(dnaGroup, DNAGroup)) and # If it is a DNAGroup, and the name starts with fishing_pond, count it - (string.find(dnaGroup.getName(), 'fishing_pond') >= 0)): + (dnaGroup.getName().find('fishing_pond') >= 0)): # Here's a fishing pond! fishingPondGroups.append(dnaGroup) fp = DistributedFishingPondAI.DistributedFishingPondAI(self, area) @@ -705,7 +695,7 @@ class ToontownAIRepository(AIDistrict): for i in range(dnaPondGroup.getNumChildren()): dnaGroup = dnaPondGroup.at(i) if ((isinstance(dnaGroup, DNAProp)) and - (string.find(dnaGroup.getCode(), 'fishing_spot') >= 0)): + (dnaGroup.getCode().find('fishing_spot') >= 0)): # Here's a fishing spot! pos = dnaGroup.getPos() hpr = dnaGroup.getHpr() @@ -720,7 +710,7 @@ class ToontownAIRepository(AIDistrict): def findRacingPads(self, dnaGroup, zoneId, area, overrideDNAZone = 0, type = 'racing_pad'): racingPads = [] racingPadGroups = [] - if ((isinstance(dnaGroup, DNAGroup)) and (string.find(dnaGroup.getName(), type) >= 0)): + if ((isinstance(dnaGroup, DNAGroup)) and (dnaGroup.getName().find(type) >= 0)): racingPadGroups.append(dnaGroup) if (type == 'racing_pad'): nameInfo = dnaGroup.getName().split('_') @@ -789,7 +779,7 @@ class ToontownAIRepository(AIDistrict): dnaGroup = dnaRacingPadGroup.at(i) # TODO - check if DNAProp instance - if ((string.find(dnaGroup.getName(), 'starting_block') >= 0)): + if ((dnaGroup.getName().find('starting_block') >= 0)): padLocation = dnaGroup.getName().split('_')[2] pos = dnaGroup.getPos() hpr = dnaGroup.getHpr() @@ -809,7 +799,7 @@ class ToontownAIRepository(AIDistrict): Find and return leader boards ''' leaderBoards = [] - if (string.find(dnaPool.getName(), 'leaderBoard') >= 0): + if (dnaPool.getName().find('leaderBoard') >= 0): #found a leader board pos = dnaPool.getPos() hpr = dnaPool.getHpr() diff --git a/toontown/ai/WelcomeValleyManagerAI.py b/toontown/ai/WelcomeValleyManagerAI.py index c937b13..a213c0e 100644 --- a/toontown/ai/WelcomeValleyManagerAI.py +++ b/toontown/ai/WelcomeValleyManagerAI.py @@ -1,42 +1,488 @@ +from pandac.PandaModules import * + +from direct.distributed import DistributedObjectAI from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI - -from toontown.hood import ZoneUtil -from toontown.hood.GSHoodDataAI import GSHoodDataAI -from toontown.hood.TTHoodDataAI import TTHoodDataAI from toontown.toonbase import ToontownGlobals +from toontown.hood import ZoneUtil +from toontown.hood import TTHoodDataAI +from toontown.hood import GSHoodDataAI +from direct.task import Task +import random + +# These are the population thresholds we use to balance our automatic +# creation of WelcomeValleys. + +# The minimum number of people that should be in the playground before +# we start to phase out the hood. +PGminimum = 1 + +# The "stable" watermark; the first time we reach this number of +# people in the playground, we consider the hood to be in a stable +# state. +PGstable = 15 + +# The maximum number of people in the playground before we stop adding +# people to the hood. +PGmaximum = 20 + +# How often, in seconds, to report the current WelcomeValley state to +# the log. +LogInterval = 300 -class WelcomeValleyManagerAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('WelcomeValleyManagerAI') +# The basic balancing algorithm for creating and destroying +# WelcomeValley hoods is as follows. - def requestZoneIdMessage(self, zoneId, context): +# There are three classes of hoods: New, Stable, and Removing. +# At any given time, there may be either zero or one New hoods, any +# number of Stable hoods, and any number of Removing hoods. +# Initially there are no hoods. + +# When a new avatar arrives, it will be assigned to the New hood if +# there is one; otherwise, it will be assigned to the Stable hood with +# the smallest playground population, unless there are no Stable hoods +# with a population less than PGmaximum (in which case a New hood will +# be created). + +# When a New hood is created, it will continue to be considered New +# (and thus receive all newly arriving avatars), until its playground +# population reaches PGstable, at which point the hood is moved into +# the Stable pool. + +# If at any point the playground population of a hood in the Stable +# pool decreases below PGminimum (and it is not the only hood +# remaining), it is moved to the Removing pool. A suitable +# replacement hood is chosen using the algorithm for a new avatar, +# above, and this new hood is associated with the Removing hood. Any +# avatar that requests a zone change to or within a Removing hood is +# instead redirected the hood's replacement. (Note that clients who +# are teleporting to a friend in a Removing hood do not request this +# zone change via the AI, and so will not be redirected to a different +# hood--they will still arrive in the same hood with their friend.) +# For the purposes of balancing, we consider the replacement hood +# immediately contains its population plus that of the Removing +# hood. + +# Finally, if the total population of any hood reaches zero, the hood +# is completely removed. + +# There are a few considerations that have led to this algorithm. +# Firstly, we want to minimize the amount of time a playground is +# nearly empty; you should always be able to enter the game and see +# several people in the playground. Thus, we aggressively fill a New +# hood up quickly, instead of distributing new avatars evenly among +# all available hoods; and once we decide to remove a hood we +# aggressively move avatars off it at the first opportunity. +# Secondly, we would like to keep avatars together whenever possible; +# thus, when we decide to remove a hood, we choose only one hood to be +# its replacement, and all avatars are moved from the source hood to +# the same replacement hood (even if this will result in an overfull +# replacement hood). + + +class WelcomeValleyManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("WelcomeValleyManagerAI") + + def __init__(self, air): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + + self.welcomeValleyAllocator = UniqueIdAllocator( + ToontownGlobals.WelcomeValleyBegin // 2000, + ToontownGlobals.WelcomeValleyEnd // 2000 - 1) + self.welcomeValleys = {} + self.avatarZones = {} + + self.newHood = None + self.stableHoods = [] + self.removingHoods = [] + + def generate(self): + DistributedObjectAI.DistributedObjectAI.generate(self) + + if simbase.config.GetBool('report-welcome-valleys', 0): + self.doReportLater() + + def delete(self): + name = self.taskName("WelcomeValleyLog") + taskMgr.remove(name) + self.ignoreAll() + DistributedObjectAI.DistributedObjectAI.delete(self) + +# now done locally on the AI +## def clientSetZone(self, zoneId): +## """ +## This is used by the client to inform the AI which zone he is +## going to. It is mainly used to balance the WelcomeValley zones, +## so the AI can know how many avatars are in each WelcomeValley. +## """ +## avId = self.air.getAvatarIdFromSender() +## lastZoneId = self.avatarSetZone(avId, zoneId) + +## # Temporary kludge to ensure ghost mode doesn't remain on +## # longer than it should--that's probably the result of an +## # unintended bug. If ghost mode is on, we turn it off +## # whenever the client switches zones. Note that this is not a +## # real solution, since a hacked client can simply not send the +## # clientSetZone messages. +## avatar = self.air.doId2do.get(avId) +## if avatar and avatar.ghostMode: +## self.air.writeServerEvent('suspicious', avId, 'has ghost mode %s transitioning from zone %s to %s' % (avatar.ghostMode, lastZoneId, zoneId)) +## if avatar.ghostMode == 1: +## avatar.b_setGhostMode(0) + +## if avatar and avatar.cogIndex >= 0: +## if (lastZoneId != 11100 and lastZoneId != 12100 and lastZoneId != 13100 ) or \ +## (zoneId < 61000 and zoneId != 11000 and zoneId != 12000 and zoneId != 13000): +## self.air.writeServerEvent('suspicious', avId, 'has cogIndex %s transitioning from zone %s to %s' % (avatar.cogIndex, lastZoneId, zoneId)) +## avatar.b_setCogIndex(-1) + + def toonSetZone(self, avId, zoneId): + lastZoneId = self.avatarSetZone(avId, zoneId) + + # Temporary kludge to ensure ghost mode doesn't remain on + # longer than it should--that's probably the result of an + # unintended bug. If ghost mode is on, we turn it off + # whenever the client switches zones. Note that this is not a + # real solution, since a hacked client can simply not send the + # clientSetZone messages. + avatar = self.air.doId2do.get(avId) + if avatar and avatar.ghostMode: + if avatar.ghostMode == 1: + avatar.b_setGhostMode(0) + + if avatar and avatar.cogIndex >= 0: + if (lastZoneId != 11100 and lastZoneId != 12100 and lastZoneId != 13100 ) or \ + (zoneId < 61000 and zoneId != 11000 and zoneId != 12000 and zoneId != 13000): + avatar.b_setCogIndex(-1) + + def avatarSetZone(self, avId, zoneId): + lastZoneId = self.avatarZones.get(avId) + if lastZoneId == zoneId: + return lastZoneId + + if zoneId == None: + self.notify.debug("Avatar %s has left the shard." % (avId)) + del self.avatarZones[avId] + self.ignore(self.air.getAvatarExitEvent(avId)) + else: + self.notify.debug("Avatar %s is now in zone %s." % (avId, zoneId)) + + # First, add the avatar into his/her new zone. We must do + # this first so we don't risk momentarily bringing the + # hood population to 0. + hoodId = ZoneUtil.getHoodId(zoneId) + + # if this is a GS hoodId, just grab the TT hood + if (hoodId % 2000) < 1000: + hood = self.welcomeValleys.get(hoodId) + else: + hood = self.welcomeValleys.get(hoodId - 1000) + + if hood: + hood[0].incrementPopulation(zoneId, 1) + if (hood == self.newHood) and hood[0].getPgPopulation() >= PGstable: + # This is now a stable hood. + self.__newToStable(hood) + + self.avatarZones[avId] = zoneId + + if lastZoneId == None: + # This is the first time we have heard from this avatar. + self.acceptOnce(self.air.getAvatarExitEvent(avId), + self.avatarLogout, extraArgs=[avId]) + else: + # Now, remove the avatar from his/her previous zone. + lastHoodId = ZoneUtil.getHoodId(lastZoneId) + + # if this is a GS hoodId, just grab the TT hood + if (lastHoodId % 2000) < 1000: + hood = self.welcomeValleys.get(lastHoodId) + else: + hood = self.welcomeValleys.get(lastHoodId - 1000) + + if hood: + hood[0].incrementPopulation(lastZoneId, -1) + if hood[0].getHoodPopulation() == 0: + self.__hoodIsEmpty(hood) + + elif (hood != self.newHood) and not hood[0].hasRedirect() and \ + hood[0].getPgPopulation() < PGminimum: + self.__stableToRemoving(hood) + + return lastZoneId + + def avatarLogout(self, avId): + self.avatarSetZone(avId, None) + + def makeNew(self, hoodId): + # Makes the indicated hoodId new, if possible. Used for magic + # word purposes only. Returns a string describing the action. + hood = self.welcomeValleys.get(hoodId) + if hood == None: + return "Hood %s is unknown." % (hoodId) + if hood == self.newHood: + return "Hood %s is already new." % (hoodId) + if self.newHood != None: + self.__newToStable(self.newHood) + + if hood in self.removingHoods: + self.removingHoods.remove(hood) + hood[0].setRedirect(None) + else: + self.stableHoods.remove(hood) + + self.newHood = hood + return "Hood %s is now New." % (hoodId) + + def makeStable(self, hoodId): + # Moves the indicated hoodId to the Stable pool, if possible. + # Used for magic word purposes only. Returns a string + # describing the action. + hood = self.welcomeValleys.get(hoodId) + if hood == None: + return "Hood %s is unknown." % (hoodId) + if hood in self.stableHoods: + return "Hood %s is already Stable." % (hoodId) + + if hood == self.newHood: + self.__newToStable(hood) + else: + self.removingHoods.remove(hood) + hood[0].setRedirect(None) + self.stableHoods.append(hood) + + return "Hood %s is now Stable." % (hoodId) + + def makeRemoving(self, hoodId): + # Moves the indicated hoodId to the Removing pool, if possible. + # Used for magic word purposes only. Returns a string + # describing the action. + hood = self.welcomeValleys.get(hoodId) + if hood == None: + return "Hood %s is unknown." % (hoodId) + if hood in self.removingHoods: + return "Hood %s is already Removing." % (hoodId) + + if hood == self.newHood: + self.__newToStable(hood) + + self.__stableToRemoving(hood) + + return "Hood %s is now Removing." % (hoodId) + + def checkAvatars(self): + # Checks that all of the avatars recorded as being logged in + # are actually still in the doId2do map, and logs out any that + # are not. Returns a list of the removed avId's. This is + # normally used for magic word purposes only. + removed = [] + for avId in self.avatarZones.keys(): + if avId not in self.air.doId2do: + # Here's one for removing. + removed.append(avId) + self.avatarLogout(avId) + + return removed + + def __newToStable(self, hood): + # This New hood's population has reached the stable limit; + # mark it as a Stable hood. + self.notify.info("Hood %s moved to Stable." % (hood[0].zoneId)) + + assert(hood == self.newHood) + self.newHood = None + self.stableHoods.append(hood) + + def __stableToRemoving(self, hood): + # This hood's population has dropped too low; + # schedule it for removal. + self.notify.info("Hood %s moved to Removing." % (hood[0].zoneId)) + + assert(hood in self.stableHoods) + self.stableHoods.remove(hood) + replacementHood = self.chooseWelcomeValley(allowCreateNew = 0) + if replacementHood == None: + # Hmm, we couldn't find a suitable + # replacement, so just keep this one. + self.stableHoods.append(hood) + else: + hood[0].setRedirect(replacementHood) + self.removingHoods.append(hood) + + def __hoodIsEmpty(self, hood): + self.notify.info("Hood %s is now empty." % (hood[0].zoneId)) + + replacementHood = hood[0].replacementHood + self.destroyWelcomeValley(hood) + + # Also check the hood this one is redirecting to; we might + # have just emptied it too. + if replacementHood and replacementHood[0].getHoodPopulation() == 0: + self.__hoodIsEmpty(replacementHood) + + + + def avatarRequestZone(self, avId, origZoneId): + # This services a redirect-zone request for a particular + # avatar. The client is requesting to go to the indicated + # zoneId, which should be a WelcomeValley zoneId; the AI + # should choose which particular WelcomeValley to direct the + # client to. + + if not ZoneUtil.isWelcomeValley(origZoneId): + # All requests for static zones are returned unchanged. + return origZoneId + + origHoodId = ZoneUtil.getHoodId(origZoneId) + hood = self.welcomeValleys.get(origHoodId) + if not hood: + # If we don't know this hood, choose a new one. + hood = self.chooseWelcomeValley() + + if not hood: + self.notify.warning("Could not create new WelcomeValley hood for avatar %s." % (avId)) + zoneId = ZoneUtil.getCanonicalZoneId(origZoneId) + else: + # use TTC hoodId + hoodId = hood[0].getRedirect().zoneId + zoneId = ZoneUtil.getTrueZoneId(origZoneId, hoodId) + + # Even though the client might choose not to go to the + # indicated zoneId for some reason, we will consider the + # client as having gone there immediately, for the purposes of + # balancing. If the client goes somewhere else instead, it + # will tell us that and we can correct this. + self.avatarSetZone(avId, zoneId) + + return zoneId + + + def requestZoneIdMessage(self, origZoneId, context): + """ + + This message is sent from the client to request a new zoneId + for a transition to WelcomeValley. + + """ avId = self.air.getAvatarIdFromSender() - if zoneId == 0: - zoneId = ToontownGlobals.WelcomeValleyBegin + zoneId = self.avatarRequestZone(avId, origZoneId) + + self.sendUpdateToAvatarId(avId, "requestZoneIdResponse", + [zoneId, context]) - self.toonSetZone(avId, zoneId) - self.sendUpdateToAvatarId(avId, 'requestZoneIdResponse', [zoneId, context]) - def toonSetZone(self, doId, zoneId): - event = self.staticGetLogicalZoneChangeEvent(doId) - inWelcomeValley = self.isAccepting(event) - if not ZoneUtil.isDynamicZone(zoneId): - if ZoneUtil.isWelcomeValley(zoneId) and not inWelcomeValley: - self.air.districtStats.b_setNewAvatarCount(self.air.districtStats.getNewAvatarCount() + 1) - self.accept(event, lambda newZoneId, _: self.toonSetZone(doId, newZoneId)) - self.accept(self.air.getAvatarExitEvent(doId), self.toonSetZone, - extraArgs=[doId, ToontownGlobals.ToontownCentral]) - elif (not ZoneUtil.isWelcomeValley(zoneId)) and inWelcomeValley: - self.air.districtStats.b_setNewAvatarCount(self.air.districtStats.getNewAvatarCount() - 1) - self.ignore(event) - self.ignore(self.air.getAvatarExitEvent(doId)) + def chooseWelcomeValley(self, allowCreateNew = 1): + # Picks a hood to assign a new avatar to. If allowCreateNew + # is 1, a new hood may be created if necessary. - def createWelcomeValleyZones(self): - self.notify.info('Creating Welcome Valley zones...') + # First, if we have a New hood, prefer that one. + if self.newHood: + return self.newHood - # Toontown Central - self.air.generateHood(TTHoodDataAI, 22000) + # Next, choose the Stable hood with the smallest playground + # population. + bestHood = None + bestPopulation = None + for hood in self.stableHoods: + population = hood[0].getPgPopulation() + if bestHood == None or population < bestPopulation: + bestHood = hood + bestPopulation = population - # Goofy Speedway - self.air.generateHood(GSHoodDataAI, 23000) + # Unless there are no hoods with a small-enough population, in + # which case we create another New hood. + if allowCreateNew and (bestHood == None or bestPopulation >= PGmaximum): + self.newHood = self.createWelcomeValley() + if self.newHood: + self.notify.info("Hood %s is New." % self.newHood[0].zoneId) + return self.newHood + + return bestHood + + def createWelcomeValley(self): + # Creates new copy of ToontownCentral and Goofy Speedway and returns + # thier HoodDataAI. Returns None if no new hoods can be created. + + index = self.welcomeValleyAllocator.allocate() + if index == -1: + return None + + # TTC + ttHoodId = index * 2000 + ttHood = TTHoodDataAI.TTHoodDataAI(self.air, ttHoodId) + self.air.startupHood(ttHood) + + # GS + gsHoodId = index * 2000 + 1000 + gsHood = GSHoodDataAI.GSHoodDataAI(self.air, gsHoodId) + self.air.startupHood(gsHood) + + # both hoods are stored in a tuple and referenced by the TTC hoodId + self.welcomeValleys[ttHoodId] = (ttHood, gsHood) + + # create a pond bingo manager ai for the new WV + if simbase.wantBingo: + self.notify.info('creating bingo mgr for Welcome Valley %s' % ttHoodId) + self.air.createPondBingoMgrAI(ttHood) + + return (ttHood, gsHood) + + def destroyWelcomeValley(self, hood): + hoodId = hood[0].zoneId + assert((hoodId % 2000) == 0) + + del self.welcomeValleys[hoodId] + self.welcomeValleyAllocator.free(hoodId / 2000) + self.air.shutdownHood(hood[0]) + self.air.shutdownHood(hood[1]) + + if self.newHood == hood: + self.newHood = None + elif hood in self.removingHoods: + self.removingHoods.remove(hood) + elif hood in self.stableHoods: + self.stableHoods.remove(hood) + + def doReportLater(self): + name = self.taskName("WelcomeValleyLog") + taskMgr.remove(name) + taskMgr.doMethodLater(LogInterval, self.doReportTask, name) + + def doReportTask(self, task): + self.reportWelcomeValleys() + self.doReportLater() + return Task.done + + def getAvatarCount(self): + # Players + # the Welcome Valley hoods. + if simbase.fakeDistrictPopulations: + return 0 + answer = 0 + hoodIds = self.welcomeValleys.keys() + for hoodId in hoodIds: + hood = self.welcomeValleys[hoodId] + answer += hood[0].getHoodPopulation() + + return answer + + def reportWelcomeValleys(self): + # Writes a message to the log file showing the current state + # of the Welcome Valley hoods. + + self.notify.info("Current Welcome Valleys:") + hoodIds = self.welcomeValleys.keys() + hoodIds.sort() + for hoodId in hoodIds: + hood = self.welcomeValleys[hoodId] + if hood == self.newHood: + flag = "N" + elif hood in self.removingHoods: + flag = "R" + else: + flag = " " + + self.notify.info("%s %s %s/%s" % ( + hood[0].zoneId, flag, + hood[0].getPgPopulation(), hood[0].getHoodPopulation())) + diff --git a/toontown/building/DistributedBuildingMgrAI.py b/toontown/building/DistributedBuildingMgrAI.py index c7f8239..ff93013 100644 --- a/toontown/building/DistributedBuildingMgrAI.py +++ b/toontown/building/DistributedBuildingMgrAI.py @@ -88,7 +88,7 @@ class DistributedBuildingMgrAI: def isValidBlockNumber(self, blockNumber): """return true if that block refers to a real block""" assert(self.debugPrint("isValidBlockNumber(blockNumber="+str(blockNumber)+")")) - return self.__buildings.has_key(blockNumber) + return blockNumber in self.__buildings def delayedSaveTask(self, task): assert(self.debugPrint("delayedSaveTask()")) @@ -99,7 +99,7 @@ class DistributedBuildingMgrAI: 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)) + assert(blockNumber in self.__buildings) return self.__buildings[blockNumber].isSuitBlock() def getSuitBlocks(self): @@ -147,7 +147,7 @@ class DistributedBuildingMgrAI: 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)) + assert(blockNumber in self.__buildings) return self.__buildings[blockNumber].getFrontDoorPoint() def getBuildingTrack(self, blockNumber): @@ -155,12 +155,12 @@ class DistributedBuildingMgrAI: 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)) + assert(blockNumber in self.__buildings) return self.__buildings[blockNumber].track def getBuilding( self, blockNumber ): assert(self.debugPrint("getBuilding(%s)" %(str(blockNumber),))) - assert(self.__buildings.has_key(blockNumber)) + assert(blockNumber in self.__buildings) return self.__buildings[blockNumber] def setFrontDoorPoint(self, blockNumber, point): @@ -169,7 +169,7 @@ class DistributedBuildingMgrAI: building""" assert(self.debugPrint("setFrontDoorPoint(blockNumber="+str(blockNumber) +", point="+str(point)+")")) - assert(self.__buildings.has_key(blockNumber)) + assert(blockNumber in self.__buildings) return self.__buildings[blockNumber].setFrontDoorPoint(point) def getDNABlockLists(self): @@ -225,7 +225,7 @@ class DistributedBuildingMgrAI: """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)) + assert(blockNumber not in self.__buildings) building=DistributedBuildingAI.DistributedBuildingAI( self.air, blockNumber, self.branchID, self.trophyMgr) @@ -261,7 +261,7 @@ class DistributedBuildingMgrAI: """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)) + assert(blockNumber not in self.__buildings) building=DistributedAnimBuildingAI.DistributedAnimBuildingAI( self.air, blockNumber, self.branchID, self.trophyMgr) @@ -290,7 +290,7 @@ class DistributedBuildingMgrAI: def newHQBuilding(self, blockNumber): """Create a new HQ building and keep track of it.""" - assert(not self.__buildings.has_key(blockNumber)) + assert(blockNumber not in self.__buildings) dnaStore = self.air.dnaStoreMap[self.canonicalBranchID] exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber) exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID) @@ -304,7 +304,7 @@ class DistributedBuildingMgrAI: 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)) + assert(blockNumber not in self.__buildings) dnaStore = self.air.dnaStoreMap[self.canonicalBranchID] exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber) exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID) @@ -316,7 +316,7 @@ class DistributedBuildingMgrAI: 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)) + assert(blockNumber not in self.__buildings) dnaStore = self.air.dnaStoreMap[self.canonicalBranchID] exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber) exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID) @@ -334,7 +334,7 @@ class DistributedBuildingMgrAI: Return: None """ assert( self.debugPrint( "newKartShopBuilding(blockNumber=" + str( blockNumber ) + ")" ) ) - assert( not self.__buildings.has_key( blockNumber ) ) + assert( blockNumber not in self.__buildings ) dnaStore = self.air.dnaStoreMap[ self.canonicalBranchID ] diff --git a/toontown/building/DistributedToonHallInteriorAI.py b/toontown/building/DistributedToonHallInteriorAI.py index 23a9252..0bccb01 100644 --- a/toontown/building/DistributedToonHallInteriorAI.py +++ b/toontown/building/DistributedToonHallInteriorAI.py @@ -2,43 +2,56 @@ from .DistributedToonInteriorAI import * from toontown.toonbase import ToontownGlobals class DistributedToonHallInteriorAI(DistributedToonInteriorAI): + """ + DistributedToonHallInteriorAI class: + """ + + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedToonHallInteriorAI') def __init__(self, block, air, zoneId, building): DistributedToonInteriorAI.__init__(self, block, air, zoneId, building) - self.accept('ToonEnteredZone', self.logToonEntered) - self.accept('ToonLeftZone', self.logToonLeft) - + + self.accept("ToonEnteredZone", self.logToonEntered) + self.accept("ToonLeftZone", self.logToonLeft) + def logToonEntered(self, avId, zoneId): result = self.getCurPhase() if result == -1: - phase = 'not available' + phase = "not available" else: phase = str(result) - self.air.writeServerEvent('sillyMeter', avId, 'enter|%s' % phase) - + self.air.writeServerEvent('sillyMeter', avId, 'enter|%s' %phase) + def logToonLeft(self, avId, zoneId): result = self.getCurPhase() if result == -1: - phase = 'not available' + phase = "not available" else: phase = str(result) - self.air.writeServerEvent('sillyMeter', avId, 'exit|%s' % phase) - + self.air.writeServerEvent('sillyMeter', avId, 'exit|%s'%phase) + def getCurPhase(self): result = -1 enoughInfoToRun = False - if ToontownGlobals.SILLYMETER_HOLIDAY in simbase.air.holidayManager.currentHolidays and simbase.air.holidayManager.currentHolidays[ToontownGlobals.SILLYMETER_HOLIDAY] != None and simbase.air.holidayManager.currentHolidays[ToontownGlobals.SILLYMETER_HOLIDAY].getRunningState(): - if hasattr(simbase.air, 'SillyMeterMgr'): + # first see if the holiday is running, and we can get the cur phase + if ToontownGlobals.SILLYMETER_HOLIDAY in simbase.air.holidayManager.currentHolidays \ + and simbase.air.holidayManager.currentHolidays[ToontownGlobals.SILLYMETER_HOLIDAY] != None \ + and simbase.air.holidayManager.currentHolidays[ToontownGlobals.SILLYMETER_HOLIDAY].getRunningState(): + if hasattr(simbase.air, "SillyMeterMgr"): enoughInfoToRun = True else: - self.notify.debug('simbase.air does not have SillyMeterMgr') + self.notify.debug("simbase.air does not have SillyMeterMgr") else: - self.notify.debug('holiday is not running') - self.notify.debug('enoughInfoToRun = %s' % enoughInfoToRun) - if enoughInfoToRun and simbase.air.SillyMeterMgr.getIsRunning(): + self.notify.debug("holiday is not running") + self.notify.debug("enoughInfoToRun = %s" % enoughInfoToRun) + if enoughInfoToRun and \ + simbase.air.SillyMeterMgr.getIsRunning(): result = simbase.air.SillyMeterMgr.getCurPhase() return result - + def delete(self): + assert(self.debugPrint("delete()")) self.ignoreAll() DistributedToonInteriorAI.delete(self) + \ No newline at end of file diff --git a/toontown/building/DistributedToonInteriorAI.py b/toontown/building/DistributedToonInteriorAI.py index 89b39b3..7f6caeb 100644 --- a/toontown/building/DistributedToonInteriorAI.py +++ b/toontown/building/DistributedToonInteriorAI.py @@ -1,75 +1,119 @@ from toontown.toonbase.ToontownGlobals import * +""" DistributedToonInteriorAI module: contains the DistributedToonInteriorAI + class, the server side representation of a 'landmark door'.""" + + from otp.ai.AIBaseGlobal import * from direct.distributed.ClockDelta import * + import pickle from direct.directnotify import DirectNotifyGlobal from direct.fsm import ClassicFSM, State from direct.distributed import DistributedObjectAI from direct.fsm import State from toontown.toon import NPCToons -from toontown.toon.ToonDNA import ToonDNA class DistributedToonInteriorAI(DistributedObjectAI.DistributedObjectAI): + """ + DistributedToonInteriorAI class: + """ + + if __debug__: + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedToonInteriorAI') def __init__(self, block, air, zoneId, building): + """blockNumber: the landmark building number (from the name)""" + #self.air=air DistributedObjectAI.DistributedObjectAI.__init__(self, air) - self.block = block - self.zoneId = zoneId - self.building = building + self.block=block + self.zoneId=zoneId + self.building=building + assert(self.debugPrint( + "DistributedToonInteriorAI(air=%s, zoneId=%s, building=%s)" + %(air, zoneId, building))) + + # Make any npcs that may be in this interior zone + # If there are none specified, this will just be an empty list self.npcs = NPCToons.createNpcsInZone(air, zoneId) - self.fsm = ClassicFSM.ClassicFSM('DistributedToonInteriorAI', [ - State.State('toon', self.enterToon, self.exitToon, [ - 'beingTakenOver']), - State.State('beingTakenOver', self.enterBeingTakenOver, self.exitBeingTakenOver, []), - State.State('off', self.enterOff, self.exitOff, [])], 'toon', 'off') + + # TODO + #for i in range(len(self.npcs)): + # npc = self.npcs[i] + # npc.d_setIndex(i) + + self.fsm = ClassicFSM.ClassicFSM('DistributedToonInteriorAI', + [State.State('toon', + self.enterToon, + self.exitToon, + ['beingTakenOver']), + State.State('beingTakenOver', + self.enterBeingTakenOver, + self.exitBeingTakenOver, + []), + State.State('off', + self.enterOff, + self.exitOff, + []), + ], + # Initial State + 'toon', + # Final State + 'off', + ) self.fsm.enterInitialState() def delete(self): + assert(self.debugPrint("delete()")) self.ignoreAll() for npc in self.npcs: npc.requestDelete() - del self.npcs del self.fsm del self.building DistributedObjectAI.DistributedObjectAI.delete(self) - + def getZoneIdAndBlock(self): - r = [ - self.zoneId, self.block] + r=[self.zoneId, self.block] + assert(self.debugPrint("getZoneIdAndBlock() returning: "+str(r))) return r - - def getSavedBy(self): - savedBy = [] - for avId, name, dnaTuple in self.building.savedBy: - dna = ToonDNA() - dna.newToonFromProperties(*dnaTuple) - savedBy.append([avId, name, dna.makeNetString()]) - return savedBy - + + def getToonData(self): + assert(self.notify.debug("getToonData()")) + return pickle.dumps(self.building.savedBy, 1) + def getState(self): - r = [ - self.fsm.getCurrentState().getName(), globalClockDelta.getRealNetworkTime()] + r=[self.fsm.getCurrentState().getName(), + globalClockDelta.getRealNetworkTime()] + assert(self.debugPrint("getState() returning: "+str(r))) return r - + def setState(self, state): + assert(self.debugPrint("setState("+str(state)+")")) self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()]) self.fsm.request(state) - + def enterOff(self): - pass - + assert(self.debugPrint("enterOff()")) + def exitOff(self): - pass - + assert(self.debugPrint("exitOff()")) + def enterToon(self): - pass - + assert(self.debugPrint("enterToon()")) + def exitToon(self): - pass - + assert(self.debugPrint("exitToon()")) + def enterBeingTakenOver(self): - pass - + """Kick everybody out of the building""" + assert(self.debugPrint("enterBeingTakenOver()")) + def exitBeingTakenOver(self): - pass + assert(self.debugPrint("exitBeingTakenOver()")) + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(self.__dict__.get('zoneId', '?'))+' '+message) + diff --git a/toontown/fishing/DistributedFishingPondAI.py b/toontown/fishing/DistributedFishingPondAI.py index 68754c4..5ccdcca 100644 --- a/toontown/fishing/DistributedFishingPondAI.py +++ b/toontown/fishing/DistributedFishingPondAI.py @@ -1,5 +1,160 @@ +from direct.distributed import DistributedObjectAI +from toontown.toonbase import TTLocalizer from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from . import DistributedFishingTargetAI +from . import FishingTargetGlobals +from toontown.hood import ZoneUtil +import random -class DistributedFishingPondAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedFishingPondAI') +class DistributedFishingPondAI(DistributedObjectAI.DistributedObjectAI): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedFishingPondAI") + + def __init__(self, air, area): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.notify.debug("init") + self.avId2SpotDict = {} + self.area = area + self.pondBingoMgr = None + + def generate(self): + DistributedObjectAI.DistributedObjectAI.generate(self) + self.notify.debug("generate: zoneId: %s, area: %s" % (self.zoneId, self.area)) + # Fishing Targets + self.targets = {} + for i in range(FishingTargetGlobals.getNumTargets(self.area)): + hunger = (FishingTargetGlobals.MinimumHunger + + (random.random() * (1 - FishingTargetGlobals.MinimumHunger))) + target = DistributedFishingTargetAI.DistributedFishingTargetAI(self.air, self, hunger) + target.generateWithRequired(self.zoneId) + self.targets[target.getDoId()] = target + + def delete(self): + self.notify.debug("delete") + # Delete all the targets + for target in self.targets.values(): + target.requestDelete() + del self.targets + DistributedObjectAI.DistributedObjectAI.delete(self) + + def getArea(self): + return self.area + + def addAvSpot(self, avId, spot): + self.notify.debug("addAvSpot: adding avId: %s to spot" % (avId)) + currentSpot = self.avId2SpotDict.get(avId) + if currentSpot: + self.notify.warning("addAvSpot: avId: %s already in a spot" % (avId)) + self.avId2SpotDict[avId] = spot + if simbase.wantBingo: + self.__addAvToGame(avId) + return 1 + + def removeAvSpot(self, avId, spot): + currentSpot = self.avId2SpotDict.get(avId) + if currentSpot: + if currentSpot == spot: + self.notify.debug("removeAvSpot: removing avId: %s from spot" % (avId)) + del self.avId2SpotDict[avId] + if simbase.wantBingo: + self.__removeAvFromGame(avId) + return 1 + else: + self.notify.warning("removeAvSpot: spots do not match, removing av anyways") + del self.avId2SpotDict[avId] + if simbase.wantBingo: + self.__removeAvFromGame(avId) + return 1 + else: + self.notify.warning("removeAvSpot: avId: %s not found" % (avId)) + # Really, if the avId is not in the avId2Spot Dict, then it should + # not be in the pondBingoMgr either. However, for precaution, check + # for it anyway. + if simbase.wantBingo: + self.__removeAvFromGame(avId) + return 0 + + def hitTarget(self, targetId): + avId = self.air.getAvatarIdFromSender() + av = self.air.doId2do.get(avId) + if not av: + return + + self.notify.debug("hitTarget: targetId: %s avId: %s" % (targetId, avId)) + # You must be fishing at a spot to hit a target + spot = self.avId2SpotDict.get(avId) + if not spot: + self.notify.warning("hitTarget: spot not found for avId: %s" % (avId)) + return + target = self.targets.get(targetId) + # See if the target bites + if (not target): + self.air.writeServerEvent('suspicious', targetId, 'FishingPondAI.hitTarget unknown target') + elif target.isHungry(): + self.notify.debug("hitTarget: targetId: %s is hungry" % (targetId)) + code, item = self.air.fishManager.recordCatch(avId, self.area, self.zoneId) + # make sure we didn't trip an error condition and return None + if code: + # Tell the fishing spot so it can send a movie to the client + spot.hitTarget(code, item) + else: + self.notify.debug("hitTarget: targetId: %s not hungry" % (targetId)) + + ############################################################ + # Method: setPondBingoManager + # Purpose: This method sets the reference to a + # PondBingoManagerAI instance. + # Input: pondBingoMgr - The pondBingoManagerAI object that + # is associated with the pond instance. + # Output: None + ############################################################ + def setPondBingoManager(self, pondBingoMgr): + self.pondBingoMgr = pondBingoMgr + + ############################################################ + # Method: getPondBingoManager + # Purpose: This method sets the reference to a + # PondBingoManagerAI instance. + # Input: None + # Output: pondBingoMgr - The pondBingoManagerAI object that + # is associated with the pond + # instance. + ############################################################ + def getPondBingoManager(self): + return self.pondBingoMgr + + ############################################################ + # Method: hasPondBingoManager + # Purpose: This method determines if the pond has a PBMgrAI + # and returns the result. + # Input: None + # Output: result 1 if there is a PBMgrAI or 0 + ############################################################ + def hasPondBingoManager(self): + return ((self.pondBingoMgr) and [1] or [0])[0] + + ############################################################ + # Method: __addAvToGame + # Purpose: This method tells to PondBingoManagerAI to add + # an avatar ID to the game because a client has + # entered a FishingSpot. + # Input: avId - Avatar ID of the client who entered the + # Fishing Spot. + # Output: None + ############################################################ + def __addAvToGame(self, avId): + if self.pondBingoMgr: + self.pondBingoMgr.addAvToGame(avId) + + ############################################################ + # Method: __addAvToGame + # Purpose: This method tells to PondBingoManagerAI to + # remove an avatar ID to the game because a client + # has exited a FishingSpot. + # Input: avId - Avatar ID of the client who exited the + # Fishing Spot. + # Output: None + ############################################################ + def __removeAvFromGame(self, avId): + if self.pondBingoMgr: + self.pondBingoMgr.removeAvFromGame(avId) diff --git a/toontown/fishing/DistributedFishingTargetAI.py b/toontown/fishing/DistributedFishingTargetAI.py index f485077..16f5b79 100644 --- a/toontown/fishing/DistributedFishingTargetAI.py +++ b/toontown/fishing/DistributedFishingTargetAI.py @@ -1,5 +1,75 @@ +from direct.distributed import DistributedNodeAI +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.task import Task +from . import FishingTargetGlobals +import random from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from toontown.toonbase import ToontownGlobals +import math +from direct.distributed.ClockDelta import * -class DistributedFishingTargetAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedFishingTargetAI') +class DistributedFishingTargetAI(DistributedNodeAI.DistributedNodeAI): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedFishingTargetAI") + + def __init__(self, air, pond, hunger): + DistributedNodeAI.DistributedNodeAI.__init__(self, air) + self.notify.debug("init") + self.pond = pond + self.area = self.pond.getArea() + self.hunger = hunger + # For now we are always moving + self.stateIndex = FishingTargetGlobals.MOVING + self.centerPoint = FishingTargetGlobals.getTargetCenter(self.area) + self.maxRadius = FishingTargetGlobals.getTargetRadius(self.area) + self.currentAngle = 0.0 + self.currentRadius = 0.0 + self.time = 0.0 + + def generate(self): + DistributedNodeAI.DistributedNodeAI.generate(self) + self.moveToNextPos() + + def delete(self): + taskMgr.remove(self.taskName('moveFishingTarget')) + del self.pond + DistributedNodeAI.DistributedNodeAI.delete(self) + + def getPondDoId(self): + return self.pond.getDoId() + + def getHunger(self): + return self.hunger + + def isHungry(self): + # See if we are hungry at this instant + return (random.random() <= self.hunger) + + def getCurrentPos(self): + x = (self.currentRadius * math.cos(self.currentAngle)) + self.centerPoint[0] + y = (self.currentRadius * math.sin(self.currentAngle)) + self.centerPoint[1] + z = self.centerPoint[2] + return (x, y, z) + + def getState(self): + return [self.stateIndex, self.currentAngle, self.currentRadius, + self.time, globalClockDelta.getRealNetworkTime()] + + def d_setState(self, stateIndex, angle, radius, time): + self.sendUpdate('setState', [stateIndex, angle, radius, time, + globalClockDelta.getRealNetworkTime()]) + + def moveToNextPos(self, task=None): + # Send out our current position before moving + self.d_setPos(*self.getCurrentPos()) + # Now grab a new angle and radius (polar coords) + self.currentAngle = random.random() * 360.0 + self.currentRadius = random.random() * self.maxRadius + # Pick a travel duration + self.time = 6.0 + (6.0 * random.random()) + self.d_setState(self.stateIndex, self.currentAngle, self.currentRadius, self.time) + waitTime = 1.0 + random.random() * 4.0 + taskMgr.doMethodLater(self.time + waitTime, + self.moveToNextPos, + self.taskName('moveFishingTarget')) diff --git a/toontown/racing/DistributedGagAI.py b/toontown/racing/DistributedGagAI.py index 993ea0d..a316087 100644 --- a/toontown/racing/DistributedGagAI.py +++ b/toontown/racing/DistributedGagAI.py @@ -1,19 +1,42 @@ -from direct.directnotify import DirectNotifyGlobal -from direct.distributed.ClockDelta import globalClockDelta -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from direct.distributed import DistributedObjectAI +from direct.distributed.ClockDelta import * +from pandac.PandaModules import * + +class DistributedGagAI(DistributedObjectAI.DistributedObjectAI): + def __init__(self, air, ownerId, race, activateTime, x, y, z, type): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.activateTime=activateTime + self.initTime=globalClockDelta.getFrameNetworkTime(16, 100) + self.pos=(x, y, z) + self.race=race + self.ownerId=ownerId + self.type = type + def generate(self): + DistributedObjectAI.DistributedObjectAI.generate(self) + #This is a good time to grab the starting time -class DistributedGagAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGagAI') + def announceGenerate(self): + DistributedObjectAI.DistributedObjectAI.announceGenerate(self) + print("I'm Here!!!!") - def __init__(self, air, ownerId, race, _, x, y, z, gagType): - DistributedObjectAI.__init__(self, air) - self.ownerId = ownerId - self.race = race - self.pos = (x, y, z) - self.gagType = gagType - self.initTime = globalClockDelta.getFrameNetworkTime() - self.activateTime = 0 + def delete(self): + DistributedObjectAI.DistributedObjectAI.delete(self) + + def getRace(self): + return self.race.doId + + def getPos(self): + return self.pos + + def setPos(self, x, y, z): + self.pos=(x, y, z) + + def getType(self): + return self.type + + def setType(self, type): + self.type = type def getInitTime(self): return self.initTime @@ -21,18 +44,12 @@ class DistributedGagAI(DistributedObjectAI): def getActivateTime(self): return self.activateTime - def getPos(self): - return self.pos - - def getRace(self): - return self.race.getDoId() - def getOwnerId(self): return self.ownerId - def getType(self): - return self.gagType + def hitSomebody(self, avId, timeStamp): + if self.type == 0: + taskMgr.doMethodLater(4, self.requestDelete, "deleting: "+self.uniqueName("banana"), extraArgs=[]) + elif self.type == 1: + taskMgr.doMethodLater(4, self.requestDelete, "deleting: "+self.uniqueName("pie"), extraArgs=[]) - def hitSomebody(self, avId, time): - self.race.thrownGags.remove(self) - self.requestDelete() diff --git a/toontown/racing/DistributedKartPadAI.py b/toontown/racing/DistributedKartPadAI.py index 03fad6d..2015b0f 100644 --- a/toontown/racing/DistributedKartPadAI.py +++ b/toontown/racing/DistributedKartPadAI.py @@ -1,30 +1,176 @@ +########################################################################## +# Module: DistributedKartPadAI.py +# Purpose: This class provides the basic methods and data members for +# derived classes which need to handle a number of karts for +# racing or viewing. +# Date: 6/28/05 +# Author: jjtaylor +########################################################################## + +########################################################################## +# Panda Import Modules +########################################################################## from direct.directnotify import DirectNotifyGlobal from direct.distributed.DistributedObjectAI import DistributedObjectAI +from toontown.racing.KartShopGlobals import KartGlobals +if(__debug__): + import pdb class DistributedKartPadAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedKartPadAI') + """ + Purpose: ... + """ - def __init__(self, air): + ###################################################################### + # Class Variables + ###################################################################### + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedKartPadAI") + #notify.setDebug(True) + + def __init__(self, air, area): + """ + Purpose: The __init__ Method handles the base initialization of + the DistributedKartPadAI object. + + Params: air - The Toontown AIRepository + area - The area in which the object is located. + Return: None + """ + + # Initialize the Super Class DistributedObjectAI.__init__(self, air) - self.area = None - self.startingBlocks = [] - self.index = -1 - def setArea(self, area): + # Initialize Instance variables self.area = area + self.startingBlocks = [] + self.avId2BlockDict = {} + self.waitingForMovies = False + + def delete(self): + """ + Comment + """ + for i in range(len(self.startingBlocks)): + # LIKELY SHOULD DELETE ASSOCIATED KART BLOCK + # DISTRIBUTED OBJECTS HERE. + self.startingBlocks.pop(0) + + # Perform necessary cleanup for Super Class + DistributedObjectAI.delete(self) def getArea(self): + """ + Purpose: The getArea Method returns the area for which the + object is located. + + Params: None + Return: Int - area id + """ return self.area - def addStartingBlock(self, startingBlock): - self.startingBlocks.append(startingBlock) + def addStartingBlock(self, block): + """ + Comment + """ + self.startingBlocks.append(block) - def addAvBlock(self, avId, startingBlock, paid): - pass + def addAvBlock(self, avId, block, paid): + """ + Purpose: The addAvBlock Method updates the starting block of the avatar which + has requested entry to the block. - def removeAvBlock(self, avId, startingBlock): - pass + Params: avId - the id of the avatar entering the block. + block - the Starting Block object that the avatar will enter. + Return: None + """ + self.notify.debug("addAvBlock: adding avId: %s to a starting block" %(avId)) + + # Retrieve the avatar + av = self.air.doId2do.get(avId, None) + if(av and not av.hasKart()): + self.notify.debug("Avatar %s does not own a kart, don't let him into the spot!") + return KartGlobals.ERROR_CODE.eNoKart + + # Make certain that the avatar is not currently in another block. + currentBlock = self.avId2BlockDict.get(avId, None) + if(currentBlock): + self.notify.warning("addAvBlock: avId: %s already in a block" % (avId)) + return KartGlobals.ERROR_CODE.eGeneric + + # The avatar is not currently in a kart block, which is what + # should be expected. + self.avId2BlockDict[avId] = block + self.notify.debug("RacePad %s has added Toon %s to block %s" % (self.doId, avId, block.doId)) + return KartGlobals.ERROR_CODE.success + + def removeAvBlock(self, avId, block): + """ + Purpose: The removeAvBlock Method updates the starting block of the avatar + which has requested removal from the starting block. + + Params: avId - the id of the avatar to remove from the block. + block - the starting block object that the avatar will exit. + Return: None + """ + + currentBlock = self.avId2BlockDict.get(avId, None) + if(currentBlock): + if(currentBlock == block): + self.notify.debug("removeAvBlock: removing avId %s from a starting block" % (avId)) + del self.avId2BlockDict[avId] + else: + self.notify.warning("removeAvBlock: blocks do not match, remove av anyways") + del self.avId2BlockDict[avId] + + self.notify.debug("RacePad %s has removed Toon %s from block %s" % (self.doId, avId, block.doId)) + else: + self.notify.warning("removeAvBlock: avId %s not found" % (avId)) + + def startCountdown(self, name, callback, time, params = []): + """ + Purpose: The __startCountdown Method generates a task that acts as + a timer. It calls a specified callback method after the time period + expires, and it additionally records a timestamp for when the timer + began. + + Params: name - a unique name for the task. + callback - method to handle the case when the timer expires. + time - amount of time before the timer expires. + params - extra arguments for the callback method. + Return: task + """ + + countdownTask = taskMgr.doMethodLater(time, callback, + self.taskName(name), + params) + return countdownTask + + def stopCountdown(self, task = None): + """ + Comment: + """ + if(task is not None): + taskMgr.remove(task.name) + return None + + def allMoviesDone(self): + # check all of these flags. If everyone is done + # pulling out their kart, go on to the next state + allDone = True + for block in self.startingBlocks: + if block.currentMovie: + allDone = False + + return allDone def kartMovieDone(self): - pass + # we only care if we are currently waiting for movies to finish + if not self.waitingForMovies: + return + + if self.allMoviesDone(): + self.stopCountdown(self.timerTask) + self.handleWaitTimeout('AllAboard') + + diff --git a/toontown/racing/DistributedLeaderBoardAI.py b/toontown/racing/DistributedLeaderBoardAI.py index b17e9f5..0e67dca 100644 --- a/toontown/racing/DistributedLeaderBoardAI.py +++ b/toontown/racing/DistributedLeaderBoardAI.py @@ -1,52 +1,186 @@ +########################################################################## +# Module: DistributedLeaderBoardAI.py +# Purpose: -determines what to display on the client side leaderboard +# -handles subscriptions to lists +# -handles timer to revolve leader board +# -handles updating lists that that leaderboardmanagaer indicates have changed +# Date: 6/24/05 +# Author: sabrina (sabrina@schellgames.com) +########################################################################## + +from direct.distributed import DistributedObjectAI +from direct.directnotify import DirectNotifyGlobal +from toontown.racing.RaceGlobals import * +from toontown.toonbase.TTLocalizer import * import pickle -from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +class DistributedLeaderBoardAI(DistributedObjectAI.DistributedObjectAI): + """ + The leader board class handles the display of player rankings. -from toontown.toonbase import TTLocalizer + Leader Board class was created to showcase race records in Toontown Kart racing -class DistributedLeaderBoardAI(DistributedObjectAI): + Inside the Toontown race code, when a race is done someone has to check race scores + decide if any of the latest scores is a new record for any of the types of leader lists. + + If the answer is yes, the pickled race list is updated and a message is sent which notifies any + distributed leader boards as to which leader list has been updated. The dict of leader lists + and the titles describing them is defined in TTLocalizer file. + + The distributed leader board then flags that id as needing actual update from the pickled leader + list file. When the distributed leader board needs to display that list it loads up the + new list and sends an updated message with the new list via sendupdate to the local leaderboard. + + This avoids a single leader board from loading from the pickle unnecessarily often as it waits + till the information is actually needed to update it. Could result in double access to pickle + files if more than one leader board is listening for it... perhaps when reading in an updated + pickle list could also sned message with new list so that other server side leader boards + can benefit from one leader board's work? not sure which would be more taxing. + + """ + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLeaderBoardAI') - def __init__(self, air, name, x, y, z, h, p, r): - DistributedObjectAI.__init__(self, air) - self.name = name - self.posHpr = (x, y, z, h, p, r) - self.records = {} - self.subscriptions = [] - self.currentIndex = -1 + def __init__(self, air, pName, zoneId, pSubscribeList=[], pos=(0, 0, 0), hpr=(0, 0, 0)): + """ + Setup the Leader Board. + """ + self.notify.debug("__init__: initialization of leaderboard AI: name=%s, Subscriptions=%s, pos=%s, hpr=%s"%(pName, pSubscribeList, pos, hpr)) + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.name = pName - def announceGenerate(self): - DistributedObjectAI.announceGenerate(self) - self.accept('UpdateRaceRecord', self.handleUpdateRaceRecord) - self.accept('GS_LeaderBoardSwap' + str(self.zoneId), self.updateDisplay) + #each subscription icludes: id : track title, period title, list of (time, name) + self.subscriptionDict = {} + #an ordered list to cycle through + self.subscriptionList = [] + self.changedList = [] #list to store id's of updated lists + #subscribe to all passed ids + #an id consits of a tuple (TrackID, PeriodID) + for id in pSubscribeList: + self.subscribeTo(id) + + self.posHpr = (pos[0], pos[1], pos[2], hpr[0], hpr[1], hpr[2]) + self.zoneId = zoneId + + self.curIndex = 0 #the index into the subscriptionsDict indicating which list we're on + self.start() def getName(self): return self.name def getPosHpr(self): + """ + """ return self.posHpr - def subscribeTo(self, subscription): - self.records.setdefault(subscription[0], {})[subscription[1]] = [(x[0], x[3]) for x in - self.air.raceMgr.getRecords(subscription[0], - subscription[1])] - self.subscriptions.append(subscription) + def getDisplay(self): + ''' + ''' + return pickle.dumps(self.subscriptionDict[self.curIndex], 1) - def handleUpdateRaceRecord(self, record): - trackId, period = record - if trackId not in self.records: - return - self.records[trackId][period] = [(x[0], x[3]) for x in self.air.raceMgr.getRecords(trackId, period)] + def sendNewDisplay(self): + self.notify.debug("sendNewDisplay: sending updated lb info to client") + self.sendUpdate("setDisplay", [pickle.dumps(self.subscriptionDict[self.subscriptionList[self.curIndex]], 1)]) - def updateDisplay(self): - self.currentIndex += 1 - if self.currentIndex >= len(self.subscriptions): - self.currentIndex = 0 + def start(self): + ''' + Starts listen for message to cycle through the boards subscribed lists. + Could make the listened for keyword a parameter if leaderboards on a different swap cycle - trackName = TTLocalizer.KartRace_TrackNames[self.subscriptions[self.currentIndex][0]] - periodName = TTLocalizer.RecordPeriodStrings[self.subscriptions[self.currentIndex][1]] - leaderList = self.records[self.subscriptions[self.currentIndex][0]][self.subscriptions[self.currentIndex][1]] - self.sendUpdate('setDisplay', [pickle.dumps((trackName, periodName, leaderList))]) + params: pSeconds - how many seconds between cycling + ''' + self.notify.debug("start: leaderboard cycling started on leaderboard %s"%(self.name)) + self.accept("GS_LeaderBoardSwap" + str(self.zoneId), self.__switchDisplayData) + + def stop(self): + self.notify.debug("stop: leaderboard cycling stopped on leaderboard %s"%(self.name)) + self.ignore("GS_LeaderBoardSwap" + str(self.zoneId)) + + def unsubscribeTo(self, pID): + self.notify.debug("unsubscribeTo: removing subscription %s for LB %s"%(pID, self.name)) + #if this subscription exists, remove it + self.subscriptionDict.pop(pID) + self.subscriptionList.remove(pID) + #self.ignore("UpdateRaceRecord_"+str(pID)) + self.ignore("UpdateRaceRecord") + + def subscribeTo(self, pID): + ''' + Adds this score ID to the list of ones this leader board cycles through and follows updates of + + params: pID - the id to listen for + ''' + self.notify.debug("subscribeTo: adding subscription %s for LB %s"%(pID, self.name)) + #first check if we're already subscribed + if pID in self.subscriptionList: + return + + + i = str(len(self.subscriptionList)) + self.subscriptionList.append(pID) + self.flagForUpdate(pID) + + self.subscriptionDict[pID] = [KartRace_TrackNames[pID[0]], RecordPeriodStrings[pID[1]], []] + # [(3.12, "Mizzenberry"), (3.12, "Princess Precious Flutterwink"), (3.12, "Mr. Loony Snapblooper"), (3.12, "Miss Pumpkin Floo"), + # (3.12, "Frizzle"), (3.12, "King Quibble"), (3.12, "Left Loopwinger"), (3.12, "Super Silly Susan "), + # (3.12, "Bingo Buffworks"), (3.12, "Mickey Mouse")]) + self.flagForUpdate(pID) + + self.accept("UpdateRaceRecord", self.flagForUpdate) + + def flagForUpdate(self, pID): + ''' + Flag this score list to be updated if its not already. + ''' + if pID not in self.changedList: + self.changedList.append(pID) + + + def __switchDisplayData(self): + ''' + ''' + #check that there's actually something in subscriptions list + if not self.subscriptionDict: + return + + #figure out next list id + self.curIndex = (self.curIndex + 1) % len(self.subscriptionList) + + curID = self.subscriptionList[self.curIndex] + #see if this list has been changed + if curID in self.changedList: + #if yes, query for updates + self.__updateScore(curID) + #remove from changed list + self.changedList.remove(curID) + + #now work on displaying scores + #sendUpdate LeaderBoard display list of scores + self.sendNewDisplay() + + def __updateScore(self, pID): + #update score list with ID pID by querying from server + #print str(self.subscriptionDict) + #print str(self.subscriptionDict[pID][2]) + newRecords = self.air.raceMgr.getRecords(pID[0], pID[1]) + + #"edited": that is, we're not using raceType and racerNum at this moment... just forget those + editedRecords = [] + + for record in newRecords: + editedRecords.append((record[0], record[3])) + + self.subscriptionDict[pID][2] = editedRecords + + #for testing- junk filler for records + #[(3.12, "Mizzenberry"), (3.12, "Princess Precious Flutterwink"), (3.12, "Mr. Loony Snapblooper"), (3.12, "Miss Pumpkin Floo"), + #(3.12, "Frizzle"), (3.12, "King Quibble"), (3.12, "Left Loopwinger"), (3.12, "Super Silly Susan "), + #(3.12, "Bingo Buffworks"), (3.12, "Mickey Mouse")] + + def delete(self): + self.notify.debug("delete: stopping distributedleaderboardAI %s" % (self.name)) + self.ignore("UpdateRaceRecord") + self.stop() + DistributedObjectAI.DistributedObjectAI.delete(self) diff --git a/toontown/racing/DistributedProjectileAI.py b/toontown/racing/DistributedProjectileAI.py index 1d5bcc0..2810586 100644 --- a/toontown/racing/DistributedProjectileAI.py +++ b/toontown/racing/DistributedProjectileAI.py @@ -1,5 +1,74 @@ -from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +#Okay, so what do we need functionally here? +#1. the ability for this to have distributed movement (created by AI, controlled by AI) +#2. The ability to move onto the curve +#3. The ability to move off the curve to hit a player. +from pandac.PandaModules import * +from direct.distributed import DistributedSmoothNodeAI -class DistributedProjectileAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedProjectileAI') + +class DistributedProjectileAI(DistributedSmoothNodeAI.DistributedSmoothNodeAI, NodePath): + def __init__(self, air, race, avId): + DistributedSmoothNodeAI.DistributedSmoothNodeAI.__init__(self, air) + NodePath.__init__(self, "Projectile") + self.avId=avId + self.air=air + self.race=race + self.toon=self.air.doId2do[self.avId] + + def announceGenerate(self): + DistributedSmoothNodeAI.DistributedSmoothNodeAI.announceGenerate(self) + self.name = self.uniqueName('projectile') + self.posHprBroadcastName = self.uniqueName('projectileBroadcast') + + self.geom=loader.loadModel("models/smiley") + self.geom.reparentTo(self) + self.reparentTo(self.race.geom) + + self.startPosHprBroadCast() + self.geom.setPos(self.toon.kart.getPos()) + + + self.setupPhysics() + #self.__enableCollisions() + + + def generate(self): + DistributedSmoothNodeAI.DistributedSmoothNodeAI.generate(self) + self.name = self.uniqueName('projectile') + self.posHprBroadcastName = self.uniqueName('projectileBroadcast') + + self.geom=loader.loadModel("models/smiley") + self.geom.reparentTo(self) + self.reparentTo(self.race.geom) + + self.startPosHprBroadcast() + self.setPos(self.toon.kart.getPos()) + + + self.setupPhysics() + #self.__enableCollisions() + + + def delete(self): + DistributedSmoothNodeAI.DistributedSmoothNodeAI.delete(self) + + + def getAvatar(self): + return self.avId + + def setupPhysics(self): + + ########################################################### + # Set up all the physics forces + self.physicsMgr = PhysicsManager() + integrator = LinearEulerIntegrator() + self.physicsMgr.attachLinearIntegrator(integrator) + + #create an engine force + fn = ForceNode("engine") + fnp = NodePath(fn) + fnp.reparentTo(self) + engine = LinearVectorForce(0, 0, 0) + fn.addForce(engine) + self.physicsMgr.addLinearForce(engine) + self.engine = engine diff --git a/toontown/racing/DistributedRaceAI.py b/toontown/racing/DistributedRaceAI.py index 25312f8..a00e47c 100644 --- a/toontown/racing/DistributedRaceAI.py +++ b/toontown/racing/DistributedRaceAI.py @@ -2,97 +2,151 @@ from direct.distributed import DistributedObjectAI from direct.directnotify import DirectNotifyGlobal from toontown.toonbase import ToontownGlobals from otp.otpbase.PythonUtil import nonRepeatingRandomList -from toontown.racing import DistributedGagAI, DistributedProjectileAI +from . import DistributedGagAI +from . import DistributedProjectileAI from direct.task import Task import random -from toontown.racing import Racer, RaceGlobals +import time +from . import Racer +from . import RaceGlobals from direct.distributed.ClockDelta import * +from toontown.toonbase import TTLocalizer class DistributedRaceAI(DistributedObjectAI.DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedRaceAI') - def __init__(self, air, trackId, zoneId, avIds, laps, raceType, racerFinishedFunc, raceDoneFunc, circuitLoop, circuitPoints, circuitTimes, qualTimes=[], circuitTimeList={}, circuitTotalBonusTickets={}): + notify = DirectNotifyGlobal.directNotify.newCategory('DistributedRaceAI') + #notify.setDebug(1) + + def __init__(self, air, trackId, zoneId, avIds, laps, raceType, racerFinishedFunc, raceDoneFunc, circuitLoop, circuitPoints, circuitTimes, qualTimes = [], circuitTimeList = {}, circuitTotalBonusTickets = {}): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.trackId = trackId + # infer direction (odd id's are rev) self.direction = self.trackId % 2 self.zoneId = zoneId self.racers = {} - self.avIds = [] + self.avIds=[] self.kickedAvIds = [] self.circuitPoints = circuitPoints self.circuitTimes = circuitTimes self.finishPending = [] self.flushPendingTask = None self.kickSlowRacersTask = None + #Create the list of avatars that we will potentially cull from for avId in avIds: - if avId and avId in self.air.doId2do: + if(avId) and avId in self.air.doId2do: self.avIds.append(avId) + #Create each racer info, which also generates the karts self.racers[avId] = Racer.Racer(self, air, avId, zoneId) + #At this point, we have karts on the AI self.toonCount = len(self.racers) self.startingPlaces = nonRepeatingRandomList(self.toonCount, 4) self.thrownGags = [] + self.ready = False self.setGo = False + self.racerFinishedFunc = racerFinishedFunc self.raceDoneFunc = raceDoneFunc self.lapCount = laps self.raceType = raceType - if raceType == RaceGlobals.Practice: - self.gagList = [] + if(raceType==RaceGlobals.Practice): + self.gagList=[] else: - self.gagList = [ - 0] * len(RaceGlobals.TrackDict[trackId][4]) + self.gagList = [0]*len(RaceGlobals.TrackDict[trackId][4]) + self.circuitLoop = circuitLoop self.qualTimes = qualTimes self.circuitTimeList = circuitTimeList + self.qualTimes.append(RaceGlobals.TrackDict[trackId][1]) + self.circuitTotalBonusTickets = circuitTotalBonusTickets - return + + #print("QUALTIMES %s" % (self.qualTimes)) + #print("CIRCUITLOOP %s" % (self.circuitLoop)) + #print("circuitTotalBonusTickets %s" % self.circuitTotalBonusTickets) + #import pdb; pdb.set_trace() def generate(self): DistributedObjectAI.DistributedObjectAI.generate(self) - self.notify.debug('generate %s, id=%s, ' % (self.doId, self.trackId)) + self.notify.debug('generate %s, id=%s, ' % + (self.doId, self.trackId)) + + if __debug__: + simbase.race = self + + trackFilepath = RaceGlobals.TrackDict[self.trackId][0] - taskMgr.doMethodLater(0.5, self.enableEntryBarrier, 'enableWaitingBarrier') + + #self.geom = loader.loadModel(trackFilepath) + #self.numItemPos = self.geom.findAllMatches("**/item*").getNumPaths() + + #self.positions=[] + #for i in range(4): + # strIndex=str(i+1) + # np=self.geom.find("**/start_pos_"+strIndex) + # self.positions.append([np.getX(), np.getY(), np.getZ(), 0, 0, 0]) + #count=0 + #for i in self.racers: + # self.racers[i].startingPlace=self.startingPlaces[count] + # count+=1 + # log that toons entered the race + #description = '%s|%s' % ( + # self.trackId, self.avIds) + #for avId in self.avIds: + # self.air.writeServerEvent('raceEntered', avId, description) + + taskMgr.doMethodLater(.5, self.enableEntryBarrier, "enableWaitingBarrier") def enableEntryBarrier(self, task): - self.enterRaceBarrier = self.beginBarrier('waitingForJoin', self.avIds, 60, self.b_racersJoined) - self.notify.debug('Waiting for Joins!!!!') - self.sendUpdate('waitingForJoin', []) + self.enterRaceBarrier=self.beginBarrier("waitingForJoin", self.avIds, TTLocalizer.DRAwaitingForJoin, self.b_racersJoined) + self.notify.debug("Waiting for Joins!!!!") + self.sendUpdate("waitingForJoin", []) + + + #A utility function for doing safe removals of DistributedObjects + #Mainly used for karts. def removeObject(self, object): - if object: - self.notify.debug('deleting object: %s' % object.doId) + if(object): + self.notify.debug("deleting object: %s" %object.doId) object.requestDelete() - def requestDelete(self, lastRace=True): + + def requestDelete(self, lastRace = True): self.notify.debug('requestDelete: %s' % self.doId) - self.ignoreBarrier('waitingForExit') + self.ignoreAll() + self.ignoreBarrier("waitingForExit") for i in self.thrownGags: i.requestDelete() - del self.thrownGags + if lastRace: for i in self.racers: - racer = self.racers[i] + racer=self.racers[i] self.ignore(racer.exitEvent) - if racer.kart: + if(racer.kart): racer.kart.requestDelete() - racer.kart = None - if racer.avatar: - racer.avatar.kart = None - racer.avatar = None + racer.kart=None + if(racer.avatar): + racer.avatar.kart=None + racer.avatar=None + + self.racers={} - self.racers = {} if self.flushPendingTask: taskMgr.remove(self.flushPendingTask) self.flushPendingTask = None + if self.kickSlowRacersTask: taskMgr.remove(self.kickSlowRacersTask) self.kickSlowRacersTask = None + DistributedObjectAI.DistributedObjectAI.requestDelete(self) - return + def delete(self): self.notify.debug('delete: %s' % self.doId) @@ -104,9 +158,16 @@ class DistributedRaceAI(DistributedObjectAI.DistributedObjectAI): return self.zoneId def allToonsGone(self): + # the race room objs clean themselves up; in fact, the first race + # room will call this for us when it detects that all toons have + # left self.notify.debug('allToonsGone') self.requestDelete() + ######################################### + # required-field getters + ######################################### + def getZoneId(self): return self.zoneId @@ -120,10 +181,9 @@ class DistributedRaceAI(DistributedObjectAI.DistributedObjectAI): return self.circuitLoop def getAvatars(self): - avIds = [] + avIds=[] for i in self.racers: avIds.append(i) - return avIds def getStartingPlaces(self): @@ -132,332 +192,489 @@ class DistributedRaceAI(DistributedObjectAI.DistributedObjectAI): def getLapCount(self): return self.lapCount + + ################################################ + #requestKart: + # This function is only used to set + # controlled on the kart. + ################################################# + def requestKart(self): - avId = self.air.getAvatarIdFromSender() - if avId in self.racers: - kart = self.racers[avId].kart - if kart: - kart.request('Controlled', avId) + avId=self.air.getAvatarIdFromSender() + if (avId in self.racers): + kart=self.racers[avId].kart + if(kart): + kart.request("Controlled", avId) + + + ############################################# + #Clear out players who didn't join yet + #Set up Toon/Kart linking on client + ############################################# def b_racersJoined(self, avIds): - self.ignoreBarrier('waitingForJoin') - racersOut = [] + assert self.notify.debug("b_racersJoined %s" % avIds) + self.ignoreBarrier("waitingForJoin") + + racersOut=[] for i in self.avIds: if i not in avIds: racersOut.append(i) - if len(avIds) == 0: - self.exitBarrier = self.beginBarrier('waitingForExit', self.avIds, 10, self.endRace) + if(len(avIds)==0): + #The racers are too slow. Make sure they know to leave, then exit + self.exitBarrier=self.beginBarrier("waitingForExit", self.avIds, 10, self.endRace) for i in self.avIds: self.d_kickRacer(i) - return + for i in racersOut: self.d_kickRacer(i) - self.avIds = avIds - self.waitingForPrepBarrier = self.beginBarrier('waitingForPrep', self.avIds, 30, self.b_prepForRace) - avAndKarts = [] + self.avIds=avIds + self.waitingForPrepBarrier=self.beginBarrier("waitingForPrep", self.avIds, 30, self.b_prepForRace) + avAndKarts=[] for i in self.racers: avAndKarts.append([self.racers[i].avId, self.racers[i].kart.doId]) + self.sendUpdate("setEnteredRacers", [avAndKarts]) + + ############################################## + #Clear out users who didn't make it + #request Prep state on client + ############################################## - self.sendUpdate('setEnteredRacers', [avAndKarts]) def b_prepForRace(self, avIds): - self.notify.debug('Prepping!!!') - self.ignoreBarrier('waitingForPrep') - racersOut = [] + self.notify.debug("Prepping!!!") + self.ignoreBarrier("waitingForPrep") + + racersOut=[] for i in self.avIds: if i not in avIds: racersOut.append(i) - if len(avIds) == 0: - self.exitBarrier = self.beginBarrier('waitingForExit', self.avIds, 10, self.endRace) + + if(len(avIds)==0): + self.exitBarrier=self.beginBarrier("waitingForExit", self.avIds, 10, self.endRace) for i in racersOut: self.d_kickRacer(i) - - if len(avIds) == 0: + if(len(avIds)==0): return - self.avIds = avIds + self.avIds=avIds + #first gen the gags for i in range(len(self.gagList)): self.d_genGag(i) - self.waitingForReadyBarrier = self.beginBarrier('waitingForReady', self.avIds, 20, self.b_startTutorial) - self.sendUpdate('prepForRace', []) + + self.waitingForReadyBarrier=self.beginBarrier("waitingForReady", self.avIds, 20, self.b_startTutorial) + self.sendUpdate("prepForRace", []) + + ########################################### + #Clear out any players who didn't make it, + #request Tutorial State on client + #Iris in on client + ########################################### def b_startTutorial(self, avIds): - self.ignoreBarrier('waitingForReady') - racersOut = [] + self.ignoreBarrier("waitingForReady") + + racersOut=[] for i in self.avIds: if i not in avIds: racersOut.append(i) - if len(avIds) == 0: - self.exitBarrier = self.beginBarrier('waitingForExit', self.avIds, 10, self.endRace) + if(len(avIds)==0): + self.exitBarrier=self.beginBarrier("waitingForExit", self.avIds, 10, self.endRace) + for i in racersOut: self.d_kickRacer(i) - if len(avIds) == 0: + if(len(avIds)==0): return + + # need to check and deduct tickets here. We're not really + # set up to handle errors, but at least throw a warning + # if somehow the toon got here without enough tix + # We check this here since this is when the irisIn happens... + # if they Alt-F4 after the irisIn, we will deduct for avId in avIds: av = self.air.doId2do.get(avId, None) - if not av: - self.notify.warning('b_racersJoined: Avatar not found with id %s' % avId) - elif not self.raceType == RaceGlobals.Practice: + if(not av): + self.notify.warning("b_racersJoined: Avatar not found with id %s" %(avId)) + elif not (self.raceType == RaceGlobals.Practice): + # circuit race only pays entry on the first race! if self.isCircuit() and not self.isFirstRace(): continue raceFee = RaceGlobals.getEntryFee(self.trackId, self.raceType) avTickets = av.getTickets() - if avTickets < raceFee: - self.notify.warning('b_racersJoined: Avatar %s does not own enough tickets for the race!') + + if(avTickets < raceFee): + self.notify.warning("b_racersJoined: Avatar %s does not own enough tickets for the race!") av.b_setTickets(0) else: + # Okay, toon has enough tickets so now we must subtract them. av.b_setTickets(avTickets - raceFee) - self.avIds = avIds - self.readRulesBarrier = self.beginBarrier('readRules', self.avIds, 10, self.b_startRace) - self.sendUpdate('startTutorial', []) - return + self.avIds=avIds + self.readRulesBarrier=self.beginBarrier("readRules", self.avIds, 10, self.b_startRace) + self.sendUpdate("startTutorial", []) + + ############################################## + #Start Countdown + #Request Start state on client + ############################################## def b_startRace(self, avIds): - self.ignoreBarrier('readRules') + self.ignoreBarrier("readRules") + + # has the race has been deleted for some reason? if self.isDeleted(): return - self.notify.debug('Going!!!!!!') + + self.notify.debug("Going!!!!!!") self.ignoreBarrier(self.waitingForReadyBarrier) + + #re-set this for 'winnings' self.toonCount = len(self.avIds) + + # don't start the race until the message has arrived on the client and countdown has finished self.baseTime = globalClock.getFrameTime() + 0.5 + RaceGlobals.RaceCountdown for i in self.racers: - self.racers[i].baseTime = self.baseTime + self.racers[i].baseTime=self.baseTime + self.sendUpdate("startRace", [globalClockDelta.localToNetworkTime(self.baseTime)]) - self.sendUpdate('startRace', [globalClockDelta.localToNetworkTime(self.baseTime)]) + # kickout racers who are taking too long qualTime = RaceGlobals.getQualifyingTime(self.trackId) - timeout = qualTime + 60 + 3 - self.kickSlowRacersTask = taskMgr.doMethodLater(timeout, self.kickSlowRacers, 'kickSlowRacers') + timeout = qualTime + TTLocalizer.DRAwaitingForJoin + 3 # 3 is for the 'countdown' + self.kickSlowRacersTask = taskMgr.doMethodLater(timeout, self.kickSlowRacers, "kickSlowRacers") def kickSlowRacers(self, task): + assert self.notify.debug("in kickSlowRacers") self.kickSlowRacersTask = None + + # has the race has been deleted for some reason? if self.isDeleted(): return - for racer in list(self.racers.values()): + + for racer in self.racers.values(): avId = racer.avId - av = simbase.air.doId2do.get(avId, None) + + # racers can be tagged to 'not allow timeout' + av = simbase.air.doId2do.get(avId,None) if av and not av.allowRaceTimeout: continue - if not racer.finished and avId not in self.kickedAvIds: + + if (not racer.finished) and (not avId in self.kickedAvIds): self.notify.info('Racer %s timed out - kicking.' % racer.avId) self.d_kickRacer(avId, RaceGlobals.Exit_Slow) self.ignore(racer.exitEvent) - racer.exited = True + racer.exited=True racer.finished = True - taskMgr.doMethodLater(10, self.removeObject, 'removeKart-%s' % racer.kart.doId, extraArgs=[racer.kart]) - taskMgr.remove('make %s invincible' % avId) - self.racers[avId].anvilTarget = True + taskMgr.doMethodLater(10, self.removeObject, "removeKart-%s"%racer.kart.doId, extraArgs=[racer.kart]) + + #Make them invincible in the eyes of the anvil dropper + taskMgr.remove("make %s invincible" % avId) + self.racers[avId].anvilTarget=True self.checkForEndOfRace() - return - def d_kickRacer(self, avId, reason=RaceGlobals.Exit_Barrier): - if avId not in self.kickedAvIds: + def d_kickRacer(self, avId, reason = RaceGlobals.Exit_Barrier): + if not avId in self.kickedAvIds: self.kickedAvIds.append(avId) + + # this kick will tell them they are not getting a refund if self.isCircuit() and not self.isFirstRace() and reason == RaceGlobals.Exit_Barrier: reason = RaceGlobals.Exit_BarrierNoRefund - self.sendUpdate('goToSpeedway', [self.kickedAvIds, reason]) + + self.sendUpdate("goToSpeedway", [self.kickedAvIds, reason]) + def d_genGag(self, slot): - index = random.randint(0, 5) - self.gagList[slot] = index - pos = slot - self.sendUpdate('genGag', [slot, pos, index]) + index=random.randint(0, 5) + self.gagList[slot]=index + #TODO random gen the pos from a subset of total gag positions + pos=slot + self.sendUpdate("genGag", [slot, pos, index]) + def d_dropAnvil(self, ownerId): - possibleTargets = [] + possibleTargets=[] for i in self.racers: - if not self.racers[i].anvilTarget: + #if(i != avId): + if (not self.racers[i].anvilTarget): possibleTargets.append(self.racers[i]) - while len(possibleTargets) > 1: - if possibleTargets[0].lapT <= possibleTargets[1].lapT: + while(len(possibleTargets)>1): + if(possibleTargets[0].lapT<=possibleTargets[1].lapT): possibleTargets = possibleTargets[1:] else: - possibleTargets = possibleTargets[1:] + possibleTargets[:1] - - if len(possibleTargets): - id = possibleTargets[0].avId - if id != ownerId: - possibleTargets[0].anvilTarget = True - taskMgr.doMethodLater(4, setattr, 'make %s invincible' % id, extraArgs=[self.racers[id], 'anvilTarget', False]) - self.sendUpdate('dropAnvilOn', [ownerId, id, globalClockDelta.getFrameNetworkTime()]) + possibleTargets= possibleTargets[1:] + possibleTargets[:1] + if(len(possibleTargets)): + id=possibleTargets[0].avId + if(id!=ownerId): + #if the anvil is gonna crush someone, make them invincible + #untill they unflatten + possibleTargets[0].anvilTarget=True + taskMgr.doMethodLater(4, setattr, "make %s invincible" % id, extraArgs=[self.racers[id], "anvilTarget", False]) + #This only happens when the player tries to drop on themselves + self.sendUpdate("dropAnvilOn", [ownerId, id, globalClockDelta.getFrameNetworkTime()]) def d_makeBanana(self, avId, x, y, z): - gag = DistributedGagAI.DistributedGagAI(simbase.air, avId, self, 3, x, y, z, 0) + gag=DistributedGagAI.DistributedGagAI(simbase.air, avId, self, 3, x, y, z, 0) self.thrownGags.append(gag) gag.generateWithRequired(self.zoneId) def d_launchPie(self, avId): ownerRacer = simbase.air.doId2do.get(avId, None) + #self.racers[] targetId = 0 type = 0 - targetDist = 10000 + #print("start launch pie") + #print(avId) + targetDist = 10000 #arbitrary large number + #searching for targets ahead of us for iiId in self.racers: - targetRacer = simbase.air.doId2do.get(iiId, None) + targetRacer = simbase.air.doId2do.get(iiId, None) + #print("Dist Calc") + #print(targetRacer.kart.getPos(ownerRacer.kart)) + + # some error checking to prevent frequent AI crashes if not (targetRacer and targetRacer.kart and ownerRacer and ownerRacer.kart): continue - if targetRacer.kart.getPos(ownerRacer.kart)[1] < 500 and targetRacer.kart.getPos(ownerRacer.kart)[1] >= 0 and abs(targetRacer.kart.getPos(ownerRacer.kart)[0]) < 50 and avId != iiId and targetDist > targetRacer.kart.getPos(ownerRacer.kart)[1]: + + if ((targetRacer.kart.getPos(ownerRacer.kart)[1] < 500) #getting the y value of the position + and (targetRacer.kart.getPos(ownerRacer.kart)[1] >= 0) + and (abs(targetRacer.kart.getPos(ownerRacer.kart)[0]) < 50) + and (avId != iiId) + and (targetDist > targetRacer.kart.getPos(ownerRacer.kart)[1])): targetId = iiId targetDist = targetRacer.kart.getPos(ownerRacer.kart)[1] - + #print("found target forward") + #print(iiId) + #print(avId) + #import pdb; pdb.set_trace() + #searching for targets behind us if targetId == 0: for iiId in self.racers: - targetRacer = simbase.air.doId2do.get(iiId, None) + targetRacer = simbase.air.doId2do.get(iiId, None) + #print("Dist Calc neg") + #print(targetRacer.kart.getPos(ownerRacer.kart)) + + # some error checking to prevent frequent AI crashes if not (targetRacer and targetRacer.kart and ownerRacer and ownerRacer.kart): continue - if targetRacer.kart.getPos(ownerRacer.kart)[1] > -80 and targetRacer.kart.getPos(ownerRacer.kart)[1] <= 0 and abs(targetRacer.kart.getPos(ownerRacer.kart)[0]) < 50 and avId != iiId: - targetId = iiId - self.sendUpdate('shootPiejectile', [avId, targetId, type]) - return + if ((targetRacer.kart.getPos(ownerRacer.kart)[1] > -80) #getting the y value of the position + and (targetRacer.kart.getPos(ownerRacer.kart)[1] <= 0) + and (abs(targetRacer.kart.getPos(ownerRacer.kart)[0]) < 50) + and (avId != iiId)): + targetId = iiId + #print("found target back") + #print(iiId) + #print(avId) + #import pdb; pdb.set_trace() + + #print("end launch pie") + self.sendUpdate("shootPiejectile", [avId, targetId, type]) + def d_makePie(self, avId, x, y, z): - gag = DistributedProjectileAI.DistributedProjectileAI(simbase.air, self, avId) + #gag=DistributedGagAI.DistributedGagAI(simbase.air, avId, self, 3, x, y, z, 1) + gag=DistributedProjectileAI.DistributedProjectileAI(simbase.air, self, avId) self.thrownGags.append(gag) gag.generateWithRequired(self.zoneId) def endRace(self, avIds): - if hasattr(self, 'raceDoneFunc'): + if hasattr(self, "raceDoneFunc"): self.raceDoneFunc(self, False) - def racerLeft(self, avIdFromClient): - avId = self.air.getAvatarIdFromSender() - if avId in self.racers and avId == avIdFromClient: - self.notify.debug('Removing %d from race %d' % (avId, self.doId)) - racer = self.racers[avId] - taskMgr.doMethodLater(10, self.removeObject, racer.kart.uniqueName('removeIt'), extraArgs=[racer.kart]) - if racer.avatar: - racer.avatar.kart = None - self.racers[avId].exited = True - taskMgr.remove('make %s invincible' % id) - self.racers[avId].anvilTarget = True - raceDone = True - for i in self.racers: - if not self.racers[i].exited: - raceDone = False - if raceDone: - self.notify.debug('race over, sending callback to raceMgr') - self.raceDoneFunc(self) - if avId in self.finishPending: - self.finishPending.remove(avId) - return + ####################################### + #Client->AI Functions + ####################################### + + def racerLeft(self, avIdFromClient): + avId=self.air.getAvatarIdFromSender() + if(self.racers.has_key(avId) and avId==avIdFromClient): + self.notify.debug("Removing %d from race %d" % (avId, self.doId)) + #Clear out the players kart + racer=self.racers[avId] + + taskMgr.doMethodLater(10, self.removeObject, racer.kart.uniqueName("removeIt"), extraArgs=[racer.kart]) + if(racer.avatar): + racer.avatar.kart=None + #we're not calling this here, cause if a player has left + #prematurely, they don't get their info passed to the manager + self.racers[avId].exited=True + + #Make them invincible in the eyes of the anvil dropper + taskMgr.remove("make %s invincible" % id) + self.racers[avId].anvilTarget=True + + raceDone=True + for i in self.racers: + if(not self.racers[i].exited): + raceDone=False + if(raceDone): + self.notify.debug("race over, sending callback to raceMgr") + + self.raceDoneFunc(self) + + if avId in self.finishPending: + self.finishPending.remove(avId) + def hasGag(self, slot, type, index): - avId = self.air.getAvatarIdFromSender() + avId=self.air.getAvatarIdFromSender() + print("has gag") + if index < 0 or index > (len(self.gagList) - 1): #check for cheaters + self.air.writeServerEvent('suspicious', avId, 'Player checking for non-existant karting gag index %s type %s index %s' % (slot, type, index)) + self.notify.warning("somebody is trying to check for a non-existant karting gag %s %s %s! avId: %s" % (slot, type, index, avId)) + + if slot < 0 or slot > (len(self.gagList) - 1): #crash from TTInjector hack + self.air.writeServerEvent('suspicious', avId, 'Player checking for non-existant karting gag slot %s type %s index %s' % (slot, type, index)) + self.notify.warning("somebody is trying to check for a non-existant karting gag %s %s %s! avId: %s" % (slot, type, index, avId)) + return + if avId in self.racers: - if self.racers[avId].hasGag: + if(self.racers[avId].hasGag): + #Bad thing return - if self.gagList[slot] == index: - self.gagList[slot] = None - taskMgr.doMethodLater(5, self.d_genGag, 'remakeGag-' + str(slot), extraArgs=[slot]) - self.racers[avId].hasGag = True - self.racers[avId].gagType = type + if(self.gagList[slot]==index): + self.gagList[slot]=None + taskMgr.doMethodLater(5, self.d_genGag, "remakeGag-"+str(slot), extraArgs=[slot]) + self.racers[avId].hasGag=True + self.racers[avId].gagType=type else: + #problem return - return def requestThrow(self, x, y, z): - avId = self.air.getAvatarIdFromSender() + avId=self.air.getAvatarIdFromSender() if avId in self.racers: - racer = self.racers[avId] - if racer.hasGag: - if racer.gagType == 1: + racer=self.racers[avId] + if(racer.hasGag): + if(racer.gagType==1): self.d_makeBanana(avId, x, y, z) - if racer.gagType == 2: + if(racer.gagType==2): + #self.d_announceTurbo pass - if racer.gagType == 3: + if(racer.gagType==3): self.d_dropAnvil(avId) - if racer.gagType == 4: + if(racer.gagType==4): + #self.d_makePie(avId, x, y, z) self.d_launchPie(avId) - racer.hasGag = False - racer.gagType = 0 + racer.hasGag=False + racer.gagType=0 + #TODO self.sendUpdate("threwGag", [type, avatarToHit] + + ########################################## + #Sent by players to announce their current + #time on the track + ########################################## def heresMyT(self, inputAvId, numLaps, t, timestamp): - avId = self.air.getAvatarIdFromSender() - if avId in self.racers and avId == inputAvId: + avId=self.air.getAvatarIdFromSender() + if avId in self.racers and avId==inputAvId: me = self.racers[avId] + me.setLapT(numLaps, t, timestamp) - if me.maxLap == self.lapCount and not me.finished: - me.finished = True - taskMgr.remove('make %s invincible' % id) - me.anvilTarget = True + if(me.maxLap==self.lapCount and not me.finished): + me.finished=True + + #Make them invincible in the eyes of the anvil dropper + taskMgr.remove("make %s invincible" % id) + me.anvilTarget=True + + # see if anyone's close someoneIsClose = False - for racer in list(self.racers.values()): - if not racer.exited and not racer.finished: - if me.lapT - racer.lapT < 0.15: + for racer in self.racers.values(): + if (not racer.exited) and (not racer.finished): + if (me.lapT - racer.lapT) < 0.15: someoneIsClose = True break + # add the racer to the pendingFinish array, sorted by totalTime index = 0 for racer in self.finishPending: if me.totalTime < racer.totalTime: break index += 1 - self.finishPending.insert(index, me) + if self.flushPendingTask: taskMgr.remove(self.flushPendingTask) self.flushPendingTask = None + if someoneIsClose: - task = taskMgr.doMethodLater(3, self.flushPending, self.uniqueName('flushPending')) + task = taskMgr.doMethodLater(3, self.flushPending, + self.uniqueName("flushPending")) self.flushPendingTask = task else: self.flushPending() - return - def flushPending(self, task=None): + + # we've waited long enough... flush the finishPending array + def flushPending(self, task = None): for racer in self.finishPending: self.racerFinishedFunc(self, racer) self.finishPending = [] self.flushPendingTask = None - return + + + #################################### + #TODO: Rename + #sent after a player finishes a race + #Sets their standing and winnings + #################################### def d_setPlace(self, avId, totalTime, place, entryFee, qualify, winnings, bonus, trophies, circuitPoints, circuitTime): - self.sendUpdate('setPlace', [avId, totalTime, place, entryFee, qualify, winnings, bonus, trophies, circuitPoints, circuitTime]) + self.sendUpdate("setPlace", [avId, totalTime, place, entryFee, qualify, winnings, bonus, trophies, circuitPoints, circuitTime]) - def d_setCircuitPlace(self, avId, place, entryFee, winnings, bonus, trophies): - self.sendUpdate('setCircuitPlace', [avId, place, entryFee, winnings, bonus, trophies]) + def d_setCircuitPlace(self, avId, place, entryFee, winnings, bonus,trophies): + self.sendUpdate("setCircuitPlace", [avId, place, entryFee, winnings, bonus,trophies]) def d_endCircuitRace(self): - self.sendUpdate('endCircuitRace') + self.sendUpdate("endCircuitRace") + + #################################### + #Racer.py calls this function on + #an unexpected exit + #################################### def unexpectedExit(self, avId): - self.notify.debug('racer disconnected: %s' % avId) - racer = self.racers.get(avId, None) - if racer: - self.sendUpdate('racerDisconnected', [avId]) + self.notify.debug("racer disconnected: %s" %avId) + racer=self.racers.get(avId, None) + if(racer): + self.sendUpdate("racerDisconnected", [avId]) self.ignore(racer.exitEvent) - racer.exited = True - taskMgr.doMethodLater(10, self.removeObject, 'removeKart-%s' % racer.kart.doId, extraArgs=[racer.kart]) - taskMgr.remove('make %s invincible' % id) - self.racers[avId].anvilTarget = True + racer.exited=True + taskMgr.doMethodLater(10, self.removeObject, "removeKart-%s"%racer.kart.doId, extraArgs=[racer.kart]) + + #Make them invincible in the eyes of the anvil dropper + taskMgr.remove("make %s invincible" % id) + self.racers[avId].anvilTarget=True + self.checkForEndOfRace() - return def checkForEndOfRace(self): if self.isCircuit() and self.everyoneDone(): simbase.air.raceMgr.endCircuitRace(self) - raceOver = True - for i in self.racers: - if not self.racers[i].exited: - raceOver = False - if raceOver: + raceOver=True + for i in self.racers: + if(not self.racers[i].exited): + raceOver=False + + if(raceOver): self.raceDoneFunc(self) def sendToonsToNextCircuitRace(self, raceZone, trackId): for avId in self.avIds: - self.notify.debug('Handling Circuit Race transisiton for avatar %s' % avId) - self.sendUpdateToAvatarId(avId, 'setRaceZone', [raceZone, trackId]) + self.notify.debug("Handling Circuit Race transisiton for avatar %s" % (avId)) + # Tell each player that they should go to the next race + self.sendUpdateToAvatarId(avId, "setRaceZone", [raceZone, trackId]) def isCircuit(self): return self.raceType == RaceGlobals.Circuit @@ -470,8 +687,10 @@ class DistributedRaceAI(DistributedObjectAI.DistributedObjectAI): def everyoneDone(self): done = True - for racer in list(self.racers.values()): - if not racer.exited and racer.avId not in self.playersFinished and racer.avId not in self.kickedAvIds: + for racer in self.racers.values(): + if (not racer.exited and (not racer.avId in self.playersFinished) and + (not racer.avId in self.kickedAvIds)): + # there is a racer who hasn't exited and who hasn't finished done = False break diff --git a/toontown/racing/DistributedRacePadAI.py b/toontown/racing/DistributedRacePadAI.py index e5571a2..b0196ac 100644 --- a/toontown/racing/DistributedRacePadAI.py +++ b/toontown/racing/DistributedRacePadAI.py @@ -1,158 +1,422 @@ -from direct.directnotify import DirectNotifyGlobal -from direct.distributed.ClockDelta import globalClockDelta -from direct.fsm.FSM import FSM +########################################################################## +# Module: DistributedRacePadAI.py +# Purpose: This class provides the necessary functionality for +# Date: 6/28/05 +# Author: jjtaylor +########################################################################## -from toontown.racing import RaceGlobals +########################################################################## +# Panda Import Modules +########################################################################## +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.ClockDelta import * +from direct.fsm.FSM import FSM +from direct.task import Task +from pandac.PandaModules import * + +########################################################################## +# Toontown Import Modules +########################################################################## from toontown.racing.DistributedKartPadAI import DistributedKartPadAI from toontown.racing.KartShopGlobals import KartGlobals +from toontown.racing import RaceGlobals from toontown.racing.RaceManagerAI import CircuitRaceHolidayMgr +from toontown.toonbase import ToontownGlobals +if( __debug__ ): + import pdb -class DistributedRacePadAI(DistributedKartPadAI, FSM): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedRacePadAI') - defaultTransitions = {'Off': ['WaitEmpty'], - 'WaitEmpty': ['WaitCountdown', 'Off'], - 'WaitCountdown': ['WaitEmpty', - 'WaitBoarding', - 'Off', - 'AllAboard'], - 'WaitBoarding': ['AllAboard', 'WaitEmpty', 'Off'], - 'AllAboard': ['Off', 'WaitEmpty', 'WaitCountdown']} +class DistributedRacePadAI( DistributedKartPadAI, FSM ): + """ + Purpose: Must fill out... DO NOT FORGET TO COMMENT CODE! + """ - def __init__(self, air): - DistributedKartPadAI.__init__(self, air) - FSM.__init__(self, 'DistributedRacePadAI') - self.genre = 'urban' - self.state = 'Off' - self.trackInfo = [0, 0] - self.laps = 3 - self.avIds = [] + ###################################################################### + # Class Variables + ###################################################################### + notify = DirectNotifyGlobal.directNotify.newCategory( "DistributedRacePadAI" ) + #notify.setDebug(True) + #notify.setInfo(True) + defaultTransitions = { + 'Off' : [ 'WaitEmpty' ], + 'WaitEmpty' : [ 'Off', 'WaitCountdown' ], + 'WaitCountdown' : [ 'WaitEmpty', 'WaitBoarding', 'AllAboard' ], + 'WaitBoarding' : [ 'AllAboard', 'WaitEmpty' ], + 'AllAboard' : [ 'WaitEmpty' ], + } + id = 0 + + def __init__( self, air, area, tunnelGenre, tunnelId ): + """ + COMMENT + """ + + # Initialize the KartPadAI and FSM Super Classes + DistributedKartPadAI.__init__( self, air, area ) + FSM.__init__( self, "RacePad_%s_FSM" % ( DistributedRacePadAI.id ) ) + + # Initialize Instance Variables + self.id = DistributedRacePadAI.id + DistributedRacePadAI.id += 1 + + self.tunnelId = tunnelId + self.tunnelGenre = tunnelGenre + self.timerTask = None + raceInfo = RaceGlobals.getNextRaceInfo( -1, tunnelGenre, tunnelId ) + self.trackId = raceInfo[0] + self.trackType = raceInfo[1] + self.numLaps = raceInfo[2] + self.raceMgr = self.air.raceMgr def delete(self): - taskMgr.remove(self.uniqueName('changeTrack')) - taskMgr.remove(self.uniqueName('countdownTask')) - taskMgr.remove(self.uniqueName('enterRaceTask')) DistributedKartPadAI.delete(self) + FSM.cleanup(self) - def request(self, state): - FSM.request(self, state) - self.b_setState(state) + def cycleTrack(self, task = None): + self.notify.debug("Cycling track - %s" % self.doId) + raceInfo = RaceGlobals.getNextRaceInfo( self.trackId, self.tunnelGenre, self.tunnelId ) + self.trackId = raceInfo[0] + self.trackType = raceInfo[1] - def setState(self, state): - self.state = state + #determine if this should be a Circuit race + if self.trackType == RaceGlobals.ToonBattle: + if bboard.get(CircuitRaceHolidayMgr.PostName): + self.trackType = RaceGlobals.Circuit - def d_setState(self, state): - self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()]) + self.numLaps = raceInfo[2] + self.sendUpdate("setTrackInfo", [[self.trackId, self.trackType]]) + self.cycleTrackTask = taskMgr.doMethodLater(RaceGlobals.TrackSignDuration, + self.cycleTrack, + self.uniqueName("cycleTrack")) - def b_setState(self, state): - self.setState(state) - self.d_setState(state) + def getTrackInfo( self ): + return [ self.trackId, self.trackType ] - def getState(self): - return self.state, globalClockDelta.getRealNetworkTime() + def addAvBlock( self, avId, block, paid ): + """ + Purpose: The addAvBlock Method updates the starting block of the + avatar that has requested entry to the block. - def setTrackInfo(self, trackInfo): - self.trackInfo = trackInfo + Params: avId - the id of the avatar entering the block. + block - the Starting Block object that the avatar will enter. + Return: None + """ - def d_setTrackInfo(self, trackInfo): - self.sendUpdate('setTrackInfo', [trackInfo]) + # Grab the avatar and make certain its valid + av = self.air.doId2do.get( avId, None ) + if( not av ): + self.notify.warning( "addAvBlock: Avatar not found with id %s" %( avId ) ) + return KartGlobals.ERROR_CODE.eGeneric - def b_setTrackInfo(self, trackInfo): - self.setTrackInfo(trackInfo) - self.d_setTrackInfo(trackInfo) - def getTrackInfo(self): - return self.trackInfo + # Make sure this track is open + #if (self.trackId in (RaceGlobals.RT_Urban_1, RaceGlobals.RT_Urban_1_rev) and + # not simbase.config.GetBool('test-urban-track', 0)): + # return KartGlobals.ERROR_CODE.eTrackClosed - def enterWaitEmpty(self): - taskMgr.doMethodLater(RaceGlobals.TrackSignDuration, self.changeTrack, self.uniqueName('changeTrack')) + grandPrixWeekendRunning = self.air.holidayManager.isHolidayRunning( + ToontownGlobals.CIRCUIT_RACING_EVENT) + + # trialer restriction - only Speedway Practice races + if not paid and not grandPrixWeekendRunning: + genre = RaceGlobals.getTrackGenre(self.trackId) + if not ( (genre == RaceGlobals.Speedway) and (self.trackType == RaceGlobals.Practice) ): + return KartGlobals.ERROR_CODE.eUnpaid - def exitWaitEmpty(self): - taskMgr.remove(self.uniqueName('changeTrack')) + if not(self.state == 'WaitEmpty' or self.state == 'WaitCountdown'): + #you can only join a racepad in one of these states + return KartGlobals.ERROR_CODE.eTooLate + + # Only check for non-practice races + if( av.hasKart() and (not self.trackType == RaceGlobals.Practice) ): + # Check if the toon owns enough tickets for the race + raceFee = RaceGlobals.getEntryFee(self.trackId, self.trackType) + avTickets = av.getTickets() + + if( avTickets < raceFee ): + self.notify.debug( "addAvBlock: Avatar %s does not own enough tickets for the race!" ) + return KartGlobals.ERROR_CODE.eTickets - def enterWaitCountdown(self): - taskMgr.doMethodLater(KartGlobals.COUNTDOWN_TIME, self.considerAllAboard, self.uniqueName('countdownTask')) + # Call the Super Class Method + success = DistributedKartPadAI.addAvBlock( self, avId, block, paid ) + if( success != KartGlobals.ERROR_CODE.success ): + return success - def exitWaitCountdown(self): - taskMgr.remove(self.uniqueName('countdownTask')) + # A valid avatar has entered a starting block, now enter wait + # countdown state. If already in the WaitCountdown state this + # will not cause any harm. + if( self.isOccupied() ): + self.request( 'WaitCountdown' ) - def enterAllAboard(self): - taskMgr.doMethodLater(KartGlobals.ENTER_RACE_TIME, self.enterRace, self.uniqueName('enterRaceTask')) + return success - def exitAllAboard(self): - self.avIds = [] - taskMgr.remove(self.uniqueName('enterRaceTask')) + def removeAvBlock( self, avId, block ): + """ + The removeAvBlock Method updates the starting block of the avatar + which has requested removal from the starting block. - def changeTrack(self, task): - trackInfo = RaceGlobals.getNextRaceInfo(self.trackInfo[0], self.genre, self.index) - trackId, raceType = trackInfo[0], trackInfo[1] - if raceType == RaceGlobals.ToonBattle and bboard.get(CircuitRaceHolidayMgr.PostName): - raceType = RaceGlobals.Circuit + Params: avId - the id of the avatar to remove from the block. + block - the starting block object that the avatar will exit. + Return: None + """ - self.b_setTrackInfo([trackId, raceType]) - self.laps = trackInfo[2] - return task.again + # Call the Super Class Method + DistributedKartPadAI.removeAvBlock( self, avId, block ) - def considerAllAboard(self, task=None): - for startingBlock in self.startingBlocks: - if startingBlock.currentMovie: - if not self.state == 'WaitBoarding': - self.request('WaitBoarding') - return + if( not self.isOccupied() and ( self.timerTask is not None ) ): + # Remove the TimerTask from the taskMgr and request + # a state transition to the WaitEmpty since there are no + # longer any toons occupying the kart. + taskMgr.remove( self.timerTask ) + self.timerTask = None - if self.trackInfo[1] in (RaceGlobals.ToonBattle, RaceGlobals.Circuit) and len(self.avIds) < 2: - for startingBlock in self.startingBlocks: - if startingBlock.avId != 0: - startingBlock.normalExit() + self.request( 'WaitEmpty' ) + + def __startCountdown( self, name, callback, time, params = [] ): + """ + Purpose: The __startCountdown Method generates a task that acts as + a timer. It calls a specified callback method after the time period + expires, and it additionally records a timestamp for when the timer + began. - self.request('WaitEmpty') - return + Params: name - a unique name for the task. + callback - method to handle the case when the timer expires. + time - amount of time before the timer expires. + params - extra arguments for the callback method. + Return: None + """ + self.timerTask = self.stopCountdown( self.timerTask ) + self.timerTask = DistributedKartPadAI.startCountdown( self, name, callback, time, params ) - self.request('AllAboard') - if task: - return task.done + def handleWaitTimeout( self, nextState ): + """ + Comment: + """ + if( self.isOccupied() ): + self.request( nextState ) + else: + self.request( 'WaitEmpty' ) - def enterRace(self, task): - trackId, raceType = self.trackInfo + #self.timerTask = None + return Task.done + + def __handleSetRaceCountdownTimeout( self, params = [] ): + """ + Comment: + """ + # set the timer task to off, because raceExit calls removeAvBlock and + # shouldn't call. SHOULD BREAK UP THOSE CALLS. + self.timerTask = None + players = self.avId2BlockDict.keys() circuitLoop = [] - if raceType == RaceGlobals.Circuit: - circuitLoop = RaceGlobals.getCircuitLoop(trackId) + if self.trackType == RaceGlobals.Circuit: + circuitLoop = RaceGlobals.getCircuitLoop(self.trackId) - raceZone = self.air.raceMgr.createRace(trackId, raceType, self.laps, self.avIds, circuitLoop=circuitLoop[1:], circuitPoints={}, circuitTimes={}) - for startingBlock in self.startingBlocks: - self.sendUpdateToAvatarId(startingBlock.avId, 'setRaceZone', [raceZone]) - startingBlock.raceExit() + raceZone = self.raceMgr.createRace( self.trackId, + self.trackType, + self.numLaps, + players, + circuitLoop[1:], + {},{}, [], {}, + circuitTotalBonusTickets = {}) + for avId in self.avId2BlockDict.keys(): + if( avId ): + self.notify.debug( "Handling Race Launch Countdown for avatar %s" % ( avId ) ) + # Tell each player that they should enter + # the mint, and which zone it is in. + self.sendUpdateToAvatarId( avId, "setRaceZone", [ raceZone ] ) + self.avId2BlockDict[ avId ].raceExit() - return task.done + # Let's now restart for a new race. + self.request( 'WaitEmpty' ) + return Task.done - def addAvBlock(self, avId, startingBlock, paid): - av = self.air.doId2do.get(avId) - if not av: - return + def isOccupied( self ): + """ + Commnet: + """ + return ( self.avId2BlockDict.keys() != [] ) - if not av.hasKart(): - return KartGlobals.ERROR_CODE.eNoKart - elif self.state == 'Off': - return KartGlobals.ERROR_CODE.eTrackClosed - elif self.state in ('AllAboard', 'WaitBoarding'): - return KartGlobals.ERROR_CODE.eBoardOver - elif startingBlock.avId != 0: - return KartGlobals.ERROR_CODE.eOcuppied - elif RaceGlobals.getEntryFee(self.trackInfo[0], self.trackInfo[1]) > av.getTickets(): - return KartGlobals.ERROR_CODE.eTickets + def enableStartingBlocks( self ): + """ + Comment + """ + for block in self.startingBlocks: + block.setActive( True ) + + def disableStartingBlocks( self ): + """ + Comment: + """ + for block in self.startingBlocks: + block.setActive( False ) - self.avIds.append(avId) - if not self.state == 'WaitCountdown': - self.request('WaitCountdown') + def getState( self ): + """ + Comment: + """ + return self.state, globalClockDelta.getRealNetworkTime() + + ###################################################################### + # Distributed Methods + ###################################################################### + def d_setState( self, state ): + """ + Comment: + """ + timeStamp = globalClockDelta.getRealNetworkTime() + self.sendUpdate( 'setState', [ state, timeStamp ] ) - return KartGlobals.ERROR_CODE.success + ###################################################################### + # FSM State Methods + ###################################################################### + def enterOff( self ): + """ + Comment + """ + self.notify.debug( "enterOff: Entering Off State for RacePad %s" % ( self.id ) ) - def removeAvBlock(self, avId, startingBlock): - if avId == startingBlock.avId and avId in self.avIds: - self.avIds.remove(avId) + self.ignore(CircuitRaceHolidayMgr.StartStopMsg) - def kartMovieDone(self): - if len(self.avIds) == 0 and not self.state == 'WaitEmpty': - self.request('WaitEmpty') - if self.state == 'WaitBoarding': - self.considerAllAboard() + def exitOff( self ): + """ + Comment + """ + self.notify.debug( "exitOff: Exiting Off state for RacePad %s" % ( self.id ) ) + + def handleCircuitHolidayStartStop(args = None): + if self.state == "WaitEmpty": + taskMgr.remove(self.cycleTrackTask) + self.cycleTrack() + + self.accept(CircuitRaceHolidayMgr.StartStopMsg, + handleCircuitHolidayStartStop) + + def enterWaitEmpty( self ): + """ + Comment + """ + self.notify.debug( "enterWaitEmpty: Entering WaitEmpty State for RacePad %s" % ( self.id ) ) + + # What needs to occur at this point. + # - Enable Accepting of Toons for the next race. + # - Signal to the client that we are in wait empty state. + + self.d_setState( 'WaitEmpty' ) + self.enableStartingBlocks() + self.cycleTrack() + + def exitWaitEmpty( self ): + """ + Comment + """ + self.notify.debug( "exitWaitEmpty: Exiting WaitEmpty State for RacePad %s" % ( self.id ) ) + taskMgr.remove(self.cycleTrackTask) + + def enterWaitCountdown( self ): + """ + Comment + """ + self.notify.debug( "enterWaitCountdown: Entering WaitCountdown State for RacePad %s" % ( self.id ) ) + + # Allow toons to enter the spot and tell the client object that + # the AI object has entered the WaitCountdown State. + self.d_setState( 'WaitCountdown' ) + + # Now, handle the actual countdown timer setup. + self.__startCountdown( self.uniqueName( 'CountdownTimer-%s' % ( self.doId ) ), + self.handleWaitTimeout, + KartGlobals.COUNTDOWN_TIME, + [ 'WaitBoarding' ] ) + + def filterWaitCountdown( self, request, args ): + """ + Comment + """ + if request == 'WaitBoarding' and self.allMoviesDone(): + return 'AllAboard' + elif( request in DistributedRacePadAI.defaultTransitions.get( 'WaitCountdown' ) ): + return request + elif( request is 'WaitCountdown' ): + # If in the WaitCountdown state, no need to loop back into + # itself since it is already counting down to the race. + return None + else: + return self.defaultFilter( request, args ) + + def exitWaitCountdown( self ): + """ + Comment + """ + self.notify.debug( "exitWaitCountdown: Exiting WaitCountdown State for RacePad %s" % ( self.id ) ) + + def enterWaitBoarding( self ): + """ + Comment: State to wait for avatars to finish boarding. + """ + self.notify.debug( "enterWaitBoarding: Entering WaitBoarding State for RacePad %s" %( self.id ) ) + + # No longer allow toons to enter the starting blocks because the + # race is doesn't accept more boarders. + self.disableStartingBlocks() + self.d_setState( 'WaitBoarding' ) + self.waitingForMovies = True + + # Allow the toons enough time to play the movie, then we + # play the enter race movie in the AllBoarded state. + self.__startCountdown( self.uniqueName( 'CountdownTimer-%s' % ( self.doId ) ), + self.handleWaitTimeout, + KartGlobals.BOARDING_TIME, + [ 'AllAboard' ] ) + + def exitWaitBoarding( self ): + """ + Comment + """ + self.notify.debug( "exitWaitBoarding: Exiting WaitBoarding State for RacePad %s" % ( self.id ) ) + self.waitingForMovies = False + + def enterAllAboard( self ): + """ + Comment + """ + self.notify.debug( "enterAllAboard: Entering AllAboard State for RacePad %s" % ( self.id ) ) + + # make certain the starting blocks are turned off + self.disableStartingBlocks() + + # Make certain that there are toons onboard, they might have + # left the district. + players = self.avId2BlockDict.keys() + + # check to see if we need to kick a solo racer + kickSoloRacer = False + if (self.trackType != RaceGlobals.Practice) and (len(players) == 1): + av = self.air.doId2do.get(players[0]) + if av and not av.allowSoloRace: + kickSoloRacer = True + + if kickSoloRacer: + # only one left... kick him off. When he's done, the + # removeAvBlock call will reset us back to 'WaitEmpty' + self.notify.info("enterAllAboard: only one toon, kicking him: %s" % players[0]) + block = self.avId2BlockDict[players[0]] + block.normalExit() + elif len(players) > 0: + # Update the State + self.d_setState( 'AllAboard' ) + + # Generate the Race + self.__startCountdown( "setRaceZoneTask", + self.__handleSetRaceCountdownTimeout, + KartGlobals.ENTER_RACE_TIME, + [] ) + else: + self.notify.warning( "The RacePad was empty, so no one entered a race. Returning to WaitEmpty State." ) + self.d_setState( 'WaitEmpty' ) + + def exitAllAboard( self ): + """ + Comment + """ + self.notify.debug( "exitAllAboard: Exiting AllAboard State for RacePad %s" % ( self.id ) ) diff --git a/toontown/racing/DistributedStartingBlockAI.py b/toontown/racing/DistributedStartingBlockAI.py index 36b577b..9a4acf3 100644 --- a/toontown/racing/DistributedStartingBlockAI.py +++ b/toontown/racing/DistributedStartingBlockAI.py @@ -6,126 +6,235 @@ from toontown.building.ElevatorConstants import * from toontown.building import DistributedElevatorExtAI from direct.fsm import ClassicFSM from direct.fsm import State + from direct.showbase.DirectObject import DirectObject from toontown.racing.KartShopGlobals import KartGlobals -if __debug__: + +if( __debug__ ): import pdb -class DistributedStartingBlockAI(DistributedObjectAI.DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedStartingBlockAI') +########################################################################## +# Temporary Class for Working with DistributedRacePad +# Modeling startblock after Pond->FishingSpot relationship so the +# startblocks can be placed via the level editor. Also going this route +# since kart viewing (Big Boy-esque) area will have more than 4 starting +# blocks and this can be changed easily in the level editor. Starting +# blocks will be props. +# +# Using temp class so that the current startingblock implementation isn't +# broken so that you can still get to the race instances. +########################################################################## - def __init__(self, air, kartPad, x, y, z, h, p, r, padLocationId): - DistributedObjectAI.DistributedObjectAI.__init__(self, air) +###### NOTE +###### If RacePad / Starting Block implementation changes, moving it +###### more towards an elevator or trolley system. Remember that the +###### DistributedViewingBlockAI code is derived from the Starting Block! +###### See bottom class. + +class DistributedStartingBlockAI( DistributedObjectAI.DistributedObjectAI ): + """ + Purpose: MUST ADD COMMENTS HERE. + """ + + ###################################################################### + # Class Variables + ###################################################################### + notify = DirectNotifyGlobal.directNotify.newCategory( "DistributedStartingBlockAI" ) + #notify.setDebug(True) + + + def __init__( self, air, kartPad, x, y, z, h, p, r, padLocationId ): + """ + Comments go here + """ + + # Initialize the Super Class + DistributedObjectAI.DistributedObjectAI.__init__( self, air ) + + # Initialize Instance Variables self.avId = 0 self.isActive = True self.kartPad = kartPad self.unexpectedEvent = None self.padLocationId = padLocationId - self.posHpr = (x, y, z, h, p, r) + self.posHpr = ( x, y, z, h, p, r ) self.currentMovie = None - return - - def delete(self): + + def delete( self ): + """ + Comments go here + """ + self.avId = 0 self.kartPad = None - DistributedObjectAI.DistributedObjectAI.delete(self) - return - - def getPadDoId(self): + + # Perform Super Class Delete call + DistributedObjectAI.DistributedObjectAI.delete( self ) + + def getPadDoId( self ): + """ + Comment: + """ return self.kartPad.getDoId() - - def getPadLocationId(self): + + def getPadLocationId( self ): + """ + Comment: + """ return self.padLocationId - - def getPosHpr(self): + + def getPosHpr( self ): + """ + Comment: + """ return self.posHpr - - def setActive(self, isActive): + + def setActive( self, isActive ): + """ + Comment: + """ self.isActive = isActive + + def requestEnter( self, paid ): + """ + comment + """ - def requestEnter(self, paid): avId = self.air.getAvatarIdFromSender() - if self.isActive and self.avId == 0: - success = self.kartPad.addAvBlock(avId, self, paid) - self.notify.debug('requestEnter: avId %s wants to enter the kart block.' % avId) - if success == KartGlobals.ERROR_CODE.success: + if( self.isActive and ( self.avId == 0 ) ): + # Obtain the Avatar Id and attempt to add the avatar to the + # kart pad. + success = self.kartPad.addAvBlock( avId, self, paid ) + + + # Debug Notification of request entry. + self.notify.debug( "requestEnter: avId %s wants to enter the kart block." % ( avId ) ) + + if( success == KartGlobals.ERROR_CODE.success ): + # There is not currently an avatar occupying this kart block. self.avId = avId self.isActive = False - self.unexpectedEvent = self.air.getAvatarExitEvent(self.avId) - self.acceptOnce(self.unexpectedEvent, self.unexpectedExit) - self.d_setOccupied(self.avId) - self.d_setMovie(KartGlobals.ENTER_MOVIE) + + # Handle an unexpected exit by the avatar. + self.unexpectedEvent = self.air.getAvatarExitEvent( self.avId ) + self.acceptOnce( self.unexpectedEvent, self.unexpectedExit ) + + # Perform other operations here. + self.d_setOccupied( self.avId ) + self.d_setMovie( KartGlobals.ENTER_MOVIE ) else: - self.sendUpdateToAvatarId(avId, 'rejectEnter', [success]) + # The request for entry has been denied. + self.sendUpdateToAvatarId( avId, "rejectEnter", [ success ] ) else: - if hasattr(self.kartPad, 'state') and self.kartPad.state in ['WaitBoarding', 'AllAboard']: + if( hasattr( self.kartPad, 'state' ) and self.kartPad.state in [ 'WaitBoarding', 'AllAboard' ] ): errorCode = KartGlobals.ERROR_CODE.eBoardOver else: errorCode = KartGlobals.ERROR_CODE.eOccupied - self.sendUpdateToAvatarId(avId, 'rejectEnter', [errorCode]) - - def requestExit(self): + + # The request for entry has been denied because the blocks are + self.sendUpdateToAvatarId( avId, "rejectEnter", [ errorCode ] ) + + def requestExit( self ): + """ + Comment: + """ + + # Obtain the avatar id who is requesting the exit. avId = self.air.getAvatarIdFromSender() - self.notify.debug('requestExit: avId %s wants to exit the Kart Block.' % avId) - success = self.validate(avId, self.avId == avId, 'requestExit: avId is not occupying this kart block.') - if not success: + + # Debug Notification of exit request + self.notify.debug( "requestExit: avId %s wants to exit the Kart Block." % ( avId ) ) + + success = self.validate( avId, ( self.avId == avId ), "requestExit: avId is not occupying this kart block." ) + if( not success ): return + self.normalExit() + # this should be called either when the avatar finishes a movie, or the AI + # has detected an unexpected exit def movieFinished(self): if self.currentMovie == KartGlobals.EXIT_MOVIE: self.cleanupAvatar() + self.currentMovie = None - if not self.kartPad: - self.handleUnexpectedCleanup() - return self.kartPad.kartMovieDone() - return - - def cleanupAvatar(self): - self.ignore(self.unexpectedEvent) - if not self.kartPad: - self.handleUnexpectedCleanup() - return - self.kartPad.removeAvBlock(self.avId, self) + + def cleanupAvatar( self ): + """ + Comment: + """ + + # Tell the KartPad that the toon is exiting the block. + + self.ignore( self.unexpectedEvent ) + self.kartPad.removeAvBlock( self.avId, self ) self.avId = 0 self.isActive = True - self.d_setOccupied(0) - - def handleUnexpectedCleanup(self): - self.notify.warning('KartPad has already been cleaned up') - from toontown.hood import GSHoodDataAI - if hasattr(simbase.air, 'hoods') and simbase.air.hoods: - for hood in simbase.air.hoods: - if isinstance(hood, GSHoodDataAI.GSHoodDataAI): - hood.logPossibleRaceCondition(self) - - def normalExit(self): - self.d_setMovie(KartGlobals.EXIT_MOVIE) - - def raceExit(self): + self.d_setOccupied( 0 ) + + def normalExit( self ): + """ + Comment: + """ + + self.d_setMovie( KartGlobals.EXIT_MOVIE ) + + def raceExit( self ): + """ + Comment: + """ self.cleanupAvatar() self.movieFinished() - - def unexpectedExit(self): + + def unexpectedExit( self ): + """ + Comment: + """ self.cleanupAvatar() self.movieFinished() + self.unexpectedEvent = None - return - - def d_setOccupied(self, avId): - self.sendUpdate('setOccupied', [avId]) - - def d_setMovie(self, mode): + + ###################################################################### + # Distributed Methods + ###################################################################### + def d_setOccupied( self, avId ): + """ + Comment: + """ + self.sendUpdate( "setOccupied", [ avId ] ) + + def d_setMovie( self, mode ): + """ + Comment: + """ self.currentMovie = mode - self.sendUpdate('setMovie', [mode]) + self.sendUpdate( "setMovie", [ mode ] ) +class DistributedViewingBlockAI( DistributedStartingBlockAI ): + """ + Derived from the Starting Block, mainly for use on the client-side + since it will need different movies for the Viewer than it will + for the Race Starting block. Not much should be handled here... most + changes will occur on client DistributedViewingBlock class. + """ + ###################################################################### + # Class Variables + ###################################################################### + notify = DirectNotifyGlobal.directNotify.newCategory( "DistributedViewingBlockAI" ) + + def __init__( self, air, kartPad, x, y, z, h, p, r, padLocationId ): + """ + """ + # Initialize the Super Class + DistributedStartingBlockAI.__init__( self, air, kartPad, + x, y, z, h, p, r, + padLocationId ) -class DistributedViewingBlockAI(DistributedStartingBlockAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedViewingBlockAI') - - def __init__(self, air, kartPad, x, y, z, h, p, r, padLocationId): - DistributedStartingBlockAI.__init__(self, air, kartPad, x, y, z, h, p, r, padLocationId) - - def delete(self): - DistributedStartingBlockAI.delete(self) + def delete( self ): + """ + """ + # Call the Super Class delete + DistributedStartingBlockAI.delete( self ) + diff --git a/toontown/racing/DistributedVehicleAI.py b/toontown/racing/DistributedVehicleAI.py index 39d3359..917ce94 100644 --- a/toontown/racing/DistributedVehicleAI.py +++ b/toontown/racing/DistributedVehicleAI.py @@ -1,137 +1,264 @@ from otp.ai.AIBase import * from toontown.toonbase.ToontownGlobals import * from toontown.racing.KartDNA import * + from direct.distributed.ClockDelta import * + from direct.distributed import DistributedSmoothNodeAI from direct.fsm import FSM from direct.task import Task -if (__debug__): + +if( __debug__ ): import pdb class DistributedVehicleAI(DistributedSmoothNodeAI.DistributedSmoothNodeAI, FSM.FSM): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedVehicleAI') - + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedVehicleAI") + def __init__(self, air, avId): + """__init__(air) + """ + assert self.notify.debug("__init__ avId = %d" % avId) self.ownerId = avId + DistributedSmoothNodeAI.DistributedSmoothNodeAI.__init__(self, air) FSM.FSM.__init__(self, 'DistributedVehicleAI') - self.driverId = 0 - self.kartDNA = [-1] * getNumFields() - self.__initDNA() - self.request('Off') + + self.driverId = 0 + + # Initialize default Kart DNA List, then update it based on the + # actual DNA found on the distributed toon. + self.kartDNA = [ -1 ] * ( getNumFields() ) + + self.__initDNA() + self.request("Off") + def generate(self): DistributedSmoothNodeAI.DistributedSmoothNodeAI.generate(self) - + def delete(self): + assert self.notify.debug("delete %d" % self.doId) DistributedSmoothNodeAI.DistributedSmoothNodeAI.delete(self) - def __initDNA(self): - owner = self.air.doId2do.get(self.ownerId) - if owner: - self.kartDNA[KartDNA.bodyType] = owner.getKartBodyType() - self.kartDNA[KartDNA.bodyColor] = owner.getKartBodyColor() - self.kartDNA[KartDNA.accColor] = owner.getKartAccessoryColor() - self.kartDNA[KartDNA.ebType] = owner.getKartEngineBlockType() - self.kartDNA[KartDNA.spType] = owner.getKartSpoilerType() - self.kartDNA[KartDNA.fwwType] = owner.getKartFrontWheelWellType() - self.kartDNA[KartDNA.bwwType] = owner.getKartBackWheelWellType() - self.kartDNA[KartDNA.rimsType] = owner.getKartRimType() - self.kartDNA[KartDNA.decalType] = owner.getKartDecalType() + # setState() + + def __initDNA( self ): + """ + Purpose: + + Params: + Return: + """ + + # Retrieve the Distributed Object of the owner in order to set + # each of the kart dna fields. + owner = self.air.doId2do.get( self.ownerId ) + if( owner ): + # If new DNA fields are added, update here as well. + self.kartDNA[ KartDNA.bodyType ] = owner.getKartBodyType() + self.kartDNA[ KartDNA.bodyColor ] = owner.getKartBodyColor() + self.kartDNA[ KartDNA.accColor ] = owner.getKartAccessoryColor() + self.kartDNA[ KartDNA.ebType ] = owner.getKartEngineBlockType() + self.kartDNA[ KartDNA.spType ] = owner.getKartSpoilerType() + self.kartDNA[ KartDNA.fwwType ] = owner.getKartFrontWheelWellType() + self.kartDNA[ KartDNA.bwwType ] = owner.getKartBackWheelWellType() + self.kartDNA[ KartDNA.rimsType ] = owner.getKartRimType() + self.kartDNA[ KartDNA.decalType ] = owner.getKartDecalType() + else: - self.notify.warning('__initDNA - OWNER %s OF KART NOT FOUND!' % self.ownerId) + self.notify.warning( "__initDNA - OWNER %s OF KART NOT FOUND!" % ( self.ownerId ) ) + + + def d_setState(self, state, avId): + assert self.notify.debug("d_setState %s %d" % (state,avId)) self.sendUpdate('setState', [state, avId]) def requestControl(self): + assert self.notify.debug("requestControl %d" % self.doId) + # A client wants to start controlling the car. avId = self.air.getAvatarIdFromSender() + + #if avId == self.ownerId and self.driverId == 0: if self.driverId == 0: self.request('Controlled', avId) def requestParked(self): + assert self.notify.debug("requestParked %d" % self.doId) + # A client wants to stop controlling the car. avId = self.air.getAvatarIdFromSender() + if avId == self.driverId: self.request('Parked') + ### How you start up the vehicle ### def start(self): + assert self.notify.debug("start %d" % self.doId) self.request('Parked') + # Specific State functions + + ##### off state ##### + def enterOff(self): + assert self.notify.debug("enterOff ownerId = %d" % self.ownerId) return None def exitOff(self): + assert self.notify.debug("exitOff ownerId= %d" % self.ownerId) return None + ##### Parked state ##### + def enterParked(self): + assert self.notify.debug("enterParked %d" % self.doId) self.driverId = 0 - self.d_setState('P', 0) + self.d_setState("P", 0) return None def exitParked(self): + assert self.notify.debug("exitParked %d" % self.doId) return None + ##### Controlled state ##### + def enterControlled(self, avId): + assert self.notify.debug("enterControllled %d" % self.doId) self.driverId = avId - fieldList = ['setComponentL', - 'setComponentX', - 'setComponentY', - 'setComponentZ', - 'setComponentH', - 'setComponentP', - 'setComponentR', - 'setComponentT', - 'setSmStop', - 'setSmH', - 'setSmZ', - 'setSmXY', - 'setSmXZ', - 'setSmPos', - 'setSmHpr', - 'setSmXYH', - 'setSmXYZH', - 'setSmPosHpr', - 'setSmPosHprL', - 'clearSmoothing', - 'suggestResync', - 'returnResync'] - self.air.setAllowClientSend(avId, self, fieldNameList=fieldList) - self.d_setState('C', self.driverId) + fieldList = [ + "setComponentL", + "setComponentX", + "setComponentY", + "setComponentZ", + "setComponentH", + "setComponentP", + "setComponentR", + "setComponentT", + "setSmStop", + "setSmH", + "setSmZ", + "setSmXY", + "setSmXZ", + "setSmPos", + "setSmHpr", + "setSmXYH", + "setSmXYZH", + "setSmPosHpr", + "setSmPosHprL", + "clearSmoothing", + "suggestResync", + "returnResync", + ] + #import pdb; pdb.set_trace() + self.air.setAllowClientSend(avId, self, fieldNameList = fieldList) + self.d_setState("C", self.driverId) + def exitControlled(self): + assert self.notify.debug("exitControlled %d" % self.doId) pass + + def __handleUnexpectedExit(self): self.notify.warning('toon: %d exited unexpectedly, resetting vehicle %d' % (self.driverId, self.doId)) - self.request('Parked') + self.request("Parked") self.requestDelete() - def getBodyType(self): - return self.kartDNA[KartDNA.bodyType] + def getBodyType( self ): + """ + Purpose: The getBodyType Method obtains the local AI side + body type of the kart that the toon currently owns. + + Params: None + Return: bodyType - the body type of the kart. + """ + return self.kartDNA[ KartDNA.bodyType ] - def getBodyColor(self): - return self.kartDNA[KartDNA.bodyColor] + def getBodyColor( self ): + """ + Purpose: The getBodyColor Method obtains the current + body color of the kart. + + Params: None + Return: bodyColor - the color of the kart body. + """ + return self.kartDNA[ KartDNA.bodyColor ] - def getAccessoryColor(self): - return self.kartDNA[KartDNA.accColor] + def getAccessoryColor( self ): + """ + Purpose: The getAccessoryColor Method obtains the + accessory color for the kart. + + Params: None + Return: accColor - the color of the accessories + """ + return self.kartDNA[ KartDNA.accColor ] - def getEngineBlockType(self): - return self.kartDNA[KartDNA.ebType] + def getEngineBlockType( self ): + """ + Purpose: The getEngineBlockType Method obtains the engine + block type accessory for the kart by accessing the + current Kart DNA. + + Params: None + Return: ebType - the type of engine block accessory. + """ + return self.kartDNA[ KartDNA.ebType ] - def getSpoilerType(self): - return self.kartDNA[KartDNA.spType] + def getSpoilerType( self ): + """ + Purpose: The getSpoilerType Method obtains the spoiler + type accessory for the kart by accessing the current Kart DNA. + + Params: None + Return: spType - the type of spoiler accessory + """ + return self.kartDNA[ KartDNA.spType ] - def getFrontWheelWellType(self): - return self.kartDNA[KartDNA.fwwType] + def getFrontWheelWellType( self ): + """ + Purpose: The getFrontWheelWellType Method obtains the + front wheel well accessory for the kart accessing the + Kart DNA. + + Params: None + Return: fwwType - the type of Front Wheel Well accessory + """ + return self.kartDNA[ KartDNA.fwwType ] + + def getBackWheelWellType( self ): + """ + Purpose: The getWheelWellType Method gets the Back + Wheel Wheel accessory for the kart by updating the Kart DNA. + + Params: bwwType - the type of Back Wheel Well accessory. + Return: None + """ + return self.kartDNA[ KartDNA.bwwType ] - def getBackWheelWellType(self): - return self.kartDNA[KartDNA.bwwType] + def getRimType( self ): + """ + Purpose: The setRimType Method sets the rims accessory + for the karts tires by accessing the Kart DNA. + + Params: None + Return: rimsType - the type of rims for the kart tires. + """ + return self.kartDNA[ KartDNA.rimsType ] - def getRimType(self): - return self.kartDNA[KartDNA.rimsType] - - def getDecalType(self): - return self.kartDNA[KartDNA.decalType] + def getDecalType( self ): + """ + Purpose: The getDecalType Method obtains the decal + accessory of the kart by accessing the Kart DNA. + + Params: None + Return: decalType - the type of decal set for the kart. + """ + return self.kartDNA[ KartDNA.decalType ] def getOwner(self): return self.ownerId + diff --git a/toontown/racing/DistributedViewPadAI.py b/toontown/racing/DistributedViewPadAI.py index aa2ee72..0fab85f 100644 --- a/toontown/racing/DistributedViewPadAI.py +++ b/toontown/racing/DistributedViewPadAI.py @@ -1,55 +1,136 @@ -from direct.directnotify import DirectNotifyGlobal -from direct.distributed.ClockDelta import globalClockDelta +########################################################################## +# Module: DistributedViewPadAI.py +# Purpose: This class provides the necessary functionality for +# Date: 7/21/05 +# Author: jjtaylor +########################################################################## +########################################################################## +# Panda Import Modules +########################################################################## +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.ClockDelta import * +from direct.task import Task +from pandac.PandaModules import * + +########################################################################## +# Toontown Import Modules +########################################################################## from toontown.racing.DistributedKartPadAI import DistributedKartPadAI from toontown.racing.KartShopGlobals import KartGlobals +if( __debug__ ): + import pdb -class DistributedViewPadAI(DistributedKartPadAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedViewPadAI') - def __init__(self, air): - DistributedKartPadAI.__init__(self, air) +class DistributedViewPadAI( DistributedKartPadAI ): + """ + Purpose: Must fill out... DO NOT FORGET TO COMMENT CODE. + """ + + ###################################################################### + # Class Variables + ###################################################################### + notify = DirectNotifyGlobal.directNotify.newCategory( "DistributedViewPadAI" ) + #notify.setDebug(True) + id = 0 + + def __init__( self, air, area ): + """ + COMMENT + """ + + # Initialize the KartPadAI Super Classes + DistributedKartPadAI.__init__( self, air, area ) + + # Initialize Instance Variables + self.id = DistributedViewPadAI.id + DistributedViewPadAI.id += 1 + self.kickAvDict = {} self.lastEntered = 0 - def announceGenerate(self): - DistributedKartPadAI.announceGenerate(self) - self.lastEntered = globalClockDelta.getRealNetworkTime() + def delete( self ): + # Remove any outstanding tasks + for avId in self.kickAvDict.keys(): + self.stopTimeout( self.kickAvDict.get( avId ) ) + del self.kickAvDict[ avId ] + del self.kickAvDict - def setLastEntered(self, lastEntered): - self.lastEntered = lastEntered + # Perform the Remaining Delete on the Super Class + DistributedKartPadAI.delete( self ) - def d_setLastEntered(self, lastEntered): - self.sendUpdate('setLastEntered', [lastEntered]) + def addAvBlock( self, avId, block, paid ): + """ + Purpose: The addAvBlock Method updates the starting block of the + avatar that has requested entry to the block. - def b_setLastEntered(self, lastEntered): - self.setLastEntered(lastEntered) - self.d_setLastEntered(lastEntered) + Params: avId - the id of the avatar entering the block. + block - the Starting Block object that the avatar will enter. + Return: None + """ + + # Call the Super Class Method + success = DistributedKartPadAI.addAvBlock( self, avId, block, paid ) + if( success != KartGlobals.ERROR_CODE.success ): + return success + + # Need to store information here.... + timeStamp = globalClockDelta.getRealNetworkTime() + #self.d_setAvEnterPad( avId, timeStamp ) + self.d_setLastEntered( timeStamp ) + + # Start the countdown to kick the avatar out of the spot... + self.kickAvDict[ avId ] = self.startCountdown( self.uniqueName( 'ExitViewPadTask|%s'%(avId) ), + self.__handleKickTimeout, + KartGlobals.COUNTDOWN_TIME, + params = [ avId ] ) + + return success + + def removeAvBlock( self, avId, block ): + """ + The removeAvBlock Method updates the starting block of the avatar + which has requested removal from the starting block. + + Params: avId - the id of the avatar to remove from the block. + block - the starting block object that the avatar will exit. + Return: None + """ + + # Call the SuperClass Method + DistributedKartPadAI.removeAvBlock( self, avId, block ) + + # Remove the avatar from the kick dictionary and update the + # local client dictionary as well. + if( self.kickAvDict.has_key( avId ) ): + self.stopCountdown(self.kickAvDict[avId]) + del self.kickAvDict[ avId ] + #self.d_setAvExitPad( avId ) + + + def __handleKickTimeout( self, avId ): + """ + """ + block = self.avId2BlockDict.get( avId ) + self.notify.debug( "__handleKickTimeout: Timer Expired for Av %s, kicking from View Block %s" % ( avId, block ) ) + + # Tell the block to release the avatar from the block, which in + # turn will play the appropriate exit movie. + block.normalExit() + + def d_setLastEntered( self, timeStamp ): + """ + """ + self.lastEntered = timeStamp + self.sendUpdate( 'setLastEntered', [ timeStamp ] ) def getLastEntered(self): return self.lastEntered + """ + def d_setAvEnterPad( self, avId, timeStamp ): + self.sendUpdate( 'setAvEnterPad', [ avId, timeStamp ] ) - def addAvBlock(self, avId, startingBlock, paid): - av = self.air.doId2do.get(avId) - if not av: - return - - if not av.hasKart(): - return KartGlobals.ERROR_CODE.eNoKart - - if not startingBlock.avId: - self.b_setLastEntered(globalClockDelta.getRealNetworkTime()) - taskMgr.doMethodLater(KartGlobals.COUNTDOWN_TIME, self.kickAvatar, - startingBlock.uniqueName('viewTimer'), - extraArgs=[avId, startingBlock]) - return KartGlobals.ERROR_CODE.success - else: - return KartGlobals.ERROR_CODE.eOccupied - - def removeAvBlock(self, avId, startingBlock): - if avId == startingBlock.avId: - taskMgr.remove(startingBlock.uniqueName('viewTimer')) - - def kickAvatar(self, avId, startingBlock): - if avId == startingBlock.avId and not startingBlock.currentMovie: - startingBlock.normalExit() + def d_setAvExitPad( self, avId ): + self.sendUpdate( 'setAvExitPad', [ avId ] ) + """ + diff --git a/toontown/racing/Racer.py b/toontown/racing/Racer.py index 0559dbd..1bc19a1 100644 --- a/toontown/racing/Racer.py +++ b/toontown/racing/Racer.py @@ -1,29 +1,41 @@ -from direct.distributed.ClockDelta import globalClockDelta +from direct.distributed.ClockDelta import * +class Racer(object): + def __init__(self,race,air,avId,zoneId): -class Racer: + self.race=race + self.air=air + self.avId=avId + self.zoneId=zoneId + + self.avatar=air.doId2do[avId] + self.avatar.takeOutKart(zoneId) - def __init__(self, race, air, avId, zoneId): - self.race = race - self.air = air - self.avId = avId - self.zoneId = zoneId - self.avatar = self.air.doId2do.get(self.avId) - self.avatar.takeOutKart(self.zoneId) - self.kart = self.avatar.kart - self.hasGag = False - self.gagType = None - self.anvilTarget = False - self.finished = False - self.maxLap = 0 - self.lapT = 0.0 - self.baseTime = 0.0 - self.totalTime = 0.0 - self.exited = False - self.exitEvent = self.air.getAvatarExitEvent(self.avId) - self.race.accept(self.exitEvent, self.race.unexpectedExit, [self.avId]) + self.kart=self.avatar.kart - def setLapT(self, numLaps, t, timestamp): - self.maxLap = numLaps - self.lapT = t - self.totalTime = globalClockDelta.networkToLocalTime(timestamp) - self.baseTime + #race necessities + self.lapT=0 + self.times=[] + self.totalTime = 0 + self.maxLap=0 + self.hasGag=False + self.gagType=0 + self.startingPlace=None + self.baseTime=0 + + #racer State + self.finished=False + self.exited=False + self.anvilTarget=False + + #in case of disconnect + self.exitEvent=self.air.getAvatarExitEvent(avId) + self.race.accept(self.exitEvent,race.unexpectedExit,extraArgs=[avId]) + + def setLapT(self,numLaps,lapT,timestamp): + self.lapT=numLaps + lapT + if(numLaps>self.maxLap): + lapTime = globalClockDelta.networkToLocalTime(timestamp) - self.baseTime + self.maxLap=numLaps + self.times.append(lapTime - self.totalTime) + self.totalTime = lapTime diff --git a/toontown/safezone/DistributedFishingSpotAI.py b/toontown/safezone/DistributedFishingSpotAI.py index ac51239..9a19ace 100644 --- a/toontown/safezone/DistributedFishingSpotAI.py +++ b/toontown/safezone/DistributedFishingSpotAI.py @@ -1,5 +1,217 @@ -from direct.directnotify import DirectNotifyGlobal -from direct.distributed.DistributedObjectAI import DistributedObjectAI +from otp.ai.AIBase import * -class DistributedFishingSpotAI(DistributedObjectAI): - notify = DirectNotifyGlobal.directNotify.newCategory('DistributedFishingSpotAI') +from direct.distributed import DistributedObjectAI +import random + +from toontown.toonbase import ToontownAccessAI +from toontown.toonbase import TTLocalizer +from direct.directnotify import DirectNotifyGlobal +from toontown.fishing import FishGlobals + +class DistributedFishingSpotAI(DistributedObjectAI.DistributedObjectAI): + + notify = DirectNotifyGlobal.directNotify.newCategory("DistributedFishingSpotAI") + + def __init__(self, air, pond, x, y, z, h, p, r): + DistributedObjectAI.DistributedObjectAI.__init__(self, air) + self.notify.debug("init") + self.posHpr = (x, y, z, h, p, r) + self.avId = 0 + self.timeoutTask = None + self.pond = pond + self.wantTimeouts = simbase.config.GetBool("want-fishing-timeouts", 1) + + def delete(self): + self.notify.debug("delete") + taskMgr.remove(self.taskName("clearEmpty")) + self.ignore(self.air.getAvatarExitEvent(self.avId)) + self.__stopTimeout() + self.d_setMovie(FishGlobals.ExitMovie) + self.avId = 0 + self.pond = None + DistributedObjectAI.DistributedObjectAI.delete(self) + + def getPondDoId(self): + return self.pond.getDoId() + + def requestEnter(self): + # A client is requesting sole use of the fishing spot. If + # it's available, he can have it. + avId = self.air.getAvatarIdFromSender() + self.notify.debug("requestEnter: avId: %s" % (avId)) + if self.avId == avId: + # This seems to happen in the estates when we get a double request + # coming out of fishing directly onto the dock + self.notify.debug("requestEnter: avId %s is already fishing here" % (avId)) + return + + # Check that player has full access + if not ToontownAccessAI.canAccess(avId, self.zoneId): + self.sendUpdateToAvatarId(avId, "rejectEnter", []) + return + + if self.avId == 0: + self.avId = avId + # Tell the pond we are here + self.pond.addAvSpot(avId, self) + self.acceptOnce(self.air.getAvatarExitEvent(self.avId), + self.unexpectedExit) + self.__stopTimeout() + self.d_setOccupied(self.avId) + self.d_setMovie(FishGlobals.EnterMovie) + self.__startTimeout(FishGlobals.CastTimeout) + self.air.writeServerEvent("fished_enter",self.avId, "%s" % (self.zoneId)) + else: + self.sendUpdateToAvatarId(avId, "rejectEnter", []) + + def requestExit(self): + # The client within the spot is ready to leave. + avId = self.air.getAvatarIdFromSender() + self.notify.debug("requestExit: avId: %s" % (avId)) + if not self.validate(avId, (self.avId == avId), "requestExit: avId is not fishing in this spot"): + return + self.normalExit() + + def d_setOccupied(self, avId): + self.notify.debug("setOccupied: %s" % (avId)) + self.sendUpdate("setOccupied", [avId]) + + def doCast(self, power, heading): + # The client begins a cast. + avId = self.air.getAvatarIdFromSender() + self.notify.debug("doCast: avId: %s" % (avId)) + if not self.validate(avId, (self.avId == avId), + "doCast: avId is not fishing in this spot"): + return + if not self.validate(avId, (0.0 <= power <= 1.0), + ("doCast: power: %s is out of range" % power)): + return + if not self.validate(avId, + (-FishGlobals.FishingAngleMax <= heading <= FishGlobals.FishingAngleMax), + ("doCast: heading: %s is out of range" % heading)): + return + + av = self.air.doId2do.get(self.avId) + if not self.validate(avId, (av), "doCast: avId not currently logged in to this AI"): + return + + self.__stopTimeout() + money = av.getMoney() + # cast cost is based on rod now + castCost = FishGlobals.getCastCost(av.getFishingRod()) + + if money < castCost: + # Not enough money to cast + self.normalExit() + return + + self.air.writeServerEvent("fished_cast", avId, "%s|%s" %(av.getFishingRod(), castCost)) + av.b_setMoney(money - castCost) + self.d_setMovie(FishGlobals.CastMovie, power=power, h=heading) + self.__startTimeout(FishGlobals.CastTimeout) + + def d_setMovie(self, mode, code=0, itemDesc1=0, itemDesc2=0, itemDesc3=0, power=0, h=0): + self.notify.debug( + "setMovie: mode:%s code:%s itemDesc1:%s itemDesc2:%s itemDesc3:%s power:%s h:%s" % + (mode, code, itemDesc1, itemDesc2, itemDesc3, power, h)) + self.sendUpdate("setMovie", [mode, code, itemDesc1, itemDesc2, itemDesc3, power, h]) + + def getPosHpr(self): + # This is needed because setPosHpr is a required field. + return self.posHpr + + def __startTimeout(self, timeLimit): + self.notify.debug("__startTimeout") + # Sets the timeout counter running. If __stopTimeout() is not + # called before the time expires, we'll exit the avatar. This + # prevents avatars from hanging out in the fishing spot all + # day. + self.__stopTimeout() + if self.wantTimeouts: + self.timeoutTask = taskMgr.doMethodLater(timeLimit, + self.__handleTimeout, + self.taskName("timeout")) + + def __stopTimeout(self): + self.notify.debug("__stopTimeout") + # Stops a previously-set timeout from expiring. + if self.timeoutTask: + taskMgr.remove(self.timeoutTask) + self.timeoutTask = None + + def __handleTimeout(self, task): + self.notify.debug("__handleTimeout") + # Called when a timeout expires, this sends the avatar home. + self.normalExit() + + def cleanupAvatar(self): + # Tell the pond we are leaving + self.air.writeServerEvent("fished_exit",self.avId, "%s" % (self.zoneId)) + self.pond.removeAvSpot(self.avId, self) + self.ignore(self.air.getAvatarExitEvent(self.avId)) + self.__stopTimeout() + self.avId = 0 + + def normalExit(self): + self.notify.debug("normalExit") + # Send the avatar out of the fishing spot, either because of + # his own request or due to some other cause (like a timeout). + self.cleanupAvatar() + self.d_setMovie(FishGlobals.ExitMovie) + # Give everyone enough time to play the goodbye movie, + # then dump the avatar. + taskMgr.doMethodLater(1.2, self.__clearEmpty, + self.taskName("clearEmpty")) + + def __clearEmpty(self, task=None): + self.notify.debug("__clearEmpty") + self.d_setOccupied(0) + + def unexpectedExit(self): + self.notify.debug("unexpectedExit") + # Called when the avatar in the fishing spot vanishes. + # Tell the pond we are leaving + self.cleanupAvatar() + self.d_setOccupied(0) + + def hitTarget(self, code, item): + self.notify.debug("hitTarget: code: %s item: %s" % (code, item)) + if code == FishGlobals.QuestItem: + self.d_setMovie(FishGlobals.PullInMovie, code, item) + elif code in (FishGlobals.FishItem, + FishGlobals.FishItemNewEntry, + FishGlobals.FishItemNewRecord): + genus, species, weight = item.getVitals() + self.d_setMovie(FishGlobals.PullInMovie, code, genus, species, weight) + elif code == FishGlobals.BootItem: + self.d_setMovie(FishGlobals.PullInMovie, code) + elif code == FishGlobals.JellybeanItem: + self.d_setMovie(FishGlobals.PullInMovie, code, item) + else: + self.d_setMovie(FishGlobals.PullInMovie, code) + self.__startTimeout(FishGlobals.CastTimeout) + + def d_sellFishComplete(self, avId, trophyResult, numFishCaught): + self.sendUpdateToAvatarId(avId, "sellFishComplete", [trophyResult, numFishCaught]) + + def sellFish(self): + # The client asks to sell his fish + gotTrophy = -1 + avId = self.air.getAvatarIdFromSender() + av = self.air.doId2do.get(self.avId) + self.notify.debug("sellFish: avId: %s" % (avId)) + if not self.validate(avId, (simbase.wantBingo), "sellFish: Currently, you can only do this if bingo is turned on"): + gotTrophy = False + elif not self.validate(avId, (self.pond.hasPondBingoManager()), "sellFish: Currently, you can only do this during bingo night"): + gotTrophy = False + elif not self.validate(avId, (self.avId == avId), "sellFish: avId is not fishing in this spot"): + gotTrophy = False + elif not self.validate(avId, (av), "sellFish: avId not currently logged in to this AI"): + gotTrophy = False + + if gotTrophy is -1: + gotTrophy = self.air.fishManager.creditFishTank(av) + self.d_sellFishComplete(avId, gotTrophy, len(av.fishCollection)) + else: + self.d_sellFishComplete(avId, False, 0) +