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
gc-save-all 0
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;
setInterface(blob = []) 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;
setClothesTopsList(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
UberZone = 2
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.toon import NPCToons
from toontown.toonbase import ToontownGlobals
from toontown.tutorial.TutorialManagerAI import TutorialManagerAI
from toontown.uberdog.DistributedInGameNewsMgrAI import DistributedInGameNewsMgrAI
import os
@ -220,6 +221,10 @@ class ToontownAIRepository(ToontownInternalRepository):
self.magicWordManager = ToontownMagicWordManagerAI(self)
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):
# Bossbot HQ doesn't use DNA, so we skip over that.
if zoneId != ToontownGlobals.BossbotHQ:

View file

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

View file

@ -1,5 +1,40 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI
from toontown.toonbase.ToontownGlobals import *
from otp.ai.AIBaseGlobal import *
from direct.distributed.ClockDelta import *
class DistributedTutorialInteriorAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTutorialInteriorAI')
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')
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('HFAReject', self.enterHFAReject, self.exitHFAReject, ['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('teleportOut', self.enterTeleportOut, self.exitTeleportOut, ['teleportIn']),
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
from direct.interval.IntervalGlobal import *
from direct.directnotify import DirectNotifyGlobal
from pandac.PandaModules import *
from panda3d.core import *
from panda3d.otp import *
from direct.showbase import DirectObject
from . import BlinkingArrows
from toontown.toon import ToonHeadFrame

View file

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

View file

@ -315,6 +315,30 @@ class RequestMinigame(MagicWord):
retStr += f" with difficulty {mgDiff}"
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.
# A bit hacky, but better than the old system
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!')
return
# FIXME:
# 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
if hasattr(self.air, 'tutorialManager') and avId in list(self.air.tutorialManager.avId2fsm.keys()):
self.generateResponse(avId=avId, responseType="Tutorial")
return
# if hasattr(self.air, 'tutorialManager') and avId in list(self.air.tutorialManager.avId2fsm.keys()):
# self.generateResponse(avId=avId, responseType="Tutorial")
# return
# 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

View file

@ -1,5 +1,55 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI
from otp.ai.AIBaseGlobal import *
class DistributedTutorialSuitAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTutorialSuitAI')
from direct.directnotify import DirectNotifyGlobal
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')
if paidStatus == 'unpaid':
access = 1
print('Setting Access %s' % access)
self.notify.debug('Setting Access %s' % access)
if access == OTPGlobals.AccessInvalid:
if not __dev__:
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('battle', self.enterBattle, self.exitBattle, ['walk', 'teleportOut', 'died']),
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('elevator', self.enterElevator, self.exitElevator, ['walk']),
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.distributed.DistributedObjectAI import DistributedObjectAI
class DistributedBattleTutorialAI(DistributedObjectAI):
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)
"""
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.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):
notify = DirectNotifyGlobal.directNotify.newCategory('TutorialManagerAI')
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