oldschool-toontown/toontown/minigame/DistributedMinigameAI.py
2019-12-30 01:07:56 -05:00

448 lines
19 KiB
Python

from otp.ai.AIBase import *
from direct.distributed.ClockDelta import *
from toontown.ai.ToonBarrier import *
from direct.distributed import DistributedObjectAI
from direct.fsm import ClassicFSM, State
from direct.fsm import State
from toontown.shtiker import PurchaseManagerAI
from toontown.shtiker import NewbiePurchaseManagerAI
from . import MinigameCreatorAI
from direct.task import Task
import random
from . import MinigameGlobals
from direct.showbase import PythonUtil
from . import TravelGameGlobals
from toontown.toonbase import ToontownGlobals
EXITED = 0
EXPECTED = 1
JOINED = 2
READY = 3
DEFAULT_POINTS = 1
MAX_POINTS = 7
JOIN_TIMEOUT = 40.0 + MinigameGlobals.latencyTolerance
READY_TIMEOUT = MinigameGlobals.MaxLoadTime + MinigameGlobals.rulesDuration + MinigameGlobals.latencyTolerance
EXIT_TIMEOUT = 20.0 + MinigameGlobals.latencyTolerance
class DistributedMinigameAI(DistributedObjectAI.DistributedObjectAI):
notify = directNotify.newCategory('DistributedMinigameAI')
def __init__(self, air, minigameId):
try:
self.DistributedMinigameAI_initialized
except:
self.DistributedMinigameAI_initialized = 1
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
self.minigameId = minigameId
self.frameworkFSM = ClassicFSM.ClassicFSM('DistributedMinigameAI', [State.State('frameworkOff', self.enterFrameworkOff, self.exitFrameworkOff, ['frameworkWaitClientsJoin']),
State.State('frameworkWaitClientsJoin', self.enterFrameworkWaitClientsJoin, self.exitFrameworkWaitClientsJoin, ['frameworkWaitClientsReady', 'frameworkWaitClientsExit', 'frameworkCleanup']),
State.State('frameworkWaitClientsReady', self.enterFrameworkWaitClientsReady, self.exitFrameworkWaitClientsReady, ['frameworkGame', 'frameworkWaitClientsExit', 'frameworkCleanup']),
State.State('frameworkGame', self.enterFrameworkGame, self.exitFrameworkGame, ['frameworkWaitClientsExit', 'frameworkCleanup']),
State.State('frameworkWaitClientsExit', self.enterFrameworkWaitClientsExit, self.exitFrameworkWaitClientsExit, ['frameworkCleanup']),
State.State('frameworkCleanup', self.enterFrameworkCleanup, self.exitFrameworkCleanup, ['frameworkOff'])], 'frameworkOff', 'frameworkOff')
self.frameworkFSM.enterInitialState()
self.avIdList = []
self.stateDict = {}
self.scoreDict = {}
self.difficultyOverride = None
self.trolleyZoneOverride = None
self.metagameRound = -1
self.startingVotes = {}
return
def addChildGameFSM(self, gameFSM):
self.frameworkFSM.getStateNamed('frameworkGame').addChild(gameFSM)
def removeChildGameFSM(self, gameFSM):
self.frameworkFSM.getStateNamed('frameworkGame').removeChild(gameFSM)
def setExpectedAvatars(self, avIds):
self.avIdList = avIds
self.numPlayers = len(self.avIdList)
self.notify.debug('BASE: setExpectedAvatars: expecting avatars: ' + str(self.avIdList))
def setNewbieIds(self, newbieIds):
self.newbieIdList = newbieIds
if len(self.newbieIdList) > 0:
self.notify.debug('BASE: setNewbieIds: %s' % self.newbieIdList)
def setTrolleyZone(self, trolleyZone):
self.trolleyZone = trolleyZone
def setDifficultyOverrides(self, difficultyOverride, trolleyZoneOverride):
self.difficultyOverride = difficultyOverride
if self.difficultyOverride is not None:
self.difficultyOverride = MinigameGlobals.QuantizeDifficultyOverride(difficultyOverride)
self.trolleyZoneOverride = trolleyZoneOverride
return
def setMetagameRound(self, roundNum):
self.metagameRound = roundNum
def _playing(self):
if not hasattr(self, 'gameFSM'):
return False
if self.gameFSM.getCurrentState() == None:
return False
return self.gameFSM.getCurrentState().getName() == 'play'
def _inState(self, states):
if not hasattr(self, 'gameFSM'):
return False
if self.gameFSM.getCurrentState() == None:
return False
return self.gameFSM.getCurrentState().getName() in makeList(states)
def generate(self):
DistributedObjectAI.DistributedObjectAI.generate(self)
self.frameworkFSM.request('frameworkWaitClientsJoin')
def delete(self):
self.notify.debug('BASE: delete: deleting AI minigame object')
del self.frameworkFSM
self.ignoreAll()
DistributedObjectAI.DistributedObjectAI.delete(self)
def isSinglePlayer(self):
if self.numPlayers == 1:
return 1
else:
return 0
def getParticipants(self):
return self.avIdList
def getTrolleyZone(self):
return self.trolleyZone
def getDifficultyOverrides(self):
response = [self.difficultyOverride, self.trolleyZoneOverride]
if response[0] is None:
response[0] = MinigameGlobals.NoDifficultyOverride
else:
response[0] *= MinigameGlobals.DifficultyOverrideMult
response[0] = int(response[0])
if response[1] is None:
response[1] = MinigameGlobals.NoTrolleyZoneOverride
return response
def b_setGameReady(self):
self.setGameReady()
self.d_setGameReady()
def d_setGameReady(self):
self.notify.debug('BASE: Sending setGameReady')
self.sendUpdate('setGameReady', [])
def setGameReady(self):
self.notify.debug('BASE: setGameReady: game ready with avatars: %s' % self.avIdList)
self.normalExit = 1
def b_setGameStart(self, timestamp):
self.d_setGameStart(timestamp)
self.setGameStart(timestamp)
def d_setGameStart(self, timestamp):
self.notify.debug('BASE: Sending setGameStart')
self.sendUpdate('setGameStart', [timestamp])
def setGameStart(self, timestamp):
self.notify.debug('BASE: setGameStart')
def b_setGameExit(self):
self.d_setGameExit()
self.setGameExit()
def d_setGameExit(self):
self.notify.debug('BASE: Sending setGameExit')
self.sendUpdate('setGameExit', [])
def setGameExit(self):
self.notify.debug('BASE: setGameExit')
def setGameAbort(self):
self.notify.debug('BASE: setGameAbort')
self.normalExit = 0
self.sendUpdate('setGameAbort', [])
self.frameworkFSM.request('frameworkCleanup')
def handleExitedAvatar(self, avId):
self.notify.warning('BASE: handleExitedAvatar: avatar id exited: ' + str(avId))
self.stateDict[avId] = EXITED
self.setGameAbort()
def gameOver(self):
self.notify.debug('BASE: gameOver')
self.frameworkFSM.request('frameworkWaitClientsExit')
def enterFrameworkOff(self):
self.notify.debug('BASE: enterFrameworkOff')
def exitFrameworkOff(self):
pass
def enterFrameworkWaitClientsJoin(self):
self.notify.debug('BASE: enterFrameworkWaitClientsJoin')
for avId in self.avIdList:
self.stateDict[avId] = EXPECTED
self.scoreDict[avId] = DEFAULT_POINTS
self.acceptOnce(self.air.getAvatarExitEvent(avId), self.handleExitedAvatar, extraArgs=[avId])
def allAvatarsJoined(self = self):
self.notify.debug('BASE: all avatars joined')
self.b_setGameReady()
self.frameworkFSM.request('frameworkWaitClientsReady')
def handleTimeout(avIds, self = self):
self.notify.debug('BASE: timed out waiting for clients %s to join' % avIds)
self.setGameAbort()
self.__barrier = ToonBarrier('waitClientsJoin', self.uniqueName('waitClientsJoin'), self.avIdList, JOIN_TIMEOUT, allAvatarsJoined, handleTimeout)
def setAvatarJoined(self):
if self.frameworkFSM.getCurrentState().getName() != 'frameworkWaitClientsJoin':
self.notify.debug('BASE: Ignoring setAvatarJoined message')
return
avId = self.air.getAvatarIdFromSender()
self.notify.debug('BASE: setAvatarJoined: avatar id joined: ' + str(avId))
self.air.writeServerEvent('minigame_joined', avId, '%s|%s' % (self.minigameId, self.trolleyZone))
self.stateDict[avId] = JOINED
self.notify.debug('BASE: setAvatarJoined: new states: ' + str(self.stateDict))
self.__barrier.clear(avId)
def exitFrameworkWaitClientsJoin(self):
self.__barrier.cleanup()
del self.__barrier
def enterFrameworkWaitClientsReady(self):
self.notify.debug('BASE: enterFrameworkWaitClientsReady')
def allAvatarsReady(self = self):
self.notify.debug('BASE: all avatars ready')
self.frameworkFSM.request('frameworkGame')
def handleTimeout(avIds, self = self):
self.notify.debug("BASE: timed out waiting for clients %s to report 'ready'" % avIds)
self.setGameAbort()
self.__barrier = ToonBarrier('waitClientsReady', self.uniqueName('waitClientsReady'), self.avIdList, READY_TIMEOUT, allAvatarsReady, handleTimeout)
for avId in list(self.stateDict.keys()):
if self.stateDict[avId] == READY:
self.__barrier.clear(avId)
self.notify.debug(' safezone: %s' % self.getSafezoneId())
self.notify.debug('difficulty: %s' % self.getDifficulty())
def setAvatarReady(self):
if self.frameworkFSM.getCurrentState().getName() not in ['frameworkWaitClientsReady', 'frameworkWaitClientsJoin']:
self.notify.debug('BASE: Ignoring setAvatarReady message')
return
avId = self.air.getAvatarIdFromSender()
self.notify.debug('BASE: setAvatarReady: avatar id ready: ' + str(avId))
self.stateDict[avId] = READY
self.notify.debug('BASE: setAvatarReady: new avId states: ' + str(self.stateDict))
if self.frameworkFSM.getCurrentState().getName() == 'frameworkWaitClientsReady':
self.__barrier.clear(avId)
def exitFrameworkWaitClientsReady(self):
self.__barrier.cleanup()
del self.__barrier
def enterFrameworkGame(self):
self.notify.debug('BASE: enterFrameworkGame')
self.gameStartTime = globalClock.getRealTime()
self.b_setGameStart(globalClockDelta.localToNetworkTime(self.gameStartTime))
def exitFrameworkGame(self):
pass
def enterFrameworkWaitClientsExit(self):
self.notify.debug('BASE: enterFrameworkWaitClientsExit')
self.b_setGameExit()
def allAvatarsExited(self = self):
self.notify.debug('BASE: all avatars exited')
self.frameworkFSM.request('frameworkCleanup')
def handleTimeout(avIds, self = self):
self.notify.debug('BASE: timed out waiting for clients %s to exit' % avIds)
self.frameworkFSM.request('frameworkCleanup')
self.__barrier = ToonBarrier('waitClientsExit', self.uniqueName('waitClientsExit'), self.avIdList, EXIT_TIMEOUT, allAvatarsExited, handleTimeout)
for avId in list(self.stateDict.keys()):
if self.stateDict[avId] == EXITED:
self.__barrier.clear(avId)
def setAvatarExited(self):
if self.frameworkFSM.getCurrentState().getName() != 'frameworkWaitClientsExit':
self.notify.debug('BASE: Ignoring setAvatarExit message')
return
avId = self.air.getAvatarIdFromSender()
self.notify.debug('BASE: setAvatarExited: avatar id exited: ' + str(avId))
self.stateDict[avId] = EXITED
self.notify.debug('BASE: setAvatarExited: new avId states: ' + str(self.stateDict))
self.__barrier.clear(avId)
def exitFrameworkWaitClientsExit(self):
self.__barrier.cleanup()
del self.__barrier
def hasScoreMult(self):
return 1
def enterFrameworkCleanup(self):
self.notify.debug('BASE: enterFrameworkCleanup: normalExit=%s' % self.normalExit)
scoreMult = MinigameGlobals.getScoreMult(self.getSafezoneId())
if not self.hasScoreMult():
scoreMult = 1.0
self.notify.debug('score multiplier: %s' % scoreMult)
for avId in self.avIdList:
self.scoreDict[avId] *= scoreMult
scoreList = []
if not self.normalExit:
randReward = random.randrange(DEFAULT_POINTS, MAX_POINTS + 1)
for avId in self.avIdList:
if self.normalExit:
score = int(self.scoreDict[avId] + 0.5)
else:
score = randReward
if ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY in simbase.air.holidayManager.currentHolidays or ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY_MONTH in simbase.air.holidayManager.currentHolidays:
score *= MinigameGlobals.JellybeanTrolleyHolidayScoreMultiplier
logEvent = False
if score > 255:
score = 255
logEvent = True
elif score < 0:
score = 0
logEvent = True
if logEvent:
self.air.writeServerEvent('suspicious', avId, 'got %s jellybeans playing minigame %s in zone %s' % (score, self.minigameId, self.getSafezoneId()))
scoreList.append(score)
self.requestDelete()
if self.metagameRound > -1:
self.handleMetagamePurchaseManager(scoreList)
else:
self.handleRegularPurchaseManager(scoreList)
self.frameworkFSM.request('frameworkOff')
def handleMetagamePurchaseManager(self, scoreList):
self.notify.debug('self.newbieIdList = %s' % self.newbieIdList)
votesToUse = self.startingVotes
if hasattr(self, 'currentVotes'):
votesToUse = self.currentVotes
votesArray = []
for avId in self.avIdList:
if avId in votesToUse:
votesArray.append(votesToUse[avId])
else:
self.notify.warning('votesToUse=%s does not have avId=%d' % (votesToUse, avId))
votesArray.append(0)
if self.metagameRound < TravelGameGlobals.FinalMetagameRoundIndex:
newRound = self.metagameRound
if not self.minigameId == ToontownGlobals.TravelGameId:
for index in range(len(scoreList)):
votesArray[index] += scoreList[index]
self.notify.debug('votesArray = %s' % votesArray)
desiredNextGame = None
if hasattr(self, 'desiredNextGame'):
desiredNextGame = self.desiredNextGame
numToons = 0
lastAvId = 0
for avId in self.avIdList:
av = simbase.air.doId2do.get(avId)
if av:
numToons += 1
lastAvId = avId
doNewbie = False
if numToons == 1 and lastAvId in self.newbieIdList:
doNewbie = True
if doNewbie:
pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI(self.air, lastAvId, self.avIdList, scoreList, self.minigameId, self.trolleyZone)
MinigameCreatorAI.acquireMinigameZone(self.zoneId)
pm.generateWithRequired(self.zoneId)
else:
pm = PurchaseManagerAI.PurchaseManagerAI(self.air, self.avIdList, scoreList, self.minigameId, self.trolleyZone, self.newbieIdList, votesArray, newRound, desiredNextGame)
pm.generateWithRequired(self.zoneId)
else:
self.notify.debug('last minigame, handling newbies')
if ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY in simbase.air.holidayManager.currentHolidays or ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY_MONTH in simbase.air.holidayManager.currentHolidays:
votesArray = [MinigameGlobals.JellybeanTrolleyHolidayScoreMultiplier * x for x in votesArray]
for id in self.newbieIdList:
pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI(self.air, id, self.avIdList, scoreList, self.minigameId, self.trolleyZone)
MinigameCreatorAI.acquireMinigameZone(self.zoneId)
pm.generateWithRequired(self.zoneId)
if len(self.avIdList) > len(self.newbieIdList):
pm = PurchaseManagerAI.PurchaseManagerAI(self.air, self.avIdList, scoreList, self.minigameId, self.trolleyZone, self.newbieIdList, votesArray=votesArray, metagameRound=self.metagameRound)
pm.generateWithRequired(self.zoneId)
return
def handleRegularPurchaseManager(self, scoreList):
for id in self.newbieIdList:
pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI(self.air, id, self.avIdList, scoreList, self.minigameId, self.trolleyZone)
MinigameCreatorAI.acquireMinigameZone(self.zoneId)
pm.generateWithRequired(self.zoneId)
if len(self.avIdList) > len(self.newbieIdList):
pm = PurchaseManagerAI.PurchaseManagerAI(self.air, self.avIdList, scoreList, self.minigameId, self.trolleyZone, self.newbieIdList)
pm.generateWithRequired(self.zoneId)
def exitFrameworkCleanup(self):
pass
def requestExit(self):
self.notify.debug('BASE: requestExit: client has requested the game to end')
self.setGameAbort()
def local2GameTime(self, timestamp):
return timestamp - self.gameStartTime
def game2LocalTime(self, timestamp):
return timestamp + self.gameStartTime
def getCurrentGameTime(self):
return self.local2GameTime(globalClock.getFrameTime())
def getDifficulty(self):
if self.difficultyOverride is not None:
return self.difficultyOverride
if hasattr(self.air, 'minigameDifficulty'):
return float(self.air.minigameDifficulty)
return MinigameGlobals.getDifficulty(self.getSafezoneId())
def getSafezoneId(self):
if self.trolleyZoneOverride is not None:
return self.trolleyZoneOverride
if hasattr(self.air, 'minigameSafezoneId'):
return MinigameGlobals.getSafezoneId(self.air.minigameSafezoneId)
return MinigameGlobals.getSafezoneId(self.trolleyZone)
def logPerfectGame(self, avId):
self.air.writeServerEvent('perfectMinigame', avId, '%s|%s|%s' % (self.minigameId, self.trolleyZone, self.avIdList))
def logAllPerfect(self):
for avId in self.avIdList:
self.logPerfectGame(avId)
def getStartingVotes(self):
retval = []
for avId in self.avIdList:
if avId in self.startingVotes:
retval.append(self.startingVotes[avId])
else:
self.notify.warning('how did this happen? avId=%d not in startingVotes %s' % (avId, self.startingVotes))
retval.append(0)
return retval
def setStartingVote(self, avId, startingVote):
self.startingVotes[avId] = startingVote
self.notify.debug('setting starting vote of avId=%d to %d' % (avId, startingVote))
def getMetagameRound(self):
return self.metagameRound