from otp.ai.AIBaseGlobal import * from panda3d.core import * from panda3d.toontown import * from direct.distributed import DistributedObjectAI from direct.directnotify import DirectNotifyGlobal from toontown.building import TutorialBuildingAI from toontown.building import TutorialHQBuildingAI from . import SuitPlannerTutorialAI from toontown.toonbase import ToontownBattleGlobals from toontown.toon import NPCToons from toontown.ai import BlackCatHolidayMgrAI from toontown.ai import DistributedBlackCatMgrAI class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI): notify = DirectNotifyGlobal.directNotify.newCategory("TutorialManagerAI") # how many seconds do we wait for the toon to appear on AI before we # nuke his skip tutorial request WaitTimeForSkipTutorial = 5.0 def __init__(self, air): DistributedObjectAI.DistributedObjectAI.__init__(self, air) # This is a dictionary of all the players who are currently in # tutorials. We need to create things when someone requests # a tutorial, and destroy them when they leave. self.playerDict = {} # There are only two blocks in the tutorial. One for the gag shop # building, and one for the Toon HQ. If there aren't, something # is wrong. self.dnaStore = DNAStorage() dnaFile = simbase.air.lookupDNAFileName("tutorial_street.dna") self.air.loadDNAFileAI(self.dnaStore, dnaFile) numBlocks = self.dnaStore.getNumBlockNumbers() assert numBlocks == 2 # Assumption: the only block that isn't an HQ is the gag shop block. self.hqBlock = None self.gagBlock = None for blockIndex in range (0, numBlocks): blockNumber = self.dnaStore.getBlockNumberAt(blockIndex) buildingType = self.dnaStore.getBlockBuildingType(blockNumber) if (buildingType == 'hq'): self.hqBlock = blockNumber else: self.gagBlock = blockNumber assert self.hqBlock and self.gagBlock # key is avId, value is real time when the request was made self.avIdsRequestingSkip = {} self.accept("avatarEntered", self.waitingToonEntered ) return None def requestTutorial(self): # TODO: possible security breach: what if client is repeatedly # requesting tutorial? can client request tutorial from playground? # can client request tutorial if hp is at least 16? How do we # handle these cases? avId = self.air.getAvatarIdFromSender() # Handle unexpected exits self.acceptOnce(self.air.getAvatarExitEvent(avId), self.__handleUnexpectedExit, extraArgs=[avId]) # allocate tutorial objects and zones zoneDict = self.__createTutorial(avId) # Tell the player to enter the zone self.d_enterTutorial(avId, zoneDict["branchZone"], zoneDict["streetZone"], zoneDict["shopZone"], zoneDict["hqZone"] ) self.air.writeServerEvent('startedTutorial', avId, '') def toonArrived(self): avId = self.air.getAvatarIdFromSender() # Make sure the avatar exists av = self.air.doId2do.get(avId) # Clear out the avatar's quests, hp, inventory, and everything else in case # he made it half way through the tutorial last time. if av: # No quests av.b_setQuests([]) av.b_setQuestHistory([]) av.b_setRewardHistory(0, []) av.b_setQuestCarryLimit(1) # Starting HP av.b_setMaxHp(15) av.b_setHp(15) # No exp av.experience.zeroOutExp() av.d_setExperience(av.experience.makeNetString()) # One cupcake and one squirting flower av.inventory.zeroInv() av.inventory.addItem(ToontownBattleGlobals.THROW_TRACK, 0) av.inventory.addItem(ToontownBattleGlobals.SQUIRT_TRACK, 0) av.d_setInventory(av.inventory.makeNetString()) # No cogs defeated av.b_setCogStatus([1] * 32) av.b_setCogCount([0] * 32) return def allDone(self): avId = self.air.getAvatarIdFromSender() # No need to worry further about unexpected exits self.ignore(self.air.getAvatarExitEvent(avId)) # Make sure the avatar exists av = self.air.doId2do.get(avId) if av: self.air.writeServerEvent('finishedTutorial', avId, '') av.b_setTutorialAck(1) self.__destroyTutorial(avId) else: self.notify.warning( "Toon " + str(avId) + " isn't here, but just finished a tutorial. " + "I will ignore this." ) return def __createTutorial(self, avId): if self.playerDict.get(avId): self.notify.warning(str(avId) + " is already in the playerDict!") branchZone = self.air.allocateZone() streetZone = self.air.allocateZone() shopZone = self.air.allocateZone() hqZone = self.air.allocateZone() # Create a building object building = TutorialBuildingAI.TutorialBuildingAI(self.air, streetZone, shopZone, self.gagBlock) # Create an HQ object hqBuilding = TutorialHQBuildingAI.TutorialHQBuildingAI(self.air, streetZone, hqZone, self.hqBlock) def battleOverCallback(zoneId): hqBuilding.battleOverCallback() building.battleOverCallback() # Create a suit planner suitPlanner = SuitPlannerTutorialAI.SuitPlannerTutorialAI( self.air, streetZone, battleOverCallback) # Create the NPC blocking the tunnel to the playground blockerNPC = NPCToons.createNPC(self.air, 20001, NPCToons.NPCToonDict[20001], streetZone, questCallback=self.__handleBlockDone) blockerNPC.setTutorial(1) # is the black cat holiday enabled? blackCatMgr = None if bboard.has(BlackCatHolidayMgrAI.BlackCatHolidayMgrAI.PostName): blackCatMgr = DistributedBlackCatMgrAI.DistributedBlackCatMgrAI( self.air, avId) blackCatMgr.generateWithRequired(streetZone) zoneDict={"branchZone" : branchZone, "streetZone" : streetZone, "shopZone" : shopZone, "hqZone" : hqZone, "building" : building, "hqBuilding" : hqBuilding, "suitPlanner" : suitPlanner, "blockerNPC" : blockerNPC, "blackCatMgr" : blackCatMgr, } self.playerDict[avId] = zoneDict return zoneDict def __handleBlockDone(self): return None def __destroyTutorial(self, avId): zoneDict = self.playerDict.get(avId) if zoneDict: zoneDict["building"].cleanup() zoneDict["hqBuilding"].cleanup() zoneDict["blockerNPC"].requestDelete() if zoneDict["blackCatMgr"]: zoneDict["blackCatMgr"].requestDelete() self.air.deallocateZone(zoneDict["branchZone"]) self.air.deallocateZone(zoneDict["streetZone"]) self.air.deallocateZone(zoneDict["shopZone"]) self.air.deallocateZone(zoneDict["hqZone"]) zoneDict["suitPlanner"].cleanup() del self.playerDict[avId] else: self.notify.warning("Tried to deallocate zones for " + str(avId) + " but none were present in playerDict.") def rejectTutorial(self): avId = self.air.getAvatarIdFromSender() # Make sure the avatar exists av = self.air.doId2do.get(avId) if av: # Acknowlege that the player has seen a tutorial self.air.writeServerEvent('finishedTutorial', avId, '') av.b_setTutorialAck(1) self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [1]) else: self.notify.warning( "Toon " + str(avId) + " isn't here, but just rejected a tutorial. " + "I will ignore this." ) return def respondToSkipTutorial(self, avId, av): """Reply to the client if we let him skip the tutorial.""" self.notify.debugStateCall(self) assert avId assert av response = 1 if av: if av.tutorialAck: self.air.writeServerEvent('suspicious', avId, 'requesting skip tutorial, but tutorialAck is 1') response = 0 if av and response: # Acknowlege that the player has seen a tutorial self.air.writeServerEvent('skippedTutorial', avId, '') av.b_setTutorialAck(1) # these values were taken by running a real tutorial self.air.questManager.assignQuest(avId, 20000, 101, 100, 1000, 1 ) self.air.questManager.completeAllQuestsMagically(av) av.removeQuest(101) self.air.questManager.assignQuest(avId, 1000, 110, 2, 1000, 0 ) self.air.questManager.completeAllQuestsMagically(av) # do whatever needs to be done to make his quest state good elif av: self.notify.debug("%s requestedSkipTutorial, but tutorialAck is 1") else: response = 0 self.notify.warning( "Toon " + str(avId) + " isn't here, but requested to skip tutorial. " + "I will ignore this." ) self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [response]) return def waitingToonEntered(self, av): """Check if the avatar is someone who's requested to skip, then proceed accordingly.""" avId = av.doId if avId in self.avIdsRequestingSkip: requestTime = self.avIdsRequestingSkip[avId] curTime = globalClock.getFrameTime() if (curTime - requestTime) <= self.WaitTimeForSkipTutorial: self.respondToSkipTutorial(avId, av) else: self.notify.warning("waited too long for toon %d responding no to skip tutorial request" % avId) self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [0]) del self.avIdsRequestingSkip[avId] self.removeTask("skipTutorialToon-%d" % avId) def waitForToonToEnter(self,avId): """Mark our toon as requesting to skip, and start a task to timeout for it.""" self.notify.debugStateCall(self) self.avIdsRequestingSkip[avId] = globalClock.getFrameTime() self.doMethodLater(self.WaitTimeForSkipTutorial, self.didNotGetToon, "skipTutorialToon-%d" % avId, [avId]) def didNotGetToon(self, avId): """Just say no since the AI didn't get it.""" self.notify.debugStateCall(self) if avId in self.avIdsRequestingSkip: del self.avIdsRequestingSkip[avId] self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [0]) return Task.done def requestSkipTutorial(self): """We are requesting to skip tutorial, add other quest history to be consistent.""" self.notify.debugStateCall(self) avId = self.air.getAvatarIdFromSender() # Make sure the avatar exists av = self.air.doId2do.get(avId) if av: self.respondToSkipTutorial(avId,av) else: self.waitForToonToEnter(avId) def d_enterTutorial(self, avId, branchZone, streetZone, shopZone, hqZone): self.sendUpdateToAvatarId(avId, "enterTutorial", [branchZone, streetZone, shopZone, hqZone]) return def __handleUnexpectedExit(self, avId): self.notify.warning("Avatar: " + str(avId) + " has exited unexpectedly") self.__destroyTutorial(avId) return