ai: No longer crashes immediately

This commit is contained in:
Open Toontown 2022-12-17 01:58:59 -05:00
parent e289553726
commit bf9cb79078
18 changed files with 2827 additions and 719 deletions

View File

@ -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()

View File

@ -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()))

View File

@ -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 ]

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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'))

View File

@ -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()

View File

@ -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')

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 ) )

View File

@ -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 )

View File

@ -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

View File

@ -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 ] )
"""

View File

@ -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

View File

@ -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)