open-toontown/toontown/racing/DistributedRaceAI.py

698 lines
26 KiB
Python

from direct.distributed import DistributedObjectAI
from direct.directnotify import DirectNotifyGlobal
from toontown.toonbase import ToontownGlobals
from otp.otpbase.PythonUtil import nonRepeatingRandomList
from . import DistributedGagAI
from . import DistributedProjectileAI
from direct.task import Task
import random
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')
#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.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:
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=[]
else:
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
#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))
if __debug__:
simbase.race = self
trackFilepath = RaceGlobals.TrackDict[self.trackId][0]
#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, 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)
object.requestDelete()
def requestDelete(self, lastRace = True):
self.notify.debug('requestDelete: %s' % self.doId)
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]
self.ignore(racer.exitEvent)
if(racer.kart):
racer.kart.requestDelete()
racer.kart=None
if(racer.avatar):
racer.avatar.kart=None
racer.avatar=None
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)
def delete(self):
self.notify.debug('delete: %s' % self.doId)
DistributedObjectAI.DistributedObjectAI.delete(self)
del self.raceDoneFunc
del self.racerFinishedFunc
def getTaskZoneId(self):
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
def getTrackId(self):
return self.trackId
def getRaceType(self):
return self.raceType
def getCircuitLoop(self):
return self.circuitLoop
def getAvatars(self):
avIds=[]
for i in self.racers:
avIds.append(i)
return avIds
def getStartingPlaces(self):
return self.startingPlaces
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)
#############################################
#Clear out players who didn't join yet
#Set up Toon/Kart linking on client
#############################################
def b_racersJoined(self, avIds):
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):
#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=[]
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
##############################################
def b_prepForRace(self, avIds):
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)
for i in racersOut:
self.d_kickRacer(i)
if(len(avIds)==0):
return
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", [])
###########################################
#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=[]
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)
for i in racersOut:
self.d_kickRacer(i)
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):
# 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!")
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", [])
##############################################
#Start Countdown
#Request Start state on client
##############################################
def b_startRace(self, avIds):
self.ignoreBarrier("readRules")
# has the race has been deleted for some reason?
if self.isDeleted():
return
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.sendUpdate("startRace", [globalClockDelta.localToNetworkTime(self.baseTime)])
# kickout racers who are taking too long
qualTime = RaceGlobals.getQualifyingTime(self.trackId)
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 self.racers.values():
avId = racer.avId
# 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 (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.finished = 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()
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])
def d_genGag(self, slot):
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=[]
for i in self.racers:
#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):
possibleTargets = possibleTargets[1:]
else:
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)
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
#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)
#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) #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)
#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) #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=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"):
self.raceDoneFunc(self, False)
#######################################
#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()
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):
#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
else:
#problem
return
def requestThrow(self, x, y, z):
avId=self.air.getAvatarIdFromSender()
if avId in self.racers:
racer=self.racers[avId]
if(racer.hasGag):
if(racer.gagType==1):
self.d_makeBanana(avId, x, y, z)
if(racer.gagType==2):
#self.d_announceTurbo
pass
if(racer.gagType==3):
self.d_dropAnvil(avId)
if(racer.gagType==4):
#self.d_makePie(avId, x, y, z)
self.d_launchPie(avId)
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:
me = self.racers[avId]
me.setLapT(numLaps, t, timestamp)
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 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"))
self.flushPendingTask = task
else:
self.flushPending()
# 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
####################################
#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])
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")
####################################
#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.ignore(racer.exitEvent)
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()
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):
self.raceDoneFunc(self)
def sendToonsToNextCircuitRace(self, raceZone, trackId):
for avId in self.avIds:
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
def isLastRace(self):
return len(self.circuitLoop) == 0
def isFirstRace(self):
return len(self.circuitLoop) == 2
def everyoneDone(self):
done = True
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
return done