open-toontown/toontown/tutorial/TutorialManagerAI.py

307 lines
13 KiB
Python

from panda3d.toontown import DNAStorage
from direct.distributed import DistributedObjectAI
from direct.directnotify import DirectNotifyGlobal
from toontown.building import TutorialBuildingAI
from toontown.building import TutorialHQBuildingAI
from toontown.tutorial 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)
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)
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."
)
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)
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 __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."
)
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(f"{avId} 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])
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(f"waited too long for toon {avId} responding no to skip tutorial request")
self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [0])
del self.avIdsRequestingSkip[avId]
self.removeTask(f"skipTutorialToon-{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, f"skipTutorialToon-{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])
def __handleUnexpectedExit(self, avId):
self.notify.warning("Avatar: " + str(avId) +
" has exited unexpectedly")
self.__destroyTutorial(avId)