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 * import random
from pandac.PandaModules import * from panda3d.core import Point3, Vec3
from panda3d.toontown import DNADoor
from direct.interval.IntervalGlobal import * 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.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObject from direct.distributed import DistributedObject
import random
from . import ToonInteriorColors from . import ToonInteriorColors
from toontown.hood import ZoneUtil from toontown.hood import ZoneUtil
from toontown.char import Char
from toontown.suit import SuitDNA from toontown.suit import SuitDNA
from toontown.suit import Suit from toontown.suit import Suit
from toontown.quest import QuestParser from toontown.quest import QuestParser
class DistributedTutorialInterior(DistributedObject.DistributedObject): class DistributedTutorialInterior(DistributedObject.DistributedObject):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTutorialInterior')
def __init__(self, cr):
DistributedObject.DistributedObject.__init__(self, cr)
def generate(self):
DistributedObject.DistributedObject.generate(self)
def announceGenerate(self): def announceGenerate(self):
DistributedObject.DistributedObject.announceGenerate(self) DistributedObject.DistributedObject.announceGenerate(self)
@ -39,104 +32,181 @@ class DistributedTutorialInterior(DistributedObject.DistributedObject):
del self.suitWalkTrack del self.suitWalkTrack
self.suit.delete() self.suit.delete()
del self.suit del self.suit
self.ignore('enterTutotialInterior') self.ignore("enterTutotialInterior")
DistributedObject.DistributedObject.disable(self) DistributedObject.DistributedObject.disable(self)
def delete(self):
DistributedObject.DistributedObject.delete(self)
def randomDNAItem(self, category, findFunc): def randomDNAItem(self, category, findFunc):
codeCount = self.dnaStore.getNumCatalogCodes(category) codeCount = self.dnaStore.getNumCatalogCodes(category)
index = self.randomGenerator.randint(0, codeCount - 1) index = self.randomGenerator.randint(0, codeCount - 1)
code = self.dnaStore.getCatalogCode(category, index) code = self.dnaStore.getCatalogCode(category, index)
# findFunc will probably be findNode or findTexture
return findFunc(code) return findFunc(code)
def replaceRandomInModel(self, model): def replaceRandomInModel(self, model):
baseTag = 'random_' """Replace named nodes with random items.
npc = model.findAllMatches('**/' + baseTag + '???_*') 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()): for i in range(npc.getNumPaths()):
np = npc.getPath(i) np = npc.getPath(i)
name = np.getName() name = np.getName()
b = len(baseTag) b = len(baseTag)
category = name[b + 4:] category = name[b + 4:]
key1 = name[b] key1 = name[b]
key2 = name[b + 1] 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) model = self.randomDNAItem(category, self.dnaStore.findNode)
assert (not model.isEmpty())
newNP = model.copyTo(np) newNP = model.copyTo(np)
# room has collisions already: remove collisions from models
c = render.findAllMatches('**/collision') c = render.findAllMatches('**/collision')
c.stash() c.stash()
if key2 == 'r': if key2 == "r":
self.replaceRandomInModel(newNP) self.replaceRandomInModel(newNP)
elif key1 == 't': elif key1 == "t":
# ...texture.
texture = self.randomDNAItem(category, self.dnaStore.findTexture) texture = self.randomDNAItem(category, self.dnaStore.findTexture)
assert (texture)
np.setTexture(texture, 100) np.setTexture(texture, 100)
newNP = np newNP = np
if key2 == 'c': if key2 == "c":
if category == 'TI_wallpaper' or category == 'TI_wallpaper_border': if (category == "TI_wallpaper") or (category == "TI_wallpaper_border"):
self.randomGenerator.seed(self.zoneId) self.randomGenerator.seed(self.zoneId)
newNP.setColorScale(self.randomGenerator.choice(self.colors[category])) newNP.setColorScale(
self.randomGenerator.choice(self.colors[category]))
else: else:
newNP.setColorScale(self.randomGenerator.choice(self.colors[category])) newNP.setColorScale(
self.randomGenerator.choice(self.colors[category]))
def setup(self): def setup(self):
self.dnaStore = base.cr.playGame.dnaStore self.dnaStore = base.cr.playGame.dnaStore
self.randomGenerator = random.Random() 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.randomGenerator.seed(self.zoneId)
self.interior = loader.loadModel('phase_3.5/models/modules/toon_interior_tutorial')
self.interior.reparentTo(render) self.interior = loader.loadModel("phase_3.5/models/modules/toon_interior_tutorial")
dnaStore = DNAStorage() self.interior.reparentTo(base.render)
node = loader.loadDNAFile(self.cr.playGame.hood.dnaStore, 'phase_3.5/dna/tutorial_street.dna') node = loader.loadDNAFile(self.cr.playGame.hood.dnaStore, "phase_3.5/dna/tutorial_street.dna")
self.street = render.attachNewNode(node) self.street = base.render.attachNewNode(node)
self.street.flattenMedium() self.street.flattenMedium()
self.street.setPosHpr(-17, 42, -0.5, 180, 0, 0) self.street.setPosHpr(-17, 42, -0.5, 180, 0, 0)
self.street.find('**/tb2:toon_landmark_TT_A1_DNARoot').stash() # Get rid of the building we are in
self.street.find('**/tb1:toon_landmark_hqTT_DNARoot/**/door_flat_0').stash() self.street.find("**/tb2:toon_landmark_TT_A1_DNARoot").stash()
self.street.findAllMatches('**/+CollisionNode').stash() # Get rid of the flashing doors on the HQ building
self.skyFile = 'phase_3.5/models/props/TT_sky' 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 = loader.loadModel(self.skyFile)
self.sky.setScale(0.8) 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.setDepthTest(0)
self.sky.setDepthWrite(0) self.sky.setDepthWrite(0)
self.sky.setBin('background', 100) self.sky.setBin("background", 100)
self.sky.find('**/Sky').reparentTo(self.sky, -1) # 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) hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
self.colors = ToonInteriorColors.colors[hoodId] self.colors = ToonInteriorColors.colors[hoodId]
# Replace all the "random_xxx_" nodes:
self.replaceRandomInModel(self.interior) self.replaceRandomInModel(self.interior)
doorModelName = 'door_double_round_ul'
if doorModelName[-1:] == 'r': # Door:
doorModelName = doorModelName[:-1] + 'l' doorModelName = "door_double_round_ul" # hack zzzzzzz
# Switch leaning of the door:
if doorModelName[-1:] == "r":
doorModelName = doorModelName[:-1] + "l"
else: else:
doorModelName = doorModelName[:-1] + 'r' doorModelName = doorModelName[:-1] + "r"
door = self.dnaStore.findNode(doorModelName) 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) 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) 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) door_origin.setPos(door_origin, 0, -0.025, 0)
color = self.randomGenerator.choice(self.colors['TI_door']) color = self.randomGenerator.choice(self.colors["TI_door"])
DNADoor.setupDoor(doorNP, self.interior, door_origin, self.dnaStore, str(self.block), color) DNADoor.setupDoor(doorNP,
doorFrame = doorNP.find('door_*_flat') 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.wrtReparentTo(self.interior)
doorFrame.setColor(color) doorFrame.setColor(color)
del self.colors del self.colors
del self.dnaStore del self.dnaStore
del self.randomGenerator del self.randomGenerator
# Get rid of any transitions and extra nodes
self.interior.flattenMedium() 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(): if not npcOrigin.isEmpty():
self.npc.reparentTo(npcOrigin) self.npc.reparentTo(npcOrigin)
self.npc.clearMat() self.npc.clearMat()
self.createSuit() 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() place = base.cr.playGame.getPlace()
if place and hasattr(place, 'fsm') and place.fsm.getCurrentState().getName(): if place and hasattr(place, 'fsm') and place.fsm.getCurrentState().getName():
self.notify.info('Tutorial movie: Place ready.') self.notify.info('Tutorial movie: Place ready.')
self.playMovie() self.playMovie()
else: 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'): 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) self.acceptOnce('enterTutorialInterior', self.playMovie)
def playMovie(self): def playMovie(self):
@ -144,6 +214,7 @@ class DistributedTutorialInterior(DistributedObject.DistributedObject):
self.mickeyMovie.play() self.mickeyMovie.play()
def createSuit(self): def createSuit(self):
# Create a suit
self.suit = Suit.Suit() self.suit = Suit.Suit()
suitDNA = SuitDNA.SuitDNA() suitDNA = SuitDNA.SuitDNA()
suitDNA.newSuit('f') suitDNA.newSuit('f')
@ -151,7 +222,19 @@ class DistributedTutorialInterior(DistributedObject.DistributedObject):
self.suit.loop('neutral') self.suit.loop('neutral')
self.suit.setPosHpr(-20, 8, 0, 0, 0, 0) self.suit.setPosHpr(-20, 8, 0, 0, 0, 0)
self.suit.reparentTo(self.interior) 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() self.suitWalkTrack.loop()
def setZoneIdAndBlock(self, zoneId, block): 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.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObjectAI from direct.distributed import DistributedObjectAI
from toontown.toon import NPCToons from toontown.toon import NPCToons
class DistributedTutorialInteriorAI(DistributedObjectAI.DistributedObjectAI): class DistributedTutorialInteriorAI(DistributedObjectAI.DistributedObjectAI):
if __debug__: if __debug__:
@ -13,14 +10,12 @@ class DistributedTutorialInteriorAI(DistributedObjectAI.DistributedObjectAI):
def __init__(self, block, air, zoneId, building, npcId): def __init__(self, block, air, zoneId, building, npcId):
"""blockNumber: the landmark building number (from the name)""" """blockNumber: the landmark building number (from the name)"""
#self.air=air
DistributedObjectAI.DistributedObjectAI.__init__(self, air) DistributedObjectAI.DistributedObjectAI.__init__(self, air)
self.block=block self.block = block
self.zoneId=zoneId self.zoneId = zoneId
self.building=building self.building = building
self.tutorialNpcId = npcId self.tutorialNpcId = npcId
# Make any npcs that may be in this interior zone # Make any npcs that may be in this interior zone
# If there are none specified, this will just be an empty list # If there are none specified, this will just be an empty list
self.npcs = NPCToons.createNpcsInZone(air, zoneId) self.npcs = NPCToons.createNpcsInZone(air, zoneId)

View File

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

View File

@ -1,11 +1,15 @@
from toontown.battle import DistributedBattle
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from toontown.battle import DistributedBattle
class DistributedBattleTutorial(DistributedBattle.DistributedBattle): class DistributedBattleTutorial(DistributedBattle.DistributedBattle):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleTutorial') 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() self.townBattle.timer.hide()
def playReward(self, ts): 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 direct.directnotify import DirectNotifyGlobal
from toontown.battle import DistributedBattleAI
class DistributedBattleTutorialAI(DistributedBattleAI.DistributedBattleAI): class DistributedBattleTutorialAI(DistributedBattleAI.DistributedBattleAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleTutorialAI') notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleTutorialAI')
def __init__(self, air, battleMgr, pos, suit, toonId, zoneId, def __init__(self, air, battleMgr, pos, suit, toonId, zoneId,
finishCallback=None, maxSuits=4, interactivePropTrackBonus = -1): finishCallback=None, maxSuits=4, interactivePropTrackBonus=-1):
"""__init__(air, battleMgr, pos, suit, toonId, zoneId,
finishCallback, maxSuits)
"""
DistributedBattleAI.DistributedBattleAI.__init__( DistributedBattleAI.DistributedBattleAI.__init__(
self, air, battleMgr, pos, suit, toonId, zoneId, self, air, battleMgr, pos, suit, toonId, zoneId,
finishCallback, maxSuits, tutorialFlag=1) 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. # There is no timer in the tutorial... The reward movie is random length.
def startRewardTimer(self): def startRewardTimer(self):
pass 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 which handles management of the suit you will fight during the
tutorial.""" tutorial."""
from otp.ai.AIBaseGlobal import * from panda3d.core import Vec3
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from toontown.suit import DistributedTutorialSuitAI from toontown.suit import DistributedTutorialSuitAI
from toontown.tutorial import TutorialBattleManagerAI from toontown.tutorial import TutorialBattleManagerAI
class SuitPlannerTutorialAI: class SuitPlannerTutorialAI:
""" """
SuitPlannerTutorialAI: manages the single suit that you fight during SuitPlannerTutorialAI: manages the single suit that you fight during
@ -44,8 +44,8 @@ class SuitPlannerTutorialAI:
self.suit.requestDelete() self.suit.requestDelete()
self.suit = None self.suit = None
if self.battle: if self.battle:
#self.battle.requestDelete() # self.battle.requestDelete()
#RAU made to kill the mem leak when you close the window in the middle of the battle tutorial # RAU made to kill the mem leak when you close the window in the middle of the battle tutorial
cellId = self.battle.battleCellId cellId = self.battle.battleCellId
battleMgr = self.battle.battleMgr battleMgr = self.battle.battleMgr
if cellId in battleMgr.cellId2battle: if cellId in battleMgr.cellId2battle:
@ -61,16 +61,14 @@ class SuitPlannerTutorialAI:
return 0 return 0
def requestBattle(self, zoneId, suit, toonId): 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( self.battle = self.battleMgr.newBattle(
zoneId, zoneId, Vec3(35, 20, 0), zoneId, zoneId, Vec3(35, 20, -0.5),
suit, toonId, suit, toonId,
finishCallback=self.battleOverCallback) finishCallback=self.battleOverCallback)
return 1 return True
def removeSuit(self, suit): def removeSuit(self, suit):
# Get rid of the suit. # Get rid of the suit.
suit.requestDelete() suit.requestDelete()
self.suit = None self.suit = None

View File

@ -1,13 +1,11 @@
from toontown.battle import BattleManagerAI
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from toontown.battle import BattleManagerAI
from toontown.tutorial import DistributedBattleTutorialAI from toontown.tutorial import DistributedBattleTutorialAI
class TutorialBattleManagerAI(BattleManagerAI.BattleManagerAI):
class TutorialBattleManagerAI(BattleManagerAI.BattleManagerAI):
notify = DirectNotifyGlobal.directNotify.newCategory('TutorialBattleManagerAI') notify = DirectNotifyGlobal.directNotify.newCategory('TutorialBattleManagerAI')
def __init__(self, air): def __init__(self, air):
BattleManagerAI.BattleManagerAI.__init__(self, air) BattleManagerAI.BattleManagerAI.__init__(self, air)
self.battleConstructor = DistributedBattleTutorialAI.DistributedBattleTutorialAI 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.toonbase import TTLocalizer
from toontown.toontowngui import TTDialog
class TutorialForceAcknowledge: class TutorialForceAcknowledge:
def __init__(self, doneEvent): def __init__(self, doneEvent):
self.doneEvent = doneEvent self.doneEvent = doneEvent
self.dialog = None self.dialog = None
return
def enter(self): def enter(self):
base.localAvatar.loop('neutral') # Make the toon stop running.
base.localAvatar.loop("neutral")
self.doneStatus = {'mode': 'incomplete'} self.doneStatus = {'mode': 'incomplete'}
msg = TTLocalizer.TutorialForceAcknowledgeMessage 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): def exit(self):
if self.dialog: if self.dialog:
self.dialog.cleanup() self.dialog.cleanup()
self.dialog = None self.dialog = None
return
def handleOk(self, value): def handleOk(self, value):
messenger.send(self.doneEvent, [self.doneStatus]) messenger.send(self.doneEvent, [self.doneStatus])

View File

@ -1,45 +1,57 @@
from pandac.PandaModules import *
from direct.distributed import DistributedObject from direct.distributed import DistributedObject
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from toontown.hood import ZoneUtil from toontown.hood import ZoneUtil
class TutorialManager(DistributedObject.DistributedObject):
notify = DirectNotifyGlobal.directNotify.newCategory('TutorialManager')
neverDisable = 1
def __init__(self, cr): class TutorialManager(DistributedObject.DistributedObject):
DistributedObject.DistributedObject.__init__(self, cr) notify = DirectNotifyGlobal.directNotify.newCategory("TutorialManager")
neverDisable = 1
def generate(self): def generate(self):
DistributedObject.DistributedObject.generate(self) DistributedObject.DistributedObject.generate(self)
messenger.send('tmGenerate') # Let the cr know we have arrived.
self.accept('requestTutorial', self.d_requestTutorial) messenger.send("tmGenerate")
self.accept('requestSkipTutorial', self.d_requestSkipTutorial) # Wait for a tutorial request or rejection.
self.accept('rejectTutorial', self.d_rejectTutorial) self.accept("requestTutorial", self.d_requestTutorial)
self.accept("requestSkipTutorial", self.d_requestSkipTutorial)
self.accept("rejectTutorial", self.d_rejectTutorial)
def disable(self): def disable(self):
self.ignoreAll() self.ignoreAll()
# In case we fell asleep in the tutorial
ZoneUtil.overrideOff() ZoneUtil.overrideOff()
DistributedObject.DistributedObject.disable(self) DistributedObject.DistributedObject.disable(self)
def d_requestTutorial(self): def d_requestTutorial(self):
self.sendUpdate('requestTutorial', []) self.sendUpdate("requestTutorial", [])
def d_rejectTutorial(self): def d_rejectTutorial(self):
self.sendUpdate('rejectTutorial', []) self.sendUpdate("rejectTutorial", [])
def d_requestSkipTutorial(self): def d_requestSkipTutorial(self):
self.sendUpdate('requestSkipTutorial', []) self.sendUpdate("requestSkipTutorial", [])
def skipTutorialResponse(self, allOk): 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): def enterTutorial(self, branchZone, streetZone, shopZone, hqZone):
base.localAvatar.cantLeaveGame = 1 base.localAvatar.cantLeaveGame = 1
ZoneUtil.overrideOn(branch=branchZone, exteriorList=[streetZone], interiorList=[shopZone, hqZone]) # Override the ZoneUtil
messenger.send('startTutorial', [shopZone]) ZoneUtil.overrideOn(branch=branchZone,
self.acceptOnce('stopTutorial', self.__handleStopTutorial) exteriorList=[streetZone],
self.acceptOnce('toonArrivedTutorial', self.d_toonArrived) 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): def __handleStopTutorial(self):
base.localAvatar.cantLeaveGame = 0 base.localAvatar.cantLeaveGame = 0
@ -47,7 +59,7 @@ class TutorialManager(DistributedObject.DistributedObject):
ZoneUtil.overrideOff() ZoneUtil.overrideOff()
def d_allDone(self): def d_allDone(self):
self.sendUpdate('allDone', []) self.sendUpdate("allDone", [])
def d_toonArrived(self): def d_toonArrived(self):
self.sendUpdate('toonArrived', []) self.sendUpdate("toonArrived", [])

View File

@ -1,5 +1,4 @@
from otp.ai.AIBaseGlobal import * from panda3d.toontown import DNAStorage
from pandac.PandaModules import *
from direct.distributed import DistributedObjectAI from direct.distributed import DistributedObjectAI
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from toontown.building import TutorialBuildingAI from toontown.building import TutorialBuildingAI
@ -7,10 +6,10 @@ from toontown.building import TutorialHQBuildingAI
from toontown.tutorial import SuitPlannerTutorialAI from toontown.tutorial import SuitPlannerTutorialAI
from toontown.toonbase import ToontownBattleGlobals from toontown.toonbase import ToontownBattleGlobals
from toontown.toon import NPCToons from toontown.toon import NPCToons
from toontown.toonbase import TTLocalizer
from toontown.ai import BlackCatHolidayMgrAI from toontown.ai import BlackCatHolidayMgrAI
from toontown.ai import DistributedBlackCatMgrAI from toontown.ai import DistributedBlackCatMgrAI
class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI): class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory("TutorialManagerAI") notify = DirectNotifyGlobal.directNotify.newCategory("TutorialManagerAI")
@ -37,7 +36,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
# Assumption: the only block that isn't an HQ is the gag shop block. # Assumption: the only block that isn't an HQ is the gag shop block.
self.hqBlock = None self.hqBlock = None
self.gagBlock = None self.gagBlock = None
for blockIndex in range (0, numBlocks): for blockIndex in range(0, numBlocks):
blockNumber = self.dnaStore.getBlockNumberAt(blockIndex) blockNumber = self.dnaStore.getBlockNumberAt(blockIndex)
buildingType = self.dnaStore.getBlockBuildingType(blockNumber) buildingType = self.dnaStore.getBlockBuildingType(blockNumber)
if (buildingType == 'hq'): if (buildingType == 'hq'):
@ -49,9 +48,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
# key is avId, value is real time when the request was made # key is avId, value is real time when the request was made
self.avIdsRequestingSkip = {} self.avIdsRequestingSkip = {}
self.accept("avatarEntered", self.waitingToonEntered ) self.accept("avatarEntered", self.waitingToonEntered)
return None
def requestTutorial(self): def requestTutorial(self):
# TODO: possible security breach: what if client is repeatedly # TODO: possible security breach: what if client is repeatedly
@ -100,7 +97,6 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
# No cogs defeated # No cogs defeated
av.b_setCogStatus([1] * 32) av.b_setCogStatus([1] * 32)
av.b_setCogCount([0] * 32) av.b_setCogCount([0] * 32)
return
def allDone(self): def allDone(self):
avId = self.air.getAvatarIdFromSender() avId = self.air.getAvatarIdFromSender()
@ -118,8 +114,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
str(avId) + str(avId) +
" isn't here, but just finished a tutorial. " + " isn't here, but just finished a tutorial. " +
"I will ignore this." "I will ignore this."
) )
return
def __createTutorial(self, avId): def __createTutorial(self, avId):
if self.playerDict.get(avId): if self.playerDict.get(avId):
@ -151,8 +146,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
battleOverCallback) battleOverCallback)
# Create the NPC blocking the tunnel to the playground # Create the NPC blocking the tunnel to the playground
blockerNPC = NPCToons.createNPC(self.air, 20001, NPCToons.NPCToonDict[20001], streetZone, blockerNPC = NPCToons.createNPC(self.air, 20001, NPCToons.NPCToonDict[20001], streetZone)
questCallback=self.__handleBlockDone)
blockerNPC.setTutorial(1) blockerNPC.setTutorial(1)
# is the black cat holiday enabled? # is the black cat holiday enabled?
@ -162,22 +156,19 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
self.air, avId) self.air, avId)
blackCatMgr.generateWithRequired(streetZone) blackCatMgr.generateWithRequired(streetZone)
zoneDict={"branchZone" : branchZone, zoneDict = {"branchZone": branchZone,
"streetZone" : streetZone, "streetZone": streetZone,
"shopZone" : shopZone, "shopZone": shopZone,
"hqZone" : hqZone, "hqZone": hqZone,
"building" : building, "building": building,
"hqBuilding" : hqBuilding, "hqBuilding": hqBuilding,
"suitPlanner" : suitPlanner, "suitPlanner": suitPlanner,
"blockerNPC" : blockerNPC, "blockerNPC": blockerNPC,
"blackCatMgr" : blackCatMgr, "blackCatMgr": blackCatMgr,
} }
self.playerDict[avId] = zoneDict self.playerDict[avId] = zoneDict
return zoneDict return zoneDict
def __handleBlockDone(self):
return None
def __destroyTutorial(self, avId): def __destroyTutorial(self, avId):
zoneDict = self.playerDict.get(avId) zoneDict = self.playerDict.get(avId)
if zoneDict: if zoneDict:
@ -213,8 +204,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
str(avId) + str(avId) +
" isn't here, but just rejected a tutorial. " + " isn't here, but just rejected a tutorial. " +
"I will ignore this." "I will ignore this."
) )
return
def respondToSkipTutorial(self, avId, av): def respondToSkipTutorial(self, avId, av):
"""Reply to the client if we let him skip the tutorial.""" """Reply to the client if we let him skip the tutorial."""
@ -253,7 +243,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
# do whatever needs to be done to make his quest state good # do whatever needs to be done to make his quest state good
elif av: elif av:
self.notify.debug("%s requestedSkipTutorial, but tutorialAck is 1") self.notify.debug(f"{avId} requestedSkipTutorial, but tutorialAck is 1")
else: else:
response = 0 response = 0
self.notify.warning( self.notify.warning(
@ -261,9 +251,8 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
str(avId) + str(avId) +
" isn't here, but requested to skip tutorial. " + " isn't here, but requested to skip tutorial. " +
"I will ignore this." "I will ignore this."
) )
self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [response]) self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [response])
return
def waitingToonEntered(self, av): def waitingToonEntered(self, av):
"""Check if the avatar is someone who's requested to skip, then proceed accordingly.""" """Check if the avatar is someone who's requested to skip, then proceed accordingly."""
@ -275,17 +264,16 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
if (curTime - requestTime) <= self.WaitTimeForSkipTutorial: if (curTime - requestTime) <= self.WaitTimeForSkipTutorial:
self.respondToSkipTutorial(avId, av) self.respondToSkipTutorial(avId, av)
else: 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]) self.sendUpdateToAvatarId(avId, "skipTutorialResponse", [0])
del self.avIdsRequestingSkip[avId] 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.""" """Mark our toon as requesting to skip, and start a task to timeout for it."""
self.notify.debugStateCall(self) self.notify.debugStateCall(self)
self.avIdsRequestingSkip[avId] = globalClock.getFrameTime() 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): def didNotGetToon(self, avId):
"""Just say no since the AI didn't get it.""" """Just say no since the AI didn't get it."""
@ -302,7 +290,7 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
# Make sure the avatar exists # Make sure the avatar exists
av = self.air.doId2do.get(avId) av = self.air.doId2do.get(avId)
if av: if av:
self.respondToSkipTutorial(avId,av) self.respondToSkipTutorial(avId, av)
else: else:
self.waitForToonToEnter(avId) self.waitForToonToEnter(avId)
@ -311,12 +299,8 @@ class TutorialManagerAI(DistributedObjectAI.DistributedObjectAI):
streetZone, streetZone,
shopZone, shopZone,
hqZone]) hqZone])
return
def __handleUnexpectedExit(self, avId): def __handleUnexpectedExit(self, avId):
self.notify.warning("Avatar: " + str(avId) + self.notify.warning("Avatar: " + str(avId) +
" has exited unexpectedly") " has exited unexpectedly")
self.__destroyTutorial(avId) self.__destroyTutorial(avId)
return