historical/toontown-classic.git/toontown/minigame/DistributedMinigameAI.py
2024-01-16 11:20:27 -06: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
import MinigameCreatorAI
from direct.task import Task
import random
import MinigameGlobals
from direct.showbase import PythonUtil
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 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 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 xrange(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 = map(lambda x: MinigameGlobals.JellybeanTrolleyHolidayScoreMultiplier * x, 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