quest: tutorial: Disney's Quest and Tutorial Mgrs.
This commit is contained in:
parent
984058ce32
commit
caaaa39a92
22 changed files with 1786 additions and 51 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from panda3d.otp import *
|
||||
QuietZone = 1
|
||||
UberZone = 2
|
||||
WallBitmask = BitMask32(1)
|
||||
|
|
18
toontown/ai/BlackCatHolidayMgrAI.py
Normal file
18
toontown/ai/BlackCatHolidayMgrAI.py
Normal 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)
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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']),
|
||||
|
|
91
toontown/building/TutorialBuildingAI.py
Normal file
91
toontown/building/TutorialBuildingAI.py
Normal 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)
|
128
toontown/building/TutorialHQBuildingAI.py
Normal file
128
toontown/building/TutorialHQBuildingAI.py
Normal 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
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, '')
|
|
@ -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')
|
||||
|
|
|
@ -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']),
|
||||
|
|
|
@ -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)
|
||||
|
|
74
toontown/tutorial/SuitPlannerTutorialAI.py
Normal file
74
toontown/tutorial/SuitPlannerTutorialAI.py
Normal 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
|
11
toontown/tutorial/TutorialBattleManagerAI.py
Normal file
11
toontown/tutorial/TutorialBattleManagerAI.py
Normal 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
|
|
@ -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
|
||||
|
||||
|
Loading…
Reference in a new issue