354 lines
15 KiB
Python
354 lines
15 KiB
Python
from pandac.PandaModules import Point3
|
|
from direct.distributed.ClockDelta import globalClockDelta
|
|
from direct.fsm import ClassicFSM, State
|
|
from direct.task import Task
|
|
from toontown.minigame import DistributedMinigameAI
|
|
from toontown.minigame import MinigameGlobals
|
|
from toontown.minigame import IceGameGlobals
|
|
from toontown.ai.ToonBarrier import ToonBarrier
|
|
|
|
class DistributedIceGameAI(DistributedMinigameAI.DistributedMinigameAI):
|
|
notify = directNotify.newCategory('DistributedIceGameAI')
|
|
|
|
def __init__(self, air, minigameId):
|
|
try:
|
|
self.DistributedIceGameAI_initialized
|
|
except:
|
|
self.DistributedIceGameAI_initialized = 1
|
|
DistributedMinigameAI.DistributedMinigameAI.__init__(self, air, minigameId)
|
|
self.gameFSM = ClassicFSM.ClassicFSM('DistributedIceGameAI', [State.State('off', self.enterOff, self.exitOff, ['waitClientsChoices']),
|
|
State.State('waitClientsChoices', self.enterWaitClientsChoices, self.exitWaitClientsChoices, ['cleanup', 'processChoices']),
|
|
State.State('processChoices', self.enterProcessChoices, self.exitProcessChoices, ['waitEndingPositions', 'cleanup']),
|
|
State.State('waitEndingPositions', self.enterWaitEndingPositions, self.exitWaitEndingPositions, ['processEndingPositions', 'cleanup']),
|
|
State.State('processEndingPositions', self.enterProcessEndingPositions, self.exitProcessEndingPositions, ['waitClientsChoices', 'scoreMatch', 'cleanup']),
|
|
State.State('scoreMatch', self.enterScoreMatch, self.exitScoreMatch, ['waitClientsChoices', 'finalResults', 'cleanup']),
|
|
State.State('finalResults', self.enterFinalResults, self.exitFinalResults, ['cleanup']),
|
|
State.State('cleanup', self.enterCleanup, self.exitCleanup, ['off'])], 'off', 'off')
|
|
self.addChildGameFSM(self.gameFSM)
|
|
self.avatarChoices = {}
|
|
self.avatarEndingPositions = {}
|
|
self.curRound = 0
|
|
self.curMatch = 0
|
|
self.finalEndingPositions = [Point3(IceGameGlobals.StartingPositions[0]),
|
|
Point3(IceGameGlobals.StartingPositions[1]),
|
|
Point3(IceGameGlobals.StartingPositions[2]),
|
|
Point3(IceGameGlobals.StartingPositions[3])]
|
|
|
|
def generate(self):
|
|
self.notify.debug('generate')
|
|
DistributedMinigameAI.DistributedMinigameAI.generate(self)
|
|
|
|
def delete(self):
|
|
self.notify.debug('delete')
|
|
taskMgr.remove(self.taskName('wait-choices-timeout'))
|
|
taskMgr.remove(self.taskName('endingPositionsTimeout'))
|
|
del self.gameFSM
|
|
DistributedMinigameAI.DistributedMinigameAI.delete(self)
|
|
|
|
def setGameReady(self):
|
|
self.notify.debug('setGameReady')
|
|
DistributedMinigameAI.DistributedMinigameAI.setGameReady(self)
|
|
self.numTreasures = IceGameGlobals.NumTreasures[self.getSafezoneId()]
|
|
self.numTreasuresTaken = 0
|
|
self.takenTreasuresTable = [0] * self.numTreasures
|
|
self.numPenalties = IceGameGlobals.NumPenalties[self.getSafezoneId()]
|
|
self.numPenaltiesTaken = 0
|
|
self.takenPenaltiesTable = [0] * self.numPenalties
|
|
|
|
def setGameStart(self, timestamp):
|
|
self.notify.debug('setGameStart')
|
|
DistributedMinigameAI.DistributedMinigameAI.setGameStart(self, timestamp)
|
|
self.gameFSM.request('waitClientsChoices')
|
|
|
|
def setGameAbort(self):
|
|
self.notify.debug('setGameAbort')
|
|
if self.gameFSM.getCurrentState():
|
|
self.gameFSM.request('cleanup')
|
|
DistributedMinigameAI.DistributedMinigameAI.setGameAbort(self)
|
|
|
|
def gameOver(self):
|
|
self.notify.debug('gameOver')
|
|
self.gameFSM.request('cleanup')
|
|
DistributedMinigameAI.DistributedMinigameAI.gameOver(self)
|
|
|
|
def enterOff(self):
|
|
self.notify.debug('enterOff')
|
|
|
|
def exitOff(self):
|
|
pass
|
|
|
|
def enterCleanup(self):
|
|
self.notify.debug('enterCleanup')
|
|
self.gameFSM.request('off')
|
|
|
|
def exitCleanup(self):
|
|
pass
|
|
|
|
def enterWaitClientsChoices(self):
|
|
self.notify.debug('enterWaitClientsChoices')
|
|
self.resetChoices()
|
|
self.sendUpdate('setMatchAndRound', [self.curMatch, self.curRound])
|
|
self.sendUpdate('setNewState', ['inputChoice'])
|
|
taskMgr.doMethodLater(IceGameGlobals.InputTimeout, self.waitClientsChoicesTimeout, self.taskName('wait-choices-timeout'))
|
|
self.sendUpdate('setTimerStartTime', [globalClockDelta.getFrameNetworkTime()])
|
|
|
|
def exitWaitClientsChoices(self):
|
|
self.notify.debug('exitWaitClientsChoices')
|
|
taskMgr.remove(self.taskName('wait-choices-timeout'))
|
|
|
|
def enterProcessChoices(self):
|
|
forceAndHeading = []
|
|
for avId in self.avIdList:
|
|
force = self.avatarChoices[avId][0]
|
|
heading = self.avatarChoices[avId][1]
|
|
forceAndHeading.append([force, heading])
|
|
|
|
self.notify.debug('tireInputs = %s' % forceAndHeading)
|
|
self.sendUpdate('setTireInputs', [forceAndHeading])
|
|
self.gameFSM.request('waitEndingPositions')
|
|
|
|
def exitProcessChoices(self):
|
|
pass
|
|
|
|
def enterWaitEndingPositions(self):
|
|
if self.curRound == 0:
|
|
self.takenTreasuresTable = [0] * self.numTreasures
|
|
self.takenPenaltiesTable = [0] * self.numPenalties
|
|
taskMgr.doMethodLater(IceGameGlobals.InputTimeout, self.waitClientsChoicesTimeout, self.taskName('endingPositionsTimeout'))
|
|
self.avatarEndingPositions = {}
|
|
|
|
def exitWaitEndingPositions(self):
|
|
taskMgr.remove(self.taskName('endingPositionsTimeout'))
|
|
|
|
def enterProcessEndingPositions(self):
|
|
averagePos = [Point3(0, 0, 0),
|
|
Point3(0, 0, 0),
|
|
Point3(0, 0, 0),
|
|
Point3(0, 0, 0)]
|
|
divisor = 0
|
|
for avId in self.avatarEndingPositions.keys():
|
|
divisor += 1
|
|
oneClientEndingPositions = self.avatarEndingPositions[avId]
|
|
avIndex = self.avIdList.index(avId)
|
|
for index in xrange(len(oneClientEndingPositions)):
|
|
pos = oneClientEndingPositions[index]
|
|
averagePos[index] += Point3(pos[0], pos[1], pos[2])
|
|
self.notify.debug('index = %d averagePos = %s' % (index, averagePos))
|
|
|
|
sentPos = []
|
|
if divisor:
|
|
for newPos in averagePos:
|
|
newPos /= divisor
|
|
newPos.setZ(IceGameGlobals.TireRadius)
|
|
sentPos.append([newPos[0], newPos[1], newPos[2]])
|
|
|
|
else:
|
|
sentPos = self.finalEndingPositions
|
|
self.sendUpdate('setFinalPositions', [sentPos])
|
|
self.finalEndingPositions = sentPos
|
|
if self.curMatch == IceGameGlobals.NumMatches - 1 and self.curRound == IceGameGlobals.NumRounds - 1:
|
|
self.gameFSM.request('scoreMatch')
|
|
elif self.curRound == IceGameGlobals.NumRounds - 1:
|
|
self.gameFSM.request('scoreMatch')
|
|
else:
|
|
self.curRound += 1
|
|
self.sendUpdate('setMatchAndRound', [self.curMatch, self.curRound])
|
|
self.gameFSM.request('waitClientsChoices')
|
|
|
|
def exitProcessEndingPositions(self):
|
|
pass
|
|
|
|
def enterScoreMatch(self):
|
|
sortedByDistance = []
|
|
for avId in self.avIdList:
|
|
index = self.avIdList.index(avId)
|
|
pos = Point3(*self.finalEndingPositions[index])
|
|
pos.setZ(0)
|
|
sortedByDistance.append((avId, pos.length()))
|
|
|
|
def compareDistance(x, y):
|
|
if x[1] - y[1] > 0:
|
|
return 1
|
|
elif x[1] - y[1] < 0:
|
|
return -1
|
|
else:
|
|
return 0
|
|
|
|
sortedByDistance.sort(cmp=compareDistance)
|
|
self.scoresAsList = []
|
|
totalPointsAdded = 0
|
|
for index in xrange(len(self.avIdList)):
|
|
pos = Point3(*self.finalEndingPositions[index])
|
|
pos.setZ(0)
|
|
length = pos.length()
|
|
points = length / IceGameGlobals.FarthestLength * (IceGameGlobals.PointsInCorner - IceGameGlobals.PointsDeadCenter[self.numPlayers])
|
|
points += IceGameGlobals.PointsDeadCenter[self.numPlayers]
|
|
self.notify.debug('length = %s points=%s avId=%d' % (length, points, avId))
|
|
avId = self.avIdList[index]
|
|
bonusIndex = 0
|
|
for sortIndex in xrange(len(sortedByDistance)):
|
|
if sortedByDistance[sortIndex][0] == avId:
|
|
bonusIndex = sortIndex
|
|
|
|
bonusIndex += 4 - len(self.avIdList)
|
|
pointsToAdd = int(points + 0.5) + IceGameGlobals.BonusPointsForPlace[bonusIndex]
|
|
totalPointsAdded += pointsToAdd
|
|
self.scoreDict[avId] += pointsToAdd
|
|
self.scoresAsList.append(self.scoreDict[avId])
|
|
|
|
self.curMatch += 1
|
|
self.curRound = 0
|
|
self.sendUpdate('setScores', [self.curMatch, self.curRound, self.scoresAsList])
|
|
self.sendUpdate('setNewState', ['scoring'])
|
|
|
|
def allToonsScoringMovieDone(self = self):
|
|
self.notify.debug('allToonsScoringMovieDone')
|
|
if self.curMatch == IceGameGlobals.NumMatches:
|
|
self.gameFSM.request('finalResults')
|
|
else:
|
|
self.gameFSM.request('waitClientsChoices')
|
|
|
|
def handleTimeout(avIds, self = self):
|
|
self.notify.debug('handleTimeout: avatars %s did not report "done"' % avIds)
|
|
if self.curMatch == IceGameGlobals.NumMatches:
|
|
self.gameFSM.request('finalResults')
|
|
else:
|
|
self.gameFSM.request('waitClientsChoices')
|
|
|
|
scoreMovieDuration = IceGameGlobals.FarthestLength * IceGameGlobals.ExpandFeetPerSec
|
|
scoreMovieDuration += totalPointsAdded * IceGameGlobals.ScoreCountUpRate
|
|
self.scoringMovieDoneBarrier = ToonBarrier('waitScoringMovieDone', self.uniqueName('waitScoringMovieDone'), self.avIdList, scoreMovieDuration + MinigameGlobals.latencyTolerance, allToonsScoringMovieDone, handleTimeout)
|
|
|
|
def exitScoreMatch(self):
|
|
self.scoringMovieDoneBarrier.cleanup()
|
|
self.scoringMovieDoneBarrier = None
|
|
return
|
|
|
|
def enterFinalResults(self):
|
|
self.checkScores()
|
|
self.sendUpdate('setNewState', ['finalResults'])
|
|
taskMgr.doMethodLater(IceGameGlobals.ShowScoresDuration, self.__doneShowingScores, self.taskName('waitShowScores'))
|
|
|
|
def exitFinalResults(self):
|
|
taskMgr.remove(self.taskName('waitShowScores'))
|
|
|
|
def __doneShowingScores(self, task):
|
|
self.notify.debug('doneShowingScores')
|
|
self.gameOver()
|
|
return Task.done
|
|
|
|
def waitClientsChoicesTimeout(self, task):
|
|
self.notify.debug('waitClientsChoicesTimeout: did not hear from all clients')
|
|
for avId in self.avatarChoices.keys():
|
|
if self.avatarChoices[avId] == (-1, 0):
|
|
self.avatarChoices[avId] = (0, 0)
|
|
|
|
self.gameFSM.request('processChoices')
|
|
return Task.done
|
|
|
|
def resetChoices(self):
|
|
for avId in self.avIdList:
|
|
self.avatarChoices[avId] = (-1, 0)
|
|
|
|
def setAvatarChoice(self, force, direction):
|
|
avatarId = self.air.getAvatarIdFromSender()
|
|
self.notify.debug('setAvatarChoice: avatar: ' + str(avatarId) + ' votes: ' + str(force) + ' direction: ' + str(direction))
|
|
self.avatarChoices[avatarId] = self.checkChoice(avatarId, force, direction)
|
|
if self.allAvatarsChosen():
|
|
self.notify.debug('setAvatarChoice: all avatars have chosen')
|
|
self.gameFSM.request('processChoices')
|
|
else:
|
|
self.notify.debug('setAvatarChoice: still waiting for more choices')
|
|
|
|
def checkChoice(self, avId, force, direction):
|
|
retForce = force
|
|
retDir = direction
|
|
if retForce < 0:
|
|
retForce = 0
|
|
if retForce > 100:
|
|
retForce = 100
|
|
return (retForce, retDir)
|
|
|
|
def allAvatarsChosen(self):
|
|
for avId in self.avatarChoices.keys():
|
|
choice = self.avatarChoices[avId]
|
|
if choice[0] == -1 and not self.stateDict[avId] == DistributedMinigameAI.EXITED:
|
|
return False
|
|
|
|
return True
|
|
|
|
def endingPositions(self, positions):
|
|
if not self.gameFSM or not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
|
|
return
|
|
self.notify.debug('got endingPositions from client %s' % positions)
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.avatarEndingPositions[avId] = positions
|
|
if self.allAvatarsSentEndingPositions():
|
|
self.gameFSM.request('processEndingPositions')
|
|
|
|
def allAvatarsSentEndingPositions(self):
|
|
if len(self.avatarEndingPositions) == len(self.avIdList):
|
|
return True
|
|
return False
|
|
|
|
def endingPositionsTimeout(self, task):
|
|
self.notify.debug('endingPositionsTimeout : did not hear from all clients')
|
|
self.gameFSM.request('processEndingPositions')
|
|
return Task.done
|
|
|
|
def reportScoringMovieDone(self):
|
|
if not self.gameFSM or not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'scoreMatch':
|
|
return
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.notify.debug('reportScoringMovieDone: avatar %s is done' % avId)
|
|
self.scoringMovieDoneBarrier.clear(avId)
|
|
|
|
def claimTreasure(self, treasureNum):
|
|
if not self.gameFSM or not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
|
|
return
|
|
avId = self.air.getAvatarIdFromSender()
|
|
if avId not in self.scoreDict:
|
|
self.notify.warning('PROBLEM: avatar %s called claimTreasure(%s) but he is not in the scoreDict: %s. avIdList is: %s' % (avId,
|
|
treasureNum,
|
|
self.scoreDict,
|
|
self.avIdList))
|
|
return
|
|
if treasureNum < 0 or treasureNum >= self.numTreasures:
|
|
self.air.writeServerEvent('warning', treasureNum, 'MazeGameAI.claimTreasure treasureNum out of range')
|
|
return
|
|
if self.takenTreasuresTable[treasureNum]:
|
|
return
|
|
self.takenTreasuresTable[treasureNum] = 1
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.sendUpdate('setTreasureGrabbed', [avId, treasureNum])
|
|
self.scoreDict[avId] += 1
|
|
self.numTreasuresTaken += 1
|
|
|
|
def claimPenalty(self, penaltyNum):
|
|
if not self.gameFSM or not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
|
|
return
|
|
avId = self.air.getAvatarIdFromSender()
|
|
if avId not in self.scoreDict:
|
|
self.notify.warning('PROBLEM: avatar %s called claimPenalty(%s) but he is not in the scoreDict: %s. avIdList is: %s' % (avId,
|
|
penaltyNum,
|
|
self.scoreDict,
|
|
self.avIdList))
|
|
return
|
|
if penaltyNum < 0 or penaltyNum >= self.numPenalties:
|
|
self.air.writeServerEvent('warning', penaltyNum, 'IceGameAI.claimPenalty penaltyNum out of range')
|
|
return
|
|
if self.takenPenaltiesTable[penaltyNum]:
|
|
return
|
|
self.takenPenaltiesTable[penaltyNum] = 1
|
|
avId = self.air.getAvatarIdFromSender()
|
|
self.sendUpdate('setPenaltyGrabbed', [avId, penaltyNum])
|
|
self.scoreDict[avId] -= 1
|
|
self.numPenaltiesTaken += 1
|
|
|
|
def checkScores(self):
|
|
self.scoresAsList = []
|
|
for index in xrange(len(self.avIdList)):
|
|
avId = self.avIdList[index]
|
|
if self.scoreDict[avId] < 0:
|
|
self.scoreDict[avId] = 1
|
|
self.scoresAsList.append(self.scoreDict[avId])
|