class DistributedGolfHoleAI(DistributedPhysicsWorldAI.DistributedPhysicsWorldAI, FSM, GolfHoleBase.GolfHoleBase): defaultTransitions = {'Off': ['Cleanup', 'WaitTee'], 'WaitTee': ['WaitSwing', 'Cleanup', 'WaitTee', 'WaitPlayback'], 'WaitSwing': ['WaitPlayback', 'Cleanup', 'WaitSwing', 'WaitTee'], 'WaitPlayback': ['WaitSwing', 'Cleanup', 'WaitTee', 'WaitPlayback'], 'Cleanup': ['Off']} id = 0 notify = directNotify.newCategory('DistributedGolfHoleAI') def __init__(self, zoneId, golfCourse, holeId): FSM.__init__(self, 'Golf_%s_FSM' % self.id) DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.__init__(self, simbase.air) GolfHoleBase.GolfHoleBase.__init__(self) self.zoneId = zoneId self.golfCourse = golfCourse self.holeId = holeId self.avIdList = golfCourse.avIdList[:] self.watched = [0, 0, 0, 0] self.barrierPlayback = None self.trustedPlayerId = None self.activeGolferIndex = None self.activeGolferId = None self.holeInfo = GolfGlobals.HoleInfo[self.holeId] self.teeChosen = {} for avId in self.avIdList: self.teeChosen[avId] = -1 self.ballPos = {} for avId in self.avIdList: self.ballPos[avId] = Vec3(0, 0, 0) self.playStarted = False return def curGolfBall(self): return self.ball def generate(self): DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.generate(self) self.ball = self.createBall() self.createRays() if len(self.teePositions) > 1: startPos = self.teePositions[1] else: startPos = self.teePositions[0] startPos += Vec3(0, 0, GolfGlobals.GOLF_BALL_RADIUS) self.ball.setPosition(startPos) def delete(self): self.notify.debug('__delete__') DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.delete(self) self.notify.debug('calling self.terrainModel.removeNode') self.terrainModel.removeNode() self.notify.debug('self.barrierPlayback is %s' % self.barrierPlayback) if self.barrierPlayback: self.notify.debug('calling self.barrierPlayback.cleanup') self.barrierPlayback.cleanup() self.notify.debug('calling self.barrierPlayback = None') self.barrierPlayback = None self.activeGolferId = None return def setZoneId(self, zoneId): self.zoneId = zoneId def setAvatarReadyHole(self): self.notify.debugStateCall(self) avId = self.air.getAvatarIdFromSender() self.golfCourse.avatarReadyHole(avId) def startPlay(self): self.notify.debug('startPlay') self.playStarted = True self.numGolfers = len(self.golfCourse.getGolferIds()) self.selectNextGolfer() def selectNextGolfer(self): self.notify.debug('selectNextGolfer, old golferIndex=%s old golferId=%s' % (self.activeGolferIndex, self.activeGolferId)) if self.golfCourse.isCurHoleDone(): return if self.activeGolferIndex == None: self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[self.activeGolferIndex] else: self.activeGolferIndex += 1 if self.activeGolferIndex >= len(self.golfCourse.getGolferIds()): self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[self.activeGolferIndex] safety = 0 while safety < 50 and not self.golfCourse.checkGolferPlaying(self.golfCourse.getGolferIds()[self.activeGolferIndex]): self.activeGolferIndex += 1 self.notify.debug('Index %s' % self.activeGolferIndex) if self.activeGolferIndex >= len(self.golfCourse.getGolferIds()): self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[self.activeGolferIndex] safety += 1 if safety != 50: golferId = self.golfCourse.getGolferIds()[self.activeGolferIndex] if self.teeChosen[golferId] == -1: self.sendUpdate('golferChooseTee', [golferId]) self.request('WaitTee') else: self.sendUpdate('golfersTurn', [golferId]) self.request('WaitSwing') else: self.notify.debug('safety') self.notify.debug('selectNextGolfer, new golferIndex=%s new golferId=%s' % (self.activeGolferIndex, self.activeGolferId)) return def clearWatched(self): self.watched = [1, 1, 1, 1] for index in range(len(self.golfCourse.getGolferIds())): self.watched[index] = 0 def setWatched(self, avId): for index in range(len(self.golfCourse.getGolferIds())): if self.golfCourse.getGolferIds()[index] == avId: self.watched[index] = 1 def checkWatched(self): if 0 not in self.watched: return True else: return False def turnDone(self): self.notify.debug('Turn Done') avId = self.air.getAvatarIdFromSender() if self.barrierPlayback: self.barrierPlayback.clear(avId) def ballInHole(self, golferId = None): self.notify.debug('ballInHole') if golferId: avId = golferId else: avId = self.air.getAvatarIdFromSender() self.golfCourse.setBallIn(avId) if self.golfCourse.isCurHoleDone(): self.notify.debug('ballInHole doing nothing') else: self.notify.debug('ballInHole calling self.selectNextGolfer') self.selectNextGolfer() def getHoleId(self): return self.holeId def finishHole(self): self.notify.debug('finishHole') self.golfCourse.holeOver() def getGolferIds(self): return self.avIdList def loadLevel(self): GolfHoleBase.GolfHoleBase.loadLevel(self) optionalObjects = self.terrainModel.findAllMatches('**/optional*') requiredObjects = self.terrainModel.findAllMatches('**/required*') self.parseLocators(optionalObjects, 1) self.parseLocators(requiredObjects, 0) self.teeNodePath = self.terrainModel.find('**/tee0') if self.teeNodePath.isEmpty(): teePos = Vec3(0, 0, 10) else: teePos = self.teeNodePath.getPos() teePos.setZ(teePos.getZ() + GolfGlobals.GOLF_BALL_RADIUS) self.notify.debug('teeNodePath heading = %s' % self.teeNodePath.getH()) self.teePositions = [teePos] teeIndex = 1 teeNode = self.terrainModel.find('**/tee%d' % teeIndex) while not teeNode.isEmpty(): teePos = teeNode.getPos() teePos.setZ(teePos.getZ() + GolfGlobals.GOLF_BALL_RADIUS) self.teePositions.append(teePos) self.notify.debug('teeNodeP heading = %s' % teeNode.getH()) teeIndex += 1 teeNode = self.terrainModel.find('**/tee%d' % teeIndex) def createLocatorDict(self): self.locDict = {} locatorNum = 1 curNodePath = self.hardSurfaceNodePath.find('**/locator%d' % locatorNum) while not curNodePath.isEmpty(): self.locDict[locatorNum] = curNodePath locatorNum += 1 curNodePath = self.hardSurfaceNodePath.find('**/locator%d' % locatorNum) def loadBlockers(self): loadAll = config.GetBool('golf-all-blockers', 0) self.createLocatorDict() self.blockerNums = self.holeInfo['blockers'] for locatorNum in self.locDict: if locatorNum in self.blockerNums or loadAll: locator = self.locDict[locatorNum] locatorParent = locator.getParent() locator.getChildren().wrtReparentTo(locatorParent) else: self.locDict[locatorNum].removeNode() self.hardSurfaceNodePath.flattenStrong() def createBall(self): golfBallGeom = self.createSphere(self.world, self.space, GolfGlobals.GOLF_BALL_DENSITY, GolfGlobals.GOLF_BALL_RADIUS, 1)[1] return golfBallGeom def preStep(self): GolfHoleBase.GolfHoleBase.preStep(self) def postStep(self): GolfHoleBase.GolfHoleBase.postStep(self) def postSwing(self, cycleTime, power, x, y, z, dirX, dirY): avId = self.air.getAvatarIdFromSender() self.storeAction = [avId, cycleTime, power, x, y, z, dirX, dirY] if self.commonHoldData: self.doAction() def postSwingState(self, cycleTime, power, x, y, z, dirX, dirY, curAimTime, commonObjectData): self.notify.debug('postSwingState') if not self.golfCourse.getStillPlayingAvIds(): return avId = self.air.getAvatarIdFromSender() self.storeAction = [avId, cycleTime, power, x, y, z, dirX, dirY] self.commonHoldData = commonObjectData self.trustedPlayerId = self.choosePlayerToSimulate() self.sendUpdateToAvatarId(self.trustedPlayerId, 'assignRecordSwing', [avId, cycleTime, power, x, y, z, dirX, dirY, commonObjectData]) self.golfCourse.addAimTime(avId, curAimTime) def choosePlayerToSimulate(self): stillPlaying = self.golfCourse.getStillPlayingAvIds() playerId = 0 if simbase.air.config.GetBool('golf-trust-driver-first', 0): if stillPlaying: playerId = stillPlaying[0] else: playerId = random.choice(stillPlaying) return playerId def ballMovie2AI(self, cycleTime, avId, movie, spinMovie, ballInFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame, commonObjectData): sentFromId = self.air.getAvatarIdFromSender() if sentFromId == self.trustedPlayerId: lastFrameNum = len(movie) - 2 if lastFrameNum < 0: lastFrameNum = 0 lastFrame = movie[lastFrameNum] lastPos = Vec3(lastFrame[1], lastFrame[2], lastFrame[3]) self.ballPos[avId] = lastPos self.golfCourse.incrementScore(avId) for id in self.golfCourse.getStillPlayingAvIds(): if not id == sentFromId: self.sendUpdateToAvatarId(id, 'ballMovie2Client', [cycleTime, avId, movie, spinMovie, ballInFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame, commonObjectData]) if self.state == 'WaitPlayback' or self.state == 'WaitTee': self.notify.warning('ballMovie2AI requesting from %s to WaitPlayback' % self.state) self.request('WaitPlayback') elif self.trustedPlayerId == None: return else: self.doAction() self.trustedPlayerId = None return def performReadyAction(self): avId = self.storeAction[0] if self.state == 'WaitPlayback': self.notify.debugStateCall(self) self.notify.debug('ignoring the postSwing for avId=%d since we are in WaitPlayback' % avId) return if avId == self.activeGolferId: self.golfCourse.incrementScore(self.activeGolferId) else: self.notify.warning('activGolferId %d not equal to sender avId %d' % (self.activeGolferId, avId)) if avId not in self.golfCourse.drivingToons: position = self.ballPos[avId] else: position = Vec3(self.storeAction[3], self.storeAction[4], self.storeAction[5]) self.useCommonObjectData(self.commonHoldData) newPos = self.trackRecordBodyFlight(self.ball, self.storeAction[1], self.storeAction[2], position, self.storeAction[6], self.storeAction[7]) if self.state == 'WaitPlayback' or self.state == 'WaitTee': self.notify.warning('performReadyAction requesting from %s to WaitPlayback' % self.state) self.request('WaitPlayback') self.sendUpdate('ballMovie2Client', [self.storeAction[1], avId, self.recording, self.aVRecording, self.ballInHoleFrame, self.ballTouchedHoleFrame, self.ballFirstTouchedHoleFrame, self.commonHoldData]) self.ballPos[avId] = newPos self.trustedPlayerId = None return def postResult(self, cycleTime, avId, recording, aVRecording, ballInHoleFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame): pass def enterWaitSwing(self): pass def exitWaitSwing(self): pass def enterWaitTee(self): pass def exitWaitTee(self): pass def enterWaitPlayback(self): self.notify.debug('enterWaitPlayback') stillPlayingList = self.golfCourse.getStillPlayingAvIds() self.barrierPlayback = ToonBarrier('waitClientsPlayback', self.uniqueName('waitClientsPlayback'), stillPlayingList, 120, self.handleWaitPlaybackDone, self.handlePlaybackTimeout) def hasCurGolferReachedMaxSwing(self): strokes = self.golfCourse.getCurHoleScore(self.activeGolferId) maxSwing = self.holeInfo['maxSwing'] retval = strokes >= maxSwing if retval: av = simbase.air.doId2do.get(self.activeGolferId) if av: if av.getUnlimitedSwing(): retval = False return retval def handleWaitPlaybackDone(self): if self.isCurBallInHole(self.activeGolferId) or self.hasCurGolferReachedMaxSwing(): if self.activeGolferId: self.ballInHole(self.activeGolferId) else: self.selectNextGolfer() def isCurBallInHole(self, golferId): retval = False for holePos in self.holePositions: displacement = self.ballPos[golferId] - holePos length = displacement.length() self.notify.debug('hole %s length=%s' % (holePos, length)) if length <= GolfGlobals.DistanceToBeInHole: retval = True break return retval def exitWaitPlayback(self): self.notify.debug('exitWaitPlayback') if hasattr(self, 'barrierPlayback') and self.barrierPlayback: self.barrierPlayback.cleanup() self.barrierPlayback = None return def enterCleanup(self): pass def exitCleanup(self): pass def handlePlaybackTimeout(self, task = None): self.notify.debug('handlePlaybackTimeout') self.handleWaitPlaybackDone() def getGolfCourseDoId(self): return self.golfCourse.doId def avatarDropped(self, avId): self.notify.warning('avId %d dropped, self.state=%s' % (avId, self.state)) if self.barrierPlayback: self.barrierPlayback.clear(avId) else: if avId == self.trustedPlayerId: self.doAction() if avId == self.activeGolferId and not self.golfCourse.haveAllGolfersExited(): self.selectNextGolfer() def setAvatarTee(self, chosenTee): golferId = self.air.getAvatarIdFromSender() self.teeChosen[golferId] = chosenTee self.ballPos[golferId] = self.teePositions[chosenTee] self.sendUpdate('setAvatarFinalTee', [golferId, chosenTee]) self.sendUpdate('golfersTurn', [golferId]) self.request('WaitSwing') def setBox(self, pos0, pos1, pos2, quat0, quat1, quat2, quat3, anV0, anV1, anV2, lnV0, lnV1, lnV2): self.sendUpdate('sendBox', [pos0, pos1, pos2, quat0, quat1, quat2, quat3, anV0, anV1, anV2, lnV0, lnV1, lnV2]) def parseLocators(self, objectCollection, optional = 0): if optional and objectCollection.getNumPaths(): if self.holeInfo.has_key('optionalMovers'): for optionalMoverId in self.holeInfo['optionalMovers']: searchStr = 'optional_mover_' + str(optionalMoverId) for objIndex in xrange(objectCollection.getNumPaths()): object = objectCollection.getPath(objIndex) if searchStr in object.getName(): self.fillLocator(objectCollection, objIndex) break else: for index in range(objectCollection.getNumPaths()): self.fillLocator(objectCollection, index) def fillLocator(self, objectCollection, index): path = objectCollection[index] pathName = path.getName() pathArray = pathName.split('_') sizeX = None sizeY = None move = None type = None for subString in pathArray: if subString[:1] == 'X': dataString = subString[1:] dataString = dataString.replace('p', '.') sizeX = float(dataString) elif subString[:1] == 'Y': dataString = subString[1:] dataString = dataString.replace('p', '.') sizeY = float(dataString) elif subString[:1] == 'd': dataString = subString[1:] dataString = dataString.replace('p', '.') move = float(dataString) elif subString == 'mover': type = 4 elif subString == 'windmillLocator': type = 3 if type == 4 and move and sizeX and sizeY: self.createCommonObject(4, path.getPos(), path.getHpr(), sizeX, sizeY, move) elif type == 3: self.createCommonObject(3, path.getPos(), path.getHpr()) return
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
class DistributedGolfCourseAI(DistributedObjectAI.DistributedObjectAI, FSM): notify = directNotify.newCategory('DistributedGolfCourseAI') defaultTransitions = { 'Off': ['WaitJoin'], 'WaitJoin': ['WaitReadyCourse', 'Cleanup'], 'WaitReadyCourse': ['WaitReadyHole', 'Cleanup'], 'WaitReadyHole': ['PlayHole', 'Cleanup', 'WaitLeaveHole', 'WaitReward'], 'PlayHole': ['PlayHole', 'WaitLeaveHole', 'Cleanup', 'WaitReward'], 'WaitLeaveHole': ['WaitReadyHole', 'WaitLeaveCourse', 'Cleanup', 'WaitReward'], 'WaitReward': ['WaitLeaveCourse', 'Cleanup', 'WaitLeaveHole'], 'WaitLeaveCourse': ['Cleanup'], 'Cleanup': ['Off'] } def __init__(self, zoneId, avIds, courseId, preferredHoleId=None): FSM.__init__(self, 'GolfCourse_%s_FSM' % zoneId) DistributedObjectAI.DistributedObjectAI.__init__(self, simbase.air) self.notify.debug('GOLF COURSE: init') self.zoneId = zoneId self.currentHole = None self.avIdList = [] self.avStateDict = {} self.addExpectedGolfers(avIds) self.courseId = courseId self.preferredHoleId = preferredHoleId self.courseInfo = GolfGlobals.CourseInfo[self.courseId] self.numHoles = self.courseInfo['numHoles'] self.holeIds = self.calcHolesToUse() self.notify.debug('self.holeIds = %s' % self.holeIds) self.numHolesPlayed = 0 self.curHoleIndex = 0 self.trophyListLen = 0 self.courseBestListLen = 0 self.holeBestListLen = 0 self.cupListLen = 0 self.scores = {} self.aimTimes = {} self.startingHistory = {} self.endingHistory = {} self.startingHoleBest = {} self.endingHoleBest = {} self.startingCourseBest = {} self.endingCourseBest = {} self.startingCups = {} self.endingCups = {} self.initHistory() self.newTrophies = {} self.newHoleBest = {} self.newCourseBest = {} self.newCups = {} self.drivingToons = [] self.__barrier = None self.winnerByTieBreak = 0 return def initHistory(self): for avId in self.avIdList: av = simbase.air.doId2do.get(avId) if av: history = av.getGolfHistory() self.startingHistory[avId] = history[:] self.endingHistory[avId] = history[:] holeBest = av.getGolfHoleBest() self.startingHoleBest[avId] = holeBest[:] self.endingHoleBest[avId] = holeBest[:] courseBest = av.getGolfCourseBest() self.startingCourseBest[avId] = courseBest[:] self.endingCourseBest[avId] = courseBest[:] def generate(self): DistributedObjectAI.DistributedObjectAI.generate(self) self.grabGolfers() def delete(self): self.notify.debug('GOLF COURSE: delete: deleting AI GolfCourse object') if hasattr(self, 'rewardBarrier'): self.rewardBarrier.cleanup() del self.rewardBarrier if self.currentHole: self.notify.debug('calling requestDelete on hole %d' % self.currentHole.doId) self.currentHole.requestDelete() self.currentHole = None self.ignoreAll() from toontown.golf import GolfManagerAI GolfManagerAI.GolfManagerAI().removeCourse(self) if self.__barrier: self.__barrier.cleanup() self.__barrier = None DistributedObjectAI.DistributedObjectAI.delete(self) return def load(self): self.b_setCourseReady() self.request('WaitReadyCourse') def getZoneId(self): return self.zoneId def addExpectedGolfers(self, avIdList): self.notify.debug('Sending %s to course %s' % (avIdList, self.zoneId)) for avId in avIdList: golfer = simbase.air.doId2do.get(avId) if golfer: if avId not in self.avIdList: self.avIdList.append(avId) self.avStateDict[avId] = INITIAL elif self.avStateDict[avId] == EXITED: if self.isGenerated(): pass else: self.notify.warning( 'GOLF COURSE: trying to grab golfer %s that is already on the course' % avId) def grabGolfers(self): for avId in self.avIdList: golfer = simbase.air.doId2do.get(avId) if golfer: if self.avStateDict[avId] == INITIAL: self.avStateDict[avId] = EXPECTED self.request('WaitJoin') def getGolferIds(self): return self.avIdList def checkGolferPlaying(self, avId): if self.avStateDict[avId] == ONHOLE: return 1 else: return 0 def b_setCourseReady(self): self.setCourseReady() self.d_setCourseReady() def d_setCourseReady(self): self.notify.debug('GOLF COURSE: Sending setCourseReady') self.sendUpdate('setCourseReady', [self.numHoles, self.holeIds, self.calcCoursePar()]) def setCourseReady(self): self.notify.debug( 'GOLF COURSE: setCourseReady: golf course ready with avatars: %s' % self.avIdList) self.trophyListLen = 0 self.courseBestListLen = 0 self.holeBestListLen = 0 self.cupListLen = 0 self.normalExit = 1 def d_setPlayHole(self): self.notify.debug( 'GOLF COURSE: setPlayHole: play on golf hole about to start') self.sendUpdate('setPlayHole', []) def b_setCourseExit(self): self.d_setCourseExit() self.setCourseExit() def d_setCourseExit(self): self.notify.debug('GOLF COURSE: Sending setGameExit') self.sendUpdate('setCourseExit', []) def setCourseExit(self): self.notify.debug('GOLF COURSE: setGameExit') def handleExitedAvatar(self, avId): self.notify.warning( 'GOLF COURSE: handleExitedAvatar: avatar id exited: ' + str(avId)) self.avStateDict[avId] = EXITED self.sendUpdate('avExited', [avId]) if self.currentHole and not self.haveAllGolfersExited(): self.currentHole.avatarDropped(avId) if self.haveAllGolfersExited(): self.setCourseAbort() elif self.isCurHoleDone(): if self.isPlayingLastHole(): if self.state not in ['WaitReward', 'WaitReadyHole']: self.safeDemand('WaitReward') else: self.notify.debug('allBalls are in holes, calling holeOver') self.holeOver() if hasattr(self, 'rewardBarrier'): if self.rewardBarrier: self.rewardBarrier.clear(avId) if hasattr(self, '__barrier'): if self.__barrier: self.__.clear(avId) def startNextHole(self): self.notify.debugStateCall(self) holeId = self.holeIds[self.numHolesPlayed] self.currentHole = DistributedGolfHoleAI.DistributedGolfHoleAI( self.zoneId, golfCourse=self, holeId=holeId) self.currentHole.generateWithRequired(self.zoneId) self.d_setCurHoleDoId(self.currentHole.doId) self.safeDemand('WaitReadyHole') def holeOver(self): self.notify.debug('GOLF COURSE: holeOver') self.numHolesPlayed += 1 if self.numHolesPlayed < self.numHoles: self.b_setCurHoleIndex(self.numHolesPlayed) self.safeDemand('WaitLeaveHole') def setCourseAbort(self): self.notify.debug('GOLF COURSE: setGameAbort') self.normalExit = 0 self.sendUpdate('setCourseAbort', [0]) self.safeDemand('Cleanup') def enterOff(self): self.notify.debug('GOLF COURSE: enterOff') def exitOff(self): self.notify.debug('GOLF COURSE: exitOff') def enterWaitJoin(self): self.notify.debug('GOLF COURSE: enterWaitJoin') for avId in self.avIdList: self.avStateDict[avId] = EXPECTED self.acceptOnce(self.air.getAvatarExitEvent(avId), self.handleExitedAvatar, extraArgs=[avId]) def allAvatarsJoined(self=self): self.notify.debug('GOLF COURSE: all avatars joined') self.load() def handleTimeout(avIds, self=self): self.notify.debug( 'GOLF COURSE: timed out waiting for clients %s to join' % avIds) for avId in self.avStateDict: if not self.avStateDict[avId] == JOINED: self.handleExitedAvatar(avId) if self.haveAllGolfersExited(): self.setCourseAbort() else: self.load() self.__barrier = ToonBarrier('waitClientsJoin', self.uniqueName('waitClientsJoin'), self.avIdList, JOIN_TIMEOUT, allAvatarsJoined, handleTimeout) def exitWaitJoin(self): self.notify.debugStateCall(self) self.__barrier.cleanup() self.__barrier = None return def setAvatarJoined(self): avId = self.air.getAvatarIdFromSender() self.notify.debug('GOLF COURSE: setAvatarJoined: avatar id joined: ' + str(avId)) self.avStateDict[avId] = JOINED self.notify.debug('GOLF COURSE: setAvatarJoined: new states: ' + str(self.avStateDict)) if hasattr(self, '_DistributedGolfCourseAI__barrier') and self.__barrier: self.__barrier.clear(avId) else: self.notify.warning( 'setAvatarJoined avId=%d but barrier is invalid' % avId) def exitFrameworkWaitClientsJoin(self): self.__barrier.cleanup() del self.__barrier def enterWaitReadyCourse(self): self.notify.debug('GOLF COURSE: enterWaitReadyCourse') def allAvatarsInCourse(self=self): self.notify.debug('GOLF COURSE: all avatars ready course') for avId in self.avIdList: blankScoreList = [0] * self.numHoles self.scores[avId] = blankScoreList self.aimTimes[avId] = 0 self.notify.debug('self.scores = %s' % self.scores) self.startNextHole() def handleTimeout(avIds, self=self): self.notify.debug( "GOLF COURSE: Course timed out waiting for clients %s to report 'ready'" % avIds) if self.haveAllGolfersExited(): self.setCourseAbort() else: allAvatarsInCourse() self.__barrier = ToonBarrier('WaitReadyCourse', self.uniqueName('WaitReadyCourse'), self.avIdList, READY_TIMEOUT, allAvatarsInCourse, handleTimeout) for avId in self.avStateDict.keys(): if self.avStateDict[avId] == READY: self.__barrier.clear(avId) def setAvatarReadyCourse(self): avId = self.air.getAvatarIdFromSender() self.notify.debug( 'GOLF COURSE: setAvatarReadyCourse: avatar id ready: ' + str(avId)) self.avStateDict[avId] = READY self.notify.debug( 'GOLF COURSE: setAvatarReadyCourse: new avId states: ' + str(self.avStateDict)) if self.state == 'WaitReadyCourse': self.__barrier.clear(avId) def exitWaitReadyCourse(self): self.notify.debugStateCall(self) self.__barrier.cleanup() self.__barrier = None return def enterWaitReadyHole(self): self.notify.debug('GOLF COURSE: enterWaitReadyHole') def allAvatarsInHole(self=self): self.notify.debug('GOLF COURSE: all avatars ready hole') if self.safeDemand('PlayHole'): self.d_setPlayHole() def handleTimeout(avIds, self=self): self.notify.debug( "GOLF COURSE: Hole timed out waiting for clients %s to report 'ready'" % avIds) if self.haveAllGolfersExited(): self.setCourseAbort() elif self.safeDemand('PlayHole'): self.d_setPlayHole() stillPlaying = self.getStillPlayingAvIds() self.__barrier = ToonBarrier('WaitReadyHole', self.uniqueName('WaitReadyHole'), stillPlaying, READY_TIMEOUT, allAvatarsInHole, handleTimeout) for avId in self.avStateDict.keys(): if self.avStateDict[avId] == ONHOLE: self.__barrier.clear(avId) def exitWaitReadyHole(self): self.notify.debugStateCall(self) if hasattr(self, '__barrier'): self.__barrier.cleanup() self.__barrier = None return def getStillPlayingAvIds(self): retval = [] for avId in self.avIdList: av = simbase.air.doId2do.get(avId) if av: if avId in self.avStateDict and not self.avStateDict[ avId] == EXITED: retval.append(avId) return retval def avatarReadyHole(self, avId): if self.state not in ['WaitJoin', 'WaitReadyCourse', 'WaitReadyHole']: self.notify.debug( 'GOLF COURSE: Ignoring setAvatarReadyHole message') return self.notify.debug( 'GOLF COURSE: setAvatarReadyHole: avatar id ready: ' + str(avId)) self.avStateDict[avId] = ONHOLE self.notify.debug( 'GOLF COURSE: setAvatarReadyHole: new avId states: ' + str(self.avStateDict)) if self.state == 'WaitReadyHole': self.__barrier.clear(avId) def enterPlayHole(self): self.notify.debug('GOLF COURSE: enterPlayHole') if self.currentHole and not self.currentHole.playStarted: self.currentHole.startPlay() def exitPlayHole(self): self.notify.debug('GOLF COURSE: exitPlayHole') def enterWaitLeaveHole(self): self.notify.debugStateCall(self) self.notify.debug('calling requestDelete on hole %d' % self.currentHole.doId) self.currentHole.requestDelete() self.currentHole = None if self.numHolesPlayed >= self.numHoles: pass else: self.startNextHole() return def exitWaitLeaveHole(self): pass def enterWaitReward(self): self.updateHistoryForCourseComplete() self.awardTrophies() self.awardCups() self.awardHoleBest() self.awardCourseBest() self.recordHoleInOne() self.recordCourseUnderPar() trophiesList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newTrophies: oneTrophyList = self.newTrophies[avId] trophiesList.append(oneTrophyList) else: trophiesList.append([]) while len(trophiesList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: trophiesList.append([]) holeBestList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newHoleBest: oneTrophyList = self.newHoleBest[avId] holeBestList.append(oneTrophyList) else: holeBestList.append([]) while len(holeBestList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: holeBestList.append([]) courseBestList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newCourseBest: oneTrophyList = self.newCourseBest[avId] courseBestList.append(oneTrophyList) else: courseBestList.append([]) while len(courseBestList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: courseBestList.append([]) cupList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newCups: oneCupList = self.newCups[avId] cupList.append(oneCupList) self.cupListLen = self.cupListLen + 1 else: cupList.append([]) while len(cupList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: cupList.append([]) REWARD_TIMEOUT = (self.trophyListLen + self.holeBestListLen + self.courseBestListLen + self.cupListLen) * 5 + 19 aimTimesList = [0] * 4 aimIndex = 0 stillPlaying = self.getStillPlayingAvIds() for avId in self.avIdList: if avId in stillPlaying: aimTime = 0 if avId in self.aimTimes: aimTime = self.aimTimes[avId] aimTimesList[aimIndex] = aimTime aimIndex += 1 self.sendUpdate('setReward', [ trophiesList, self.rankings, holeBestList, courseBestList, cupList, self.winnerByTieBreak, aimTimesList[0], aimTimesList[1], aimTimesList[2], aimTimesList[3] ]) def allAvatarsRewarded(self=self): self.notify.debug('GOLF COURSE: all avatars rewarded') self.rewardDone() def handleRewardTimeout(avIds, self=self): self.notify.debug( 'GOLF COURSE: timed out waiting for clients %s to finish reward' % avIds) self.rewardDone() stillPlaying = self.getStillPlayingAvIds() self.rewardBarrier = ToonBarrier('waitReward', self.uniqueName('waitReward'), stillPlaying, REWARD_TIMEOUT, allAvatarsRewarded, handleRewardTimeout) def exitWaitReward(self): pass def enterWaitLeaveCourse(self): self.notify.debugStateCall(self) self.setCourseAbort() def exitWaitLeaveCourse(self): pass def enterCleanup(self): self.notify.debug('GOLF COURSE: enterCleanup') self.requestDelete() def exitCleanup(self): self.notify.debug('GOLF COURSE: exitCleanup') def isCurHoleDone(self): retval = False if self.areAllBallsInHole(): retval = True else: retval = True for state in self.avStateDict.values(): if not (state == BALLIN or state == EXITED): retval = False break return retval def areAllBallsInHole(self): self.notify.debug('areAllBallsInHole, self.avStateDict=%s' % self.avStateDict) allBallsInHole = True for state in self.avStateDict.values(): if state != BALLIN: allBallsInHole = False return allBallsInHole def isPlayingLastHole(self): retval = self.numHoles - self.numHolesPlayed == 1 return retval def setBallIn(self, avId): self.notify.debug('setBallIn %d' % avId) if self.avStateDict[avId] == BALLIN: self.notify.debug( 'setBallIn already in BALLIN state, just returning') return self.avStateDict[avId] = BALLIN self.updateHistoryForBallIn(avId) if self.isCurHoleDone(): if self.isPlayingLastHole(): if self.state != 'WaitReward': self.safeDemand('WaitReward') else: self.notify.debug('allBalls are in holes, calling holeOver') self.holeOver() def updateHistoryForBallIn(self, avId): if self.currentHole == None: return holeId = self.currentHole.holeId holeInfo = GolfGlobals.HoleInfo[holeId] par = holeInfo['par'] holeIndex = self.numHolesPlayed if holeIndex >= self.numHoles: self.notify.warning('updateHistoryForBallIn invalid holeIndex %d' % holeIndex) holeIndex = self.numHoles - 1 elif holeIndex < 0: self.notify.warning('updateHistoryForBallIn invalid holeIndex %d' % holeIndex) holeIndex = 0 strokes = self.scores[avId][holeIndex] self.notify.debug('self.scores = %s' % self.scores) diff = strokes - par if strokes == 1: self.incrementEndingHistory(avId, GolfGlobals.HoleInOneShots) if diff <= -2: self.incrementEndingHistory(avId, GolfGlobals.EagleOrBetterShots) if diff <= -1: self.incrementEndingHistory(avId, GolfGlobals.BirdieOrBetterShots) if diff <= 0: self.endingHistory[avId][GolfGlobals.ParOrBetterShots] += 1 if strokes < self.endingHoleBest[avId][holeId] or self.endingHoleBest[ avId][holeId] == 0: self.endingHoleBest[avId][holeId] = strokes return def incrementEndingHistory(self, avId, historyIndex): if avId in self.endingHistory and historyIndex in GolfGlobals.TrophyRequirements: maximumAmount = GolfGlobals.TrophyRequirements[historyIndex][-1] if self.endingHistory[avId][historyIndex] < maximumAmount: self.endingHistory[avId][historyIndex] += 1 def getCourseId(self): return self.courseId def abortCurrentHole(self): holeId = self.currentHole.holeId holeDoId = self.currentHole.doId self.currentHole.finishHole() return (holeId, holeDoId) def calcHolesToUse(self): retval = [] if simbase.air.config.GetBool('golf-course-randomized', 1): retval = self.calcHolesToUseRandomized(self.courseId) self.notify.debug('randomized courses!') for x in xrange(len(retval)): self.notify.debug('Hole is: %s' % retval[x]) else: validHoles = self.calcUniqueHoles(self.courseId) if self.preferredHoleId in validHoles: retval.append(self.preferredHoleId) while len(retval) < self.numHoles: for holeId in GolfGlobals.CourseInfo[self.courseId]['holeIds']: if type(holeId) == type(0): retval.append(holeId) elif type(holeId) == type(()): retval.append(holeId[0]) else: self.notify.warning('cant handle %s' % self.holeId) if len(retval) >= self.numHoles: break return retval def incrementScore(self, avId): self.notify.debug('incrementScore self.scores=%s avId=%s' % (self.scores, avId)) self.scores[avId][self.numHolesPlayed] += 1 self.notify.debug('after increment self.score=%s' % self.scores) self.sendScores() def sendScores(self): self.notify.debug('sendScores self.scores = %s' % self.scores) scorelist = [] for avId in self.avIdList: for score in self.scores[avId]: scorelist.append(score) self.sendUpdate('setScores', [scorelist]) self.notify.debug('sendScores end self.scores = %s' % self.scores) def getCurHoleIndex(self): return self.curHoleIndex def b_setCurHoleIndex(self, holeIndex): self.setCurHoleIndex(holeIndex) self.d_setCurHoleIndex(holeIndex) def d_setCurHoleIndex(self, holeIndex): self.sendUpdate('setCurHoleIndex', [holeIndex]) def setCurHoleIndex(self, holeIndex): self.curHoleIndex = holeIndex def setDoneReward(self): avId = self.air.getAvatarIdFromSender() self.notify.debug('got rewardDone from %d' % avId) if hasattr(self, 'rewardBarrier'): self.rewardBarrier.clear(avId) self.sendUpdate('setCourseAbort', [avId]) def rewardDone(self): self.notify.debug('rewardDone') self.holeOver() self.safeDemand('WaitLeaveCourse') def updateHistoryForCourseComplete(self): self.calcRankings() stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: self.incrementEndingHistory(avId, GolfGlobals.CoursesCompleted) coursePar = self.calcCoursePar() totalScore = self.getTotalScore(avId) if totalScore < coursePar: self.incrementEndingHistory(avId, GolfGlobals.CoursesUnderPar) if len(stillPlaying) > 1: self.incrementEndingHistory( avId, GolfGlobals.MultiPlayerCoursesCompleted) if self.rankingsById[avId] == 1: if self.courseId == 0: self.incrementEndingHistory(avId, GolfGlobals.CourseZeroWins) elif self.courseId == 1: self.incrementEndingHistory(avId, GolfGlobals.CourseOneWins) elif self.courseId == 2: self.incrementEndingHistory(avId, GolfGlobals.CourseTwoWins) else: self.notify.warning( 'unhandled case, self.courseId=%s' % self.courseId) if totalScore < self.endingCourseBest[avId][ self.courseId] or self.endingCourseBest[avId][ self.courseId] == 0: self.endingCourseBest[avId][self.courseId] = totalScore def calcRankings(self): stillPlaying = self.getStillPlayingAvIds() self.rankings = [] totalScores = [] for avId in self.avIdList: aimTime = 0 if avId in self.aimTimes: aimTime = self.aimTimes[avId] if avId in stillPlaying: totalScores.append((avId, self.getTotalScore(avId), aimTime)) else: totalScores.append((avId, 255, aimTime)) def scoreCompareNoTime(tupleA, tupleB): if tupleA[1] > tupleB[1]: return 1 elif tupleA[1] == tupleB[1]: return 0 else: return -1 def scoreCompareWithTime(tupleA, tupleB): if tupleA[1] > tupleB[1]: return 1 elif tupleA[1] == tupleB[1]: if tupleA[2] > tupleB[2]: return 1 elif tupleA[2] == tupleB[2]: return 0 else: return -1 else: return -1 if GolfGlobals.TIME_TIE_BREAKER: totalScores.sort(scoreCompareWithTime) else: totalScores.sort(scoreCompareNoTime) curRank = 0 oldScore = 0 oldTime = 0 self.rankingsById = {} for scoreTuple in totalScores: time = scoreTuple[2] score = scoreTuple[1] avId = scoreTuple[0] if score > oldScore or GolfGlobals.TIME_TIE_BREAKER and score == oldScore and time > oldTime: curRank += 1 oldScore = score oldTime = time self.rankingsById[avId] = curRank tiedForFirst = [] tempRank = 0 oldScore = 0 oldTime = 0 for scoreTuple in totalScores: time = scoreTuple[2] score = scoreTuple[1] avId = scoreTuple[0] if score > oldScore: tempRank += 1 oldScore = score oldTime = time if tempRank == 1: tiedForFirst.append(avId) for avId in self.avIdList: if avId in stillPlaying: self.rankings.append(self.rankingsById[avId]) else: self.rankings.append(-1) if len(tiedForFirst) >= 2 and not GolfGlobals.TIME_TIE_BREAKER: winnerAvId = random.choice(tiedForFirst) winnerIndex = self.avIdList.index(winnerAvId) self.winnerByTieBreak = winnerAvId for index in xrange(len(self.rankings)): if self.rankings[index] > 0 and index != winnerIndex: self.rankings[index] += 1 for avId in self.rankingsById: if self.rankingsById[avId] > 0 and avId != winnerAvId: self.rankingsById[avId] += 1 elif len(tiedForFirst) >= 2: winnerAvId = totalScores[0][0] self.winnerByTieBreak = winnerAvId def awardTrophies(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHistory = self.startingHistory[avId] endingHistory = self.endingHistory[avId] oldTrophies = GolfGlobals.calcTrophyListFromHistory(oldHistory) endingTrophies = GolfGlobals.calcTrophyListFromHistory( endingHistory) av.b_setGolfHistory(endingHistory) newTrophies = [] for index in xrange(len(oldTrophies)): if not oldTrophies[index] and endingTrophies[index]: self.notify.debug('New Trophy %d' % index) self.air.writeServerEvent('golf_trophy', avId, '%s' % index) newTrophies.append(True) self.trophyListLen = self.trophyListLen + 1 else: newTrophies.append(False) self.newTrophies[avId] = newTrophies def awardCups(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHistory = self.startingHistory[avId] endingHistory = self.endingHistory[avId] oldCups = GolfGlobals.calcCupListFromHistory(oldHistory) endingCups = GolfGlobals.calcCupListFromHistory(endingHistory) newCups = [] for index in xrange(len(oldCups)): if not oldCups[index] and endingCups[index]: self.notify.debug('New Trophy %d' % index) newCups.append(True) self.air.writeServerEvent('golf_cup', avId, '%s' % index) newMaxHp = av.getMaxHp() + 1 av.b_setMaxHp(newMaxHp) av.toonUp(newMaxHp) else: newCups.append(False) self.newCups[avId] = newCups def awardHoleBest(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHoleBest = self.startingHoleBest[avId] endingHoleBest = self.endingHoleBest[avId] av.b_setGolfHoleBest(endingHoleBest) newHoleBest = [] longestHoleBestList = 0 for index in xrange(len(oldHoleBest)): if endingHoleBest[index] < oldHoleBest[index]: self.notify.debug('New HoleBest %d' % index) newHoleBest.append(True) longestHoleBestList = longestHoleBestList + 1 else: newHoleBest.append(False) if longestHoleBestList > self.holeBestListLen: self.holeBestListLen = longestHoleBestList self.newHoleBest[avId] = newHoleBest def awardCourseBest(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldCourseBest = self.startingCourseBest[avId] endingCourseBest = self.endingCourseBest[avId] av.b_setGolfCourseBest(endingCourseBest) newCourseBest = [] longestCourseBestList = 0 for index in xrange(len(oldCourseBest)): if endingCourseBest[index] < oldCourseBest[index]: self.notify.debug('New CourseBest %d' % index) newCourseBest.append(True) longestCourseBestList = longestCourseBestList + 1 else: newCourseBest.append(False) if longestCourseBestList > self.courseBestListLen: self.courseBestListLen = longestCourseBestList self.newCourseBest[avId] = newCourseBest def haveAllGolfersExited(self): retval = True for avId in self.avStateDict: if not self.avStateDict[avId] == EXITED: retval = False break return retval def getCurHoleDoId(self): retval = 0 if self.currentHole: retval = self.currentHole.doId return retval def d_setCurHoleDoId(self, curHoleDoId): self.sendUpdate('setCurHoleDoId', [curHoleDoId]) def calcCoursePar(self): retval = 0 for holeId in self.holeIds: holeInfo = GolfGlobals.HoleInfo[holeId] retval += holeInfo['par'] return retval def getTotalScore(self, avId): retval = 0 if avId in self.scores: for holeScore in self.scores[avId]: retval += holeScore return retval def getCurHoleScore(self, avId): retval = 0 if avId in self.scores and self.numHolesPlayed < len( self.scores[avId]): retval = self.scores[avId][self.numHolesPlayed] return retval def toggleDrivePermission(self, avId): if avId in self.drivingToons: self.drivingToons.remove(avId) self.sendUpdate('changeDrivePermission', [avId, 0]) retval = False else: self.drivingToons.append(avId) self.sendUpdate('changeDrivePermission', [avId, 1]) retval = True return retval def safeDemand(self, newState): doingDemand = False if self.state == 'Cleanup': pass else: if self.state in self.defaultTransitions: if newState in self.defaultTransitions[self.state]: self.demand(newState) doingDemand = True elif self.state == None: self.demand(newState) doingDemand = True if not doingDemand: self.notify.warning('doId=%d ignoring demand from %s to %s' % (self.doId, self.state, newState)) return doingDemand def setAvatarExited(self): avId = self.air.getAvatarIdFromSender() self.handleExitedAvatar(avId) def createChoicesList(self, courseId, possibleHoles): retval = [] holeIds = GolfGlobals.CourseInfo[courseId]['holeIds'] for holeOrTuple in holeIds: if type(holeOrTuple) == type(()): holeId = holeOrTuple[0] weight = holeOrTuple[1] elif type(holeOrTuple) == type(0): holeId = holeOrTuple weight = 1 else: self.notify.warning('cant handle %s' % holeOrTuple) continue if holeId in possibleHoles: retval += [holeId] * weight return retval def calcUniqueHoles(self, courseId): uniqueHoles = set() for holeOrTuple in GolfGlobals.CourseInfo[courseId]['holeIds']: if type(holeOrTuple) == type(()): uniqueHoles.add(holeOrTuple[0]) elif type(holeOrTuple) == type(0): uniqueHoles.add(holeOrTuple) else: self.notify.warning('cant handle %s' % holeOrTuple) return uniqueHoles def calcHolesToUseRandomized(self, courseId): retval = [] numHoles = GolfGlobals.CourseInfo[courseId]['numHoles'] uniqueHoles = self.calcUniqueHoles(courseId) curHolesChosen = set() while len(retval) < numHoles: if uniqueHoles == curHolesChosen: curHolesChosen = set() possibleHoles = uniqueHoles - curHolesChosen choicesList = self.createChoicesList(courseId, possibleHoles) if not self.preferredHoleId == None and self.preferredHoleId in choicesList and self.preferredHoleId not in curHolesChosen: holeChosen = self.preferredHoleId else: holeChosen = random.choice(choicesList) retval.append(holeChosen) curHolesChosen.add(holeChosen) return retval def recordHoleInOne(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: scoreList = self.scores[avId] for holeIndex in xrange(len(scoreList)): strokes = scoreList[holeIndex] if strokes == 1: holeId = self.holeIds[holeIndex] self.air.writeServerEvent( 'golf_ace', avId, '%d|%d|%s' % (self.courseId, holeId, stillPlaying)) def recordCourseUnderPar(self): coursePar = self.calcCoursePar() stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: totalScore = self.getTotalScore(avId) netScore = totalScore - coursePar if netScore < 0: self.air.writeServerEvent( 'golf_underPar', avId, '%d|%d|%s' % (self.courseId, netScore, stillPlaying)) def addAimTime(self, avId, aimTime): if avId in self.aimTimes: self.aimTimes[avId] += aimTime
class DistributedMinigameAI(DistributedObjectAI.DistributedObjectAI): notify = directNotify.newCategory('DistributedMinigameAI') def __init__(self, air, minigameId): try: pass 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 = { } 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 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._DistributedMinigameAI__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 None 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._DistributedMinigameAI__barrier.clear(avId) def exitFrameworkWaitClientsJoin(self): self._DistributedMinigameAI__barrier.cleanup() del self._DistributedMinigameAI__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._DistributedMinigameAI__barrier = ToonBarrier('waitClientsReady', self.uniqueName('waitClientsReady'), self.avIdList, READY_TIMEOUT, allAvatarsReady, handleTimeout) for avId in self.stateDict.keys(): if self.stateDict[avId] == READY: self._DistributedMinigameAI__barrier.clear(avId) continue 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 None 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._DistributedMinigameAI__barrier.clear(avId) def exitFrameworkWaitClientsReady(self): self._DistributedMinigameAI__barrier.cleanup() del self._DistributedMinigameAI__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._DistributedMinigameAI__barrier = ToonBarrier('waitClientsExit', self.uniqueName('waitClientsExit'), self.avIdList, EXIT_TIMEOUT, allAvatarsExited, handleTimeout) for avId in self.stateDict.keys(): if self.stateDict[avId] == EXITED: self._DistributedMinigameAI__barrier.clear(avId) continue def setAvatarExited(self): if self.frameworkFSM.getCurrentState().getName() != 'frameworkWaitClientsExit': self.notify.debug('BASE: Ignoring setAvatarExit message') return None 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._DistributedMinigameAI__barrier.clear(avId) def exitFrameworkWaitClientsExit(self): self._DistributedMinigameAI__barrier.cleanup() del self._DistributedMinigameAI__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 votesToUse.has_key(avId): votesArray.append(votesToUse[avId]) continue 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 continue 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) 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 self.startingVotes.has_key(avId): retval.append(self.startingVotes[avId]) continue 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
class DistributedTwoDGameAI(DistributedMinigameAI): notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTwoDGameAI') def __init__(self, air, minigameId): try: self.DistributedTwoDGameAI_initialized except: self.DistributedTwoDGame_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) self.gameFSM = ClassicFSM.ClassicFSM('DistributedTwoDGameAI', [State.State('inactive', self.enterInactive, self.exitInactive, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, ['inactive'])], 'inactive', 'inactive') self.addChildGameFSM(self.gameFSM) self.finishedBonusDict = {} self.finishedTimeLeftDict = {} self.numFallDownDict = {} self.numHitByEnemyDict = {} self.numSquishDict = {} self.treasuresCollectedDict = {} self.sectionsSelected = [] self.enemyHealthTable = [] self.treasureTakenTable = [] self.sectionIndexList = [] def generate(self): self.notify.debug('generate') DistributedMinigameAI.generate(self) def delete(self): self.notify.debug('delete') del self.gameFSM DistributedMinigameAI.delete(self) def setTrolleyZone(self, trolleyZone): DistributedMinigameAI.setTrolleyZone(self, trolleyZone) self.setupSections() def setGameReady(self): self.notify.debug('setGameReady') DistributedMinigameAI.setGameReady(self) self.numTreasures = ToonBlitzGlobals.NumTreasures self.numEnemies = ToonBlitzGlobals.NumEnemies self.numTreasuresTaken = 0 self.numEnemiesKilled = 0 for avId in self.scoreDict.keys(): self.scoreDict[avId] = 0 self.finishedBonusDict[avId] = 0 self.finishedTimeLeftDict[avId] = -1 self.numFallDownDict[avId] = 0 self.numHitByEnemyDict[avId] = 0 self.numSquishDict[avId] = 0 self.treasuresCollectedDict[avId] = [0, 0, 0, 0] for i in xrange(len(self.sectionsSelected)): sectionIndex = self.sectionsSelected[i][0] attribs = ToonBlitzGlobals.SectionTypes[sectionIndex] enemiesPool = attribs[3] self.enemyHealthTable += [[]] enemyIndicesSelected = self.sectionsSelected[i][1] for j in xrange(len(enemyIndicesSelected)): enemyIndex = enemyIndicesSelected[j] enemyType = enemiesPool[enemyIndex][0] self.enemyHealthTable[i] += [ToonBlitzGlobals.EnemyBaseHealth] self.enemyHealthTable[i][j] *= self.numPlayers if enemyType in ToonBlitzGlobals.EnemyHealthMultiplier: self.enemyHealthTable[i][j] *= ToonBlitzGlobals.EnemyHealthMultiplier[enemyType] self.treasureTakenTable += [[]] treasureIndicesSelected = self.sectionsSelected[i][2] for j in xrange(len(treasureIndicesSelected)): self.treasureTakenTable[i] += [0] enemyIndicesSelected = self.sectionsSelected[i][1] for j in xrange(len(enemyIndicesSelected)): self.treasureTakenTable[i] += [0] def setGameStart(self, timestamp): self.notify.debug('setGameStart') DistributedMinigameAI.setGameStart(self, timestamp) self.gameFSM.request('play') def setGameAbort(self): self.notify.debug('setGameAbort') if self.gameFSM.getCurrentState(): self.gameFSM.request('cleanup') DistributedMinigameAI.setGameAbort(self) def gameOver(self): self.notify.debug('gameOver') scoreList = [] finishedBonusList = [] timeLeftList = [] treasureCollectedList = [] playerErrorList = [] for avId in self.avIdList: scoreList.append(self.scoreDict[avId]) finishedBonusList.append(self.finishedBonusDict[avId]) timeLeftList.append(self.finishedTimeLeftDict[avId]) treasureCollectedList.append(self.treasuresCollectedDict[avId]) playerError = [self.numFallDownDict[avId], self.numHitByEnemyDict[avId], self.numSquishDict[avId]] playerErrorList.append(playerError) self.scoreDict[avId] = max(0, self.scoreDict[avId]) jellybeans = sqrt(self.scoreDict[avId] * ToonBlitzGlobals.ScoreToJellyBeansMultiplier) self.scoreDict[avId] = max(1, int(jellybeans)) self.air.writeServerEventMessage('minigame_twoD', self.doId, '%s|%s|%s|%s|%s|%s|%s|%s|%s' % (ToontownGlobals.TwoDGameId, self.getSafezoneId(), self.avIdList, scoreList, finishedBonusList, timeLeftList, treasureCollectedList, playerErrorList, self.sectionIndexList)) self.notify.debug('minigame_twoD%s: %s|%s|%s|%s|%s|%s|%s|%s|%s' % (self.doId, ToontownGlobals.TwoDGameId, self.getSafezoneId(), self.avIdList, scoreList, finishedBonusList, timeLeftList, treasureCollectedList, playerErrorList, self.sectionIndexList)) self.gameFSM.request('cleanup') DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug('enterInactive') def exitInactive(self): pass def enterPlay(self): self.notify.debug('enterPlay') def allToonsDone(self = self): self.notify.debug('allToonsDone') self.sendUpdate('setEveryoneDone') if not ToonBlitzGlobals.EndlessGame: self.gameOver() def handleTimeout(avIds, self = self): self.notify.debug('handleTimeout: avatars %s did not report "done"' % avIds) self.setGameAbort() self.doneBarrier = ToonBarrier('waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, ToonBlitzGlobals.GameDuration[self.getSafezoneId()] + ToonBlitzGlobals.ShowScoresDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout) def exitPlay(self): pass def enterCleanup(self): self.notify.debug('enterCleanup') self.doneBarrier.cleanup() del self.doneBarrier self.gameFSM.request('inactive') def exitCleanup(self): pass def claimTreasure(self, sectionIndex, treasureIndex): avId = self.air.getAvatarIdFromSender() self.notify.debug('treasure %s-%s claimed by %s' % (sectionIndex, treasureIndex, avId)) if sectionIndex < 0 or sectionIndex >= len(self.sectionsSelected): self.air.writeServerEventMessage('warning', sectionIndex, 'TwoDGameAI.claimTreasure sectionIndex out of range.') return if treasureIndex < 0 or treasureIndex >= len(self.treasureTakenTable[sectionIndex]): self.notify.warning('Treasure %s: TwoDGameAI.claimTreasure treasureIndex out of range.' % treasureIndex) self.air.writeServerEventMessage('warning', treasureIndex, 'TwoDGameAI.claimTreasure treasureIndex out of range.') return if self.treasureTakenTable[sectionIndex][treasureIndex]: return initialTreasureList = self.sectionsSelected[sectionIndex][2] if treasureIndex < len(initialTreasureList): treasureValue = initialTreasureList[treasureIndex][1] else: treasureValue = self.numPlayers self.treasureTakenTable[sectionIndex][treasureIndex] = treasureValue self.treasuresCollectedDict[avId][treasureValue - 1] += 1 self.scoreDict[avId] += ToonBlitzGlobals.ScoreGainPerTreasure * treasureValue self.numTreasuresTaken += 1 self.sendUpdate('setTreasureGrabbed', [avId, sectionIndex, treasureIndex]) def claimEnemyShot(self, sectionIndex, enemyIndex): avId = self.air.getAvatarIdFromSender() self.notify.debug('enemy %s-%s shot claimed by %s' % (sectionIndex, enemyIndex, avId)) if sectionIndex < 0 or sectionIndex >= len(self.sectionsSelected): self.air.writeServerEventMessage('warning', sectionIndex, 'TwoDGameAI.claimEnemyShot sectionIndex out of range.') return if enemyIndex < 0 or enemyIndex >= len(self.sectionsSelected[sectionIndex][1]): self.air.writeServerEventMessage('warning', enemyIndex, 'TwoDGameAI.claimEnemyShot enemyIndex out of range.') return if self.enemyHealthTable[sectionIndex][enemyIndex] > 0: self.enemyHealthTable[sectionIndex][enemyIndex] -= ToonBlitzGlobals.DamagePerBullet if self.enemyHealthTable[sectionIndex][enemyIndex] <= 0: self.numEnemiesKilled += 1 self.sendUpdate('setEnemyShot', [avId, sectionIndex, enemyIndex, self.enemyHealthTable[sectionIndex][enemyIndex]]) def reportDone(self): if self.gameFSM.getCurrentState() == None or self.gameFSM.getCurrentState().getName() != 'play': return avId = self.air.getAvatarIdFromSender() self.notify.debug('reportDone: avatar %s is done' % avId) self.doneBarrier.clear(avId) return def toonVictory(self, avId, timestamp): if self.gameFSM.getCurrentState() == None or self.gameFSM.getCurrentState().getName() != 'play': msg = 'TwoDGameAI.toonVictory not in play state!' self.notify.warning('suspicious: ' + str(avId) + ' ' + msg) self.air.writeServerEventMessage('suspicious: ', avId, msg) return if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEventMessage('suspicious: ', avId, 'TwoDGameAI.toonVictory toon not in list.') return curTime = self.getCurrentGameTime() timeLeft = ToonBlitzGlobals.GameDuration[self.getSafezoneId()] - curTime self.notify.debug('curTime =%s timeLeft = %s' % (curTime, timeLeft)) addBonus = int(ToonBlitzGlobals.BaseBonusOnCompletion[self.getSafezoneId()] + ToonBlitzGlobals.BonusPerSecondLeft * timeLeft) self.notify.debug('addBOnus = %d' % addBonus) if addBonus < 0: addBonus = 0 self.finishedBonusDict[avId] = addBonus timeLeftStr = '%.1f' % timeLeft self.finishedTimeLeftDict[avId] = timeLeftStr self.scoreDict[avId] += addBonus self.sendUpdate('addVictoryScore', [avId, addBonus]) self.doneBarrier.clear(avId) return def toonFellDown(self, avId, timestamp): if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEventMessage('warning', avId, 'TwoDGameAI.toonFellDown toon not in list.') return self.numFallDownDict[avId] += 1 self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerFallDown[self.getSafezoneId()] def toonHitByEnemy(self, avId, timestamp): if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEventMessage('warning', avId, 'TwoDGameAI.toonHitByEnemy toon not in list.') return self.numHitByEnemyDict[avId] += 1 self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerEnemyCollision[self.getSafezoneId()] def toonSquished(self, avId, timestamp): if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEventMessage('warning', avId, 'TwoDGameAI.toonSquished toon not in list.') return self.numSquishDict[avId] += 1 self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerStomperSquish[self.getSafezoneId()] def setupSections(self): szId = self.getSafezoneId() sectionWeights = ToonBlitzGlobals.SectionWeights[szId] numSections = ToonBlitzGlobals.NumSections[szId] difficultyPool = [] difficultyList = [] sectionsPool = ToonBlitzGlobals.SectionsPool sectionTypes = ToonBlitzGlobals.SectionTypes sectionsPoolByDifficulty = [[], [], [], [], [], []] sectionsSelectedByDifficulty = [[], [], [], [], [], []] sectionIndicesSelected = [] for weight in sectionWeights: difficulty, probability = weight difficultyPool += [difficulty] * probability for i in xrange(numSections): difficulty = random.choice(difficultyPool) difficultyList.append(difficulty) difficultyList.sort() for sectionIndex in sectionsPool: difficulty = sectionTypes[sectionIndex][0] sectionsPoolByDifficulty[difficulty] += [sectionIndex] for targetDifficulty in difficultyList: whileCount = 0 difficulty = targetDifficulty while not len(sectionsPoolByDifficulty[difficulty]) > 0: difficulty += 1 if difficulty >= 5: difficulty = 0 whileCount += 1 if whileCount > 1: break else: sectionIndexChoice = random.choice(sectionsPoolByDifficulty[difficulty]) sectionsSelectedByDifficulty[difficulty] += [sectionIndexChoice] sectionsPoolByDifficulty[difficulty].remove(sectionIndexChoice) if whileCount > 1: self.notify.debug('We need more sections than we have choices. We have to now repeat.') for i in xrange(len(sectionsSelectedByDifficulty)): for j in xrange(len(sectionsSelectedByDifficulty[i])): sectionIndicesSelected.append(sectionsSelectedByDifficulty[i][j]) for i in xrange(len(sectionIndicesSelected)): sectionIndex = sectionIndicesSelected[i] self.sectionIndexList.append(sectionIndex) attribs = sectionTypes[sectionIndex] difficulty = attribs[0] length = attribs[1] blocksPool = attribs[2] enemiesPool = attribs[3] treasuresPool = attribs[4] spawnPointsPool = attribs[5] stompersPool = attribs[6] enemyIndicesPool = [] enemyIndicesSelected = [] if enemiesPool != None: minEnemies, maxEnemies = attribs[7] for i in xrange(len(enemiesPool)): enemyIndicesPool += [i] numEnemies = maxEnemies * ToonBlitzGlobals.PercentMaxEnemies[szId] / 100 numEnemies = max(numEnemies, minEnemies) for j in xrange(int(numEnemies)): if len(enemyIndicesPool) == 0: break enemyIndex = random.choice(enemyIndicesPool) enemyIndicesSelected.append(enemyIndex) enemyIndicesPool.remove(enemyIndex) enemyIndicesSelected.sort() treasureIndicesPool = [] treasureValuePool = [] for value in xrange(1, 5): treasureValuePool += [value] * ToonBlitzGlobals.TreasureValueProbability[value] treasureIndicesSelected = [] if treasuresPool != None: minTreasures, maxTreasures = attribs[8] for i in xrange(len(treasuresPool)): treasureIndicesPool += [i] numTreasures = maxTreasures * ToonBlitzGlobals.PercentMaxTreasures[szId] / 100 numTreasures = max(numTreasures, minTreasures) for i in xrange(int(numTreasures)): if len(treasureIndicesPool) == 0: break treasureIndex = random.choice(treasureIndicesPool) treasureValue = random.choice(treasureValuePool) treasure = (treasureIndex, treasureValue) treasureIndicesPool.remove(treasureIndex) treasureIndicesSelected.append(treasure) treasureIndicesSelected.sort() spawnPointIndicesPool = [] spawnPointIndicesSelected = [] if spawnPointsPool != None: minSpawnPoints, maxSpawnPoints = attribs[9] for i in xrange(len(spawnPointsPool)): spawnPointIndicesPool += [i] numSpawnPoints = maxSpawnPoints * ToonBlitzGlobals.PercentMaxSpawnPoints[szId] / 100 numSpawnPoints = max(numSpawnPoints, minSpawnPoints) for i in xrange(int(numSpawnPoints)): if len(spawnPointIndicesPool) == 0: break spawnPoint = random.choice(spawnPointIndicesPool) spawnPointIndicesSelected.append(spawnPoint) spawnPointIndicesPool.remove(spawnPoint) spawnPointIndicesSelected.sort() stomperIndicesPool = [] stomperIndicesSelected = [] if stompersPool != None: minStompers, maxStompers = attribs[10] for i in xrange(len(stompersPool)): stomperIndicesPool += [i] numStompers = maxStompers * ToonBlitzGlobals.PercentMaxStompers[szId] / 100 numStompers = max(numStompers, minStompers) for i in xrange(int(numStompers)): if len(stomperIndicesPool) == 0: break stomper = random.choice(stomperIndicesPool) stomperIndicesSelected.append(stomper) stomperIndicesPool.remove(stomper) stomperIndicesSelected.sort() sctionTuple = (sectionIndex, enemyIndicesSelected, treasureIndicesSelected, spawnPointIndicesSelected, stomperIndicesSelected) self.sectionsSelected.append(sctionTuple) return def getSectionsSelected(self): return self.sectionsSelected
class DistributedCatchGameAI(DistributedMinigameAI): def __init__(self, air, minigameId): try: self.DistributedCatchGameAI_initialized return except: self.DistributedCatchGameAI_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) self.gameFSM = ClassicFSM.ClassicFSM('DistributedCatchGameAI', [ State.State('inactive', self.enterInactive, self.exitInactive, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, ['inactive']) ], 'inactive', 'inactive') self.addChildGameFSM(self.gameFSM) def generate(self): self.notify.debug('generate') DistributedMinigameAI.generate(self) def delete(self): self.notify.debug('delete') del self.gameFSM DistributedMinigameAI.delete(self) def setGameReady(self): self.notify.debug('setGameReady') DistributedMinigameAI.setGameReady(self) def setGameStart(self, timestamp): self.notify.debug('setGameStart') DistributedMinigameAI.setGameStart(self, timestamp) self.gameFSM.request('play') def setGameAbort(self): self.notify.debug('setGameAbort') if self.gameFSM.getCurrentState(): self.gameFSM.request('cleanup') DistributedMinigameAI.setGameAbort(self) def gameOver(self): self.notify.debug('gameOver') self.notify.debug('fruits: %s, fruits caught: %s' % (self.numFruits, self.fruitsCaught)) perfect = self.fruitsCaught >= self.numFruits for avId in self.avIdList: self.scoreDict[avId] = max(1, int(self.scoreDict[avId] / 2)) if perfect: self.notify.debug('PERFECT GAME!') self.scoreDict[avId] += round(self.numFruits / 4.0) self.logAllPerfect() self.gameFSM.request('cleanup') DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug('enterInactive') def exitInactive(self): pass def enterPlay(self): self.notify.debug('enterPlay') self.caughtList = [0] * 100 table = CatchGameGlobals.NumFruits[self.numPlayers - 1] self.numFruits = table[self.getSafezoneId()] self.notify.debug('numFruits: %s' % self.numFruits) self.fruitsCaught = 0 def allToonsDone(self=self): self.notify.debug('allToonsDone') self.sendUpdate('setEveryoneDone') if not CatchGameGlobals.EndlessGame: self.gameOver() def handleTimeout(avIds, self=self): self.notify.debug( 'handleTimeout: avatars %s did not report "done"' % avIds) self.setGameAbort() self.doneBarrier = ToonBarrier( 'waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, CatchGameGlobals.GameDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout) def exitPlay(self): del self.caughtList self.doneBarrier.cleanup() del self.doneBarrier def claimCatch(self, objNum, DropObjTypeId): if self.gameFSM.getCurrentState().getName() != 'play': return if DropObjTypeId < 0 or DropObjTypeId >= len( CatchGameGlobals.DOTypeId2Name): self.air.writeServerEvent( 'warning', DropObjTypeId, 'CatchGameAI.claimCatch DropObjTypeId out of range') return if objNum < 0 or objNum > 5000 or objNum >= 2 * len(self.caughtList): self.air.writeServerEvent( 'warning', objNum, 'CatchGameAI.claimCatch objNum is too high or negative') return if objNum >= len(self.caughtList): self.caughtList += [0] * len(self.caughtList) if not self.caughtList[objNum]: self.caughtList[objNum] = 1 avId = self.air.getAvatarIdFromSender() self.sendUpdate('setObjectCaught', [avId, objNum]) objName = CatchGameGlobals.DOTypeId2Name[DropObjTypeId] self.notify.debug('avatar %s caught object %s: %s' % (avId, objNum, objName)) if CatchGameGlobals.Name2DropObjectType[objName].good: self.scoreDict[avId] += 1 self.fruitsCaught += 1 def reportDone(self): if not self.gameFSM or not self.gameFSM.getCurrentState( ) or self.gameFSM.getCurrentState().getName() != 'play': return avId = self.air.getAvatarIdFromSender() self.notify.debug('reportDone: avatar %s is done' % avId) self.doneBarrier.clear(avId) def enterCleanup(self): self.notify.debug('enterCleanup') self.gameFSM.request('inactive') def exitCleanup(self): pass
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.skippable = True self.skipAvIds = [] 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 _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.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) self.skippable = False 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) self.checkForSkip() 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 = [] for avId in self.avIdList: if self.normalExit: score = int(self.scoreDict[avId] + 0.5) else: score = 0 if simbase.air.newsManager.isHolidayRunning( ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY ) or simbase.air.newsManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY_MONTH): 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() self.handleRegularPurchaseManager(scoreList) self.frameworkFSM.request("frameworkOff") 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 requestSkip(self): avId = self.air.getAvatarIdFromSender() if (not self.skippable) or (avId not in self.avIdList) or (avId in self.skipAvIds): return self.skipAvIds.append(avId) self.checkForSkip() def checkForSkip(self): if len(self.skipAvIds) >= len(self.avIdList): self.skippable = False self.setGameAbort() else: self.sendUpdate("setSkipCount", [len(self.skipAvIds)])
class DistributedGolfHoleAI( DistributedPhysicsWorldAI.DistributedPhysicsWorldAI, FSM, GolfHoleBase.GolfHoleBase): defaultTransitions = { 'Off': ['Cleanup', 'WaitTee'], 'WaitTee': ['WaitSwing', 'Cleanup', 'WaitTee', 'WaitPlayback'], 'WaitSwing': ['WaitPlayback', 'Cleanup', 'WaitSwing', 'WaitTee'], 'WaitPlayback': ['WaitSwing', 'Cleanup', 'WaitTee', 'WaitPlayback'], 'Cleanup': ['Off'] # we can get a WaitSwing to WaitSwing transition when the active golfer disconnects # we can get a WaitTee to WaitTee transition when the active golfer disconnects # WaitSwing to WaitTee can happen on unexpected disconnects # WaitTee to WaitPlayback, unsure how this can happen, logging when it does # WaitPlayback to WaitPlayback, unsure how this can happen, logging when it does } id = 0 notify = directNotify.newCategory("DistributedGolfHoleAI") def __init__(self, zoneId, golfCourse, holeId): FSM.__init__(self, "Golf_%s_FSM" % (self.id)) DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.__init__( self, simbase.air) GolfHoleBase.GolfHoleBase.__init__(self) self.zoneId = zoneId self.golfCourse = golfCourse self.holeId = holeId self.avIdList = golfCourse.avIdList[:] self.watched = [0, 0, 0, 0] self.barrierPlayback = None self.trustedPlayerId = None self.activeGolferIndex = None self.activeGolferId = None self.holeInfo = GolfGlobals.HoleInfo[self.holeId] # -1 means he hasn't chosen a tee pos yet, otherwise its 0-left, 1-center or 2-right self.teeChosen = {} for avId in self.avIdList: self.teeChosen[avId] = -1 self.ballPos = {} for avId in self.avIdList: self.ballPos[avId] = Vec3(0, 0, 0) self.playStarted = False def curGolfBall(self): return self.ball def generate(self): DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.generate(self) # WARNING remove this line or else it will leak # simbase.golfHole = self self.ball = self.createBall() self.createRays() if len(self.teePositions) > 1: startPos = self.teePositions[1] else: startPos = self.teePositions[0] startPos += Vec3(0, 0, GolfGlobals.GOLF_BALL_RADIUS) self.ball.setPosition(startPos) def delete(self): self.notify.debug('__delete__') DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.delete(self) self.notify.debug('calling self.terrainModel.removeNode') self.terrainModel.removeNode() self.notify.debug('self.barrierPlayback is %s' % self.barrierPlayback) if self.barrierPlayback: self.notify.debug('calling self.barrierPlayback.cleanup') self.barrierPlayback.cleanup() self.notify.debug('calling self.barrierPlayback = None') self.barrierPlayback = None self.activeGolferId = None def setZoneId(self, zoneId): self.zoneId = zoneId def setAvatarReadyHole(self): self.notify.debugStateCall(self) avId = self.air.getAvatarIdFromSender() self.golfCourse.avatarReadyHole(avId) def startPlay(self): self.notify.debug('startPlay') self.playStarted = True self.numGolfers = len(self.golfCourse.getGolferIds()) self.selectNextGolfer() def selectNextGolfer(self): self.notify.debug( 'selectNextGolfer, old golferIndex=%s old golferId=%s' % (self.activeGolferIndex, self.activeGolferId)) if self.golfCourse.isCurHoleDone(): return if self.activeGolferIndex == None: self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[ self.activeGolferIndex] else: self.activeGolferIndex += 1 if self.activeGolferIndex >= len(self.golfCourse.getGolferIds()): self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[ self.activeGolferIndex] safety = 0 while (safety < 50) and (not self.golfCourse.checkGolferPlaying( self.golfCourse.getGolferIds()[self.activeGolferIndex])): self.activeGolferIndex += 1 self.notify.debug("Index %s" % (self.activeGolferIndex)) if self.activeGolferIndex >= len(self.golfCourse.getGolferIds()): self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[ self.activeGolferIndex] safety += 1 if safety != 50: golferId = self.golfCourse.getGolferIds()[self.activeGolferIndex] if self.teeChosen[golferId] == -1: # we need to wait for him to choose his starting tee position self.sendUpdate("golferChooseTee", [golferId]) self.request("WaitTee") else: self.sendUpdate("golfersTurn", [golferId]) self.request("WaitSwing") else: # we did not find a valid golfer to swing, finish the hole? self.notify.debug("safety") pass self.notify.debug( 'selectNextGolfer, new golferIndex=%s new golferId=%s' % (self.activeGolferIndex, self.activeGolferId)) def clearWatched(self): self.watched = [1, 1, 1, 1] for index in range(len(self.golfCourse.getGolferIds())): self.watched[index] = 0 def setWatched(self, avId): for index in range(len(self.golfCourse.getGolferIds())): if self.golfCourse.getGolferIds()[index] == avId: self.watched[index] = 1 def checkWatched(self): if 0 not in self.watched: return True else: return False def turnDone(self): self.notify.debug("Turn Done") avId = self.air.getAvatarIdFromSender() if self.barrierPlayback: self.barrierPlayback.clear(avId) #if avId == self.golfCourse.getGolferIds()[self.activeGolferIndex]: def ballInHole(self, golferId=None): self.notify.debug('ballInHole') if golferId: avId = golferId else: avId = self.air.getAvatarIdFromSender() self.golfCourse.setBallIn(avId) if self.golfCourse.isCurHoleDone(): # should we do something here? # self.finishHole() self.notify.debug('ballInHole doing nothing') pass else: self.notify.debug('ballInHole calling self.selectNextGolfer') self.selectNextGolfer() def getHoleId(self): """Return the holeId for this hole.""" return self.holeId def finishHole(self): """Finish this hole. We can get here through a magic word, or when everyone sinks the ball. """ self.notify.debug('finishHole') self.golfCourse.holeOver() #self.requestDelete() #this gets called in DistributedGolfCourseAI def getGolferIds(self): return self.avIdList def loadLevel(self): GolfHoleBase.GolfHoleBase.loadLevel(self) """Load all the assets needed by this golf hole for the AI.""" optionalObjects = self.terrainModel.findAllMatches("**/optional*") requiredObjects = self.terrainModel.findAllMatches("**/required*") self.parseLocators(optionalObjects, 1) self.parseLocators(requiredObjects, 0) #self.teeNodePath = self.terrainModel.find('**/tee0') #if self.teeNodePath.isEmpty(): # self.teePos = Vec3(0,0,10) #else: # self.teePos = self.teeNodePath.getPos() # setup the multiple tee starting positions self.teeNodePath = self.terrainModel.find('**/tee0') if self.teeNodePath.isEmpty(): teePos = Vec3(0, 0, 10) else: teePos = self.teeNodePath.getPos() teePos.setZ(teePos.getZ() + GolfGlobals.GOLF_BALL_RADIUS) self.notify.debug('teeNodePath heading = %s' % self.teeNodePath.getH()) self.teePositions = [teePos] teeIndex = 1 teeNode = self.terrainModel.find('**/tee%d' % teeIndex) while not teeNode.isEmpty(): teePos = teeNode.getPos() teePos.setZ(teePos.getZ() + GolfGlobals.GOLF_BALL_RADIUS) self.teePositions.append(teePos) self.notify.debug('teeNodeP heading = %s' % teeNode.getH()) teeIndex += 1 teeNode = self.terrainModel.find('**/tee%d' % teeIndex) #import pdb; pdb.set_trace() def createLocatorDict(self): """Create a dictionary of locator numbers to the actual nodepath.""" self.locDict = {} locatorNum = 1 curNodePath = self.hardSurfaceNodePath.find('**/locator%d' % locatorNum) while not curNodePath.isEmpty(): self.locDict[locatorNum] = curNodePath locatorNum += 1 curNodePath = self.hardSurfaceNodePath.find('**/locator%d' % locatorNum) def loadBlockers(self): """Load the programmable blockers.""" loadAll = simbase.config.GetBool('golf-all-blockers', 0) self.createLocatorDict() self.blockerNums = self.holeInfo['blockers'] for locatorNum in self.locDict: if locatorNum in self.blockerNums or loadAll: locator = self.locDict[locatorNum] locatorParent = locator.getParent() locator.getChildren().wrtReparentTo(locatorParent) else: self.locDict[locatorNum].removeNode() self.hardSurfaceNodePath.flattenStrong() def createBall(self): golfBallGeom = self.createSphere(self.world, self.space, GolfGlobals.GOLF_BALL_DENSITY, GolfGlobals.GOLF_BALL_RADIUS, 1)[1] #golfBallGeom.setName('golfBallGeom') return golfBallGeom def preStep(self): GolfHoleBase.GolfHoleBase.preStep(self) def postStep(self): GolfHoleBase.GolfHoleBase.postStep(self) def postSwing(self, cycleTime, power, x, y, z, dirX, dirY): #print("Swing Posted") avId = self.air.getAvatarIdFromSender() self.storeAction = [avId, cycleTime, power, x, y, z, dirX, dirY] if self.commonHoldData: self.doAction() def postSwingState(self, cycleTime, power, x, y, z, dirX, dirY, curAimTime, commonObjectData): """Handle the client telling the AI how the current player swung.""" self.notify.debug('postSwingState') # do not do anything if we have no players if not self.golfCourse.getStillPlayingAvIds(): return avId = self.air.getAvatarIdFromSender() self.storeAction = [avId, cycleTime, power, x, y, z, dirX, dirY] self.commonHoldData = commonObjectData self.trustedPlayerId = self.choosePlayerToSimulate() self.sendUpdateToAvatarId( self.trustedPlayerId, "assignRecordSwing", [avId, cycleTime, power, x, y, z, dirX, dirY, commonObjectData]) self.golfCourse.addAimTime(avId, curAimTime) #self.trackRecordBodyFlight(ball, cycleTime, power, Vec3(x,y,z), dirX, dirY) #self.doAction() def choosePlayerToSimulate(self): stillPlaying = self.golfCourse.getStillPlayingAvIds() playerId = 0 if simbase.air.config.GetBool('golf-trust-driver-first', 0): if stillPlaying: playerId = stillPlaying[0] else: playerId = random.choice(stillPlaying) return playerId def ballMovie2AI(self, cycleTime, avId, movie, spinMovie, ballInFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame, commonObjectData): sentFromId = self.air.getAvatarIdFromSender() if sentFromId == self.trustedPlayerId: #print'ballMovie2AI' lastFrameNum = len(movie) - 2 if lastFrameNum < 0: lastFrameNum = 0 lastFrame = movie[lastFrameNum] lastPos = Vec3(lastFrame[1], lastFrame[2], lastFrame[3]) self.ballPos[avId] = lastPos self.golfCourse.incrementScore(avId) for id in self.golfCourse.getStillPlayingAvIds(): if not id == sentFromId: self.sendUpdateToAvatarId(id, "ballMovie2Client", [ cycleTime, avId, movie, spinMovie, ballInFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame, commonObjectData ]) if self.state == 'WaitPlayback' or self.state == 'WaitTee': self.notify.warning( 'ballMovie2AI requesting from %s to WaitPlayback' % self.state) self.request("WaitPlayback") #self.sendUpdate("ballMovie2Client", [cycleTime, avId, movie, spinMovie, ballInFrame, ballTouchedHoleFrame, commonObjectData]) elif self.trustedPlayerId == None: return else: self.doAction() self.trustedPlayerId = None def performReadyAction(self): #hmm #print("performReadyAction") avId = self.storeAction[0] # it is possible for the client to send 2 post swings, avoid the crash if self.state == 'WaitPlayback': self.notify.debugStateCall(self) self.notify.debug( 'ignoring the postSwing for avId=%d since we are in WaitPlayback' % avId) return if avId == self.activeGolferId: self.golfCourse.incrementScore(self.activeGolferId) else: self.notify.warning('activGolferId %d not equal to sender avId %d' % \ (self.activeGolferId, avId)) if avId not in self.golfCourse.drivingToons: position = self.ballPos[avId] else: position = Vec3(self.storeAction[3], self.storeAction[4], self.storeAction[5]) self.useCommonObjectData(self.commonHoldData) newPos = self.trackRecordBodyFlight(self.ball, self.storeAction[1], self.storeAction[2], position, self.storeAction[6], self.storeAction[7]) if self.state == 'WaitPlayback' or self.state == 'WaitTee': self.notify.warning( 'performReadyAction requesting from %s to WaitPlayback' % self.state) self.request("WaitPlayback") #self.sendUpdate("ballMovie2Client", [self.storeAction[1], avId, self.recording, self.aVRecording, self.ballInHoleFrame, self.ballTouchedHoleFrame]) self.sendUpdate("ballMovie2Client", [ self.storeAction[1], avId, self.recording, self.aVRecording, self.ballInHoleFrame, self.ballTouchedHoleFrame, self.ballFirstTouchedHoleFrame, self.commonHoldData ]) self.ballPos[avId] = newPos self.trustedPlayerId = None def postResult(self, cycleTime, avId, recording, aVRecording, ballInHoleFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame): pass def enterWaitSwing(self): pass def exitWaitSwing(self): pass def enterWaitTee(self): pass def exitWaitTee(self): pass def enterWaitPlayback(self): self.notify.debug('enterWaitPlayback') stillPlayingList = self.golfCourse.getStillPlayingAvIds() self.barrierPlayback = ToonBarrier( 'waitClientsPlayback', self.uniqueName('waitClientsPlayback'), stillPlayingList, 120, self.handleWaitPlaybackDone, self.handlePlaybackTimeout) pass def hasCurGolferReachedMaxSwing(self): """Return true if the golfer has reached the maximum number of swings allowed.""" strokes = self.golfCourse.getCurHoleScore(self.activeGolferId) maxSwing = self.holeInfo['maxSwing'] retval = strokes >= maxSwing if retval: # double check that this golfer doesn't have unlimited swings av = simbase.air.doId2do.get(self.activeGolferId) if av: if av.getUnlimitedSwing(): retval = False return retval def handleWaitPlaybackDone(self): """We are done doing the ball rolling movie, check for the ball in the hole.""" if self.isCurBallInHole( self.activeGolferId) or self.hasCurGolferReachedMaxSwing(): if self.activeGolferId: self.ballInHole(self.activeGolferId) else: self.selectNextGolfer() def isCurBallInHole(self, golferId): """Returns True if the current ball is inside a hole, False otherwise.""" #import pdb; pdb.set_trace() retval = False for holePos in self.holePositions: #displacement = self.ball.getPosition() - holePos displacement = self.ballPos[golferId] - holePos length = displacement.length() self.notify.debug('hole %s length=%s' % (holePos, length)) if length <= GolfGlobals.DistanceToBeInHole: retval = True break return retval def exitWaitPlayback(self): self.notify.debug('exitWaitPlayback') if hasattr(self, "barrierPlayback") and self.barrierPlayback: self.barrierPlayback.cleanup() self.barrierPlayback = None pass def enterCleanup(self): pass def exitCleanup(self): pass def handlePlaybackTimeout(self, task=None): self.notify.debug('handlePlaybackTimeout') # TODO maybe check for disconnect? # do proper cleanup on a playback timeout self.handleWaitPlaybackDone() pass def getGolfCourseDoId(self): """Return the doid of golf course we are a part of.""" return self.golfCourse.doId def avatarDropped(self, avId): """Handle one of the player dropping unexpectedly.""" self.notify.warning('avId %d dropped, self.state=%s' % (avId, self.state)) if self.barrierPlayback: self.barrierPlayback.clear(avId) else: if avId == self.trustedPlayerId: self.doAction() if avId == self.activeGolferId and not self.golfCourse.haveAllGolfersExited( ): # the golfer aiming dropped, switch to a different one self.selectNextGolfer() def setAvatarTee(self, chosenTee): """Handle the client telling us his starting tee.""" golferId = self.air.getAvatarIdFromSender() self.teeChosen[golferId] = chosenTee self.ballPos[golferId] = self.teePositions[chosenTee] #print ("SETTING TEE POSITION %s" % (self.ballPos[golferId])) self.sendUpdate("setAvatarFinalTee", [golferId, chosenTee]) self.sendUpdate("golfersTurn", [golferId]) self.request("WaitSwing") def setBox(self, pos0, pos1, pos2, quat0, quat1, quat2, quat3, anV0, anV1, anV2, lnV0, lnV1, lnV2): self.sendUpdate("sendBox", [ pos0, pos1, pos2, quat0, quat1, quat2, quat3, anV0, anV1, anV2, lnV0, lnV1, lnV2 ]) def parseLocators(self, objectCollection, optional=0): if optional and objectCollection.getNumPaths(): # setup the optional movers as dictated by HoleInfo if 'optionalMovers' in self.holeInfo: for optionalMoverId in self.holeInfo['optionalMovers']: searchStr = 'optional_mover_' + str(optionalMoverId) for objIndex in range(objectCollection.getNumPaths()): object = objectCollection.getPath(objIndex) if searchStr in object.getName(): self.fillLocator(objectCollection, objIndex) break else: for index in range(objectCollection.getNumPaths()): self.fillLocator(objectCollection, index) def fillLocator(self, objectCollection, index): path = objectCollection[index] pathName = path.getName() pathArray = pathName.split("_") sizeX = None sizeY = None move = None type = None for subString in pathArray: #print subString if subString[:1] == "X": dataString = subString[1:] dataString = dataString.replace("p", ".") sizeX = float(dataString) elif subString[:1] == "Y": dataString = subString[1:] dataString = dataString.replace("p", ".") sizeY = float(dataString) elif subString[:1] == "d": dataString = subString[1:] dataString = dataString.replace("p", ".") move = float(dataString) elif subString == "mover": type = 4 elif subString == "windmillLocator": type = 3 else: pass if type == 4 and move and sizeX and sizeY: pass #self.create self.createCommonObject(4, path.getPos(), path.getHpr(), sizeX, sizeY, move) elif type == 3: self.createCommonObject(3, path.getPos(), path.getHpr())
class DistributedGolfCourseAI(DistributedObjectAI.DistributedObjectAI, FSM): notify = directNotify.newCategory('DistributedGolfCourseAI') defaultTransitions = {'Off': ['WaitJoin'], 'WaitJoin': ['WaitReadyCourse', 'Cleanup'], 'WaitReadyCourse': ['WaitReadyHole', 'Cleanup'], 'WaitReadyHole': ['PlayHole', 'Cleanup', 'WaitLeaveHole', 'WaitReward'], 'PlayHole': ['PlayHole', 'WaitLeaveHole', 'Cleanup', 'WaitReward'], 'WaitLeaveHole': ['WaitReadyHole', 'WaitLeaveCourse', 'Cleanup', 'WaitReward'], 'WaitReward': ['WaitLeaveCourse', 'Cleanup', 'WaitLeaveHole'], 'WaitLeaveCourse': ['Cleanup'], 'Cleanup': ['Off']} def __init__(self, zoneId, avIds, courseId, preferredHoleId = None): FSM.__init__(self, 'GolfCourse_%s_FSM' % zoneId) DistributedObjectAI.DistributedObjectAI.__init__(self, simbase.air) self.notify.debug('GOLF COURSE: init') self.zoneId = zoneId self.currentHole = None self.avIdList = [] self.avStateDict = {} self.addExpectedGolfers(avIds) self.courseId = courseId self.preferredHoleId = preferredHoleId self.courseInfo = GolfGlobals.CourseInfo[self.courseId] self.numHoles = self.courseInfo['numHoles'] self.holeIds = self.calcHolesToUse() self.notify.debug('self.holeIds = %s' % self.holeIds) self.numHolesPlayed = 0 self.curHoleIndex = 0 self.trophyListLen = 0 self.courseBestListLen = 0 self.holeBestListLen = 0 self.cupListLen = 0 self.scores = {} self.aimTimes = {} self.startingHistory = {} self.endingHistory = {} self.startingHoleBest = {} self.endingHoleBest = {} self.startingCourseBest = {} self.endingCourseBest = {} self.startingCups = {} self.endingCups = {} self.initHistory() self.newTrophies = {} self.newHoleBest = {} self.newCourseBest = {} self.newCups = {} self.drivingToons = [] self.__barrier = None self.winnerByTieBreak = 0 return def initHistory(self): for avId in self.avIdList: av = simbase.air.doId2do.get(avId) if av: history = av.getGolfHistory() self.startingHistory[avId] = history[:] self.endingHistory[avId] = history[:] holeBest = av.getGolfHoleBest() self.startingHoleBest[avId] = holeBest[:] self.endingHoleBest[avId] = holeBest[:] courseBest = av.getGolfCourseBest() self.startingCourseBest[avId] = courseBest[:] self.endingCourseBest[avId] = courseBest[:] def generate(self): DistributedObjectAI.DistributedObjectAI.generate(self) self.grabGolfers() def delete(self): self.notify.debug('GOLF COURSE: delete: deleting AI GolfCourse object') if hasattr(self, 'rewardBarrier'): self.rewardBarrier.cleanup() del self.rewardBarrier if self.currentHole: self.notify.debug('calling requestDelete on hole %d' % self.currentHole.doId) self.currentHole.requestDelete() self.currentHole = None self.ignoreAll() self.air.deallocateZone(self.zoneId) if self.__barrier: self.__barrier.cleanup() self.__barrier = None DistributedObjectAI.DistributedObjectAI.delete(self) return def load(self): self.b_setCourseReady() self.request('WaitReadyCourse') def getZoneId(self): return self.zoneId def addExpectedGolfers(self, avIdList): self.notify.debug('Sending %s to course %s' % (avIdList, self.zoneId)) for avId in avIdList: golfer = simbase.air.doId2do.get(avId) if golfer: if avId not in self.avIdList: self.avIdList.append(avId) self.avStateDict[avId] = INITIAL elif self.avStateDict[avId] == EXITED: if self.isGenerated(): pass else: self.notify.warning('GOLF COURSE: trying to grab golfer %s that is already on the course' % avId) def grabGolfers(self): for avId in self.avIdList: golfer = simbase.air.doId2do.get(avId) if golfer: if self.avStateDict[avId] == INITIAL: self.avStateDict[avId] = EXPECTED self.request('WaitJoin') def getGolferIds(self): return self.avIdList def checkGolferPlaying(self, avId): if self.avStateDict[avId] == ONHOLE: return 1 else: return 0 def b_setCourseReady(self): self.setCourseReady() self.d_setCourseReady() def d_setCourseReady(self): self.notify.debug('GOLF COURSE: Sending setCourseReady') self.sendUpdate('setCourseReady', [self.numHoles, self.holeIds, self.calcCoursePar()]) def setCourseReady(self): self.notify.debug('GOLF COURSE: setCourseReady: golf course ready with avatars: %s' % self.avIdList) self.trophyListLen = 0 self.courseBestListLen = 0 self.holeBestListLen = 0 self.cupListLen = 0 self.normalExit = 1 def d_setPlayHole(self): self.notify.debug('GOLF COURSE: setPlayHole: play on golf hole about to start') self.sendUpdate('setPlayHole', []) def b_setCourseExit(self): self.d_setCourseExit() self.setCourseExit() def d_setCourseExit(self): self.notify.debug('GOLF COURSE: Sending setGameExit') self.sendUpdate('setCourseExit', []) def setCourseExit(self): self.notify.debug('GOLF COURSE: setGameExit') def handleExitedAvatar(self, avId): self.notify.warning('GOLF COURSE: handleExitedAvatar: avatar id exited: ' + str(avId)) self.avStateDict[avId] = EXITED self.sendUpdate('avExited', [avId]) if self.currentHole and not self.haveAllGolfersExited(): self.currentHole.avatarDropped(avId) if self.haveAllGolfersExited(): self.setCourseAbort() elif self.isCurHoleDone(): if self.isPlayingLastHole(): if self.state not in ['WaitReward', 'WaitReadyHole']: self.safeDemand('WaitReward') else: self.notify.debug('allBalls are in holes, calling holeOver') self.holeOver() if hasattr(self, 'rewardBarrier'): if self.rewardBarrier: self.rewardBarrier.clear(avId) if hasattr(self, '__barrier'): if self.__barrier: self.__.clear(avId) def startNextHole(self): self.notify.debugStateCall(self) holeId = self.holeIds[self.numHolesPlayed] self.currentHole = DistributedGolfHoleAI.DistributedGolfHoleAI(self.zoneId, golfCourse=self, holeId=holeId) self.currentHole.generateWithRequired(self.zoneId) self.d_setCurHoleDoId(self.currentHole.doId) self.safeDemand('WaitReadyHole') def holeOver(self): self.notify.debug('GOLF COURSE: holeOver') self.numHolesPlayed += 1 if self.numHolesPlayed < self.numHoles: self.b_setCurHoleIndex(self.numHolesPlayed) self.safeDemand('WaitLeaveHole') def setCourseAbort(self): self.notify.debug('GOLF COURSE: setGameAbort') self.normalExit = 0 self.sendUpdate('setCourseAbort', [0]) self.safeDemand('Cleanup') def enterOff(self): self.notify.debug('GOLF COURSE: enterOff') def exitOff(self): self.notify.debug('GOLF COURSE: exitOff') def enterWaitJoin(self): self.notify.debug('GOLF COURSE: enterWaitJoin') for avId in self.avIdList: self.avStateDict[avId] = EXPECTED self.acceptOnce(self.air.getAvatarExitEvent(avId), self.handleExitedAvatar, extraArgs=[avId]) def allAvatarsJoined(self = self): self.notify.debug('GOLF COURSE: all avatars joined') self.load() def handleTimeout(avIds, self = self): self.notify.debug('GOLF COURSE: timed out waiting for clients %s to join' % avIds) for avId in self.avStateDict: if not self.avStateDict[avId] == JOINED: self.handleExitedAvatar(avId) if self.haveAllGolfersExited(): self.setCourseAbort() else: self.load() self.__barrier = ToonBarrier('waitClientsJoin', self.uniqueName('waitClientsJoin'), self.avIdList, JOIN_TIMEOUT, allAvatarsJoined, handleTimeout) def exitWaitJoin(self): self.notify.debugStateCall(self) self.__barrier.cleanup() self.__barrier = None return def setAvatarJoined(self): avId = self.air.getAvatarIdFromSender() self.notify.debug('GOLF COURSE: setAvatarJoined: avatar id joined: ' + str(avId)) self.avStateDict[avId] = JOINED self.notify.debug('GOLF COURSE: setAvatarJoined: new states: ' + str(self.avStateDict)) if hasattr(self, '_DistributedGolfCourseAI__barrier') and self.__barrier: self.__barrier.clear(avId) else: self.notify.warning('setAvatarJoined avId=%d but barrier is invalid' % avId) def exitFrameworkWaitClientsJoin(self): self.__barrier.cleanup() del self.__barrier def enterWaitReadyCourse(self): self.notify.debug('GOLF COURSE: enterWaitReadyCourse') def allAvatarsInCourse(self = self): self.notify.debug('GOLF COURSE: all avatars ready course') for avId in self.avIdList: blankScoreList = [0] * self.numHoles self.scores[avId] = blankScoreList self.aimTimes[avId] = 0 self.notify.debug('self.scores = %s' % self.scores) self.startNextHole() def handleTimeout(avIds, self = self): self.notify.debug("GOLF COURSE: Course timed out waiting for clients %s to report 'ready'" % avIds) if self.haveAllGolfersExited(): self.setCourseAbort() else: allAvatarsInCourse() self.__barrier = ToonBarrier('WaitReadyCourse', self.uniqueName('WaitReadyCourse'), self.avIdList, READY_TIMEOUT, allAvatarsInCourse, handleTimeout) for avId in self.avStateDict.keys(): if self.avStateDict[avId] == READY: self.__barrier.clear(avId) def setAvatarReadyCourse(self): avId = self.air.getAvatarIdFromSender() self.notify.debug('GOLF COURSE: setAvatarReadyCourse: avatar id ready: ' + str(avId)) self.avStateDict[avId] = READY self.notify.debug('GOLF COURSE: setAvatarReadyCourse: new avId states: ' + str(self.avStateDict)) if self.state == 'WaitReadyCourse': self.__barrier.clear(avId) def exitWaitReadyCourse(self): self.notify.debugStateCall(self) self.__barrier.cleanup() self.__barrier = None return def enterWaitReadyHole(self): self.notify.debug('GOLF COURSE: enterWaitReadyHole') def allAvatarsInHole(self = self): self.notify.debug('GOLF COURSE: all avatars ready hole') if self.safeDemand('PlayHole'): self.d_setPlayHole() def handleTimeout(avIds, self = self): self.notify.debug("GOLF COURSE: Hole timed out waiting for clients %s to report 'ready'" % avIds) if self.haveAllGolfersExited(): self.setCourseAbort() elif self.safeDemand('PlayHole'): self.d_setPlayHole() stillPlaying = self.getStillPlayingAvIds() self.__barrier = ToonBarrier('WaitReadyHole', self.uniqueName('WaitReadyHole'), stillPlaying, READY_TIMEOUT, allAvatarsInHole, handleTimeout) for avId in self.avStateDict.keys(): if self.avStateDict[avId] == ONHOLE: self.__barrier.clear(avId) def exitWaitReadyHole(self): self.notify.debugStateCall(self) if hasattr(self, '__barrier'): self.__barrier.cleanup() self.__barrier = None return def getStillPlayingAvIds(self): retval = [] for avId in self.avIdList: av = simbase.air.doId2do.get(avId) if av: if self.avStateDict.has_key(avId) and not self.avStateDict[avId] == EXITED: retval.append(avId) return retval def avatarReadyHole(self, avId): if self.state not in ['WaitJoin', 'WaitReadyCourse', 'WaitReadyHole']: self.notify.debug('GOLF COURSE: Ignoring setAvatarReadyHole message') return self.notify.debug('GOLF COURSE: setAvatarReadyHole: avatar id ready: ' + str(avId)) self.avStateDict[avId] = ONHOLE self.notify.debug('GOLF COURSE: setAvatarReadyHole: new avId states: ' + str(self.avStateDict)) if self.state == 'WaitReadyHole': self.__barrier.clear(avId) def enterPlayHole(self): self.notify.debug('GOLF COURSE: enterPlayHole') if self.currentHole and not self.currentHole.playStarted: self.currentHole.startPlay() def exitPlayHole(self): self.notify.debug('GOLF COURSE: exitPlayHole') def enterWaitLeaveHole(self): self.notify.debugStateCall(self) self.notify.debug('calling requestDelete on hole %d' % self.currentHole.doId) self.currentHole.requestDelete() self.currentHole = None if self.numHolesPlayed >= self.numHoles: pass else: self.startNextHole() return def exitWaitLeaveHole(self): pass def enterWaitReward(self): self.updateHistoryForCourseComplete() self.awardTrophies() self.awardCups() self.awardHoleBest() self.awardCourseBest() self.recordHoleInOne() self.recordCourseUnderPar() trophiesList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newTrophies: oneTrophyList = self.newTrophies[avId] trophiesList.append(oneTrophyList) else: trophiesList.append([]) while len(trophiesList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: trophiesList.append([]) holeBestList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newHoleBest: oneTrophyList = self.newHoleBest[avId] holeBestList.append(oneTrophyList) else: holeBestList.append([]) while len(holeBestList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: holeBestList.append([]) courseBestList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newCourseBest: oneTrophyList = self.newCourseBest[avId] courseBestList.append(oneTrophyList) else: courseBestList.append([]) while len(courseBestList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: courseBestList.append([]) cupList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newCups: oneCupList = self.newCups[avId] cupList.append(oneCupList) self.cupListLen = self.cupListLen + 1 else: cupList.append([]) while len(cupList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: cupList.append([]) REWARD_TIMEOUT = (self.trophyListLen + self.holeBestListLen + self.courseBestListLen + self.cupListLen) * 5 + 19 aimTimesList = [0] * 4 aimIndex = 0 stillPlaying = self.getStillPlayingAvIds() for avId in self.avIdList: if avId in stillPlaying: aimTime = 0 if avId in self.aimTimes: aimTime = self.aimTimes[avId] aimTimesList[aimIndex] = aimTime aimIndex += 1 self.sendUpdate('setReward', [trophiesList, self.rankings, holeBestList, courseBestList, cupList, self.winnerByTieBreak, aimTimesList[0], aimTimesList[1], aimTimesList[2], aimTimesList[3]]) def allAvatarsRewarded(self = self): self.notify.debug('GOLF COURSE: all avatars rewarded') self.rewardDone() def handleRewardTimeout(avIds, self = self): self.notify.debug('GOLF COURSE: timed out waiting for clients %s to finish reward' % avIds) self.rewardDone() stillPlaying = self.getStillPlayingAvIds() self.rewardBarrier = ToonBarrier('waitReward', self.uniqueName('waitReward'), stillPlaying, REWARD_TIMEOUT, allAvatarsRewarded, handleRewardTimeout) def exitWaitReward(self): pass def enterWaitLeaveCourse(self): self.notify.debugStateCall(self) self.setCourseAbort() def exitWaitLeaveCourse(self): pass def enterCleanup(self): self.notify.debug('GOLF COURSE: enterCleanup') self.requestDelete() def exitCleanup(self): self.notify.debug('GOLF COURSE: exitCleanup') def isCurHoleDone(self): retval = False if self.areAllBallsInHole(): retval = True else: retval = True for state in self.avStateDict.values(): if not (state == BALLIN or state == EXITED): retval = False break return retval def areAllBallsInHole(self): self.notify.debug('areAllBallsInHole, self.avStateDict=%s' % self.avStateDict) allBallsInHole = True for state in self.avStateDict.values(): if state != BALLIN: allBallsInHole = False return allBallsInHole def isPlayingLastHole(self): retval = self.numHoles - self.numHolesPlayed == 1 return retval def setBallIn(self, avId): self.notify.debug('setBallIn %d' % avId) if self.avStateDict[avId] == BALLIN: self.notify.debug('setBallIn already in BALLIN state, just returning') return self.avStateDict[avId] = BALLIN self.updateHistoryForBallIn(avId) if self.isCurHoleDone(): if self.isPlayingLastHole(): if self.state != 'WaitReward': self.safeDemand('WaitReward') else: self.notify.debug('allBalls are in holes, calling holeOver') self.holeOver() def updateHistoryForBallIn(self, avId): if self.currentHole == None: return holeId = self.currentHole.holeId holeInfo = GolfGlobals.HoleInfo[holeId] par = holeInfo['par'] holeIndex = self.numHolesPlayed if holeIndex >= self.numHoles: self.notify.warning('updateHistoryForBallIn invalid holeIndex %d' % holeIndex) holeIndex = self.numHoles - 1 elif holeIndex < 0: self.notify.warning('updateHistoryForBallIn invalid holeIndex %d' % holeIndex) holeIndex = 0 strokes = self.scores[avId][holeIndex] self.notify.debug('self.scores = %s' % self.scores) diff = strokes - par if strokes == 1: self.incrementEndingHistory(avId, GolfGlobals.HoleInOneShots) if diff <= -2: self.incrementEndingHistory(avId, GolfGlobals.EagleOrBetterShots) if diff <= -1: self.incrementEndingHistory(avId, GolfGlobals.BirdieOrBetterShots) if diff <= 0: self.endingHistory[avId][GolfGlobals.ParOrBetterShots] += 1 if strokes < self.endingHoleBest[avId][holeId] or self.endingHoleBest[avId][holeId] == 0: self.endingHoleBest[avId][holeId] = strokes return def incrementEndingHistory(self, avId, historyIndex): if self.endingHistory.has_key(avId) and GolfGlobals.TrophyRequirements.has_key(historyIndex): maximumAmount = GolfGlobals.TrophyRequirements[historyIndex][-1] if self.endingHistory[avId][historyIndex] < maximumAmount: self.endingHistory[avId][historyIndex] += 1 def getCourseId(self): return self.courseId def abortCurrentHole(self): holeId = self.currentHole.holeId holeDoId = self.currentHole.doId self.currentHole.finishHole() return (holeId, holeDoId) def calcHolesToUse(self): retval = [] if simbase.air.config.GetBool('golf-course-randomized', 1): retval = self.calcHolesToUseRandomized(self.courseId) self.notify.debug('randomized courses!') for x in range(len(retval)): self.notify.debug('Hole is: %s' % retval[x]) else: validHoles = self.calcUniqueHoles(self.courseId) if self.preferredHoleId in validHoles: retval.append(self.preferredHoleId) while len(retval) < self.numHoles: for holeId in GolfGlobals.CourseInfo[self.courseId]['holeIds']: if type(holeId) == type(0): retval.append(holeId) elif type(holeId) == type(()): retval.append(holeId[0]) else: self.notify.warning('cant handle %s' % self.holeId) if len(retval) >= self.numHoles: break return retval def incrementScore(self, avId): self.notify.debug('incrementScore self.scores=%s avId=%s' % (self.scores, avId)) self.scores[avId][self.numHolesPlayed] += 1 self.notify.debug('after increment self.score=%s' % self.scores) self.sendScores() def sendScores(self): self.notify.debug('sendScores self.scores = %s' % self.scores) scorelist = [] for avId in self.avIdList: for score in self.scores[avId]: scorelist.append(score) self.sendUpdate('setScores', [scorelist]) self.notify.debug('sendScores end self.scores = %s' % self.scores) def getCurHoleIndex(self): return self.curHoleIndex def b_setCurHoleIndex(self, holeIndex): self.setCurHoleIndex(holeIndex) self.d_setCurHoleIndex(holeIndex) def d_setCurHoleIndex(self, holeIndex): self.sendUpdate('setCurHoleIndex', [holeIndex]) def setCurHoleIndex(self, holeIndex): self.curHoleIndex = holeIndex def setDoneReward(self): avId = self.air.getAvatarIdFromSender() self.notify.debug('got rewardDone from %d' % avId) if hasattr(self, 'rewardBarrier'): self.rewardBarrier.clear(avId) self.sendUpdate('setCourseAbort', [avId]) def rewardDone(self): self.notify.debug('rewardDone') self.holeOver() self.safeDemand('WaitLeaveCourse') def updateHistoryForCourseComplete(self): self.calcRankings() stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: self.incrementEndingHistory(avId, GolfGlobals.CoursesCompleted) coursePar = self.calcCoursePar() totalScore = self.getTotalScore(avId) if totalScore < coursePar: self.incrementEndingHistory(avId, GolfGlobals.CoursesUnderPar) if len(stillPlaying) > 1: self.incrementEndingHistory(avId, GolfGlobals.MultiPlayerCoursesCompleted) if self.rankingsById[avId] == 1: if self.courseId == 0: self.incrementEndingHistory(avId, GolfGlobals.CourseZeroWins) elif self.courseId == 1: self.incrementEndingHistory(avId, GolfGlobals.CourseOneWins) elif self.courseId == 2: self.incrementEndingHistory(avId, GolfGlobals.CourseTwoWins) else: self.notify.warning('unhandled case, self.courseId=%s' % self.courseId) if totalScore < self.endingCourseBest[avId][self.courseId] or self.endingCourseBest[avId][self.courseId] == 0: self.endingCourseBest[avId][self.courseId] = totalScore def calcRankings(self): stillPlaying = self.getStillPlayingAvIds() self.rankings = [] totalScores = [] for avId in self.avIdList: aimTime = 0 if avId in self.aimTimes: aimTime = self.aimTimes[avId] if avId in stillPlaying: totalScores.append((avId, self.getTotalScore(avId), aimTime)) else: totalScores.append((avId, 255, aimTime)) def scoreCompareNoTime(tupleA, tupleB): if tupleA[1] > tupleB[1]: return 1 elif tupleA[1] == tupleB[1]: return 0 else: return -1 def scoreCompareWithTime(tupleA, tupleB): if tupleA[1] > tupleB[1]: return 1 elif tupleA[1] == tupleB[1]: if tupleA[2] > tupleB[2]: return 1 elif tupleA[2] == tupleB[2]: return 0 else: return -1 else: return -1 if GolfGlobals.TIME_TIE_BREAKER: totalScores.sort(scoreCompareWithTime) else: totalScores.sort(scoreCompareNoTime) curRank = 0 oldScore = 0 oldTime = 0 self.rankingsById = {} for scoreTuple in totalScores: time = scoreTuple[2] score = scoreTuple[1] avId = scoreTuple[0] if score > oldScore or GolfGlobals.TIME_TIE_BREAKER and score == oldScore and time > oldTime: curRank += 1 oldScore = score oldTime = time self.rankingsById[avId] = curRank tiedForFirst = [] tempRank = 0 oldScore = 0 oldTime = 0 for scoreTuple in totalScores: time = scoreTuple[2] score = scoreTuple[1] avId = scoreTuple[0] if score > oldScore: tempRank += 1 oldScore = score oldTime = time if tempRank == 1: tiedForFirst.append(avId) for avId in self.avIdList: if avId in stillPlaying: self.rankings.append(self.rankingsById[avId]) else: self.rankings.append(-1) if len(tiedForFirst) >= 2 and not GolfGlobals.TIME_TIE_BREAKER: winnerAvId = random.choice(tiedForFirst) winnerIndex = self.avIdList.index(winnerAvId) self.winnerByTieBreak = winnerAvId for index in xrange(len(self.rankings)): if self.rankings[index] > 0 and index != winnerIndex: self.rankings[index] += 1 for avId in self.rankingsById: if self.rankingsById[avId] > 0 and avId != winnerAvId: self.rankingsById[avId] += 1 elif len(tiedForFirst) >= 2: winnerAvId = totalScores[0][0] self.winnerByTieBreak = winnerAvId def awardTrophies(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHistory = self.startingHistory[avId] endingHistory = self.endingHistory[avId] oldTrophies = GolfGlobals.calcTrophyListFromHistory(oldHistory) endingTrophies = GolfGlobals.calcTrophyListFromHistory(endingHistory) av.b_setGolfHistory(endingHistory) newTrophies = [] for index in xrange(len(oldTrophies)): if not oldTrophies[index] and endingTrophies[index]: self.notify.debug('New Trophy %d' % index) self.air.writeServerEvent('golf_trophy', avId, '%s' % index) newTrophies.append(True) self.trophyListLen = self.trophyListLen + 1 else: newTrophies.append(False) self.newTrophies[avId] = newTrophies def awardCups(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHistory = self.startingHistory[avId] endingHistory = self.endingHistory[avId] oldCups = GolfGlobals.calcCupListFromHistory(oldHistory) endingCups = GolfGlobals.calcCupListFromHistory(endingHistory) newCups = [] for index in xrange(len(oldCups)): if not oldCups[index] and endingCups[index]: self.notify.debug('New Trophy %d' % index) newCups.append(True) self.air.writeServerEvent('golf_cup', avId, '%s' % index) newMaxHp = av.getMaxHp() + 1 av.b_setMaxHp(newMaxHp) av.toonUp(newMaxHp) else: newCups.append(False) self.newCups[avId] = newCups def awardHoleBest(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHoleBest = self.startingHoleBest[avId] endingHoleBest = self.endingHoleBest[avId] av.b_setGolfHoleBest(endingHoleBest) newHoleBest = [] longestHoleBestList = 0 for index in xrange(len(oldHoleBest)): if endingHoleBest[index] < oldHoleBest[index]: self.notify.debug('New HoleBest %d' % index) newHoleBest.append(True) longestHoleBestList = longestHoleBestList + 1 else: newHoleBest.append(False) if longestHoleBestList > self.holeBestListLen: self.holeBestListLen = longestHoleBestList self.newHoleBest[avId] = newHoleBest def awardCourseBest(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldCourseBest = self.startingCourseBest[avId] endingCourseBest = self.endingCourseBest[avId] av.b_setGolfCourseBest(endingCourseBest) newCourseBest = [] longestCourseBestList = 0 for index in xrange(len(oldCourseBest)): if endingCourseBest[index] < oldCourseBest[index]: self.notify.debug('New CourseBest %d' % index) newCourseBest.append(True) longestCourseBestList = longestCourseBestList + 1 else: newCourseBest.append(False) if longestCourseBestList > self.courseBestListLen: self.courseBestListLen = longestCourseBestList self.newCourseBest[avId] = newCourseBest def haveAllGolfersExited(self): retval = True for avId in self.avStateDict: if not self.avStateDict[avId] == EXITED: retval = False break return retval def getCurHoleDoId(self): retval = 0 if self.currentHole: retval = self.currentHole.doId return retval def d_setCurHoleDoId(self, curHoleDoId): self.sendUpdate('setCurHoleDoId', [curHoleDoId]) def calcCoursePar(self): retval = 0 for holeId in self.holeIds: holeInfo = GolfGlobals.HoleInfo[holeId] retval += holeInfo['par'] return retval def getTotalScore(self, avId): retval = 0 if self.scores.has_key(avId): for holeScore in self.scores[avId]: retval += holeScore return retval def getCurHoleScore(self, avId): retval = 0 if avId in self.scores and self.numHolesPlayed < len(self.scores[avId]): retval = self.scores[avId][self.numHolesPlayed] return retval def toggleDrivePermission(self, avId): if avId in self.drivingToons: self.drivingToons.remove(avId) self.sendUpdate('changeDrivePermission', [avId, 0]) retval = False else: self.drivingToons.append(avId) self.sendUpdate('changeDrivePermission', [avId, 1]) retval = True return retval def safeDemand(self, newState): doingDemand = False if self.state == 'Cleanup': pass else: if self.state in self.defaultTransitions: if newState in self.defaultTransitions[self.state]: self.demand(newState) doingDemand = True elif self.state == None: self.demand(newState) doingDemand = True if not doingDemand: self.notify.warning('doId=%d ignoring demand from %s to %s' % (self.doId, self.state, newState)) return doingDemand def setAvatarExited(self): avId = self.air.getAvatarIdFromSender() self.handleExitedAvatar(avId) def createChoicesList(self, courseId, possibleHoles): retval = [] holeIds = GolfGlobals.CourseInfo[courseId]['holeIds'] for holeOrTuple in holeIds: if type(holeOrTuple) == type(()): holeId = holeOrTuple[0] weight = holeOrTuple[1] elif type(holeOrTuple) == type(0): holeId = holeOrTuple weight = 1 else: self.notify.warning('cant handle %s' % holeOrTuple) continue if holeId in possibleHoles: retval += [holeId] * weight return retval def calcUniqueHoles(self, courseId): uniqueHoles = set() for holeOrTuple in GolfGlobals.CourseInfo[courseId]['holeIds']: if type(holeOrTuple) == type(()): uniqueHoles.add(holeOrTuple[0]) elif type(holeOrTuple) == type(0): uniqueHoles.add(holeOrTuple) else: self.notify.warning('cant handle %s' % holeOrTuple) return uniqueHoles def calcHolesToUseRandomized(self, courseId): retval = [] numHoles = GolfGlobals.CourseInfo[courseId]['numHoles'] uniqueHoles = self.calcUniqueHoles(courseId) curHolesChosen = set() while len(retval) < numHoles: if uniqueHoles == curHolesChosen: curHolesChosen = set() possibleHoles = uniqueHoles - curHolesChosen choicesList = self.createChoicesList(courseId, possibleHoles) if not self.preferredHoleId == None and self.preferredHoleId in choicesList and self.preferredHoleId not in curHolesChosen: holeChosen = self.preferredHoleId else: holeChosen = random.choice(choicesList) retval.append(holeChosen) curHolesChosen.add(holeChosen) return retval def recordHoleInOne(self): stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: scoreList = self.scores[avId] for holeIndex in xrange(len(scoreList)): strokes = scoreList[holeIndex] if strokes == 1: holeId = self.holeIds[holeIndex] self.air.writeServerEvent('golf_ace', avId, '%d|%d|%s' % (self.courseId, holeId, stillPlaying)) def recordCourseUnderPar(self): coursePar = self.calcCoursePar() stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: totalScore = self.getTotalScore(avId) netScore = totalScore - coursePar if netScore < 0: self.air.writeServerEvent('golf_underPar', avId, '%d|%d|%s' % (self.courseId, netScore, stillPlaying)) def addAimTime(self, avId, aimTime): if avId in self.aimTimes: self.aimTimes[avId] += aimTime
class DistributedMinigameAI(DistributedObjectAI.DistributedObjectAI): """ This is the base class for all Distributed Minigames on the AI. """ # private so as not to conflict with subclass notify 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 # prefix every state name with 'framework' to avoid naming overlaps # with minigame subclasses 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']), ], # Initial State 'frameworkOff', # Final State 'frameworkOff', ) self.frameworkFSM.enterInitialState() # Actual avatars that will play the game self.avIdList = [] self.stateDict = {} self.scoreDict = {} self.difficultyOverride = None self.trolleyZoneOverride = None self.metagameRound = -1 self.startingVotes = {} #the votes that carry over def addChildGameFSM(self, gameFSM): """ inheritors should call this with their game ClassicFSM """ self.frameworkFSM.getStateNamed('frameworkGame').addChild(gameFSM) def removeChildGameFSM(self, gameFSM): """ inheritors should call this with their game ClassicFSM """ self.frameworkFSM.getStateNamed('frameworkGame').removeChild(gameFSM) def setExpectedAvatars(self, avIds): """ Whoever created this minigame on the AI should call this to tell us who will be playing the game. The DistributedMinigameAI then waits to hear join messages from each of the avIds. Or instead of joining, we might hear an exitEvent instead. """ assert len(avIds) > 0 and len(avIds) <= 4 assert 0 not in avIds assert None not in avIds self.avIdList = avIds self.numPlayers = len(self.avIdList) self.notify.debug("BASE: setExpectedAvatars: expecting avatars: " + str(self.avIdList)) def setNewbieIds(self, newbieIds): """ Minigame creator should call this to let us know which players are playing for the first time. """ assert len(newbieIds) >= 0 and len(newbieIds) <= 4 assert 0 not in newbieIds assert None not in newbieIds self.newbieIdList = newbieIds if len(self.newbieIdList) > 0: self.notify.debug('BASE: setNewbieIds: %s' % self.newbieIdList) def setTrolleyZone(self, trolleyZone): """ This must be called before the object is generated trolleyZone is the zone that the toons were in before entering this minigame """ self.trolleyZone = trolleyZone def setDifficultyOverrides(self, difficultyOverride, trolleyZoneOverride): """ This must be called before the object is generated difficultyOverride is the difficulty value that should be used and sent to the clients trolleyZoneOverride is similar """ self.difficultyOverride = difficultyOverride if self.difficultyOverride is not None: # modify difficultyOverride so that it will convert to an # integer and back without losing any precision self.difficultyOverride = ( MinigameGlobals.QuantizeDifficultyOverride(difficultyOverride)) self.trolleyZoneOverride = trolleyZoneOverride def setMetagameRound(self, roundNum): """ -1 means it's a normal minigame any other number means its that round typically 0 - travel game 1 - minigame resulting from travel game 2 - travel game 3 - minigame resulting from travel game 4 - travel game 5 - minigame resulting from travel game """ self.metagameRound = roundNum def generate(self): DistributedObjectAI.DistributedObjectAI.generate(self) # kick off the ClassicFSM self.frameworkFSM.request('frameworkWaitClientsJoin') # Disable is never called on the AI so we do not define one def delete(self): self.notify.debug("BASE: delete: deleting AI minigame object") del self.frameworkFSM self.ignoreAll() DistributedObjectAI.DistributedObjectAI.delete(self) def isSinglePlayer(self): """ returns nonzero if there is only one player """ if self.numPlayers == 1: return 1 else: return 0 """ def b_setParticipants(self, avIds): self.setParticipants(avIds) self.d_setParticipants(avIds) def d_setParticipants(self, avIds): self.notify.debug("BASE: Sending setParticipants") self.sendUpdate("setParticipants", [avIds]) def setParticipants(self, avIds): self.notify.debug("BASE: setParticipants: game will be played by " "these avatars: %s" % avIds) """ def getParticipants(self): # getter for setParticipants # note that clients will not be able to access the avatars until # setGameReady() is called return self.avIdList def getTrolleyZone(self): # getter for setTrolleyZone return self.trolleyZone def getDifficultyOverrides(self): # getter for difficulty overrides response = [self.difficultyOverride, self.trolleyZoneOverride] if response[0] is None: response[0] = MinigameGlobals.NoDifficultyOverride else: # convert the difficulty override to an integer so that # the AI and clients will have the exact same value 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): """ This method gets called when all avatars have joined """ self.notify.debug("BASE: setGameReady: game ready with avatars: %s" % self.avIdList) self.normalExit = 1 def b_setGameStart(self, timestamp): # send the distributed message first, so that any network msgs # sent by the subclass upon start of the game will arrive # after the game start msg 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): """ This method gets called when all avatars are ready Inheritors should call this plus the code to start the game """ 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): """ This method gets called when it's time for avatars to exit the game """ self.notify.debug("BASE: setGameExit") def setGameAbort(self): """ This gets called in the case of an unexpected abort If the minigame needs to do anything before we send the abort msg to the clients, override this func (but be sure to call this func on the base class) """ self.notify.debug("BASE: setGameAbort") self.normalExit = 0 self.sendUpdate("setGameAbort", []) # only transition to cleanup after we've sent the gameAbort msg self.frameworkFSM.request("frameworkCleanup") def handleExitedAvatar(self, avId): """ An avatar bailed out because he lost his connection or quit unexpectedly. We have decided when this happens, we will just kill the minigame altogether """ # TODO: what if they have all exited already? self.notify.warning("BASE: handleExitedAvatar: avatar id exited: " + str(avId)) self.stateDict[avId] = EXITED # Send the game exit update to kill the minigame and cause # all the clients to exit and cleanup self.setGameAbort() def gameOver(self): """ Called by the subclass to indicate to the framework ClassicFSM that the game is over and the framework ClassicFSM should move ahead into cleanup state """ self.notify.debug("BASE: gameOver") # wait for the clients to tell us they've exited self.frameworkFSM.request('frameworkWaitClientsExit') # Framework state machine functions def enterFrameworkOff(self): self.notify.debug("BASE: enterFrameworkOff") def exitFrameworkOff(self): pass def enterFrameworkWaitClientsJoin(self): """ This state waits for all of the clients to join. see setAvatarJoined """ self.notify.debug("BASE: enterFrameworkWaitClientsJoin") for avId in self.avIdList: self.stateDict[avId] = EXPECTED self.scoreDict[avId] = DEFAULT_POINTS # listen for this avatar's exit event self.acceptOnce(self.air.getAvatarExitEvent(avId), self.handleExitedAvatar, extraArgs=[avId]) def allAvatarsJoined(self=self): self.notify.debug("BASE: all avatars joined") # Everybody is here, wait for them to read the rules self.b_setGameReady() # wait for clients to be ready 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) # at this point, it's not possible for any avatars to have # already joined def setAvatarJoined(self): """ This is a distributed update that gets called from the clients when this distributed object is created on their machine. Each time we hear that a single avatar has joined, we check to see if they have all joined. """ # check to make sure this message is still relevant 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): """ This state waits for all of the clients to be ready. see setAvatarReady """ self.notify.debug("BASE: enterFrameworkWaitClientsReady") def allAvatarsReady(self=self): self.notify.debug("BASE: all avatars ready") # Everybody is here, start the game 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) # some clients may already be ready 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): """ This is a distributed update that gets called from the clients when they are ready. Usually this means they have finished reading the rules panel. Each time we hear that a single avatar is ready, we check to see if they are all ready. If they are all ready, we send a setGameStart to actually start playing the minigame. """ # check to make sure this message is still relevant # note that it's possible for one client to report 'joined' and # 'ready' before another client has even reported 'joined' 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 we're in the waitClientsReady state, update the barrier; # otherwise, just having set this avatar's stateDict entry is # sufficient (the barrier will be updated accordingly when we # enter the waitClientsReady state) if (self.frameworkFSM.getCurrentState().getName() == 'frameworkWaitClientsReady'): self.__barrier.clear(avId) def exitFrameworkWaitClientsReady(self): self.__barrier.cleanup() del self.__barrier def enterFrameworkGame(self): """ The primary job of this state is to kick off the subclass gameFSM (which should be a child state machine of this state) """ self.notify.debug("BASE: enterFrameworkGame") self.gameStartTime = globalClock.getRealTime() self.b_setGameStart(globalClockDelta.localToNetworkTime(\ self.gameStartTime)) def exitFrameworkGame(self): pass def enterFrameworkWaitClientsExit(self): """ this state waits for all of the clients to report that they have exited the minigame """ self.notify.debug("BASE: enterFrameworkWaitClientsExit") # tell the clients to leave self.b_setGameExit() def allAvatarsExited(self=self): self.notify.debug("BASE: all avatars exited") # go to the cleanup state self.frameworkFSM.request('frameworkCleanup') def handleTimeout(avIds, self=self): """ Well, we did not hear from all the clients that they exited, but it has been long enough. Go ahead and get out of here """ self.notify.debug("BASE: timed out waiting for clients %s " "to exit" % avIds) self.frameworkFSM.request('frameworkCleanup') # time out on waiting for clients to exit - then abort self.__barrier = ToonBarrier( 'waitClientsExit', self.uniqueName('waitClientsExit'), self.avIdList, EXIT_TIMEOUT, allAvatarsExited, handleTimeout) # process any toons that have already exited for avId in list(self.stateDict.keys()): if self.stateDict[avId] == EXITED: self.__barrier.clear(avId) def setAvatarExited(self): """ This is a distributed update that gets called from the clients when they leave after the game is over """ # check to make sure this message is still relevant 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) # scale the scores based on the neighborhood # use self.getSafezoneId to pick up debug overrides scoreMult = MinigameGlobals.getScoreMult(self.getSafezoneId()) if not self.hasScoreMult(): scoreMult = 1.0 self.notify.debug('score multiplier: %s' % scoreMult) for avId in self.avIdList: assert avId not in [0, None] self.scoreDict[avId] *= scoreMult # create a score list that parallels the avIdList scoreList = [] if not self.normalExit: # if game exited abnormally, pick a uniform number of points # for all toons randReward = random.randrange(DEFAULT_POINTS, MAX_POINTS+1) for avId in self.avIdList: assert avId not in [0, None] # put in some bogus points if we have requested abort if self.normalExit: score = int(self.scoreDict[avId]+.5) if score > 255: self.notify.warning('avatar %s got %s jellybeans playing minigame %s in zone %s' % (avId, score, self.minigameId, self.getSafezoneId())) score = 255 elif score < 0: # RAU just in case I miss something in ice game score = 0 scoreList.append(score) else: scoreList.append(randReward) # Delete yourself self.requestDelete() if self.metagameRound > -1: self.handleMetagamePurchaseManager(scoreList) else: self.handleRegularPurchaseManager(scoreList) self.frameworkFSM.request('frameworkOff') def handleMetagamePurchaseManager(self,scoreList): """ metagame being played, handle play again and consider newbies """ 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 # let purchaseManager handle incrementing it # if this is not the travel game, add the beans we earned to the votes list # also make sure it's not the last game 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: # newbie PM gets a single newbie, and we also give it the # list of all players; note that we don't give newbie PMs the # 'newbie list' -- that's only for the regular PM. pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI( self.air, lastAvId, self.avIdList, scoreList, self.minigameId, self.trolleyZone) # We have no idea if the newbie PM is going to be around longer # than the 'regular' minigame->purchase->minigame... sequence. # We have to reference-count the zone. PMs decrement the zone # reference count when all participants leave to the playground. 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: # this is the last minigame, handle newbies. and playAgain # also for now weare doing a regular minigame if only 1 person # presses play again, self.notify.debug('last minigame, handling newbies') # create separate NewbiePurchaseManagerAIs for the noobs for id in self.newbieIdList: # newbie PM gets a single newbie, and we also give it the # list of all players; note that we don't give newbie PMs the # 'newbie list' -- that's only for the regular PM. pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI( self.air, id, self.avIdList, scoreList, self.minigameId, self.trolleyZone) # We have no idea if the newbie PM is going to be around longer # than the 'regular' minigame->purchase->minigame... sequence. # We have to reference-count the zone. PMs decrement the zone # reference count when all participants leave to the playground. MinigameCreatorAI.acquireMinigameZone(self.zoneId) pm.generateWithRequired(self.zoneId) if len(self.avIdList) > len(self.newbieIdList): # Create a PurchaseManager pm = PurchaseManagerAI.PurchaseManagerAI( self.air, self.avIdList, scoreList, self.minigameId, self.trolleyZone, self.newbieIdList, votesArray = votesArray, metagameRound = self.metagameRound) pm.generateWithRequired(self.zoneId) def handleRegularPurchaseManager(self, scoreList): """ regular minigame, handle purchase manager stuff """ # create separate NewbiePurchaseManagerAIs for the noobs for id in self.newbieIdList: # newbie PM gets a single newbie, and we also give it the # list of all players; note that we don't give newbie PMs the # 'newbie list' -- that's only for the regular PM. pm = NewbiePurchaseManagerAI.NewbiePurchaseManagerAI( self.air, id, self.avIdList, scoreList, self.minigameId, self.trolleyZone) # We have no idea if the newbie PM is going to be around longer # than the 'regular' minigame->purchase->minigame... sequence. # We have to reference-count the zone. PMs decrement the zone # reference count when all participants leave to the playground. MinigameCreatorAI.acquireMinigameZone(self.zoneId) pm.generateWithRequired(self.zoneId) if len(self.avIdList) > len(self.newbieIdList): # Create a PurchaseManager 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): """ This is a handler for debugging... it lets players request an immediate end to the minigame. """ self.notify.debug( "BASE: requestExit: client has requested the game to end") self.setGameAbort() # time-related utility functions def local2GameTime(self, timestamp): """ given a local-time timestamp, returns the corresponding timestamp relative to the start of the game """ return timestamp - self.gameStartTime def game2LocalTime(self, timestamp): """ given a game-time timestamp, returns the corresponding local timestamp """ return timestamp + self.gameStartTime def getCurrentGameTime(self): return self.local2GameTime(globalClock.getFrameTime()) # difficulty-related utility functions def getDifficulty(self): """ returns 0..1 """ 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): """ returns 1000-multiple safezone zoneId; can be matched to safezone IDs in ToontownGlobals.py """ 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): """ records the fact that this avatar had a perfect game """ self.air.writeServerEvent('perfectMinigame', avId, '%s|%s|%s' % ( self.minigameId, self.trolleyZone, self.avIdList)) def logAllPerfect(self): """ records a perfect game for all participants """ for avId in self.avIdList: self.logPerfectGame(avId) def getStartingVotes(self): """ make sure we return it in the same order as avIdList """ 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
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.skippable = True self.skipAvIds = [] 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 _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.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) self.skippable = False 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) self.checkForSkip() 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 = [] for avId in self.avIdList: if self.normalExit: score = int(self.scoreDict[avId] + 0.5) else: score = 0 if simbase.air.newsManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY) or simbase.air.newsManager.isHolidayRunning(ToontownGlobals.JELLYBEAN_TROLLEY_HOLIDAY_MONTH): 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() self.handleRegularPurchaseManager(scoreList) self.frameworkFSM.request('frameworkOff') 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 requestSkip(self): avId = self.air.getAvatarIdFromSender() if (not self.skippable) or (avId not in self.avIdList) or (avId in self.skipAvIds): return self.skipAvIds.append(avId) self.checkForSkip() def checkForSkip(self): if len(self.skipAvIds) >= len(self.avIdList): self.skippable = False self.setGameAbort() else: self.sendUpdate('setSkipCount', [len(self.skipAvIds)])
class DistributedGolfCourseAI(DistributedObjectAI.DistributedObjectAI, FSM): notify = directNotify.newCategory("DistributedGolfCourseAI") defaultTransitions = { 'Off': [ 'WaitJoin', ], 'WaitJoin': ['WaitReadyCourse', 'Cleanup'], 'WaitReadyCourse': ['WaitReadyHole', 'Cleanup'], 'WaitReadyHole': ['PlayHole', 'Cleanup', "WaitLeaveHole", 'WaitReward'], 'PlayHole': ['PlayHole', 'WaitLeaveHole', 'Cleanup', 'WaitReward'], 'WaitLeaveHole': ['WaitReadyHole', 'WaitLeaveCourse', 'Cleanup', 'WaitReward'], 'WaitReward': ['WaitLeaveCourse', 'Cleanup', 'WaitLeaveHole'], 'WaitLeaveCourse': [ 'Cleanup', ], 'Cleanup': ['Off'], } def __init__(self, zoneId, avIds, courseId, preferredHoleId=None): FSM.__init__(self, "GolfCourse_%s_FSM" % (zoneId)) DistributedObjectAI.DistributedObjectAI.__init__(self, simbase.air) self.notify.debug("GOLF COURSE: init") self.zoneId = zoneId self.currentHole = None #self.currentHole = DistributedGolfHoleAI.DistributedGolfHoleAI(self.zoneId, golfCourse = self) #self.currentHole.generateWithRequired(self.zoneId) # avatars that will play the game self.avIdList = [] self.avStateDict = {} self.addExpectedGolfers(avIds) self.courseId = courseId self.preferredHoleId = preferredHoleId self.courseInfo = GolfGlobals.CourseInfo[self.courseId] self.numHoles = self.courseInfo['numHoles'] self.holeIds = self.calcHolesToUse() self.notify.debug('self.holeIds = %s' % self.holeIds) self.numHolesPlayed = 0 self.curHoleIndex = 0 self.trophyListLen = 0 self.courseBestListLen = 0 self.holeBestListLen = 0 self.cupListLen = 0 self.scores = {} # scores of each toon self.aimTimes = {} # total aim times of each toon self.startingHistory = { } # the golf history of the toons before the course starts self.endingHistory = { } # the golf history of the toons after the course ends self.startingHoleBest = {} self.endingHoleBest = {} self.startingCourseBest = {} self.endingCourseBest = {} self.startingCups = {} self.endingCups = {} self.initHistory() self.newTrophies = { } # when they finish the course, the new trophies each toon receives self.newHoleBest = { } # when they finish the course, the new hole best each toon receives self.newCourseBest = { } # when they finish the course, the new course besteach toon receives self.newCups = {} self.drivingToons = [ ] # list of toons who can cheat and drive the ball self.__barrier = None self.winnerByTieBreak = 0 def initHistory(self): """Make a copy of the avatars golf history for us to manipulate.""" for avId in self.avIdList: av = simbase.air.doId2do.get(avId) if av: history = av.getGolfHistory() self.startingHistory[avId] = history[:] self.endingHistory[avId] = history[:] holeBest = av.getGolfHoleBest() self.startingHoleBest[avId] = holeBest[:] self.endingHoleBest[avId] = holeBest[:] courseBest = av.getGolfCourseBest() self.startingCourseBest[avId] = courseBest[:] self.endingCourseBest[avId] = courseBest[:] def generate(self): DistributedObjectAI.DistributedObjectAI.generate(self) # WARNING remove this line or else it will leak # simbase.golfCourse = self self.grabGolfers() def delete(self): self.notify.debug("GOLF COURSE: delete: deleting AI GolfCourse object") if hasattr(self, 'rewardBarrier'): self.rewardBarrier.cleanup() del self.rewardBarrier if self.currentHole: self.notify.debug('calling requestDelete on hole %d' % self.currentHole.doId) self.currentHole.requestDelete() self.currentHole = None self.ignoreAll() # tell GolfManagerAI to remove us # TODO figure out why the import doesn't work at the top of the file from toontown.golf import GolfManagerAI GolfManagerAI.GolfManagerAI().removeCourse(self) if self.__barrier: self.__barrier.cleanup() self.__barrier = None DistributedObjectAI.DistributedObjectAI.delete(self) def load(self): self.b_setCourseReady() self.request('WaitReadyCourse') def getZoneId(self): return self.zoneId def addExpectedGolfers(self, avIdList): self.notify.debug("Sending %s to course %s" % (avIdList, self.zoneId)) for avId in avIdList: golfer = simbase.air.doId2do.get(avId) if golfer: if avId not in self.avIdList: self.avIdList.append(avId) self.avStateDict[avId] = INITIAL #if self.isGenerated(): # golfer.sendUpdate("sendToGolfCourse", [self.zoneId]) elif self.avStateDict[avId] == EXITED: if self.isGenerated(): #golfer.sendUpdate("sendToGolfCourse", [self.zoneId]) pass else: self.notify.warning( "GOLF COURSE: trying to grab golfer %s that is already on the course" % (avId)) def grabGolfers(self): for avId in self.avIdList: golfer = simbase.air.doId2do.get(avId) if golfer: if self.avStateDict[avId] == INITIAL: #golfer.sendUpdate("sendToGolfCourse", [self.zoneId]) self.avStateDict[avId] = EXPECTED self.request('WaitJoin') def getGolferIds(self): return self.avIdList def checkGolferPlaying(self, avId): if self.avStateDict[avId] == ONHOLE: return 1 else: return 0 def b_setCourseReady(self): self.setCourseReady() self.d_setCourseReady() def d_setCourseReady(self): self.notify.debug("GOLF COURSE: Sending setCourseReady") self.sendUpdate("setCourseReady", [self.numHoles, self.holeIds, self.calcCoursePar()]) def setCourseReady(self): """ This method gets called when all avatars have joined """ self.notify.debug( "GOLF COURSE: setCourseReady: golf course ready with avatars: %s" % self.avIdList) self.trophyListLen = 0 self.courseBestListLen = 0 self.holeBestListLen = 0 self.cupListLen = 0 self.normalExit = 1 def d_setPlayHole(self): """ Tells the distributed golf course that play is about to begin """ self.notify.debug( "GOLF COURSE: setPlayHole: play on golf hole about to start") self.sendUpdate("setPlayHole", []) def b_setCourseExit(self): self.d_setCourseExit() self.setCourseExit() def d_setCourseExit(self): self.notify.debug("GOLF COURSE: Sending setGameExit") self.sendUpdate("setCourseExit", []) def setCourseExit(self): """ This method gets called when it's time for avatars to exit the golf course """ self.notify.debug("GOLF COURSE: setGameExit") def handleExitedAvatar(self, avId): """ An avatar bailed out because he lost his connection or quit unexpectedly. We have decided when this happens, we will continue wihtout them """ # TODO: what if they have all exited already? self.notify.warning( "GOLF COURSE: handleExitedAvatar: avatar id exited: " + str(avId)) self.avStateDict[avId] = EXITED # Let the clients know that one of them has dropped self.sendUpdate("avExited", [avId]) if self.currentHole and not self.haveAllGolfersExited(): # if the guy who dropped was taking his turn, we need to move to the next guy self.currentHole.avatarDropped(avId) if self.haveAllGolfersExited(): self.setCourseAbort() else: # we need to move to next hole if the guy who dropped was the only one who hasn't sunk a ball if self.isCurHoleDone(): if self.isPlayingLastHole(): if self.state not in ["WaitReward", "WaitReadyHole"]: self.safeDemand('WaitReward') else: self.notify.debug( 'allBalls are in holes, calling holeOver') self.holeOver() # remove the avId from any ToonBarriers waiting on it if hasattr(self, 'rewardBarrier'): if self.rewardBarrier: self.rewardBarrier.clear(avId) if hasattr(self, '__barrier'): if self.__barrier: self.__.clear(avId) def startNextHole(self): self.notify.debugStateCall(self) holeId = self.holeIds[self.numHolesPlayed] self.currentHole = DistributedGolfHoleAI.DistributedGolfHoleAI( self.zoneId, golfCourse=self, holeId=holeId) self.currentHole.generateWithRequired(self.zoneId) self.d_setCurHoleDoId(self.currentHole.doId) self.safeDemand('WaitReadyHole') def holeOver(self): """ Called from the hole to let the course know to start the next hole """ self.notify.debug("GOLF COURSE: holeOver") self.numHolesPlayed += 1 if self.numHolesPlayed < self.numHoles: self.b_setCurHoleIndex(self.numHolesPlayed) self.safeDemand('WaitLeaveHole') def setCourseAbort(self): """ This gets called in the case of an unexpected abort """ self.notify.debug("GOLF COURSE: setGameAbort") self.normalExit = 0 self.sendUpdate("setCourseAbort", [0]) # only transition to cleanup after we've sent the gameAbort msg self.safeDemand("Cleanup") ####################################################################################### ### FSM FUNCTIONS ####################################################################################### def enterOff(self): self.notify.debug("GOLF COURSE: enterOff") def exitOff(self): self.notify.debug("GOLF COURSE: exitOff") def enterWaitJoin(self): """ This state waits for all of the clients to join. see setAvatarJoined """ self.notify.debug("GOLF COURSE: enterWaitJoin") for avId in self.avIdList: self.avStateDict[avId] = EXPECTED # listen for this avatar's exit event self.acceptOnce(self.air.getAvatarExitEvent(avId), self.handleExitedAvatar, extraArgs=[avId]) def allAvatarsJoined(self=self): self.notify.debug("GOLF COURSE: all avatars joined") # Everybody is here, wait for them to ready the course # wait for clients to be ready self.load() def handleTimeout(avIds, self=self): self.notify.debug("GOLF COURSE: timed out waiting for clients %s " "to join" % avIds) for avId in self.avStateDict: if not self.avStateDict[avId] == JOINED: #kick player self.handleExitedAvatar(avId) if self.haveAllGolfersExited(): self.setCourseAbort() else: self.load() self.__barrier = ToonBarrier('waitClientsJoin', self.uniqueName('waitClientsJoin'), self.avIdList, JOIN_TIMEOUT, allAvatarsJoined, handleTimeout) # at this point, it's not possible for any avatars to have # already joined def exitWaitJoin(self): self.notify.debugStateCall(self) self.__barrier.cleanup() self.__barrier = None def setAvatarJoined(self): """ This is a distributed update that gets called from the clients when this distributed object is created on their machine. Each time we hear that a single avatar has joined, we check to see if they have all joined. """ # check to make sure this message is still relevant #if (self.state != # 'WaitJoin'): # self.notify.debug("GOLF COURSE: Ignoring setAvatarJoined message") # return avId = self.air.getAvatarIdFromSender() self.notify.debug("GOLF COURSE: setAvatarJoined: avatar id joined: " + str(avId)) self.avStateDict[avId] = JOINED self.notify.debug("GOLF COURSE: setAvatarJoined: new states: " + str(self.avStateDict)) if hasattr(self, "_DistributedGolfCourseAI__barrier" ) and self._DistributedGolfCourseAI__barrier: self.__barrier.clear(avId) else: self.notify.warning( "setAvatarJoined avId=%d but barrier is invalid" % avId) def exitFrameworkWaitClientsJoin(self): self.__barrier.cleanup() del self.__barrier def enterWaitReadyCourse(self): """ This state waits for all of the clients to be ready. see setAvatarReady """ self.notify.debug("GOLF COURSE: enterWaitReadyCourse") def allAvatarsInCourse(self=self): self.notify.debug("GOLF COURSE: all avatars ready course") # Everybody is here, start the game # prepopulate the scores with zeros for avId in self.avIdList: blankScoreList = [0] * self.numHoles self.scores[avId] = blankScoreList self.aimTimes[avId] = 0 self.notify.debug('self.scores = %s' % self.scores) self.startNextHole() def handleTimeout(avIds, self=self): self.notify.debug( "GOLF COURSE: Course timed out waiting for clients %s " "to report 'ready'" % avIds) if self.haveAllGolfersExited(): self.setCourseAbort() else: allAvatarsInCourse() self.__barrier = ToonBarrier('WaitReadyCourse', self.uniqueName('WaitReadyCourse'), self.avIdList, READY_TIMEOUT, allAvatarsInCourse, handleTimeout) # some clients may already be ready for avId in self.avStateDict.keys(): if self.avStateDict[avId] == READY: self.__barrier.clear(avId) def setAvatarReadyCourse(self): """ This is a distributed update that gets called from the clients when they are ready. Usually this means they have finished reading the rules panel. Each time we hear that a single avatar is ready, we check to see if they are all ready. If they are all ready, we send a ??? to actually start playing the golf round. """ # check to make sure this message is still relevant # note that it's possible for one client to report 'joined' and # 'ready' before another client has even reported 'joined' #if (self.state not in # ['WaitJoin', 'WaitReadyCourse']): # self.notify.debug("GOLF COURSE: Ignoring setAvatarReadyCourse message") # return avId = self.air.getAvatarIdFromSender() self.notify.debug( "GOLF COURSE: setAvatarReadyCourse: avatar id ready: " + str(avId)) self.avStateDict[avId] = READY self.notify.debug( "GOLF COURSE: setAvatarReadyCourse: new avId states: " + str(self.avStateDict)) # if we're in the waitClientsReady state, update the barrier; # otherwise, just having set this avatar's stateDict entry is # sufficient (the barrier will be updated accordingly when we # enter the waitClientsReady state) if (self.state == 'WaitReadyCourse'): self.__barrier.clear(avId) def exitWaitReadyCourse(self): self.notify.debugStateCall(self) self.__barrier.cleanup() self.__barrier = None def enterWaitReadyHole(self): self.notify.debug("GOLF COURSE: enterWaitReadyHole") def allAvatarsInHole(self=self): self.notify.debug("GOLF COURSE: all avatars ready hole") # Everybody is here, start the game if self.safeDemand('PlayHole'): self.d_setPlayHole() def handleTimeout(avIds, self=self): self.notify.debug( "GOLF COURSE: Hole timed out waiting for clients %s " "to report 'ready'" % avIds) if self.haveAllGolfersExited(): self.setCourseAbort() else: if self.safeDemand('PlayHole'): self.d_setPlayHole() stillPlaying = self.getStillPlayingAvIds() self.__barrier = ToonBarrier('WaitReadyHole', self.uniqueName('WaitReadyHole'), stillPlaying, READY_TIMEOUT, allAvatarsInHole, handleTimeout) # some clients may already be ready for avId in self.avStateDict.keys(): if self.avStateDict[avId] == ONHOLE: self.__barrier.clear(avId) def exitWaitReadyHole(self): self.notify.debugStateCall(self) if hasattr(self, "__barrier"): self.__barrier.cleanup() self.__barrier = None def getStillPlayingAvIds(self): """Return a list of av ids are still playing and have not been disconnected.""" retval = [] for avId in self.avIdList: av = simbase.air.doId2do.get(avId) if av: if self.avStateDict.has_key(avId) and not \ self.avStateDict[avId] == EXITED: retval.append(avId) return retval def avatarReadyHole(self, avId): """ This is a distributed update that gets called from the clients when they are ready. Usually this means they have finished reading the rules panel. Each time we hear that a single avatar is ready, we check to see if they are all ready. If they are all ready, we send a ??? to actually start playing the golf round. """ # check to make sure this message is still relevant # note that it's possible for one client to report 'joined' and # 'ready' before another client has even reported 'joined' if (self.state not in ['WaitJoin', 'WaitReadyCourse', 'WaitReadyHole']): self.notify.debug( "GOLF COURSE: Ignoring setAvatarReadyHole message") return #avId = self.air.getAvatarIdFromSender() self.notify.debug( "GOLF COURSE: setAvatarReadyHole: avatar id ready: " + str(avId)) self.avStateDict[avId] = ONHOLE self.notify.debug( "GOLF COURSE: setAvatarReadyHole: new avId states: " + str(self.avStateDict)) # if we're in the waitClientsReady state, update the barrier; # otherwise, just having set this avatar's stateDict entry is # sufficient (the barrier will be updated accordingly when we # enter the waitClientsReady state) if (self.state == 'WaitReadyHole'): self.__barrier.clear(avId) def enterPlayHole(self): self.notify.debug("GOLF COURSE: enterPlayHole") if self.currentHole and not self.currentHole.playStarted: self.currentHole.startPlay() pass def exitPlayHole(self): self.notify.debug("GOLF COURSE: exitPlayHole") pass def enterWaitLeaveHole(self): """We are finished with our current hole, figure out what to do next.""" self.notify.debugStateCall(self) self.notify.debug('calling requestDelete on hole %d' % self.currentHole.doId) self.currentHole.requestDelete() self.currentHole = None if self.numHolesPlayed >= self.numHoles: # we've played all the holes #self.safeDemand('WaitReward') pass else: self.startNextHole() pass def exitWaitLeaveHole(self): pass def enterWaitReward(self): """Done playing all the holes, display a reward movie on the client.""" # TODO calculate any trophy rewards, send those to the client self.updateHistoryForCourseComplete() self.awardTrophies() self.awardCups() self.awardHoleBest() self.awardCourseBest() self.recordHoleInOne() self.recordCourseUnderPar() trophiesList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newTrophies: oneTrophyList = self.newTrophies[avId] trophiesList.append(oneTrophyList) else: # probably a disconnected player, just us an empty list trophiesList.append([]) while len(trophiesList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: # toon.dc expects 4 lists trophiesList.append([]) holeBestList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newHoleBest: oneTrophyList = self.newHoleBest[avId] holeBestList.append(oneTrophyList) else: # probably a disconnected player, just us an empty list holeBestList.append([]) while len(holeBestList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: # toon.dc expects 4 lists holeBestList.append([]) courseBestList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newCourseBest: oneTrophyList = self.newCourseBest[avId] courseBestList.append(oneTrophyList) else: # probably a disconnected player, just us an empty list courseBestList.append([]) while len(courseBestList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: # toon.dc expects 4 lists courseBestList.append([]) cupList = [] for index in xrange(len(self.avIdList)): avId = self.avIdList[index] if avId in self.newCups: oneCupList = self.newCups[avId] cupList.append(oneCupList) self.cupListLen = self.cupListLen + 1 else: # probably a disconnected player, just us an empty list cupList.append([]) while len(cupList) < GolfGlobals.MAX_PLAYERS_PER_HOLE: # toon.dc expects 4 lists cupList.append([]) REWARD_TIMEOUT = (self.trophyListLen + self.holeBestListLen + self.courseBestListLen + self.cupListLen) * 5 + 19 # the extra 15 seconds are for: 8 seconds of rankings and 7 as a buffer # send total aim times too aimTimesList = [0] * 4 aimIndex = 0 stillPlaying = self.getStillPlayingAvIds() for avId in self.avIdList: if avId in stillPlaying: aimTime = 0 if avId in self.aimTimes: aimTime = self.aimTimes[avId] aimTimesList[aimIndex] = aimTime aimIndex += 1 self.sendUpdate('setReward', [trophiesList, self.rankings, \ holeBestList, courseBestList, cupList, self.winnerByTieBreak, aimTimesList[0], aimTimesList[1], aimTimesList[2], aimTimesList[3]] ) def allAvatarsRewarded(self=self): self.notify.debug("GOLF COURSE: all avatars rewarded") # Everybody is here, leave the golf course #self.safeDemand('WaitLeaveCourse') self.rewardDone() def handleRewardTimeout(avIds, self=self): self.notify.debug("GOLF COURSE: timed out waiting for clients %s " "to finish reward" % avIds) # self.setCourseAbort() #self.safeDemand('WaitLeaveCourse') self.rewardDone() stillPlaying = self.getStillPlayingAvIds() self.rewardBarrier = ToonBarrier('waitReward', self.uniqueName('waitReward'), stillPlaying, REWARD_TIMEOUT, allAvatarsRewarded, handleRewardTimeout) def exitWaitReward(self): """Handle exiting the WaitReward state.""" pass def enterWaitLeaveCourse(self): self.notify.debugStateCall(self) # TODO we should be calling self.courseFinished() self.setCourseAbort() pass def exitWaitLeaveCourse(self): pass def enterCleanup(self): self.notify.debug("GOLF COURSE: enterCleanup") #delete myself self.requestDelete() pass def exitCleanup(self): self.notify.debug("GOLF COURSE: exitCleanup") pass ####################################################################################### ### END FSM FUNCTIONS ####################################################################################### def isCurHoleDone(self): """Returns true if the current hole is finished.""" retval = False if self.areAllBallsInHole(): #if all the balls are in the hole we're done retval = True else: # if everyone is exited or Ball in we're done retval = True for state in self.avStateDict.values(): if not (state == BALLIN or state == EXITED): retval = False break return retval def areAllBallsInHole(self): """Return true if everyone's ball is in the hole.""" self.notify.debug('areAllBallsInHole, self.avStateDict=%s' % self.avStateDict) allBallsInHole = True for state in self.avStateDict.values(): if state != BALLIN: allBallsInHole = False return allBallsInHole def isPlayingLastHole(self): """Return true if we are currently playing the last hole in the course.""" retval = (self.numHoles - self.numHolesPlayed == 1) return retval def setBallIn(self, avId): self.notify.debug('setBallIn %d' % avId) if self.avStateDict[avId] == BALLIN: # we've already process this ball going in, just return self.notify.debug( 'setBallIn already in BALLIN state, just returning') return self.avStateDict[avId] = BALLIN self.updateHistoryForBallIn(avId) if self.isCurHoleDone(): if self.isPlayingLastHole(): if self.state != "WaitReward": self.safeDemand('WaitReward') else: self.notify.debug('allBalls are in holes, calling holeOver') self.holeOver() def updateHistoryForBallIn(self, avId): """Update our history on anything that is hole dependent.""" if self.currentHole == None: return holeId = self.currentHole.holeId holeInfo = GolfGlobals.HoleInfo[holeId] par = holeInfo['par'] holeIndex = self.numHolesPlayed if holeIndex >= self.numHoles: self.notify.warning('updateHistoryForBallIn invalid holeIndex %d' % holeIndex) holeIndex = self.numHoles - 1 elif holeIndex < 0: self.notify.warning('updateHistoryForBallIn invalid holeIndex %d' % holeIndex) holeIndex = 0 strokes = self.scores[avId][holeIndex] self.notify.debug('self.scores = %s' % self.scores) diff = strokes - par # check for hole in one if strokes == 1: self.incrementEndingHistory(avId, GolfGlobals.HoleInOneShots) # now do eagle, birdie, par if diff <= -2: self.incrementEndingHistory(avId, GolfGlobals.EagleOrBetterShots) if diff <= -1: self.incrementEndingHistory(avId, GolfGlobals.BirdieOrBetterShots) if diff <= 0: self.endingHistory[avId][GolfGlobals.ParOrBetterShots] += 1 # check our personal best if strokes < self.endingHoleBest[avId][holeId] or \ self.endingHoleBest[avId][holeId] == 0: self.endingHoleBest[avId][holeId] = strokes def incrementEndingHistory(self, avId, historyIndex): """Safely increment the golf ending history for an avatar.""" if self.endingHistory.has_key(avId) and \ GolfGlobals.TrophyRequirements.has_key(historyIndex): maximumAmount = GolfGlobals.TrophyRequirements[historyIndex][-1] if self.endingHistory[avId][historyIndex] < maximumAmount: self.endingHistory[avId][historyIndex] += 1 def getCourseId(self): """Return the courseId of this course.""" return self.courseId def abortCurrentHole(self): """Abort the current hole being played, and go to the next hole.""" holeId = self.currentHole.holeId holeDoId = self.currentHole.doId self.currentHole.finishHole() return holeId, holeDoId def calcHolesToUse(self): """Return the list of holes to use in our course. If this is changed, update client version too. """ retval = [] if simbase.air.config.GetBool('golf-course-randomized', 1): retval = self.calcHolesToUseRandomized(self.courseId) self.notify.debug("randomized courses!") for x in range(len(retval)): self.notify.debug("Hole is: %s" % retval[x]) else: validHoles = self.calcUniqueHoles(self.courseId) if self.preferredHoleId in validHoles: retval.append(self.preferredHoleId) while len(retval) < self.numHoles: for holeId in GolfGlobals.CourseInfo[self.courseId]['holeIds']: if type(holeId) == type(0): retval.append(holeId) elif type(holeId) == type(()): retval.append(holeId[0]) else: self.notify.warning('cant handle %s' % self.holeId) if len(retval) >= self.numHoles: break return retval def incrementScore(self, avId): self.notify.debug('incrementScore self.scores=%s avId=%s' % (self.scores, avId)) self.scores[avId][self.numHolesPlayed] += 1 self.notify.debug('after increment self.score=%s' % self.scores) self.sendScores() def sendScores(self): self.notify.debug('sendScores self.scores = %s' % self.scores) scorelist = [] for avId in self.avIdList: for score in self.scores[avId]: scorelist.append(score) self.sendUpdate('setScores', [scorelist]) self.notify.debug('sendScores end self.scores = %s' % self.scores) def getCurHoleIndex(self): return self.curHoleIndex def b_setCurHoleIndex(self, holeIndex): self.setCurHoleIndex(holeIndex) self.d_setCurHoleIndex(holeIndex) def d_setCurHoleIndex(self, holeIndex): self.sendUpdate('setCurHoleIndex', [holeIndex]) def setCurHoleIndex(self, holeIndex): self.curHoleIndex = holeIndex def setDoneReward(self): """ Clear the avatar from the reward barrier since he is done with the reward movie. """ avId = self.air.getAvatarIdFromSender() self.notify.debug('got rewardDone from %d' % avId) if hasattr(self, 'rewardBarrier'): self.rewardBarrier.clear(avId) self.sendUpdate("setCourseAbort", [avId]) def rewardDone(self): """All clients have confirmed done with the reward movie. do proper cleanup.""" # we didn't delete the hole, so we don't see grayspace for the reward movie # now go ahead and delete self.notify.debug('rewardDone') self.holeOver() self.safeDemand('WaitLeaveCourse') def updateHistoryForCourseComplete(self): """Update the histories for finishing a course.""" self.calcRankings() stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: # he definitely completed a course self.incrementEndingHistory(avId, GolfGlobals.CoursesCompleted) # check if he's under course par coursePar = self.calcCoursePar() totalScore = self.getTotalScore(avId) if totalScore < coursePar: self.incrementEndingHistory(avId, GolfGlobals.CoursesUnderPar) # check if it's a multiplayer win if len(stillPlaying) > 1: self.incrementEndingHistory( avId, GolfGlobals.MultiPlayerCoursesCompleted) if self.rankingsById[avId] == 1: # check for course zero, one or two wins if self.courseId == 0: self.incrementEndingHistory(avId, GolfGlobals.CourseZeroWins) elif self.courseId == 1: self.incrementEndingHistory(avId, GolfGlobals.CourseOneWins) elif self.courseId == 2: self.incrementEndingHistory(avId, GolfGlobals.CourseTwoWins) else: self.notify.warning( 'unhandled case, self.courseId=%s' % self.courseId) # check for our personal best if totalScore < self.endingCourseBest[avId][self.courseId] or \ self.endingCourseBest[avId][self.courseId] == 0: self.endingCourseBest[avId][self.courseId] = totalScore def calcRankings(self): """Calculate the rankings of the avatars still playing. -1 represents not ranked, 1 is 1st, 2 is 2nd, two 1st places are possible. Sets self.rankings and self.rankingsById 8/29/2007 too much collusion. randomly choose winner if tied for 1st 9/14/2007 use aim time as tie breaker """ stillPlaying = self.getStillPlayingAvIds() self.rankings = [] totalScores = [] # get the total scores for each player, disconnected players get a high score for avId in self.avIdList: aimTime = 0 if avId in self.aimTimes: aimTime = self.aimTimes[avId] if avId in stillPlaying: totalScores.append((avId, self.getTotalScore(avId), aimTime)) else: totalScores.append((avId, 255, aimTime)) def scoreCompareNoTime(tupleA, tupleB): if tupleA[1] > tupleB[1]: return 1 elif tupleA[1] == tupleB[1]: return 0 else: return -1 def scoreCompareWithTime(tupleA, tupleB): if tupleA[1] > tupleB[1]: return 1 elif tupleA[1] == tupleB[1]: if tupleA[2] > tupleB[2]: return 1 elif tupleA[2] == tupleB[2]: return 0 else: return -1 else: return -1 # sort it by score, make sure the avId gets sorted too if GolfGlobals.TIME_TIE_BREAKER: totalScores.sort(scoreCompareWithTime) else: totalScores.sort(scoreCompareNoTime) # calculate the rank, handle 2 1st place winners curRank = 0 oldScore = 0 oldTime = 0 self.rankingsById = {} # calculate rankings, including time if the option is turned on for scoreTuple in totalScores: time = scoreTuple[2] score = scoreTuple[1] avId = scoreTuple[0] if score > oldScore or \ (GolfGlobals.TIME_TIE_BREAKER and score == oldScore and time > oldTime): curRank += 1 oldScore = score oldTime = time self.rankingsById[avId] = curRank # calculate which toons had the exact same number of strokes for 1st tiedForFirst = [] tempRank = 0 oldScore = 0 oldTime = 0 for scoreTuple in totalScores: time = scoreTuple[2] score = scoreTuple[1] avId = scoreTuple[0] if score > oldScore: tempRank += 1 oldScore = score oldTime = time if tempRank == 1: tiedForFirst.append(avId) for avId in self.avIdList: if avId in stillPlaying: self.rankings.append(self.rankingsById[avId]) else: self.rankings.append(-1) # randomly choose winner if tied for 1st if (len(tiedForFirst) >= 2) and not GolfGlobals.TIME_TIE_BREAKER: winnerAvId = random.choice(tiedForFirst) winnerIndex = self.avIdList.index(winnerAvId) self.winnerByTieBreak = winnerAvId for index in xrange(len(self.rankings)): if (self.rankings[index] > 0) and (index != winnerIndex): self.rankings[index] += 1 for avId in self.rankingsById: if (self.rankingsById[avId] > 0) and (avId != winnerAvId): self.rankingsById[avId] += 1 elif len(tiedForFirst) >= 2: winnerAvId = totalScores[0][0] self.winnerByTieBreak = winnerAvId def awardTrophies(self): """Award the trophies. Also update self.newTrophies.""" stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHistory = self.startingHistory[avId] endingHistory = self.endingHistory[avId] oldTrophies = GolfGlobals.calcTrophyListFromHistory(oldHistory) endingTrophies = GolfGlobals.calcTrophyListFromHistory( endingHistory) av.b_setGolfHistory(endingHistory) # now figure out which trophies he just got newTrophies = [] for index in xrange(len(oldTrophies)): if not oldTrophies[index] and endingTrophies[index]: self.notify.debug('New Trophy %d' % index) self.air.writeServerEvent("golf_trophy", avId, "%s" % (index)) newTrophies.append(True) self.trophyListLen = self.trophyListLen + 1 else: newTrophies.append(False) self.newTrophies[avId] = newTrophies def awardCups(self): """Award the cups and laff boost. Also update self.newCups.""" stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHistory = self.startingHistory[avId] endingHistory = self.endingHistory[avId] oldCups = GolfGlobals.calcCupListFromHistory(oldHistory) endingCups = GolfGlobals.calcCupListFromHistory(endingHistory) # now figure out which trophies he just got newCups = [] for index in xrange(len(oldCups)): if not oldCups[index] and endingCups[index]: self.notify.debug('New Trophy %d' % index) newCups.append(True) self.air.writeServerEvent("golf_cup", avId, "%s" % (index)) newMaxHp = av.getMaxHp() + 1 av.b_setMaxHp(newMaxHp) # Also, give them a full heal av.toonUp(newMaxHp) else: newCups.append(False) self.newCups[avId] = newCups def awardHoleBest(self): """Award the personal best in goles. Also update self.newHoleBest.""" stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldHoleBest = self.startingHoleBest[avId] endingHoleBest = self.endingHoleBest[avId] av.b_setGolfHoleBest(endingHoleBest) # now figure out which trophies he just got newHoleBest = [] longestHoleBestList = 0 for index in xrange(len(oldHoleBest)): if endingHoleBest[index] < oldHoleBest[index]: self.notify.debug('New HoleBest %d' % index) newHoleBest.append(True) longestHoleBestList = longestHoleBestList + 1 else: newHoleBest.append(False) if (longestHoleBestList > self.holeBestListLen): self.holeBestListLen = longestHoleBestList self.newHoleBest[avId] = newHoleBest def awardCourseBest(self): """Award the personal best in goles. Also update self.newCourseBest.""" stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: av = simbase.air.doId2do.get(avId) if av: oldCourseBest = self.startingCourseBest[avId] endingCourseBest = self.endingCourseBest[avId] av.b_setGolfCourseBest(endingCourseBest) # now figure out which trophies he just got newCourseBest = [] longestCourseBestList = 0 for index in xrange(len(oldCourseBest)): if endingCourseBest[index] < oldCourseBest[index]: self.notify.debug('New CourseBest %d' % index) newCourseBest.append(True) longestCourseBestList = longestCourseBestList + 1 else: newCourseBest.append(False) if (longestCourseBestList > self.courseBestListLen): self.courseBestListLen = longestCourseBestList self.newCourseBest[avId] = newCourseBest def haveAllGolfersExited(self): """Returns true if all the golfers are gone""" retval = True for avId in self.avStateDict: if not self.avStateDict[avId] == EXITED: retval = False break return retval def getCurHoleDoId(self): """Return the DoId of the current hole.""" retval = 0 if self.currentHole: retval = self.currentHole.doId return retval def d_setCurHoleDoId(self, curHoleDoId): """Send the current hole doId to the client.""" self.sendUpdate('setCurHoleDoId', [curHoleDoId]) def calcCoursePar(self): """Return the par for the course.""" retval = 0 for holeId in self.holeIds: holeInfo = GolfGlobals.HoleInfo[holeId] retval += holeInfo['par'] return retval def getTotalScore(self, avId): """Return the total golf score for an avatar.""" retval = 0 if self.scores.has_key(avId): for holeScore in self.scores[avId]: retval += holeScore return retval def getCurHoleScore(self, avId): """Return the current hole score for an avatar.""" retval = 0 if avId in self.scores and self.numHolesPlayed < len( self.scores[avId]): retval = self.scores[avId][self.numHolesPlayed] return retval def toggleDrivePermission(self, avId): """Toggle the drive cheat for this toon. Returns true id driving was turned on.""" if avId in self.drivingToons: self.drivingToons.remove(avId) self.sendUpdate('changeDrivePermission', [avId, 0]) retval = False pass else: self.drivingToons.append(avId) self.sendUpdate('changeDrivePermission', [avId, 1]) retval = True return retval def safeDemand(self, newState): """Demand the new state only if we're not in Cleanup and its safe. Returns true if the demand was done.""" doingDemand = False if self.state == 'Cleanup': # deliberately do nothing pass else: if self.state in self.defaultTransitions: if newState in self.defaultTransitions[self.state]: self.demand(newState) doingDemand = True elif self.state == None: # we are in the middle of the transition, demand and pray it works self.demand(newState) doingDemand = True if not doingDemand: self.notify.warning('doId=%d ignoring demand from %s to %s' % (self.doId, self.state, newState)) return doingDemand def setAvatarExited(self): """Handle a client telling us a toon fell asleep.""" avId = self.air.getAvatarIdFromSender() self.handleExitedAvatar(avId) def createChoicesList(self, courseId, possibleHoles): """Return a weighted list of hole choices.""" retval = [] # assuming all input is correct holeIds = GolfGlobals.CourseInfo[courseId]['holeIds'] for holeOrTuple in holeIds: if type(holeOrTuple) == type(()): # if its a tuple, the first Id is hole, 2nd is its weight holeId = holeOrTuple[0] weight = holeOrTuple[1] elif type(holeOrTuple) == type(0): holeId = holeOrTuple weight = 1 else: self.notify.warning('cant handle %s' % holeOrTuple) continue if holeId in possibleHoles: retval += [holeId] * weight return retval def calcUniqueHoles(self, courseId): """Return a set of the unique holes used in this course.""" uniqueHoles = set() for holeOrTuple in GolfGlobals.CourseInfo[courseId]['holeIds']: if type(holeOrTuple) == type(()): uniqueHoles.add(holeOrTuple[0]) elif type(holeOrTuple) == type(0): uniqueHoles.add(holeOrTuple) else: self.notify.warning('cant handle %s' % holeOrTuple) return uniqueHoles def calcHolesToUseRandomized(self, courseId): """Randomizes which golf holes to use for each course.""" retval = [] numHoles = GolfGlobals.CourseInfo[courseId]['numHoles'] uniqueHoles = self.calcUniqueHoles(courseId) curHolesChosen = set() while len(retval) < numHoles: if uniqueHoles == curHolesChosen: curHolesChosen = set() possibleHoles = uniqueHoles - curHolesChosen choicesList = self.createChoicesList(courseId, possibleHoles) if not (self.preferredHoleId == None) and \ self.preferredHoleId in choicesList and \ self.preferredHoleId not in curHolesChosen: holeChosen = self.preferredHoleId else: holeChosen = random.choice(choicesList) retval.append(holeChosen) curHolesChosen.add(holeChosen) return retval def recordHoleInOne(self): """Write to the server log hole in ones the players got.""" stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: scoreList = self.scores[avId] for holeIndex in xrange(len(scoreList)): strokes = scoreList[holeIndex] if strokes == 1: holeId = self.holeIds[holeIndex] self.air.writeServerEvent( "golf_ace", avId, "%d|%d|%s" % (self.courseId, holeId, stillPlaying)) def recordCourseUnderPar(self): """Write to the server log hole if they are course under par.""" coursePar = self.calcCoursePar() stillPlaying = self.getStillPlayingAvIds() for avId in stillPlaying: totalScore = self.getTotalScore(avId) netScore = totalScore - coursePar if netScore < 0: self.air.writeServerEvent( "golf_underPar", avId, "%d|%d|%s" % (self.courseId, netScore, stillPlaying)) def addAimTime(self, avId, aimTime): """Add to the aim time of a toom.""" if avId in self.aimTimes: self.aimTimes[avId] += aimTime
class DistributedPatternGameAI(DistributedMinigameAI): def __init__(self, air, minigameId): try: self.DistributedPatternGameAI_initialized except: self.DistributedPatternGameAI_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) self.gameFSM = ClassicFSM.ClassicFSM('DistributedPatternGameAI', [ State.State('off', self.enterInactive, self.exitInactive, ['waitClientsReady', 'cleanup']), State.State('waitClientsReady', self.enterWaitClientsReady, self.exitWaitClientsReady, ['generatePattern', 'cleanup']), State.State('generatePattern', self.enterGeneratePattern, self.exitGeneratePattern, ['waitForResults', 'cleanup']), State.State('waitForResults', self.enterWaitForResults, self.exitWaitForResults, ['waitClientsReady', 'cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, []), ], # Initial State 'off', # Final State 'cleanup', ) # Add our game ClassicFSM to the framework ClassicFSM self.addChildGameFSM(self.gameFSM) # Generate is never called on the AI so we do not define one # Disable is never called on the AI so we do not define one def delete(self): self.notify.debug("delete") del self.gameFSM DistributedMinigameAI.delete(self) # override some network message handlers def setGameReady(self): self.notify.debug("setGameReady") DistributedMinigameAI.setGameReady(self) self.__initGameVars() def setGameStart(self, timestamp): self.notify.debug("setGameStart") # base class will cause gameFSM to enter initial state DistributedMinigameAI.setGameStart(self, timestamp) self.gameFSM.request('waitClientsReady') def setGameAbort(self): self.notify.debug("setGameAbort") # this is called when the minigame is unexpectedly # ended (a player got disconnected, etc.) if self.gameFSM.getCurrentState(): self.gameFSM.request('cleanup') DistributedMinigameAI.setGameAbort(self) def gameOver(self): self.notify.debug("gameOver") # call this when the game is done # clean things up in this class self.gameFSM.request('cleanup') # tell the base class to wrap things up DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug("enterInactive") def exitInactive(self): pass def __initGameVars(self): self.pattern = [] self.round = 0 self.perfectResults = {} for avId in self.avIdList: self.perfectResults[avId] = 1 self.readyClients = [] self.timeoutTaskName = self.uniqueName('PatternGameResultsTimeout') def enterWaitClientsReady(self): self.notify.debug("enterWaitClientsReady") self.nextRoundBarrier = ToonBarrier( 'nextRoundReady', self.uniqueName('nextRoundReady'), self.avIdList, PatternGameGlobals.ClientsReadyTimeout, self.__allPlayersReady, self.__clientsReadyTimeout) # some players may have already checked in for avId in self.readyClients: self.nextRoundBarrier.clear(avId) def reportPlayerReady(self): if self.gameFSM.getCurrentState().getName() != 'waitClientsReady': return avId = self.air.getAvatarIdFromSender() assert not avId in self.readyClients if avId not in self.avIdList: self.notify.warning( 'Got reportPlayerReady from an avId: %s not in our list: %s' % (avId, self.avIdList)) else: self.readyClients.append(avId) self.nextRoundBarrier.clear(avId) def __allPlayersReady(self): self.readyClients = [] self.gameFSM.request('generatePattern') def __clientsReadyTimeout(self, avIds): # hmm, someone hasn't responded. self.notify.debug( "__clientsReadyTimeout: clients %s have not responded" % avIds) # abort the minigame self.setGameAbort() def exitWaitClientsReady(self): self.nextRoundBarrier.cleanup() del self.nextRoundBarrier def enterGeneratePattern(self): self.notify.debug("enterGeneratePattern") self.round += 1 # add to the pattern if necessary targetLen = PatternGameGlobals.INITIAL_ROUND_LENGTH + \ (PatternGameGlobals.ROUND_LENGTH_INCREMENT * (self.round-1)) count = targetLen - len(self.pattern) for i in range(0,count): # add a random button index to the pattern self.pattern.append(random.randint(0,3)) # don't send the pattern until we're ready for results self.gameFSM.request("waitForResults") self.sendUpdate("setPattern", [self.pattern]) def exitGeneratePattern(self): pass def enterWaitForResults(self): self.notify.debug("enterWaitForResults") self.results = [None] * self.numPlayers self.fastestTime = PatternGameGlobals.InputTime*2 self.fastestAvId = 0 # allow some additional time to show the pattern to the players self.resultsBarrier = ToonBarrier( 'results', self.uniqueName('results'), self.avIdList, PatternGameGlobals.InputTimeout + (1. * self.round), self.__gotAllPatterns, self.__resultsTimeout) def reportButtonPress(self, index, wrong): if self.gameFSM.getCurrentState().getName() != 'waitForResults': return # validate avId, index and wrong avId = self.air.getAvatarIdFromSender() if avId not in self.avIdList: self.air.writeServerEvent('suspicious', avId, 'PatternGameAI.reportButtonPress avId not on list') return if index < 0 or index > 3: self.air.writeServerEvent('warning', index, 'PatternGameAI.reportButtonPress got bad index') return if wrong not in [0,1]: self.air.writeServerEvent('warning', wrong, "PatternGameAI.reportButtonPress got bad 'wrong'") return self.sendUpdate("remoteButtonPressed", [avId, index, wrong]) def __resultsTimeout(self, avIds): self.notify.debug("__resultsTimeout: %s" % avIds) # time's up; simulate a response from whoever didn't respond for avId in avIds: index = self.avIdList.index(avId) assert self.results[index] is None self.__acceptPlayerPattern( avId, [], PatternGameGlobals.InputTime*2) # and proceed self.__gotAllPatterns() def reportPlayerPattern(self, pattern, totalTime): if self.gameFSM.getCurrentState().getName() != 'waitForResults': return avId = self.air.getAvatarIdFromSender() self.__acceptPlayerPattern(avId, pattern, totalTime) # update the barrier self.resultsBarrier.clear(avId) def __acceptPlayerPattern(self, avId, pattern, totalTime): index = self.avIdList.index(avId) if (self.results[index] != None): # Ignore repeated submissions from the same player. return self.results[index] = pattern # If the time they took to complete the pattern is less then the # current fastest and they got it right, update. fastestAvId # defaults to 0 which becomes important in DistributedPatternGame.py if totalTime < self.fastestTime and pattern == self.pattern: self.fastestTime = totalTime self.fastestAvId = avId if self.numPlayers == 1: self.fastestAvId = 1 else: # If they were fastest and they're not alone, # give them a 2 jelly bonus! self.scoreDict[self.fastestAvId] += 2 def __gotAllPatterns(self): # build a list of four patterns, even if there aren't four players patterns = [[]] * 4 for i in range(0, len(self.results)): patterns[i] = self.results[i] # careful: if a player hasn't responded, their entry in the results # table will be None if patterns[i] is None: patterns[i] = [] # send the clients all of the patterns and the fastest av self.sendUpdate("setPlayerPatterns", patterns + [self.fastestAvId]) for i in range(0,self.numPlayers): avId = self.avIdList[i] if not (self.results[i] == self.pattern): # update the 'perfect' table self.perfectResults[avId] = 0 else: # give that toon some jellybeans! self.scoreDict[avId] += self.round if self.round < PatternGameGlobals.NUM_ROUNDS: self.gameFSM.request('waitClientsReady') else: # increase score of players that had a perfect game # (not including bonuses) for avId in self.avIdList: if self.perfectResults[avId]: self.scoreDict[avId] += 4 self.logPerfectGame(avId) # the game is over self.gameOver() # explicitly transition out of the waitForResults state # to guard against late results msgs self.gameFSM.request('cleanup') def exitWaitForResults(self): self.resultsBarrier.cleanup() del self.resultsBarrier def enterCleanup(self): self.notify.debug("enterCleanup") def exitCleanup(self): pass
class DistributedCatchGameAI(DistributedMinigameAI): def __init__(self, air, minigameId): try: self.DistributedCatchGameAI_initialized except: self.DistributedCatchGameAI_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) self.gameFSM = ClassicFSM.ClassicFSM( 'DistributedCatchGameAI', [ State.State('inactive', self.enterInactive, self.exitInactive, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, ['inactive']), ], # Initial State 'inactive', # Final State 'inactive', ) # Add our game ClassicFSM to the framework ClassicFSM self.addChildGameFSM(self.gameFSM) def generate(self): self.notify.debug("generate") DistributedMinigameAI.generate(self) # Disable is never called on the AI so we do not define one def delete(self): self.notify.debug("delete") del self.gameFSM DistributedMinigameAI.delete(self) # override some network message handlers def setGameReady(self): self.notify.debug("setGameReady") DistributedMinigameAI.setGameReady(self) # all of the players have checked in # they will now be shown the rules def setGameStart(self, timestamp): self.notify.debug("setGameStart") # base class will cause gameFSM to enter initial state DistributedMinigameAI.setGameStart(self, timestamp) # all of the players are ready to start playing the game # transition to the appropriate ClassicFSM state self.gameFSM.request('play') def setGameAbort(self): self.notify.debug("setGameAbort") # this is called when the minigame is unexpectedly # ended (a player got disconnected, etc.) if self.gameFSM.getCurrentState(): self.gameFSM.request('cleanup') DistributedMinigameAI.setGameAbort(self) def gameOver(self): self.notify.debug("gameOver") # call this when the game is done # dole out the jellybeans self.notify.debug("fruits: %s, fruits caught: %s" % (self.numFruits, self.fruitsCaught)) perfect = (self.fruitsCaught >= self.numFruits) for avId in self.avIdList: self.scoreDict[avId] = max(1, int(self.scoreDict[avId] / 2)) if perfect: self.notify.debug("PERFECT GAME!") self.scoreDict[avId] += round(self.numFruits / 4.) self.logAllPerfect() # clean things up in this class self.gameFSM.request('cleanup') # tell the base class to wrap things up DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug("enterInactive") def exitInactive(self): pass def enterPlay(self): self.notify.debug("enterPlay") self.caughtList = [0] * 100 # get the number of fruits that will be dropped table = CatchGameGlobals.NumFruits[self.numPlayers - 1] self.numFruits = table[self.getSafezoneId()] self.notify.debug('numFruits: %s' % self.numFruits) # and keep track of how many are caught self.fruitsCaught = 0 # set up a barrier to wait for the 'game done' msgs def allToonsDone(self=self): self.notify.debug('allToonsDone') self.sendUpdate('setEveryoneDone') if not CatchGameGlobals.EndlessGame: self.gameOver() def handleTimeout(avIds, self=self): self.notify.debug( 'handleTimeout: avatars %s did not report "done"' % avIds) self.setGameAbort() self.doneBarrier = ToonBarrier( 'waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, CatchGameGlobals.GameDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout) def exitPlay(self): del self.caughtList self.doneBarrier.cleanup() del self.doneBarrier def claimCatch(self, objNum, DropObjTypeId): if self.gameFSM.getCurrentState().getName() != 'play': return # range check DropObjTypeId if DropObjTypeId < 0 or DropObjTypeId >= len( CatchGameGlobals.DOTypeId2Name): self.air.writeServerEvent( 'warning', DropObjTypeId, 'CatchGameAI.claimCatch DropObjTypeId out of range') return # sanity check; don't allow hackers to allocate unlimited memory if objNum < 0 or objNum > 5000 or objNum >= 2 * len(self.caughtList): # self.notify.debug('object num %s is too high. ignoring' % objNum) self.air.writeServerEvent( 'warning', objNum, 'CatchGameAI.claimCatch objNum is too high or negative') return # double the size of the caught table as needed if objNum >= len(self.caughtList): self.caughtList += [0] * len(self.caughtList) # if nobody's caught this object yet, announce that it's been caught if not self.caughtList[objNum]: self.caughtList[objNum] = 1 avId = self.air.getAvatarIdFromSender() self.sendUpdate('setObjectCaught', [avId, objNum]) # if it's a good obj, update the score objName = CatchGameGlobals.DOTypeId2Name[DropObjTypeId] self.notify.debug('avatar %s caught object %s: %s' % (avId, objNum, objName)) if CatchGameGlobals.Name2DropObjectType[objName].good: self.scoreDict[avId] += 1 self.fruitsCaught += 1 def reportDone(self): if self.gameFSM.getCurrentState().getName() != 'play': return avId = self.air.getAvatarIdFromSender() # all of the objects on this avatar's client have landed # or been caught self.notify.debug('reportDone: avatar %s is done' % avId) self.doneBarrier.clear(avId) def enterCleanup(self): self.notify.debug("enterCleanup") self.gameFSM.request('inactive') def exitCleanup(self): pass
class DistributedGolfHoleAI( DistributedPhysicsWorldAI.DistributedPhysicsWorldAI, FSM, GolfHoleBase.GolfHoleBase): defaultTransitions = { 'Off': ['Cleanup', 'WaitTee'], 'WaitTee': ['WaitSwing', 'Cleanup', 'WaitTee', 'WaitPlayback'], 'WaitSwing': ['WaitPlayback', 'Cleanup', 'WaitSwing', 'WaitTee'], 'WaitPlayback': ['WaitSwing', 'Cleanup', 'WaitTee', 'WaitPlayback'], 'Cleanup': ['Off'] } id = 0 notify = directNotify.newCategory('DistributedGolfHoleAI') def __init__(self, zoneId, golfCourse, holeId): FSM.__init__(self, 'Golf_%s_FSM' % self.id) DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.__init__( self, simbase.air) GolfHoleBase.GolfHoleBase.__init__(self) self.zoneId = zoneId self.golfCourse = golfCourse self.holeId = holeId self.avIdList = golfCourse.avIdList[:] self.watched = [0, 0, 0, 0] self.barrierPlayback = None self.trustedPlayerId = None self.activeGolferIndex = None self.activeGolferId = None self.holeInfo = GolfGlobals.HoleInfo[self.holeId] self.teeChosen = {} for avId in self.avIdList: self.teeChosen[avId] = -1 self.ballPos = {} for avId in self.avIdList: self.ballPos[avId] = Vec3(0, 0, 0) self.playStarted = False return def curGolfBall(self): return self.ball def generate(self): DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.generate(self) self.ball = self.createBall() self.createRays() if len(self.teePositions) > 1: startPos = self.teePositions[1] else: startPos = self.teePositions[0] startPos += Vec3(0, 0, GolfGlobals.GOLF_BALL_RADIUS) self.ball.setPosition(startPos) def delete(self): self.notify.debug('__delete__') DistributedPhysicsWorldAI.DistributedPhysicsWorldAI.delete(self) self.notify.debug('calling self.terrainModel.removeNode') self.terrainModel.removeNode() self.notify.debug('self.barrierPlayback is %s' % self.barrierPlayback) if self.barrierPlayback: self.notify.debug('calling self.barrierPlayback.cleanup') self.barrierPlayback.cleanup() self.notify.debug('calling self.barrierPlayback = None') self.barrierPlayback = None self.activeGolferId = None return def setZoneId(self, zoneId): self.zoneId = zoneId def setAvatarReadyHole(self): self.notify.debugStateCall(self) avId = self.air.getAvatarIdFromSender() self.golfCourse.avatarReadyHole(avId) def startPlay(self): self.notify.debug('startPlay') self.playStarted = True self.numGolfers = len(self.golfCourse.getGolferIds()) self.selectNextGolfer() def selectNextGolfer(self): self.notify.debug( 'selectNextGolfer, old golferIndex=%s old golferId=%s' % (self.activeGolferIndex, self.activeGolferId)) if self.golfCourse.isCurHoleDone(): return if self.activeGolferIndex == None: self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[ self.activeGolferIndex] else: self.activeGolferIndex += 1 if self.activeGolferIndex >= len(self.golfCourse.getGolferIds()): self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[ self.activeGolferIndex] safety = 0 while safety < 50 and not self.golfCourse.checkGolferPlaying( self.golfCourse.getGolferIds()[self.activeGolferIndex]): self.activeGolferIndex += 1 self.notify.debug('Index %s' % self.activeGolferIndex) if self.activeGolferIndex >= len(self.golfCourse.getGolferIds()): self.activeGolferIndex = 0 self.activeGolferId = self.golfCourse.getGolferIds()[ self.activeGolferIndex] safety += 1 if safety != 50: golferId = self.golfCourse.getGolferIds()[self.activeGolferIndex] if self.teeChosen[golferId] == -1: self.sendUpdate('golferChooseTee', [golferId]) self.request('WaitTee') else: self.sendUpdate('golfersTurn', [golferId]) self.request('WaitSwing') else: self.notify.debug('safety') self.notify.debug( 'selectNextGolfer, new golferIndex=%s new golferId=%s' % (self.activeGolferIndex, self.activeGolferId)) return def clearWatched(self): self.watched = [1, 1, 1, 1] for index in range(len(self.golfCourse.getGolferIds())): self.watched[index] = 0 def setWatched(self, avId): for index in range(len(self.golfCourse.getGolferIds())): if self.golfCourse.getGolferIds()[index] == avId: self.watched[index] = 1 def checkWatched(self): if 0 not in self.watched: return True else: return False def turnDone(self): self.notify.debug('Turn Done') avId = self.air.getAvatarIdFromSender() if self.barrierPlayback: self.barrierPlayback.clear(avId) def ballInHole(self, golferId=None): self.notify.debug('ballInHole') if golferId: avId = golferId else: avId = self.air.getAvatarIdFromSender() self.golfCourse.setBallIn(avId) if self.golfCourse.isCurHoleDone(): self.notify.debug('ballInHole doing nothing') else: self.notify.debug('ballInHole calling self.selectNextGolfer') self.selectNextGolfer() def getHoleId(self): return self.holeId def finishHole(self): self.notify.debug('finishHole') self.golfCourse.holeOver() def getGolferIds(self): return self.avIdList def loadLevel(self): GolfHoleBase.GolfHoleBase.loadLevel(self) optionalObjects = self.terrainModel.findAllMatches('**/optional*') requiredObjects = self.terrainModel.findAllMatches('**/required*') self.parseLocators(optionalObjects, 1) self.parseLocators(requiredObjects, 0) self.teeNodePath = self.terrainModel.find('**/tee0') if self.teeNodePath.isEmpty(): teePos = Vec3(0, 0, 10) else: teePos = self.teeNodePath.getPos() teePos.setZ(teePos.getZ() + GolfGlobals.GOLF_BALL_RADIUS) self.notify.debug('teeNodePath heading = %s' % self.teeNodePath.getH()) self.teePositions = [teePos] teeIndex = 1 teeNode = self.terrainModel.find('**/tee%d' % teeIndex) while not teeNode.isEmpty(): teePos = teeNode.getPos() teePos.setZ(teePos.getZ() + GolfGlobals.GOLF_BALL_RADIUS) self.teePositions.append(teePos) self.notify.debug('teeNodeP heading = %s' % teeNode.getH()) teeIndex += 1 teeNode = self.terrainModel.find('**/tee%d' % teeIndex) def createLocatorDict(self): self.locDict = {} locatorNum = 1 curNodePath = self.hardSurfaceNodePath.find('**/locator%d' % locatorNum) while not curNodePath.isEmpty(): self.locDict[locatorNum] = curNodePath locatorNum += 1 curNodePath = self.hardSurfaceNodePath.find('**/locator%d' % locatorNum) def loadBlockers(self): loadAll = simbase.config.GetBool('golf-all-blockers', 0) self.createLocatorDict() self.blockerNums = self.holeInfo['blockers'] for locatorNum in self.locDict: if locatorNum in self.blockerNums or loadAll: locator = self.locDict[locatorNum] locatorParent = locator.getParent() locator.getChildren().wrtReparentTo(locatorParent) else: self.locDict[locatorNum].removeNode() self.hardSurfaceNodePath.flattenStrong() def createBall(self): golfBallGeom = self.createSphere(self.world, self.space, GolfGlobals.GOLF_BALL_DENSITY, GolfGlobals.GOLF_BALL_RADIUS, 1)[1] return golfBallGeom def preStep(self): GolfHoleBase.GolfHoleBase.preStep(self) def postStep(self): GolfHoleBase.GolfHoleBase.postStep(self) def postSwing(self, cycleTime, power, x, y, z, dirX, dirY): avId = self.air.getAvatarIdFromSender() self.storeAction = [avId, cycleTime, power, x, y, z, dirX, dirY] if self.commonHoldData: self.doAction() def postSwingState(self, cycleTime, power, x, y, z, dirX, dirY, curAimTime, commonObjectData): self.notify.debug('postSwingState') if not self.golfCourse.getStillPlayingAvIds(): return avId = self.air.getAvatarIdFromSender() self.storeAction = [avId, cycleTime, power, x, y, z, dirX, dirY] self.commonHoldData = commonObjectData self.trustedPlayerId = self.choosePlayerToSimulate() self.sendUpdateToAvatarId( self.trustedPlayerId, 'assignRecordSwing', [avId, cycleTime, power, x, y, z, dirX, dirY, commonObjectData]) self.golfCourse.addAimTime(avId, curAimTime) def choosePlayerToSimulate(self): stillPlaying = self.golfCourse.getStillPlayingAvIds() playerId = 0 if simbase.air.config.GetBool('golf-trust-driver-first', 0): if stillPlaying: playerId = stillPlaying[0] else: playerId = random.choice(stillPlaying) return playerId def ballMovie2AI(self, cycleTime, avId, movie, spinMovie, ballInFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame, commonObjectData): sentFromId = self.air.getAvatarIdFromSender() if sentFromId == self.trustedPlayerId: lastFrameNum = len(movie) - 2 if lastFrameNum < 0: lastFrameNum = 0 lastFrame = movie[lastFrameNum] lastPos = Vec3(lastFrame[1], lastFrame[2], lastFrame[3]) self.ballPos[avId] = lastPos self.golfCourse.incrementScore(avId) for id in self.golfCourse.getStillPlayingAvIds(): if not id == sentFromId: self.sendUpdateToAvatarId(id, 'ballMovie2Client', [ cycleTime, avId, movie, spinMovie, ballInFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame, commonObjectData ]) if self.state == 'WaitPlayback' or self.state == 'WaitTee': self.notify.warning( 'ballMovie2AI requesting from %s to WaitPlayback' % self.state) self.request('WaitPlayback') else: if self.trustedPlayerId == None: return else: self.doAction() self.trustedPlayerId = None return def performReadyAction(self): avId = self.storeAction[0] if self.state == 'WaitPlayback': self.notify.debugStateCall(self) self.notify.debug( 'ignoring the postSwing for avId=%d since we are in WaitPlayback' % avId) return if avId == self.activeGolferId: self.golfCourse.incrementScore(self.activeGolferId) else: self.notify.warning( 'activGolferId %d not equal to sender avId %d' % (self.activeGolferId, avId)) if avId not in self.golfCourse.drivingToons: position = self.ballPos[avId] else: position = Vec3(self.storeAction[3], self.storeAction[4], self.storeAction[5]) self.useCommonObjectData(self.commonHoldData) newPos = self.trackRecordBodyFlight(self.ball, self.storeAction[1], self.storeAction[2], position, self.storeAction[6], self.storeAction[7]) if self.state == 'WaitPlayback' or self.state == 'WaitTee': self.notify.warning( 'performReadyAction requesting from %s to WaitPlayback' % self.state) self.request('WaitPlayback') self.sendUpdate('ballMovie2Client', [ self.storeAction[1], avId, self.recording, self.aVRecording, self.ballInHoleFrame, self.ballTouchedHoleFrame, self.ballFirstTouchedHoleFrame, self.commonHoldData ]) self.ballPos[avId] = newPos self.trustedPlayerId = None return def postResult(self, cycleTime, avId, recording, aVRecording, ballInHoleFrame, ballTouchedHoleFrame, ballFirstTouchedHoleFrame): pass def enterWaitSwing(self): pass def exitWaitSwing(self): pass def enterWaitTee(self): pass def exitWaitTee(self): pass def enterWaitPlayback(self): self.notify.debug('enterWaitPlayback') stillPlayingList = self.golfCourse.getStillPlayingAvIds() self.barrierPlayback = ToonBarrier( 'waitClientsPlayback', self.uniqueName('waitClientsPlayback'), stillPlayingList, 120, self.handleWaitPlaybackDone, self.handlePlaybackTimeout) def hasCurGolferReachedMaxSwing(self): strokes = self.golfCourse.getCurHoleScore(self.activeGolferId) maxSwing = self.holeInfo['maxSwing'] retval = strokes >= maxSwing if retval: av = simbase.air.doId2do.get(self.activeGolferId) if av: if av.getUnlimitedSwing(): retval = False return retval def handleWaitPlaybackDone(self): if self.isCurBallInHole( self.activeGolferId) or self.hasCurGolferReachedMaxSwing(): if self.activeGolferId: self.ballInHole(self.activeGolferId) else: self.selectNextGolfer() def isCurBallInHole(self, golferId): retval = False senderId = self.air.getAvatarIdFromSender() if golferId not in self.ballPos: self.notify.warning('golferId=%s not in self.ballPos=%s' % (golferId, self.ballPos)) simbase.air.writeServerEvent( 'suspicious', senderId, 'isCurBallInHole golferId=%s not in self.ballPos=%s' % (golferId, self.ballPos)) return False for holePos in self.holePositions: displacement = self.ballPos[golferId] - holePos length = displacement.length() self.notify.debug('hole %s length=%s' % (holePos, length)) if length <= GolfGlobals.DistanceToBeInHole: retval = True break return retval def exitWaitPlayback(self): self.notify.debug('exitWaitPlayback') if hasattr(self, 'barrierPlayback') and self.barrierPlayback: self.barrierPlayback.cleanup() self.barrierPlayback = None return def enterCleanup(self): pass def exitCleanup(self): pass def handlePlaybackTimeout(self, task=None): self.notify.debug('handlePlaybackTimeout') self.handleWaitPlaybackDone() def getGolfCourseDoId(self): return self.golfCourse.doId def avatarDropped(self, avId): self.notify.warning('avId %d dropped, self.state=%s' % (avId, self.state)) if self.barrierPlayback: self.barrierPlayback.clear(avId) else: if avId == self.trustedPlayerId: self.doAction() if avId == self.activeGolferId and not self.golfCourse.haveAllGolfersExited( ): self.selectNextGolfer() def setAvatarTee(self, chosenTee): golferId = self.air.getAvatarIdFromSender() self.teeChosen[golferId] = chosenTee self.ballPos[golferId] = self.teePositions[chosenTee] self.sendUpdate('setAvatarFinalTee', [golferId, chosenTee]) self.sendUpdate('golfersTurn', [golferId]) self.request('WaitSwing') def setBox(self, pos0, pos1, pos2, quat0, quat1, quat2, quat3, anV0, anV1, anV2, lnV0, lnV1, lnV2): self.sendUpdate('sendBox', [ pos0, pos1, pos2, quat0, quat1, quat2, quat3, anV0, anV1, anV2, lnV0, lnV1, lnV2 ]) def parseLocators(self, objectCollection, optional=0): if optional and objectCollection.getNumPaths(): if self.holeInfo.has_key('optionalMovers'): for optionalMoverId in self.holeInfo['optionalMovers']: searchStr = 'optional_mover_' + str(optionalMoverId) for objIndex in range(objectCollection.getNumPaths()): object = objectCollection.getPath(objIndex) if searchStr in object.getName(): self.fillLocator(objectCollection, objIndex) break else: for index in range(objectCollection.getNumPaths()): self.fillLocator(objectCollection, index) def fillLocator(self, objectCollection, index): path = objectCollection[index] pathName = path.getName() pathArray = pathName.split('_') sizeX = None sizeY = None move = None type = None for subString in pathArray: if subString[:1] == 'X': dataString = subString[1:] dataString = dataString.replace('p', '.') sizeX = float(dataString) elif subString[:1] == 'Y': dataString = subString[1:] dataString = dataString.replace('p', '.') sizeY = float(dataString) elif subString[:1] == 'd': dataString = subString[1:] dataString = dataString.replace('p', '.') move = float(dataString) elif subString == 'mover': type = 4 elif subString == 'windmillLocator': type = 3 if type == 4 and move and sizeX and sizeY: self.createCommonObject(4, path.getPos(), path.getHpr(), sizeX, sizeY, move) elif type == 3: self.createCommonObject(3, path.getPos(), path.getHpr()) return
class DistributedCatchGameAI(DistributedMinigameAI): def __init__(self, air, minigameId): try: pass except: self.DistributedCatchGameAI_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) self.gameFSM = ClassicFSM.ClassicFSM('DistributedCatchGameAI', [ State.State('inactive', self.enterInactive, self.exitInactive, [ 'play']), State.State('play', self.enterPlay, self.exitPlay, [ 'cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, [ 'inactive'])], 'inactive', 'inactive') self.addChildGameFSM(self.gameFSM) def generate(self): self.notify.debug('generate') DistributedMinigameAI.generate(self) def delete(self): self.notify.debug('delete') del self.gameFSM DistributedMinigameAI.delete(self) def setGameReady(self): self.notify.debug('setGameReady') DistributedMinigameAI.setGameReady(self) def setGameStart(self, timestamp): self.notify.debug('setGameStart') DistributedMinigameAI.setGameStart(self, timestamp) self.gameFSM.request('play') def setGameAbort(self): self.notify.debug('setGameAbort') if self.gameFSM.getCurrentState(): self.gameFSM.request('cleanup') DistributedMinigameAI.setGameAbort(self) def gameOver(self): self.notify.debug('gameOver') self.notify.debug('fruits: %s, fruits caught: %s' % (self.numFruits, self.fruitsCaught)) perfect = self.fruitsCaught >= self.numFruits for avId in self.avIdList: self.scoreDict[avId] = max(1, int(self.scoreDict[avId] / 2)) if perfect: self.notify.debug('PERFECT GAME!') self.scoreDict[avId] += round(self.numFruits / 4.0) self.logAllPerfect() continue self.gameFSM.request('cleanup') DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug('enterInactive') def exitInactive(self): pass def enterPlay(self): self.notify.debug('enterPlay') self.caughtList = [ 0] * 100 table = CatchGameGlobals.NumFruits[self.numPlayers - 1] self.numFruits = table[self.getSafezoneId()] self.notify.debug('numFruits: %s' % self.numFruits) self.fruitsCaught = 0 def allToonsDone(self = self): self.notify.debug('allToonsDone') self.sendUpdate('setEveryoneDone') if not CatchGameGlobals.EndlessGame: self.gameOver() def handleTimeout(avIds, self = self): self.notify.debug('handleTimeout: avatars %s did not report "done"' % avIds) self.setGameAbort() self.doneBarrier = ToonBarrier('waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, CatchGameGlobals.GameDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout) def exitPlay(self): del self.caughtList self.doneBarrier.cleanup() del self.doneBarrier def claimCatch(self, objNum, DropObjTypeId): if self.gameFSM.getCurrentState().getName() != 'play': return None if DropObjTypeId < 0 or DropObjTypeId >= len(CatchGameGlobals.DOTypeId2Name): self.air.writeServerEvent('warning', DropObjTypeId, 'CatchGameAI.claimCatch DropObjTypeId out of range') return None if objNum < 0 and objNum > 5000 or objNum >= 2 * len(self.caughtList): self.air.writeServerEvent('warning', objNum, 'CatchGameAI.claimCatch objNum is too high or negative') return None if objNum >= len(self.caughtList): self.caughtList += [ 0] * len(self.caughtList) if not self.caughtList[objNum]: self.caughtList[objNum] = 1 avId = self.air.getAvatarIdFromSender() self.sendUpdate('setObjectCaught', [ avId, objNum]) objName = CatchGameGlobals.DOTypeId2Name[DropObjTypeId] self.notify.debug('avatar %s caught object %s: %s' % (avId, objNum, objName)) if CatchGameGlobals.Name2DropObjectType[objName].good: self.scoreDict[avId] += 1 self.fruitsCaught += 1 def reportDone(self): if not (self.gameFSM) and not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'play': return None avId = self.air.getAvatarIdFromSender() self.notify.debug('reportDone: avatar %s is done' % avId) self.doneBarrier.clear(avId) def enterCleanup(self): self.notify.debug('enterCleanup') self.gameFSM.request('inactive') def exitCleanup(self): pass
class DistributedPatternGameAI(DistributedMinigameAI): __module__ = __name__ def __init__(self, air, minigameId): try: self.DistributedPatternGameAI_initialized except: self.DistributedPatternGameAI_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) self.gameFSM = ClassicFSM.ClassicFSM('DistributedPatternGameAI', [ State.State('off', self.enterInactive, self.exitInactive, ['waitClientsReady', 'cleanup']), State.State('waitClientsReady', self.enterWaitClientsReady, self.exitWaitClientsReady, ['generatePattern', 'cleanup']), State.State('generatePattern', self.enterGeneratePattern, self.exitGeneratePattern, ['waitForResults', 'cleanup']), State.State('waitForResults', self.enterWaitForResults, self.exitWaitForResults, ['waitClientsReady', 'cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, []) ], 'off', 'cleanup') self.addChildGameFSM(self.gameFSM) def delete(self): self.notify.debug('delete') del self.gameFSM DistributedMinigameAI.delete(self) def setGameReady(self): self.notify.debug('setGameReady') DistributedMinigameAI.setGameReady(self) self.__initGameVars() def setGameStart(self, timestamp): self.notify.debug('setGameStart') DistributedMinigameAI.setGameStart(self, timestamp) self.gameFSM.request('waitClientsReady') def setGameAbort(self): self.notify.debug('setGameAbort') if self.gameFSM.getCurrentState(): self.gameFSM.request('cleanup') DistributedMinigameAI.setGameAbort(self) def gameOver(self): self.notify.debug('gameOver') self.gameFSM.request('cleanup') DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug('enterInactive') def exitInactive(self): pass def __initGameVars(self): self.pattern = [] self.round = 0 self.perfectResults = {} for avId in self.avIdList: self.perfectResults[avId] = 1 self.readyClients = [] self.timeoutTaskName = self.uniqueName('PatternGameResultsTimeout') def enterWaitClientsReady(self): self.notify.debug('enterWaitClientsReady') self.nextRoundBarrier = ToonBarrier( 'nextRoundReady', self.uniqueName('nextRoundReady'), self.avIdList, PatternGameGlobals.ClientsReadyTimeout, self.__allPlayersReady, self.__clientsReadyTimeout) for avId in self.readyClients: self.nextRoundBarrier.clear(avId) def reportPlayerReady(self): if not self._inState('waitClientsReady'): return avId = self.air.getAvatarIdFromSender() if avId not in self.avIdList: self.notify.warning( 'Got reportPlayerReady from an avId: %s not in our list: %s' % (avId, self.avIdList)) else: self.readyClients.append(avId) self.nextRoundBarrier.clear(avId) def __allPlayersReady(self): self.readyClients = [] self.gameFSM.request('generatePattern') def __clientsReadyTimeout(self, avIds): self.notify.debug( '__clientsReadyTimeout: clients %s have not responded' % avIds) self.setGameAbort() def exitWaitClientsReady(self): self.nextRoundBarrier.cleanup() del self.nextRoundBarrier def enterGeneratePattern(self): self.notify.debug('enterGeneratePattern') self.round += 1 targetLen = PatternGameGlobals.INITIAL_ROUND_LENGTH + PatternGameGlobals.ROUND_LENGTH_INCREMENT * ( self.round - 1) count = targetLen - len(self.pattern) for i in range(0, count): self.pattern.append(random.randint(0, 3)) self.gameFSM.request('waitForResults') self.sendUpdate('setPattern', [self.pattern]) def exitGeneratePattern(self): pass def enterWaitForResults(self): self.notify.debug('enterWaitForResults') self.results = [None] * self.numPlayers self.fastestTime = PatternGameGlobals.InputTime * 2 self.fastestAvId = 0 self.resultsBarrier = ToonBarrier( 'results', self.uniqueName('results'), self.avIdList, PatternGameGlobals.InputTimeout + 1.0 * self.round, self.__gotAllPatterns, self.__resultsTimeout) return def reportButtonPress(self, index, wrong): if not self._inState('waitForResults'): return avId = self.air.getAvatarIdFromSender() if avId not in self.avIdList: self.air.writeServerEvent( 'suspicious', avId, 'PatternGameAI.reportButtonPress avId not on list') return if index < 0 or index > 3: self.air.writeServerEvent( 'warning', index, 'PatternGameAI.reportButtonPress got bad index') return if wrong not in [0, 1]: self.air.writeServerEvent( 'warning', wrong, "PatternGameAI.reportButtonPress got bad 'wrong'") return self.sendUpdate('remoteButtonPressed', [avId, index, wrong]) def __resultsTimeout(self, avIds): self.notify.debug('__resultsTimeout: %s' % avIds) for avId in avIds: index = self.avIdList.index(avId) self.__acceptPlayerPattern(avId, [], PatternGameGlobals.InputTime * 2) self.__gotAllPatterns() def reportPlayerPattern(self, pattern, totalTime): if not self._inState('waitForResults'): return avId = self.air.getAvatarIdFromSender() self.__acceptPlayerPattern(avId, pattern, totalTime) self.resultsBarrier.clear(avId) def __acceptPlayerPattern(self, avId, pattern, totalTime): index = self.avIdList.index(avId) if self.results[index] != None: return self.results[index] = pattern if totalTime < self.fastestTime and pattern == self.pattern: self.fastestTime = totalTime self.fastestAvId = avId if self.numPlayers == 1: self.fastestAvId = 1 else: self.scoreDict[self.fastestAvId] += 2 return def __gotAllPatterns(self): patterns = [[]] * 4 for i in range(0, len(self.results)): patterns[i] = self.results[i] if patterns[i] is None: patterns[i] = [] self.sendUpdate('setPlayerPatterns', patterns + [self.fastestAvId]) for i in range(0, self.numPlayers): avId = self.avIdList[i] if not self.results[i] == self.pattern: self.perfectResults[avId] = 0 else: self.scoreDict[avId] += self.round if self.round < PatternGameGlobals.NUM_ROUNDS: self.gameFSM.request('waitClientsReady') else: for avId in self.avIdList: if self.perfectResults[avId]: self.scoreDict[avId] += 4 self.logPerfectGame(avId) self.gameOver() self.gameFSM.request('cleanup') return def exitWaitForResults(self): self.resultsBarrier.cleanup() del self.resultsBarrier def enterCleanup(self): self.notify.debug('enterCleanup') def exitCleanup(self): pass
class DistributedPatternGameAI(DistributedMinigameAI): def __init__(self, air, minigameId): try: self.DistributedPatternGameAI_initialized except: self.DistributedPatternGameAI_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) self.gameFSM = ClassicFSM.ClassicFSM( "DistributedPatternGameAI", [ State.State("off", self.enterInactive, self.exitInactive, ["waitClientsReady", "cleanup"]), State.State( "waitClientsReady", self.enterWaitClientsReady, self.exitWaitClientsReady, ["generatePattern", "cleanup"], ), State.State( "generatePattern", self.enterGeneratePattern, self.exitGeneratePattern, ["waitForResults", "cleanup"], ), State.State( "waitForResults", self.enterWaitForResults, self.exitWaitForResults, ["waitClientsReady", "cleanup"], ), State.State("cleanup", self.enterCleanup, self.exitCleanup, []), ], "off", "cleanup", ) self.addChildGameFSM(self.gameFSM) def delete(self): self.notify.debug("delete") del self.gameFSM DistributedMinigameAI.delete(self) def setGameReady(self): self.notify.debug("setGameReady") DistributedMinigameAI.setGameReady(self) self.__initGameVars() def setGameStart(self, timestamp): self.notify.debug("setGameStart") DistributedMinigameAI.setGameStart(self, timestamp) self.gameFSM.request("waitClientsReady") def setGameAbort(self): self.notify.debug("setGameAbort") if self.gameFSM.getCurrentState(): self.gameFSM.request("cleanup") DistributedMinigameAI.setGameAbort(self) def gameOver(self): self.notify.debug("gameOver") self.gameFSM.request("cleanup") DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug("enterInactive") def exitInactive(self): pass def __initGameVars(self): self.pattern = [] self.round = 0 self.perfectResults = {} for avId in self.avIdList: self.perfectResults[avId] = 1 self.readyClients = [] self.timeoutTaskName = self.uniqueName("PatternGameResultsTimeout") def enterWaitClientsReady(self): self.notify.debug("enterWaitClientsReady") self.nextRoundBarrier = ToonBarrier( "nextRoundReady", self.uniqueName("nextRoundReady"), self.avIdList, PatternGameGlobals.ClientsReadyTimeout, self.__allPlayersReady, self.__clientsReadyTimeout, ) for avId in self.readyClients: self.nextRoundBarrier.clear(avId) def reportPlayerReady(self): if not self._inState("waitClientsReady"): return avId = self.air.getAvatarIdFromSender() if avId not in self.avIdList: self.notify.warning("Got reportPlayerReady from an avId: %s not in our list: %s" % (avId, self.avIdList)) else: self.readyClients.append(avId) self.nextRoundBarrier.clear(avId) def __allPlayersReady(self): self.readyClients = [] self.gameFSM.request("generatePattern") def __clientsReadyTimeout(self, avIds): self.notify.debug("__clientsReadyTimeout: clients %s have not responded" % avIds) self.setGameAbort() def exitWaitClientsReady(self): self.nextRoundBarrier.cleanup() del self.nextRoundBarrier def enterGeneratePattern(self): self.notify.debug("enterGeneratePattern") self.round += 1 targetLen = PatternGameGlobals.INITIAL_ROUND_LENGTH + PatternGameGlobals.ROUND_LENGTH_INCREMENT * ( self.round - 1 ) count = targetLen - len(self.pattern) for i in range(0, count): self.pattern.append(random.randint(0, 3)) self.gameFSM.request("waitForResults") self.sendUpdate("setPattern", [self.pattern]) def exitGeneratePattern(self): pass def enterWaitForResults(self): self.notify.debug("enterWaitForResults") self.results = [None] * self.numPlayers self.fastestTime = PatternGameGlobals.InputTime * 2 self.fastestAvId = 0 self.resultsBarrier = ToonBarrier( "results", self.uniqueName("results"), self.avIdList, PatternGameGlobals.InputTimeout + 1.0 * self.round, self.__gotAllPatterns, self.__resultsTimeout, ) return def reportButtonPress(self, index, wrong): if not self._inState("waitForResults"): return avId = self.air.getAvatarIdFromSender() if avId not in self.avIdList: self.air.writeServerEvent("suspicious", avId, "PatternGameAI.reportButtonPress avId not on list") return if index < 0 or index > 3: self.air.writeServerEvent("warning", index, "PatternGameAI.reportButtonPress got bad index") return if wrong not in [0, 1]: self.air.writeServerEvent("warning", wrong, "PatternGameAI.reportButtonPress got bad 'wrong'") return self.sendUpdate("remoteButtonPressed", [avId, index, wrong]) def __resultsTimeout(self, avIds): self.notify.debug("__resultsTimeout: %s" % avIds) for avId in avIds: index = self.avIdList.index(avId) self.__acceptPlayerPattern(avId, [], PatternGameGlobals.InputTime * 2) self.__gotAllPatterns() def reportPlayerPattern(self, pattern, totalTime): if not self._inState("waitForResults"): return avId = self.air.getAvatarIdFromSender() self.__acceptPlayerPattern(avId, pattern, totalTime) self.resultsBarrier.clear(avId) def __acceptPlayerPattern(self, avId, pattern, totalTime): index = self.avIdList.index(avId) if self.results[index] != None: return self.results[index] = pattern if totalTime < self.fastestTime and pattern == self.pattern: self.fastestTime = totalTime self.fastestAvId = avId if self.numPlayers == 1: self.fastestAvId = 1 else: self.scoreDict[self.fastestAvId] += 2 return def __gotAllPatterns(self): patterns = [[]] * 4 for i in range(0, len(self.results)): patterns[i] = self.results[i] if patterns[i] is None: patterns[i] = [] self.sendUpdate("setPlayerPatterns", patterns + [self.fastestAvId]) for i in range(0, self.numPlayers): avId = self.avIdList[i] if not self.results[i] == self.pattern: self.perfectResults[avId] = 0 else: self.scoreDict[avId] += self.round if self.round < PatternGameGlobals.NUM_ROUNDS: self.gameFSM.request("waitClientsReady") else: for avId in self.avIdList: if self.perfectResults[avId]: self.scoreDict[avId] += 4 self.logPerfectGame(avId) self.gameOver() self.gameFSM.request("cleanup") return def exitWaitForResults(self): self.resultsBarrier.cleanup() del self.resultsBarrier def enterCleanup(self): self.notify.debug("enterCleanup") def exitCleanup(self): pass
class DistributedTwoDGameAI(DistributedMinigameAI): notify = DirectNotifyGlobal.directNotify.newCategory( 'DistributedTwoDGameAI') def __init__(self, air, minigameId): try: self.DistributedTwoDGameAI_initialized except: self.DistributedTwoDGame_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) self.gameFSM = ClassicFSM.ClassicFSM('DistributedTwoDGameAI', [ State.State('inactive', self.enterInactive, self.exitInactive, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, ['inactive']) ], 'inactive', 'inactive') self.addChildGameFSM(self.gameFSM) self.finishedBonusDict = {} self.finishedTimeLeftDict = {} self.numFallDownDict = {} self.numHitByEnemyDict = {} self.numSquishDict = {} self.treasuresCollectedDict = {} self.sectionsSelected = [] self.enemyHealthTable = [] self.treasureTakenTable = [] self.sectionIndexList = [] def generate(self): self.notify.debug('generate') DistributedMinigameAI.generate(self) def delete(self): self.notify.debug('delete') del self.gameFSM DistributedMinigameAI.delete(self) def setTrolleyZone(self, trolleyZone): DistributedMinigameAI.setTrolleyZone(self, trolleyZone) self.setupSections() def setGameReady(self): self.notify.debug('setGameReady') DistributedMinigameAI.setGameReady(self) self.numTreasures = ToonBlitzGlobals.NumTreasures self.numEnemies = ToonBlitzGlobals.NumEnemies self.numTreasuresTaken = 0 self.numEnemiesKilled = 0 for avId in self.scoreDict.keys(): self.scoreDict[avId] = 0 self.finishedBonusDict[avId] = 0 self.finishedTimeLeftDict[avId] = -1 self.numFallDownDict[avId] = 0 self.numHitByEnemyDict[avId] = 0 self.numSquishDict[avId] = 0 self.treasuresCollectedDict[avId] = [0, 0, 0, 0] for i in xrange(len(self.sectionsSelected)): sectionIndex = self.sectionsSelected[i][0] attribs = ToonBlitzGlobals.SectionTypes[sectionIndex] enemiesPool = attribs[3] self.enemyHealthTable += [[]] enemyIndicesSelected = self.sectionsSelected[i][1] for j in xrange(len(enemyIndicesSelected)): enemyIndex = enemyIndicesSelected[j] enemyType = enemiesPool[enemyIndex][0] self.enemyHealthTable[i] += [ToonBlitzGlobals.EnemyBaseHealth] self.enemyHealthTable[i][j] *= self.numPlayers if enemyType in ToonBlitzGlobals.EnemyHealthMultiplier: self.enemyHealthTable[i][ j] *= ToonBlitzGlobals.EnemyHealthMultiplier[enemyType] self.treasureTakenTable += [[]] treasureIndicesSelected = self.sectionsSelected[i][2] for j in xrange(len(treasureIndicesSelected)): self.treasureTakenTable[i] += [0] enemyIndicesSelected = self.sectionsSelected[i][1] for j in xrange(len(enemyIndicesSelected)): self.treasureTakenTable[i] += [0] def setGameStart(self, timestamp): self.notify.debug('setGameStart') DistributedMinigameAI.setGameStart(self, timestamp) self.gameFSM.request('play') def setGameAbort(self, normalExit=0): self.notify.debug('setGameAbort') if self.gameFSM.getCurrentState(): self.gameFSM.request('cleanup') DistributedMinigameAI.setGameAbort(self, normalExit) def gameOver(self): self.notify.debug('gameOver') scoreList = [] finishedBonusList = [] timeLeftList = [] treasureCollectedList = [] playerErrorList = [] for avId in self.avIdList: scoreList.append(self.scoreDict[avId]) finishedBonusList.append(self.finishedBonusDict[avId]) timeLeftList.append(self.finishedTimeLeftDict[avId]) treasureCollectedList.append(self.treasuresCollectedDict[avId]) playerError = [ self.numFallDownDict[avId], self.numHitByEnemyDict[avId], self.numSquishDict[avId] ] playerErrorList.append(playerError) self.scoreDict[avId] = max(0, self.scoreDict[avId]) jellybeans = sqrt(self.scoreDict[avId] * ToonBlitzGlobals.ScoreToJellyBeansMultiplier) self.scoreDict[avId] = max(1, int(jellybeans)) self.air.writeServerEvent( 'minigame_twoD', self.doId, '%s|%s|%s|%s|%s|%s|%s|%s|%s' % (ToontownGlobals.TwoDGameId, self.getSafezoneId(), self.avIdList, scoreList, finishedBonusList, timeLeftList, treasureCollectedList, playerErrorList, self.sectionIndexList)) self.notify.debug( 'minigame_twoD%s: %s|%s|%s|%s|%s|%s|%s|%s|%s' % (self.doId, ToontownGlobals.TwoDGameId, self.getSafezoneId(), self.avIdList, scoreList, finishedBonusList, timeLeftList, treasureCollectedList, playerErrorList, self.sectionIndexList)) self.gameFSM.request('cleanup') DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug('enterInactive') def exitInactive(self): pass def enterPlay(self): self.notify.debug('enterPlay') def allToonsDone(self=self): self.notify.debug('allToonsDone') self.sendUpdate('setEveryoneDone') if not ToonBlitzGlobals.EndlessGame: self.gameOver() def handleTimeout(avIds, self=self): self.notify.debug( 'handleTimeout: avatars %s did not report "done"' % avIds) self.setGameAbort() self.doneBarrier = ToonBarrier( 'waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, ToonBlitzGlobals.GameDuration[self.getSafezoneId()] + ToonBlitzGlobals.ShowScoresDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout) def exitPlay(self): pass def enterCleanup(self): self.notify.debug('enterCleanup') self.doneBarrier.cleanup() del self.doneBarrier self.gameFSM.request('inactive') def exitCleanup(self): pass def claimTreasure(self, sectionIndex, treasureIndex): avId = self.air.getAvatarIdFromSender() self.notify.debug('treasure %s-%s claimed by %s' % (sectionIndex, treasureIndex, avId)) if sectionIndex < 0 or sectionIndex >= len(self.sectionsSelected): self.air.writeServerEvent( 'warning', sectionIndex, 'TwoDGameAI.claimTreasure sectionIndex out of range.') return if treasureIndex < 0 or treasureIndex >= len( self.treasureTakenTable[sectionIndex]): self.notify.warning( 'Treasure %s: TwoDGameAI.claimTreasure treasureIndex out of range.' % treasureIndex) self.air.writeServerEvent( 'warning', treasureIndex, 'TwoDGameAI.claimTreasure treasureIndex out of range.') return if self.treasureTakenTable[sectionIndex][treasureIndex]: return initialTreasureList = self.sectionsSelected[sectionIndex][2] if treasureIndex < len(initialTreasureList): treasureValue = initialTreasureList[treasureIndex][1] else: treasureValue = self.numPlayers self.treasureTakenTable[sectionIndex][treasureIndex] = treasureValue self.treasuresCollectedDict[avId][(treasureValue - 1)] += 1 self.scoreDict[ avId] += ToonBlitzGlobals.ScoreGainPerTreasure * treasureValue self.numTreasuresTaken += 1 self.sendUpdate('setTreasureGrabbed', [avId, sectionIndex, treasureIndex]) def claimEnemyShot(self, sectionIndex, enemyIndex): avId = self.air.getAvatarIdFromSender() self.notify.debug('enemy %s-%s shot claimed by %s' % (sectionIndex, enemyIndex, avId)) if sectionIndex < 0 or sectionIndex >= len(self.sectionsSelected): self.air.writeServerEvent( 'warning', sectionIndex, 'TwoDGameAI.claimEnemyShot sectionIndex out of range.') return if enemyIndex < 0 or enemyIndex >= len( self.sectionsSelected[sectionIndex][1]): self.air.writeServerEvent( 'warning', enemyIndex, 'TwoDGameAI.claimEnemyShot enemyIndex out of range.') return if self.enemyHealthTable[sectionIndex][enemyIndex] > 0: self.enemyHealthTable[sectionIndex][ enemyIndex] -= ToonBlitzGlobals.DamagePerBullet if self.enemyHealthTable[sectionIndex][enemyIndex] <= 0: self.numEnemiesKilled += 1 self.sendUpdate('setEnemyShot', [ avId, sectionIndex, enemyIndex, self.enemyHealthTable[sectionIndex][enemyIndex] ]) def reportDone(self): if self.gameFSM.getCurrentState( ) == None or self.gameFSM.getCurrentState().getName() != 'play': return avId = self.air.getAvatarIdFromSender() self.notify.debug('reportDone: avatar %s is done' % avId) self.doneBarrier.clear(avId) return def toonVictory(self, avId, timestamp): if self.gameFSM.getCurrentState( ) == None or self.gameFSM.getCurrentState().getName() != 'play': msg = 'TwoDGameAI.toonVictory not in play state!' self.notify.warning('suspicious: ' + str(avId) + ' ' + msg) self.air.writeServerEvent('suspicious: ', avId, msg) return else: if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEvent( 'suspicious: ', avId, 'TwoDGameAI.toonVictory toon not in list.') return curTime = self.getCurrentGameTime() timeLeft = ToonBlitzGlobals.GameDuration[ self.getSafezoneId()] - curTime self.notify.debug('curTime =%s timeLeft = %s' % (curTime, timeLeft)) addBonus = int( ToonBlitzGlobals.BaseBonusOnCompletion[self.getSafezoneId()] + ToonBlitzGlobals.BonusPerSecondLeft * timeLeft) self.notify.debug('addBOnus = %d' % addBonus) if addBonus < 0: addBonus = 0 self.finishedBonusDict[avId] = addBonus timeLeftStr = '%.1f' % timeLeft self.finishedTimeLeftDict[avId] = timeLeftStr self.scoreDict[avId] += addBonus self.sendUpdate('addVictoryScore', [avId, addBonus]) self.doneBarrier.clear(avId) return def toonFellDown(self, avId, timestamp): if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEvent( 'warning', avId, 'TwoDGameAI.toonFellDown toon not in list.') return self.numFallDownDict[avId] += 1 self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerFallDown[ self.getSafezoneId()] def toonHitByEnemy(self, avId, timestamp): if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEvent( 'warning', avId, 'TwoDGameAI.toonHitByEnemy toon not in list.') return self.numHitByEnemyDict[avId] += 1 self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerEnemyCollision[ self.getSafezoneId()] def toonSquished(self, avId, timestamp): if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEvent( 'warning', avId, 'TwoDGameAI.toonSquished toon not in list.') return self.numSquishDict[avId] += 1 self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerStomperSquish[ self.getSafezoneId()] def setupSections(self): szId = self.getSafezoneId() sectionWeights = ToonBlitzGlobals.SectionWeights[szId] numSections = ToonBlitzGlobals.NumSections[szId] difficultyPool = [] difficultyList = [] sectionsPool = ToonBlitzGlobals.SectionsPool sectionTypes = ToonBlitzGlobals.SectionTypes sectionsPoolByDifficulty = [[], [], [], [], [], []] sectionsSelectedByDifficulty = [[], [], [], [], [], []] sectionIndicesSelected = [] for weight in sectionWeights: difficulty, probability = weight difficultyPool += [difficulty] * probability for i in xrange(numSections): difficulty = random.choice(difficultyPool) difficultyList.append(difficulty) difficultyList.sort() for sectionIndex in sectionsPool: difficulty = sectionTypes[sectionIndex][0] sectionsPoolByDifficulty[difficulty] += [sectionIndex] for targetDifficulty in difficultyList: whileCount = 0 difficulty = targetDifficulty while not len(sectionsPoolByDifficulty[difficulty]) > 0: difficulty += 1 if difficulty >= 5: difficulty = 0 whileCount += 1 if whileCount > 1: break else: sectionIndexChoice = random.choice( sectionsPoolByDifficulty[difficulty]) sectionsSelectedByDifficulty[difficulty] += [ sectionIndexChoice ] sectionsPoolByDifficulty[difficulty].remove(sectionIndexChoice) if whileCount > 1: self.notify.debug( 'We need more sections than we have choices. We have to now repeat.' ) for i in xrange(len(sectionsSelectedByDifficulty)): for j in xrange(len(sectionsSelectedByDifficulty[i])): sectionIndicesSelected.append( sectionsSelectedByDifficulty[i][j]) for i in xrange(len(sectionIndicesSelected)): sectionIndex = sectionIndicesSelected[i] self.sectionIndexList.append(sectionIndex) attribs = sectionTypes[sectionIndex] difficulty = attribs[0] length = attribs[1] blocksPool = attribs[2] enemiesPool = attribs[3] treasuresPool = attribs[4] spawnPointsPool = attribs[5] stompersPool = attribs[6] enemyIndicesPool = [] enemyIndicesSelected = [] if enemiesPool != None: minEnemies, maxEnemies = attribs[7] for i in xrange(len(enemiesPool)): enemyIndicesPool += [i] numEnemies = maxEnemies * ToonBlitzGlobals.PercentMaxEnemies[ szId] / 100 numEnemies = max(numEnemies, minEnemies) for j in xrange(int(numEnemies)): if len(enemyIndicesPool) == 0: break enemyIndex = random.choice(enemyIndicesPool) enemyIndicesSelected.append(enemyIndex) enemyIndicesPool.remove(enemyIndex) enemyIndicesSelected.sort() treasureIndicesPool = [] treasureValuePool = [] for value in xrange(1, 5): treasureValuePool += [ value ] * ToonBlitzGlobals.TreasureValueProbability[value] treasureIndicesSelected = [] if treasuresPool != None: minTreasures, maxTreasures = attribs[8] for i in xrange(len(treasuresPool)): treasureIndicesPool += [i] numTreasures = maxTreasures * ToonBlitzGlobals.PercentMaxTreasures[ szId] / 100 numTreasures = max(numTreasures, minTreasures) for i in xrange(int(numTreasures)): if len(treasureIndicesPool) == 0: break treasureIndex = random.choice(treasureIndicesPool) treasureValue = random.choice(treasureValuePool) treasure = (treasureIndex, treasureValue) treasureIndicesPool.remove(treasureIndex) treasureIndicesSelected.append(treasure) treasureIndicesSelected.sort() spawnPointIndicesPool = [] spawnPointIndicesSelected = [] if spawnPointsPool != None: minSpawnPoints, maxSpawnPoints = attribs[9] for i in xrange(len(spawnPointsPool)): spawnPointIndicesPool += [i] numSpawnPoints = maxSpawnPoints * ToonBlitzGlobals.PercentMaxSpawnPoints[ szId] / 100 numSpawnPoints = max(numSpawnPoints, minSpawnPoints) for i in xrange(int(numSpawnPoints)): if len(spawnPointIndicesPool) == 0: break spawnPoint = random.choice(spawnPointIndicesPool) spawnPointIndicesSelected.append(spawnPoint) spawnPointIndicesPool.remove(spawnPoint) spawnPointIndicesSelected.sort() stomperIndicesPool = [] stomperIndicesSelected = [] if stompersPool != None: minStompers, maxStompers = attribs[10] for i in xrange(len(stompersPool)): stomperIndicesPool += [i] numStompers = maxStompers * ToonBlitzGlobals.PercentMaxStompers[ szId] / 100 numStompers = max(numStompers, minStompers) for i in xrange(int(numStompers)): if len(stomperIndicesPool) == 0: break stomper = random.choice(stomperIndicesPool) stomperIndicesSelected.append(stomper) stomperIndicesPool.remove(stomper) stomperIndicesSelected.sort() sctionTuple = (sectionIndex, enemyIndicesSelected, treasureIndicesSelected, spawnPointIndicesSelected, stomperIndicesSelected) self.sectionsSelected.append(sctionTuple) return def getSectionsSelected(self): return self.sectionsSelected
class DistributedTwoDGameAI(DistributedMinigameAI): notify = DirectNotifyGlobal.directNotify.newCategory( 'DistributedTwoDGameAI') def __init__(self, air, minigameId): try: self.DistributedTwoDGameAI_initialized except: self.DistributedTwoDGame_initialized = 1 DistributedMinigameAI.__init__(self, air, minigameId) ## simbase.mgAI = self self.gameFSM = ClassicFSM.ClassicFSM( 'DistributedTwoDGameAI', [ State.State('inactive', self.enterInactive, self.exitInactive, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, ['inactive']), ], # Initial State 'inactive', # Final State 'inactive', ) # Add our game ClassicFSM to the framework ClassicFSM self.addChildGameFSM(self.gameFSM) self.finishedBonusDict = {} self.finishedTimeLeftDict = {} self.numFallDownDict = {} self.numHitByEnemyDict = {} self.numSquishDict = {} self.treasuresCollectedDict = {} self.sectionsSelected = [] self.enemyHealthTable = [] self.treasureTakenTable = [] self.sectionIndexList = [] def generate(self): self.notify.debug("generate") DistributedMinigameAI.generate(self) # Disable is never called on the AI so we do not define one def delete(self): self.notify.debug("delete") del self.gameFSM DistributedMinigameAI.delete(self) def setTrolleyZone(self, trolleyZone): # We need the trolley zone before we can setup the sections, so do it here DistributedMinigameAI.setTrolleyZone(self, trolleyZone) self.setupSections() # override some network message handlers def setGameReady(self): self.notify.debug("setGameReady") DistributedMinigameAI.setGameReady(self) # all of the players have checked in # they will now be shown the rules # @TODO: Samik 05/29/08: DistributedTwoDGameAI should decide self.numTreasures & self.numEnemies. # It shouldn't directly get it from ToonBlitzGlobals. self.numTreasures = ToonBlitzGlobals.NumTreasures self.numEnemies = ToonBlitzGlobals.NumEnemies self.numTreasuresTaken = 0 self.numEnemiesKilled = 0 # Reset scores for avId in self.scoreDict.keys(): self.scoreDict[avId] = 0 self.finishedBonusDict[avId] = 0 self.finishedTimeLeftDict[avId] = -1 self.numFallDownDict[avId] = 0 self.numHitByEnemyDict[avId] = 0 self.numSquishDict[avId] = 0 self.treasuresCollectedDict[avId] = [ 0, 0, 0, 0 ] # [value1, value2, value3, value4] # Maintaining a table for enemy health and another table for treasure taken for i in xrange(len(self.sectionsSelected)): sectionIndex = self.sectionsSelected[i][0] attribs = ToonBlitzGlobals.SectionTypes[sectionIndex] enemiesPool = attribs[3] # Set up enemy health table self.enemyHealthTable += [[]] enemyIndicesSelected = self.sectionsSelected[i][1] for j in xrange(len(enemyIndicesSelected)): # Maintaining this enemy's health in enemyHealthTable enemyIndex = enemyIndicesSelected[j] enemyType = enemiesPool[enemyIndex][0] self.enemyHealthTable[i] += [ToonBlitzGlobals.EnemyBaseHealth] self.enemyHealthTable[i][j] *= self.numPlayers if ToonBlitzGlobals.EnemyHealthMultiplier.has_key(enemyType): self.enemyHealthTable[i][ j] *= ToonBlitzGlobals.EnemyHealthMultiplier[enemyType] # Set up the treasure taken table self.treasureTakenTable += [[]] treasureIndicesSelected = self.sectionsSelected[i][2] for j in xrange(len(treasureIndicesSelected)): # Maintaining this treasure's taken flag in treasureTakenTable self.treasureTakenTable[i] += [0] # Adding the enemy generated treasures to this list also. enemyIndicesSelected = self.sectionsSelected[i][1] for j in xrange(len(enemyIndicesSelected)): self.treasureTakenTable[i] += [0] def setGameStart(self, timestamp): self.notify.debug("setGameStart") # base class will cause gameFSM to enter initial state DistributedMinigameAI.setGameStart(self, timestamp) # all of the players are ready to start playing the game # transition to the appropriate ClassicFSM state self.gameFSM.request('play') def setGameAbort(self): self.notify.debug("setGameAbort") # this is called when the minigame is unexpectedly # ended (a player got disconnected, etc.) if self.gameFSM.getCurrentState(): self.gameFSM.request('cleanup') DistributedMinigameAI.setGameAbort(self) def gameOver(self): self.notify.debug("gameOver") # call this when the game is done # Log balancing variables to the event server scoreList = [] finishedBonusList = [] timeLeftList = [] treasureCollectedList = [] playerErrorList = [] for avId in self.avIdList: scoreList.append(self.scoreDict[avId]) finishedBonusList.append(self.finishedBonusDict[avId]) timeLeftList.append(self.finishedTimeLeftDict[avId]) treasureCollectedList.append(self.treasuresCollectedDict[avId]) playerError = [ self.numFallDownDict[avId], self.numHitByEnemyDict[avId], self.numSquishDict[avId] ] playerErrorList.append(playerError) self.scoreDict[avId] = max(0, self.scoreDict[avId]) jellybeans = sqrt(self.scoreDict[avId] * ToonBlitzGlobals.ScoreToJellyBeansMultiplier) self.scoreDict[avId] = max(1, int(jellybeans)) self.air.writeServerEvent( 'minigame_twoD', self.doId, '%s|%s|%s|%s|%s|%s|%s|%s|%s' % (ToontownGlobals.TwoDGameId, self.getSafezoneId(), self.avIdList, scoreList, finishedBonusList, timeLeftList, treasureCollectedList, playerErrorList, self.sectionIndexList)) self.notify.debug( 'minigame_twoD%s: %s|%s|%s|%s|%s|%s|%s|%s|%s' % (self.doId, ToontownGlobals.TwoDGameId, self.getSafezoneId(), self.avIdList, scoreList, finishedBonusList, timeLeftList, treasureCollectedList, playerErrorList, self.sectionIndexList)) # clean things up in this class self.gameFSM.request('cleanup') # tell the base class to wrap things up DistributedMinigameAI.gameOver(self) def enterInactive(self): self.notify.debug("enterInactive") def exitInactive(self): pass def enterPlay(self): self.notify.debug("enterPlay") # set up a barrier to wait for the 'game done' msgs def allToonsDone(self=self): self.notify.debug('allToonsDone') self.sendUpdate('setEveryoneDone') if not ToonBlitzGlobals.EndlessGame: # Show scores here and then end game self.gameOver() def handleTimeout(avIds, self=self): self.notify.debug( 'handleTimeout: avatars %s did not report "done"' % avIds) self.setGameAbort() self.doneBarrier = ToonBarrier( 'waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, ToonBlitzGlobals.GameDuration[self.getSafezoneId()] + \ ToonBlitzGlobals.ShowScoresDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout) # when the game is done, call gameOver() ## self.gameOver() def exitPlay(self): pass def enterCleanup(self): self.notify.debug("enterCleanup") self.doneBarrier.cleanup() del self.doneBarrier self.gameFSM.request('inactive') def exitCleanup(self): pass def claimTreasure(self, sectionIndex, treasureIndex): avId = self.air.getAvatarIdFromSender() self.notify.debug('treasure %s-%s claimed by %s' % (sectionIndex, treasureIndex, avId)) # Check validitiy of sectionIndex if (sectionIndex < 0) or (sectionIndex >= len(self.sectionsSelected)): self.air.writeServerEvent( 'warning', sectionIndex, 'TwoDGameAI.claimTreasure sectionIndex out of range.') return # Check validitiy of treasureIndex if (treasureIndex < 0) or (treasureIndex >= len( self.treasureTakenTable[sectionIndex])): self.notify.warning( 'Treasure %s: TwoDGameAI.claimTreasure treasureIndex out of range.' % treasureIndex) self.air.writeServerEvent( 'warning', treasureIndex, 'TwoDGameAI.claimTreasure treasureIndex out of range.') return # Give the treasure only to the first toon that claims it. if self.treasureTakenTable[sectionIndex][treasureIndex]: return initialTreasureList = self.sectionsSelected[sectionIndex][2] if (treasureIndex < len(initialTreasureList)): # treasureValue can be found from what the AI had initially determined it. treasureValue = initialTreasureList[treasureIndex][1] else: # This is the case of a enemy generated treasure. treasureValue = self.numPlayers self.treasureTakenTable[sectionIndex][treasureIndex] = treasureValue # Register count self.treasuresCollectedDict[avId][treasureValue - 1] += 1 self.scoreDict[ avId] += ToonBlitzGlobals.ScoreGainPerTreasure * treasureValue self.numTreasuresTaken += 1 self.sendUpdate("setTreasureGrabbed", [avId, sectionIndex, treasureIndex]) def claimEnemyShot(self, sectionIndex, enemyIndex): avId = self.air.getAvatarIdFromSender() self.notify.debug('enemy %s-%s shot claimed by %s' % (sectionIndex, enemyIndex, avId)) # Check validitiy of sectionIndex if (sectionIndex < 0) or (sectionIndex >= len(self.sectionsSelected)): self.air.writeServerEvent( 'warning', sectionIndex, 'TwoDGameAI.claimEnemyShot sectionIndex out of range.') return # Check validitiy of enemyIndex if (enemyIndex < 0) or (enemyIndex >= len( self.sectionsSelected[sectionIndex][1])): self.air.writeServerEvent( 'warning', enemyIndex, 'TwoDGameAI.claimEnemyShot enemyIndex out of range.') return # send update only if the enemy's health > 0 if (self.enemyHealthTable[sectionIndex][enemyIndex] > 0): self.enemyHealthTable[sectionIndex][ enemyIndex] -= ToonBlitzGlobals.DamagePerBullet if (self.enemyHealthTable[sectionIndex][enemyIndex] <= 0): self.numEnemiesKilled += 1 ## # Add a treasure slot to the treasureTakenTable ## self.treasureTakenTable[sectionIndex] += [0] self.sendUpdate('setEnemyShot', [ avId, sectionIndex, enemyIndex, self.enemyHealthTable[sectionIndex][enemyIndex] ]) def reportDone(self): if self.gameFSM.getCurrentState().getName() != 'play': return avId = self.air.getAvatarIdFromSender() # This avatar client's timer is up self.notify.debug('reportDone: avatar %s is done' % avId) self.doneBarrier.clear(avId) def toonVictory(self, avId, timestamp): """ Called when a remote toon reaches the end of tunnel. """ if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEvent( 'suspicious: ', avId, 'TwoDGameAI.toonVictory toon not in list.') return # This toon has reached the end. Give him bonus points. curTime = self.getCurrentGameTime() timeLeft = ToonBlitzGlobals.GameDuration[ self.getSafezoneId()] - curTime self.notify.debug('curTime =%s timeLeft = %s' % (curTime, timeLeft)) addBonus = int(ToonBlitzGlobals.BaseBonusOnCompletion[self.getSafezoneId()] + \ ToonBlitzGlobals.BonusPerSecondLeft * timeLeft) self.notify.debug('addBOnus = %d' % addBonus) if addBonus < 0: addBonus = 0 self.finishedBonusDict[avId] = addBonus timeLeftStr = '%.1f' % timeLeft self.finishedTimeLeftDict[avId] = timeLeftStr self.scoreDict[avId] += addBonus self.sendUpdate("addVictoryScore", [avId, addBonus]) if self.gameFSM.getCurrentState().getName() != 'play': return self.doneBarrier.clear(avId) def toonFellDown(self, avId, timestamp): """ Called when a toon falls through a hole.""" if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEvent( 'warning', avId, 'TwoDGameAI.toonFellDown toon not in list.') return # Register count self.numFallDownDict[avId] += 1 # Subtract score for that toon self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerFallDown[ self.getSafezoneId()] def toonHitByEnemy(self, avId, timestamp): """ Called when a toon is hit by suit """ if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEvent( 'warning', avId, 'TwoDGameAI.toonHitByEnemy toon not in list.') return # Register count self.numHitByEnemyDict[avId] += 1 # Subtract score for that toon self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerEnemyCollision[ self.getSafezoneId()] def toonSquished(self, avId, timestamp): """ Called when a toon is squished by a stomper.""" if avId not in self.scoreDict.keys(): self.notify.warning('Avatar %s not in list.' % avId) self.air.writeServerEvent( 'warning', avId, 'TwoDGameAI.toonSquished toon not in list.') return # Register count self.numSquishDict[avId] += 1 # Subtract score for that toon self.scoreDict[avId] += ToonBlitzGlobals.ScoreLossPerStomperSquish[ self.getSafezoneId()] def setupSections(self): """ Make a course by selecting sections based on difficulty and probability of occurence in that safeZone.""" szId = self.getSafezoneId() sectionWeights = ToonBlitzGlobals.SectionWeights[szId] numSections = ToonBlitzGlobals.NumSections[szId] difficultyPool = [] difficultyList = [] sectionsPool = ToonBlitzGlobals.SectionsPool sectionTypes = ToonBlitzGlobals.SectionTypes sectionsPoolByDifficulty = [[], [], [], [], [], []] sectionsSelectedByDifficulty = [[], [], [], [], [], []] sectionIndicesSelected = [] for weight in sectionWeights: difficulty, probability = weight difficultyPool += [difficulty] * probability # Now make a list of difficulty from the difficultyPool for i in xrange(numSections): difficulty = random.choice(difficultyPool) difficultyList.append(difficulty) # Sort the difficultyList so that the more difficult sections appear at the end of the game difficultyList.sort() # Split SectionsPool into sectionsPoolByDifficulty for sectionIndex in sectionsPool: difficulty = sectionTypes[sectionIndex][0] sectionsPoolByDifficulty[difficulty] += [sectionIndex] # Now go through the difficutly list, and select a section from the sectionsPool with that difficulty # Do not repeat. If we are out of sections with that difficulty take one from the next difficulty level for targetDifficulty in difficultyList: whileCount = 0 difficulty = targetDifficulty # If there is no section left to pick from that difficulty level pick one from the next difficulty level while not (len(sectionsPoolByDifficulty[difficulty]) > 0): difficulty += 1 if (difficulty >= 5): difficulty = 0 whileCount += 1 if (whileCount > 1): break else: sectionIndexChoice = random.choice( sectionsPoolByDifficulty[difficulty]) # Adding this sectionIndex to the list of selectedSections sectionsSelectedByDifficulty[difficulty] += [ sectionIndexChoice ] # Removing this sectionIndex from the list of sectionsPoolByDifficulty sectionsPoolByDifficulty[difficulty].remove(sectionIndexChoice) if (whileCount > 1): self.notify.debug( 'We need more sections than we have choices. We have to now repeat.' ) # Fill up sectionIndicesSelected from sectionsSelectedByDifficulty to maintain 1 comprehensive list for i in xrange(len(sectionsSelectedByDifficulty)): for j in xrange(len(sectionsSelectedByDifficulty[i])): sectionIndicesSelected.append( sectionsSelectedByDifficulty[i][j]) # Now go through the sectionIndicesSelected and get their properties for i in xrange(len(sectionIndicesSelected)): sectionIndex = sectionIndicesSelected[i] self.sectionIndexList.append(sectionIndex) attribs = sectionTypes[sectionIndex] difficulty = attribs[0] length = attribs[1] blocksPool = attribs[2] enemiesPool = attribs[3] treasuresPool = attribs[4] spawnPointsPool = attribs[5] stompersPool = attribs[6] # Select a random list of numEnemies enemyIndices from the enemyIndicesPool enemyIndicesPool = [] enemyIndicesSelected = [] if (enemiesPool != None): minEnemies, maxEnemies = attribs[7] for i in xrange(len(enemiesPool)): enemyIndicesPool += [i] numEnemies = maxEnemies * ToonBlitzGlobals.PercentMaxEnemies[ szId] / 100 numEnemies = max(numEnemies, minEnemies) for j in xrange(int(numEnemies)): if (len(enemyIndicesPool) == 0): break enemyIndex = random.choice(enemyIndicesPool) enemyIndicesSelected.append(enemyIndex) enemyIndicesPool.remove(enemyIndex) # Sort the indices in enemyIndicesSelected so that they appear in the right order of location in a section enemyIndicesSelected.sort() # Select a random list of numTreasures treasureIndices from the treasureIndicesPool treasureIndicesPool = [] # 1 value treasures have a 40% chance of getting picked and the 4 value treasures have only a 10% chance of getting picked. treasureValuePool = [] for value in range(1, 5): treasureValuePool += [ value ] * ToonBlitzGlobals.TreasureValueProbability[value] treasureIndicesSelected = [] if (treasuresPool != None): minTreasures, maxTreasures = attribs[8] for i in xrange(len(treasuresPool)): treasureIndicesPool += [i] numTreasures = maxTreasures * ToonBlitzGlobals.PercentMaxTreasures[ szId] / 100 numTreasures = max(numTreasures, minTreasures) for i in xrange(int(numTreasures)): if (len(treasureIndicesPool) == 0): break treasureIndex = random.choice(treasureIndicesPool) treasureValue = random.choice(treasureValuePool) treasure = (treasureIndex, treasureValue) treasureIndicesPool.remove(treasureIndex) treasureIndicesSelected.append(treasure) # Sort the indices in treasureIndicesSelected so that they appear in the right order of location in a section treasureIndicesSelected.sort() # Select a random list of numSpawnPoints spawnPointIndices from the spawnPointIndicesPool spawnPointIndicesPool = [] spawnPointIndicesSelected = [] if (spawnPointsPool != None): minSpawnPoints, maxSpawnPoints = attribs[9] for i in xrange(len(spawnPointsPool)): spawnPointIndicesPool += [i] numSpawnPoints = maxSpawnPoints * ToonBlitzGlobals.PercentMaxSpawnPoints[ szId] / 100 numSpawnPoints = max(numSpawnPoints, minSpawnPoints) for i in xrange(int(numSpawnPoints)): if (len(spawnPointIndicesPool) == 0): break spawnPoint = random.choice(spawnPointIndicesPool) spawnPointIndicesSelected.append(spawnPoint) spawnPointIndicesPool.remove(spawnPoint) # Sort the spawnPoints in a section so that they appear in the right order in the section spawnPointIndicesSelected.sort() # Select a random list of numStompers stomperIndices from the stomperIndicesPool stomperIndicesPool = [] stomperIndicesSelected = [] if (stompersPool != None): minStompers, maxStompers = attribs[10] for i in xrange(len(stompersPool)): stomperIndicesPool += [i] numStompers = maxStompers * ToonBlitzGlobals.PercentMaxStompers[ szId] / 100 numStompers = max(numStompers, minStompers) for i in xrange(int(numStompers)): if (len(stomperIndicesPool) == 0): break stomper = random.choice(stomperIndicesPool) stomperIndicesSelected.append(stomper) stomperIndicesPool.remove(stomper) # Sort the indices in stomperIndicesSelected so that they appear in the right order of location in a section stomperIndicesSelected.sort() # Change these attribs and make a tuples: (sectionIndex, enemyIndicesSelected, treasureIndicesSelected, spawnPointIndicesPool) # The clients can take the length and the blockList from ToonBlitzGlobals.SectionTypes sctionTuple = (sectionIndex, enemyIndicesSelected, treasureIndicesSelected, spawnPointIndicesSelected, stomperIndicesSelected) self.sectionsSelected.append(sctionTuple) # self.sectionsSelected is the finalised list of sections along with enemyIndices, treasureIndices, spawnPointIndices of each section. def getSectionsSelected(self): return self.sectionsSelected