from pandac.PandaModules import * from toontown.toonbase.ToonBaseGlobal import * from direct.gui.DirectGui import * from pandac.PandaModules 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 from . 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 from . import MinigameGlobals from direct.showbase import PythonUtil from toontown.toon import TTEmote from otp.avatar import Emote from otp.distributed.TelemetryLimiter import RotationLimitToH, TLGatherAllAvs class DistributedMinigame(DistributedObject.DistributedObject): notify = DirectNotifyGlobal.directNotify.newCategory('DistributedMinigame') def __init__(self, cr): DistributedObject.DistributedObject.__init__(self, cr) self.waitingStartLabel = DirectLabel(text=TTLocalizer.MinigameWaitingForOtherPlayers, 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) 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.startingVotes = {} self.metagameRound = -1 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 loader.beginBulkLoad('minigame', TTLocalizer.HeadingToMinigameTitle % self.getTitle(), count, 1, TTLocalizer.TIP_MINIGAME) 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): self.notify.debug('BASE: onstage') 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) 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) 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 enterFrameworkWaitServerStart(self): self.notify.debug('BASE: enterFrameworkWaitServerStart') if self.numPlayers > 1: msg = TTLocalizer.MinigameWaitingForOtherPlayers 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): print('setGameExit') if not self.hasLocalToon: return self.notify.debug('BASE: setGameExit: now safe to exit game') if self.frameworkFSM.getCurrentState().getName() != 'frameworkWaitServerFinish': print('not waiting') self.__serverFinished = 1 else: print('waiting') 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') print('cleanup') 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 setStartingVotes(self, startingVotesArray): if not len(startingVotesArray) == len(self.avIdList): self.notify.error('length does not match, startingVotes=%s, avIdList=%s' % (startingVotesArray, self.avIdList)) return for index in range(len(self.avIdList)): avId = self.avIdList[index] self.startingVotes[avId] = startingVotesArray[index] self.notify.debug('starting votes = %s' % self.startingVotes) def setMetagameRound(self, metagameRound): self.metagameRound = metagameRound