oldschool-toontown/toontown/tutorial/TutorialManagerAI.py

322 lines
No EOL
13 KiB
Python

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