open-toontown/toontown/racing/DistributedRacePadAI.py

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