439 lines
17 KiB
Python
439 lines
17 KiB
Python
from panda3d.core import *
|
|
from toontown.toonbase.ToonBaseGlobal import *
|
|
from direct.gui.DirectGui import *
|
|
from direct.distributed.ClockDelta import *
|
|
from toontown.toonbase import ToontownGlobals
|
|
from direct.distributed import DistributedObject
|
|
from direct.directnotify import DirectNotifyGlobal
|
|
from direct.fsm import ClassicFSM, State
|
|
from direct.fsm import State
|
|
import MinigameRulesPanel
|
|
from direct.task.Task import Task
|
|
from toontown.toon import Toon
|
|
from direct.showbase import RandomNumGen
|
|
from toontown.toonbase import TTLocalizer
|
|
import random
|
|
import MinigameGlobals
|
|
from direct.showbase import PythonUtil
|
|
from toontown.toon import TTEmote
|
|
from otp.avatar import Emote
|
|
from otp.distributed.TelemetryLimiter import RotationLimitToH, TLGatherAllAvs
|
|
from otp.ai.MagicWordGlobal import *
|
|
|
|
class DistributedMinigame(DistributedObject.DistributedObject):
|
|
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedMinigame')
|
|
|
|
def __init__(self, cr):
|
|
DistributedObject.DistributedObject.__init__(self, cr)
|
|
self.waitingStartLabel = DirectLabel(text=TTLocalizer.MinigameWaitingForOtherToons, text_fg=VBase4(1, 1, 1, 1), relief=None, pos=(-0.6, 0, -0.75), scale=0.075)
|
|
self.waitingStartLabel.hide()
|
|
self.avIdList = []
|
|
self.remoteAvIdList = []
|
|
self.localAvId = base.localAvatar.doId
|
|
self.frameworkFSM = ClassicFSM.ClassicFSM('DistributedMinigame', [State.State('frameworkInit', self.enterFrameworkInit, self.exitFrameworkInit, ['frameworkRules', 'frameworkCleanup', 'frameworkAvatarExited']),
|
|
State.State('frameworkRules', self.enterFrameworkRules, self.exitFrameworkRules, ['frameworkWaitServerStart', 'frameworkCleanup', 'frameworkAvatarExited']),
|
|
State.State('frameworkWaitServerStart', self.enterFrameworkWaitServerStart, self.exitFrameworkWaitServerStart, ['frameworkGame', 'frameworkCleanup', 'frameworkAvatarExited']),
|
|
State.State('frameworkGame', self.enterFrameworkGame, self.exitFrameworkGame, ['frameworkWaitServerFinish', 'frameworkCleanup', 'frameworkAvatarExited']),
|
|
State.State('frameworkWaitServerFinish', self.enterFrameworkWaitServerFinish, self.exitFrameworkWaitServerFinish, ['frameworkCleanup']),
|
|
State.State('frameworkAvatarExited', self.enterFrameworkAvatarExited, self.exitFrameworkAvatarExited, ['frameworkCleanup']),
|
|
State.State('frameworkCleanup', self.enterFrameworkCleanup, self.exitFrameworkCleanup, [])], 'frameworkInit', 'frameworkCleanup')
|
|
hoodMinigameState = self.cr.playGame.hood.fsm.getStateNamed('minigame')
|
|
hoodMinigameState.addChild(self.frameworkFSM)
|
|
self.rulesDoneEvent = 'rulesDone'
|
|
self.acceptOnce('minigameAbort', self.d_requestExit)
|
|
self.acceptOnce('minigameSkip', self.requestSkip)
|
|
base.curMinigame = self
|
|
self.modelCount = 500
|
|
self.cleanupActions = []
|
|
self.usesSmoothing = 0
|
|
self.usesLookAround = 0
|
|
self.difficultyOverride = None
|
|
self.trolleyZoneOverride = None
|
|
self.hasLocalToon = 0
|
|
self.frameworkFSM.enterInitialState()
|
|
self._telemLimiter = None
|
|
return
|
|
|
|
def addChildGameFSM(self, gameFSM):
|
|
self.frameworkFSM.getStateNamed('frameworkGame').addChild(gameFSM)
|
|
|
|
def removeChildGameFSM(self, gameFSM):
|
|
self.frameworkFSM.getStateNamed('frameworkGame').removeChild(gameFSM)
|
|
|
|
def setUsesSmoothing(self):
|
|
self.usesSmoothing = 1
|
|
|
|
def setUsesLookAround(self):
|
|
self.usesLookAround = 1
|
|
|
|
def getTitle(self):
|
|
return TTLocalizer.DefaultMinigameTitle
|
|
|
|
def getInstructions(self):
|
|
return TTLocalizer.DefaultMinigameInstructions
|
|
|
|
def getMaxDuration(self):
|
|
raise Exception('Minigame implementer: you must override getMaxDuration()')
|
|
|
|
def __createRandomNumGen(self):
|
|
self.notify.debug('BASE: self.doId=0x%08X' % self.doId)
|
|
self.randomNumGen = RandomNumGen.RandomNumGen(self.doId)
|
|
|
|
def destroy(self = self):
|
|
self.notify.debug('BASE: destroying random num gen')
|
|
del self.randomNumGen
|
|
|
|
self.cleanupActions.append(destroy)
|
|
|
|
def generate(self):
|
|
self.notify.debug('BASE: generate, %s' % self.getTitle())
|
|
DistributedObject.DistributedObject.generate(self)
|
|
self.__createRandomNumGen()
|
|
|
|
def announceGenerate(self):
|
|
DistributedObject.DistributedObject.announceGenerate(self)
|
|
if not self.hasLocalToon:
|
|
return
|
|
self.notify.debug('BASE: handleAnnounceGenerate: send setAvatarJoined')
|
|
if base.randomMinigameNetworkPlugPull and random.random() < 1.0 / 25:
|
|
print '*** DOING RANDOM MINIGAME NETWORK-PLUG-PULL BEFORE SENDING setAvatarJoined ***'
|
|
base.cr.pullNetworkPlug()
|
|
self.sendUpdate('setAvatarJoined', [])
|
|
self.normalExit = 1
|
|
count = self.modelCount
|
|
zoneId = 0 #TODO: Make a system for picking minigame backgrounds
|
|
loader.beginBulkLoad('minigame', TTLocalizer.HeadingToMinigameTitle % self.getTitle(), count, 1, TTLocalizer.TIP_MINIGAME, zoneId)
|
|
self.load()
|
|
loader.endBulkLoad('minigame')
|
|
globalClock.syncFrameTime()
|
|
self.onstage()
|
|
|
|
def cleanup(self = self):
|
|
self.notify.debug('BASE: cleanup: normalExit=%s' % self.normalExit)
|
|
self.offstage()
|
|
base.cr.renderFrame()
|
|
if self.normalExit:
|
|
self.sendUpdate('setAvatarExited', [])
|
|
|
|
self.cleanupActions.append(cleanup)
|
|
self._telemLimiter = self.getTelemetryLimiter()
|
|
self.frameworkFSM.request('frameworkRules')
|
|
|
|
def disable(self):
|
|
self.notify.debug('BASE: disable')
|
|
if self._telemLimiter:
|
|
self._telemLimiter.destroy()
|
|
self._telemLimiter = None
|
|
self.frameworkFSM.request('frameworkCleanup')
|
|
taskMgr.remove(self.uniqueName('random-abort'))
|
|
taskMgr.remove(self.uniqueName('random-disconnect'))
|
|
taskMgr.remove(self.uniqueName('random-netplugpull'))
|
|
DistributedObject.DistributedObject.disable(self)
|
|
return
|
|
|
|
def delete(self):
|
|
self.notify.debug('BASE: delete')
|
|
if self.hasLocalToon:
|
|
self.unload()
|
|
self.ignoreAll()
|
|
if self.cr.playGame.hood:
|
|
hoodMinigameState = self.cr.playGame.hood.fsm.getStateNamed('minigame')
|
|
hoodMinigameState.removeChild(self.frameworkFSM)
|
|
self.waitingStartLabel.destroy()
|
|
del self.waitingStartLabel
|
|
del self.frameworkFSM
|
|
DistributedObject.DistributedObject.delete(self)
|
|
|
|
def getTelemetryLimiter(self):
|
|
return TLGatherAllAvs('Minigame', RotationLimitToH)
|
|
|
|
def load(self):
|
|
self.notify.debug('BASE: load')
|
|
Toon.loadMinigameAnims()
|
|
|
|
def onstage(self):
|
|
base.localAvatar.laffMeter.hide()
|
|
|
|
def calcMaxDuration(self = self):
|
|
return (self.getMaxDuration() + MinigameGlobals.rulesDuration) * 1.1
|
|
|
|
if not base.cr.networkPlugPulled():
|
|
if base.randomMinigameAbort:
|
|
maxDuration = calcMaxDuration()
|
|
self.randomAbortDelay = random.random() * maxDuration
|
|
taskMgr.doMethodLater(self.randomAbortDelay, self.doRandomAbort, self.uniqueName('random-abort'))
|
|
if base.randomMinigameDisconnect:
|
|
maxDuration = calcMaxDuration()
|
|
self.randomDisconnectDelay = random.random() * maxDuration
|
|
taskMgr.doMethodLater(self.randomDisconnectDelay, self.doRandomDisconnect, self.uniqueName('random-disconnect'))
|
|
if base.randomMinigameNetworkPlugPull:
|
|
maxDuration = calcMaxDuration()
|
|
self.randomNetPlugPullDelay = random.random() * maxDuration
|
|
taskMgr.doMethodLater(self.randomNetPlugPullDelay, self.doRandomNetworkPlugPull, self.uniqueName('random-netplugpull'))
|
|
|
|
def doRandomAbort(self, task):
|
|
print '*** DOING RANDOM MINIGAME ABORT AFTER %.2f SECONDS ***' % self.randomAbortDelay
|
|
self.d_requestExit()
|
|
return Task.done
|
|
|
|
def doRandomDisconnect(self, task):
|
|
print '*** DOING RANDOM MINIGAME DISCONNECT AFTER %.2f SECONDS ***' % self.randomDisconnectDelay
|
|
self.sendUpdate('setGameReady')
|
|
return Task.done
|
|
|
|
def doRandomNetworkPlugPull(self, task):
|
|
print '*** DOING RANDOM MINIGAME NETWORK-PLUG-PULL AFTER %.2f SECONDS ***' % self.randomNetPlugPullDelay
|
|
base.cr.pullNetworkPlug()
|
|
return Task.done
|
|
|
|
def offstage(self):
|
|
self.notify.debug('BASE: offstage')
|
|
for avId in self.avIdList:
|
|
av = self.getAvatar(avId)
|
|
if av:
|
|
av.detachNode()
|
|
|
|
messenger.send('minigameOffstage')
|
|
|
|
def unload(self):
|
|
self.notify.debug('BASE: unload')
|
|
if hasattr(base, 'curMinigame'):
|
|
del base.curMinigame
|
|
Toon.unloadMinigameAnims()
|
|
|
|
def setParticipants(self, avIds):
|
|
self.avIdList = avIds
|
|
self.numPlayers = len(self.avIdList)
|
|
self.hasLocalToon = self.localAvId in self.avIdList
|
|
if not self.hasLocalToon:
|
|
self.notify.warning('localToon (%s) not in list of minigame players: %s' % (self.localAvId, self.avIdList))
|
|
return
|
|
self.notify.info('BASE: setParticipants: %s' % self.avIdList)
|
|
self.remoteAvIdList = []
|
|
for avId in self.avIdList:
|
|
if avId != self.localAvId:
|
|
self.remoteAvIdList.append(avId)
|
|
self.setSkipCount(0)
|
|
|
|
def setTrolleyZone(self, trolleyZone):
|
|
if not self.hasLocalToon:
|
|
return
|
|
self.notify.debug('BASE: setTrolleyZone: %s' % trolleyZone)
|
|
self.trolleyZone = trolleyZone
|
|
|
|
def setDifficultyOverrides(self, difficultyOverride, trolleyZoneOverride):
|
|
if not self.hasLocalToon:
|
|
return
|
|
if difficultyOverride != MinigameGlobals.NoDifficultyOverride:
|
|
self.difficultyOverride = difficultyOverride / float(MinigameGlobals.DifficultyOverrideMult)
|
|
if trolleyZoneOverride != MinigameGlobals.NoTrolleyZoneOverride:
|
|
self.trolleyZoneOverride = trolleyZoneOverride
|
|
|
|
def setGameReady(self):
|
|
if not self.hasLocalToon:
|
|
return
|
|
self.notify.debug('BASE: setGameReady: Ready for game with avatars: %s' % self.avIdList)
|
|
self.notify.debug(' safezone: %s' % self.getSafezoneId())
|
|
self.notify.debug('difficulty: %s' % self.getDifficulty())
|
|
self.__serverFinished = 0
|
|
for avId in self.remoteAvIdList:
|
|
if avId not in self.cr.doId2do:
|
|
self.notify.warning('BASE: toon %s already left or has not yet arrived; waiting for server to abort the game' % avId)
|
|
return 1
|
|
|
|
for avId in self.remoteAvIdList:
|
|
avatar = self.cr.doId2do[avId]
|
|
event = avatar.uniqueName('disable')
|
|
self.acceptOnce(event, self.handleDisabledAvatar, [avId])
|
|
|
|
def ignoreToonDisable(self = self, event = event):
|
|
self.ignore(event)
|
|
|
|
self.cleanupActions.append(ignoreToonDisable)
|
|
|
|
for avId in self.avIdList:
|
|
avatar = self.getAvatar(avId)
|
|
if avatar:
|
|
if not self.usesSmoothing:
|
|
avatar.stopSmooth()
|
|
if not self.usesLookAround:
|
|
avatar.stopLookAround()
|
|
|
|
def cleanupAvatars(self = self):
|
|
for avId in self.avIdList:
|
|
avatar = self.getAvatar(avId)
|
|
if avatar:
|
|
avatar.stopSmooth()
|
|
avatar.startLookAround()
|
|
|
|
self.cleanupActions.append(cleanupAvatars)
|
|
return 0
|
|
|
|
def setGameStart(self, timestamp):
|
|
if not self.hasLocalToon:
|
|
return
|
|
self.notify.debug('BASE: setGameStart: Starting game')
|
|
self.gameStartTime = globalClockDelta.networkToLocalTime(timestamp)
|
|
self.frameworkFSM.request('frameworkGame')
|
|
|
|
def setGameAbort(self):
|
|
if not self.hasLocalToon:
|
|
return
|
|
self.notify.warning('BASE: setGameAbort: Aborting game')
|
|
self.normalExit = 0
|
|
self.frameworkFSM.request('frameworkCleanup')
|
|
|
|
def gameOver(self):
|
|
if not self.hasLocalToon:
|
|
return
|
|
self.notify.debug('BASE: gameOver')
|
|
self.frameworkFSM.request('frameworkWaitServerFinish')
|
|
|
|
def getAvatar(self, avId):
|
|
if avId in self.cr.doId2do:
|
|
return self.cr.doId2do[avId]
|
|
else:
|
|
self.notify.warning('BASE: getAvatar: No avatar in doId2do with id: ' + str(avId))
|
|
return None
|
|
return None
|
|
|
|
def getAvatarName(self, avId):
|
|
avatar = self.getAvatar(avId)
|
|
if avatar:
|
|
return avatar.getName()
|
|
else:
|
|
return 'Unknown'
|
|
|
|
def isSinglePlayer(self):
|
|
if self.numPlayers == 1:
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
def handleDisabledAvatar(self, avId):
|
|
self.notify.warning('BASE: handleDisabledAvatar: disabled avId: ' + str(avId))
|
|
self.frameworkFSM.request('frameworkAvatarExited')
|
|
|
|
def d_requestExit(self):
|
|
self.notify.debug('BASE: Sending requestExit')
|
|
self.sendUpdate('requestExit', [])
|
|
|
|
def enterFrameworkInit(self):
|
|
self.notify.debug('BASE: enterFrameworkInit')
|
|
self.setEmotes()
|
|
self.cleanupActions.append(self.unsetEmotes)
|
|
|
|
def exitFrameworkInit(self):
|
|
pass
|
|
|
|
def enterFrameworkRules(self):
|
|
self.notify.debug('BASE: enterFrameworkRules')
|
|
self.accept(self.rulesDoneEvent, self.handleRulesDone)
|
|
self.rulesPanel = MinigameRulesPanel.MinigameRulesPanel('MinigameRulesPanel', self.getTitle(), self.getInstructions(), self.rulesDoneEvent, playerCount=len(self.avIdList))
|
|
self.rulesPanel.load()
|
|
self.rulesPanel.enter()
|
|
|
|
def exitFrameworkRules(self):
|
|
self.ignore(self.rulesDoneEvent)
|
|
self.rulesPanel.exit()
|
|
self.rulesPanel.unload()
|
|
del self.rulesPanel
|
|
|
|
def handleRulesDone(self):
|
|
self.notify.debug('BASE: handleRulesDone')
|
|
self.sendUpdate('setAvatarReady', [])
|
|
self.frameworkFSM.request('frameworkWaitServerStart')
|
|
|
|
def setAvatarReady(self):
|
|
messenger.send('disableMinigameSkip')
|
|
|
|
def enterFrameworkWaitServerStart(self):
|
|
self.notify.debug('BASE: enterFrameworkWaitServerStart')
|
|
if self.numPlayers > 1:
|
|
msg = TTLocalizer.MinigameWaitingForOtherToons
|
|
else:
|
|
msg = TTLocalizer.MinigamePleaseWait
|
|
self.waitingStartLabel['text'] = msg
|
|
self.waitingStartLabel.show()
|
|
|
|
def exitFrameworkWaitServerStart(self):
|
|
self.waitingStartLabel.hide()
|
|
|
|
def enterFrameworkGame(self):
|
|
self.notify.debug('BASE: enterFrameworkGame')
|
|
|
|
def exitFrameworkGame(self):
|
|
pass
|
|
|
|
def enterFrameworkWaitServerFinish(self):
|
|
self.notify.debug('BASE: enterFrameworkWaitServerFinish')
|
|
if self.__serverFinished:
|
|
self.frameworkFSM.request('frameworkCleanup')
|
|
|
|
def setGameExit(self):
|
|
if not self.hasLocalToon:
|
|
return
|
|
|
|
self.notify.debug('BASE: setGameExit -- it is now safe to exit the game.')
|
|
if self.frameworkFSM.getCurrentState().getName() != 'frameworkWaitServerFinish':
|
|
self.__serverFinished = 1
|
|
else:
|
|
self.notify.debug("Must wait for server to exit game: ask the framework to cleanup.")
|
|
self.frameworkFSM.request('frameworkCleanup')
|
|
|
|
def exitFrameworkWaitServerFinish(self):
|
|
pass
|
|
|
|
def enterFrameworkAvatarExited(self):
|
|
self.notify.debug('BASE: enterFrameworkAvatarExited')
|
|
|
|
def exitFrameworkAvatarExited(self):
|
|
pass
|
|
|
|
def enterFrameworkCleanup(self):
|
|
self.notify.debug('BASE: enterFrameworkCleanup')
|
|
for action in self.cleanupActions:
|
|
action()
|
|
|
|
self.cleanupActions = []
|
|
self.ignoreAll()
|
|
if self.hasLocalToon:
|
|
messenger.send(self.cr.playGame.hood.minigameDoneEvent)
|
|
|
|
def exitFrameworkCleanup(self):
|
|
pass
|
|
|
|
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(base, 'minigameDifficulty'):
|
|
return float(base.minigameDifficulty)
|
|
return MinigameGlobals.getDifficulty(self.getSafezoneId())
|
|
|
|
def getSafezoneId(self):
|
|
if self.trolleyZoneOverride is not None:
|
|
return self.trolleyZoneOverride
|
|
if hasattr(base, 'minigameSafezoneId'):
|
|
return MinigameGlobals.getSafezoneId(base.minigameSafezoneId)
|
|
return MinigameGlobals.getSafezoneId(self.trolleyZone)
|
|
|
|
def setEmotes(self):
|
|
Emote.globalEmote.disableAll(base.localAvatar)
|
|
|
|
def unsetEmotes(self):
|
|
Emote.globalEmote.releaseAll(base.localAvatar)
|
|
|
|
def requestSkip(self):
|
|
self.sendUpdate('requestSkip')
|
|
|
|
def setSkipCount(self, count):
|
|
messenger.send('gameSkipCountChange', [count, len(self.avIdList)])
|