spellbook: Implement magic word system
This commit is contained in:
parent
20ee51972c
commit
44a90f07f8
12 changed files with 922 additions and 5 deletions
|
@ -70,7 +70,7 @@ from toontown.estate import DistributedHouse/AI
|
||||||
from toontown.estate import DistributedHouseInterior/AI
|
from toontown.estate import DistributedHouseInterior/AI
|
||||||
from toontown.estate import DistributedGarden/AI
|
from toontown.estate import DistributedGarden/AI
|
||||||
from toontown.shtiker import DeleteManager/AI
|
from toontown.shtiker import DeleteManager/AI
|
||||||
from toontown.ai import ToontownMagicWordManager/AI
|
from toontown.spellbook import ToontownMagicWordManager/AI
|
||||||
from toontown.ai import NewsManager/AI
|
from toontown.ai import NewsManager/AI
|
||||||
from toontown.shtiker import PurchaseManager/AI
|
from toontown.shtiker import PurchaseManager/AI
|
||||||
from toontown.shtiker import NewbiePurchaseManager/AI
|
from toontown.shtiker import NewbiePurchaseManager/AI
|
||||||
|
@ -1406,8 +1406,10 @@ dclass DeleteManager : DistributedObject {
|
||||||
setInventory(blob) airecv clsend;
|
setInventory(blob) airecv clsend;
|
||||||
};
|
};
|
||||||
|
|
||||||
dclass ToontownMagicWordManager : MagicWordManager {
|
dclass ToontownMagicWordManager : DistributedObject {
|
||||||
requestTeleport(string, string, uint32, uint32, uint32);
|
requestExecuteMagicWord(int8, int8, int16, uint32, string) airecv clsend;
|
||||||
|
executeMagicWord(string, string, uint32[], blob, int8, int8, int16, uint32);
|
||||||
|
generateResponse(string, string, blob, string, int8, int8, int16, uint32, string);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct weeklyCalendarHoliday {
|
struct weeklyCalendarHoliday {
|
||||||
|
@ -3265,4 +3267,3 @@ dclass DistributedTrashcanZeroMgr : DistributedPhaseEventMgr {
|
||||||
|
|
||||||
dclass DistributedSillyMeterMgr : DistributedPhaseEventMgr {
|
dclass DistributedSillyMeterMgr : DistributedPhaseEventMgr {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -457,3 +457,6 @@ class DistributedPlayer(DistributedAvatar.DistributedAvatar, PlayerBase.PlayerBa
|
||||||
|
|
||||||
def setAccessLevel(self, accessLevel):
|
def setAccessLevel(self, accessLevel):
|
||||||
self.accessLevel = accessLevel
|
self.accessLevel = accessLevel
|
||||||
|
|
||||||
|
def getAccessLevel(self):
|
||||||
|
return self.accessLevel
|
||||||
|
|
|
@ -113,6 +113,9 @@ class DistributedPlayerAI(DistributedAvatarAI.DistributedAvatarAI, PlayerBase.Pl
|
||||||
def setAccessLevel(self, accessLevel):
|
def setAccessLevel(self, accessLevel):
|
||||||
self.accessLevel = accessLevel
|
self.accessLevel = accessLevel
|
||||||
|
|
||||||
|
def getAccessLevel(self):
|
||||||
|
return self.accessLevel
|
||||||
|
|
||||||
def d_setFriendsList(self, friendsList):
|
def d_setFriendsList(self, friendsList):
|
||||||
self.sendUpdate('setFriendsList', [friendsList])
|
self.sendUpdate('setFriendsList', [friendsList])
|
||||||
|
|
||||||
|
|
|
@ -617,7 +617,7 @@ class TalkAssistant(DirectObject.DirectObject):
|
||||||
|
|
||||||
def sendOpenTalk(self, message):
|
def sendOpenTalk(self, message):
|
||||||
error = None
|
error = None
|
||||||
if base.cr.wantMagicWords and len(message) > 0 and message[0] == '~':
|
if base.cr.magicWordManager and base.localAvatar.getAccessLevel() > OTPGlobals.AccessLevelName2Int.get('NO_ACCESS') and len(message) > 0 and message[0] == base.cr.magicWordManager.chatPrefix:
|
||||||
messenger.send('magicWord', [message])
|
messenger.send('magicWord', [message])
|
||||||
self.receiveDeveloperMessage(message)
|
self.receiveDeveloperMessage(message)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -44,6 +44,7 @@ from toontown.racing.DistributedViewPadAI import DistributedViewPadAI
|
||||||
from toontown.racing.RaceManagerAI import RaceManagerAI
|
from toontown.racing.RaceManagerAI import RaceManagerAI
|
||||||
from toontown.safezone.SafeZoneManagerAI import SafeZoneManagerAI
|
from toontown.safezone.SafeZoneManagerAI import SafeZoneManagerAI
|
||||||
from toontown.shtiker.CogPageManagerAI import CogPageManagerAI
|
from toontown.shtiker.CogPageManagerAI import CogPageManagerAI
|
||||||
|
from toontown.spellbook.ToontownMagicWordManagerAI import ToontownMagicWordManagerAI
|
||||||
from toontown.suit.SuitInvasionManagerAI import SuitInvasionManagerAI
|
from toontown.suit.SuitInvasionManagerAI import SuitInvasionManagerAI
|
||||||
from toontown.toon import NPCToons
|
from toontown.toon import NPCToons
|
||||||
from toontown.toonbase import ToontownGlobals
|
from toontown.toonbase import ToontownGlobals
|
||||||
|
@ -84,6 +85,7 @@ class ToontownAIRepository(ToontownInternalRepository):
|
||||||
self.catalogManager = None
|
self.catalogManager = None
|
||||||
self.trophyMgr = None
|
self.trophyMgr = None
|
||||||
self.safeZoneManager = None
|
self.safeZoneManager = None
|
||||||
|
self.magicWordManager = None
|
||||||
self.zoneTable = {}
|
self.zoneTable = {}
|
||||||
self.dnaStoreMap = {}
|
self.dnaStoreMap = {}
|
||||||
self.dnaDataMap = {}
|
self.dnaDataMap = {}
|
||||||
|
@ -202,6 +204,10 @@ class ToontownAIRepository(ToontownInternalRepository):
|
||||||
self.safeZoneManager = SafeZoneManagerAI(self)
|
self.safeZoneManager = SafeZoneManagerAI(self)
|
||||||
self.safeZoneManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
|
self.safeZoneManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
|
||||||
|
|
||||||
|
# Generate our magic word manager...
|
||||||
|
self.magicWordManager = ToontownMagicWordManagerAI(self)
|
||||||
|
self.magicWordManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
|
||||||
|
|
||||||
def generateHood(self, hoodConstructor, zoneId):
|
def generateHood(self, hoodConstructor, zoneId):
|
||||||
# Bossbot HQ doesn't use DNA, so we skip over that.
|
# Bossbot HQ doesn't use DNA, so we skip over that.
|
||||||
if zoneId != ToontownGlobals.BossbotHQ:
|
if zoneId != ToontownGlobals.BossbotHQ:
|
||||||
|
|
|
@ -106,6 +106,7 @@ class ToontownClientRepository(OTPClientRepository.OTPClientRepository):
|
||||||
self.streetSign = None
|
self.streetSign = None
|
||||||
self.furnitureManager = None
|
self.furnitureManager = None
|
||||||
self.objectManager = None
|
self.objectManager = None
|
||||||
|
self.magicWordManager = None
|
||||||
self.friendsMap = {}
|
self.friendsMap = {}
|
||||||
self.friendsOnline = {}
|
self.friendsOnline = {}
|
||||||
self.friendsMapPending = 0
|
self.friendsMapPending = 0
|
||||||
|
|
86
toontown/spellbook/MagicWordConfig.py
Normal file
86
toontown/spellbook/MagicWordConfig.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
##################################################
|
||||||
|
# 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
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
OUTGOING_CHAT_MESSAGE_NAME = 'magicWord'
|
||||||
|
CLICKED_NAMETAG_MESSAGE_NAME = 'clickedNametag'
|
||||||
|
FOCUS_OUT_MESSAGE_NAME = 'focusOut'
|
||||||
|
|
||||||
|
PREFIX_DEFAULT = '~'
|
||||||
|
PREFIX_ALLOWED = ['~', '?', '/', '<', ':', ';']
|
||||||
|
if config.GetBool('exec-chat', False):
|
||||||
|
PREFIX_ALLOWED.append('>')
|
||||||
|
|
||||||
|
WIZARD_DEFAULT = 'Spellbook'
|
||||||
|
|
||||||
|
MAGIC_WORD_SUCCESS_PHRASES = ['Alakazam!', 'Voila!', 'Ta-da!', 'Presto!', 'Abracadabra!']
|
||||||
|
MAGIC_WORD_RESPONSES = {
|
||||||
|
"SuccessNoResp": 'response will be randomly selected from MAGIC_WORD_SUCCESS_PHRASES',
|
||||||
|
"Success": 'response will be provided by magic word',
|
||||||
|
"Teleporting": 'Yikes! Don\'t use Magic words while switching zones!',
|
||||||
|
"OtherTeleporting": 'Your target is currently switching zones!',
|
||||||
|
"BadWord": 'Uh-oh! This Magic Word doesn\'t exist!',
|
||||||
|
"CloseWord": 'This Magic Word doesn\'t exist! Did you mean {}?',
|
||||||
|
"NoEffect": "This word doesn't affect anybody!",
|
||||||
|
"BadTarget": 'Invalid target specified!',
|
||||||
|
"NoAccessToTarget": "You don't have a high enough Access Level to target them!",
|
||||||
|
"NoAccessSingleTarget": "You don't have a high enough Access Level to target {}! Their Access Level: {}. Your Access Level: {}.",
|
||||||
|
"NoTarget": 'Unable to find a target!',
|
||||||
|
"NoAccess": 'Your Toon does not have enough power to use this Magic Word!',
|
||||||
|
"NotEnoughArgs": 'This command takes at least {}!',
|
||||||
|
"TooManyArgs": 'This command takes at most {}!',
|
||||||
|
"BadArgs": 'These arguments are of the wrong type!',
|
||||||
|
"CannotTarget": 'You cannot target other players with this Magic Word!',
|
||||||
|
"Locked": 'You are temporarily locked down and cannot use Magic Words.',
|
||||||
|
"RestrictionOther": 'You may only target one other player with this Magic Word!',
|
||||||
|
'NonCheaty': 'You cannot use cheaty Magic Words at this time!',
|
||||||
|
'Tutorial': 'You cannot use Magic Words in the Toontorial!'
|
||||||
|
}
|
||||||
|
MAGIC_WORD_NO_RESPONSE = "...I don't know how to respond!"
|
||||||
|
HAS_EXTRA_MESSAGE_DATA = ["NotEnoughArgs", "TooManyArgs", "CloseWord"]
|
||||||
|
|
||||||
|
MAGIC_WORD_DEFAULT_DESC = 'A simple Magic Word.'
|
||||||
|
MAGIC_WORD_DEFAULT_ADV_DESC = 'This Magic Word does a lot of things, because reasons.'
|
||||||
|
|
||||||
|
AFFECT_TYPES = ['singular', 'zone', 'server', 'rank']
|
||||||
|
AFFECT_TYPES_NAMES = ['Everyone in this zone', 'The entire server', 'Everyone with an Access Level of']
|
||||||
|
AFFECT_NONE = -1
|
||||||
|
AFFECT_SELF = 0
|
||||||
|
AFFECT_OTHER = 1
|
||||||
|
AFFECT_BOTH = 2
|
||||||
|
AFFECT_NORMAL = 0
|
||||||
|
AFFECT_ZONE = 1
|
||||||
|
AFFECT_SERVER = 2
|
||||||
|
AFFECT_RANK = 3
|
||||||
|
GROUP_AFFECTS = [AFFECT_ZONE, AFFECT_SERVER, AFFECT_RANK]
|
||||||
|
|
||||||
|
EXEC_LOC_INVALID = -1
|
||||||
|
EXEC_LOC_CLIENT = 0
|
||||||
|
EXEC_LOC_SERVER = 1
|
||||||
|
|
||||||
|
ARGUMENT_NAME = 0
|
||||||
|
ARGUMENT_TYPE = 1
|
||||||
|
ARGUMENT_REQUIRED = 2
|
||||||
|
ARGUMENT_DEFAULT = 3
|
||||||
|
|
||||||
|
CUSTOM_SPELLBOOK_DEFAULT = '''{
|
||||||
|
"words":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "SetPos",
|
||||||
|
"access": "MODERATOR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GetPos",
|
||||||
|
"access": "MODERATOR"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
'''
|
253
toontown/spellbook/MagicWordIndex.py
Normal file
253
toontown/spellbook/MagicWordIndex.py
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
##################################################
|
||||||
|
# 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 libotp import NametagGroup, WhisperPopup
|
||||||
|
|
||||||
|
from otp.otpbase import OTPLocalizer
|
||||||
|
from otp.otpbase import OTPGlobals
|
||||||
|
|
||||||
|
from . import MagicWordConfig
|
||||||
|
import time, random, re, json
|
||||||
|
|
||||||
|
magicWordIndex = collections.OrderedDict()
|
||||||
|
|
||||||
|
|
||||||
|
class MagicWord:
|
||||||
|
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. You can also toggle this mode by pressing the " \
|
||||||
|
"'F4' key, or whichever other keybind you have set."
|
||||||
|
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. This running speed stacks with other speed multipliers, such as the one given" \
|
||||||
|
"by the 'SetSpeed' Magic Word. You will automatically toggle Run Mode by using the 'EnableGod' " \
|
||||||
|
"Magic Word."
|
||||||
|
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."
|
||||||
|
|
||||||
|
|
||||||
|
# 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()
|
262
toontown/spellbook/ToontownMagicWordManager.py
Normal file
262
toontown/spellbook/ToontownMagicWordManager.py
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
##################################################
|
||||||
|
# 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
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
from direct.directnotify import DirectNotifyGlobal
|
||||||
|
from direct.distributed import DistributedObject
|
||||||
|
|
||||||
|
from libotp import WhisperPopup
|
||||||
|
from otp.otpbase.OTPGlobals import *
|
||||||
|
|
||||||
|
from toontown.friends import FriendHandle
|
||||||
|
from toontown.spellbook.MagicWordConfig import *
|
||||||
|
from toontown.spellbook.MagicWordIndex import *
|
||||||
|
from toontown.toon import Toon
|
||||||
|
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
|
||||||
|
MagicWordIndex = magicWordIndex.copy()
|
||||||
|
|
||||||
|
|
||||||
|
class ToontownMagicWordManager(DistributedObject.DistributedObject):
|
||||||
|
notify = DirectNotifyGlobal.directNotify.newCategory('ToontownMagicWordManager')
|
||||||
|
neverDisable = 1
|
||||||
|
|
||||||
|
def __init__(self, cr):
|
||||||
|
DistributedObject.DistributedObject.__init__(self, cr)
|
||||||
|
base.cr.magicWordManager = self
|
||||||
|
|
||||||
|
# The default chat prefix we use to determine if said phrase is a Magic Word
|
||||||
|
self.chatPrefix = PREFIX_DEFAULT
|
||||||
|
|
||||||
|
# The default name of the "wizard" that returns responses when executing Magic Words
|
||||||
|
self.wizardName = WIZARD_DEFAULT
|
||||||
|
|
||||||
|
# Keep track of the last clicked avatar for targeting purposes
|
||||||
|
self.lastClickedAvId = 0
|
||||||
|
|
||||||
|
def announceGenerate(self):
|
||||||
|
DistributedObject.DistributedObject.announceGenerate(self)
|
||||||
|
|
||||||
|
# Only use a custom Magic Word activator if the index is allowed
|
||||||
|
# TODO: Uncomment after adding settings support
|
||||||
|
activatorIndex = 0 # base.settings.getInt('game', 'magic-word-activator', 0)
|
||||||
|
if 0 <= activatorIndex <= (len(PREFIX_ALLOWED) - 1):
|
||||||
|
self.chatPrefix = PREFIX_ALLOWED[activatorIndex]
|
||||||
|
|
||||||
|
# Accept events such as outgoing chat messages and clicking on nametags
|
||||||
|
self.accept(OUTGOING_CHAT_MESSAGE_NAME, self.checkMagicWord)
|
||||||
|
self.accept(CLICKED_NAMETAG_MESSAGE_NAME, self.__handleClickedNametag)
|
||||||
|
self.accept(FOCUS_OUT_MESSAGE_NAME, self.__handleFocusOutNametag)
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
DistributedObject.DistributedObject.disable(self)
|
||||||
|
|
||||||
|
# Ignore the events we were accepting earlier
|
||||||
|
self.ignore(OUTGOING_CHAT_MESSAGE_NAME)
|
||||||
|
self.ignore(CLICKED_NAMETAG_MESSAGE_NAME)
|
||||||
|
self.ignore(FOCUS_OUT_MESSAGE_NAME)
|
||||||
|
|
||||||
|
def setChatPrefix(self, chatPrefix):
|
||||||
|
self.chatPrefix = chatPrefix
|
||||||
|
|
||||||
|
def __handleClickedNametag(self, avatar):
|
||||||
|
if avatar:
|
||||||
|
# Make sure the nametag we clicked on is a Toon
|
||||||
|
if isinstance(avatar, Toon.Toon) or isinstance(avatar, FriendHandle.FriendHandle):
|
||||||
|
# Store the avId of our target
|
||||||
|
self.lastClickedAvId = avatar.getDoId()
|
||||||
|
return
|
||||||
|
# Clear our target avId
|
||||||
|
self.lastClickedAvId = 0
|
||||||
|
|
||||||
|
def __handleFocusOutNametag(self):
|
||||||
|
# We've clicked off of a nametag, so reset the target avId
|
||||||
|
self.lastClickedAvId = 0
|
||||||
|
|
||||||
|
def checkMagicWord(self, magicWord):
|
||||||
|
# Well, this obviously isn't a Magic Word if it doesn't begin with our prefix
|
||||||
|
if not magicWord.startswith(self.chatPrefix):
|
||||||
|
return
|
||||||
|
|
||||||
|
# We don't even have access to be using Magic Words in the first place
|
||||||
|
if base.localAvatar.getAccessLevel() < OTPGlobals.AccessLevelName2Int.get('MODERATOR'):
|
||||||
|
self.generateResponse(responseType="NoAccess")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Using Magic Words while teleporting or going through tunnels is scary
|
||||||
|
if base.localAvatar.getTransitioning():
|
||||||
|
self.generateResponse(responseType="Teleporting")
|
||||||
|
return
|
||||||
|
|
||||||
|
# We're allowed to use the Magic Word then, so let's proceed
|
||||||
|
self.handleMagicWord(magicWord)
|
||||||
|
|
||||||
|
def generateResponse(self, responseType, magicWord='', args=None, returnValue=None, affectRange=None,
|
||||||
|
affectType=None, affectExtra=None, lastClickedAvId=None, extraMessageData = None):
|
||||||
|
# Generate and send the response to the use of our Magic Word
|
||||||
|
response = self.generateMagicWordResponse(responseType, magicWord, args, returnValue, affectRange, affectType,
|
||||||
|
affectExtra, lastClickedAvId, extraMessageData)
|
||||||
|
base.localAvatar.setSystemMessage(0, self.wizardName + ': ' + response, WhisperPopup.WTSystem)
|
||||||
|
self.notify.info(response)
|
||||||
|
|
||||||
|
def generateMagicWordResponse(self, responseType, magicWord, args, returnValue, affectRange, affectType,
|
||||||
|
affectExtra, lastClickedAvId, extraMessageData):
|
||||||
|
# Start out with a blank response
|
||||||
|
response = ''
|
||||||
|
|
||||||
|
# If our Magic Word was a success but has no return value, just send a randomized success phrase
|
||||||
|
if responseType == "SuccessNoResp" and magicWord:
|
||||||
|
successExclaim = random.choice(MAGIC_WORD_SUCCESS_PHRASES)
|
||||||
|
response += successExclaim
|
||||||
|
return response
|
||||||
|
# We had a successful Magic Word and also got a return value, so let's just use that
|
||||||
|
elif responseType == "Success":
|
||||||
|
response += returnValue
|
||||||
|
return response
|
||||||
|
|
||||||
|
# Guess it wasn't a success, so let's grab our response via the given code
|
||||||
|
response += MAGIC_WORD_RESPONSES.get(responseType, MAGIC_WORD_NO_RESPONSE)
|
||||||
|
|
||||||
|
# If we want to provide extra info, format the response
|
||||||
|
if responseType in MagicWordConfig.HAS_EXTRA_MESSAGE_DATA:
|
||||||
|
response = response.format(extraMessageData)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def handleMagicWord(self, magicWord):
|
||||||
|
# By default, our Magic Word affects nobody- not even ourself!
|
||||||
|
affectRange = AFFECT_NONE
|
||||||
|
|
||||||
|
# A normal affect type- we aren't trying to target all Toons in the zone, server, or a specific Access Level
|
||||||
|
affectType = AFFECT_NORMAL
|
||||||
|
|
||||||
|
# Only used for determining what Access Level will be targeted, if we decide to target a specific one
|
||||||
|
affectExtra = -1
|
||||||
|
|
||||||
|
# Used for determining what the affectRange is- it counts the amount of activators uses (ranges 1-3)
|
||||||
|
for x in range(3):
|
||||||
|
if magicWord.startswith(self.chatPrefix * (3 - x)):
|
||||||
|
affectRange = 2 - x
|
||||||
|
break
|
||||||
|
|
||||||
|
# If so some reason our affectRange is still none, we can't go any further
|
||||||
|
if affectRange == AFFECT_NONE:
|
||||||
|
self.generateResponse(responseType="NoEffect")
|
||||||
|
return
|
||||||
|
# Our affectRange is other, meaning we want to target someone- let's make sure we're allowed to
|
||||||
|
elif affectRange == AFFECT_OTHER:
|
||||||
|
# If they don't exist, why would we want to continue?
|
||||||
|
toon = base.cr.doId2do.get(self.lastClickedAvId)
|
||||||
|
if not toon:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Like earlier, Magic Words are no good if used while moving between zones
|
||||||
|
if toon.getTransitioning():
|
||||||
|
self.generateResponse(responseType="OtherTeleporting")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get how many activators were used in this Magic Word execution
|
||||||
|
activatorLength = affectRange + 1
|
||||||
|
|
||||||
|
# The Magic word without the activators
|
||||||
|
magicWordNoPrefix = magicWord[activatorLength:]
|
||||||
|
|
||||||
|
# Iterate through the affectType strings and see if any of them were used (e.g. zone, server, or rank)
|
||||||
|
for type in AFFECT_TYPES:
|
||||||
|
if magicWordNoPrefix.startswith(type):
|
||||||
|
magicWordNoPrefix = magicWordNoPrefix[len(type):]
|
||||||
|
affectType = AFFECT_TYPES.index(type)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Calculate the Access Level to affect if affectType is RANK
|
||||||
|
if affectType == AFFECT_RANK:
|
||||||
|
# Iterate over all the possible Access Level integers and see if any match with the one provided
|
||||||
|
for level in list(OTPGlobals.AccessLevelName2Int.values()):
|
||||||
|
# It matches, woohoo!
|
||||||
|
if magicWordNoPrefix.startswith(str(level)):
|
||||||
|
# Sorry, I'm commenting this way after the fact, so not even I know why there is a try/except here
|
||||||
|
# My guess is that sometimes this doesn't work for whatever reason, but I'm not too sure
|
||||||
|
# It typically works fine for me but I will keep it here just in-case
|
||||||
|
try:
|
||||||
|
int(magicWordNoPrefix[len(str(level)):][:1])
|
||||||
|
self.generateResponse(responseType="BadTarget")
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Strip the Access Level integer from the Magic Word string
|
||||||
|
magicWordNoPrefix = magicWordNoPrefix[len(str(level)):]
|
||||||
|
|
||||||
|
# Store the Access Level integer here instead
|
||||||
|
affectExtra = level
|
||||||
|
break
|
||||||
|
|
||||||
|
# The invoker wanted to target an Access Level but provided an invalid integer, so let them know
|
||||||
|
if affectExtra == -1:
|
||||||
|
self.generateResponse(responseType="BadTarget")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Finally, we can get the name of the Magic Word used
|
||||||
|
word = magicWordNoPrefix.split(' ', 1)[0].lower()
|
||||||
|
|
||||||
|
# The Magic Word the invoker used doesn't exist
|
||||||
|
if word not in MagicWordIndex:
|
||||||
|
# Iterate over all Magic Word names and see if the one provided is close to any of them
|
||||||
|
for magicWord in list(MagicWordIndex.keys()):
|
||||||
|
# If it is close, suggest to the invoker that they made a typo
|
||||||
|
if word in magicWord:
|
||||||
|
self.generateResponse(responseType="CloseWord", extraMessageData=magicWord)
|
||||||
|
return
|
||||||
|
# Couldn't find any Magic Word close to what was provided, so let them know the word doesn't exist
|
||||||
|
self.generateResponse(responseType="BadWord")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Grab the Magic Word info based off of it's name
|
||||||
|
magicWordInfo = MagicWordIndex[word]
|
||||||
|
|
||||||
|
# The location of the Magic Word's execution was not specified, so raise an error
|
||||||
|
if magicWordInfo['execLocation'] == EXEC_LOC_INVALID:
|
||||||
|
raise ValueError("execLocation not set for magic word {}!".format(magicWordInfo['classname']))
|
||||||
|
# The execLocation is valid, so let's finally send data over to the server to execute our Magic Word
|
||||||
|
elif magicWordInfo['execLocation'] in (EXEC_LOC_SERVER, EXEC_LOC_CLIENT):
|
||||||
|
self.sendUpdate('requestExecuteMagicWord', [affectRange, affectType, affectExtra, self.lastClickedAvId,
|
||||||
|
magicWordNoPrefix])
|
||||||
|
|
||||||
|
def executeMagicWord(self, word, commandName, targetIds, args, affectRange, affectType, affectExtra, lastClickedAvId):
|
||||||
|
# We have have a target avId and the affectRange isn't ourself, we want to execute this Magic Word on the target
|
||||||
|
# This is alright, but we should only execute it on the target if they are visible on our client
|
||||||
|
if self.lastClickedAvId and affectRange != AFFECT_SELF:
|
||||||
|
toon = base.cr.doId2do.get(self.lastClickedAvId)
|
||||||
|
if not toon:
|
||||||
|
self.generateResponse(responseType="NoTarget")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the Magic Word info based off of it's name
|
||||||
|
magicWord = commandName.lower()
|
||||||
|
magicWordInfo = MagicWordIndex[magicWord]
|
||||||
|
|
||||||
|
# Load the class tied to the Magic Word
|
||||||
|
command = magicWordInfo['class']
|
||||||
|
command.loadWord(None, self.cr, base.localAvatar.getDoId(), targetIds, args)
|
||||||
|
|
||||||
|
# Execute the Magic Word and store the return value
|
||||||
|
returnValue = command.executeWord()
|
||||||
|
|
||||||
|
# If we have a return value, route it through
|
||||||
|
if returnValue:
|
||||||
|
self.generateResponse(responseType="Success", returnValue=returnValue)
|
||||||
|
# If not just route a generic response through
|
||||||
|
else:
|
||||||
|
self.generateResponse(responseType="SuccessNoResp", magicWord=word, args=args, affectRange=affectRange,
|
||||||
|
affectType=affectType, affectExtra=affectExtra, lastClickedAvId=lastClickedAvId)
|
298
toontown/spellbook/ToontownMagicWordManagerAI.py
Normal file
298
toontown/spellbook/ToontownMagicWordManagerAI.py
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
##################################################
|
||||||
|
# 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
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
from direct.directnotify import DirectNotifyGlobal
|
||||||
|
from direct.distributed import DistributedObjectAI
|
||||||
|
|
||||||
|
from otp.avatar.DistributedPlayerAI import DistributedPlayerAI
|
||||||
|
|
||||||
|
from toontown.spellbook.MagicWordConfig import *
|
||||||
|
from toontown.spellbook.MagicWordIndex import *
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# All of the data regarding our Magic Words
|
||||||
|
MagicWordIndex = magicWordIndex.copy()
|
||||||
|
|
||||||
|
# You should only concern yourself with the following code if you want to add customization features to Magic Words
|
||||||
|
# This is only really useful in Toontown Offline, so other projects shouldn't have to really worry about it
|
||||||
|
|
||||||
|
# We allow server hosters to change a few things about the Magic Words on their server
|
||||||
|
# These are the default values generated with spellbook.json to help get them started
|
||||||
|
spellbookJsonDefaultValues = CUSTOM_SPELLBOOK_DEFAULT
|
||||||
|
|
||||||
|
# If we don't have a config directory, make it
|
||||||
|
if not os.path.exists('config/'):
|
||||||
|
os.mkdir('config/')
|
||||||
|
|
||||||
|
# If spellbook.json doesn't exist, make it
|
||||||
|
if not os.path.isfile('config/spellbook.json'):
|
||||||
|
with open('config/spellbook.json', 'w') as data:
|
||||||
|
data.write(spellbookJsonDefaultValues)
|
||||||
|
data.close()
|
||||||
|
|
||||||
|
# Now load the data from spellbook.json
|
||||||
|
with open('config/spellbook.json') as data:
|
||||||
|
spellbook = json.load(data)
|
||||||
|
|
||||||
|
# Make changes to all the Magic Words based on the data in spellbook.json
|
||||||
|
for word in spellbook['words']:
|
||||||
|
name = word['name']
|
||||||
|
accessLevel = word['access']
|
||||||
|
|
||||||
|
if accessLevel not in list(OTPGlobals.AccessLevelName2Int.keys()):
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
wordInfo = MagicWordIndex[str(name.lower())]
|
||||||
|
for alias in wordInfo['aliases']:
|
||||||
|
MagicWordIndex[alias]['access'] = accessLevel
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ToontownMagicWordManagerAI(DistributedObjectAI.DistributedObjectAI):
|
||||||
|
notify = DirectNotifyGlobal.directNotify.newCategory('ToontownMagicWordManagerAI')
|
||||||
|
|
||||||
|
def requestExecuteMagicWord(self, affectRange, affectType, affectExtra, lastClickedAvId, magicWord):
|
||||||
|
avId = self.air.getAvatarIdFromSender()
|
||||||
|
|
||||||
|
# It's not good if we can't get the avId for whatever reason
|
||||||
|
if not avId:
|
||||||
|
self.notify.warning('requestExecuteMagicWord: Magic Word use requested but invoker avId is non-existent!')
|
||||||
|
return
|
||||||
|
|
||||||
|
# We also need the Toon as well, or else this Magic Word isn't going to do much good
|
||||||
|
toon = self.air.doId2do.get(avId)
|
||||||
|
if not toon:
|
||||||
|
self.notify.warning('requestExecuteMagicWord: Magic Word use requested but invoker avatar is non-existent!')
|
||||||
|
return
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
targetIds = []
|
||||||
|
if affectRange in (AFFECT_SELF, AFFECT_BOTH):
|
||||||
|
targetIds.append(avId)
|
||||||
|
|
||||||
|
# This Magic Word's affectRange is either OTHER (a target) or BOTH (invoker and a target)
|
||||||
|
# However, it's also a NORMAL affectType, so it's not as if we're targeting a zone or the whole server
|
||||||
|
# In that case, let's try to grab the single target by the lastClickedAvId provided by the invoker
|
||||||
|
lastClickedToon = None
|
||||||
|
if (affectRange in (AFFECT_OTHER, AFFECT_BOTH)) and affectType == AFFECT_NORMAL:
|
||||||
|
if lastClickedAvId:
|
||||||
|
lastClickedToon = self.air.doId2do.get(lastClickedAvId)
|
||||||
|
if lastClickedToon:
|
||||||
|
targetIds.append(lastClickedAvId)
|
||||||
|
else:
|
||||||
|
self.generateResponse(avId=avId, responseType="NoTarget")
|
||||||
|
return
|
||||||
|
|
||||||
|
# The affectType is ZONE (zone the invoker is in), SERVER (the entire server), or RANK (specified access level)
|
||||||
|
# Gather all of the Toons using whichever method this Magic Word requests
|
||||||
|
if affectType in (AFFECT_ZONE, AFFECT_SERVER, AFFECT_RANK):
|
||||||
|
toonIds = []
|
||||||
|
# Iterate over a copy of every single doId on the server
|
||||||
|
for doId in list(self.air.doId2do.keys())[:]:
|
||||||
|
do = self.air.doId2do.get(doId)
|
||||||
|
# We only care if our DistributedObject is a player that is NOT our invoker (we dealt with that earlier)
|
||||||
|
if isinstance(do, DistributedPlayerAI) and do.isPlayerControlled() and do != toon:
|
||||||
|
# Only add the Toons that are in the same zone as the invoker
|
||||||
|
if affectType == AFFECT_ZONE and do.zoneId == toon.zoneId:
|
||||||
|
toonIds.append(doId)
|
||||||
|
# Add every Toon regardless of zone
|
||||||
|
elif affectType == AFFECT_SERVER:
|
||||||
|
toonIds.append(doId)
|
||||||
|
# Only add the Toons that have the Access Level specified when the Magic Word was used
|
||||||
|
elif affectType == AFFECT_RANK and do.getAccessLevel() == affectExtra:
|
||||||
|
toonIds.append(doId)
|
||||||
|
|
||||||
|
# There were no Toons we could perform this Magic Word on, so let the invoker know that
|
||||||
|
if not toonIds and not targetIds:
|
||||||
|
self.generateResponse(avId=avId, responseType="NoTarget")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add the found Toons to the targetId list
|
||||||
|
targetIds += toonIds
|
||||||
|
|
||||||
|
# If, at this point, we still don't have any targets somehow, then let the invoker know that
|
||||||
|
if not targetIds:
|
||||||
|
self.generateResponse(avId=avId, responseType="NoTarget")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Access level of the invoker
|
||||||
|
invokerAccess = int(round(toon.getAccessLevel(), -2))
|
||||||
|
|
||||||
|
# Access level of the selected target, if we have one
|
||||||
|
targetAccess = 0
|
||||||
|
if lastClickedAvId and lastClickedToon:
|
||||||
|
targetAccess = lastClickedToon.getAccessLevel()
|
||||||
|
|
||||||
|
# Now that we have all the avIds of who we want to target with this Magic Word, let's run some sanity checks
|
||||||
|
# First things first, let's make sure the invoker is allowed to target who they want to target
|
||||||
|
for targetId in targetIds:
|
||||||
|
# Of course the invoker has access to target themselves
|
||||||
|
if targetId == avId:
|
||||||
|
continue
|
||||||
|
# If our target DistributedObject doesn't exist anymore for whatever reason, just ignore them
|
||||||
|
targetToon = self.air.doId2do.get(targetId)
|
||||||
|
if not targetToon:
|
||||||
|
continue
|
||||||
|
# Get the Access Level of the target and round it to the nearest 100th
|
||||||
|
# This kind of thing is useful for roles like BUILDER, that are technically higher than USER
|
||||||
|
# They should have more perms than USERS, but shouldn't be allowed to target them
|
||||||
|
targetAccess = int(round(targetToon.getAccessLevel(), -2))
|
||||||
|
# If the Access Level of the target is greater than or equal to than that of the invoker, remove them
|
||||||
|
if targetAccess >= invokerAccess:
|
||||||
|
targetIds.remove(targetId)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Function that returns a readable name in place of the Toon's Access Level
|
||||||
|
def getAccessName(accessLevel):
|
||||||
|
return OTPGlobals.AccessLevelDebug2Name.get(OTPGlobals.AccessLevelInt2Name.get(accessLevel))
|
||||||
|
|
||||||
|
# If, after the previous check, we don't have any more targets, let's inform the invoker about it
|
||||||
|
if len(targetIds) == 0:
|
||||||
|
# If affectType is NORMAL, let the invoker know what their Access Level is compared to their target
|
||||||
|
if (affectRange in (AFFECT_OTHER, AFFECT_BOTH)) and affectType == AFFECT_NORMAL:
|
||||||
|
# Parse the Access Level of the invoker and target
|
||||||
|
parsedTargetAccess = getAccessName(targetAccess)
|
||||||
|
parsedInvokerAccess = getAccessName(invokerAccess)
|
||||||
|
# Create a nice little message to tell the invoker the difference between the Access Levels
|
||||||
|
returnValue = MAGIC_WORD_RESPONSES.get("NoAccessSingleTarget")
|
||||||
|
returnValue = returnValue.format(lastClickedToon.getName(), parsedTargetAccess, parsedInvokerAccess)
|
||||||
|
self.generateResponse(avId=avId, responseType="Success", returnValue=returnValue)
|
||||||
|
# Otherwise, just let the invoker know that everyone who was targeted was not allowed to be
|
||||||
|
else:
|
||||||
|
self.generateResponse(avId=avId, responseType="NoAccessToTarget")
|
||||||
|
return
|
||||||
|
|
||||||
|
# We're finally done determining everything related to the targets. Finally, let's get into the word itself
|
||||||
|
# We start by separating the word used from it's arguments
|
||||||
|
magicWord, args = (magicWord.split(' ', 1) + [''])[:2]
|
||||||
|
|
||||||
|
# Get the name of the word in lowercase
|
||||||
|
magicWord = magicWord.lower()
|
||||||
|
|
||||||
|
# Lookup the info for this word
|
||||||
|
magicWordInfo = MagicWordIndex[magicWord]
|
||||||
|
|
||||||
|
# Make sure the invoker has a high enough Access Level to use this Magic Word in the first place
|
||||||
|
# If they don't, them let them know about it
|
||||||
|
if toon.getAccessLevel() < OTPGlobals.AccessLevelName2Int.get(magicWordInfo['access']):
|
||||||
|
self.generateResponse(avId=avId, responseType="NoAccess")
|
||||||
|
return
|
||||||
|
|
||||||
|
# If a config option disables cheaty Magic Words and ours is deemed cheaty, let the invoker know
|
||||||
|
if hasattr(self.air, 'nonCheaty') and self.air.nonCheaty:
|
||||||
|
if not magicWordInfo['administrative']:
|
||||||
|
self.generateResponse(avId=avId, responseType="NonCheaty")
|
||||||
|
return
|
||||||
|
|
||||||
|
# If the affectRange circumstance made by the invoker is not allowed, let them know about it
|
||||||
|
# This kind of thing is good to make sure that words that shouldn't really have a particular target don't end
|
||||||
|
# up getting used in mass. For example, you don't want to use a word intended to kill a Cog Boss on other Toons
|
||||||
|
if affectRange not in magicWordInfo['affectRange']:
|
||||||
|
self.generateResponse(avId=avId, responseType="RestrictionOther")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the arguments the Magic Word will accept
|
||||||
|
commandArgs = magicWordInfo['args']
|
||||||
|
|
||||||
|
# Determine the max and min amount of arguments the word will accept
|
||||||
|
maxArgs = len(commandArgs)
|
||||||
|
minArgs = 0
|
||||||
|
argList = args.split(None, maxArgs-1)
|
||||||
|
for argSet in commandArgs:
|
||||||
|
isRequired = argSet[ARGUMENT_REQUIRED]
|
||||||
|
if isRequired:
|
||||||
|
minArgs += 1
|
||||||
|
|
||||||
|
# If we have less arguments provided than are required, let the invoker know that
|
||||||
|
messageData = "{} argument{}"
|
||||||
|
if len(argList) < minArgs:
|
||||||
|
messageData = messageData.format(minArgs, "s" if minArgs != 1 else '')
|
||||||
|
self.generateResponse(avId=avId, responseType="NotEnoughArgs", extraMessageData=messageData)
|
||||||
|
return
|
||||||
|
# On the other hand, if we have more than what we need, tell them that instead
|
||||||
|
elif len(argList) > maxArgs:
|
||||||
|
messageData = messageData.format(maxArgs, "s" if maxArgs != 1 else '')
|
||||||
|
self.generateResponse(avId=avId, responseType="TooManyArgs", extraMessageData=messageData)
|
||||||
|
return
|
||||||
|
|
||||||
|
# If we have less arguments provided than the max, use the defaults of the ones not provided
|
||||||
|
if len(argList) < maxArgs:
|
||||||
|
for x in range(minArgs, maxArgs):
|
||||||
|
if commandArgs[x][ARGUMENT_REQUIRED] or len(argList) >= x + 1:
|
||||||
|
continue
|
||||||
|
argList.append(commandArgs[x][ARGUMENT_DEFAULT])
|
||||||
|
|
||||||
|
# Parse through all the args we had provided
|
||||||
|
parsedArgList = []
|
||||||
|
for x in range(len(argList)):
|
||||||
|
arg = argList[x]
|
||||||
|
argType = commandArgs[x][ARGUMENT_TYPE]
|
||||||
|
try:
|
||||||
|
parsedArg = argType(arg)
|
||||||
|
except:
|
||||||
|
self.generateResponse(avId=avId, responseType="BadArgs")
|
||||||
|
return
|
||||||
|
|
||||||
|
parsedArgList.append(parsedArg)
|
||||||
|
|
||||||
|
# If this is a client-sided Magic Word, execute it on the client
|
||||||
|
if magicWordInfo['execLocation'] == EXEC_LOC_CLIENT:
|
||||||
|
# We are only allowed to target ourselves with client-sided Magic Words
|
||||||
|
if len(targetIds) == 1 and avId in targetIds:
|
||||||
|
self.sendClientCommand(avId, magicWord, magicWordInfo['classname'], targetIds=targetIds,
|
||||||
|
parsedArgList=parsedArgList, affectRange=affectRange, affectType=affectType,
|
||||||
|
affectExtra=affectExtra, lastClickedAvId=lastClickedAvId)
|
||||||
|
else:
|
||||||
|
self.generateResponse(avId=avId, responseType="CannotTarget")
|
||||||
|
return
|
||||||
|
# But if it's a server-sided one, execute it on the server
|
||||||
|
else:
|
||||||
|
# Find the class associated with our Magic Word and load it
|
||||||
|
command = magicWordInfo['class']
|
||||||
|
command.loadWord(self.air, None, avId, targetIds, parsedArgList)
|
||||||
|
# Execute the Magic Word and grab a return value
|
||||||
|
returnValue = command.executeWord()
|
||||||
|
# If we have a return value, pass it over to the invoker
|
||||||
|
if returnValue:
|
||||||
|
self.generateResponse(avId=avId, responseType="Success", returnValue=returnValue)
|
||||||
|
# Otherwise just throw a default response to them
|
||||||
|
else:
|
||||||
|
self.generateResponse(avId=avId, responseType="SuccessNoResp", magicWord=magicWord,
|
||||||
|
parsedArgList=parsedArgList, affectRange=affectRange, affectType=affectType,
|
||||||
|
affectExtra=affectExtra, lastClickedAvId=lastClickedAvId)
|
||||||
|
|
||||||
|
def generateResponse(self, avId, responseType="BadWord", magicWord='', parsedArgList=(), returnValue='',
|
||||||
|
affectRange=0, affectType=0, affectExtra=0, lastClickedAvId=0, extraMessageData=''):
|
||||||
|
# Pack up the arg list so it's ready to ship to the client
|
||||||
|
parsedArgList = json.dumps(parsedArgList)
|
||||||
|
# Send the invoker a response to their use of the word
|
||||||
|
self.sendUpdateToAvatarId(avId, 'generateResponse',
|
||||||
|
[responseType, magicWord, parsedArgList, returnValue, affectRange, affectType,
|
||||||
|
affectExtra, lastClickedAvId, extraMessageData])
|
||||||
|
|
||||||
|
def sendClientCommand(self, avId, word, commandName, targetIds=(), parsedArgList=(), affectRange=0, affectType=0,
|
||||||
|
affectExtra=0, lastClickedAvId=0):
|
||||||
|
# Pack up the arg list so it's ready to ship to the client
|
||||||
|
parsedArgList = json.dumps(parsedArgList)
|
||||||
|
# Execute the Magic Word on the client, because it's a client-sided Magic Word
|
||||||
|
self.sendUpdateToAvatarId(avId, "executeMagicWord", [word, commandName, targetIds, parsedArgList, affectRange,
|
||||||
|
affectType, affectExtra, lastClickedAvId])
|
0
toontown/spellbook/__init__.py
Normal file
0
toontown/spellbook/__init__.py
Normal file
|
@ -186,6 +186,7 @@ class DistributedToon(DistributedPlayer.DistributedPlayer, Toon.Toon, Distribute
|
||||||
self.gmNameTagColor = 'whiteGM'
|
self.gmNameTagColor = 'whiteGM'
|
||||||
self.gmNameTagString = ''
|
self.gmNameTagString = ''
|
||||||
self._lastZombieContext = None
|
self._lastZombieContext = None
|
||||||
|
self.transitioning = False
|
||||||
return
|
return
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
|
@ -2610,3 +2611,6 @@ class DistributedToon(DistributedPlayer.DistributedPlayer, Toon.Toon, Distribute
|
||||||
if not present:
|
if not present:
|
||||||
self.notify.warning('hiding av %s because they are not on the district!' % self.doId)
|
self.notify.warning('hiding av %s because they are not on the district!' % self.doId)
|
||||||
self.setParent(OTPGlobals.SPHidden)
|
self.setParent(OTPGlobals.SPHidden)
|
||||||
|
|
||||||
|
def getTransitioning(self):
|
||||||
|
return self.transitioning
|
||||||
|
|
Loading…
Reference in a new issue