class DistributedPartyTeamActivity(DistributedPartyActivity): notify = directNotify.newCategory('DistributedPartyTeamActivity') def __init__(self, cr, activityId, startDelay = PartyGlobals.TeamActivityStartDelay, balanceTeams = False): DistributedPartyActivity.__init__(self, cr, activityId, PartyGlobals.ActivityTypes.GuestInitiated, wantRewardGui=True) self.notify.debug('__init__') self.toonIds = ([], []) self.isLocalToonPlaying = False self.localToonTeam = None self.advantage = 1.0 self.waitToStartTimestamp = None self._maxPlayersPerTeam = 0 self._minPlayersPerTeam = 0 self._duration = 0 self._startDelay = config.GetFloat('party-team-activity-start-delay', startDelay) self._willBalanceTeams = False # balanceTeams self._currentStatus = '' def load(self): DistributedPartyActivity.load(self) self.teamActivityGui = TeamActivityGui(self) self.activityFSM = TeamActivityFSM(self) def unload(self): DistributedPartyActivity.unload(self) del self.activityFSM self.teamActivityGui.unload() del self.teamActivityGui if hasattr(self, 'toonIds'): del self.toonIds del self.isLocalToonPlaying del self.localToonTeam del self.advantage del self.waitToStartTimestamp def handleToonShifted(self, toonId): self.notify.error('handleToonShifted( toonId=%d ) must be overriden by child class.' % toonId) def handleToonSwitchedTeams(self, toonId): if toonId == base.localAvatar.doId and self._canSwitchTeams: if self.isState('WaitForEnough') or self.isState('WaitToStart'): self.teamActivityGui.enableExitButton() self.teamActivityGui.enableSwitchButton() def handleToonDisabled(self, toonId): self.notify.error('handleToonDisabled( toonId=%d ) must be overriden by child class.' % toonId) def setPlayersPerTeam(self, min, max): self._minPlayersPerTeam = min self._maxPlayersPerTeam = max def setDuration(self, duration): self._duration = duration def setCanSwitchTeams(self, canSwitchTeams): self._canSwitchTeams = canSwitchTeams def d_toonJoinRequest(self, team): if self.isLocalToonInActivity(): return if self.activityFSM.state in ('WaitForEnough', 'WaitToStart') and self._localToonRequestStatus is None: base.cr.playGame.getPlace().fsm.request('activity') self.localToonJoining() self.sendUpdate('toonJoinRequest', [team]) def d_toonExitRequest(self): toonId = base.localAvatar.doId team = self.getTeam(toonId) if team is not None: if self._localToonRequestStatus is None: self.localToonExiting() self.sendUpdate('toonExitRequest', [team]) else: self.notify.warning('Not sending exitRequest as localToon has no team.') def joinRequestDenied(self, reason): DistributedPartyActivity.joinRequestDenied(self, reason) self.notify.debug('joinRequestDenied') if reason == PartyGlobals.DenialReasons.Full: self.showMessage(TTLocalizer.PartyTeamActivityTeamFull) elif reason == PartyGlobals.DenialReasons.Default: self.showMessage(TTLocalizer.PartyTeamActivityJoinDenied % self.getTitle()) def exitRequestDenied(self, reason): DistributedPartyActivity.exitRequestDenied(self, reason) if reason == PartyGlobals.DenialReasons.Default: self.showMessage(TTLocalizer.PartyTeamActivityExitDenied % self.getTitle()) if self.isLocalToonPlaying and (self.isState('WaitToStart') or self.isState('WaitForEnough')): self.teamActivityGui.enableExitButton() if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() def d_toonSwitchTeamRequest(self): if not self._canSwitchTeams: return self.sendUpdate('toonSwitchTeamRequest') def switchTeamRequestDenied(self, reason): self.notify.debug('switchTeamRequestDenied') if reason == PartyGlobals.DenialReasons.Full: self.showMessage(TTLocalizer.PartyTeamActivityTeamFull, endState='activity') elif reason == PartyGlobals.DenialReasons.Default: self.showMessage(TTLocalizer.PartyTeamActivitySwitchDenied, endState='activity') if self.isLocalToonPlaying and (self.isState('WaitToStart') or self.isState('WaitForEnough')) and self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() def setToonsPlaying(self, leftTeamToonIds, rightTeamToonIds): newToonIds = [leftTeamToonIds, rightTeamToonIds] exitedToons, joinedToons, shifters, switchers = self.getToonsPlayingChanges(self.toonIds, newToonIds) if base.localAvatar.doId in exitedToons: self.localToonExiting() self._processExitedToons(exitedToons) self.setToonIds([leftTeamToonIds, rightTeamToonIds]) self._processJoinedToons(joinedToons) for toonId in shifters: self.handleToonShifted(toonId) if self._canSwitchTeams: for toonId in switchers: self.handleToonSwitchedTeams(toonId) if self.isState('WaitForEnough'): self._updateWaitForEnoughStatus() def _updateWaitForEnoughStatus(self): amount = self.getNumToonsNeededToStart() if self._willBalanceTeams: text = TTLocalizer.PartyTeamActivityForMoreWithBalance else: text = TTLocalizer.PartyTeamActivityForMore if amount > 1: plural = TTLocalizer.PartyTeamActivityForMorePlural else: plural = '' self.setStatus(text % (amount, plural)) def setState(self, newState, timestamp, data): DistributedPartyActivity.setState(self, newState, timestamp) if newState == 'WaitToStart': self.activityFSM.request(newState, timestamp) elif newState == 'Conclusion': self.activityFSM.request(newState, data) else: self.activityFSM.request(newState) def d_toonReady(self): self.sendUpdate('toonReady') def setAdvantage(self, advantage): self.advantage = advantage def handleToonJoined(self, toonId): if toonId == base.localAvatar.doId: self.isLocalToonPlaying = True self.localToonTeam = self.getTeam(base.localAvatar.doId) self.teamActivityGui.load() self.teamActivityGui.enableExitButton() if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() if self.activityFSM.state == 'WaitToStart': self.showWaitToStartCountdown() else: self.showStatus() def handleToonExited(self, toonId): if toonId == base.localAvatar.doId: self.hideStatus() self.teamActivityGui.disableExitButton() if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() if self.activityFSM.state == 'WaitToStart': self.hideWaitToStartCountdown() self.teamActivityGui.unload() self.isLocalToonPlaying = False self.localToonTeam = None def handleRulesDone(self): self.notify.debug('handleRulesDone') self.d_toonReady() self.activityFSM.request('WaitForServer') def handleGameTimerExpired(self): pass def getToonsPlayingChanges(self, oldToonIds, newToonIds): oldLeftTeam = oldToonIds[0] oldRightTeam = oldToonIds[1] newLeftTeam = newToonIds[0] newRightTeam = newToonIds[1] oldToons = set(oldLeftTeam + oldRightTeam) newToons = set(newLeftTeam + newRightTeam) exitedToons = oldToons.difference(newToons) joinedToons = newToons.difference(oldToons) shifters = [] if self._canSwitchTeams: switchers = list(set(oldLeftTeam) & set(newRightTeam)) + list(set(oldRightTeam) & set(newLeftTeam)) else: switchers = [] for i in range(len(PartyGlobals.TeamActivityTeams)): persistentToons = set(oldToonIds[i]) & set(newToonIds[i]) for toonId in persistentToons: if oldToonIds[i].index(toonId) != newToonIds[i].index(toonId): shifters.append(toonId) return (list(exitedToons), list(joinedToons), shifters, switchers) def getMaxPlayersPerTeam(self): return self._maxPlayersPerTeam def getMinPlayersPerTeam(self): return self._minPlayersPerTeam def getNumToonsNeededToStart(self): if self._willBalanceTeams: return abs(self._minPlayersPerTeam * 2 - self.getNumToonsPlaying()) else: return self._minPlayersPerTeam def getToonIdsAsList(self): return self.toonIds[0] + self.toonIds[1] def getNumToonsPlaying(self): return len(self.toonIds[0]) + len(self.toonIds[1]) def getNumToonsInTeam(self, team): return len(self.toonIds[team]) def getTeam(self, toonId): for i in range(len(PartyGlobals.TeamActivityTeams)): if self.toonIds[i].count(toonId) > 0: return i else: return None def getIndex(self, toonId, team): if self.toonIds[team].count(toonId) > 0: return self.toonIds[team].index(toonId) else: return None def _joinLeftTeam(self, collEntry): if self.isLocalToonInActivity(): return self.d_toonJoinRequest(PartyGlobals.TeamActivityTeams.LeftTeam) def _joinRightTeam(self, collEntry): if self.isLocalToonInActivity(): return self.d_toonJoinRequest(PartyGlobals.TeamActivityTeams.RightTeam) def showWaitToStartCountdown(self): if self.waitToStartTimestamp is None: self.notify.warning('showWaitToStartCountdown was called when self.waitToStartTimestamp was None') return self.teamActivityGui.showWaitToStartCountdown(self._startDelay, self.waitToStartTimestamp, almostDoneCallback=self._onCountdownAlmostDone) self.showStatus() self.teamActivityGui.enableExitButton() def _onCountdownAlmostDone(self): if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() def hideWaitToStartCountdown(self): self.teamActivityGui.hideWaitToStartCountdown() self.teamActivityGui.disableExitButton() self.hideStatus() def setStatus(self, text): self._currentStatus = text if self.isLocalToonPlaying: self.showStatus() def showStatus(self): if self.teamActivityGui is not None: self.teamActivityGui.showStatus(self._currentStatus) def hideStatus(self): if self.teamActivityGui is not None: self.teamActivityGui.hideStatus() def toonsCanSwitchTeams(self): return self._canSwitchTeams def isState(self, state): return hasattr(self, 'activityFSM') and self.activityFSM.getCurrentOrNextState() == state def startWaitForEnough(self): self.notify.debug('startWaitForEnough') self.advantage = 1.0 self._updateWaitForEnoughStatus() if self.isLocalToonPlaying: self.teamActivityGui.enableExitButton() if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() def finishWaitForEnough(self): self.notify.debug('finishWaitForEnough') def startWaitToStart(self, waitStartTimestamp): self.notify.debug('startWaitToStart') self.waitToStartTimestamp = globalClockDelta.networkToLocalTime(waitStartTimestamp) self._updateWaitForEnoughStatus() self.setStatus(TTLocalizer.PartyTeamActivityWaitingToStart) if self.isLocalToonPlaying: self.showWaitToStartCountdown() def finishWaitToStart(self): self.notify.debug('finishWaitToStart') if self.isLocalToonPlaying: self.hideWaitToStartCountdown() if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() self.waitToStartTimestamp = None def startRules(self): self.notify.debug('startRules') if self.isLocalToonPlaying: DistributedPartyActivity.startRules(self) def finishRules(self): self.notify.debug('finishRules') if self.isLocalToonPlaying: DistributedPartyActivity.finishRules(self) if self.activityFSM.getCurrentOrNextState() == 'WaitForEnough': DistributedPartyActivity.finishRules(self) def startWaitForServer(self): self.notify.debug('startWaitForServer') self.setStatus(TTLocalizer.PartyTeamActivityWaitingForOtherPlayers) def finishWaitForServer(self): self.notify.debug('finishWaitForServer') if self.isLocalToonPlaying: self.hideStatus() def startActive(self): self.notify.debug('startActive') if self.isLocalToonPlaying: self.hideStatus() self.teamActivityGui.showTimer(self._duration) def finishActive(self): self.notify.debug('finishActive') if self.isLocalToonPlaying: self.teamActivityGui.hideTimer() self.hideStatus() def startConclusion(self, data): self.notify.debug('startConclusion') self.setStatus('') if self.isLocalToonPlaying: self.localToonExiting() def finishConclusion(self): self.notify.debug('finishConclusion') if self.isLocalToonPlaying: self.hideStatus()
class DistributedPartyTeamActivity(DistributedPartyActivity): notify = directNotify.newCategory('DistributedPartyTeamActivity') def __init__(self, cr, activityId, startDelay=PartyGlobals.TeamActivityStartDelay, balanceTeams=False): DistributedPartyActivity.__init__( self, cr, activityId, PartyGlobals.ActivityTypes.GuestInitiated, wantRewardGui=True) self.notify.debug('__init__') self.toonIds = ([], []) self.isLocalToonPlaying = False self.localToonTeam = None self.advantage = 1.0 self.waitToStartTimestamp = None self._maxPlayersPerTeam = 0 self._minPlayersPerTeam = 0 self._duration = 0 self._startDelay = config.GetFloat('party-team-activity-start-delay', startDelay) self._willBalanceTeams = balanceTeams self._currentStatus = '' def load(self): DistributedPartyActivity.load(self) self.teamActivityGui = TeamActivityGui(self) self.activityFSM = TeamActivityFSM(self) def unload(self): DistributedPartyActivity.unload(self) del self.activityFSM self.teamActivityGui.unload() del self.teamActivityGui if hasattr(self, 'toonIds'): del self.toonIds del self.isLocalToonPlaying del self.localToonTeam del self.advantage del self.waitToStartTimestamp def handleToonShifted(self, toonId): self.notify.error( 'handleToonShifted( toonId=%d ) must be overriden by child class.' % toonId) def handleToonSwitchedTeams(self, toonId): if toonId == base.localAvatar.doId and self._canSwitchTeams: if self.isState('WaitForEnough') or self.isState('WaitToStart'): self.teamActivityGui.enableExitButton() self.teamActivityGui.enableSwitchButton() def handleToonDisabled(self, toonId): self.notify.error( 'handleToonDisabled( toonId=%d ) must be overriden by child class.' % toonId) def setPlayersPerTeam(self, min, max): self._minPlayersPerTeam = min self._maxPlayersPerTeam = max def setDuration(self, duration): self._duration = duration def setCanSwitchTeams(self, canSwitchTeams): self._canSwitchTeams = canSwitchTeams def d_toonJoinRequest(self, team): if self.isLocalToonInActivity(): return if self.activityFSM.state in ( 'WaitForEnough', 'WaitToStart') and self._localToonRequestStatus is None: base.cr.playGame.getPlace().fsm.request('activity') self.localToonJoining() self.sendUpdate('toonJoinRequest', [team]) def d_toonExitRequest(self): toonId = base.localAvatar.doId team = self.getTeam(toonId) if team is not None: if self._localToonRequestStatus is None: self.localToonExiting() self.sendUpdate('toonExitRequest', [team]) else: self.notify.warning( 'Not sending exitRequest as localToon has no team.') def joinRequestDenied(self, reason): DistributedPartyActivity.joinRequestDenied(self, reason) self.notify.debug('joinRequestDenied') if reason == PartyGlobals.DenialReasons.Full: self.showMessage(TTLocalizer.PartyTeamActivityTeamFull) elif reason == PartyGlobals.DenialReasons.Default: self.showMessage(TTLocalizer.PartyTeamActivityJoinDenied % self.getTitle()) def exitRequestDenied(self, reason): DistributedPartyActivity.exitRequestDenied(self, reason) if reason == PartyGlobals.DenialReasons.Default: self.showMessage(TTLocalizer.PartyTeamActivityExitDenied % self.getTitle()) if self.isLocalToonPlaying and (self.isState('WaitToStart') or self.isState('WaitForEnough')): self.teamActivityGui.enableExitButton() if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() def d_toonSwitchTeamRequest(self): if not self._canSwitchTeams: return self.sendUpdate('toonSwitchTeamRequest') def switchTeamRequestDenied(self, reason): self.notify.debug('switchTeamRequestDenied') if reason == PartyGlobals.DenialReasons.Full: self.showMessage(TTLocalizer.PartyTeamActivityTeamFull, endState='activity') elif reason == PartyGlobals.DenialReasons.Default: self.showMessage(TTLocalizer.PartyTeamActivitySwitchDenied, endState='activity') if self.isLocalToonPlaying and ( self.isState('WaitToStart') or self.isState('WaitForEnough')) and self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() def setToonsPlaying(self, leftTeamToonIds, rightTeamToonIds): newToonIds = [leftTeamToonIds, rightTeamToonIds] exitedToons, joinedToons, shifters, switchers = self.getToonsPlayingChanges( self.toonIds, newToonIds) if base.localAvatar.doId in exitedToons: self.localToonExiting() self._processExitedToons(exitedToons) self.setToonIds([leftTeamToonIds, rightTeamToonIds]) self._processJoinedToons(joinedToons) for toonId in shifters: self.handleToonShifted(toonId) if self._canSwitchTeams: for toonId in switchers: self.handleToonSwitchedTeams(toonId) if self.isState('WaitForEnough'): self._updateWaitForEnoughStatus() def _updateWaitForEnoughStatus(self): amount = self.getNumToonsNeededToStart() if self._willBalanceTeams: text = TTLocalizer.PartyTeamActivityForMoreWithBalance else: text = TTLocalizer.PartyTeamActivityForMore if amount > 1: plural = TTLocalizer.PartyTeamActivityForMorePlural else: plural = '' self.setStatus(text % (amount, plural)) def setState(self, newState, timestamp, data): DistributedPartyActivity.setState(self, newState, timestamp) if newState == 'WaitToStart': self.activityFSM.request(newState, timestamp) elif newState == 'Conclusion': self.activityFSM.request(newState, data) else: self.activityFSM.request(newState) def d_toonReady(self): self.sendUpdate('toonReady') def setAdvantage(self, advantage): self.advantage = advantage def handleToonJoined(self, toonId): if toonId == base.localAvatar.doId: self.isLocalToonPlaying = True self.localToonTeam = self.getTeam(base.localAvatar.doId) self.teamActivityGui.load() self.teamActivityGui.enableExitButton() if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() if self.activityFSM.state == 'WaitToStart': self.showWaitToStartCountdown() else: self.showStatus() def handleToonExited(self, toonId): if toonId == base.localAvatar.doId: self.hideStatus() self.teamActivityGui.disableExitButton() if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() if self.activityFSM.state == 'WaitToStart': self.hideWaitToStartCountdown() self.teamActivityGui.unload() self.isLocalToonPlaying = False self.localToonTeam = None def handleRulesDone(self): self.notify.debug('handleRulesDone') self.d_toonReady() self.activityFSM.request('WaitForServer') def handleGameTimerExpired(self): pass def getToonsPlayingChanges(self, oldToonIds, newToonIds): oldLeftTeam = oldToonIds[0] oldRightTeam = oldToonIds[1] newLeftTeam = newToonIds[0] newRightTeam = newToonIds[1] oldToons = set(oldLeftTeam + oldRightTeam) newToons = set(newLeftTeam + newRightTeam) exitedToons = oldToons.difference(newToons) joinedToons = newToons.difference(oldToons) shifters = [] if self._canSwitchTeams: switchers = list(set(oldLeftTeam) & set(newRightTeam)) + list( set(oldRightTeam) & set(newLeftTeam)) else: switchers = [] for i in range(len(PartyGlobals.TeamActivityTeams)): persistentToons = set(oldToonIds[i]) & set(newToonIds[i]) for toonId in persistentToons: if oldToonIds[i].index(toonId) != newToonIds[i].index(toonId): shifters.append(toonId) return (list(exitedToons), list(joinedToons), shifters, switchers) def getMaxPlayersPerTeam(self): return self._maxPlayersPerTeam def getMinPlayersPerTeam(self): return self._minPlayersPerTeam def getNumToonsNeededToStart(self): if self._willBalanceTeams: return abs(self._minPlayersPerTeam * 2 - self.getNumToonsPlaying()) else: return self._minPlayersPerTeam def getToonIdsAsList(self): return self.toonIds[0] + self.toonIds[1] def getNumToonsPlaying(self): return len(self.toonIds[0]) + len(self.toonIds[1]) def getNumToonsInTeam(self, team): return len(self.toonIds[team]) def getTeam(self, toonId): for i in range(len(PartyGlobals.TeamActivityTeams)): if self.toonIds[i].count(toonId) > 0: return i else: return None def getIndex(self, toonId, team): if self.toonIds[team].count(toonId) > 0: return self.toonIds[team].index(toonId) else: return None def _joinLeftTeam(self, collEntry): if self.isLocalToonInActivity(): return self.d_toonJoinRequest(PartyGlobals.TeamActivityTeams.LeftTeam) def _joinRightTeam(self, collEntry): if self.isLocalToonInActivity(): return self.d_toonJoinRequest(PartyGlobals.TeamActivityTeams.RightTeam) def showWaitToStartCountdown(self): if self.waitToStartTimestamp is None: self.notify.warning( 'showWaitToStartCountdown was called when self.waitToStartTimestamp was None' ) return self.teamActivityGui.showWaitToStartCountdown( self._startDelay, self.waitToStartTimestamp, almostDoneCallback=self._onCountdownAlmostDone) self.showStatus() self.teamActivityGui.enableExitButton() def _onCountdownAlmostDone(self): if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() def hideWaitToStartCountdown(self): self.teamActivityGui.hideWaitToStartCountdown() self.teamActivityGui.disableExitButton() self.hideStatus() def setStatus(self, text): self._currentStatus = text if self.isLocalToonPlaying: self.showStatus() def showStatus(self): if self.teamActivityGui is not None: self.teamActivityGui.showStatus(self._currentStatus) def hideStatus(self): if self.teamActivityGui is not None: self.teamActivityGui.hideStatus() def toonsCanSwitchTeams(self): return self._canSwitchTeams def isState(self, state): return hasattr(self, 'activityFSM' ) and self.activityFSM.getCurrentOrNextState() == state def startWaitForEnough(self): self.notify.debug('startWaitForEnough') self.advantage = 1.0 self._updateWaitForEnoughStatus() if self.isLocalToonPlaying: self.teamActivityGui.enableExitButton() if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() def finishWaitForEnough(self): self.notify.debug('finishWaitForEnough') def startWaitToStart(self, waitStartTimestamp): self.notify.debug('startWaitToStart') self.waitToStartTimestamp = globalClockDelta.networkToLocalTime( waitStartTimestamp) self._updateWaitForEnoughStatus() self.setStatus(TTLocalizer.PartyTeamActivityWaitingToStart) if self.isLocalToonPlaying: self.showWaitToStartCountdown() def finishWaitToStart(self): self.notify.debug('finishWaitToStart') if self.isLocalToonPlaying: self.hideWaitToStartCountdown() if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() self.waitToStartTimestamp = None def startRules(self): self.notify.debug('startRules') if self.isLocalToonPlaying: DistributedPartyActivity.startRules(self) def finishRules(self): self.notify.debug('finishRules') if self.isLocalToonPlaying: DistributedPartyActivity.finishRules(self) if self.activityFSM.getCurrentOrNextState() == 'WaitForEnough': DistributedPartyActivity.finishRules(self) def startWaitForServer(self): self.notify.debug('startWaitForServer') self.setStatus(TTLocalizer.PartyTeamActivityWaitingForOtherPlayers) def finishWaitForServer(self): self.notify.debug('finishWaitForServer') if self.isLocalToonPlaying: self.hideStatus() def startActive(self): self.notify.debug('startActive') if self.isLocalToonPlaying: self.hideStatus() self.teamActivityGui.showTimer(self._duration) def finishActive(self): self.notify.debug('finishActive') if self.isLocalToonPlaying: self.teamActivityGui.hideTimer() self.hideStatus() def startConclusion(self, data): self.notify.debug('startConclusion') self.setStatus('') if self.isLocalToonPlaying: self.localToonExiting() def finishConclusion(self): self.notify.debug('finishConclusion') if self.isLocalToonPlaying: self.hideStatus()
class DistributedPartyTeamActivity(DistributedPartyActivity): notify = directNotify.newCategory("DistributedPartyTeamActivity") def __init__(self, cr, activityId, startDelay=PartyGlobals.TeamActivityStartDelay, balanceTeams=False): """ Parameters cr is the instance of ClientRepository activityId is the activity's id (set in PartyGlobals) """ DistributedPartyActivity.__init__( self, cr, activityId, PartyGlobals.ActivityTypes.GuestInitiated, wantRewardGui=True, ) self.notify.debug("__init__") self.toonIds = ( [], # left team toon Ids [], # right team toon Ids ) self.isLocalToonPlaying = False self.localToonTeam = None # a PartyGlobals.TeamActivityTeams value # used to even out a lopsided game self.advantage = 1.0 self.waitToStartTimestamp = None # tracks when WaitToStart began for late comers # These are required fields set by the ai self._maxPlayersPerTeam = 0 self._minPlayersPerTeam = 0 self._duration = 0 self._startDelay = ConfigVariableDouble( "party-team-activity-start-delay", startDelay).getValue() self._willBalanceTeams = balanceTeams self._currentStatus = "" def load(self): """Load the necessary assets""" DistributedPartyActivity.load(self) assert (self.notify.debug("load")) self.teamActivityGui = TeamActivityGui(self) # create state machine and set initial state self.activityFSM = TeamActivityFSM(self) def unload(self): DistributedPartyActivity.unload(self) del self.activityFSM self.teamActivityGui.unload() del self.teamActivityGui # delete variables if hasattr(self, "toonIds"): del self.toonIds del self.isLocalToonPlaying del self.localToonTeam del self.advantage del self.waitToStartTimestamp #=============================================================================== # Should be overridden by child class #=============================================================================== def handleToonShifted(self, toonId): """Toon successfully shifted teams""" self.notify.error( "handleToonShifted( toonId=%d ) must be overriden by child class." % toonId) def handleToonSwitchedTeams(self, toonId): """Toon successfully shifted teams""" if toonId == base.localAvatar.doId and self._canSwitchTeams: if self.isState("WaitForEnough") or self.isState("WaitToStart"): self.teamActivityGui.enableExitButton() self.teamActivityGui.enableSwitchButton() def handleToonDisabled(self, toonId): """Toon/player has unexpectedly dropped out of the activity (exited game, crashed, disconnected, etc)""" self.notify.error( "handleToonDisabled( toonId=%d ) must be overriden by child class." % toonId) #def handleRewardDone(self): # DistributedPartyTeamActivity.handleRewardDone(self) #=============================================================================== # Distributed Methods #=============================================================================== # broadcast required def setPlayersPerTeam(self, min, max): """ Coming from the AI, it sets the minimimum and maximum players that can join a team. """ assert (self.notify.debug("setPlayersPerTeam min=%d max=%d" % (min, max))) self._minPlayersPerTeam = min self._maxPlayersPerTeam = max # broadcast required def setDuration(self, duration): """ Coming from the AI, it sets how long the active state lasts. """ assert (self.notify.debug("setDuration duration=%d" % duration)) self._duration = duration # broadcast required def setCanSwitchTeams(self, canSwitchTeams): self._canSwitchTeams = canSwitchTeams # clsend airecv def d_toonJoinRequest(self, team): """ Send request for local toon to join the activity. Expect a joinRequestDenied or handleToonJoined in reply. """ if self.isLocalToonInActivity(): return if (self.activityFSM._state in ["WaitForEnough", "WaitToStart"] and self._localToonRequestStatus is None): assert (self.notify.debug( "d_toonJoinRequest( team=%s )" % PartyGlobals.TeamActivityTeams.getString(team))) base.cr.playGame.getPlace().fsm.request("activity") self.localToonJoining() self.sendUpdate("toonJoinRequest", [team]) # clsend airecv def d_toonExitRequest(self): """ Requests to for local toon to drop out of the activity. Expect a exitRequestDenied or handleToonExited in reply. """ # determine which team the local toon is on toonId = base.localAvatar.doId team = self.getTeam(toonId) if team is not None: if self._localToonRequestStatus is None: assert (self.notify.debug("d_toonExitRequest")) self.localToonExiting() self.sendUpdate("toonExitRequest", [team]) else: self.notify.warning( "Not sending exitRequest as localToon has no team.") # from ai to a single avatar def joinRequestDenied(self, reason): """Local Toon was not allowed to join activity""" DistributedPartyActivity.joinRequestDenied(self, reason) self.notify.debug("joinRequestDenied") # let the local toon know that they were denied if reason == PartyGlobals.DenialReasons.Full: self.showMessage(TTLocalizer.PartyTeamActivityTeamFull) elif reason == PartyGlobals.DenialReasons.Default: self.showMessage(TTLocalizer.PartyTeamActivityJoinDenied % self.getTitle()) # from ai to avatar def exitRequestDenied(self, reason): """Local Toon was not allowed to exit activity""" DistributedPartyActivity.exitRequestDenied(self, reason) if reason == PartyGlobals.DenialReasons.Default: self.showMessage(TTLocalizer.PartyTeamActivityExitDenied % self.getTitle()) if self.isLocalToonPlaying and \ (self.isState("WaitToStart") or self.isState("WaitForEnough")): self.teamActivityGui.enableExitButton() if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() # clsend airecv def d_toonSwitchTeamRequest(self): assert (self.notify.debug("d_switchTeamRequest")) if not self._canSwitchTeams: return self.sendUpdate("toonSwitchTeamRequest") # from ai to avatar def switchTeamRequestDenied(self, reason): """Local Toon was not allowed to switch teams""" self.notify.debug("switchTeamRequestDenied") # let the local toon know that they were denied if reason == PartyGlobals.DenialReasons.Full: # Unlike joinRequestDenied, the toon stays in the activity. self.showMessage(TTLocalizer.PartyTeamActivityTeamFull, endState='activity') elif reason == PartyGlobals.DenialReasons.Default: # Unlike joinRequestDenied, the toon stays in the activity. self.showMessage(TTLocalizer.PartyTeamActivitySwitchDenied, endState='activity') if self.isLocalToonPlaying and \ (self.isState("WaitToStart") or self.isState("WaitForEnough")) and \ self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() # required broadcast ram def setToonsPlaying(self, leftTeamToonIds, rightTeamToonIds): """ Overrides DistributedPartyActivity's setToonsPlaying. Computes who has entered, left, shifted, or switched on each team and calls the appropriate handlers. """ assert (self.notify.debug("setToonsPlaying")) newToonIds = [leftTeamToonIds, rightTeamToonIds] (exitedToons, joinedToons, shifters, switchers) = self.getToonsPlayingChanges(self.toonIds, newToonIds) assert (self.notify.debug("\texitedToons: %s" % exitedToons)) assert (self.notify.debug("\tjoinedToons: %s" % joinedToons)) assert (self.notify.debug("\tshifters: %s" % shifters)) assert (self.notify.debug("\tswitchers: %s" % switchers)) if base.localAvatar.doId in exitedToons: # make sure localToon's exit animation plays when the server clears out all players self.localToonExiting() self._processExitedToons(exitedToons) # update self.toonIds after we have dealt with exited toons so code # can still reference teams self.setToonIds([leftTeamToonIds, rightTeamToonIds]) self._processJoinedToons(joinedToons) # update self.toonIds after we have dealt with exited toons so code # can still reference teams for toonId in shifters: self.handleToonShifted(toonId) if self._canSwitchTeams: for toonId in switchers: self.handleToonSwitchedTeams(toonId) if self.isState("WaitForEnough"): self._updateWaitForEnoughStatus() def _updateWaitForEnoughStatus(self): amount = self.getNumToonsNeededToStart() if self._willBalanceTeams: text = TTLocalizer.PartyTeamActivityForMoreWithBalance else: text = TTLocalizer.PartyTeamActivityForMore if amount > 1: plural = TTLocalizer.PartyTeamActivityForMorePlural else: plural = "" self.setStatus(text % (amount, plural)) # broadcast ram def setState(self, newState, timestamp, data): DistributedPartyActivity.setState(self, newState, timestamp) assert (self.notify.debug("setState( newState=%s, ... )" % newState)) # pass additional parameters only to those states that need it if newState == "WaitToStart": self.activityFSM.request(newState, timestamp) elif newState == "Conclusion": self.activityFSM.request(newState, data) else: self.activityFSM.request(newState) # clsend airecv def d_toonReady(self): """Tells the AI that the local toon is ready to play.""" assert (self.notify.debug("toonReady")) self.sendUpdate("toonReady") # from ai to a single avatar def setAdvantage(self, advantage): assert (self.notify.debug("setAdvantage %d" % advantage)) self.advantage = advantage #=============================================================================== # Handlers #=============================================================================== def handleToonJoined(self, toonId): """Toon successfully joined the activity""" assert (self.notify.debug("handleToonJoined %d" % toonId)) if toonId == base.localAvatar.doId: self.isLocalToonPlaying = True self.localToonTeam = self.getTeam(base.localAvatar.doId) self.teamActivityGui.load() self.teamActivityGui.enableExitButton() # display "hop off" button if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() if self.activityFSM._state == "WaitToStart": self.showWaitToStartCountdown() else: self.showStatus() # display game status def handleToonExited(self, toonId): """Toon successfully exited the activity""" assert (self.notify.debug("handleToonExited %d" % toonId)) if toonId == base.localAvatar.doId: self.hideStatus() self.teamActivityGui.disableExitButton() if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() if self.activityFSM._state == "WaitToStart": self.hideWaitToStartCountdown() self.teamActivityGui.unload() self.isLocalToonPlaying = False self.localToonTeam = None def handleRulesDone(self): """ LocalToon has read the rules. """ self.notify.debug("handleRulesDone") self.d_toonReady() self.activityFSM.request("WaitForServer") def handleGameTimerExpired(self): """Called when the GUI gamer timer expires""" pass #=============================================================================== # Utility Methods #=============================================================================== def getToonsPlayingChanges(self, oldToonIds, newToonIds): """ Returns a tuple of ( list of toonIds of toons who exited list of toonIds of toons who joined list of toonIds of toons that shifted spots in the same team list of toonIds that switched teams ) """ oldLeftTeam = oldToonIds[0] oldRightTeam = oldToonIds[1] newLeftTeam = newToonIds[0] newRightTeam = newToonIds[1] oldToons = set(oldLeftTeam + oldRightTeam) newToons = set(newLeftTeam + newRightTeam) # find toons that are no longer in the game exitedToons = oldToons.difference(newToons) # find toons that just joined the game joinedToons = newToons.difference(oldToons) # find toons that switched spots or switched Teams shifters = [] if self._canSwitchTeams: switchers = list(set(oldLeftTeam) & set(newRightTeam)) + \ list(set(oldRightTeam) & set(newLeftTeam)) else: switchers = [] for i in range(len(PartyGlobals.TeamActivityTeams)): persistentToons = set(oldToonIds[i]) & set(newToonIds[i]) for toonId in persistentToons: if oldToonIds[i].index(toonId) != newToonIds[i].index(toonId): shifters.append(toonId) return (list(exitedToons), list(joinedToons), shifters, switchers) def getMaxPlayersPerTeam(self): return self._maxPlayersPerTeam def getMinPlayersPerTeam(self): return self._minPlayersPerTeam def getNumToonsNeededToStart(self): if self._willBalanceTeams: return abs((self._minPlayersPerTeam * 2) - self.getNumToonsPlaying()) else: return self._minPlayersPerTeam def getToonIdsAsList(self): """ Returns a list of doId's of all toons in this activity. """ return self.toonIds[0] + self.toonIds[1] def getNumToonsPlaying(self): """Returns the total number of toons in the activity""" return (len(self.toonIds[0]) + len(self.toonIds[1])) def getNumToonsInTeam(self, team): assert (team >= 0 and team < 2) return len(self.toonIds[team]) def getTeam(self, toonId): """ Utility function that returns the team toonId is on, as a PartyGlobals.TeamActivityTeams value, or None if toonId isn't on either team. """ for i in range(len(PartyGlobals.TeamActivityTeams)): if self.toonIds[i].count(toonId) > 0: return i else: return None def getIndex(self, toonId, team): """ Return the index/order of the toon in the team list. """ if self.toonIds[team].count(toonId) > 0: return self.toonIds[team].index(toonId) else: return None #=============================================================================== # Actions #=============================================================================== def _joinLeftTeam(self, collEntry): """Ask the AI if we can join the left team""" assert (self.notify.debug("_joinLeftTeam")) if self.isLocalToonInActivity(): # the toon is in another activity (probably the cannon), do not # start this one return self.d_toonJoinRequest(PartyGlobals.TeamActivityTeams.LeftTeam) def _joinRightTeam(self, collEntry): """Ask the AI if we can join the right team""" assert (self.notify.debug("_joinRightTeam")) if self.isLocalToonInActivity(): # the toon is in another activity (probably the cannon), do not # start this one return self.d_toonJoinRequest(PartyGlobals.TeamActivityTeams.RightTeam) def showWaitToStartCountdown(self): """Shows the countdown timer during the WaitToStart state""" if self.waitToStartTimestamp is None: self.notify.warning( "showWaitToStartCountdown was called when self.waitToStartTimestamp was None" ) return self.teamActivityGui.showWaitToStartCountdown( self._startDelay, self.waitToStartTimestamp, almostDoneCallback=self._onCountdownAlmostDone) self.showStatus() self.teamActivityGui.enableExitButton() def _onCountdownAlmostDone(self): if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() def hideWaitToStartCountdown(self): self.teamActivityGui.hideWaitToStartCountdown() self.teamActivityGui.disableExitButton() self.hideStatus() def setStatus(self, text): """Sets the status text""" self._currentStatus = text if self.isLocalToonPlaying: self.showStatus() def showStatus(self): """Show the status text""" if self.teamActivityGui is not None: self.teamActivityGui.showStatus(self._currentStatus) def hideStatus(self): """Hides the status text""" if self.teamActivityGui is not None: self.teamActivityGui.hideStatus() #=============================================================================== # FSM transitions #=============================================================================== def toonsCanSwitchTeams(self): """ Check if the local toon can switch to the other team. This is dependent on the implementation of the activity, and some will not let the player switch. """ return self._canSwitchTeams def isState(self, state): return hasattr(self, "activityFSM" ) and self.activityFSM.getCurrentOrNextState() == state def startWaitForEnough(self): """ Wait for for the minimum number players to join the activity. """ self.notify.debug("startWaitForEnough") self.advantage = 1.0 self._updateWaitForEnoughStatus() if self.isLocalToonPlaying: self.teamActivityGui.enableExitButton() # display "hop off" button if self._canSwitchTeams: self.teamActivityGui.enableSwitchButton() def finishWaitForEnough(self): """ There are enough players in the activity to start. Transitioning to WaitToStart. """ self.notify.debug("finishWaitForEnough") def startWaitToStart(self, waitStartTimestamp): """ Enough players are in. Wait a few seconds before the activity begins to let more players come in and participate. """ self.notify.debug("startWaitToStart") self.waitToStartTimestamp = globalClockDelta.networkToLocalTime( waitStartTimestamp) self._updateWaitForEnoughStatus() # display a countdown showing how long until the game starts self.setStatus(TTLocalizer.PartyTeamActivityWaitingToStart) if self.isLocalToonPlaying: self.showWaitToStartCountdown() def finishWaitToStart(self): """ The wait time is over and now we're displaying the rules on the clients and the AI is waiting until everyone reads the rules. Or we're below the number of toons required for the activity, so we go back to the WaitForEnough state. """ self.notify.debug("finishWaitToStart") if self.isLocalToonPlaying: self.hideWaitToStartCountdown() if self._canSwitchTeams: self.teamActivityGui.disableSwitchButton() self.waitToStartTimestamp = None def startRules(self): """ The activity is currently display the rules to the clients, and waiting for all to read them. """ self.notify.debug("startRules") # display rules to the local toon if we have one if self.isLocalToonPlaying: DistributedPartyActivity.startRules(self) # show instructions def finishRules(self): """ The clients have finished reading the rules and we're going to the active state. Or one the clients dropped and we're back to WaitToStart """ self.notify.debug("finishRules") if self.isLocalToonPlaying: DistributedPartyActivity.finishRules(self) # clean up instructions # check for a non-standard transition and do additional cleanup as needed if self.activityFSM.getCurrentOrNextState() == "WaitForEnough": DistributedPartyActivity.finishRules(self) # clean up instructions def startWaitForServer(self): """ Waiting for the server to start the activity. """ self.notify.debug("startWaitForServer") self.setStatus(TTLocalizer.PartyTeamActivityWaitingForOtherPlayers) def finishWaitForServer(self): """ Finished waiting for the server. We're either starting the activity or we are back to WaitForEnough in case a toon dropped out at the last minute. """ self.notify.debug("finishWaitForServer") # clean up status display if self.isLocalToonPlaying: self.hideStatus() def startActive(self): """ The activity begins! """ self.notify.debug("startActive") if self.isLocalToonPlaying: self.hideStatus() self.teamActivityGui.showTimer(self._duration) def finishActive(self): """ The activity ends or a toon dropped out causing the activity to go back to WaitToStart. """ self.notify.debug("finishActive") if self.isLocalToonPlaying: self.teamActivityGui.hideTimer() self.hideStatus() # check for a non-standard transition and do additional cleanup as needed # if self.activityFSM.getCurrentOrNextState() == "WaitForEnough": # pass # should additional cleanup happen here? def startConclusion(self, data): """ Display the results for the activity. Parameters data is a unsigned 32 bit integer packed with some important information regarding the conclusion. """ self.notify.debug("startConclusion") self.setStatus("") if self.isLocalToonPlaying: # Because conclusion is bypassing d_toonExitRequest during the conclusion, this needs to be # set in order for the server to clean up the Toon properly. self.localToonExiting() def finishConclusion(self): """ The conclusion timer is over and we're going back to WaitToStart for a new cycle. """ self.notify.debug("finishConclusion") if self.isLocalToonPlaying: self.hideStatus()