tutorial: Cleanup and fix floating Flunky in battle

The following has been cleaned up:
- Import statements
- Whitespace
- Small unnecessary comments

Futhermore, I have added Anesidora documentation to the Tutorial files that did not have them.
This commit is contained in:
Leo 2026-01-31 17:22:21 -05:00
parent 4d566e1da9
commit e22fee8fa8
10 changed files with 251 additions and 184 deletions

View File

@ -1,26 +1,19 @@
from toontown.toonbase.ToonBaseGlobal import *
from pandac.PandaModules import *
import random
from panda3d.core import Point3, Vec3
from panda3d.toontown import DNADoor
from direct.interval.IntervalGlobal import *
from direct.distributed.ClockDelta import *
from toontown.toonbase import ToontownGlobals
from . import ToonInterior
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObject
import random
from . import ToonInteriorColors
from toontown.hood import ZoneUtil
from toontown.char import Char
from toontown.suit import SuitDNA
from toontown.suit import Suit
from toontown.quest import QuestParser
class DistributedTutorialInterior(DistributedObject.DistributedObject):
def __init__(self, cr):
DistributedObject.DistributedObject.__init__(self, cr)
def generate(self):
DistributedObject.DistributedObject.generate(self)
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTutorialInterior')
def announceGenerate(self):
DistributedObject.DistributedObject.announceGenerate(self)
@ -39,104 +32,181 @@ class DistributedTutorialInterior(DistributedObject.DistributedObject):
del self.suitWalkTrack
self.suit.delete()
del self.suit
self.ignore('enterTutotialInterior')
self.ignore("enterTutotialInterior")
DistributedObject.DistributedObject.disable(self)
def delete(self):
DistributedObject.DistributedObject.delete(self)
def randomDNAItem(self, category, findFunc):
codeCount = self.dnaStore.getNumCatalogCodes(category)
index = self.randomGenerator.randint(0, codeCount - 1)
code = self.dnaStore.getCatalogCode(category, index)
# findFunc will probably be findNode or findTexture
return findFunc(code)
def replaceRandomInModel(self, model):
baseTag = 'random_'
npc = model.findAllMatches('**/' + baseTag + '???_*')
"""Replace named nodes with random items.
Here are the name Here is
prefixes that are what they
affected: do:
random_mox_ change the Model Only.
random_mcx_ change the Model and the Color.
random_mrx_ change the Model and Recurse.
random_tox_ change the Texture Only.
random_tcx_ change the Texture and the Color.
x is simply a uniquifying integer because Multigen will not
let you have multiple nodes with the same name
"""
baseTag = "random_"
npc = model.findAllMatches("**/" + baseTag + "???_*")
for i in range(npc.getNumPaths()):
np = npc.getPath(i)
name = np.getName()
b = len(baseTag)
category = name[b + 4:]
key1 = name[b]
key2 = name[b + 1]
if key1 == 'm':
assert (key1 in ["m", "t"])
assert (key2 in ["c", "o", "r"])
if key1 == "m":
# ...model.
model = self.randomDNAItem(category, self.dnaStore.findNode)
assert (not model.isEmpty())
newNP = model.copyTo(np)
# room has collisions already: remove collisions from models
c = render.findAllMatches('**/collision')
c.stash()
if key2 == 'r':
if key2 == "r":
self.replaceRandomInModel(newNP)
elif key1 == 't':
elif key1 == "t":
# ...texture.
texture = self.randomDNAItem(category, self.dnaStore.findTexture)
assert (texture)
np.setTexture(texture, 100)
newNP = np
if key2 == 'c':
if category == 'TI_wallpaper' or category == 'TI_wallpaper_border':
if key2 == "c":
if (category == "TI_wallpaper") or (category == "TI_wallpaper_border"):
self.randomGenerator.seed(self.zoneId)
newNP.setColorScale(self.randomGenerator.choice(self.colors[category]))
newNP.setColorScale(
self.randomGenerator.choice(self.colors[category]))
else:
newNP.setColorScale(self.randomGenerator.choice(self.colors[category]))
newNP.setColorScale(
self.randomGenerator.choice(self.colors[category]))
def setup(self):
self.dnaStore = base.cr.playGame.dnaStore
self.randomGenerator = random.Random()
# The math here is a little arbitrary. I'm trying to get a
# substantially different seed for each zondId, even on the
# same street. But we don't want to weigh to much on the
# block number, because we want the same block number on
# different streets to be different.
# Here we use the block number and a little of the branchId:
# seedX=self.zoneId&0x00ff
# Here we're using only the branchId:
# seedY=self.zoneId/100
# Here we're using only the block number:
# seedZ=256-int(self.block)
self.randomGenerator.seed(self.zoneId)
self.interior = loader.loadModel('phase_3.5/models/modules/toon_interior_tutorial')
self.interior.reparentTo(render)
dnaStore = DNAStorage()
node = loader.loadDNAFile(self.cr.playGame.hood.dnaStore, 'phase_3.5/dna/tutorial_street.dna')
self.street = render.attachNewNode(node)
self.interior = loader.loadModel("phase_3.5/models/modules/toon_interior_tutorial")
self.interior.reparentTo(base.render)
node = loader.loadDNAFile(self.cr.playGame.hood.dnaStore, "phase_3.5/dna/tutorial_street.dna")
self.street = base.render.attachNewNode(node)
self.street.flattenMedium()
self.street.setPosHpr(-17, 42, -0.5, 180, 0, 0)
self.street.find('**/tb2:toon_landmark_TT_A1_DNARoot').stash()
self.street.find('**/tb1:toon_landmark_hqTT_DNARoot/**/door_flat_0').stash()
self.street.findAllMatches('**/+CollisionNode').stash()
self.skyFile = 'phase_3.5/models/props/TT_sky'
# Get rid of the building we are in
self.street.find("**/tb2:toon_landmark_TT_A1_DNARoot").stash()
# Get rid of the flashing doors on the HQ building
self.street.find("**/tb1:toon_landmark_hqTT_DNARoot/**/door_flat_0").stash()
# Get rid of collisions because we do not need them and they get in the way
self.street.findAllMatches("**/+CollisionNode").stash()
self.skyFile = "phase_3.5/models/props/TT_sky"
self.sky = loader.loadModel(self.skyFile)
self.sky.setScale(0.8)
self.sky.reparentTo(render)
# Parent the sky to our camera, the task will counter rotate it
self.sky.reparentTo(base.render)
# Turn off depth tests on the sky because as the cloud layers interpenetrate
# we do not want to see the polys cutoff. Since there is nothing behing them
# we can get away with this.
self.sky.setDepthTest(0)
self.sky.setDepthWrite(0)
self.sky.setBin('background', 100)
self.sky.find('**/Sky').reparentTo(self.sky, -1)
self.sky.setBin("background", 100)
# Make sure they are drawn in the correct order in the hierarchy
# The sky should be first, then the clouds
self.sky.find("**/Sky").reparentTo(self.sky, -1)
# Load a color dictionary for this hood:
hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
self.colors = ToonInteriorColors.colors[hoodId]
# Replace all the "random_xxx_" nodes:
self.replaceRandomInModel(self.interior)
doorModelName = 'door_double_round_ul'
if doorModelName[-1:] == 'r':
doorModelName = doorModelName[:-1] + 'l'
# Door:
doorModelName = "door_double_round_ul" # hack zzzzzzz
# Switch leaning of the door:
if doorModelName[-1:] == "r":
doorModelName = doorModelName[:-1] + "l"
else:
doorModelName = doorModelName[:-1] + 'r'
doorModelName = doorModelName[:-1] + "r"
door = self.dnaStore.findNode(doorModelName)
door_origin = render.find('**/door_origin;+s')
# Determine where should we put the door:
door_origin = base.render.find("**/door_origin;+s")
doorNP = door.copyTo(door_origin)
assert (not doorNP.isEmpty())
assert (not door_origin.isEmpty())
# The rooms are too small for doors:
door_origin.setScale(0.8, 0.8, 0.8)
# Move the origin away from the wall so it does not shimmer
# We do this instead of decals
door_origin.setPos(door_origin, 0, -0.025, 0)
color = self.randomGenerator.choice(self.colors['TI_door'])
DNADoor.setupDoor(doorNP, self.interior, door_origin, self.dnaStore, str(self.block), color)
doorFrame = doorNP.find('door_*_flat')
color = self.randomGenerator.choice(self.colors["TI_door"])
DNADoor.setupDoor(doorNP,
self.interior, door_origin,
self.dnaStore,
str(self.block), color)
# Setting the wallpaper texture with a priority overrides
# the door texture, if it's decalled. So, we're going to
# move it out from the decal, and float it in front of
# the wall:
doorFrame = doorNP.find("door_*_flat")
doorFrame.wrtReparentTo(self.interior)
doorFrame.setColor(color)
del self.colors
del self.dnaStore
del self.randomGenerator
# Get rid of any transitions and extra nodes
self.interior.flattenMedium()
npcOrigin = self.interior.find('**/npc_origin_' + repr((self.npc.posIndex)))
# Ok, this is a hack, but I'm tired of this freakin tutorial.
# The problem is the interior must be created first so the npc can find the origin
# of where to stand, but in this case the npc must be created first so the tutorial
# can get a handle on him. Instead, I'll let the npc be created first which means
# he will not find his origin. We'll just do that work here again.
npcOrigin = self.interior.find(f"**/npc_origin_{self.npc.posIndex}")
# Now he's no longer parented to render, but no one minds.
if not npcOrigin.isEmpty():
self.npc.reparentTo(npcOrigin)
self.npc.clearMat()
self.createSuit()
self.mickeyMovie = QuestParser.NPCMoviePlayer('tutorial_mickey', base.localAvatar, self.npc)
self.mickeyMovie = QuestParser.NPCMoviePlayer("tutorial_mickey", base.localAvatar, self.npc)
place = base.cr.playGame.getPlace()
if place and hasattr(place, 'fsm') and place.fsm.getCurrentState().getName():
self.notify.info('Tutorial movie: Place ready.')
self.playMovie()
else:
self.notify.info('Tutorial movie: Waiting for place=%s, has fsm=%s' % (place, hasattr(place, 'fsm')))
self.notify.info(f'Tutorial movie: Waiting for place={place}, has fsm={hasattr(place, "fsm")}')
if hasattr(place, 'fsm'):
self.notify.info('Tutorial movie: place state=%s' % place.fsm.getCurrentState().getName())
self.notify.info(f'Tutorial movie: place state={place.fsm.getCurrentState().getName()}')
self.acceptOnce('enterTutorialInterior', self.playMovie)
def playMovie(self):
@ -144,6 +214,7 @@ class DistributedTutorialInterior(DistributedObject.DistributedObject):
self.mickeyMovie.play()
def createSuit(self):
# Create a suit
self.suit = Suit.Suit()
suitDNA = SuitDNA.SuitDNA()
suitDNA.newSuit('f')
@ -151,7 +222,19 @@ class DistributedTutorialInterior(DistributedObject.DistributedObject):
self.suit.loop('neutral')
self.suit.setPosHpr(-20, 8, 0, 0, 0, 0)
self.suit.reparentTo(self.interior)
self.suitWalkTrack = Sequence(self.suit.hprInterval(0.1, Vec3(0, 0, 0)), Func(self.suit.loop, 'walk'), self.suit.posInterval(2, Point3(-20, 20, 0)), Func(self.suit.loop, 'neutral'), Wait(1.0), self.suit.hprInterval(0.1, Vec3(180, 0, 0)), Func(self.suit.loop, 'walk'), self.suit.posInterval(2, Point3(-20, 10, 0)), Func(self.suit.loop, 'neutral'), Wait(1.0))
self.suitWalkTrack = Sequence(
self.suit.hprInterval(0.1, Vec3(0, 0, 0)),
Func(self.suit.loop, 'walk'),
self.suit.posInterval(2, Point3(-20, 20, 0)),
Func(self.suit.loop, 'neutral'),
Wait(1.0),
self.suit.hprInterval(0.1, Vec3(180, 0, 0)),
Func(self.suit.loop, 'walk'),
self.suit.posInterval(2, Point3(-20, 10, 0)),
Func(self.suit.loop, 'neutral'),
Wait(1.0),
)
self.suitWalkTrack.loop()
def setZoneIdAndBlock(self, zoneId, block):

View File

@ -1,11 +1,8 @@
from toontown.toonbase.ToontownGlobals import *
from otp.ai.AIBaseGlobal import *
from direct.distributed.ClockDelta import *
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObjectAI
from toontown.toon import NPCToons
class DistributedTutorialInteriorAI(DistributedObjectAI.DistributedObjectAI):
if __debug__:
@ -13,14 +10,12 @@ class DistributedTutorialInteriorAI(DistributedObjectAI.DistributedObjectAI):
def __init__(self, block, air, zoneId, building, npcId):
"""blockNumber: the landmark building number (from the name)"""
#self.air=air
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
self.block=block
self.zoneId=zoneId
self.building=building
self.block = block
self.zoneId = zoneId
self.building = building
self.tutorialNpcId = npcId
# Make any npcs that may be in this interior zone
# If there are none specified, this will just be an empty list
self.npcs = NPCToons.createNpcsInZone(air, zoneId)
@ -32,9 +27,9 @@ class DistributedTutorialInteriorAI(DistributedObjectAI.DistributedObjectAI):
del self.npcs
del self.building
DistributedObjectAI.DistributedObjectAI.delete(self)
def getZoneIdAndBlock(self):
return [self.zoneId, self.block]
def getTutorialNpcId(self):
return self.tutorialNpcId

View File

@ -1,5 +1,3 @@
from pandac.PandaModules import *
from direct.directnotify import DirectNotifyGlobal
from toontown.building import DistributedDoorAI
from toontown.building import DistributedHQInteriorAI
from toontown.building import FADoorCodes
@ -8,17 +6,19 @@ from toontown.toon import NPCToons
from toontown.quest import Quests
from toontown.toonbase import TTLocalizer
# This is not a distributed class... It just owns and manages some distributed
# classes.
class TutorialHQBuildingAI:
def __init__(self, air, exteriorZone, interiorZone, blockNumber):
# While this is not a distributed object, it needs to know about
# the repository.
self.air = air
self.exteriorZone = exteriorZone
self.interiorZone = interiorZone
self.setup(blockNumber)
def cleanup(self):
@ -34,17 +34,17 @@ class TutorialHQBuildingAI:
del self.insideDoor0
self.insideDoor1.requestDelete()
del self.insideDoor1
return
def setup(self, blockNumber):
# The interior
self.interior=DistributedHQInteriorAI.DistributedHQInteriorAI(
self.interior = DistributedHQInteriorAI.DistributedHQInteriorAI(
blockNumber, self.air, self.interiorZone)
# We do not use a standard npc toon here becuase these npcs are created on
# the fly for as many tutorials as we need. The interior zone is not known
# until the ai allocates a zone, so we fabricate the description here.
desc = (self.interiorZone, TTLocalizer.TutorialHQOfficerName, ('dls', 'ms', 'm', 'm', 6,0,6,6,0,10,0,10,2,9), "m", 1, 0)
desc = (self.interiorZone, TTLocalizer.TutorialHQOfficerName,
('dls', 'ms', 'm', 'm', 6, 0, 6, 6, 0, 10, 0, 10, 2, 9), "m", 1, 0)
self.npc = NPCToons.createNPC(self.air, Quests.ToonHQ, desc,
self.interiorZone,
questCallback=self.unlockInsideDoor1)
@ -53,24 +53,24 @@ class TutorialHQBuildingAI:
self.interior.generateWithRequired(self.interiorZone)
# Outside door 0. Locked til you defeat the Flunky:
door0=DistributedDoorAI.DistributedDoorAI(
door0 = DistributedDoorAI.DistributedDoorAI(
self.air, blockNumber, DoorTypes.EXT_HQ,
doorIndex=0,
lockValue=FADoorCodes.DEFEAT_FLUNKY_HQ)
# Outside door 1. Always locked.
door1=DistributedDoorAI.DistributedDoorAI(
door1 = DistributedDoorAI.DistributedDoorAI(
self.air, blockNumber, DoorTypes.EXT_HQ,
doorIndex=1,
lockValue=FADoorCodes.GO_TO_PLAYGROUND)
# Inside door 0. Always locked, but the message will change.
insideDoor0=DistributedDoorAI.DistributedDoorAI(
insideDoor0 = DistributedDoorAI.DistributedDoorAI(
self.air,
blockNumber,
DoorTypes.INT_HQ,
doorIndex=0,
lockValue=FADoorCodes.TALK_TO_HQ)
# Inside door 1. Locked til you get your HQ reward.
insideDoor1=DistributedDoorAI.DistributedDoorAI(
insideDoor1 = DistributedDoorAI.DistributedDoorAI(
self.air,
blockNumber,
DoorTypes.INT_HQ,
@ -82,10 +82,10 @@ class TutorialHQBuildingAI:
door1.setOtherDoor(insideDoor1)
insideDoor1.setOtherDoor(door1)
# Put them in the right zones
door0.zoneId=self.exteriorZone
door1.zoneId=self.exteriorZone
insideDoor0.zoneId=self.interiorZone
insideDoor1.zoneId=self.interiorZone
door0.zoneId = self.exteriorZone
door1.zoneId = self.exteriorZone
insideDoor0.zoneId = self.interiorZone
insideDoor1.zoneId = self.interiorZone
# Now that they both now about each other, generate them:
door0.generateWithRequired(self.exteriorZone)
door1.generateWithRequired(self.exteriorZone)
@ -96,14 +96,13 @@ class TutorialHQBuildingAI:
insideDoor0.sendUpdate("setDoorIndex", [insideDoor0.getDoorIndex()])
insideDoor1.sendUpdate("setDoorIndex", [insideDoor1.getDoorIndex()])
# keep track of them:
self.door0=door0
self.door1=door1
self.insideDoor0=insideDoor0
self.insideDoor1=insideDoor1
self.door0 = door0
self.door1 = door1
self.insideDoor0 = insideDoor0
self.insideDoor1 = insideDoor1
# hide the periscope
self.interior.setTutorial(1)
return
def unlockDoor(self, door):
door.setDoorLock(FADoorCodes.UNLOCKED)

View File

@ -1,11 +1,15 @@
from toontown.battle import DistributedBattle
from direct.directnotify import DirectNotifyGlobal
from toontown.battle import DistributedBattle
class DistributedBattleTutorial(DistributedBattle.DistributedBattle):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleTutorial')
def startTimer(self, ts = 0):
def startTimer(self, ts=0):
# Instead of starting the countdown,
# hide the clock!
self.townBattle.timer.hide()
def playReward(self, ts):
self.movie.playTutorialReward(ts, self.uniqueName('reward'), self.handleRewardDone)
self.movie.playTutorialReward(ts, self.uniqueName('reward'),
self.handleRewardDone)

View File

@ -1,15 +1,12 @@
from toontown.battle import DistributedBattleAI
from toontown.battle import DistributedBattleBaseAI
from direct.directnotify import DirectNotifyGlobal
from toontown.battle import DistributedBattleAI
class DistributedBattleTutorialAI(DistributedBattleAI.DistributedBattleAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleTutorialAI')
def __init__(self, air, battleMgr, pos, suit, toonId, zoneId,
finishCallback=None, maxSuits=4, interactivePropTrackBonus = -1):
"""__init__(air, battleMgr, pos, suit, toonId, zoneId,
finishCallback, maxSuits)
"""
finishCallback=None, maxSuits=4, interactivePropTrackBonus=-1):
DistributedBattleAI.DistributedBattleAI.__init__(
self, air, battleMgr, pos, suit, toonId, zoneId,
finishCallback, maxSuits, tutorialFlag=1)
@ -17,7 +14,3 @@ class DistributedBattleTutorialAI(DistributedBattleAI.DistributedBattleAI):
# There is no timer in the tutorial... The reward movie is random length.
def startRewardTimer(self):
pass
#def handleRewardDone(self):
# DistributedBattleAI.DistributedBattleAI.handleRewardDone(self)

View File

@ -2,12 +2,12 @@
which handles management of the suit you will fight during the
tutorial."""
from otp.ai.AIBaseGlobal import *
from panda3d.core import Vec3
from direct.directnotify import DirectNotifyGlobal
from toontown.suit import DistributedTutorialSuitAI
from toontown.tutorial import TutorialBattleManagerAI
class SuitPlannerTutorialAI:
"""
SuitPlannerTutorialAI: manages the single suit that you fight during
@ -44,13 +44,13 @@ class SuitPlannerTutorialAI:
self.suit.requestDelete()
self.suit = None
if self.battle:
#self.battle.requestDelete()
#RAU made to kill the mem leak when you close the window in the middle of the battle tutorial
# self.battle.requestDelete()
# RAU made to kill the mem leak when you close the window in the middle of the battle tutorial
cellId = self.battle.battleCellId
battleMgr = self.battle.battleMgr
if cellId in battleMgr.cellId2battle:
battleMgr.destroy(self.battle)
self.battle = None
def getDoId(self):
@ -61,16 +61,14 @@ class SuitPlannerTutorialAI:
return 0
def requestBattle(self, zoneId, suit, toonId):
# 70, 20, 0 is a battle cell position that I just made up.
# 35, 20, -0.5 is a battle cell position that I just made up.
self.battle = self.battleMgr.newBattle(
zoneId, zoneId, Vec3(35, 20, 0),
zoneId, zoneId, Vec3(35, 20, -0.5),
suit, toonId,
finishCallback=self.battleOverCallback)
return 1
return True
def removeSuit(self, suit):
# Get rid of the suit.
suit.requestDelete()
self.suit = None

View File

@ -1,13 +1,11 @@
from toontown.battle import BattleManagerAI
from direct.directnotify import DirectNotifyGlobal
from toontown.battle import BattleManagerAI
from toontown.tutorial import DistributedBattleTutorialAI
class TutorialBattleManagerAI(BattleManagerAI.BattleManagerAI):
class TutorialBattleManagerAI(BattleManagerAI.BattleManagerAI):
notify = DirectNotifyGlobal.directNotify.newCategory('TutorialBattleManagerAI')
def __init__(self, air):
BattleManagerAI.BattleManagerAI.__init__(self, air)
self.battleConstructor = DistributedBattleTutorialAI.DistributedBattleTutorialAI

View File

@ -1,25 +1,26 @@
from pandac.PandaModules import *
from toontown.toontowngui import TTDialog
from toontown.toonbase import TTLocalizer
from toontown.toontowngui import TTDialog
class TutorialForceAcknowledge:
def __init__(self, doneEvent):
self.doneEvent = doneEvent
self.dialog = None
return
def enter(self):
base.localAvatar.loop('neutral')
# Make the toon stop running.
base.localAvatar.loop("neutral")
self.doneStatus = {'mode': 'incomplete'}
msg = TTLocalizer.TutorialForceAcknowledgeMessage
self.dialog = TTDialog.TTDialog(text=msg, command=self.handleOk, style=TTDialog.Acknowledge)
self.dialog = TTDialog.TTDialog(text=msg,
command=self.handleOk,
style=TTDialog.Acknowledge)
def exit(self):
if self.dialog:
self.dialog.cleanup()
self.dialog = None
return
def handleOk(self, value):
messenger.send(self.doneEvent, [self.doneStatus])

View File

@ -1,45 +1,57 @@
from pandac.PandaModules import *
from direct.distributed import DistributedObject
from direct.directnotify import DirectNotifyGlobal
from toontown.hood import ZoneUtil
class TutorialManager(DistributedObject.DistributedObject):
notify = DirectNotifyGlobal.directNotify.newCategory('TutorialManager')
neverDisable = 1
def __init__(self, cr):
DistributedObject.DistributedObject.__init__(self, cr)
class TutorialManager(DistributedObject.DistributedObject):
notify = DirectNotifyGlobal.directNotify.newCategory("TutorialManager")
neverDisable = 1
def generate(self):
DistributedObject.DistributedObject.generate(self)
messenger.send('tmGenerate')
self.accept('requestTutorial', self.d_requestTutorial)
self.accept('requestSkipTutorial', self.d_requestSkipTutorial)
self.accept('rejectTutorial', self.d_rejectTutorial)
# Let the cr know we have arrived.
messenger.send("tmGenerate")
# Wait for a tutorial request or rejection.
self.accept("requestTutorial", self.d_requestTutorial)
self.accept("requestSkipTutorial", self.d_requestSkipTutorial)
self.accept("rejectTutorial", self.d_rejectTutorial)
def disable(self):
self.ignoreAll()
# In case we fell asleep in the tutorial
ZoneUtil.overrideOff()
DistributedObject.DistributedObject.disable(self)
def d_requestTutorial(self):
self.sendUpdate('requestTutorial', [])
self.sendUpdate("requestTutorial", [])
def d_rejectTutorial(self):
self.sendUpdate('rejectTutorial', [])
self.sendUpdate("rejectTutorial", [])
def d_requestSkipTutorial(self):
self.sendUpdate('requestSkipTutorial', [])
self.sendUpdate("requestSkipTutorial", [])
def skipTutorialResponse(self, allOk):
messenger.send('skipTutorialAnswered', [allOk])
"""Handle AI responding to our skip tutorial request."""
messenger.send("skipTutorialAnswered", [allOk])
def enterTutorial(self, branchZone, streetZone, shopZone, hqZone):
base.localAvatar.cantLeaveGame = 1
ZoneUtil.overrideOn(branch=branchZone, exteriorList=[streetZone], interiorList=[shopZone, hqZone])
messenger.send('startTutorial', [shopZone])
self.acceptOnce('stopTutorial', self.__handleStopTutorial)
self.acceptOnce('toonArrivedTutorial', self.d_toonArrived)
# Override the ZoneUtil
ZoneUtil.overrideOn(branch=branchZone,
exteriorList=[streetZone],
interiorList=[shopZone, hqZone])
# We start the tutorial in the gag shop.
messenger.send("startTutorial", [shopZone])
# Add a hook on the tutorialDone event, which will get
# thrown when we are leaving the tutorial (by the handleEnterTunnel
# function in TutorialStreet.py
self.acceptOnce("stopTutorial", self.__handleStopTutorial)
# Add a hook that the Tutorial hood will send when the toon
# is fully in a zone. This lets the AI know it is clear to
# reset the toon properties in preparation for the tutorial
# (in case they bailed halfway through before)
self.acceptOnce("toonArrivedTutorial", self.d_toonArrived)
def __handleStopTutorial(self):
base.localAvatar.cantLeaveGame = 0
@ -47,7 +59,7 @@ class TutorialManager(DistributedObject.DistributedObject):
ZoneUtil.overrideOff()
def d_allDone(self):
self.sendUpdate('allDone', [])
self.sendUpdate("allDone", [])
def d_toonArrived(self):
self.sendUpdate('toonArrived', [])
self.sendUpdate("toonArrived", [])

View File

@ -1,5 +1,4 @@
from otp.ai.AIBaseGlobal import *
from pandac.PandaModules import *
from panda3d.toontown import DNAStorage
from direct.distributed import DistributedObjectAI
from direct.directnotify import DirectNotifyGlobal
from toontown.building import TutorialBuildingAI
@ -7,10 +6,10 @@ from toontown.building import TutorialHQBuildingAI
from toontown.tutorial import SuitPlannerTutorialAI
from toontown.toonbase import ToontownBattleGlobals
from toontown.toon import NPCToons
from toontown.toonbase import TTLocalizer
from toontown.ai import BlackCatHolidayMgrAI
from toontown.ai import DistributedBlackCatMgrAI
class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory("TutorialManagerAI")
@ -37,21 +36,19 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
# 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):
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
self.accept("avatarEntered", self.waitingToonEntered)
def requestTutorial(self):
# TODO: possible security breach: what if client is repeatedly
@ -100,7 +97,6 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
# No cogs defeated
av.b_setCogStatus([1] * 32)
av.b_setCogCount([0] * 32)
return
def allDone(self):
avId = self.air.getAvatarIdFromSender()
@ -118,13 +114,12 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
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()
@ -143,7 +138,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
def battleOverCallback(zoneId):
hqBuilding.battleOverCallback()
building.battleOverCallback()
# Create a suit planner
suitPlanner = SuitPlannerTutorialAI.SuitPlannerTutorialAI(
self.air,
@ -151,8 +146,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
battleOverCallback)
# Create the NPC blocking the tunnel to the playground
blockerNPC = NPCToons.createNPC(self.air, 20001, NPCToons.NPCToonDict[20001], streetZone,
questCallback=self.__handleBlockDone)
blockerNPC = NPCToons.createNPC(self.air, 20001, NPCToons.NPCToonDict[20001], streetZone)
blockerNPC.setTutorial(1)
# is the black cat holiday enabled?
@ -161,23 +155,20 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
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,
}
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:
@ -213,8 +204,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
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."""
@ -239,7 +229,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
1000,
1
)
self.air.questManager.completeAllQuestsMagically(av)
av.removeQuest(101)
self.air.questManager.assignQuest(avId,
@ -253,7 +243,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
# do whatever needs to be done to make his quest state good
elif av:
self.notify.debug("%s requestedSkipTutorial, but tutorialAck is 1")
self.notify.debug(f"{avId} requestedSkipTutorial, but tutorialAck is 1")
else:
response = 0
self.notify.warning(
@ -261,31 +251,29 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
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.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("skipTutorialToon-%d" % avId)
self.removeTask(f"skipTutorialToon-{avId}")
def waitForToonToEnter(self,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])
self.doMethodLater(self.WaitTimeForSkipTutorial, self.didNotGetToon, f"skipTutorialToon-{avId}", [avId])
def didNotGetToon(self, avId):
"""Just say no since the AI didn't get it."""
@ -300,23 +288,19 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
self.notify.debugStateCall(self)
avId = self.air.getAvatarIdFromSender()
# Make sure the avatar exists
av = self.air.doId2do.get(avId)
av = self.air.doId2do.get(avId)
if av:
self.respondToSkipTutorial(avId,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