quest: tutorial: Disney's Quest and Tutorial Mgrs.

This commit is contained in:
Little Cat 2022-12-24 00:11:13 -04:00
parent 984058ce32
commit caaaa39a92
No known key found for this signature in database
GPG key ID: 96455BD9C4399BE8
22 changed files with 1786 additions and 51 deletions

View file

@ -97,3 +97,7 @@ accept-clock-skew 1
text-minfilter linear_mipmap_linear text-minfilter linear_mipmap_linear
gc-save-all 0 gc-save-all 0
server-data-folder data server-data-folder data
# TEMPORARY
skip-friend-quest true
skip-phone-quest true

View file

@ -467,7 +467,7 @@ dclass DistributedToon : DistributedPlayer {
setHoodsVisited(uint32[] = [2000]) required ownrecv db; setHoodsVisited(uint32[] = [2000]) required ownrecv db;
setInterface(blob = []) required ownrecv db; setInterface(blob = []) required ownrecv db;
setLastHood(uint32 = 0) required ownrecv db; setLastHood(uint32 = 0) required ownrecv db;
setTutorialAck(uint8 = 1) required ownrecv db; setTutorialAck(uint8 = 0) required ownrecv db;
setMaxClothes(uint32 = 10) required ownrecv db; setMaxClothes(uint32 = 10) required ownrecv db;
setClothesTopsList(uint8[] = []) required ownrecv db; setClothesTopsList(uint8[] = []) required ownrecv db;
setClothesBottomsList(uint8[] = []) required ownrecv db; setClothesBottomsList(uint8[] = []) required ownrecv db;

View file

@ -1,4 +1,5 @@
from pandac.PandaModules import * from panda3d.core import *
from panda3d.otp import *
QuietZone = 1 QuietZone = 1
UberZone = 2 UberZone = 2
WallBitmask = BitMask32(1) WallBitmask = BitMask32(1)

View file

@ -0,0 +1,18 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.ai import HolidayBaseAI
from toontown.toonbase import ToontownGlobals
class BlackCatHolidayMgrAI(HolidayBaseAI.HolidayBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'BlackCatHolidayMgrAI')
PostName = 'blackCatHoliday'
def __init__(self, air, holidayId):
HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
def start(self):
bboard.post(BlackCatHolidayMgrAI.PostName)
def stop(self):
bboard.remove(BlackCatHolidayMgrAI.PostName)

View file

@ -48,6 +48,7 @@ from toontown.spellbook.ToontownMagicWordManagerAI import ToontownMagicWordManag
from toontown.suit.SuitInvasionManagerAI import SuitInvasionManagerAI from toontown.suit.SuitInvasionManagerAI import SuitInvasionManagerAI
from toontown.toon import NPCToons from toontown.toon import NPCToons
from toontown.toonbase import ToontownGlobals from toontown.toonbase import ToontownGlobals
from toontown.tutorial.TutorialManagerAI import TutorialManagerAI
from toontown.uberdog.DistributedInGameNewsMgrAI import DistributedInGameNewsMgrAI from toontown.uberdog.DistributedInGameNewsMgrAI import DistributedInGameNewsMgrAI
import os import os
@ -220,6 +221,10 @@ class ToontownAIRepository(ToontownInternalRepository):
self.magicWordManager = ToontownMagicWordManagerAI(self) self.magicWordManager = ToontownMagicWordManagerAI(self)
self.magicWordManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) self.magicWordManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
# Generate our Tutorial manager...
self.tutorialManager = TutorialManagerAI(self)
self.tutorialManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
def generateHood(self, hoodConstructor, zoneId): def generateHood(self, hoodConstructor, zoneId):
# Bossbot HQ doesn't use DNA, so we skip over that. # Bossbot HQ doesn't use DNA, so we skip over that.
if zoneId != ToontownGlobals.BossbotHQ: if zoneId != ToontownGlobals.BossbotHQ:

View file

@ -1,5 +1,6 @@
from toontown.toonbase.ToonBaseGlobal import * from toontown.toonbase.ToonBaseGlobal import *
from pandac.PandaModules import * from panda3d.core import *
from panda3d.toontown import *
from direct.interval.IntervalGlobal import * from direct.interval.IntervalGlobal import *
from direct.distributed.ClockDelta import * from direct.distributed.ClockDelta import *
from toontown.toonbase import ToontownGlobals from toontown.toonbase import ToontownGlobals

View file

@ -1,5 +1,40 @@
from direct.directnotify import DirectNotifyGlobal from toontown.toonbase.ToontownGlobals import *
from direct.distributed.DistributedObjectAI import DistributedObjectAI from otp.ai.AIBaseGlobal import *
from direct.distributed.ClockDelta import *
class DistributedTutorialInteriorAI(DistributedObjectAI): from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObjectAI
from toontown.toon import NPCToons
class DistributedTutorialInteriorAI(DistributedObjectAI.DistributedObjectAI):
if __debug__:
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTutorialInteriorAI') notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTutorialInteriorAI')
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.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)
def delete(self):
self.ignoreAll()
for npc in self.npcs:
npc.requestDelete()
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

@ -61,7 +61,7 @@ class ToonInterior(Place.Place):
State.State('HFA', self.enterHFA, self.exitHFA, ['HFAReject', 'teleportOut', 'tunnelOut']), State.State('HFA', self.enterHFA, self.exitHFA, ['HFAReject', 'teleportOut', 'tunnelOut']),
State.State('HFAReject', self.enterHFAReject, self.exitHFAReject, ['walk']), State.State('HFAReject', self.enterHFAReject, self.exitHFAReject, ['walk']),
State.State('doorIn', self.enterDoorIn, self.exitDoorIn, ['walk']), State.State('doorIn', self.enterDoorIn, self.exitDoorIn, ['walk']),
State.State('doorOut', self.enterDoorOut, self.exitDoorOut, ['walk']), State.State('doorOut', self.enterDoorOut, self.exitDoorOut, ['walk', 'stopped']),
State.State('teleportIn', self.enterTeleportIn, self.exitTeleportIn, ['walk']), State.State('teleportIn', self.enterTeleportIn, self.exitTeleportIn, ['walk']),
State.State('teleportOut', self.enterTeleportOut, self.exitTeleportOut, ['teleportIn']), State.State('teleportOut', self.enterTeleportOut, self.exitTeleportOut, ['teleportIn']),
State.State('quest', self.enterQuest, self.exitQuest, ['walk', 'doorOut']), State.State('quest', self.enterQuest, self.exitQuest, ['walk', 'doorOut']),

View file

@ -0,0 +1,91 @@
from pandac.PandaModules import *
from direct.directnotify import DirectNotifyGlobal
from . import DistributedDoorAI
from . import DistributedTutorialInteriorAI
from . import FADoorCodes
from . import DoorTypes
from toontown.toon import NPCToons
from toontown.toonbase import TTLocalizer
# This is not a distributed class... It just owns and manages some distributed
# classes.
class TutorialBuildingAI:
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
# This is because we are "pretending" to be a DistributedBuilding.
# The DistributedTutorialInterior takes a peek at savedBy. It really
# should make a function call. Perhaps TutorialBuildingAI and
# DistributedBuildingAI should inherit from each other somehow,
# but I can't see an easy way to do that.
self.savedBy = None
self.setup(blockNumber)
def cleanup(self):
self.interior.requestDelete()
del self.interior
self.door.requestDelete()
del self.door
self.insideDoor.requestDelete()
del self.insideDoor
self.gagShopNPC.requestDelete()
del self.gagShopNPC
return
def setup(self, blockNumber):
# Put an NPC in here. Give him id# 20000. When he has assigned
# his quest, he will unlock the interior door.
self.gagShopNPC = NPCToons.createNPC(
self.air, 20000,
(self.interiorZone,
TTLocalizer.NPCToonNames[20000],
("dll" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,2 ,6 ,2 ,6 ,2 ,16), "m", 1, NPCToons.NPC_REGULAR),
self.interiorZone,
questCallback=self.unlockInteriorDoor)
# Flag him as being part of tutorial
self.gagShopNPC.setTutorial(1)
npcId = self.gagShopNPC.getDoId()
# Toon interior (with tutorial flag set to 1)
self.interior=DistributedTutorialInteriorAI.DistributedTutorialInteriorAI(
blockNumber, self.air, self.interiorZone, self, npcId)
self.interior.generateWithRequired(self.interiorZone)
# Outside door:
door=DistributedDoorAI.DistributedDoorAI(self.air, blockNumber,
DoorTypes.EXT_STANDARD,
lockValue=FADoorCodes.DEFEAT_FLUNKY_TOM)
# Inside door. Locked until you get your gags.
insideDoor=DistributedDoorAI.DistributedDoorAI(
self.air,
blockNumber,
DoorTypes.INT_STANDARD,
lockValue=FADoorCodes.TALK_TO_TOM)
# Tell them about each other:
door.setOtherDoor(insideDoor)
insideDoor.setOtherDoor(door)
door.zoneId=self.exteriorZone
insideDoor.zoneId=self.interiorZone
# Now that they both now about each other, generate them:
door.generateWithRequired(self.exteriorZone)
#door.sendUpdate("setDoorIndex", [door.getDoorIndex()])
insideDoor.generateWithRequired(self.interiorZone)
#insideDoor.sendUpdate("setDoorIndex", [door.getDoorIndex()])
# keep track of them:
self.door=door
self.insideDoor=insideDoor
return
def unlockInteriorDoor(self):
self.insideDoor.setDoorLock(FADoorCodes.UNLOCKED)
def battleOverCallback(self):
# There is an if statement here because it is possible for
# the callback to get called after cleanup has already taken
# place.
if hasattr(self, "door"):
self.door.setDoorLock(FADoorCodes.TALK_TO_HQ_TOM)

View file

@ -0,0 +1,128 @@
from panda3d.core import *
from direct.directnotify import DirectNotifyGlobal
from . import DistributedDoorAI
from . import DistributedHQInteriorAI
from . import FADoorCodes
from . import DoorTypes
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):
self.interior.requestDelete()
del self.interior
self.npc.requestDelete()
del self.npc
self.door0.requestDelete()
del self.door0
self.door1.requestDelete()
del self.door1
self.insideDoor0.requestDelete()
del self.insideDoor0
self.insideDoor1.requestDelete()
del self.insideDoor1
return
def setup(self, blockNumber):
# The interior
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)
self.npc = NPCToons.createNPC(self.air, Quests.ToonHQ, desc,
self.interiorZone,
questCallback=self.unlockInsideDoor1)
# Flag npc as part of tutorial
self.npc.setTutorial(1)
self.interior.generateWithRequired(self.interiorZone)
# Outside door 0. Locked til you defeat the Flunky:
door0=DistributedDoorAI.DistributedDoorAI(
self.air, blockNumber, DoorTypes.EXT_HQ,
doorIndex=0,
lockValue=FADoorCodes.DEFEAT_FLUNKY_HQ)
# Outside door 1. Always locked.
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(
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(
self.air,
blockNumber,
DoorTypes.INT_HQ,
doorIndex=1,
lockValue=FADoorCodes.TALK_TO_HQ)
# Tell them about each other:
door0.setOtherDoor(insideDoor0)
insideDoor0.setOtherDoor(door0)
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
# Now that they both now about each other, generate them:
door0.generateWithRequired(self.exteriorZone)
door1.generateWithRequired(self.exteriorZone)
door0.sendUpdate("setDoorIndex", [door0.getDoorIndex()])
door1.sendUpdate("setDoorIndex", [door1.getDoorIndex()])
insideDoor0.generateWithRequired(self.interiorZone)
insideDoor1.generateWithRequired(self.interiorZone)
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
# hide the periscope
self.interior.setTutorial(1)
return
def unlockDoor(self, door):
door.setDoorLock(FADoorCodes.UNLOCKED)
def battleOverCallback(self):
# There is an if statement here because it is possible for
# the callback to get called after cleanup has already taken
# place.
if hasattr(self, "door0"):
self.unlockDoor(self.door0)
# This callback type happens to give zoneId. We don't need it.
def unlockInsideDoor1(self):
# There is an if statement here because it is possible for
# the callback to get called after cleanup has already taken
# place.
if hasattr(self, "insideDoor1"):
self.unlockDoor(self.insideDoor1)
# Change the message on this locked door to tell you to go
# through the other door. Maybe this door should not be
# here at all?
if hasattr(self, "insideDoor0"):
self.insideDoor0.setDoorLock(FADoorCodes.WRONG_DOOR_HQ)

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,8 @@ import tokenize
import copy import copy
from direct.interval.IntervalGlobal import * from direct.interval.IntervalGlobal import *
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from pandac.PandaModules import * from panda3d.core import *
from panda3d.otp import *
from direct.showbase import DirectObject from direct.showbase import DirectObject
from . import BlinkingArrows from . import BlinkingArrows
from toontown.toon import ToonHeadFrame from toontown.toon import ToonHeadFrame

View file

@ -1743,11 +1743,10 @@ class TrackChoiceQuest(Quest):
class FriendQuest(Quest): class FriendQuest(Quest):
def filterFunc(avatar): def filterFunc(avatar):
if len(avatar.getFriendsList()) == 0: if not config.GetBool('skip-friend-quest', False) and len(avatar.getFriendsList()) == 0:
return 1 return 1
else: else:
return 0 return 0
filterFunc = staticmethod(filterFunc) filterFunc = staticmethod(filterFunc)
def __init__(self, id, quest): def __init__(self, id, quest):
@ -1889,6 +1888,13 @@ class MailboxQuest(Quest):
class PhoneQuest(Quest): class PhoneQuest(Quest):
def filterFunc(avatar):
if not config.GetBool('skip-phone-quest', False):
return 1
else:
return 0
filterFunc = staticmethod(filterFunc)
def __init__(self, id, quest): def __init__(self, id, quest):
Quest.__init__(self, id, quest) Quest.__init__(self, id, quest)
@ -2115,7 +2121,7 @@ QuestDict = {
'type'), 'type'),
ToonHQ, ToonHQ,
ToonHQ, ToonHQ,
NA, 100,
150, 150,
DefaultDialog), DefaultDialog),
150: (TT_TIER, 150: (TT_TIER,
@ -2123,7 +2129,7 @@ QuestDict = {
(FriendQuest,), (FriendQuest,),
Same, Same,
Same, Same,
NA, 100,
175, 175,
DefaultDialog), DefaultDialog),
160: (TT_TIER, 160: (TT_TIER,

View file

@ -315,6 +315,30 @@ class RequestMinigame(MagicWord):
retStr += f" with difficulty {mgDiff}" retStr += f" with difficulty {mgDiff}"
return retStr + "." return retStr + "."
class Quests(MagicWord):
aliases = ["quest", "tasks", "task", "toontasks"]
desc = "Quest manupliation"
execLocation = MagicWordConfig.EXEC_LOC_SERVER
arguments = [("command", str, True), ("index", int, False, -1)]
def handleWord(self, invoker, avId, toon, *args):
command = args[0]
index = args[1]
"""
Commands:
- "finish": Finish a task (sets the progress to 1000), finishes all by default
"""
if command == "finish":
if index == -1:
self.air.questManager.completeAllQuestsMagically(toon)
return "Finished all quests."
else:
if self.air.questManager.completeQuestMagically(toon, index):
return f"Finished quest {index}."
return f"Quest {index} not found. (Hint: Quest indexes start at 0)"
else:
return "Valid commands: \"finish\""
# Instantiate all classes defined here to register them. # Instantiate all classes defined here to register them.
# A bit hacky, but better than the old system # A bit hacky, but better than the old system
for item in list(globals().values()): for item in list(globals().values()):

View file

@ -77,11 +77,12 @@ class ToontownMagicWordManagerAI(DistributedObjectAI.DistributedObjectAI):
self.notify.warning('requestExecuteMagicWord: Magic Word use requested but invoker avatar is non-existent!') self.notify.warning('requestExecuteMagicWord: Magic Word use requested but invoker avatar is non-existent!')
return return
# FIXME:
# Same thing with the Toontorial. Magic Words are strictly forbidden here # Same thing with the Toontorial. Magic Words are strictly forbidden here
# Tell the user they can't use it because they're in the Toontorial # Tell the user they can't use it because they're in the Toontorial
if hasattr(self.air, 'tutorialManager') and avId in list(self.air.tutorialManager.avId2fsm.keys()): # if hasattr(self.air, 'tutorialManager') and avId in list(self.air.tutorialManager.avId2fsm.keys()):
self.generateResponse(avId=avId, responseType="Tutorial") # self.generateResponse(avId=avId, responseType="Tutorial")
return # return
# Our Magic Word affectRange is either SELF (the invoker) or BOTH (invoker and a target) # Our Magic Word affectRange is either SELF (the invoker) or BOTH (invoker and a target)
# Because of this, we should add the invoker to the target list # Because of this, we should add the invoker to the target list

View file

@ -1,5 +1,55 @@
from direct.directnotify import DirectNotifyGlobal from otp.ai.AIBaseGlobal import *
from direct.distributed.DistributedObjectAI import DistributedObjectAI
class DistributedTutorialSuitAI(DistributedObjectAI): from direct.directnotify import DirectNotifyGlobal
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTutorialSuitAI') from toontown.battle import SuitBattleGlobals
from . import DistributedSuitBaseAI
class DistributedTutorialSuitAI(DistributedSuitBaseAI.DistributedSuitBaseAI):
notify = DirectNotifyGlobal.directNotify.newCategory(
'DistributedTutorialSuitAI')
def __init__(self, air, suitPlanner):
"""__init__(air, suitPlanner)"""
DistributedSuitBaseAI.DistributedSuitBaseAI.__init__(self, air,
suitPlanner)
def delete(self):
DistributedSuitBaseAI.DistributedSuitBaseAI.delete(self)
self.ignoreAll()
def requestBattle(self, x, y, z, h, p, r):
"""requestBattle(x, y, z, h, p, r)
"""
toonId = self.air.getAvatarIdFromSender()
if self.notify.getDebug():
self.notify.debug( str( self.getDoId() ) + \
str( self.zoneId ) + \
': request battle with toon: %d' % toonId )
# Store the suit's actual pos and hpr on the client
self.confrontPos = Point3(x, y, z)
self.confrontHpr = Vec3(h, p, r)
# Request a battle from the suit planner
if (self.sp.requestBattle(self.zoneId, self, toonId)):
self.acceptOnce(self.getDeathEvent(), self._logDeath, [toonId])
if self.notify.getDebug():
self.notify.debug( "Suit %d requesting battle in zone %d" %
(self.getDoId(), self.zoneId) )
else:
# Suit tells toon to get lost
if self.notify.getDebug():
self.notify.debug('requestBattle from suit %d - denied by battle manager' % (self.getDoId()))
self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName()))
self.d_denyBattle( toonId )
def getConfrontPosHpr(self):
""" getConfrontPosHpr()
"""
return (self.confrontPos, self.confrontHpr)
def _logDeath(self, toonId):
self.air.writeServerEvent('beatFirstCog', toonId, '')

View file

@ -3781,7 +3781,7 @@ class DistributedToonAI(DistributedPlayerAI.DistributedPlayerAI, DistributedSmoo
paidStatus = simbase.config.GetString('force-paid-status', 'none') paidStatus = simbase.config.GetString('force-paid-status', 'none')
if paidStatus == 'unpaid': if paidStatus == 'unpaid':
access = 1 access = 1
print('Setting Access %s' % access) self.notify.debug('Setting Access %s' % access)
if access == OTPGlobals.AccessInvalid: if access == OTPGlobals.AccessInvalid:
if not __dev__: if not __dev__:
self.air.writeServerEvent('Setting Access', self.doId, 'setAccess not being sent by the OTP Server, changing access to unpaid') self.air.writeServerEvent('Setting Access', self.doId, 'setAccess not being sent by the OTP Server, changing access to unpaid')

View file

@ -64,7 +64,7 @@ class Street(BattlePlace.BattlePlace):
State.State('WaitForBattle', self.enterWaitForBattle, self.exitWaitForBattle, ['battle', 'walk']), State.State('WaitForBattle', self.enterWaitForBattle, self.exitWaitForBattle, ['battle', 'walk']),
State.State('battle', self.enterBattle, self.exitBattle, ['walk', 'teleportOut', 'died']), State.State('battle', self.enterBattle, self.exitBattle, ['walk', 'teleportOut', 'died']),
State.State('doorIn', self.enterDoorIn, self.exitDoorIn, ['walk']), State.State('doorIn', self.enterDoorIn, self.exitDoorIn, ['walk']),
State.State('doorOut', self.enterDoorOut, self.exitDoorOut, ['walk']), State.State('doorOut', self.enterDoorOut, self.exitDoorOut, ['walk', 'stopped']),
State.State('elevatorIn', self.enterElevatorIn, self.exitElevatorIn, ['walk']), State.State('elevatorIn', self.enterElevatorIn, self.exitElevatorIn, ['walk']),
State.State('elevator', self.enterElevator, self.exitElevator, ['walk']), State.State('elevator', self.enterElevator, self.exitElevator, ['walk']),
State.State('trialerFA', self.enterTrialerFA, self.exitTrialerFA, ['trialerFAReject', 'DFA']), State.State('trialerFA', self.enterTrialerFA, self.exitTrialerFA, ['trialerFAReject', 'DFA']),

View file

@ -1,5 +1,21 @@
from toontown.battle import DistributedBattleAI
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI
class DistributedBattleTutorialAI(DistributedObjectAI): class DistributedBattleTutorialAI(DistributedBattleAI.DistributedBattleAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleTutorialAI') 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)
"""
DistributedBattleAI.DistributedBattleAI.__init__(
self, air, battleMgr, pos, suit, toonId, zoneId,
finishCallback, maxSuits, tutorialFlag=1)
# 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

@ -0,0 +1,74 @@
""" SuitPlannerTutorial module: contains the SuitPlannerTutorial class
which handles management of the suit you will fight during the
tutorial."""
from otp.ai.AIBaseGlobal import *
from direct.directnotify import DirectNotifyGlobal
from toontown.suit import DistributedTutorialSuitAI
from . import TutorialBattleManagerAI
class SuitPlannerTutorialAI:
"""
SuitPlannerTutorialAI: manages the single suit that you fight during
the tutorial.
"""
notify = DirectNotifyGlobal.directNotify.newCategory(
'SuitPlannerTutorialAI')
def __init__(self, air, zoneId, battleOverCallback):
# Store these things
self.zoneId = zoneId
self.air = air
self.battle = None
# This callback will be used to open the HQ doors when the
# battle is over.
self.battleOverCallback = battleOverCallback
# Create a battle manager
self.battleMgr = TutorialBattleManagerAI.TutorialBattleManagerAI(
self.air)
# Create a flunky
newSuit = DistributedTutorialSuitAI.DistributedTutorialSuitAI(self.air, self)
newSuit.setupSuitDNA(1, 1, "c")
# This is a special tutorial path state
newSuit.generateWithRequired(self.zoneId)
self.suit = newSuit
def cleanup(self):
self.zoneId = None
self.air = None
if self.suit:
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
cellId = self.battle.battleCellId
battleMgr = self.battle.battleMgr
if cellId in battleMgr.cellId2battle:
battleMgr.destroy(self.battle)
self.battle = None
def getDoId(self):
# This is here because the suit expects the suit planner to be
# a distributed object, if it has a suit planner. We want it to
# have a suit planner, but not a distributed one, so we return
# 0 when asked what our DoId is. Kind of hackful, I guess.
return 0
def requestBattle(self, zoneId, suit, toonId):
# 70, 20, 0 is a battle cell position that I just made up.
self.battle = self.battleMgr.newBattle(
zoneId, zoneId, Vec3(35, 20, 0),
suit, toonId,
finishCallback=self.battleOverCallback)
return 1
def removeSuit(self, suit):
# Get rid of the suit.
suit.requestDelete()
self.suit = None

View file

@ -0,0 +1,11 @@
from toontown.battle import BattleManagerAI
from direct.directnotify import DirectNotifyGlobal
from . import DistributedBattleTutorialAI
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,5 +1,322 @@
from otp.ai.AIBaseGlobal import *
from panda3d.core import *
from panda3d.toontown import *
from direct.distributed import DistributedObjectAI
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI 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
class TutorialManagerAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('TutorialManagerAI')