spellbook: Boss battle create/manip magic words.

This commit is contained in:
Little Cat 2022-12-28 19:15:38 -04:00
parent 5c0ca00131
commit 8051c15e89
No known key found for this signature in database
GPG key ID: 96455BD9C4399BE8
16 changed files with 238 additions and 7 deletions

View file

@ -714,8 +714,6 @@ class DistributedBattleBaseAI(DistributedObjectAI.DistributedObjectAI, BattleBas
empty = InventoryBase.InventoryBase(toon)
toon.b_setInventory(empty.makeNetString())
toon.b_setHp(0)
db = DatabaseObject.DatabaseObject(self.air, toonId)
db.storeObject(toon, ['setInventory', 'setHp'])
self.notify.info('killing mem leak from temporary DistributedToonAI %d' % toonId)
toon.deleteDummy()
return

View file

@ -9,7 +9,7 @@ class BossbotHQBossBattle(CogHQBossBattle.CogHQBossBattle):
def __init__(self, loader, parentFSM, doneEvent):
CogHQBossBattle.CogHQBossBattle.__init__(self, loader, parentFSM, doneEvent)
self.teleportInPosHpr = (88, -214, 0, 210, 0, 0)
self.teleportInPosHpr = (-1.40, 59.78, 0, 360, 0, 0)
for stateName in ['movie']:
state = self.fsm.getStateNamed(stateName)
state.addTransition('crane')

View file

@ -110,10 +110,20 @@ class CogHQBossBattle(BattlePlace.BattlePlace):
self.bossCog = bossCog
if self.bossCog:
self.bossCog.d_avatarEnter()
else:
# HACK: For some reason, when teleporting to a boss via a magic word, the place may load
# first instead of the boss cog. So listen to when the boss is generated.
self.acceptOnce('announceBoss', self.__bossGenerate)
self._telemLimiter = TLGatherAllAvs('CogHQBossBattle', RotationLimitToH)
NametagGlobals.setMasterArrowsOn(1)
base.localAvatar.inventory.setRespectInvasions(0)
self.fsm.request(requestStatus['how'], [requestStatus])
def __bossGenerate(self, boss):
if self.bossCog:
return
self.bossCog = boss
self.bossCog.d_avatarEnter()
def exit(self):
self.fsm.requestFinalState()

View file

@ -143,6 +143,7 @@ class CogHQLoader(StateData.StateData):
def enterCogHQBossBattle(self, requestStatus):
self.placeClass = self.getBossPlaceClass()
self.enterPlace(requestStatus)
self.hood.hideTitleText()
def exitCogHQBossBattle(self):
self.exitPlace()

View file

@ -1,4 +1,5 @@
from panda3d.core import *
from panda3d.physics import *
from direct.interval.IntervalGlobal import *
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedSmoothNode

View file

@ -9,7 +9,7 @@ class LawbotHQBossBattle(CogHQBossBattle.CogHQBossBattle):
def __init__(self, loader, parentFSM, doneEvent):
CogHQBossBattle.CogHQBossBattle.__init__(self, loader, parentFSM, doneEvent)
self.teleportInPosHpr = (88, -214, 0, 210, 0, 0)
self.teleportInPosHpr = (-2.84, -99.47, 0, 360, 0, 0)
def load(self):
CogHQBossBattle.CogHQBossBattle.load(self)

View file

@ -9,7 +9,7 @@ class SellbotHQBossBattle(CogHQBossBattle.CogHQBossBattle):
def __init__(self, loader, parentFSM, doneEvent):
CogHQBossBattle.CogHQBossBattle.__init__(self, loader, parentFSM, doneEvent)
self.teleportInPosHpr = (0, 95, 18, 180, 0, 0)
self.teleportInPosHpr = (0, 61, 18, 180, 0, 0)
def load(self):
CogHQBossBattle.CogHQBossBattle.load(self)

View file

@ -192,7 +192,8 @@ class PlayGame(StateData.StateData):
if how in ['tunnelIn',
'teleportIn',
'doorIn',
'elevatorIn']:
'elevatorIn',
'movie']:
self.fsm.request('quietZone', [doneStatus])
else:
self.notify.error('Exited hood with unexpected mode %s' % how)

View file

@ -250,6 +250,9 @@ class MaxToon(MagicWord):
def handleWord(self, invoker, avId, toon, *args):
from toontown.toonbase import ToontownGlobals
from toontown.quest import Quests
from toontown.suit import SuitDNA
from toontown.coghq import CogDisguiseGlobals
# TODO: Handle this better, like giving out all awards, set the quest tier, stuff like that.
# This is mainly copied from Anesidora just so I can better work on things.
@ -271,6 +274,14 @@ class MaxToon(MagicWord):
toon.b_setMoney(toon.maxMoney)
toon.b_setBankMoney(toon.maxBankMoney)
toon.b_setQuests([])
toon.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit)
toon.b_setRewardHistory(Quests.LOOPING_FINAL_TIER, [])
toon.b_setCogParts([*CogDisguiseGlobals.PartsPerSuitBitmasks])
toon.b_setCogTypes([SuitDNA.suitsPerDept - 1] * 4)
toon.b_setCogLevels([ToontownGlobals.MaxCogSuitLevel] * 4)
return f"Successfully maxed {toon.getName()}!"
class AbortMinigame(MagicWord):
@ -339,6 +350,115 @@ class Quests(MagicWord):
else:
return "Valid commands: \"finish\""
class BossBattle(MagicWord):
aliases = ["boss"]
desc = "Create a new or manupliate the current boss battle."
execLocation = MagicWordConfig.EXEC_LOC_SERVER
arguments = [("command", str, True), ("type", str, False, ""), ("start", int, False, 1)]
def handleWord(self, invoker, avId, toon, *args):
command = args[0].lower()
type = args[1].lower()
start = args[2]
"""
Commands:
- create [type] [start: 1]: Creates a boss and teleports to it.
- start: Starts/Restarts the battle from the beginning.
- stop: Stops the battle by going to the Frolic state.
- skip: Skips the boss to the next state (needs getNextState to be implemented).
- final: Skips the boss to the final round.
- kill: Skips the boss to the Victory state.
"""
# create command shortcut:
if command in ("vp", "cfo", "cj", "ceo"):
type = command
command = "create"
try:
start = int(args[1])
except ValueError:
start = 1
from toontown.suit.DistributedBossCogAI import DistributedBossCogAI
boss = None
for do in self.air.doId2do.values():
if isinstance(do, DistributedBossCogAI):
if do.isToonKnown(invoker.doId):
boss = do
break
if command == "create":
if boss:
return "You're already in a boss battle. Please finish this one."
if type == "vp":
from toontown.suit.DistributedSellbotBossAI import DistributedSellbotBossAI
boss = DistributedSellbotBossAI(self.air)
elif type == "cfo":
from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI
boss = DistributedCashbotBossAI(self.air)
elif type == "cj":
from toontown.suit.DistributedLawbotBossAI import DistributedLawbotBossAI
boss = DistributedLawbotBossAI(self.air)
elif type == "ceo":
from toontown.suit.DistributedBossbotBossAI import DistributedBossbotBossAI
boss = DistributedBossbotBossAI(self.air)
else:
return f"Unknown boss type: \"{type}\""
zoneId = self.air.allocateZone()
boss.generateWithRequired(zoneId)
if start:
boss.addToon(avId)
boss.b_setState('WaitForToons')
else:
boss.b_setState('Frolic')
respText = f"Created {type.upper()} boss battle"
if not start:
respText += " in Frolic state"
return respText + ", teleporting...", ["cogHQLoader", "cogHQBossBattle", "movie" if start else "teleportIn", boss.getHoodId(), boss.zoneId, 0]
# The following commands needs the invoker to be in a boss battle.
if not boss:
return "You ain't in a boss battle! Use the \"create\" command to create a boss battle."
boss.acceptNewToons()
if command == "start":
boss.b_setState('WaitForToons')
return "Boss battle started!"
elif command == "stop":
boss.b_setState("Frolic")
return "Boss battle stopped!"
elif command == "skip":
try:
nextState = boss.getNextState()
except NotImplementedError:
return "\"getNextState\" is not implemented for this boss battle!"
if nextState:
boss.b_setState(nextState)
return f"Skipped to {nextState}!"
return f"Cannot skip \"{boss.getCurrentOrNextState()}\" state."
elif command in ("final", "pie", "crane"):
if boss.dept == 'c':
boss.b_setState("BattleFour")
else:
boss.b_setState("BattleThree")
return "Skipped to final round!"
elif command in ("kill", "victory", "finish"):
boss.b_setState("Victory")
return "Killed the boss!"
# The create command is already described when the invoker is not in a battle. These are the commands
# they can use INSIDE the battle.
return respText + f"Unknown command: \"{command}\". Valid commands: \"start\", \"stop\", \"skip\", \"final\", \"kill\"."
# 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

@ -118,6 +118,9 @@ class DistributedBossCog(DistributedAvatar.DistributedAvatar, BossCog.BossCog):
self.bubbleF.setTag('attackCode', str(ToontownGlobals.BossCogFrontAttack))
self.bubbleF.stash()
# HACK: See toontown/coghq/CogHQBossBattle.py
messenger.send('announceBoss', [self])
def disable(self):
DistributedAvatar.DistributedAvatar.disable(self)
self.battleAId = None

View file

@ -685,3 +685,6 @@ class DistributedBossCogAI(DistributedAvatarAI.DistributedAvatarAI):
def doNextAttack(self, task):
self.b_setAttackCode(ToontownGlobals.BossCogNoAttack)
def getNextState(self):
raise NotImplementedError

View file

@ -87,7 +87,7 @@ class DistributedBossbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FS
self.battleOneBattlesMade = True
def getHoodId(self):
return ToontownGlobals.LawbotHQ
return ToontownGlobals.BossbotHQ
def generateSuits(self, battleNumber):
if battleNumber == 1:
@ -885,3 +885,29 @@ class DistributedBossbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FS
def toggleMove(self):
self.moveAttackAllowed = not self.moveAttackAllowed
return self.moveAttackAllowed
def getNextState(self):
currState = self.getCurrentOrNextState()
if currState == "Elevator":
return "Introduction"
elif currState == "Introduction":
return "BattleOne"
elif currState == "BattleOne":
return "PrepareBattleTwo"
elif currState == "PrepareBattleTwo":
return "BattleTwo"
elif currState == "BattleTwo":
return "PrepareBattleThree"
elif currState == "PrepareBattleThree":
return "BattleThree"
elif currState == "BattleThree":
return "PrepareBattleFour"
elif currState == "PrepareBattleFour":
return "BattleFour"
elif currState == "BattleFour":
return "Victory"
# Do not skip Victory, weird stuff may happen, like not collecting their rewards.
elif currState == "Reward":
return "Epilogue"
return None

View file

@ -19,6 +19,7 @@ from toontown.distributed import DelayDelete
from toontown.chat import ResistanceChat
from toontown.coghq import CogDisguiseGlobals
from panda3d.core import *
from panda3d.physics import *
from panda3d.otp import *
import random
import math
@ -481,6 +482,14 @@ class DistributedCashbotBoss(DistributedBossCog.DistributedBossCog, FSM.FSM):
track.append(toon.posHprInterval(0.2, pos, hpr))
return track
def moveLocalToonToBattleThreePos(self):
# This exists because when skipping to the crane round via a magic word, the toon gets
# teleported to the center, taking damage in the progress. This is done to remedy that.
i = self.involvedToons.index(localAvatar.doId)
if not i:
i = 0
localAvatar.setPosHpr(*ToontownGlobals.CashbotToonsBattleThreeStartPosHpr[i])
def makeBossFleeMovie(self):
hadEnough = TTLocalizer.CashbotBossHadEnough
@ -758,6 +767,7 @@ class DistributedCashbotBoss(DistributedBossCog.DistributedBossCog, FSM.FSM):
self.evWalls.unstash()
self.midVault.stash()
self.__hideResistanceToon()
self.moveLocalToonToBattleThreePos()
localAvatar.setCameraFov(ToontownGlobals.BossBattleCameraFov)
self.generateHealthBar()
self.updateHealthBar()

View file

@ -477,3 +477,21 @@ class DistributedCashbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FS
def enterEpilogue(self):
DistributedBossCogAI.DistributedBossCogAI.enterEpilogue(self)
self.d_setRewardId(self.rewardId)
def getNextState(self):
currState = self.getCurrentOrNextState()
if currState == "Elevator":
return "Introduction"
elif currState == "Introduction":
return "BattleOne"
elif currState == "BattleOne":
return "PrepareBattleThree"
elif currState == "PrepareBattleThree":
return "BattleThree"
elif currState == "BattleThree":
return "Victory"
# Do not skip Victory, weird stuff may happen, like not collecting their rewards.
elif currState == "Reward":
return "Epilogue"
return None

View file

@ -856,3 +856,23 @@ class DistributedLawbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FSM
if battleDifficulty >= numDifficultyLevels:
battleDifficulty = numDifficultyLevels - 1
self.b_setBattleDifficulty(battleDifficulty)
def getNextState(self):
currState = self.getCurrentOrNextState()
if currState == "Elevator":
return "Introduction"
elif currState == "Introduction":
return "BattleOne"
elif currState == "BattleOne":
return "RollToBattleTwo"
elif currState == "RollToBattleTwo":
return "PrepareBattleTwo"
elif currState in ("PrepareBattleTwo", "BattleTwo"):
return "PrepareBattleThree"
elif currState in ("PrepareBattleThree", "BattleThree", "NearVictory"):
return "Victory"
# Do not skip Victory, weird stuff may happen, like not collecting their rewards.
elif currState == "Reward":
return "Epilogue"
return None

View file

@ -452,3 +452,23 @@ class DistributedSellbotBossAI(DistributedBossCogAI.DistributedBossCogAI, FSM.FS
barrel.requestDelete()
self.barrels = []
def getNextState(self):
currState = self.getCurrentOrNextState()
if currState == "Elevator":
return "Introduction"
elif currState == "Introduction":
return "BattleOne"
elif currState == "BattleOne":
return "RollToBattleTwo"
elif currState == "RollToBattleTwo":
return "PrepareBattleTwo"
elif currState in ("PrepareBattleTwo", "BattleTwo"):
return "PrepareBattleThree"
elif currState in ("PrepareBattleThree", "BattleThree", "NearVictory"):
return "Victory"
# Do not skip Victory, weird stuff may happen, like not collecting their rewards.
elif currState == "Reward":
return "Epilogue"
return None