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 import functools 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 list(self.avatarEndingPositions.keys()): divisor += 1 oneClientEndingPositions = self.avatarEndingPositions[avId] avIndex = self.avIdList.index(avId) for index in range(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(key=functools.cmp_to_key(compareDistance)) self.scoresAsList = [] totalPointsAdded = 0 for index in range(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 range(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 list(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 list(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 range(len(self.avIdList)): avId = self.avIdList[index] if self.scoreDict[avId] < 0: self.scoreDict[avId] = 1 self.scoresAsList.append(self.scoreDict[avId])