dkmud/commands/command.py
Francesco Cappelli 5ef1b78c42 sistemati spells
2022-02-17 18:33:26 +01:00

760 lines
26 KiB
Python

"""
Commands
Commands describe the input the account can do to the game.
"""
import re
import random
from evennia import utils, default_cmds, create_script, logger
from evennia.utils import evtable
from evennia.contrib.health_bar import display_meter
from evennia.utils import inherits_from
from evennia.utils.utils import list_to_string
from evennia.utils.eveditor import EvEditor
from utils.building import create_exit
from utils.utils import has_tag, fmt_light, fmt_dark, toggle_effect, has_effect, indefinite_article
from typeclasses.exits import BaseDoor
from typeclasses.rooms import IndoorRoom
from typeclasses.objects import Item, EquippableItem
from typeclasses.scripts import Script, CmdActionScript
CMD_SEARCH_TIME = 5
class Command(default_cmds.MuxCommand):
"""
Inherit from this if you want to create your own command styles
from scratch. Note that Evennia's default commands inherits from
MuxCommand instead.
Note that the class's `__doc__` string (this text) is
used by Evennia to create the automatic help entry for
the command, so make sure to document consistently here.
Each Command implements the following methods, called
in this order (only func() is actually required):
- at_pre_cmd(): If this returns anything truthy, execution is aborted.
- parse(): Should perform any extra parsing needed on self.args
and store the result on self.
- func(): Performs the actual work.
- at_post_cmd(): Extra actions, often things done after
every command, like prompts.
"""
def parse(self):
"""
We need to expand the default parsing to get all
the cases, see the module doc.
"""
# get all the normal parsing done (switches etc)
super().parse()
obj_defs = ([], []) # stores left- and right-hand side of '='
obj_attrs = ([], []) # "
for iside, arglist in enumerate((self.lhslist, self.rhslist)):
# lhslist/rhslist is already split by ',' at this point
for objdef in arglist:
aliases, option, attrs = [], None, []
if ":" in objdef:
objdef, option = [part.strip() for part in objdef.rsplit(":", 1)]
if ";" in objdef:
objdef, aliases = [part.strip() for part in objdef.split(";", 1)]
aliases = [alias.strip() for alias in aliases.split(";") if alias.strip()]
if "/" in objdef:
objdef, attrs = [part.strip() for part in objdef.split("/", 1)]
attrs = [part.strip().lower() for part in attrs.split("/") if part.strip()]
# store data
obj_defs[iside].append({"name": objdef, "option": option, "aliases": aliases})
obj_attrs[iside].append({"name": objdef, "attrs": attrs})
# store for future access
self.lhs_objs = obj_defs[0]
self.rhs_objs = obj_defs[1]
self.lhs_objattr = obj_attrs[0]
self.rhs_objattr = obj_attrs[1]
def at_post_cmd(self):
caller = self.caller
prompt = "|_|/°|w%s|n°: " % (caller.location)
caller.msg(prompt=prompt)
# overloading the look command with our custom MuxCommand with at_post_cmd hook
class CmdLook(default_cmds.CmdLook, Command):
pass
class CmdDrop(default_cmds.CmdDrop, Command):
pass
class CmdGet(Command):
"""
pick up something
Usage:
get <obj> [from <container>]
Picks up an object from your location and puts it in
your inventory.
"""
key = "get"
aliases = "grab"
locks = "cmd:all()"
arg_regex = r"\s|$"
def parse(self):
"""
Handle parsing of:
<object> [FROM <container>]
"""
self.args = args = self.args.strip().lower()
if "from" in args:
obj, *rest = args.split(" from ", 1)
container = rest[0] if rest else ""
else:
obj = self.args
container = ""
self.obj_name = obj.strip()
self.container_name = container.strip()
def func(self):
"""implements the command."""
caller = self.caller
if not self.args or not self.obj_name:
caller.msg("Get what?")
return
if inherits_from(caller.location, IndoorRoom) and not caller.location.db.is_lit and not caller.is_superuser:
caller.msg("Its too dark to get anything.")
return
if self.container_name:
container = caller.search(self.container_name, location=caller.location)
obj = caller.search(self.obj_name, location=container)
else:
obj = caller.search(self.obj_name, location=caller.location)
if not obj:
return
if caller == obj:
caller.msg("You can't get yourself.")
return
if not obj.access(caller, "get"):
if obj.db.get_err_msg:
caller.msg(obj.db.get_err_msg)
else:
caller.msg("You can't get that.")
return
# calling at_before_get hook method
if not obj.at_before_get(caller):
return
success = obj.move_to(caller, quiet=True)
if not success:
caller.msg("This can't be picked up.")
else:
caller.msg("You pick up %s." % obj.name)
caller.location.msg_contents(
"%s picks up %s." % (caller.name, obj.name), exclude=caller
)
# calling at_get hook method
obj.at_get(caller)
class CmdCharacter(Command):
"""
Show character sheet.
Usage:
character
Displays a list of your current ability values.
"""
key = "character"
aliases = ["char"]
lock = "cmd:all()"
help_category = "General"
def func(self):
self.caller.msg(
'\u250B|w' + " {0:27}".format(self.caller.name,) + '|n\u250B|/')
self.caller.msg('-HEALTH' + '-' * 23)
meter = display_meter(self.caller.get_health(), 100,
empty_color="222", align="center", fill_color=["R"])
self.caller.msg(meter)
self.caller.msg('-MANA' + '-' * 25)
meter = display_meter(self.caller.get_mana(), 100,
empty_color="222", align="center", fill_color=["B"])
self.caller.msg(meter + '|/')
str, agi, intel = self.caller.get_abilities()
table = evtable.EvTable(border="tablecols")
table.add_column("STRENGTH", "AGILITY",
"INTELLECT", header="Attributes")
table.add_column("|g%03d|n/100" % str, "|g%03d|n/100" %
agi, "|g%03d|n/100" % intel, header="")
self.caller.msg(table)
class CmdLight(Command):
"""
A command to light objects that support it.
Usage:
light <someone>
Light an object in your inventory.
"""
key = "light"
aliases = []
lock = "cmd:all()"
help_category = "General"
arg_regex = r"(?:^(?:\s+|\/).*$)|^$"
def func(self):
caller = self.caller
if not self.args:
self.msg("Light what?")
return
else:
target = caller.search(self.args)
if not target:
self.msg("Light what?")
return
if not target.access(caller, 'light'):
self.msg("You cannot do that.")
return
if not has_effect(target, "emit_light"):
toggle_effect(target, "emit_light")
self.msg("You light {}.".format(target.name))
else:
toggle_effect(target, "emit_light")
self.msg("You put off {}.".format(target.name))
if caller.location and inherits_from(caller.location, IndoorRoom):
caller.location.check_light_state()
class CmdSearch(Command):
"""
"""
key = "search"
aliases = ["ss"]
locks = "cmd:all()"
help_category = "General"
arg_regex = r"(?:^(?:\s+|\/).*$)|^$"
def func(self):
caller = self.caller
if has_effect(caller, "is_busy"):
caller.msg("You are already busy {}.".format(caller.db.current_action.busy_msg()))
return
if not self.args:
target = caller.location
if not target:
caller.msg("You have no location to search!")
return
else:
target = caller.search(self.args)
if not target:
caller.msg("You cannot search {}!".format(self.args))
return
if target.access(caller, "search"):
toggle_effect(caller, "is_busy")
caller.msg("You search {}.".format(target.get_display_name(caller)))
action_script = create_script("commands.command.CmdSearchComplete", obj=caller, interval=CMD_SEARCH_TIME, attributes=[("target", self.args)])
caller.db.current_action = action_script
else:
caller.msg("You cannot search {}!".format(target.get_display_name(caller)))
class CmdSearchComplete(CmdActionScript):
def at_script_creation(self):
super().at_script_creation()
self.key = "cmd_search_complete"
self.desc = ""
self.db.target = ""
def at_repeat(self):
caller = self.obj
target_string = self.db.target
if has_effect(caller, "is_busy"):
toggle_effect(caller, "is_busy")
if not target_string:
target = caller.location
if not target:
caller.msg("You have no location to search!")
return
else:
target = caller.search(target_string)
if not target:
caller.msg("You cannot search {} anymore!".format(target_string))
return
if target.access(caller, "search"):
items = []
for con in target.contents:
if inherits_from(con, Item) and con.access(caller, "get"):
items.append(con)
if items:
found_item_idx = random.randrange(0, len(items))
found_item = items[found_item_idx]
found_item_name = found_item.get_numbered_name(1, caller)[0]
caller.msg("You find {}.".format(found_item_name))
if not found_item.access(caller, "get"):
if found_item.db.get_err_msg:
caller.msg(found_item.db.get_err_msg)
else:
caller.msg("You can't get that.")
return
# calling at_before_get hook method
if not found_item.at_before_get(caller):
return
success = found_item.move_to(caller, quiet=True)
if not success:
caller.msg("This can't be picked up.")
else:
caller.msg("You pick up %s." % found_item_name)
caller.location.msg_contents(
"%s picks up %s." % (caller.name, found_item_name), exclude=caller
)
# calling at_get hook method
found_item.at_get(caller)
else:
caller.msg("There is nothing to be found here.")
else:
caller.msg("You cannot search {} anymore!".format(target.get_display_name(caller)))
def busy_msg(self):
return "searching {}".format(self.db.target)
class CmdPut(Command):
"""
Put an item inside a container
Usage:
put <item> in <container>
"""
key = "put"
locks = "cmd:all()"
help_category = "General"
def parse(self):
"""
Handle parsing of:
<item> in <container>
"""
self.args = args = self.args.strip().lower()
item_name, container_name = "", ""
if " in " in args:
item_name, *rest = args.split(" in ", 1)
container_name = rest[0] if rest else ""
self.item_name = item_name.strip()
self.container_name = container_name.strip()
def func(self):
caller = self.caller
if not self.args or not self.item_name or not self.container_name:
caller.msg("Usage: put <item> in <container>")
return
item = caller.search(self.item_name, typeclass="typeclasses.objects.Item")
if not item:
return
container = caller.search(self.container_name, typeclass="typeclasses.objects.ContainerFeature")
if not container:
return
if not item.access(caller, "get"):
caller.msg("You cannot do that with {}.".format(item.name))
return
if not container.access(caller, "put"):
caller.msg("You cannot access {}.".format(container.name))
return
if has_tag(item, "equipped", "general"):
caller.msg("{} is equipped. Remove it first.".format(item.name))
return
item.move_to(container, use_destination=False)
caller.msg("You put {} inside {}.".format(item.name, container.name))
class CmdOpenCloseDoor(Command):
"""
Open and close a door
Usage:
open <door>
close <door>
"""
key = "op"
aliases = ["close"]
locks = "cmd:all()"
help_category = "General"
def func(self):
if not self.args:
self.caller.msg("Usage: open||close <door>")
return
door = self.caller.search(self.args)
if not door:
return
if not inherits_from(door, BaseDoor):
self.caller.msg("This is not a door.")
return
if self.cmdstring == "op":
if door.locks.check(self.caller, "traverse"):
self.caller.msg("%s is already open." % door.key)
else:
door.setlock("traverse:true()")
self.caller.msg("You open %s." % door.key)
else: # close
if not door.locks.check(self.caller, "traverse"):
self.caller.msg("%s is already closed." % door.key)
else:
door.setlock("traverse:false()")
self.caller.msg("You close %s." % door.key)
class CmdEquip(Command):
key = "equip"
aliases = ["eq", "remove"]
lock = "cmd:all()"
help_category = "General"
arg_regex = r"\s.+|$"
def func(self):
caller = self.caller
if not self.args:
caller.msg("You cannot do that.")
return
item = caller.search(self.args, location=caller, nofound_string="")
if not item:
caller.msg("You don't have any {}".format(self.args))
return
if inherits_from(item, EquippableItem) and item.access(caller, "equip"):
if self.cmdstring == "remove":
if has_tag(item, "equipped", "general"):
if item.at_unequip(caller, ""):
self.remove(caller, item)
else:
return
else:
caller.msg("{} is not equipped.".format(item.name))
return
else:
if has_tag(item, "equipped", "general"):
caller.msg("{} is already equipped.".format(item.name))
return
slots = [slot for slot in caller.db.equipment.keys() if item.db.slot in slot]
if not slots:
caller.msg("You don't have {} {}".format(indefinite_article(item.db.slot), item.db.slot))
return
selected_slot = slots[0]
# find an empty slot
empty_slots = [slot for slot in slots if not caller.db.equipment[slot]]
if empty_slots:
selected_slot = empty_slots[0]
# remove equipment if slot is already in use
if caller.db.equipment[selected_slot]:
old_equipment = caller.search(caller.db.equipment[selected_slot], location=caller, quiet=True)
if old_equipment:
if old_equipment[0].at_unequip(caller, selected_slot):
self.remove(caller, old_equipment[0])
else:
return
if item.at_equip(caller, selected_slot):
caller.db.equipment[selected_slot] = item.dbref
item.tags.add("equipped", category="general")
caller.msg("You equip |w{}|n on |w{}|n".format(item.name, selected_slot))
caller.location.msg_contents("{} equips |w{}|n on {}".format(caller.name, item.name, selected_slot), exclude=caller)
else:
caller.msg("You cannot equip {}".format(item.name))
def remove(self, caller, item):
selected_slot = [slot for slot in caller.db.equipment.keys() if caller.db.equipment[slot] == item.dbref][0]
caller.msg("You remove {} from {}".format(item, selected_slot))
caller.db.equipment[selected_slot] = None
item.tags.remove("equipped", category="general")
class CmdInventory(Command):
"""
view inventory
Usage:
inventory
inv
Shows your inventory.
"""
key = "inventory"
aliases = ["inv", "i"]
locks = "cmd:all()"
arg_regex = r"$"
def func(self):
"""check inventory"""
items = self.caller.contents
if not items:
string = "You are not carrying anything."
else:
from evennia.utils.ansi import raw as raw_ansi
table = self.styled_table()
for item in items:
effect_string = ""
if has_tag(item, 'equipped', 'general'):
effect_string += "|w⚒|n"
else:
effect_string += "|=a⚒|n"
if has_effect(item, 'emit_light'):
effect_string += "|y☀|n"
else:
effect_string += "|=a☀|n"
table.add_row(
f"|w{item.name}|n",
effect_string,
"{}|n".format(utils.crop(raw_ansi(item.db.desc), width=50) or ""),
)
string = f"|wYou are carrying:\n{table}"
self.caller.msg(string)
class CmdTestPy(Command):
key = "testpy"
aliases = ["testpy"]
lock = "cmd:false()"
help_category = "General"
arg_regex = r"\s.+|$"
def func(self):
caller = self.caller
caller.msg(self.lhs_objs)
pattern = re.compile(r'''((?:[^ "']|"[^"]*"|'[^']*')+)''')
self.args = pattern.split(self.lhs)[1::2]
# room = create_room(self.args[0].strip(" \"'"), int(self.args[1].strip(" \"'")), int(self.args[2].strip(" \"'")), self.args[3].strip(" \"'"))
# caller.msg(room)
# exit = create_exit("exit_empty", caller.location, "north")
# caller.msg(exit)
# caller.msg(dkmud_oob=({"testarg": "valuetestarg"}))
# if not self.args:
# target = caller.location
# if not target:
# caller.msg("You have no location to test!")
# return
# else:
# target = caller.search(self.args)
# if not target:
# return
# -------------------------------------------------------------
#
# The default commands inherit from
#
# evennia.commands.default.muxcommand.MuxCommand.
#
# If you want to make sweeping changes to default commands you can
# uncomment this copy of the MuxCommand parent and add
#
# COMMAND_DEFAULT_CLASS = "commands.command.MuxCommand"
#
# to your settings file. Be warned that the default commands expect
# the functionality implemented in the parse() method, so be
# careful with what you change.
#
# -------------------------------------------------------------
# from evennia.utils import utils
#
#
# class MuxCommand(Command):
# """
# This sets up the basis for a MUX command. The idea
# is that most other Mux-related commands should just
# inherit from this and don't have to implement much
# parsing of their own unless they do something particularly
# advanced.
#
# Note that the class's __doc__ string (this text) is
# used by Evennia to create the automatic help entry for
# the command, so make sure to document consistently here.
# """
# def has_perm(self, srcobj):
# """
# This is called by the cmdhandler to determine
# if srcobj is allowed to execute this command.
# We just show it here for completeness - we
# are satisfied using the default check in Command.
# """
# return super().has_perm(srcobj)
#
# def at_pre_cmd(self):
# """
# This hook is called before self.parse() on all commands
# """
# pass
#
# def at_post_cmd(self):
# """
# This hook is called after the command has finished executing
# (after self.func()).
# """
# pass
#
# def parse(self):
# """
# This method is called by the cmdhandler once the command name
# has been identified. It creates a new set of member variables
# that can be later accessed from self.func() (see below)
#
# The following variables are available for our use when entering this
# method (from the command definition, and assigned on the fly by the
# cmdhandler):
# self.key - the name of this command ('look')
# self.aliases - the aliases of this cmd ('l')
# self.permissions - permission string for this command
# self.help_category - overall category of command
#
# self.caller - the object calling this command
# self.cmdstring - the actual command name used to call this
# (this allows you to know which alias was used,
# for example)
# self.args - the raw input; everything following self.cmdstring.
# self.cmdset - the cmdset from which this command was picked. Not
# often used (useful for commands like 'help' or to
# list all available commands etc)
# self.obj - the object on which this command was defined. It is often
# the same as self.caller.
#
# A MUX command has the following possible syntax:
#
# name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]]
#
# The 'name[ with several words]' part is already dealt with by the
# cmdhandler at this point, and stored in self.cmdname (we don't use
# it here). The rest of the command is stored in self.args, which can
# start with the switch indicator /.
#
# This parser breaks self.args into its constituents and stores them in the
# following variables:
# self.switches = [list of /switches (without the /)]
# self.raw = This is the raw argument input, including switches
# self.args = This is re-defined to be everything *except* the switches
# self.lhs = Everything to the left of = (lhs:'left-hand side'). If
# no = is found, this is identical to self.args.
# self.rhs: Everything to the right of = (rhs:'right-hand side').
# If no '=' is found, this is None.
# self.lhslist - [self.lhs split into a list by comma]
# self.rhslist - [list of self.rhs split into a list by comma]
# self.arglist = [list of space-separated args (stripped, including '=' if it exists)]
#
# All args and list members are stripped of excess whitespace around the
# strings, but case is preserved.
# """
# raw = self.args
# args = raw.strip()
#
# # split out switches
# switches = []
# if args and len(args) > 1 and args[0] == "/":
# # we have a switch, or a set of switches. These end with a space.
# switches = args[1:].split(None, 1)
# if len(switches) > 1:
# switches, args = switches
# switches = switches.split('/')
# else:
# args = ""
# switches = switches[0].split('/')
# arglist = [arg.strip() for arg in args.split()]
#
# # check for arg1, arg2, ... = argA, argB, ... constructs
# lhs, rhs = args, None
# lhslist, rhslist = [arg.strip() for arg in args.split(',')], []
# if args and '=' in args:
# lhs, rhs = [arg.strip() for arg in args.split('=', 1)]
# lhslist = [arg.strip() for arg in lhs.split(',')]
# rhslist = [arg.strip() for arg in rhs.split(',')]
#
# # save to object properties:
# self.raw = raw
# self.switches = switches
# self.args = args.strip()
# self.arglist = arglist
# self.lhs = lhs
# self.lhslist = lhslist
# self.rhs = rhs
# self.rhslist = rhslist
#
# # if the class has the account_caller property set on itself, we make
# # sure that self.caller is always the account if possible. We also create
# # a special property "character" for the puppeted object, if any. This
# # is convenient for commands defined on the Account only.
# if hasattr(self, "account_caller") and self.account_caller:
# if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
# # caller is an Object/Character
# self.character = self.caller
# self.caller = self.caller.account
# elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"):
# # caller was already an Account
# self.character = self.caller.get_puppet(self.session)
# else:
# self.character = None