Merge with develop and fix merge conflicts
This commit is contained in:
commit
72f4fedcbe
148 changed files with 20005 additions and 2718 deletions
|
|
@ -10,9 +10,10 @@ from evennia.objects.models import ObjectDB
|
|||
from evennia.locks.lockhandler import LockException
|
||||
from evennia.commands.cmdhandler import get_and_merge_cmdsets
|
||||
from evennia.utils import create, utils, search
|
||||
from evennia.utils.utils import inherits_from, class_from_module
|
||||
from evennia.utils.utils import inherits_from, class_from_module, get_all_typeclasses
|
||||
from evennia.utils.eveditor import EvEditor
|
||||
from evennia.utils.spawner import spawn
|
||||
from evennia.utils.evmore import EvMore
|
||||
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
|
||||
from evennia.utils.ansi import raw
|
||||
|
||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
|
@ -26,12 +27,8 @@ __all__ = ("ObjManipCommand", "CmdSetObjAlias", "CmdCopy",
|
|||
"CmdLock", "CmdExamine", "CmdFind", "CmdTeleport",
|
||||
"CmdScript", "CmdTag", "CmdSpawn")
|
||||
|
||||
try:
|
||||
# used by @set
|
||||
from ast import literal_eval as _LITERAL_EVAL
|
||||
except ImportError:
|
||||
# literal_eval is not available before Python 2.6
|
||||
_LITERAL_EVAL = None
|
||||
# used by @set
|
||||
from ast import literal_eval as _LITERAL_EVAL
|
||||
|
||||
# used by @find
|
||||
CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
|
||||
|
|
@ -106,9 +103,15 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
|
|||
Usage:
|
||||
@alias <obj> [= [alias[,alias,alias,...]]]
|
||||
@alias <obj> =
|
||||
@alias/category <obj> = [alias[,alias,...]:<category>
|
||||
|
||||
Switches:
|
||||
category - requires ending input with :category, to store the
|
||||
given aliases with the given category.
|
||||
|
||||
Assigns aliases to an object so it can be referenced by more
|
||||
than one name. Assign empty to remove all aliases from object.
|
||||
than one name. Assign empty to remove all aliases from object. If
|
||||
assigning a category, all aliases given will be using this category.
|
||||
|
||||
Observe that this is not the same thing as personal aliases
|
||||
created with the 'nick' command! Aliases set with @alias are
|
||||
|
|
@ -118,6 +121,7 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "@alias"
|
||||
aliases = "@setobjalias"
|
||||
switch_options = ("category",)
|
||||
locks = "cmd:perm(setobjalias) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -138,9 +142,12 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
if self.rhs is None:
|
||||
# no =, so we just list aliases on object.
|
||||
aliases = obj.aliases.all()
|
||||
aliases = obj.aliases.all(return_key_and_category=True)
|
||||
if aliases:
|
||||
caller.msg("Aliases for '%s': %s" % (obj.get_display_name(caller), ", ".join(aliases)))
|
||||
caller.msg("Aliases for %s: %s" % (
|
||||
obj.get_display_name(caller),
|
||||
", ".join("'%s'%s" % (alias, "" if category is None else "[category:'%s']" % category)
|
||||
for (alias, category) in aliases)))
|
||||
else:
|
||||
caller.msg("No aliases exist for '%s'." % obj.get_display_name(caller))
|
||||
return
|
||||
|
|
@ -159,17 +166,27 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
|
|||
caller.msg("No aliases to clear.")
|
||||
return
|
||||
|
||||
category = None
|
||||
if "category" in self.switches:
|
||||
if ":" in self.rhs:
|
||||
rhs, category = self.rhs.rsplit(':', 1)
|
||||
category = category.strip()
|
||||
else:
|
||||
caller.msg("If specifying the /category switch, the category must be given "
|
||||
"as :category at the end.")
|
||||
else:
|
||||
rhs = self.rhs
|
||||
|
||||
# merge the old and new aliases (if any)
|
||||
old_aliases = obj.aliases.all()
|
||||
new_aliases = [alias.strip().lower() for alias in self.rhs.split(',')
|
||||
if alias.strip()]
|
||||
old_aliases = obj.aliases.get(category=category, return_list=True)
|
||||
new_aliases = [alias.strip().lower() for alias in rhs.split(',') if alias.strip()]
|
||||
|
||||
# make the aliases only appear once
|
||||
old_aliases.extend(new_aliases)
|
||||
aliases = list(set(old_aliases))
|
||||
|
||||
# save back to object.
|
||||
obj.aliases.add(aliases)
|
||||
obj.aliases.add(aliases, category=category)
|
||||
|
||||
# we need to trigger this here, since this will force
|
||||
# (default) Exits to rebuild their Exit commands with the new
|
||||
|
|
@ -177,7 +194,8 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
|
|||
obj.at_cmdset_get(force_init=True)
|
||||
|
||||
# report all aliases on the object
|
||||
caller.msg("Alias(es) for '%s' set to %s." % (obj.get_display_name(caller), str(obj.aliases)))
|
||||
caller.msg("Alias(es) for '%s' set to '%s'%s." % (obj.get_display_name(caller),
|
||||
str(obj.aliases), " (category: '%s')" % category if category else ""))
|
||||
|
||||
|
||||
class CmdCopy(ObjManipCommand):
|
||||
|
|
@ -198,6 +216,7 @@ class CmdCopy(ObjManipCommand):
|
|||
"""
|
||||
|
||||
key = "@copy"
|
||||
switch_options = ("reset",)
|
||||
locks = "cmd:perm(copy) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -279,6 +298,7 @@ class CmdCpAttr(ObjManipCommand):
|
|||
If you don't supply a source object, yourself is used.
|
||||
"""
|
||||
key = "@cpattr"
|
||||
switch_options = ("move",)
|
||||
locks = "cmd:perm(cpattr) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -420,6 +440,7 @@ class CmdMvAttr(ObjManipCommand):
|
|||
object. If you don't supply a source object, yourself is used.
|
||||
"""
|
||||
key = "@mvattr"
|
||||
switch_options = ("copy",)
|
||||
locks = "cmd:perm(mvattr) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -468,6 +489,7 @@ class CmdCreate(ObjManipCommand):
|
|||
"""
|
||||
|
||||
key = "@create"
|
||||
switch_options = ("drop",)
|
||||
locks = "cmd:perm(create) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -553,6 +575,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
|
|||
"""
|
||||
key = "@desc"
|
||||
aliases = "@describe"
|
||||
switch_options = ("edit",)
|
||||
locks = "cmd:perm(desc) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -568,6 +591,9 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
|
|||
if not obj:
|
||||
return
|
||||
|
||||
if not (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')):
|
||||
self.caller.msg("You don't have permission to edit the description of %s." % obj.key)
|
||||
|
||||
self.caller.db.evmenu_target = obj
|
||||
# launch the editor
|
||||
EvEditor(self.caller, loadfunc=_desc_load, savefunc=_desc_save,
|
||||
|
|
@ -597,7 +623,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
|
|||
if not obj:
|
||||
return
|
||||
desc = self.args
|
||||
if obj.access(caller, "edit"):
|
||||
if (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')):
|
||||
obj.db.desc = desc
|
||||
caller.msg("The description was set on %s." % obj.get_display_name(caller))
|
||||
else:
|
||||
|
|
@ -611,11 +637,11 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
|
|||
Usage:
|
||||
@destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]
|
||||
|
||||
switches:
|
||||
Switches:
|
||||
override - The @destroy command will usually avoid accidentally
|
||||
destroying account objects. This switch overrides this safety.
|
||||
force - destroy without confirmation.
|
||||
examples:
|
||||
Examples:
|
||||
@destroy house, roof, door, 44-78
|
||||
@destroy 5-10, flower, 45
|
||||
@destroy/force north
|
||||
|
|
@ -628,6 +654,7 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "@destroy"
|
||||
aliases = ["@delete", "@del"]
|
||||
switch_options = ("override", "force")
|
||||
locks = "cmd:perm(destroy) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -751,6 +778,7 @@ class CmdDig(ObjManipCommand):
|
|||
would be 'north;no;n'.
|
||||
"""
|
||||
key = "@dig"
|
||||
switch_options = ("teleport",)
|
||||
locks = "cmd:perm(dig) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -860,7 +888,7 @@ class CmdDig(ObjManipCommand):
|
|||
new_back_exit.dbref,
|
||||
alias_string)
|
||||
caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string))
|
||||
if new_room and ('teleport' in self.switches or "tel" in self.switches):
|
||||
if new_room and 'teleport' in self.switches:
|
||||
caller.move_to(new_room)
|
||||
|
||||
|
||||
|
|
@ -893,6 +921,7 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "@tunnel"
|
||||
aliases = ["@tun"]
|
||||
switch_options = ("oneway", "tel")
|
||||
locks = "cmd: perm(tunnel) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -1087,7 +1116,7 @@ class CmdSetHome(CmdLink):
|
|||
set an object's home location
|
||||
|
||||
Usage:
|
||||
@home <obj> [= <home_location>]
|
||||
@sethome <obj> [= <home_location>]
|
||||
|
||||
The "home" location is a "safety" location for objects; they
|
||||
will be moved there if their current location ceases to exist. All
|
||||
|
|
@ -1098,13 +1127,13 @@ class CmdSetHome(CmdLink):
|
|||
"""
|
||||
|
||||
key = "@sethome"
|
||||
locks = "cmd:perm(@home) or perm(Builder)"
|
||||
locks = "cmd:perm(@sethome) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
"""implement the command"""
|
||||
if not self.args:
|
||||
string = "Usage: @home <obj> [= <home_location>]"
|
||||
string = "Usage: @sethome <obj> [= <home_location>]"
|
||||
self.caller.msg(string)
|
||||
return
|
||||
|
||||
|
|
@ -1426,17 +1455,16 @@ def _convert_from_string(cmd, strobj):
|
|||
# if nothing matches, return as-is
|
||||
return obj
|
||||
|
||||
if _LITERAL_EVAL:
|
||||
# Use literal_eval to parse python structure exactly.
|
||||
try:
|
||||
return _LITERAL_EVAL(strobj)
|
||||
except (SyntaxError, ValueError):
|
||||
# treat as string
|
||||
strobj = utils.to_str(strobj)
|
||||
string = "|RNote: name \"|r%s|R\" was converted to a string. " \
|
||||
"Make sure this is acceptable." % strobj
|
||||
cmd.caller.msg(string)
|
||||
return strobj
|
||||
# Use literal_eval to parse python structure exactly.
|
||||
try:
|
||||
return _LITERAL_EVAL(strobj)
|
||||
except (SyntaxError, ValueError):
|
||||
# treat as string
|
||||
strobj = utils.to_str(strobj)
|
||||
string = "|RNote: name \"|r%s|R\" was converted to a string. " \
|
||||
"Make sure this is acceptable." % strobj
|
||||
cmd.caller.msg(string)
|
||||
return strobj
|
||||
else:
|
||||
# fall back to old recursive solution (does not support
|
||||
# nested lists/dicts)
|
||||
|
|
@ -1455,6 +1483,13 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
|
||||
Switch:
|
||||
edit: Open the line editor (string values only)
|
||||
script: If we're trying to set an attribute on a script
|
||||
channel: If we're trying to set an attribute on a channel
|
||||
account: If we're trying to set an attribute on an account
|
||||
room: Setting an attribute on a room (global search)
|
||||
exit: Setting an attribute on an exit (global search)
|
||||
char: Setting an attribute on a character (global search)
|
||||
character: Alias for char, as above.
|
||||
|
||||
Sets attributes on objects. The second form clears
|
||||
a previously set attribute while the last form
|
||||
|
|
@ -1555,6 +1590,38 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
# start the editor
|
||||
EvEditor(self.caller, load, save, key="%s/%s" % (obj, attr))
|
||||
|
||||
def search_for_obj(self, objname):
|
||||
"""
|
||||
Searches for an object matching objname. The object may be of different typeclasses.
|
||||
Args:
|
||||
objname: Name of the object we're looking for
|
||||
|
||||
Returns:
|
||||
A typeclassed object, or None if nothing is found.
|
||||
"""
|
||||
from evennia.utils.utils import variable_from_module
|
||||
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
||||
caller = self.caller
|
||||
if objname.startswith('*') or "account" in self.switches:
|
||||
found_obj = caller.search_account(objname.lstrip('*'))
|
||||
elif "script" in self.switches:
|
||||
found_obj = _AT_SEARCH_RESULT(search.search_script(objname), caller)
|
||||
elif "channel" in self.switches:
|
||||
found_obj = _AT_SEARCH_RESULT(search.search_channel(objname), caller)
|
||||
else:
|
||||
global_search = True
|
||||
if "char" in self.switches or "character" in self.switches:
|
||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
elif "room" in self.switches:
|
||||
typeclass = settings.BASE_ROOM_TYPECLASS
|
||||
elif "exit" in self.switches:
|
||||
typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
else:
|
||||
global_search = False
|
||||
typeclass = None
|
||||
found_obj = caller.search(objname, global_search=global_search, typeclass=typeclass)
|
||||
return found_obj
|
||||
|
||||
def func(self):
|
||||
"""Implement the set attribute - a limited form of @py."""
|
||||
|
||||
|
|
@ -1568,10 +1635,7 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
objname = self.lhs_objattr[0]['name']
|
||||
attrs = self.lhs_objattr[0]['attrs']
|
||||
|
||||
if objname.startswith('*'):
|
||||
obj = caller.search_account(objname.lstrip('*'))
|
||||
else:
|
||||
obj = caller.search(objname)
|
||||
obj = self.search_for_obj(objname)
|
||||
if not obj:
|
||||
return
|
||||
|
||||
|
|
@ -1581,6 +1645,10 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
result = []
|
||||
if "edit" in self.switches:
|
||||
# edit in the line editor
|
||||
if not (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')):
|
||||
caller.msg("You don't have permission to edit %s." % obj.key)
|
||||
return
|
||||
|
||||
if len(attrs) > 1:
|
||||
caller.msg("The Line editor can only be applied "
|
||||
"to one attribute at a time.")
|
||||
|
|
@ -1601,12 +1669,18 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
return
|
||||
else:
|
||||
# deleting the attribute(s)
|
||||
if not (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')):
|
||||
caller.msg("You don't have permission to edit %s." % obj.key)
|
||||
return
|
||||
for attr in attrs:
|
||||
if not self.check_attr(obj, attr):
|
||||
continue
|
||||
result.append(self.rm_attr(obj, attr))
|
||||
else:
|
||||
# setting attribute(s). Make sure to convert to real Python type before saving.
|
||||
if not (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')):
|
||||
caller.msg("You don't have permission to edit %s." % obj.key)
|
||||
return
|
||||
for attr in attrs:
|
||||
if not self.check_attr(obj, attr):
|
||||
continue
|
||||
|
|
@ -1624,17 +1698,22 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
@typeclass[/switch] <object> [= typeclass.path]
|
||||
@type ''
|
||||
@parent ''
|
||||
@typeclass/list/show [typeclass.path]
|
||||
@swap - this is a shorthand for using /force/reset flags.
|
||||
@update - this is a shorthand for using the /force/reload flag.
|
||||
|
||||
Switch:
|
||||
show - display the current typeclass of object (default)
|
||||
show, examine - display the current typeclass of object (default) or, if
|
||||
given a typeclass path, show the docstring of that typeclass.
|
||||
update - *only* re-run at_object_creation on this object
|
||||
meaning locks or other properties set later may remain.
|
||||
reset - clean out *all* the attributes and properties on the
|
||||
object - basically making this a new clean object.
|
||||
force - change to the typeclass also if the object
|
||||
already has a typeclass of the same name.
|
||||
list - show available typeclasses.
|
||||
|
||||
|
||||
Example:
|
||||
@type button = examples.red_button.RedButton
|
||||
|
||||
|
|
@ -1658,6 +1737,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "@typeclass"
|
||||
aliases = ["@type", "@parent", "@swap", "@update"]
|
||||
switch_options = ("show", "examine", "update", "reset", "force", "list")
|
||||
locks = "cmd:perm(typeclass) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -1666,10 +1746,56 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
caller = self.caller
|
||||
|
||||
if 'list' in self.switches:
|
||||
tclasses = get_all_typeclasses()
|
||||
contribs = [key for key in sorted(tclasses)
|
||||
if key.startswith("evennia.contrib")] or ["<None loaded>"]
|
||||
core = [key for key in sorted(tclasses)
|
||||
if key.startswith("evennia") and key not in contribs] or ["<None loaded>"]
|
||||
game = [key for key in sorted(tclasses)
|
||||
if not key.startswith("evennia")] or ["<None loaded>"]
|
||||
string = ("|wCore typeclasses|n\n"
|
||||
" {core}\n"
|
||||
"|wLoaded Contrib typeclasses|n\n"
|
||||
" {contrib}\n"
|
||||
"|wGame-dir typeclasses|n\n"
|
||||
" {game}").format(core="\n ".join(core),
|
||||
contrib="\n ".join(contribs),
|
||||
game="\n ".join(game))
|
||||
EvMore(caller, string, exit_on_lastpage=True)
|
||||
return
|
||||
|
||||
if not self.args:
|
||||
caller.msg("Usage: %s <object> [= typeclass]" % self.cmdstring)
|
||||
return
|
||||
|
||||
if "show" in self.switches or "examine" in self.switches:
|
||||
oquery = self.lhs
|
||||
obj = caller.search(oquery, quiet=True)
|
||||
if not obj:
|
||||
# no object found to examine, see if it's a typeclass-path instead
|
||||
tclasses = get_all_typeclasses()
|
||||
matches = [(key, tclass)
|
||||
for key, tclass in tclasses.items() if key.endswith(oquery)]
|
||||
nmatches = len(matches)
|
||||
if nmatches > 1:
|
||||
caller.msg("Multiple typeclasses found matching {}:\n {}".format(
|
||||
oquery, "\n ".join(tup[0] for tup in matches)))
|
||||
elif not matches:
|
||||
caller.msg("No object or typeclass path found to match '{}'".format(oquery))
|
||||
else:
|
||||
# one match found
|
||||
caller.msg("Docstring for typeclass '{}':\n{}".format(
|
||||
oquery, matches[0][1].__doc__))
|
||||
else:
|
||||
# do the search again to get the error handling in case of multi-match
|
||||
obj = caller.search(oquery)
|
||||
if not obj:
|
||||
return
|
||||
caller.msg("{}'s current typeclass is '{}.{}'".format(
|
||||
obj.name, obj.__class__.__module__, obj.__class__.__name__))
|
||||
return
|
||||
|
||||
# get object to swap on
|
||||
obj = caller.search(self.lhs)
|
||||
if not obj:
|
||||
|
|
@ -1682,7 +1808,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
new_typeclass = self.rhs or obj.path
|
||||
|
||||
if "show" in self.switches:
|
||||
if "show" in self.switches or "examine" in self.switches:
|
||||
string = "%s's current typeclass is %s." % (obj.name, obj.__class__)
|
||||
caller.msg(string)
|
||||
return
|
||||
|
|
@ -1807,13 +1933,13 @@ class CmdLock(ObjManipCommand):
|
|||
|
||||
For example:
|
||||
'get: id(25) or perm(Admin)'
|
||||
The 'get' access_type is checked by the get command and will
|
||||
an object locked with this string will only be possible to
|
||||
pick up by Admins or by object with id=25.
|
||||
The 'get' lock access_type is checked e.g. by the 'get' command.
|
||||
An object locked with this example lock will only be possible to pick up
|
||||
by Admins or by an object with id=25.
|
||||
|
||||
You can add several access_types after one another by separating
|
||||
them by ';', i.e:
|
||||
'get:id(25);delete:perm(Builder)'
|
||||
'get:id(25); delete:perm(Builder)'
|
||||
"""
|
||||
key = "@lock"
|
||||
aliases = ["@locks"]
|
||||
|
|
@ -1840,9 +1966,16 @@ class CmdLock(ObjManipCommand):
|
|||
obj = caller.search(objname)
|
||||
if not obj:
|
||||
return
|
||||
if not (obj.access(caller, 'control') or obj.access(caller, "edit")):
|
||||
has_control_access = obj.access(caller, 'control')
|
||||
if access_type == 'control' and not has_control_access:
|
||||
# only allow to change 'control' access if you have 'control' access already
|
||||
caller.msg("You need 'control' access to change this type of lock.")
|
||||
return
|
||||
|
||||
if not has_control_access or obj.access(caller, "edit"):
|
||||
caller.msg("You are not allowed to do that.")
|
||||
return
|
||||
|
||||
lockdef = obj.locks.get(access_type)
|
||||
|
||||
if lockdef:
|
||||
|
|
@ -2093,12 +2226,15 @@ class CmdExamine(ObjManipCommand):
|
|||
else:
|
||||
things.append(content)
|
||||
if exits:
|
||||
string += "\n|wExits|n: %s" % ", ".join(["%s(%s)" % (exit.name, exit.dbref) for exit in exits])
|
||||
string += "\n|wExits|n: %s" % ", ".join(
|
||||
["%s(%s)" % (exit.name, exit.dbref) for exit in exits])
|
||||
if pobjs:
|
||||
string += "\n|wCharacters|n: %s" % ", ".join(["|c%s|n(%s)" % (pobj.name, pobj.dbref) for pobj in pobjs])
|
||||
string += "\n|wCharacters|n: %s" % ", ".join(
|
||||
["|c%s|n(%s)" % (pobj.name, pobj.dbref) for pobj in pobjs])
|
||||
if things:
|
||||
string += "\n|wContents|n: %s" % ", ".join(["%s(%s)" % (cont.name, cont.dbref) for cont in obj.contents
|
||||
if cont not in exits and cont not in pobjs])
|
||||
string += "\n|wContents|n: %s" % ", ".join(
|
||||
["%s(%s)" % (cont.name, cont.dbref) for cont in obj.contents
|
||||
if cont not in exits and cont not in pobjs])
|
||||
separator = "-" * _DEFAULT_WIDTH
|
||||
# output info
|
||||
return '%s\n%s\n%s' % (separator, string.strip(), separator)
|
||||
|
|
@ -2181,12 +2317,15 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
Usage:
|
||||
@find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]]
|
||||
@locate - this is a shorthand for using the /loc switch.
|
||||
|
||||
Switches:
|
||||
room - only look for rooms (location=None)
|
||||
exit - only look for exits (destination!=None)
|
||||
char - only look for characters (BASE_CHARACTER_TYPECLASS)
|
||||
exact- only exact matches are returned.
|
||||
room - only look for rooms (location=None)
|
||||
exit - only look for exits (destination!=None)
|
||||
char - only look for characters (BASE_CHARACTER_TYPECLASS)
|
||||
exact - only exact matches are returned.
|
||||
loc - display object location if exists and match has one result
|
||||
startswith - search for names starting with the string, rather than containing
|
||||
|
||||
Searches the database for an object of a particular name or exact #dbref.
|
||||
Use *accountname to search for an account. The switches allows for
|
||||
|
|
@ -2197,6 +2336,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "@find"
|
||||
aliases = "@search, @locate"
|
||||
switch_options = ("room", "exit", "char", "exact", "loc", "startswith")
|
||||
locks = "cmd:perm(find) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -2209,6 +2349,9 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
|||
caller.msg("Usage: @find <string> [= low [-high]]")
|
||||
return
|
||||
|
||||
if "locate" in self.cmdstring: # Use option /loc as a default for @locate command alias
|
||||
switches.append('loc')
|
||||
|
||||
searchstring = self.lhs
|
||||
low, high = 1, ObjectDB.objects.all().order_by("-id")[0].id
|
||||
if self.rhs:
|
||||
|
|
@ -2230,7 +2373,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
restrictions = ""
|
||||
if self.switches:
|
||||
restrictions = ", %s" % (",".join(self.switches))
|
||||
restrictions = ", %s" % (", ".join(self.switches))
|
||||
|
||||
if is_dbref or is_account:
|
||||
|
||||
|
|
@ -2258,6 +2401,8 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
|||
else:
|
||||
result = result[0]
|
||||
string += "\n|g %s - %s|n" % (result.get_display_name(caller), result.path)
|
||||
if "loc" in self.switches and not is_account and result.location:
|
||||
string += " (|wlocation|n: |g{}|n)".format(result.location.get_display_name(caller))
|
||||
else:
|
||||
# Not an account/dbref search but a wider search; build a queryset.
|
||||
# Searchs for key and aliases
|
||||
|
|
@ -2265,10 +2410,14 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
|||
keyquery = Q(db_key__iexact=searchstring, id__gte=low, id__lte=high)
|
||||
aliasquery = Q(db_tags__db_key__iexact=searchstring,
|
||||
db_tags__db_tagtype__iexact="alias", id__gte=low, id__lte=high)
|
||||
else:
|
||||
elif "startswith" in switches:
|
||||
keyquery = Q(db_key__istartswith=searchstring, id__gte=low, id__lte=high)
|
||||
aliasquery = Q(db_tags__db_key__istartswith=searchstring,
|
||||
db_tags__db_tagtype__iexact="alias", id__gte=low, id__lte=high)
|
||||
else:
|
||||
keyquery = Q(db_key__icontains=searchstring, id__gte=low, id__lte=high)
|
||||
aliasquery = Q(db_tags__db_key__icontains=searchstring,
|
||||
db_tags__db_tagtype__iexact="alias", id__gte=low, id__lte=high)
|
||||
|
||||
results = ObjectDB.objects.filter(keyquery | aliasquery).distinct()
|
||||
nresults = results.count()
|
||||
|
|
@ -2293,6 +2442,8 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
|||
else:
|
||||
string = "|wOne Match|n(#%i-#%i%s):" % (low, high, restrictions)
|
||||
string += "\n |g%s - %s|n" % (results[0].get_display_name(caller), results[0].path)
|
||||
if "loc" in self.switches and nresults == 1 and results[0].location:
|
||||
string += " (|wlocation|n: |g{}|n)".format(results[0].location.get_display_name(caller))
|
||||
else:
|
||||
string = "|wMatch|n(#%i-#%i%s):" % (low, high, restrictions)
|
||||
string += "\n |RNo matches found for '%s'|n" % searchstring
|
||||
|
|
@ -2306,11 +2457,11 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
teleport object to another location
|
||||
|
||||
Usage:
|
||||
@tel/switch [<object> =] <target location>
|
||||
@tel/switch [<object> to||=] <target location>
|
||||
|
||||
Examples:
|
||||
@tel Limbo
|
||||
@tel/quiet box Limbo
|
||||
@tel/quiet box = Limbo
|
||||
@tel/tonone box
|
||||
|
||||
Switches:
|
||||
|
|
@ -2326,9 +2477,12 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
loc - teleport object to the target's location instead of its contents
|
||||
|
||||
Teleports an object somewhere. If no object is given, you yourself
|
||||
is teleported to the target location. """
|
||||
is teleported to the target location.
|
||||
"""
|
||||
key = "@tel"
|
||||
aliases = "@teleport"
|
||||
switch_options = ("quiet", "intoexit", "tonone", "loc")
|
||||
rhs_split = ("=", " to ") # Prefer = delimiter, but allow " to " usage.
|
||||
locks = "cmd:perm(teleport) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -2436,6 +2590,7 @@ class CmdScript(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "@script"
|
||||
aliases = "@addscript"
|
||||
switch_options = ("start", "stop")
|
||||
locks = "cmd:perm(script) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -2535,6 +2690,7 @@ class CmdTag(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "@tag"
|
||||
aliases = ["@tags"]
|
||||
options = ("search", "del")
|
||||
locks = "cmd:perm(tag) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
arg_regex = r"(/\w+?(\s|$))|\s|$"
|
||||
|
|
@ -2632,100 +2788,312 @@ class CmdTag(COMMAND_DEFAULT_CLASS):
|
|||
string = "No tags attached to %s." % obj
|
||||
self.caller.msg(string)
|
||||
|
||||
#
|
||||
# To use the prototypes with the @spawn function set
|
||||
# PROTOTYPE_MODULES = ["commands.prototypes"]
|
||||
# Reload the server and the prototypes should be available.
|
||||
#
|
||||
|
||||
|
||||
class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
spawn objects from prototype
|
||||
|
||||
Usage:
|
||||
@spawn
|
||||
@spawn[/switch] <prototype_name>
|
||||
@spawn[/switch] {prototype dictionary}
|
||||
@spawn[/noloc] <prototype_key>
|
||||
@spawn[/noloc] <prototype_dict>
|
||||
|
||||
Switch:
|
||||
@spawn/search [prototype_keykey][;tag[,tag]]
|
||||
@spawn/list [tag, tag, ...]
|
||||
@spawn/show [<prototype_key>]
|
||||
@spawn/update <prototype_key>
|
||||
|
||||
@spawn/save <prototype_dict>
|
||||
@spawn/edit [<prototype_key>]
|
||||
@olc - equivalent to @spawn/edit
|
||||
|
||||
Switches:
|
||||
noloc - allow location to be None if not specified explicitly. Otherwise,
|
||||
location will default to caller's current location.
|
||||
search - search prototype by name or tags.
|
||||
list - list available prototypes, optionally limit by tags.
|
||||
show, examine - inspect prototype by key. If not given, acts like list.
|
||||
save - save a prototype to the database. It will be listable by /list.
|
||||
delete - remove a prototype from database, if allowed to.
|
||||
update - find existing objects with the same prototype_key and update
|
||||
them with latest version of given prototype. If given with /save,
|
||||
will auto-update all objects with the old version of the prototype
|
||||
without asking first.
|
||||
edit, olc - create/manipulate prototype in a menu interface.
|
||||
|
||||
Example:
|
||||
@spawn GOBLIN
|
||||
@spawn {"key":"goblin", "typeclass":"monster.Monster", "location":"#2"}
|
||||
@spawn/save {"key": "grunt", prototype: "goblin"};;mobs;edit:all()
|
||||
|
||||
Dictionary keys:
|
||||
|wprototype |n - name of parent prototype to use. Can be a list for
|
||||
multiple inheritance (inherits left to right)
|
||||
|wprototype_parent |n - name of parent prototype to use. Required if typeclass is
|
||||
not set. Can be a path or a list for multiple inheritance (inherits
|
||||
left to right). If set one of the parents must have a typeclass.
|
||||
|wtypeclass |n - string. Required if prototype_parent is not set.
|
||||
|wkey |n - string, the main object identifier
|
||||
|wtypeclass |n - string, if not set, will use settings.BASE_OBJECT_TYPECLASS
|
||||
|wlocation |n - this should be a valid object or #dbref
|
||||
|whome |n - valid object or #dbref
|
||||
|wdestination|n - only valid for exits (object or dbref)
|
||||
|wpermissions|n - string or list of permission strings
|
||||
|wlocks |n - a lock-string
|
||||
|waliases |n - string or list of strings
|
||||
|waliases |n - string or list of strings.
|
||||
|wndb_|n<name> - value of a nattribute (ndb_ is stripped)
|
||||
|
||||
|wprototype_key|n - name of this prototype. Unique. Used to store/retrieve from db
|
||||
and update existing prototyped objects if desired.
|
||||
|wprototype_desc|n - desc of this prototype. Used in listings
|
||||
|wprototype_locks|n - locks of this prototype. Limits who may use prototype
|
||||
|wprototype_tags|n - tags of this prototype. Used to find prototype
|
||||
|
||||
any other keywords are interpreted as Attributes and their values.
|
||||
|
||||
The available prototypes are defined globally in modules set in
|
||||
settings.PROTOTYPE_MODULES. If @spawn is used without arguments it
|
||||
displays a list of available prototypes.
|
||||
|
||||
"""
|
||||
|
||||
key = "@spawn"
|
||||
aliases = ["olc"]
|
||||
switch_options = ("noloc", "search", "list", "show", "save", "delete", "menu", "olc", "update")
|
||||
locks = "cmd:perm(spawn) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
"""Implements the spawner"""
|
||||
|
||||
def _show_prototypes(prototypes):
|
||||
"""Helper to show a list of available prototypes"""
|
||||
prots = ", ".join(sorted(prototypes.keys()))
|
||||
return "\nAvailable prototypes (case sensistive): %s" % (
|
||||
"\n" + utils.fill(prots) if prots else "None")
|
||||
def _parse_prototype(inp, expect=dict):
|
||||
err = None
|
||||
try:
|
||||
prototype = _LITERAL_EVAL(inp)
|
||||
except (SyntaxError, ValueError) as err:
|
||||
# treat as string
|
||||
prototype = utils.to_str(inp)
|
||||
finally:
|
||||
if not isinstance(prototype, expect):
|
||||
if err:
|
||||
string = ("{}\n|RCritical Python syntax error in argument. Only primitive "
|
||||
"Python structures are allowed. \nYou also need to use correct "
|
||||
"Python syntax. Remember especially to put quotes around all "
|
||||
"strings inside lists and dicts.|n For more advanced uses, embed "
|
||||
"inline functions in the strings.".format(err))
|
||||
else:
|
||||
string = "Expected {}, got {}.".format(expect, type(prototype))
|
||||
self.caller.msg(string)
|
||||
return None
|
||||
if expect == dict:
|
||||
# an actual prototype. We need to make sure it's safe. Don't allow exec
|
||||
if "exec" in prototype and not self.caller.check_permstring("Developer"):
|
||||
self.caller.msg("Spawn aborted: You are not allowed to "
|
||||
"use the 'exec' prototype key.")
|
||||
return None
|
||||
try:
|
||||
protlib.validate_prototype(prototype)
|
||||
except RuntimeError as err:
|
||||
self.caller.msg(str(err))
|
||||
return
|
||||
return prototype
|
||||
|
||||
prototypes = spawn(return_prototypes=True)
|
||||
if not self.args:
|
||||
string = "Usage: @spawn {key:value, key, value, ... }"
|
||||
self.caller.msg(string + _show_prototypes(prototypes))
|
||||
return
|
||||
try:
|
||||
# make use of _convert_from_string from the SetAttribute command
|
||||
prototype = _convert_from_string(self, self.args)
|
||||
except SyntaxError:
|
||||
# this means literal_eval tried to parse a faulty string
|
||||
string = "|RCritical Python syntax error in argument. "
|
||||
string += "Only primitive Python structures are allowed. "
|
||||
string += "\nYou also need to use correct Python syntax. "
|
||||
string += "Remember especially to put quotes around all "
|
||||
string += "strings inside lists and dicts.|n"
|
||||
self.caller.msg(string)
|
||||
def _search_show_prototype(query, prototypes=None):
|
||||
# prototype detail
|
||||
if not prototypes:
|
||||
prototypes = protlib.search_prototype(key=query)
|
||||
if prototypes:
|
||||
return "\n".join(protlib.prototype_to_str(prot) for prot in prototypes)
|
||||
else:
|
||||
return False
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if self.cmdstring == "olc" or 'menu' in self.switches or 'olc' in self.switches:
|
||||
# OLC menu mode
|
||||
prototype = None
|
||||
if self.lhs:
|
||||
key = self.lhs
|
||||
prototype = spawner.search_prototype(key=key, return_meta=True)
|
||||
if len(prototype) > 1:
|
||||
caller.msg("More than one match for {}:\n{}".format(
|
||||
key, "\n".join(proto.get('prototype_key', '') for proto in prototype)))
|
||||
return
|
||||
elif prototype:
|
||||
# one match
|
||||
prototype = prototype[0]
|
||||
olc_menus.start_olc(caller, session=self.session, prototype=prototype)
|
||||
return
|
||||
|
||||
if isinstance(prototype, str):
|
||||
# A prototype key
|
||||
keystr = prototype
|
||||
prototype = prototypes.get(prototype, None)
|
||||
if 'search' in self.switches:
|
||||
# query for a key match
|
||||
if not self.args:
|
||||
self.switches.append("list")
|
||||
else:
|
||||
key, tags = self.args.strip(), None
|
||||
if ';' in self.args:
|
||||
key, tags = (part.strip().lower() for part in self.args.split(";", 1))
|
||||
tags = [tag.strip() for tag in tags.split(",")] if tags else None
|
||||
EvMore(caller, unicode(protlib.list_prototypes(caller, key=key, tags=tags)),
|
||||
exit_on_lastpage=True)
|
||||
return
|
||||
|
||||
if 'show' in self.switches or 'examine' in self.switches:
|
||||
# the argument is a key in this case (may be a partial key)
|
||||
if not self.args:
|
||||
self.switches.append('list')
|
||||
else:
|
||||
matchstring = _search_show_prototype(self.args)
|
||||
if matchstring:
|
||||
caller.msg(matchstring)
|
||||
else:
|
||||
caller.msg("No prototype '{}' was found.".format(self.args))
|
||||
return
|
||||
|
||||
if 'list' in self.switches:
|
||||
# for list, all optional arguments are tags
|
||||
# import pudb; pudb.set_trace()
|
||||
|
||||
EvMore(caller, str(protlib.list_prototypes(caller,
|
||||
tags=self.lhslist)), exit_on_lastpage=True)
|
||||
return
|
||||
|
||||
if 'save' in self.switches:
|
||||
# store a prototype to the database store
|
||||
if not self.args:
|
||||
caller.msg(
|
||||
"Usage: @spawn/save <key>[;desc[;tag,tag[,...][;lockstring]]] = <prototype_dict>")
|
||||
return
|
||||
|
||||
# handle rhs:
|
||||
prototype = _parse_prototype(self.lhs.strip())
|
||||
if not prototype:
|
||||
string = "No prototype named '%s'." % keystr
|
||||
self.caller.msg(string + _show_prototypes(prototypes))
|
||||
return
|
||||
elif isinstance(prototype, dict):
|
||||
# we got the prototype on the command line. We must make sure to not allow
|
||||
# the 'exec' key unless we are developers or higher.
|
||||
if "exec" in prototype and not self.caller.check_permstring("Developer"):
|
||||
self.caller.msg("Spawn aborted: You don't have access to use the 'exec' prototype key.")
|
||||
|
||||
# present prototype to save
|
||||
new_matchstring = _search_show_prototype("", prototypes=[prototype])
|
||||
string = "|yCreating new prototype:|n\n{}".format(new_matchstring)
|
||||
question = "\nDo you want to continue saving? [Y]/N"
|
||||
|
||||
prototype_key = prototype.get("prototype_key")
|
||||
if not prototype_key:
|
||||
caller.msg("\n|yTo save a prototype it must have the 'prototype_key' set.")
|
||||
return
|
||||
else:
|
||||
self.caller.msg("The prototype must be a prototype key or a Python dictionary.")
|
||||
|
||||
# check for existing prototype,
|
||||
old_matchstring = _search_show_prototype(prototype_key)
|
||||
|
||||
if old_matchstring:
|
||||
string += "\n|yExisting saved prototype found:|n\n{}".format(old_matchstring)
|
||||
question = "\n|yDo you want to replace the existing prototype?|n [Y]/N"
|
||||
|
||||
answer = yield(string + question)
|
||||
if answer.lower() in ["n", "no"]:
|
||||
caller.msg("|rSave cancelled.|n")
|
||||
return
|
||||
|
||||
# all seems ok. Try to save.
|
||||
try:
|
||||
prot = protlib.save_prototype(**prototype)
|
||||
if not prot:
|
||||
caller.msg("|rError saving:|R {}.|n".format(prototype_key))
|
||||
return
|
||||
except protlib.PermissionError as err:
|
||||
caller.msg("|rError saving:|R {}|n".format(err))
|
||||
return
|
||||
caller.msg("|gSaved prototype:|n {}".format(prototype_key))
|
||||
|
||||
# check if we want to update existing objects
|
||||
existing_objects = protlib.search_objects_with_prototype(prototype_key)
|
||||
if existing_objects:
|
||||
if 'update' not in self.switches:
|
||||
n_existing = len(existing_objects)
|
||||
slow = " (note that this may be slow)" if n_existing > 10 else ""
|
||||
string = ("There are {} objects already created with an older version "
|
||||
"of prototype {}. Should it be re-applied to them{}? [Y]/N".format(
|
||||
n_existing, prototype_key, slow))
|
||||
answer = yield(string)
|
||||
if answer.lower() in ["n", "no"]:
|
||||
caller.msg("|rNo update was done of existing objects. "
|
||||
"Use @spawn/update <key> to apply later as needed.|n")
|
||||
return
|
||||
n_updated = spawner.batch_update_objects_with_prototype(existing_objects, key)
|
||||
caller.msg("{} objects were updated.".format(n_updated))
|
||||
return
|
||||
|
||||
if not self.args:
|
||||
ncount = len(protlib.search_prototype())
|
||||
caller.msg("Usage: @spawn <prototype-key> or {{key: value, ...}}"
|
||||
"\n ({} existing prototypes. Use /list to inspect)".format(ncount))
|
||||
return
|
||||
|
||||
if 'delete' in self.switches:
|
||||
# remove db-based prototype
|
||||
matchstring = _search_show_prototype(self.args)
|
||||
if matchstring:
|
||||
string = "|rDeleting prototype:|n\n{}".format(matchstring)
|
||||
question = "\nDo you want to continue deleting? [Y]/N"
|
||||
answer = yield(string + question)
|
||||
if answer.lower() in ["n", "no"]:
|
||||
caller.msg("|rDeletion cancelled.|n")
|
||||
return
|
||||
try:
|
||||
success = protlib.delete_db_prototype(caller, self.args)
|
||||
except protlib.PermissionError as err:
|
||||
caller.msg("|rError deleting:|R {}|n".format(err))
|
||||
caller.msg("Deletion {}.".format(
|
||||
'successful' if success else 'failed (does the prototype exist?)'))
|
||||
return
|
||||
else:
|
||||
caller.msg("Could not find prototype '{}'".format(key))
|
||||
|
||||
if 'update' in self.switches:
|
||||
# update existing prototypes
|
||||
key = self.args.strip().lower()
|
||||
existing_objects = protlib.search_objects_with_prototype(key)
|
||||
if existing_objects:
|
||||
n_existing = len(existing_objects)
|
||||
slow = " (note that this may be slow)" if n_existing > 10 else ""
|
||||
string = ("There are {} objects already created with an older version "
|
||||
"of prototype {}. Should it be re-applied to them{}? [Y]/N".format(
|
||||
n_existing, key, slow))
|
||||
answer = yield(string)
|
||||
if answer.lower() in ["n", "no"]:
|
||||
caller.msg("|rUpdate cancelled.")
|
||||
return
|
||||
n_updated = spawner.batch_update_objects_with_prototype(existing_objects, key)
|
||||
caller.msg("{} objects were updated.".format(n_updated))
|
||||
|
||||
# A direct creation of an object from a given prototype
|
||||
|
||||
prototype = _parse_prototype(
|
||||
self.args, expect=dict if self.args.strip().startswith("{") else basestring)
|
||||
if not prototype:
|
||||
# this will only let through dicts or strings
|
||||
return
|
||||
|
||||
key = '<unnamed>'
|
||||
if isinstance(prototype, basestring):
|
||||
# A prototype key we are looking to apply
|
||||
key = prototype
|
||||
prototypes = protlib.search_prototype(prototype)
|
||||
nprots = len(prototypes)
|
||||
if not prototypes:
|
||||
caller.msg("No prototype named '%s'." % prototype)
|
||||
return
|
||||
elif nprots > 1:
|
||||
caller.msg("Found {} prototypes matching '{}':\n {}".format(
|
||||
nprots, prototype, ", ".join(prot.get('prototype_key', '')
|
||||
for proto in prototypes)))
|
||||
return
|
||||
# we have a prototype, check access
|
||||
prototype = prototypes[0]
|
||||
if not caller.locks.check_lockstring(caller, prototype.get('prototype_locks', ''), access_type='spawn'):
|
||||
caller.msg("You don't have access to use this prototype.")
|
||||
return
|
||||
|
||||
if "noloc" not in self.switches and "location" not in prototype:
|
||||
prototype["location"] = self.caller.location
|
||||
|
||||
for obj in spawn(prototype):
|
||||
self.caller.msg("Spawned %s." % obj.get_display_name(self.caller))
|
||||
# proceed to spawning
|
||||
try:
|
||||
for obj in spawner.spawn(prototype):
|
||||
self.caller.msg("Spawned %s." % obj.get_display_name(self.caller))
|
||||
except RuntimeError as err:
|
||||
caller.msg(err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue