825 lines
35 KiB
Python
825 lines
35 KiB
Python
##################################################
|
|
# The Toontown Offline Magic Word Manager
|
|
##################################################
|
|
# Author: Benjamin Frisby
|
|
# Copyright: Copyright 2020, Toontown Offline
|
|
# Credits: Benjamin Frisby, John Cote, Ruby Lord, Frank, Nick, Little Cat, Ooowoo
|
|
# License: MIT
|
|
# Version: 1.0.0
|
|
# Email: belloqzafarian@gmail.com
|
|
##################################################
|
|
|
|
import collections, types
|
|
|
|
from direct.distributed.ClockDelta import *
|
|
from direct.interval.IntervalGlobal import *
|
|
from direct.showbase.DirectObject import DirectObject
|
|
from direct.showbase.PythonUtil import *
|
|
|
|
from panda3d.otp import NametagGroup, WhisperPopup
|
|
|
|
from otp.otpbase import OTPLocalizer
|
|
from otp.otpbase import OTPGlobals
|
|
from otp.otpbase.PythonUtil import *
|
|
|
|
from . import MagicWordConfig
|
|
import time, random, re, json
|
|
|
|
magicWordIndex = collections.OrderedDict()
|
|
|
|
|
|
class MagicWord(DirectObject):
|
|
notify = DirectNotifyGlobal.directNotify.newCategory('MagicWord')
|
|
|
|
# Whether this Magic word should be considered "hidden"
|
|
# If your Toontown source has a page for Magic Words in the Sthickerbook, this will be useful for that
|
|
hidden = False
|
|
|
|
# Whether this Magic Word is an administrative command or not
|
|
# Good for config settings where you want to disable cheaty Magic Words, but still want moderation ones
|
|
administrative = False
|
|
|
|
# List of names that will also invoke this word - a setHP magic word might have "hp", for example
|
|
# A Magic Word will always be callable with its class name, so you don't have to put that in the aliases
|
|
aliases = None
|
|
|
|
# Description of the Magic Word
|
|
# If your Toontown source has a page for Magic Words in the Sthickerbook, this will be useful for that
|
|
desc = MagicWordConfig.MAGIC_WORD_DEFAULT_DESC
|
|
|
|
# Advanced description that gives the user a lot more information than normal
|
|
# If your Toontown source has a page for Magic Words in the Sthickerbook, this will be useful for that
|
|
advancedDesc = MagicWordConfig.MAGIC_WORD_DEFAULT_ADV_DESC
|
|
|
|
# Default example with for commands with no arguments set
|
|
# If your Toontown source has a page for Magic Words in the Sthickerbook, this will be useful for that
|
|
example = ""
|
|
|
|
# The minimum access level required to use this Magic Word
|
|
accessLevel = 'MODERATOR'
|
|
|
|
# A restriction on the Magic Word which sets what kind or set of Distributed Objects it can be used on
|
|
# By default, a Magic Word can affect everyone
|
|
affectRange = [MagicWordConfig.AFFECT_SELF, MagicWordConfig.AFFECT_OTHER, MagicWordConfig.AFFECT_BOTH]
|
|
|
|
# Where the magic word will be executed -- EXEC_LOC_CLIENT or EXEC_LOC_SERVER
|
|
execLocation = MagicWordConfig.EXEC_LOC_INVALID
|
|
|
|
# List of all arguments for this word, with the format [(type, isRequired), (type, isRequired)...]
|
|
# If the parameter is not required, you must provide a default argument: (type, False, default)
|
|
arguments = None
|
|
|
|
def __init__(self):
|
|
if self.__class__.__name__ != "MagicWord":
|
|
self.aliases = self.aliases if self.aliases is not None else []
|
|
self.aliases.insert(0, self.__class__.__name__)
|
|
self.aliases = [x.lower() for x in self.aliases]
|
|
self.arguments = self.arguments if self.arguments is not None else []
|
|
|
|
if len(self.arguments) > 0:
|
|
for arg in self.arguments:
|
|
argInfo = ""
|
|
if not arg[MagicWordConfig.ARGUMENT_REQUIRED]:
|
|
argInfo += "(default: {0})".format(arg[MagicWordConfig.ARGUMENT_DEFAULT])
|
|
self.example += "[{0}{1}] ".format(arg[MagicWordConfig.ARGUMENT_NAME], argInfo)
|
|
|
|
self.__register()
|
|
|
|
def __register(self):
|
|
for wordName in self.aliases:
|
|
if wordName in magicWordIndex:
|
|
self.notify.error('Duplicate Magic Word name or alias detected! Invalid name: {}'. format(wordName))
|
|
magicWordIndex[wordName] = {'class': self,
|
|
'classname': self.__class__.__name__,
|
|
'hidden': self.hidden,
|
|
'administrative': self.administrative,
|
|
'aliases': self.aliases,
|
|
'desc': self.desc,
|
|
'advancedDesc': self.advancedDesc,
|
|
'example': self.example,
|
|
'execLocation': self.execLocation,
|
|
'access': self.accessLevel,
|
|
'affectRange': self.affectRange,
|
|
'args': self.arguments}
|
|
|
|
def loadWord(self, air=None, cr=None, invokerId=None, targets=None, args=None):
|
|
self.air = air
|
|
self.cr = cr
|
|
self.invokerId = invokerId
|
|
self.targets = targets
|
|
self.args = args
|
|
|
|
def executeWord(self):
|
|
executedWord = None
|
|
validTargets = len(self.targets)
|
|
for avId in self.targets:
|
|
invoker = None
|
|
toon = None
|
|
if self.air:
|
|
invoker = self.air.doId2do.get(self.invokerId)
|
|
toon = self.air.doId2do.get(avId)
|
|
elif self.cr:
|
|
invoker = self.cr.doId2do.get(self.invokerId)
|
|
toon = self.cr.doId2do.get(avId)
|
|
if hasattr(toon, "getName"):
|
|
name = toon.getName()
|
|
else:
|
|
name = avId
|
|
|
|
if not self.validateTarget(toon):
|
|
if len(self.targets) > 1:
|
|
validTargets -= 1
|
|
continue
|
|
return "{} is not a valid target!".format(name)
|
|
|
|
# TODO: Should we implement locking?
|
|
# if toon.getLocked() and not self.administrative:
|
|
# if len(self.targets) > 1:
|
|
# validTargets -= 1
|
|
# continue
|
|
# return "{} is currently locked. You can only use administrative commands on them.".format(name)
|
|
|
|
if invoker.getAccessLevel() <= toon.getAccessLevel() and toon != invoker:
|
|
if len(self.targets) > 1:
|
|
validTargets -= 1
|
|
continue
|
|
targetAccess = OTPGlobals.AccessLevelDebug2Name.get(OTPGlobals.AccessLevelInt2Name.get(toon.getAccessLevel()))
|
|
invokerAccess = OTPGlobals.AccessLevelDebug2Name.get(OTPGlobals.AccessLevelInt2Name.get(invoker.getAccessLevel()))
|
|
return "You don't have a high enough Access Level to target {0}! Their Access Level: {1}. Your Access Level: {2}.".format(name, targetAccess, invokerAccess)
|
|
|
|
if self.execLocation == MagicWordConfig.EXEC_LOC_CLIENT:
|
|
self.args = json.loads(self.args)
|
|
|
|
executedWord = self.handleWord(invoker, avId, toon, *self.args)
|
|
# If you're only using the Magic Word on one person and there is a response, return that response
|
|
if executedWord and len(self.targets) == 1:
|
|
return executedWord
|
|
# If the amount of targets is higher than one...
|
|
elif validTargets > 0:
|
|
# And it's only 1, and that's yourself, return None
|
|
if validTargets == 1 and self.invokerId in self.targets:
|
|
return None
|
|
# Otherwise, state how many targets you executed it on
|
|
return "Magic Word successfully executed on %s target(s)." % validTargets
|
|
else:
|
|
return "Magic Word unable to execute on any targets."
|
|
|
|
def validateTarget(self, target):
|
|
if self.air:
|
|
from toontown.toon.DistributedToonAI import DistributedToonAI
|
|
return isinstance(target, DistributedToonAI)
|
|
elif self.cr:
|
|
from toontown.toon.DistributedToon import DistributedToon
|
|
return isinstance(target, DistributedToon)
|
|
return False
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
raise NotImplementedError
|
|
|
|
class SetHP(MagicWord):
|
|
aliases = ["hp", "setlaff", "laff"]
|
|
desc = "Sets the target's current laff."
|
|
advancedDesc = "This Magic Word will change the current amount of laff points the target has to whichever " \
|
|
"value you specify. You are only allowed to specify a value between -1 and the target's maximum " \
|
|
"laff points. If you specify a value less than 1, the target will instantly go sad unless they " \
|
|
"are in Immortal Mode."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("hp", int, True)]
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
hp = args[0]
|
|
|
|
if not -1 <= hp <= toon.getMaxHp():
|
|
return "Can't set {0}'s laff to {1}! Specify a value between -1 and {0}'s max laff ({2}).".format(
|
|
toon.getName(), hp, toon.getMaxHp())
|
|
|
|
if hp <= 0 and toon.immortalMode:
|
|
return "Can't set {0}'s laff to {1} because they are in Immortal Mode!".format(toon.getName(), hp)
|
|
|
|
toon.b_setHp(hp)
|
|
return "{}'s laff has been set to {}.".format(toon.getName(), hp)
|
|
|
|
class SetMaxHP(MagicWord):
|
|
aliases = ["maxhp", "setmaxlaff", "maxlaff"]
|
|
desc = "Sets the target's max laff."
|
|
advancedDesc = "This Magic Word will change the maximum amount of laff points the target has to whichever value " \
|
|
"you specify. You are only allowed to specify a value between 15 and 137 laff points."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("maxhp", int, True)]
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
maxhp = args[0]
|
|
|
|
if not 15 <= maxhp <= 137:
|
|
return "Can't set {}'s max laff to {}! Specify a value between 15 and 137.".format(toon.getName(), maxhp)
|
|
|
|
toon.b_setMaxHp(maxhp)
|
|
toon.toonUp(maxhp)
|
|
return "{}'s max laff has been set to {}.".format(toon.getName(), maxhp)
|
|
|
|
class ToggleOobe(MagicWord):
|
|
aliases = ["oobe"]
|
|
desc = "Toggles the out of body experience mode, which lets you move the camera freely."
|
|
advancedDesc = "This Magic Word will toggle what is known as 'Out Of Body Experience' Mode, hence the name " \
|
|
"'Oobe'. When this mode is active, you are able to move the camera around with your mouse- " \
|
|
"though your camera will still follow your Toon."
|
|
execLocation = MagicWordConfig.EXEC_LOC_CLIENT
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
base.oobe()
|
|
return "Oobe mode has been toggled."
|
|
|
|
class ToggleRun(MagicWord):
|
|
aliases = ["run"]
|
|
desc = "Toggles run mode, which gives you a faster running speed."
|
|
advancedDesc = "This Magic Word will toggle Run Mode. When this mode is active, the target can run around at a " \
|
|
"very fast speed."
|
|
execLocation = MagicWordConfig.EXEC_LOC_CLIENT
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
from direct.showbase.InputStateGlobal import inputState
|
|
inputState.set('debugRunning', not inputState.isSet('debugRunning'))
|
|
return "Run mode has been toggled."
|
|
|
|
class MaxToon(MagicWord):
|
|
aliases = ["max", "idkfa"]
|
|
desc = "Maxes your target toon."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
accessLevel = 'ADMIN'
|
|
|
|
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.
|
|
toon.b_setTrackAccess([1, 1, 1, 1, 1, 1, 1])
|
|
|
|
toon.b_setMaxCarry(ToontownGlobals.MaxCarryLimit)
|
|
toon.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit)
|
|
|
|
toon.experience.maxOutExp()
|
|
toon.d_setExperience(toon.experience.makeNetString())
|
|
|
|
toon.inventory.maxOutInv()
|
|
toon.d_setInventory(toon.inventory.makeNetString())
|
|
|
|
toon.b_setMaxHp(ToontownGlobals.MaxHpLimit)
|
|
toon.b_setHp(ToontownGlobals.MaxHpLimit)
|
|
|
|
toon.b_setMaxMoney(250)
|
|
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 Inventory(MagicWord):
|
|
# by default restock the inventory
|
|
aliases = ['gags', 'inv']
|
|
desc = 'This allows you to modify your inventory in various ways.'
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("command", str, False, ''), ("track", str, False, ""), ("level", int, False, 0), ("amount", int, False, 0)]
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
command = args[0]
|
|
# the list of words that can be used to restock the inventory
|
|
restockInvWords = ['restock', 'max', 'all', '', 'fill']
|
|
# the list of words that can be used to empty the inventory
|
|
emptyInvWords = ['empty', 'zero', 'null', 'clear', 'none', 'reset']
|
|
if command in restockInvWords:
|
|
toon.inventory.maxOutInv()
|
|
toon.d_setInventory(toon.inventory.makeNetString())
|
|
return ("Maxing out inventory for " + toon.getName() + ".")
|
|
if command in emptyInvWords:
|
|
toon.inventory.zeroInv()
|
|
toon.d_setInventory(toon.inventory.makeNetString())
|
|
return ("Zeroing inventory for " + toon.getName() + ".")
|
|
|
|
class SetPinkSlips(MagicWord):
|
|
# this command gives the target toon the specified amount of pink slips
|
|
# default is 255
|
|
aliases = ["pinkslips", "fires", 'setfires']
|
|
desc = "Gives the target toon the specified amount of pink slips."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("amount", int, False, 255)]
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
toon.b_setPinkSlips(args[0])
|
|
return f"Gave {toon.getName()} {args[0]} pink slips!"
|
|
|
|
class AbortMinigame(MagicWord):
|
|
aliases = ["exitgame", "exitminigame", "quitgame", "quitminigame", "skipgame", "skipminigame"]
|
|
desc = "Aborts an ongoing minigame."
|
|
execLocation = MagicWordConfig.EXEC_LOC_CLIENT
|
|
arguments = []
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
messenger.send("minigameAbort")
|
|
return "Requested minigame abort."
|
|
|
|
class SkipMiniGolfHole(MagicWord):
|
|
aliases = ["skipgolfhole", "skipgolf", "skiphole"]
|
|
desc = "Skips the current golf hole."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = []
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI
|
|
course = None
|
|
for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it.
|
|
if isinstance(do, DistributedGolfCourseAI):
|
|
if invoker.doId in do.avIdList:
|
|
course = do
|
|
break
|
|
if not course:
|
|
return "You aren't in a golf course!"
|
|
|
|
if course.isPlayingLastHole(): # If the Toon is on the final hole, calling holeOver() will softlock, so instead we move onto the reward screen.
|
|
course.demand('WaitReward')
|
|
else:
|
|
course.holeOver()
|
|
|
|
return "Skipped the current hole."
|
|
|
|
class AbortGolfCourse(MagicWord):
|
|
aliases = ["abortminigolf", "abortgolf", "abortcourse", "leavegolf", "leavecourse"]
|
|
desc = "Aborts the current golf course."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = []
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI
|
|
course = None
|
|
for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it.
|
|
if isinstance(do, DistributedGolfCourseAI):
|
|
if invoker.doId in do.avIdList:
|
|
course = do
|
|
break
|
|
if not course:
|
|
return "You aren't in a golf course!"
|
|
|
|
course.setCourseAbort()
|
|
|
|
return "Aborted golf course."
|
|
|
|
class Minigame(MagicWord):
|
|
aliases = ["mg"]
|
|
desc = "Teleport to or request the next trolley minigame."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("command", str, True), ("minigame", str, False, ''), ("difficulty", float, False, 0)]
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
command = args[0]
|
|
minigame = args[1]
|
|
difficulty = args[2]
|
|
|
|
from toontown.toonbase import ToontownGlobals
|
|
from toontown.hood import ZoneUtil
|
|
from toontown.minigame import MinigameCreatorAI
|
|
if command in ToontownGlobals.MinigameNames:
|
|
# Shortcut
|
|
minigame = args[0]
|
|
try:
|
|
difficulty = float(args[2])
|
|
except ValueError:
|
|
difficulty = 0
|
|
|
|
if toon.zoneId in MinigameCreatorAI.MinigameZoneRefs:
|
|
# Already in minigame zone, assume request
|
|
command = "request"
|
|
elif toon.zoneId == ZoneUtil.getSafeZoneId(toon.zoneId):
|
|
# Assume teleport
|
|
command = "teleport"
|
|
else:
|
|
# Request by default
|
|
command = "request"
|
|
|
|
isTeleport = command in ('teleport', 'tp')
|
|
isRequest = command in ('request', 'next')
|
|
|
|
mgId = None
|
|
mgDiff = None if difficulty == 0 else difficulty
|
|
mgKeep = None
|
|
mgSzId = ZoneUtil.getSafeZoneId(toon.zoneId) if isTeleport else None
|
|
|
|
if not any ((isTeleport, isRequest)):
|
|
return f"Unknown command or minigame \"{command}\". Valid commands: \"teleport\", \"request\", or a minigame to automatically teleport or request"
|
|
|
|
try:
|
|
mgId = int(minigame)
|
|
if mgId not in ToontownGlobals.MinigameIDs:
|
|
return f"Unknown minigame ID {mgId}."
|
|
except:
|
|
if minigame not in ToontownGlobals.MinigameNames:
|
|
return f"Unknown minigame name \"{minigame}\"."
|
|
mgId = ToontownGlobals.MinigameNames.get(minigame)
|
|
|
|
if any((isTeleport, isRequest)):
|
|
if isTeleport:
|
|
if ZoneUtil.isDynamicZone(toon.zoneId) or not toon.zoneId == mgSzId:
|
|
return "Target needs to be in a playground to teleport to a minigame."
|
|
mgSzId = ToontownGlobals.ToontownCentral if ZoneUtil.isWelcomeValley(mgSzId) else mgSzId
|
|
MinigameCreatorAI.RequestMinigame[avId] = (mgId, mgKeep, mgDiff, mgSzId)
|
|
if isTeleport:
|
|
try:
|
|
result = MinigameCreatorAI.createMinigame(self.air, [avId], mgSzId)
|
|
except:
|
|
return f"Unable to create \"{minigame}\" minigame"
|
|
|
|
minigameZone = result['minigameZone']
|
|
retStr = f"Teleporting {toon.getName()} to minigame \"{minigame}\""
|
|
if mgDiff:
|
|
retStr += f" with difficulty {mgDiff}"
|
|
return retStr + "...", avId, ["minigame", "minigame", "", mgSzId, minigameZone, 0]
|
|
|
|
# isRequest
|
|
retStr = f"Successfully requested minigame \"{minigame}\""
|
|
if mgDiff:
|
|
retStr += f" with difficulty {mgDiff}"
|
|
return retStr + "."
|
|
|
|
return f"Unknown command or minigame \"{command}\". Valid commands: \"teleport\", \"request\", or a minigame to automatically teleport or request"
|
|
|
|
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\""
|
|
|
|
class Factory(MagicWord):
|
|
desc = "Quickly start a Sellbot Factory."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [('sideEnterace', int, False, 0)]
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
if not hasattr(self.air, "factoryMgr"):
|
|
return "No factory manager."
|
|
|
|
from toontown.toonbase import ToontownGlobals
|
|
zoneId = self.air.factoryMgr.createFactory(ToontownGlobals.SellbotFactoryInt, 1 if args[0] > 0 else 0, [avId])
|
|
return "Created factory, teleporting...", avId, ["cogHQLoader", "factoryInterior", "", ToontownGlobals.SellbotHQ, zoneId, 0]
|
|
|
|
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 AllBossCogs
|
|
boss = None
|
|
for bc in AllBossCogs:
|
|
if bc.isToonKnown(invoker.doId):
|
|
boss = bc
|
|
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')
|
|
|
|
self.acceptOnce(boss.uniqueName('BossDone'), self.__destroyBoss, extraArgs=[boss])
|
|
|
|
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]
|
|
|
|
elif command == "list":
|
|
# List all the ongoing boss battles.
|
|
dept2name = {'c': 'ceo',
|
|
'l': 'cj',
|
|
'm': 'cfo',
|
|
's': 'vp'}
|
|
name2dept = invertDict(dept2name)
|
|
|
|
if not AllBossCogs:
|
|
return "No ongoing boss battles."
|
|
|
|
respText = "\nBoss Battles:"
|
|
|
|
if type:
|
|
# Filter by boss type
|
|
dept = name2dept.get(type)
|
|
if not dept:
|
|
return f"Can't filter by unknown type \"{type.upper()}\""
|
|
bossBattles = (boss for boss in AllBossCogs if boss.dept == dept)
|
|
else:
|
|
bossBattles = AllBossCogs
|
|
|
|
for boss in bossBattles:
|
|
index = AllBossCogs.index(boss)
|
|
respText += f"\n - #{index}: {dept2name.get(boss.dept, '???').upper()}, {boss.zoneId}, {boss.state}, {len(boss.involvedToons)}"
|
|
return respText
|
|
|
|
elif command == "join":
|
|
# Join an ongoing boss battle.
|
|
if boss:
|
|
return "You're already in a boss battle. Please finish this one."
|
|
try:
|
|
index = int(type)
|
|
except ValueError:
|
|
return "Boss index not an integer!"
|
|
|
|
if index not in range(len(AllBossCogs)):
|
|
return "Index out of range!"
|
|
|
|
boss = AllBossCogs[index]
|
|
return "Teleporting to boss battle...", ["cogHQLoader", "cogHQBossBattle", "", 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\"."
|
|
|
|
def __destroyBoss(self, boss):
|
|
bossZone = boss.zoneId
|
|
boss.requestDelete()
|
|
self.air.deallocateZone(bossZone)
|
|
|
|
class GlobalTeleport(MagicWord):
|
|
aliases = ["globaltp", "tpaccess"]
|
|
desc = "Enables teleport access to all zones."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
from toontown.toonbase import ToontownGlobals
|
|
toon.b_setHoodsVisited(ToontownGlobals.HoodsForTeleportAll)
|
|
toon.b_setTeleportAccess(ToontownGlobals.HoodsForTeleportAll)
|
|
return f"Enabled teleport access to all zones for {toon.getName()}."
|
|
|
|
class Teleport(MagicWord):
|
|
aliases = ["tp", "goto"]
|
|
desc = "Teleport to a specified zone."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("zoneName", str, False, '')]
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
from toontown.toonbase import ToontownGlobals
|
|
zoneName = args[0]
|
|
|
|
# Can add stuff like streets to this too if you wanted, but if you do you'll want it to be a valid zone on that street. eg: 2100 is invalid, but any value 2101 to 2156 is fine.
|
|
# so if you wanted to add a silly street key, theroetically you could do something like this: 'sillystreet': ToontownGlobals.SillyStreet +1,
|
|
zoneName2Id = {'ttc': ToontownGlobals.ToontownCentral,
|
|
'dd': ToontownGlobals.DonaldsDock,
|
|
'dg': ToontownGlobals.DaisyGardens,
|
|
'mml': ToontownGlobals.MinniesMelodyland,
|
|
'tb': ToontownGlobals.TheBrrrgh,
|
|
'ddl': ToontownGlobals.DonaldsDreamland,
|
|
'gs': ToontownGlobals.GoofySpeedway,
|
|
'oz': ToontownGlobals.OutdoorZone,
|
|
'aa': ToontownGlobals.OutdoorZone,
|
|
'gz': ToontownGlobals.GolfZone,
|
|
'sbhq': ToontownGlobals.SellbotHQ,
|
|
'factory': ToontownGlobals.SellbotFactoryExt,
|
|
'cbhq': ToontownGlobals.CashbotHQ,
|
|
'lbhq': ToontownGlobals.LawbotHQ,
|
|
'bbhq': ToontownGlobals.BossbotHQ}
|
|
|
|
try:
|
|
zone = zoneName2Id[zoneName]
|
|
except KeyError:
|
|
return "Unknown zone name!"
|
|
|
|
toon.d_magicWordTeleport(zone)
|
|
|
|
return f"Requested to teleport {toon.getName()} to zone {zone}."
|
|
|
|
class ToggleSleep(MagicWord):
|
|
aliases = ["sleep", "nosleep", "neversleep", "togglesleeping", "insomnia"]
|
|
desc = "Toggles sleeping for the target."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
toon.d_toggleSleep()
|
|
return f"Toggled sleeping for {toon.getName()}."
|
|
|
|
class ToggleImmortal(MagicWord):
|
|
aliases = ["immortal", "invincible", "invulnerable"]
|
|
desc = "Toggle immortal mode. This makes the Toon immune to damage."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
toon.setImmortalMode(not toon.immortalMode)
|
|
return f"Toggled immortal mode for {toon.getName()}"
|
|
|
|
class ToggleGhost(MagicWord):
|
|
aliases = ["ghost", "invisible", "spy"]
|
|
desc = "Toggle ghost mode."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
# 1 is for the attic, 2 enables you to see yourself other ghost toons. 0 is off.
|
|
toon.b_setGhostMode(2 if not toon.ghostMode else 0) # As it's primarily for moderation purposes, we set it to 2 here, or 0 if it's already on.
|
|
return f"Toggled ghost mode for {toon.getName()}"
|
|
|
|
class SetGM(MagicWord):
|
|
aliases = ["icon", "seticon", "gm", "gmicon", "setgmicon"]
|
|
desc = "Sets the GM icon on the target."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("iconRequest", int, False, 0),]
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
from toontown.toonbase import TTLocalizer
|
|
|
|
iconRequest = args[0]
|
|
if iconRequest > len(TTLocalizer.GM_NAMES) or iconRequest < 0:
|
|
return "Invalid GM icon ID!"
|
|
|
|
toon.b_setGM(0) # Reset it first, otherwise the Toon keeps the old icon, but the name still changes.
|
|
toon.b_setGM(iconRequest)
|
|
return f"GM icon set to {iconRequest} for {toon.getName()}"
|
|
|
|
class SetMaxCarry(MagicWord):
|
|
aliases = ["gagpouch", "pouch", "gagcapacity"]
|
|
desc = "Set a Toon's gag pouch size."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("pouchSize", int, True)]
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
pouchSize = args[0]
|
|
|
|
if pouchSize > 255 or pouchSize < 0:
|
|
return "Specified pouch size must be between 1 and 255."
|
|
|
|
toon.b_setMaxCarry(pouchSize)
|
|
return f"Set gag pouch size to {pouchSize} for {toon.getName()}"
|
|
|
|
class Fireworks(MagicWord):
|
|
aliases = ["firework"]
|
|
desc = "Starts a firework show."
|
|
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
|
arguments = [("name", str, False, "newyear"), ("hood", str, False, "")]
|
|
|
|
# List of firework shows currently in progress
|
|
fireworkShows = {}
|
|
|
|
def handleWord(self, invoker, avId, toon, *args):
|
|
name = args[0]
|
|
hood = args[1]
|
|
|
|
from toontown.toonbase import ToontownGlobals
|
|
from toontown.parties import PartyGlobals
|
|
name2showId = {
|
|
'newyear': ToontownGlobals.NEWYEARS_FIREWORKS,
|
|
'newyears': ToontownGlobals.NEWYEARS_FIREWORKS,
|
|
'summer': ToontownGlobals.JULY4_FIREWORKS,
|
|
'combo': ToontownGlobals.COMBO_FIREWORKS,
|
|
'party': PartyGlobals.FireworkShows.Summer
|
|
}
|
|
|
|
if name not in name2showId:
|
|
return f"Unknown firework name \"{name}\". Valid names: {list(name2showId.keys())}"
|
|
showId = name2showId[name]
|
|
|
|
zoneToStyleDict = {
|
|
ToontownGlobals.DonaldsDock : 5,
|
|
ToontownGlobals.ToontownCentral : 0,
|
|
ToontownGlobals.TheBrrrgh : 4,
|
|
ToontownGlobals.MinniesMelodyland : 3,
|
|
ToontownGlobals.DaisyGardens : 1,
|
|
ToontownGlobals.OutdoorZone : 0,
|
|
ToontownGlobals.GoofySpeedway : 0,
|
|
ToontownGlobals.DonaldsDreamland : 2
|
|
}
|
|
|
|
from toontown.hood import ZoneUtil
|
|
zones = []
|
|
if not hood:
|
|
zones = (toon.zoneId,)
|
|
elif hood == "all":
|
|
zones = zoneToStyleDict.keys()
|
|
else:
|
|
return "Missing hood argument."
|
|
|
|
# Generate our firework shows
|
|
from toontown.effects.DistributedFireworkShowAI import DistributedFireworkShowAI
|
|
count = 0
|
|
for zone in zones:
|
|
if zone not in self.fireworkShows:
|
|
show = DistributedFireworkShowAI(self.air, self)
|
|
show.generateWithRequired(zone)
|
|
self.fireworkShows[zone] = show
|
|
show.d_startShow(showId, zoneToStyleDict.get(zone, 0))
|
|
count += 1
|
|
|
|
return f"Started firework {'show' if count == 1 else 'shows'} in {count} {'zone' if count == 1 else 'zones'}!"
|
|
|
|
def stopShow(self, zoneId):
|
|
if zoneId in self.fireworkShows:
|
|
show = self.fireworkShows[zoneId]
|
|
show.requestDelete()
|
|
del self.fireworkShows[zoneId]
|
|
|
|
|
|
# Instantiate all classes defined here to register them.
|
|
# A bit hacky, but better than the old system
|
|
for item in list(globals().values()):
|
|
if isinstance(item, type) and issubclass(item, MagicWord):
|
|
i = item()
|