open-toontown/toontown/racing/RaceManagerAI.py

1109 lines
48 KiB
Python

from direct.directnotify import DirectNotifyGlobal
from . import DistributedRaceAI
from toontown.toonbase import ToontownGlobals, TTLocalizer
from toontown.coghq import MintLayout
from toontown.ai import HolidayBaseAI
from direct.showbase import DirectObject
from . import RaceGlobals
import random
import os
import pickle
def getDefaultRecord(trackId):
"""
Get the default record for this track. Used for reseting records.
"""
# format: time, race type, num racers, racer name
return (RaceGlobals.getDefaultRecordTime(trackId), 0, 1, TTLocalizer.Goofy)
class RaceManagerAI(DirectObject.DirectObject):
notify = DirectNotifyGlobal.directNotify.newCategory('RaceManagerAI')
serverDataFolder = simbase.config.GetString('server-data-folder', "")
SuspiciousPercent = 0.15 # what percent of qualifying time counts as suspicious
def __init__(self, air):
DirectObject.DirectObject.__init__(self)
self.air = air
self.races = []
self.shard = str(air.districtId)
self.filename = self.getFilename()
self.trackRecords = self.loadRecords()
def getDoId(self):
# DistributedElevatorAI needs this
return 0
def createRace(self, trackId, raceType, laps, players, circuitLoop, circuitPoints, circuitTimes, qualTimes = [], circuitTimeList = {}, circuitTotalBonusTickets = {}):
#print('Creating Race: circuit Points %s' % circuitPoints)
assert self.notify.debug("createRace: players = %s" % players)
raceZone = self.air.allocateZone()
race = DistributedRaceAI.DistributedRaceAI(
self.air, trackId, raceZone, players, laps, raceType,
self.exitedRace, self.raceOver,
circuitLoop, circuitPoints, circuitTimes, qualTimes, circuitTimeList, circuitTotalBonusTickets)
race.generateWithRequired(raceZone)
# TODO: where should this be stored?
race.playersFinished = []
race.lastTotalTime = 0
self.races.append(race)
return raceZone
def exitedRace(self, race, playerInfo):
# The Object returned here, playerInfo, is a Racer object,
# and can be found in Racer.py
self.notify.debug("exited race: %s" % playerInfo.avId)
# calculate total race time
totalTime=playerInfo.totalTime
entryFee = 0
bonus = 0
placeMultiplier=0
qualify = 0
winnings = 0
trophies = []
points = []
newHistory = None
suspicious = False # if True, his time is suspiciously low
# determine their place
race.playersFinished.append(playerInfo.avId)
place = len(race.playersFinished)
self.notify.debug( "place: %s of %s" % (place, race.toonCount) )
self.notify.debug("pre-tie totalTime: %s" % totalTime)
# HACK: make sure they don't tie
if totalTime <= race.lastTotalTime:
totalTime = race.lastTotalTime + 0.01
race.lastTotalTime = totalTime
self.notify.debug("totalTime: %s, qualify: %s" % (totalTime, RaceGlobals.getQualifyingTime(race.trackId) ))
# update this player's total time for this set of races
circuitTime = totalTime + race.circuitTimes.get(playerInfo.avId, 0)
race.circuitTimes[playerInfo.avId] = circuitTime
# keep track of the players time
if not race.circuitTimeList.get(playerInfo.avId):
race.circuitTimeList[playerInfo.avId] = []
race.circuitTimeList[playerInfo.avId].append([totalTime, 0])
self.notify.debug("CircuitTimeList %s" % (race.circuitTimeList))
# update circuit points
if race.raceType == RaceGlobals.Circuit:
points = race.circuitPoints.get(playerInfo.avId, [])
points.append(RaceGlobals.CircuitPoints[place-1])
race.circuitPoints[playerInfo.avId] = points
currentTimeIndex = len(race.circuitTimeList[playerInfo.avId]) -1
# if they qualified
if totalTime <= RaceGlobals.getQualifyingTime(race.trackId):
race.circuitTimeList[playerInfo.avId][currentTimeIndex][1] = 1
self.notify.debug("Racer Qualified time: %s required: %s" % (totalTime, RaceGlobals.getQualifyingTime(race.trackId)))
# set the flag for the gui
qualify = 1
# see if the broke a personal record
self.checkPersonalBest(race.trackId, totalTime, race.raceType, race.toonCount, playerInfo.avId)
# check for suspiciousTime
qualTime = RaceGlobals.getQualifyingTime(race.trackId)
threshold = qualTime * self.SuspiciousPercent
if totalTime < threshold:
self.air.writeServerEvent("kartingSuspiciousRacer", playerInfo.avId, "%s|%s" % (race.trackId,totalTime))
suspicious = True
# you can only place if you beat qualifying time and you are NOT practicing
if race.raceType == RaceGlobals.Practice:
winnings = RaceGlobals.PracticeWinnings
self.notify.debug("GrandTouring: Checking from branch: practice %s" % (playerInfo.avId))
trophies = self.checkForNonRaceTrophies(playerInfo.avId)
if trophies:
# need to update the history from a list of trophiesWon
self.updateTrophiesFromList(playerInfo.avId, trophies)
else:
# keep track of placemnet for contests
self.air.writeServerEvent("kartingPlaced", playerInfo.avId, "%s|%s" % (place, race.toonCount))
if race.raceType != RaceGlobals.Circuit:
# calculate their winnings
entryFee = RaceGlobals.getEntryFee(race.trackId, race.raceType)
placeMultiplier = RaceGlobals.Winnings[(place - 1) + (RaceGlobals.MaxRacers - race.toonCount)]
winnings = int(entryFee * placeMultiplier)
# win any trophies?
#trophies = self.checkForTrophies(place, race.trackId, race.raceType, race.toonCount, playerInfo.avId)
# update history
newHistory = self.getNewSingleRaceHistory(race, playerInfo.avId, place)
av = self.air.doId2do.get(playerInfo.avId)
if newHistory:
self.notify.debug("history %s" % (newHistory))
av.b_setKartingHistory(newHistory)
# win any trophies?
trophies = self.checkForRaceTrophies(race, playerInfo.avId)
else:
trophies = self.checkForNonRaceTrophies(playerInfo.avId)
if trophies:
# need to update the history from a list of trophiesWon
self.updateTrophiesFromList(playerInfo.avId, trophies)
# see if they broke a server record
if not suspicious:
bonus = self.checkTimeRecord(race.trackId, totalTime, race.raceType, race.toonCount, playerInfo.avId)
if (race.circuitTotalBonusTickets.has_key(playerInfo.avId)):
race.circuitTotalBonusTickets[playerInfo.avId] += bonus
else:
race.circuitTotalBonusTickets[playerInfo.avId] = bonus
# set the tickets on the toon
av = self.air.doId2do.get(playerInfo.avId)
if av:
oldTickets = av.getTickets()
self.notify.debug( "old tickets: %s" % oldTickets )
newTickets = oldTickets + winnings + entryFee + bonus
# Write to the server log
self.air.writeServerEvent("kartingTicketsWon", playerInfo.avId, "%s" % (newTickets - oldTickets))
self.notify.debug( "entry fee: %s" % entryFee )
self.notify.debug( "place mult: %s" % placeMultiplier )
self.notify.debug( "winnings: %s" % winnings )
self.notify.debug( "bonus: %s" % bonus )
self.notify.debug( "new tickets: %s" % newTickets )
self.notify.debug( "circuit points: %s" % points )
self.notify.debug( "circuit time: %s" % circuitTime )
self.notify.debug( "circuitTotalBonusTickets: %s" % race.circuitTotalBonusTickets)
av.b_setTickets(newTickets)
else:
race.circuitTimeList[playerInfo.avId][currentTimeIndex][1] = -1
self.notify.debug("GrandTouring: Checking from branch: Not Qualified %s" % (playerInfo.avId))
trophies = self.checkForNonRaceTrophies(playerInfo.avId)
if trophies:
# need to update the history from a list of trophiesWon
self.updateTrophiesFromList(playerInfo.avId, trophies)
# report the good news!
if(race in self.races):
race.d_setPlace(playerInfo.avId,
totalTime,
place,
entryFee,
qualify,
winnings,
bonus,
trophies,
points,
circuitTime)
# handle the end of a circuit race
if race.isCircuit():
self.notify.debug("isCircuit")
if race.everyoneDone():
self.notify.debug("everyoneDone")
#doing this immediately kills showing the breaking a record sequence.
#it's a problem only when the last player in the 3rd race breaks a record
if race.isLastRace():
taskMgr.doMethodLater(10,self.endCircuitRace,"DelayEndCircuitRace=%d" % race.doId , (race,bonus))
else:
self.endCircuitRace(race, bonus)
else:
self.notify.debug("not isCircuit")
#trophies.extend(self.checkForNonRaceTrophies(playerInfo.avId, newHistory))
def endCircuitRace(self, race, bonus = 0):
self.notify.debug("endCircuitRace")
# look at all the points and decide the standings
pointTotals = []
for avId in race.circuitPoints:
pointTotals.append([avId, sum(race.circuitPoints[avId])])
# do a quick n dirty bubble sort (first place -> last place)
numVals = len(pointTotals)
finalStandings = []
def swap(x, y):
t = pointTotals[x]
pointTotals[x] = pointTotals[y]
pointTotals[y] = t
# the old bubble sort seemed wonky - grabbed this one from wikisource! =]
for i in range(numVals-1,0,-1):
for j in range(i):
if pointTotals[j][1] < pointTotals[j+1][1]:
swap(j, j+1)
elif pointTotals[j][1] == pointTotals[j+1][1]:
# ties are resolved via totalTimes
avId1 = pointTotals[j][0]
avId2 = pointTotals[j+1][0]
if race.circuitTimes[avId1] > race.circuitTimes[avId2]:
swap(j,j+1)
# don't trust grabbing them out while sorting
for i in range(numVals):
finalStandings.append(pointTotals[i][0])
self.notify.debug("Final standings %s" % (finalStandings))
# check for trophies, apply winnings, etc.
for avId in finalStandings:
self.notify.debug("avId %s" % (avId))
place = finalStandings.index(avId) + 1
places = len(finalStandings)
winnings = 0
trophies = []
if race.isLastRace():
self.notify.debug("isLastRace")
# make sure we don't reward any disconnected racers
av = self.air.doId2do.get(avId)
if av and (avId in race.playersFinished):
# keep track of placement for contests
self.air.writeServerEvent("kartingCircuitFinished", avId, "%s|%s|%s|%s" % (place, places, race.circuitTimes[avId], race.trackId))
print("kartingCircuitFinished", avId, "%s|%s|%s|%s" % (place, places, race.circuitTimes[avId], race.trackId))
# calculate their winnings
entryFee = RaceGlobals.getEntryFee(race.trackId, race.raceType)
placeMultiplier = RaceGlobals.Winnings[(place - 1) + (RaceGlobals.MaxRacers - places)]
winnings = int(entryFee * placeMultiplier)
# update history
newHistory = self.getNewCircuitHistory(race, avId, place)
if newHistory:
self.notify.debug("history %s" % (newHistory))
# win any trophies?
trophies = self.checkForCircuitTrophies(race, avId, newHistory)
av.b_setKartingHistory(newHistory)
else:
self.notify.debug("no new history")
trophies = self.checkForNonRaceTrophies(avId)
if trophies:
# need to update the history from a list of trophiesWon
self.updateTrophiesFromList(avId, trophies)
oldTickets = av.getTickets()
self.notify.debug( "endCircuitRace: old tickets: %s" % oldTickets )
newTickets = oldTickets + winnings + entryFee
# Write to the server log
self.air.writeServerEvent("kartingTicketsWonCircuit", avId, "%s" % (newTickets - oldTickets))
self.notify.debug( "entry fee: %s" % entryFee )
self.notify.debug( "place mult: %s" % placeMultiplier )
self.notify.debug( "winnings: %s" % winnings )
self.notify.debug( "new tickets: %s" % newTickets )
self.notify.debug( "trophies: %s" % trophies )
self.notify.debug( "bonus: %s" % bonus)
av.b_setTickets(newTickets)
finalBonus = 0
if (race.circuitTotalBonusTickets.has_key(avId)):
finalBonus = race.circuitTotalBonusTickets[avId]
#race.d_setCircuitPlace(avId, place, entryFee, winnings, bonus, trophies)
race.d_setCircuitPlace(avId, place, entryFee, winnings, finalBonus, trophies)
race.playersFinished = finalStandings
race.d_endCircuitRace()
def updateTrophiesFromList(self, avId, trophies):
self.air.writeServerEvent("Writing Trophies: Updating trophies from list", avId, "%s" % (trophies))
av = self.air.doId2do.get(avId)
if not av:
return
if not trophies:
return
trophyField = av.getKartingTrophies()
for trophy in trophies:
trophyField[trophy] = 1
av.b_setKartingTrophies(trophyField)
def checkForCircuitTrophies(self, race, avId, inHistory = None):
#checks for qualify, win, and sweep trophies for curcuit races
#returns a list of new trophies
#check and retrieve the needed data
av = self.air.doId2do.get(avId)
if not av:
return []
trophies = av.getKartingTrophies()
if inHistory:
history = inHistory
else:
history = av.getKartingHistory()
#trackGenre = RaceGlobals.getTrackGenre(race.trackId)
#setup the test groups: wins, sweeps, and qualifies
winIndex = RaceGlobals.CircuitWins
winReqList = RaceGlobals.WonCircuitRaces
winIndices = RaceGlobals.CircuitWinsList
sweepIndex = RaceGlobals.CircuitSweeps
sweepReqList = RaceGlobals.SweptCircuitRaces
sweepIndices = RaceGlobals.CircuitSweepsList
qualIndex = RaceGlobals.CircuitQuals
qualReqList = RaceGlobals.QualifiedCircuitRaces
qualIndices = RaceGlobals.CircuitQualList
trophies = av.getKartingTrophies()
newTrophies = []
#check to see which win trophies are won
newTrophies.extend(self.checkHistoryForTrophy(trophies, history, winIndex, winReqList, winIndices))
#check to see which sweep trophies are won
newTrophies.extend(self.checkHistoryForTrophy(trophies, history, sweepIndex, sweepReqList, sweepIndices))
#check to see which qualify trophies are won
newTrophies.extend(self.checkHistoryForTrophy(trophies, history, qualIndex, qualReqList, qualIndices))
self.notify.debug("GrandTouring: Checking from branch: Circuit %s" % (avId))
newTrophies.extend(self.checkForNonRaceTrophies(avId, history))
newTrophies.sort()
return newTrophies
def checkForRaceTrophies(self, race, avId, inHistory = None):
#checks for single race win and qualified trophies
#check and retrieve the needed data
av = self.air.doId2do.get(avId)
if not av:
return []
trophies = av.getKartingTrophies()
if inHistory:
history = inHistory
else:
history = av.getKartingHistory()
newTrophies = []
#check for genre wins trophies
numTrackGenres = len(RaceGlobals.WinsList)
for genre in range(numTrackGenres):
singleGenreNewTrophies = self.checkHistoryForTrophy(trophies,
history,
RaceGlobals.WinsList[genre],
RaceGlobals.WonRaces,
RaceGlobals.AllWinsList[genre])
newTrophies.extend(singleGenreNewTrophies)
#check for genre quals trophies
numTrackGenres = len(RaceGlobals.QualsList)
for genre in range(numTrackGenres):
singleGenreNewTrophies = self.checkHistoryForTrophy(trophies,
history,
RaceGlobals.QualsList[genre],
RaceGlobals.QualifiedRaces,
RaceGlobals.AllQualsList[genre])
newTrophies.extend(singleGenreNewTrophies)
#check for total wins trophy
totalWins = 0
for genre in range(numTrackGenres):
totalWins += history[RaceGlobals.WinsList[genre]]
singleGenreNewTrophies = self.checkHistoryForTrophyByValue(trophies,
history,
totalWins,
[RaceGlobals.TotalWonRaces],
[RaceGlobals.TotalWins])
newTrophies.extend(singleGenreNewTrophies)
#check for total quals trophy
totalQuals = 0
for genre in range(numTrackGenres):
totalQuals += history[RaceGlobals.QualsList[genre]]
singleGenreNewTrophies = self.checkHistoryForTrophyByValue(trophies,
history,
totalQuals,
[RaceGlobals.TotalQualifiedRaces],
[RaceGlobals.TotalQuals])
newTrophies.extend(singleGenreNewTrophies)
self.notify.debug("GrandTouring: Checking from branch: Race %s " % (avId))
newTrophies.extend(self.checkForNonRaceTrophies(avId, history))
newTrophies.sort()
return newTrophies
def checkForNonRaceTrophies(self, avId, inHistory = None):
#checks for race independent trophies like laff cups
self.notify.debug("CHECKING FOR NONRACE TROPHIES")
self.notify.debug("GrandTouring: Checking for non-race trophies %s" % (avId))
#check and retrieve the needed data
av = self.air.doId2do.get(avId)
if not av:
self.notify.debug("NO AV %s" % (avId))
self.notify.debug("GrandTouring: can't convert avId to Av %s" % (avId))
return []
trophies = av.getKartingTrophies()
if inHistory:
history = inHistory
else:
history = av.getKartingHistory()
newTrophies = []
self.notify.debug("GrandTouring: history-- %s" % (history))
self.notify.debug("GrandTouring: trophies- %s" % (trophies))
addTrophyCount = 0
#check for grandtouring
if not trophies[RaceGlobals.GrandTouring]:
self.notify.debug("checking for grand touring")
self.notify.debug("GrandTouring: checking for grand touring %s" % (trophies[RaceGlobals.GrandTouring]))
#import pdb; pdb.set_trace()
best = av.getKartingPersonalBestAll()
self.notify.debug("personal best %s" % (best))
self.notify.debug("GrandTouring: checking personal best %s" % (best))
counter = 0
for time in best:
if not time == 0:
counter +=1
self.notify.debug("counter %s tracks %s" % (counter, len(RaceGlobals.TrackDict)))
self.notify.debug("GrandTouring: bests comparison counter: %s tracks: %s" % (counter, len(RaceGlobals.TrackDict)))
if counter >= len(RaceGlobals.TrackDict):
newTrophies.append(RaceGlobals.GrandTouring)
addTrophyCount += 1
self.air.writeServerEvent("kartingTrophy", avId, "%s" % (RaceGlobals.GrandTouring))
self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[RaceGlobals.GrandTouring] )
self.notify.debug("GrandTouring: awarding grand touring new trophies %s" % (newTrophies))
else:
self.notify.debug("already has grandtouring")
self.notify.debug("trophies %s" % (trophies))
self.notify.debug("GrandTouring: already has grand touring %s" % (trophies[RaceGlobals.GrandTouring]))
# check to see if we need a laff point
for i in range(1, RaceGlobals.NumTrophies/RaceGlobals.TrophiesPerCup + 1):
cupNum = (trophies[:RaceGlobals.NumTrophies].count(1) + addTrophyCount) / (i * RaceGlobals.TrophiesPerCup)
self.notify.debug( "cupNum: %s" % cupNum)
trophyIndex = RaceGlobals.TrophyCups[i - 1]
if cupNum and not trophies[trophyIndex]:
# award the cup!
#trophies[trophyIndex] = 1
newTrophies.append(trophyIndex)
# laff point boost!
oldMaxHp = av.getMaxHp()
newMaxHp = min(ToontownGlobals.MaxHpLimit, oldMaxHp + 1)
self.notify.debug( "cup awarded! new max laff : %s" % newMaxHp)
av.b_setMaxHp(newMaxHp)
# Also, give them a full heal
av.toonUp(newMaxHp)
# Write to the server log
self.air.writeServerEvent("kartingTrophy", avId, "%s" % (trophyIndex))
self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[trophyIndex] )
self.notify.debug("NONRACE TROPHIES %s" % (newTrophies))
return newTrophies
def checkHistoryForTrophyByValue(self, trophies, history, historyIndexValue, trophyReqList, trophyIndices):
#takes the index of the history we are interested in "number of wins etc.."
#compares it to a list of required wins for each stage
#and returns the new trophy indices won at each stage
newTrophies = []
self.notify.debug("Checking History for Trophy")
self.notify.debug("Index %s Num %s ReqList %s Indices %s" % (0, historyIndexValue, trophyReqList, trophyIndices))
for index in range(len(trophyIndices)):
if not trophies[trophyIndices[index]]:
if historyIndexValue >= trophyReqList[index]:
trophies[trophyIndices[index]] = 1
newTrophies.append(trophyIndices[index])
return newTrophies
def checkHistoryForTrophy(self, trophies, history, historyIndex, trophyReqList, trophyIndices):
#takes the index of the history we are interested in "number of wins etc.."
#compares it to a list of required wins for each stage
#and returns the new trophy indices won at each stage
newTrophies = []
self.notify.debug("Checking History for Trophy")
self.notify.debug("Index %s Num %s ReqList %s Indices %s" % (historyIndex, history[historyIndex], trophyReqList, trophyIndices))
for index in range(len(trophyIndices)):
if not trophies[trophyIndices[index]]:
if history[historyIndex] >= trophyReqList[index]:
trophies[trophyIndices[index]] = 1
newTrophies.append(trophyIndices[index])
return newTrophies
def mergeHistories(self, historyA, historyB):
newHistorySize = len(historyA)
if len(historyB) > len(historyA):
newHistorySize = historyB
mergedHistory = []
for index in range(newHistorySize):
mergedHistory[index] = 0
if len(historyA) > index:
if historyA[index] > mergedHistory[index]:
historyA[index] = mergedHistory[index]
if len(historyB) > index:
if historyB[index] > mergedHistory[index]:
historyB[index] = mergedHistory[index]
return mergedHistory
def getNewSingleRaceHistory(self, race, avId, positionFinished):
newHistory = 0
av = self.air.doId2do.get(avId)
if not av:
return []
history = av.getKartingHistory()
trackGenre = RaceGlobals.getTrackGenre(race.trackId)
winIndex = RaceGlobals.WinsList[trackGenre]
winReqList = RaceGlobals.WonRaces
qualIndex = RaceGlobals.QualsList[trackGenre]
qualReqList = RaceGlobals.QualifiedRaces
#win history
if history[winIndex] < winReqList[-1] and positionFinished == 1:
history[winIndex] += 1
self.notify.debug("New History Won!")
newHistory = 1
#qual history
if history[qualIndex] < qualReqList[-1]:
history[qualIndex] += 1
self.notify.debug("New History Qualified!")
newHistory = 1
if newHistory:
return history
def getNewCircuitHistory(self, race, avId, positionFinished):
newHistory = 0
av = self.air.doId2do.get(avId)
if not av:
return []
history = av.getKartingHistory()
trackGenre = RaceGlobals.getTrackGenre(race.trackId)
historyIndex = RaceGlobals.CircuitWins
trophyReqList = RaceGlobals.WonCircuitRaces
sweepIndices = RaceGlobals.CircuitSweepsList
sweepReqList = RaceGlobals.SweptCircuitRaces
self.notify.debug("getNewCircuitHistory: avId=%d positionFinished=%d history =%s" % (avId, positionFinished, history))
#win history
if history[historyIndex] < trophyReqList[-1] and positionFinished == 1:
history[historyIndex] += 1
self.notify.debug("New History Won!")
newHistory = 1
#sweep history
#first determine if a sweep occured ~ if the player has maximum points
swept = 0
totalPoints = sum(race.circuitPoints[avId])
if totalPoints == len(race.circuitPoints[avId]) * RaceGlobals.CircuitPoints[0]:
swept = 1
#increment the sweep history if it's not maxed ~ reached the highest requirement
if swept:
if history[RaceGlobals.CircuitSweeps] < sweepReqList[-1]:
if not history[RaceGlobals.CircuitSweeps]:
history[RaceGlobals.CircuitSweeps] = 0
history[RaceGlobals.CircuitSweeps] += 1
self.notify.debug("New History Swept!")
newHistory = 1
#qual history
#first determine if the player qualified for all their circuit races
qualified = 0
#check to see if the avatar has a time for each race
self.notify.debug("qual times %s" % (race.qualTimes))
self.notify.debug("avatar times %s" % (race.circuitTimeList[avId]))
#if len(race.qualTimes) <= len(race.circuitTimeList[avId]):
qualified = 1
self.notify.debug("End Race Circuit Time List %s" % (race.circuitTimeList))
self.notify.debug("check for qualify")
for qual in race.circuitTimeList[avId]:
self.notify.debug("qual %s" % (qual))
if qual[1] == -1:
qualified = 0
self.notify.debug("not qualified")
#increment the qualify history if it's not maxed ~ reached the highest requirement
if qualified:
self.notify.debug("qualified has %s needs %s" % (history[RaceGlobals.CircuitQuals], RaceGlobals.QualifiedCircuitRaces[-1]))
if history[RaceGlobals.CircuitQuals] < RaceGlobals.QualifiedCircuitRaces[-1]:
history[RaceGlobals.CircuitQuals] += 1
self.notify.debug("New History qualified!")
newHistory = 1
if newHistory:
return history
def raceOver(self, race, normalExit = True):
assert self.notify.debug("raceOver: race.doId=%d normalExit=%s" % (race.doId, normalExit))
if(race in self.races):
if normalExit and race.isCircuit() and not race.isLastRace():
nextTrackId = race.circuitLoop[0]
assert self.notify.debug("raceOver: creating a new race %d" % nextTrackId)
continuingAvs = []
for avId in race.avIds:
if not avId in race.kickedAvIds:
continuingAvs.append(avId)
assert self.notify.debug("race.kickedAvIds=%s continuingAvs = %s" % (race.kickedAvIds,continuingAvs))
lastRace = False
if len(continuingAvs) > 0:
raceZone = self.createRace( nextTrackId,
race.raceType,
race.lapCount,
continuingAvs,
race.circuitLoop[1:],
race.circuitPoints,
race.circuitTimes,
race.qualTimes,
race.circuitTimeList,
race.circuitTotalBonusTickets)
race.sendToonsToNextCircuitRace(raceZone, nextTrackId)
else:
lastRace = True
assert self.notify.debug("no avatars to continue race, not creating a new one")
assert self.notify.debug("removing race %d" % race.doId)
self.races.remove(race)
race.requestDelete(lastRace)
else:
assert self.notify.debug("removing race %d" % race.doId)
self.races.remove(race)
race.requestDelete()
def checkForTrophies(self, place, trackId, raceType, numRacers, avId):
"""Update race history and award trophies if necessary"""
# I have tried my best to abstract this so that checking for all trophies can be done
# by this one for loop. By maintaining some strategic lists of indices in RaceGlobals
# I avoided a ridiculously long cascade of if-else statements. I apologize if it
# seems obtuse - grw
av = self.air.doId2do.get(avId)
outHistory = av.getKartingHistory()
trophies = av.getKartingTrophies()
trophiesWon = []
# determine track type
trackGenre = RaceGlobals.getTrackGenre(trackId)
# win
if place == 1:
historyIndex = RaceGlobals.WinsList[trackGenre]
trophyIndices = RaceGlobals.AllWinsList[trackGenre]
trophyReqList = RaceGlobals.WonRaces
historyTotalList = RaceGlobals.WinsList
totalTrophyIndex = RaceGlobals.TotalWins
totalReq = RaceGlobals.TotalWonRaces
# check for win trophies
trophiesWon += self.checkForTrophy(place, trackId, raceType, numRacers, avId,
historyIndex, trophyIndices, trophyReqList,
historyTotalList, totalTrophyIndex, totalReq)
# quals
#circuit quals cannot use checkfortrophy because it assumes you get a qual if you get anything
historyIndex = RaceGlobals.QualsList[trackGenre]
trophyIndices = RaceGlobals.AllQualsList[trackGenre]
trophyReqList = RaceGlobals.QualifiedRaces
historyTotalList = RaceGlobals.QualsList
totalTrophyIndex = RaceGlobals.TotalQuals
totalReq = RaceGlobals.TotalQualifiedRaces
trophiesWon += self.checkForTrophy(place, trackId, raceType, numRacers, avId,
historyIndex, trophyIndices, trophyReqList,
historyTotalList, totalTrophyIndex, totalReq)
# check for qual trophies
#check for grandtouring
if not trophies[RaceGlobals.GrandTouring]:
self.notify.debug("checking for grand touring")
#import pdb; pdb.set_trace()
best = av.getKartingPersonalBestAll()
self.notify.debug("personal best %s" % (best))
counter = 0
for time in best:
if not time == 0:
counter +=1
self.notify.debug("counter %s tracks %s" % (counter, len(RaceGlobals.TrackDict)))
if counter >= len(RaceGlobals.TrackDict):
trophiesWon.append(RaceGlobals.GrandTouring)
# set the new trophies and history if changed
if outHistory:
av.b_setKartingHistory(outHistory)
if len(trophiesWon):
for trophy in trophiesWon:
trophies[trophy] = 1
av.b_setKartingTrophies(trophies)
trophiesWon.sort()
return trophiesWon
def checkForTrophy(self, place, trackId, raceType, numRacers, avId, historyIndex, trophyIndices, trophyReqList,
historyTotalList, totalTrophyIndex, totalReq):
"""Update race history and award trophies if necessary"""
av = self.air.doId2do.get(avId)
if not av:
return []
history = av.getKartingHistory()
newHistory = 0
trophies = av.getKartingTrophies()
newTrophies = []
# If the number of races the player has completed is less than the most needed for any trophy,
# then add this race to players race history (keeps us from adding up to infinity)
if history[historyIndex] < trophyReqList[-1]:
history[historyIndex] += 1
newHistory = 1
# check for all three laff point trophies
for i in range(0, len(trophyReqList)):
if not trophies[trophyIndices[i]]:
#self.notify.debug( "checking for trophy: %s == %s" % (history[historyIndex], trophyReqList[i]))
if history[historyIndex] == trophyReqList[i]:
trophies[trophyIndices[i]] = 1
newTrophies.append(trophyIndices[i])
# Write to the server log
self.air.writeServerEvent("kartingTrophy", avId, "%s" % (trophyIndices[i]))
self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[trophyIndices[i]] )
break
# if this is not tourney, count toward total wins
if not raceType == RaceGlobals.Circuit:
# now check for total wins style trophies
if not trophies[totalTrophyIndex]:
total = 0
# calculate total since its not stored explicitly
for i in historyTotalList:
total += history[i]
self.notify.debug( "checking for total trophy: %s >= %s" % (total, totalReq))
if total >= totalReq:
trophies[totalTrophyIndex] = 1
newTrophies.append(totalTrophyIndex)
# Write to the server log
self.air.writeServerEvent("kartingTrophy", avId, "%s" % (totalTrophyIndex))
self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[totalTrophyIndex] )
# check to see if we need a laff point
for i in range(1, RaceGlobals.NumTrophies/RaceGlobals.TrophiesPerCup + 1):
cupNum = trophies[:RaceGlobals.NumTrophies].count(1) / (i * RaceGlobals.TrophiesPerCup)
self.notify.debug( "cupNum: %s" % cupNum)
trophyIndex = RaceGlobals.TrophyCups[i - 1]
if cupNum and not trophies[trophyIndex]:
# award the cup!
trophies[trophyIndex] = 1
newTrophies.append(trophyIndex)
# laff point boost!
oldMaxHp = av.getMaxHp()
newMaxHp = min(ToontownGlobals.MaxHpLimit, oldMaxHp + 1)
self.notify.debug( "cup awarded! new max laff : %s" % newMaxHp)
av.b_setMaxHp(newMaxHp)
# Also, give them a full heal
av.toonUp(newMaxHp)
# Write to the server log
self.air.writeServerEvent("kartingTrophy", avId, "%s" % (trophyIndex))
self.notify.debug( "trophy: " + TTLocalizer.KartTrophyDescriptions[trophyIndex] )
self.notify.debug( "newTrophies: %s" % newTrophies )
self.notify.debug( "trophies: %s" % trophies )
self.notify.debug( "kartingHistory: %s" % history )
## set the new trophies and history if changed
if newHistory:
av.b_setKartingHistory(history)
return newTrophies
#
# personal best methods
#
def checkPersonalBest(self, trackId, time, raceType, numRacers, avId):
"""Check time against top three personal best times for each track."""
av = simbase.air.doId2do.get(avId)
# TODO: what do we do if the av goes away?
if av:
bestTimes = av.getKartingPersonalBestAll()
trackIndex = RaceGlobals.TrackIds.index(trackId)
bestTime = bestTimes[trackIndex]
self.notify.debug( "thisTime: %s, bestTime: %s" % (time, bestTime) )
if (bestTime == 0.0) or (time < bestTime):
bestTimes[trackIndex] = time
self.notify.debug( "new personal best!" )
av.b_setKartingPersonalBest(bestTimes)
self.notify.debug( "personal best: %s" % bestTimes )
#
# high score file writing/reading methods
#
def checkTimeRecord(self, trackId, time, raceType, numRacers, avId):
"""Check a time against current shard record. Returns bonus tickets won if any."""
bonus = 0
newRecord = 0
for period in RaceGlobals.PeriodIds:
for record in range(0, RaceGlobals.NumRecordsPerPeriod):
recordTime = self.trackRecords[trackId][period][record][0]
if time < recordTime:
newRecord = 1
# new record!
self.notify.debug( "new %s record!" % TTLocalizer.RecordPeriodStrings[period] )
# TODO: alert player
# format: time, race type, num racers, racer name
av = simbase.air.doId2do.get(avId)
# TODO: what do we do if the av goes away?
if av:
name = av.name
# insert new record in this spot
self.trackRecords[trackId][period].insert(record, (time, raceType, numRacers, name))
# make sure we haven't gone over record list length
self.trackRecords[trackId][period] = self.trackRecords[trackId][period][:RaceGlobals.NumRecordsPerPeriod]
# notify the leaderboards of the update
self.updateLeaderboards(trackId, period)
bonus = RaceGlobals.PeriodDict[period]
# keep a log for contests, etc.
self.air.writeServerEvent("kartingRecord", avId, "%s|%s|%s" % (period, trackId, time))
else:
self.notify.warning("warning: av not logged in!")
# no need to keep checking times
break
if newRecord:
# write new record out to disk
# TODO: does this need to be cached?
self.updateRecordFile()
return bonus
def updateRecordFile(self):
"""Update current track record in this shard's record file"""
# notify the leader boards that there has been an update
try:
backup = self.filename + '.bu'
if os.path.exists(self.filename):
os.rename(self.filename, backup)
file = open(self.filename, 'w')
file.seek(0)
pickle.dump(self.trackRecords, file)
file.close()
if os.path.exists(backup):
os.remove(backup)
except EnvironmentError:
self.notify.warning(str(sys.exc_info()[1]))
def getFilename(self):
"""Compose the track record filename"""
return "%s%s.trackRecords" % (self.serverDataFolder, self.shard)
def loadRecords(self):
"""Load track record data from default location"""
try:
# Try to open the backup file:
file = open(self.filename + '.bu', 'r')
# Remove the (assumed) broken file:
if os.path.exists(self.filename):
os.remove(self.filename)
except IOError:
# OK, there's no backup file, good.
try:
# Open the real file:
file = open(self.filename, 'r')
except IOError:
# OK, there's no file. Grab the default times.
return self.getRecordTimes()
file.seek(0)
records = self.loadFrom(file)
file.close()
#check for new tracks
for trackId in RaceGlobals.TrackIds:
if not records.has_key(trackId):
records[trackId] = {}
# for each recording period (daily, etc.)
for i in RaceGlobals.PeriodIds:
records[trackId][i] = []
# for each top score
for j in range(0, RaceGlobals.NumRecordsPerPeriod):
records[trackId][i].append(getDefaultRecord(trackId))
# update all leaderboards
self.resetLeaderboards()
return records
def loadFrom(self, file):
"""Load track record data from specified file"""
records = {}
try:
while 1:
records = pickle.load(file)
except EOFError:
pass
return records
def getRecordTimes(self):
"""
Create a set of track records from default times.
Used when no record file can be found.
"""
records = {}
# for each track
for trackId in RaceGlobals.TrackIds:
records[trackId] = {}
# for each recording period (daily, etc.)
for i in RaceGlobals.PeriodIds:
records[trackId][i] = []
# for each top score
for j in range(0, RaceGlobals.NumRecordsPerPeriod):
records[trackId][i].append(getDefaultRecord(trackId))
return records
# TODO: who will call this? HolidayMgrAI?
def resetRecordPeriod(self, period):
"""
Reset the records for a given period.
"""
# for each track
for trackId in RaceGlobals.TrackIds:
# for each top score
for i in range(0, RaceGlobals.NumRecordsPerPeriod):
self.trackRecords[trackId][period][i] = getDefaultRecord(trackId)
# notify the leaderboards that the records have changed
self.updateLeaderboards(trackId, period)
# save new records to disk
self.updateRecordFile()
#
# leaderboard methods
#
def getRecords(self, trackId, period):
return self.trackRecords[trackId][period]
def updateLeaderboards(self, trackId, period):
# notify the leaderboards of the update
messenger.send("UpdateRaceRecord", [(trackId, period)])
def resetLeaderboards(self):
# usually done at startup
for track in RaceGlobals.TrackIds:
for period in RaceGlobals.PeriodIds:
self.updateLeaderboards(track, period)
# classes for the HolidayManagerAI
class KartRecordDailyResetter(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'ResistanceEventMgrAI')
PostName = 'kertRecordDailyReset'
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
def start(self):
# let the holiday system know we started
bboard.post(KartRecordDailyResetter.PostName)
simbase.air.raceMgr.resetRecordPeriod(RaceGlobals.Daily)
def stop(self):
# let the holiday system know we stopped
bboard.remove(KartRecordDailyResetter.PostName)
class KartRecordWeeklyResetter(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'ResistanceEventMgrAI')
PostName = 'kartRecordWeeklyReset'
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
def start(self):
# let the holiday system know we started
bboard.post(KartRecordWeeklyResetter.PostName)
simbase.air.raceMgr.resetRecordPeriod(RaceGlobals.Weekly)
def stop(self):
# let the holiday system know we stopped
bboard.remove(KartRecordWeeklyResetter.PostName)
class CircuitRaceHolidayMgr(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'CircuitRaceHolidayMgr')
PostName = 'CircuitRaceHoliday'
StartStopMsg = 'CircuitRaceHolidayStartStop'
def __init__(self, air, holidayId):
# Because of Silly Saturday and the Circuit Racing Event, these holidays can overlap.
# We will use a counter to determine when to actually stop and start these events so
# the two holidays don't stomp on each other.
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
def start(self):
# let the holiday system know we started
# note: the circuit race holidays can overlap, so we use a counter to control start/stop
bboard.post(CircuitRaceHolidayMgr.PostName, bboard.get(CircuitRaceHolidayMgr.PostName, 0) + 1)
if bboard.get(CircuitRaceHolidayMgr.PostName) == 1:
# tell everyone race night is starting
simbase.air.newsManager.circuitRaceStart()
messenger.send(CircuitRaceHolidayMgr.StartStopMsg)
def stop(self):
# let the holiday system know we stopped
# note: the circuit race holidays can overlap, so we use a counter to control start/stop
bboard.post(CircuitRaceHolidayMgr.PostName, bboard.get(CircuitRaceHolidayMgr.PostName) - 1)
if bboard.get(CircuitRaceHolidayMgr.PostName) == 0:
# tell everyone race night is stopping
simbase.air.newsManager.circuitRaceEnd()
messenger.send(CircuitRaceHolidayMgr.StartStopMsg)