Merge conflicts against master, including cmdhandler support for direct cmdobject input together with prefix-ignore mechanism from devel.

This commit is contained in:
Griatch 2017-04-01 16:08:23 +02:00
commit a648433db8
69 changed files with 2617 additions and 1771 deletions

View file

@ -40,16 +40,21 @@ class ObjectCreateForm(forms.ModelForm):
fields = '__all__'
db_key = forms.CharField(label="Name/Key",
widget=forms.TextInput(attrs={'size': '78'}),
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",)
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. "
"If creating a Character, check so the name is unique among characters!",)
db_typeclass_path = forms.CharField(label="Typeclass",
initial=settings.BASE_OBJECT_TYPECLASS,
widget=forms.TextInput(attrs={'size': '78'}),
help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
help_text="This defines what 'type' of entity this is. This variable holds a "
"Python path to a module with a valid Evennia Typeclass. If you are "
"creating a Character you should use the typeclass defined by "
"settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
db_cmdset_storage = forms.CharField(label="CmdSet",
initial="",
required=False,
widget=forms.TextInput(attrs={'size': '78'}),
help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
help_text="Most non-character objects don't need a cmdset"
" and can leave this field blank.")
raw_id_fields = ('db_destination', 'db_location', 'db_home')
@ -63,8 +68,10 @@ class ObjectEditForm(ObjectCreateForm):
fields = '__all__'
db_lock_storage = forms.CharField(label="Locks",
required=False,
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
widget=forms.Textarea(attrs={'cols': '100', 'rows': '2'}),
help_text="In-game lock definition string. If not given, defaults will be used. "
"This string should be on the form "
"<i>type:lockfunction(args);type2:lockfunction2(args);...")
class ObjectDBAdmin(admin.ModelAdmin):
@ -90,15 +97,15 @@ class ObjectDBAdmin(admin.ModelAdmin):
form = ObjectEditForm
fieldsets = (
(None, {
'fields': (('db_key','db_typeclass_path'), ('db_lock_storage', ),
('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
'fields': (('db_key', 'db_typeclass_path'), ('db_lock_storage', ),
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
)}),
)
add_form = ObjectCreateForm
add_fieldsets = (
(None, {
'fields': (('db_key','db_typeclass_path'),
'fields': (('db_key', 'db_typeclass_path'),
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
)}),
)

View file

@ -21,6 +21,7 @@ _MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U)
# Try to use a custom way to parse id-tagged multimatches.
class ObjectDBManager(TypedObjectManager):
"""
This ObjectManager implements methods for searching
@ -79,11 +80,13 @@ class ObjectDBManager(TypedObjectManager):
if dbref:
return dbref
# not a dbref. Search by name.
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
if exact:
return self.filter(cand_restriction & Q(db_player__username__iexact=ostring))
else: # fuzzy matching
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)).values_list("db_key", flat=True)
else: # fuzzy matching
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)
).values_list("db_key", flat=True)
if candidates:
index_matches = string_partial_matching(ply_cands, ostring, ret_index=True)
return [obj for ind, obj in enumerate(make_iter(candidates)) if ind in index_matches]
@ -103,7 +106,8 @@ class ObjectDBManager(TypedObjectManager):
Returns:
matches (list): The matching objects.
"""
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
return self.filter(cand_restriction & Q(db_key__iexact=oname, db_typeclass_path__exact=otypeclass_path))
# attr/property related
@ -121,7 +125,9 @@ class ObjectDBManager(TypedObjectManager):
matches (list): All objects having the given attribute_name defined at all.
"""
cand_restriction = candidates != None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
cand_restriction = candidates is not None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj
in make_iter(candidates)
if obj]) or Q()
return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)))
@returns_typeclass_list
@ -144,20 +150,23 @@ class ObjectDBManager(TypedObjectManager):
cannot be indexed, searching by Attribute key is to be preferred whenever possible.
"""
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
## This doesn't work if attribute_value is an object. Workaround below
# This doesn't work if attribute_value is an object. Workaround below
if isinstance(attribute_value, (basestring, int, float, bool)):
return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name, db_attributes__db_value=attribute_value))
return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name,
db_attributes__db_value=attribute_value))
else:
# We have to loop for safety since the referenced lookup gives deepcopy error if attribute value is an object.
# We must loop for safety since the referenced lookup gives deepcopy error if attribute value is an object.
global _ATTR
if not _ATTR:
from evennia.typeclasses.models import Attribute as _ATTR
cands = list(self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name)))
results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands, db_value=attribute_value)]
results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands,
db_value=attribute_value)]
return chain(*results)
@returns_typeclass_list
@ -174,8 +183,9 @@ class ObjectDBManager(TypedObjectManager):
"""
property_name = "db_%s" % property_name.lstrip('db_')
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
querykwargs = {property_name:None}
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
querykwargs = {property_name: None}
try:
return list(self.filter(cand_restriction).exclude(Q(**querykwargs)))
except exceptions.FieldError:
@ -198,8 +208,9 @@ class ObjectDBManager(TypedObjectManager):
if isinstance(property_name, basestring):
if not property_name.startswith('db_'):
property_name = "db_%s" % property_name
querykwargs = {property_name:property_value}
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
querykwargs = {property_name: property_value}
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
try:
return list(self.filter(cand_restriction & type_restriction & Q(**querykwargs)))
@ -207,7 +218,8 @@ class ObjectDBManager(TypedObjectManager):
return []
except ValueError:
from evennia.utils import logger
logger.log_err("The property '%s' does not support search criteria of the type %s." % (property_name, type(property_value)))
logger.log_err("The property '%s' does not support search criteria of the type %s." %
(property_name, type(property_value)))
return []
@returns_typeclass_list
@ -228,7 +240,7 @@ class ObjectDBManager(TypedObjectManager):
@returns_typeclass_list
def get_objs_with_key_or_alias(self, ostring, exact=True,
candidates=None, typeclasses=None):
candidates=None, typeclasses=None):
"""
Args:
ostring (str): A search criterion.
@ -253,7 +265,7 @@ class ObjectDBManager(TypedObjectManager):
# build query objects
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj]
cand_restriction = candidates != None and Q(pk__in=candidates_id) or Q()
cand_restriction = candidates is not None and Q(pk__in=candidates_id) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
if exact:
# exact match - do direct search
@ -264,7 +276,8 @@ class ObjectDBManager(TypedObjectManager):
search_candidates = self.filter(cand_restriction & type_restriction)
else:
# fuzzy without supplied candidates - we select our own candidates
search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct()
search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) |
Q(db_tags__db_key__istartswith=ostring))).distinct()
# fuzzy matching
key_strings = search_candidates.values_list("db_key", flat=True).order_by("id")
@ -275,10 +288,10 @@ class ObjectDBManager(TypedObjectManager):
else:
# match by alias rather than by key
search_candidates = search_candidates.filter(db_tags__db_tagtype__iexact="alias",
db_tags__db_key__icontains=ostring)
db_tags__db_key__icontains=ostring)
alias_strings = []
alias_candidates = []
#TODO create the alias_strings and alias_candidates lists more effiently?
# TODO create the alias_strings and alias_candidates lists more efficiently?
for candidate in search_candidates:
for alias in candidate.aliases.all():
alias_strings.append(alias)
@ -343,13 +356,16 @@ class ObjectDBManager(TypedObjectManager):
"""
if attribute_name:
# attribute/property search (always exact).
matches = self.get_objs_with_db_property_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
matches = self.get_objs_with_db_property_value(attribute_name, searchdata,
candidates=candidates, typeclasses=typeclass)
if matches:
return matches
return self.get_objs_with_attr_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
return self.get_objs_with_attr_value(attribute_name, searchdata,
candidates=candidates, typeclasses=typeclass)
else:
# normal key/alias search
return self.get_objs_with_key_or_alias(searchdata, exact=exact, candidates=candidates, typeclasses=typeclass)
return self.get_objs_with_key_or_alias(searchdata, exact=exact,
candidates=candidates, typeclasses=typeclass)
if not searchdata and searchdata != 0:
return []
@ -372,7 +388,7 @@ class ObjectDBManager(TypedObjectManager):
candidates = [cand for cand in make_iter(candidates) if cand]
if typeclass:
candidates = [cand for cand in candidates
if _GA(cand, "db_typeclass_path") in typeclass]
if _GA(cand, "db_typeclass_path") in typeclass]
dbref = not attribute_name and exact and use_dbref and self.dbref(searchdata)
if dbref:
@ -418,7 +434,6 @@ class ObjectDBManager(TypedObjectManager):
#
# ObjectManager Copy method
#
def copy_object(self, original_object, new_key=None,
new_location=None, new_home=None,

View file

@ -51,8 +51,7 @@ class ContentsHandler(object):
Re-initialize the content cache
"""
self._pkcache.update(dict((obj.pk, None) for obj in
ObjectDB.objects.filter(db_location=self.obj) if obj.pk))
self._pkcache.update(dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk))
def get(self, exclude=None):
"""
@ -79,7 +78,7 @@ class ContentsHandler(object):
return [self._idcache[pk] for pk in pks]
except KeyError:
# this means an actual failure of caching. Return real database match.
logger.log_err("contents cache failed for %s." % (self.obj.key))
logger.log_err("contents cache failed for %s." % self.obj.key)
return list(ObjectDB.objects.filter(db_location=self.obj))
def add(self, obj):
@ -110,11 +109,12 @@ class ContentsHandler(object):
self._pkcache = {}
self.init()
#------------------------------------------------------------
# -------------------------------------------------------------
#
# ObjectDB
#
#------------------------------------------------------------
# -------------------------------------------------------------
class ObjectDB(TypedObject):
"""
@ -173,17 +173,18 @@ class ObjectDB(TypedObject):
help_text='a Player connected to this object, if any.')
# the session id associated with this player, if any
db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id",
help_text="csv list of session ids of connected Player, if any.")
help_text="csv list of session ids of connected Player, if any.")
# The location in the game world. Since this one is likely
# to change often, we set this with the 'location' property
# to transparently handle Typeclassing.
db_location = models.ForeignKey('self', related_name="locations_set", db_index=True, on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='game location')
blank=True, null=True, verbose_name='game location')
# a safety location, this usually don't change much.
db_home = models.ForeignKey('self', related_name="homes_set", on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='home location')
blank=True, null=True, verbose_name='home location')
# destination of this object - primarily used by exits.
db_destination = models.ForeignKey('self', related_name="destinations_set", db_index=True, on_delete=models.SET_NULL,
db_destination = models.ForeignKey('self', related_name="destinations_set",
db_index=True, on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='destination',
help_text='a destination, used only by exit objects.')
# database storage of persistant cmdsets.
@ -204,28 +205,28 @@ class ObjectDB(TypedObject):
# cmdset_storage property handling
def __cmdset_storage_get(self):
"getter"
"""getter"""
storage = self.db_cmdset_storage
return [path.strip() for path in storage.split(',')] if storage else []
def __cmdset_storage_set(self, value):
"setter"
self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value))
"""setter"""
self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value))
self.save(update_fields=["db_cmdset_storage"])
def __cmdset_storage_del(self):
"deleter"
"""deleter"""
self.db_cmdset_storage = None
self.save(update_fields=["db_cmdset_storage"])
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del)
# location getsetter
def __location_get(self):
"Get location"
"""Get location"""
return self.db_location
def __location_set(self, location):
"Set location, checking for loops and allowing dbref"
"""Set location, checking for loops and allowing dbref"""
if isinstance(location, (basestring, int)):
# allow setting of #dbref
dbid = dbref(location, reqhash=False)
@ -237,9 +238,9 @@ class ObjectDB(TypedObject):
pass
try:
def is_loc_loop(loc, depth=0):
"Recursively traverse target location, trying to catch a loop."
"""Recursively traverse target location, trying to catch a loop."""
if depth > 10:
return
return None
elif loc == self:
raise RuntimeError
elif loc is None:
@ -248,7 +249,7 @@ class ObjectDB(TypedObject):
try:
is_loc_loop(location)
except RuntimeWarning:
# we caught a infitite location loop!
# we caught an infinite location loop!
# (location1 is in location2 which is in location1 ...)
pass
@ -281,7 +282,7 @@ class ObjectDB(TypedObject):
return
def __location_del(self):
"Cleanly delete the location reference"
"""Cleanly delete the location reference"""
self.db_location = None
self.save(update_fields=["db_location"])
location = property(__location_get, __location_set, __location_del)
@ -311,7 +312,6 @@ class ObjectDB(TypedObject):
[o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()]
class Meta(object):
"Define Django meta options"
"""Define Django meta options"""
verbose_name = "Object"
verbose_name_plural = "Objects"

View file

@ -21,7 +21,7 @@ from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.commands import cmdhandler
from evennia.utils import logger
from evennia.utils.utils import (variable_from_module, lazy_property,
make_iter, to_unicode, calledby)
make_iter, to_unicode, calledby, is_iter)
_MULTISESSION_MODE = settings.MULTISESSION_MODE
@ -34,6 +34,7 @@ _SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1
from django.utils.translation import ugettext as _
class ObjectSessionHandler(object):
"""
Handles the get/setting of the sessid
@ -133,7 +134,7 @@ class ObjectSessionHandler(object):
Remove session from handler.
Args:
sessid (Session or int): Session or session id to remove.
session (Session or int): Session or session id to remove.
"""
try:
@ -144,7 +145,7 @@ class ObjectSessionHandler(object):
sessid_cache = self._sessid_cache
if sessid in sessid_cache:
sessid_cache.remove(sessid)
self.obj.db_sessid = ",".join(str(val) for val in sessid_cache)
self.obj.db_sessid = ",".join(str(val) for val in sessid_cache)
self.obj.save(update_fields=["db_sessid"])
def clear(self):
@ -167,10 +168,9 @@ class ObjectSessionHandler(object):
return len(self._sessid_cache)
#
# Base class to inherit from.
#
class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
@ -221,7 +221,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
return self.db_player and self.db_player.is_superuser \
and not self.db_player.attributes.get("_quell")
and not self.db_player.attributes.get("_quell")
def contents_get(self, exclude=None):
"""
@ -241,7 +241,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
con = self.contents_cache.get(exclude=exclude)
#print "contents_get:", self, con, id(self), calledby()
# print "contents_get:", self, con, id(self), calledby() # DEBUG
return con
contents = property(contents_get)
@ -370,8 +370,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# do nick-replacement on search
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
if(global_search or (is_string and searchdata.startswith("#") and
len(searchdata) > 1 and searchdata[1:].isdigit())):
if (global_search or (is_string and searchdata.startswith("#") and
len(searchdata) > 1 and searchdata[1:].isdigit())):
# only allow exact matching if searching the entire database
# or unique #dbrefs
exact = True
@ -403,8 +403,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
use_dbref=use_dbref)
if quiet:
return results
return _AT_SEARCH_RESULT(results, self, query=searchdata,
nofound_string=nofound_string, multimatch_string=multimatch_string)
return _AT_SEARCH_RESULT(results, self, query=searchdata,
nofound_string=nofound_string, multimatch_string=multimatch_string)
def search_player(self, searchdata, quiet=False):
"""
@ -474,11 +474,9 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# nick replacement - we require full-word matching.
# do text encoding conversion
raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=True)
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=True)
return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs)
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
"""
Emits something to a session attached to the object.
@ -549,21 +547,28 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
for obj in contents:
func(obj, **kwargs)
def msg_contents(self, message, exclude=None, from_obj=None, mapping=None, **kwargs):
def msg_contents(self, text=None, exclude=None, from_obj=None, mapping=None, **kwargs):
"""
Emits a message to all objects inside this object.
Args:
message (str): Message to send.
text (str or tuple): Message to send. If a tuple, this should be
on the valid OOB outmessage form `(message, {kwargs})`,
where kwargs are optional data passed to the `text`
outputfunc.
exclude (list, optional): A list of objects not to send to.
from_obj (Object, optional): An object designated as the
"sender" of the message. See `DefaultObject.msg()` for
more info.
mapping (dict, optional): A mapping of formatting keys
`{"key":<object>, "key2":<object2>,...}. The keys
must match `{key}` markers in `message` and will be
must match `{key}` markers in the `text` if this is a string or
in the internal `message` if `text` is a tuple. These
formatting statements will be
replaced by the return of `<object>.get_display_name(looker)`
for every looker that is messaged.
for every looker in contents that receives the
message. This allows for every object to potentially
get its own customized string.
Kwargs:
Keyword arguments will be passed on to `obj.msg()` for all
messaged objects.
@ -577,14 +582,23 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
not have `get_display_name()`, its string value will be used.
Example:
Say char is a Character object and npc is an NPC object:
Say Char is a Character object and Npc is an NPC object:
action = 'kicks'
char.location.msg_contents(
"{attacker} {action} {defender}",
mapping=dict(attacker=char, defender=npc, action=action),
exclude=(char, npc))
"{attacker} kicks {defender}",
mapping=dict(attacker=char, defender=npc), exclude=(char, npc))
This will result in everyone in the room seeing 'Char kicks NPC'
where everyone may potentially see different results for Char and Npc
depending on the results of `char.get_display_name(looker)` and
`npc.get_display_name(looker)` for each particular onlooker
"""
# we also accept an outcommand on the form (message, {kwargs})
is_outcmd = text and is_iter(text)
inmessage = text[0] if is_outcmd else text
outkwargs = text[1] if is_outcmd and len(text) > 1 else {}
contents = self.contents
if exclude:
exclude = make_iter(exclude)
@ -592,12 +606,12 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
for obj in contents:
if mapping:
substitutions = {t: sub.get_display_name(obj)
if hasattr(sub, 'get_display_name')
else str(sub)
for t, sub in mapping.items()}
obj.msg(message.format(**substitutions), from_obj=from_obj, **kwargs)
if hasattr(sub, 'get_display_name')
else str(sub) for t, sub in mapping.items()}
outmessage = inmessage.format(**substitutions)
else:
obj.msg(message, from_obj=from_obj, **kwargs)
outmessage = inmessage
obj.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
def move_to(self, destination, quiet=False,
emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True):
@ -642,7 +656,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
def logerr(string="", err=None):
"Simple log helper method"
"""Simple log helper method"""
logger.log_trace()
self.msg("%s%s" % (string, "" if err is None else " (%s)" % err))
return
@ -658,7 +672,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.location = None
return True
emit_to_obj.msg(_("The destination doesn't exist."))
return
return False
if destination.destination and use_destination:
# traverse exits
destination = destination.destination
@ -667,7 +681,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
if move_hooks:
try:
if not self.at_before_move(destination):
return
return False
except Exception as err:
logerr(errtxt % "at_before_move()", err)
return False
@ -684,7 +698,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return False
if not quiet:
#tell the old room we are leaving
# tell the old room we are leaving
try:
self.announce_move_from(destination)
except Exception as err:
@ -704,7 +718,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.announce_move_to(source_location)
except Exception as err:
logerr(errtxt % "announce_move_to()", err)
return False
return False
if move_hooks:
# Perform eventual extra commands on the receiving location
@ -779,7 +793,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
obj.msg(_(string))
obj.move_to(home)
def copy(self, new_key=None):
"""
Makes an identical copy of this object, identical except for a
@ -803,15 +816,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
key = self.key
num = 1
for obj in (obj for obj in self.location.contents
if obj.key.startswith(key) and
obj.key.lstrip(key).isdigit()):
for _ in (obj for obj in self.location.contents
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
num += 1
return "%s%03i" % (key, num)
new_key = new_key or find_clone_key()
return ObjectDB.objects.copy_object(self, new_key=new_key)
delete_iter = 0
def delete(self):
"""
Deletes this object. Before deletion, this method makes sure
@ -862,7 +875,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.attributes.clear()
self.nicks.clear()
self.aliases.clear()
self.location = None # this updates contents_cache for our location
self.location = None # this updates contents_cache for our location
# Perform the deletion of the object
super(DefaultObject, self).delete()
@ -955,8 +968,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.basetype_posthook_setup()
## hooks called by the game engine
# hooks called by the game engine #
def basetype_setup(self):
"""
@ -1032,8 +1044,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
have no cmdsets.
Kwargs:
Usually not set but could be used e.g. to force rebuilding
of a dynamically created cmdset or similar.
caller (Session, Object or Player): The caller requesting
this cmdset.
"""
pass
@ -1118,9 +1130,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
result (bool): The outcome of the access call.
accessing_obj (Object or Player): The entity trying to
gain access. access_type (str): The type of access that
was requested.
accessing_obj (Object or Player): The entity trying to gain access.
access_type (str): The type of access that was requested.
Kwargs:
Not used by default, added for possible expandability in a
@ -1147,10 +1158,10 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
before it is even started.
"""
#return has_perm(self, destination, "can_move")
# return has_perm(self, destination, "can_move")
return True
def announce_move_from(self, destination):
def announce_move_from(self, destination, msg=None, mapping=None):
"""
Called if the move is to be announced. This is
called while we are still standing in the old
@ -1158,25 +1169,56 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
destination (Object): The place we are going to.
msg (str, optional): a replacement message.
mapping (dict, optional): additional mapping objects.
You can override this method and call its parent with a
message to simply change the default message. In the string,
you can use the following as mappings (between braces):
object: the object which is moving.
exit: the exit from which the object is moving (if found).
origin: the location of the object before the move.
destination: the location of the object after moving.
"""
if not self.location:
return
string = "%s is leaving %s, heading for %s."
location = self.location
for obj in self.location.contents:
if obj != self:
obj.msg(string % (self.get_display_name(obj),
location.get_display_name(obj) if location else "nowhere",
destination.get_display_name(obj)))
if msg:
string = msg
else:
string = "{object} is leaving {origin}, heading for {destination}."
def announce_move_to(self, source_location):
location = self.location
exits = [o for o in location.contents if o.location is location and o.destination is destination]
if not mapping:
mapping = {}
mapping.update({
"object": self,
"exit": exits[0] if exits else "somwhere",
"origin": location or "nowhere",
"destination": destination or "nowhere",
})
location.msg_contents(string, exclude=(self, ), mapping=mapping)
def announce_move_to(self, source_location, msg=None, mapping=None):
"""
Called after the move if the move was not quiet. At this point
we are standing in the new location.
Args:
source_location (Object): The place we came from
msg (str, optional): the replacement message if location.
mapping (dict, optional): additional mapping objects.
You can override this method and call its parent with a
message to simply change the default message. In the string,
you can use the following as mappings (between braces):
object: the object which is moving.
exit: the exit from which the object is moving (if found).
origin: the location of the object before the move.
destination: the location of the object after moving.
"""
@ -1187,13 +1229,31 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.location.msg(string)
return
string = "%s arrives to %s%s."
location = self.location
for obj in self.location.contents:
if obj != self:
obj.msg(string % (self.get_display_name(obj),
location.get_display_name(obj) if location else "nowhere",
" from %s" % source_location.get_display_name(obj) if source_location else ""))
if source_location:
if msg:
string = msg
else:
string = "{object} arrives to {destination} from {origin}."
else:
string = "{object} arrives to {destination}."
origin = source_location
destination = self.location
exits = []
if origin:
exits = [o for o in destination.contents if o.location is destination and o.destination is origin]
if not mapping:
mapping = {}
mapping.update({
"object": self,
"exit": exits[0] if exits else "somewhere",
"origin": origin or "nowhere",
"destination": destination or "nowhere",
})
destination.msg_contents(string, exclude=(self, ), mapping=mapping)
def at_after_move(self, source_location):
"""
@ -1288,7 +1348,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
check for this. .
Consider this a pre-processing method before msg is passed on
to the user sesssion. If this method returns False, the msg
to the user session. If this method returns False, the msg
will not be passed on.
Args:
@ -1341,7 +1401,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return ""
# get and identify all objects
visible = (con for con in self.contents if con != looker and
con.access(looker, "view"))
con.access(looker, "view"))
exits, users, things = [], [], []
for con in visible:
key = con.get_display_name(looker)
@ -1416,6 +1476,22 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
pass
def at_give(self, giver, getter):
"""
Called by the default `give` command when this object has been
given.
Args:
giver (Object): The object giving this object.
getter (Object): The object getting this object.
Notes:
This hook cannot stop the give from happening. Use
permissions for that.
"""
pass
def at_drop(self, dropper):
"""
Called by the default `drop` command when this object has been
@ -1425,7 +1501,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
dropper (Object): The object which just dropped this object.
Notes:
This hook cannot stop the pickup from happening. Use
This hook cannot stop the drop from happening. Use
permissions from that.
"""
@ -1473,7 +1549,7 @@ class DefaultCharacter(DefaultObject):
"""
super(DefaultCharacter, self).basetype_setup()
self.locks.add(";".join(["get:false()", # noone can pick up the character
"call:false()"])) # no commands can be called on character from outside
"call:false()"])) # no commands can be called on character from outside
# add the default cmdset
self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
@ -1565,7 +1641,7 @@ class DefaultCharacter(DefaultObject):
#
# Base Room object
#
class DefaultRoom(DefaultObject):
"""
@ -1581,7 +1657,7 @@ class DefaultRoom(DefaultObject):
super(DefaultRoom, self).basetype_setup()
self.locks.add(";".join(["get:false()",
"puppet:false()"])) # would be weird to puppet a room ...
"puppet:false()"])) # would be weird to puppet a room ...
self.location = None
@ -1631,7 +1707,7 @@ class ExitCommand(command.Command):
#
# Base Exit object
#
class DefaultExit(DefaultObject):
"""
@ -1683,8 +1759,8 @@ class DefaultExit(DefaultObject):
exit_cmdset.add(cmd)
return exit_cmdset
# Command hooks
def basetype_setup(self):
"""
Setup exit-security
@ -1696,8 +1772,8 @@ class DefaultExit(DefaultObject):
super(DefaultExit, self).basetype_setup()
# setting default locks (overload these in at_object_creation()
self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ...
"traverse:all()", # who can pass through exit by default
self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ...
"traverse:all()", # who can pass through exit by default
"get:false()"])) # noone can pick up the exit
# an exit should have a destination (this is replaced at creation time)