423 lines
16 KiB
Python
423 lines
16 KiB
Python
##########################################################################
|
|
# Module: DistributedRacePadAI.py
|
|
# Purpose: This class provides the necessary functionality for
|
|
# Date: 6/28/05
|
|
# Author: jjtaylor
|
|
##########################################################################
|
|
|
|
##########################################################################
|
|
# 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 ):
|
|
"""
|
|
Purpose: Must fill out... DO NOT FORGET TO COMMENT CODE!
|
|
"""
|
|
|
|
######################################################################
|
|
# 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):
|
|
DistributedKartPadAI.delete(self)
|
|
FSM.cleanup(self)
|
|
|
|
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]
|
|
|
|
#determine if this should be a Circuit race
|
|
if self.trackType == RaceGlobals.ToonBattle:
|
|
if bboard.get(CircuitRaceHolidayMgr.PostName):
|
|
self.trackType = RaceGlobals.Circuit
|
|
|
|
self.numLaps = raceInfo[2]
|
|
self.sendUpdate("setTrackInfo", [[self.trackId, self.trackType]])
|
|
self.cycleTrackTask = taskMgr.doMethodLater(RaceGlobals.TrackSignDuration,
|
|
self.cycleTrack,
|
|
self.uniqueName("cycleTrack"))
|
|
|
|
def getTrackInfo( self ):
|
|
return [ self.trackId, self.trackType ]
|
|
|
|
def addAvBlock( self, avId, block, paid ):
|
|
"""
|
|
Purpose: The addAvBlock Method updates the starting block of the
|
|
avatar that has requested entry to the block.
|
|
|
|
Params: avId - the id of the avatar entering the block.
|
|
block - the Starting Block object that the avatar will enter.
|
|
Return: None
|
|
"""
|
|
|
|
# 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
|
|
|
|
|
|
# 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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
# Call the Super Class Method
|
|
success = DistributedKartPadAI.addAvBlock( self, avId, block, paid )
|
|
if( success != KartGlobals.ERROR_CODE.success ):
|
|
return success
|
|
|
|
# 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' )
|
|
|
|
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 Super Class Method
|
|
DistributedKartPadAI.removeAvBlock( self, avId, block )
|
|
|
|
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
|
|
|
|
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.
|
|
|
|
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 )
|
|
|
|
def handleWaitTimeout( self, nextState ):
|
|
"""
|
|
Comment:
|
|
"""
|
|
if( self.isOccupied() ):
|
|
self.request( nextState )
|
|
else:
|
|
self.request( 'WaitEmpty' )
|
|
|
|
#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 self.trackType == RaceGlobals.Circuit:
|
|
circuitLoop = RaceGlobals.getCircuitLoop(self.trackId)
|
|
|
|
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()
|
|
|
|
# Let's now restart for a new race.
|
|
self.request( 'WaitEmpty' )
|
|
return Task.done
|
|
|
|
def isOccupied( self ):
|
|
"""
|
|
Commnet:
|
|
"""
|
|
return ( self.avId2BlockDict.keys() != [] )
|
|
|
|
def enableStartingBlocks( self ):
|
|
"""
|
|
Comment
|
|
"""
|
|
for block in self.startingBlocks:
|
|
block.setActive( True )
|
|
|
|
def disableStartingBlocks( self ):
|
|
"""
|
|
Comment:
|
|
"""
|
|
for block in self.startingBlocks:
|
|
block.setActive( False )
|
|
|
|
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 ] )
|
|
|
|
######################################################################
|
|
# FSM State Methods
|
|
######################################################################
|
|
def enterOff( self ):
|
|
"""
|
|
Comment
|
|
"""
|
|
self.notify.debug( "enterOff: Entering Off State for RacePad %s" % ( self.id ) )
|
|
|
|
self.ignore(CircuitRaceHolidayMgr.StartStopMsg)
|
|
|
|
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 ) )
|