mirror of
https://github.com/evennia/evennia.git
synced 2026-03-19 06:16:31 +01:00
As part of this overhaul I implemented the partial_matching algorithm originally asked for by user "Adam_ASE" over IRC. This will allow for (local-only) partial matching of objects. So "big black sword" will now be matched by "bi", "sword", "bi bla" and so on. The partial matcher sits in src.utils.utils.py if one wants to use it for something else.
915 lines
34 KiB
Python
915 lines
34 KiB
Python
"""
|
|
This module defines the database models for all in-game objects, that
|
|
is, all objects that has an actual existence in-game.
|
|
|
|
Each database object is 'decorated' with a 'typeclass', a normal
|
|
python class that implements all the various logics needed by the game
|
|
in question. Objects created of this class transparently communicate
|
|
with its related database object for storing all attributes. The
|
|
admin should usually not have to deal directly with this database
|
|
object layer.
|
|
|
|
Attributes are separate objects that store values persistently onto
|
|
the database object. Like everything else, they can be accessed
|
|
transparently through the decorating TypeClass.
|
|
"""
|
|
|
|
import traceback
|
|
from django.db import models
|
|
from django.conf import settings
|
|
|
|
from src.utils.idmapper.models import SharedMemoryModel
|
|
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
|
|
from src.typeclasses.models import _get_cache, _set_cache, _del_cache
|
|
from src.typeclasses.typeclass import TypeClass
|
|
from src.objects.manager import ObjectManager
|
|
from src.commands.cmdsethandler import CmdSetHandler
|
|
from src.commands import cmdhandler
|
|
from src.scripts.scripthandler import ScriptHandler
|
|
from src.utils import logger
|
|
from src.utils.utils import make_iter, to_unicode, variable_from_module, inherits_from
|
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
#__all__ = ("ObjAttribute", "Alias", "ObjectNick", "ObjectDB")
|
|
|
|
|
|
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
|
|
|
_GA = object.__getattribute__
|
|
_SA = object.__setattr__
|
|
_DA = object.__delattr__
|
|
|
|
_ME = _("me")
|
|
_SELF = _("self")
|
|
_HERE = _("here")
|
|
|
|
|
|
def clean_content_cache(obj):
|
|
"Clean obj's content cache"
|
|
_SA(obj, "_contents_cache", None)
|
|
|
|
#------------------------------------------------------------
|
|
#
|
|
# ObjAttribute
|
|
#
|
|
#------------------------------------------------------------
|
|
|
|
class ObjAttribute(Attribute):
|
|
"Attributes for ObjectDB objects."
|
|
db_obj = models.ForeignKey("ObjectDB")
|
|
|
|
class Meta:
|
|
"Define Django meta options"
|
|
verbose_name = "Object Attribute"
|
|
verbose_name_plural = "Object Attributes"
|
|
|
|
#------------------------------------------------------------
|
|
#
|
|
# Alias
|
|
#
|
|
#------------------------------------------------------------
|
|
|
|
class Alias(SharedMemoryModel):
|
|
"""
|
|
This model holds a range of alternate names for an object.
|
|
These are intrinsic properties of the object. The split
|
|
is so as to allow for effective global searches also by
|
|
alias.
|
|
"""
|
|
db_key = models.CharField('alias', max_length=255, db_index=True)
|
|
db_obj = models.ForeignKey("ObjectDB", verbose_name='object')
|
|
|
|
class Meta:
|
|
"Define Django meta options"
|
|
verbose_name = "Object alias"
|
|
verbose_name_plural = "Object aliases"
|
|
def __unicode__(self):
|
|
return u"%s" % self.db_key
|
|
def __str__(self):
|
|
return str(self.db_key)
|
|
|
|
|
|
|
|
#------------------------------------------------------------
|
|
#
|
|
# Object Nicks
|
|
#
|
|
#------------------------------------------------------------
|
|
|
|
class ObjectNick(TypeNick):
|
|
"""
|
|
|
|
The default nick types used by Evennia are:
|
|
inputline (default) - match against all input
|
|
player - match against player searches
|
|
obj - match against object searches
|
|
channel - used to store own names for channels
|
|
"""
|
|
db_obj = models.ForeignKey("ObjectDB", verbose_name='object')
|
|
|
|
class Meta:
|
|
"Define Django meta options"
|
|
verbose_name = "Nickname for Objects"
|
|
verbose_name_plural = "Nicknames for Objects"
|
|
unique_together = ("db_nick", "db_type", "db_obj")
|
|
|
|
class ObjectNickHandler(TypeNickHandler):
|
|
"""
|
|
Handles nick access and setting. Accessed through ObjectDB.nicks
|
|
"""
|
|
NickClass = ObjectNick
|
|
|
|
|
|
#------------------------------------------------------------
|
|
#
|
|
# ObjectDB
|
|
#
|
|
#------------------------------------------------------------
|
|
|
|
class ObjectDB(TypedObject):
|
|
"""
|
|
All objects in the game use the ObjectDB model to store
|
|
data in the database. This is handled transparently through
|
|
the typeclass system.
|
|
|
|
Note that the base objectdb is very simple, with
|
|
few defined fields. Use attributes to extend your
|
|
type class with new database-stored variables.
|
|
|
|
The TypedObject supplies the following (inherited) properties:
|
|
key - main name
|
|
name - alias for key
|
|
typeclass_path - the path to the decorating typeclass
|
|
typeclass - auto-linked typeclass
|
|
date_created - time stamp of object creation
|
|
permissions - perm strings
|
|
locks - lock definitions (handler)
|
|
dbref - #id of object
|
|
db - persistent attribute storage
|
|
ndb - non-persistent attribute storage
|
|
|
|
The ObjectDB adds the following properties:
|
|
player - optional connected player
|
|
location - in-game location of object
|
|
home - safety location for object (handler)
|
|
|
|
scripts - scripts assigned to object (handler from typeclass)
|
|
cmdset - active cmdset on object (handler from typeclass)
|
|
aliases - aliases for this object (property)
|
|
nicks - nicknames for *other* things in Evennia (handler)
|
|
sessions - sessions connected to this object (see also player)
|
|
has_player - bool if an active player is currently connected
|
|
contents - other objects having this object as location
|
|
exits - exits from this object
|
|
"""
|
|
|
|
#
|
|
# ObjectDB Database model setup
|
|
#
|
|
#
|
|
# inherited fields (from TypedObject):
|
|
# db_key (also 'name' works), db_typeclass_path, db_date_created,
|
|
# db_permissions
|
|
#
|
|
# These databse fields (including the inherited ones) are all set
|
|
# using their corresponding properties, named same as the field,
|
|
# but withtout the db_* prefix.
|
|
|
|
# If this is a character object, the player is connected here.
|
|
db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player',
|
|
help_text='a Player connected to this object, 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,
|
|
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",
|
|
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,
|
|
blank=True, null=True, verbose_name='destination',
|
|
help_text='a destination, used only by exit objects.')
|
|
# database storage of persistant cmdsets.
|
|
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True, blank=True,
|
|
help_text="optional python path to a cmdset class.")
|
|
|
|
# Database manager
|
|
objects = ObjectManager()
|
|
|
|
# Add the object-specific handlers
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"Parent must be initialized first."
|
|
TypedObject.__init__(self, *args, **kwargs)
|
|
# handlers
|
|
self.cmdset = CmdSetHandler(self)
|
|
self.cmdset.update(init_mode=True)
|
|
self.scripts = ScriptHandler(self)
|
|
self.nicks = ObjectNickHandler(self)
|
|
# store the attribute class
|
|
|
|
# Wrapper properties to easily set database fields. These are
|
|
# @property decorators that allows to access these fields using
|
|
# normal python operations (without having to remember to save()
|
|
# etc). So e.g. a property 'attr' has a get/set/del decorator
|
|
# defined that allows the user to do self.attr = value,
|
|
# value = self.attr and del self.attr respectively (where self
|
|
# is the object in question).
|
|
|
|
# aliases property (wraps (db_aliases)
|
|
#@property
|
|
def __aliases_get(self):
|
|
"Getter. Allows for value = self.aliases"
|
|
try:
|
|
return _GA(self, "_cached_aliases")
|
|
except AttributeError:
|
|
aliases = list(Alias.objects.filter(db_obj=self).values_list("db_key", flat=True))
|
|
_SA(self, "_cached_aliases", aliases)
|
|
return aliases
|
|
#@aliases.setter
|
|
def __aliases_set(self, aliases):
|
|
"Setter. Allows for self.aliases = value"
|
|
for alias in make_iter(aliases):
|
|
new_alias = Alias(db_key=alias, db_obj=self)
|
|
new_alias.save()
|
|
_SA(self, "_cached_aliases", aliases)
|
|
#@aliases.deleter
|
|
def __aliases_del(self):
|
|
"Deleter. Allows for del self.aliases"
|
|
for alias in Alias.objects.filter(db_obj=self):
|
|
alias.delete()
|
|
_DA(self, "_cached_aliases")
|
|
aliases = property(__aliases_get, __aliases_set, __aliases_del)
|
|
|
|
# player property (wraps db_player)
|
|
#@property
|
|
def __player_get(self):
|
|
"""
|
|
Getter. Allows for value = self.player.
|
|
We have to be careful here since Player is also
|
|
a TypedObject, so as to not create a loop.
|
|
"""
|
|
return _get_cache(self, "player")
|
|
#@player.setter
|
|
def __player_set(self, player):
|
|
"Setter. Allows for self.player = value"
|
|
if inherits_from(player, TypeClass):
|
|
player = player.dbobj
|
|
_set_cache(self, "player", player)
|
|
#@player.deleter
|
|
def __player_del(self):
|
|
"Deleter. Allows for del self.player"
|
|
_del_cache(self, "player")
|
|
player = property(__player_get, __player_set, __player_del)
|
|
|
|
# location property (wraps db_location)
|
|
#@property
|
|
def __location_get(self):
|
|
"Getter. Allows for value = self.location."
|
|
loc = _get_cache(self, "location")
|
|
if loc:
|
|
return loc.typeclass
|
|
return None
|
|
#@location.setter
|
|
def __location_set(self, location):
|
|
"Setter. Allows for self.location = location"
|
|
try:
|
|
old_loc = _GA(self, "location")
|
|
if location == None or type(location) == ObjectDB:
|
|
# location is None or a valid object
|
|
loc = location
|
|
elif ObjectDB.objects.dbref(location):
|
|
# location is a dbref; search
|
|
loc = ObjectDB.objects.dbref_search(location)
|
|
if loc and hasattr(loc,'dbobj'):
|
|
loc = loc.dbobj
|
|
else:
|
|
loc = location.dbobj
|
|
else:
|
|
loc = location.dbobj
|
|
_set_cache(self, "location", loc)
|
|
# update the contents of each location
|
|
if old_loc:
|
|
old_loc.contents_update(self, remove=True)
|
|
if loc:
|
|
loc.contents_update(self.typeclass)
|
|
except Exception:
|
|
string = "Cannot set location: "
|
|
string += "%s is not a valid location."
|
|
self.msg(_(string) % location)
|
|
logger.log_trace(string)
|
|
raise
|
|
#@location.deleter
|
|
def __location_del(self):
|
|
"Deleter. Allows for del self.location"
|
|
self.location.contents_update(self, remove=True)
|
|
_SA(self, "db_location", None)
|
|
_GA(self, "save")()
|
|
_del_cache(self, "location")
|
|
location = property(__location_get, __location_set, __location_del)
|
|
|
|
# home property (wraps db_home)
|
|
#@property
|
|
def __home_get(self):
|
|
"Getter. Allows for value = self.home"
|
|
home = _get_cache(self, "home")
|
|
if home:
|
|
return home.typeclass
|
|
return None
|
|
#@home.setter
|
|
def __home_set(self, home):
|
|
"Setter. Allows for self.home = value"
|
|
try:
|
|
if home == None or type(home) == ObjectDB:
|
|
hom = home
|
|
elif ObjectDB.objects.dbref(home):
|
|
hom = ObjectDB.objects.dbref_search(home)
|
|
if hom and hasattr(hom,'dbobj'):
|
|
hom = hom.dbobj
|
|
else:
|
|
hom = home.dbobj
|
|
else:
|
|
hom = home.dbobj
|
|
_set_cache(self, "home", hom)
|
|
except Exception:
|
|
string = "Cannot set home: "
|
|
string += "%s is not a valid home."
|
|
self.msg(_(string) % home)
|
|
logger.log_trace(string)
|
|
#raise
|
|
#@home.deleter
|
|
def __home_del(self):
|
|
"Deleter. Allows for del self.home."
|
|
self.db_home = None
|
|
self.save()
|
|
_del_cache(self, "home")
|
|
home = property(__home_get, __home_set, __home_del)
|
|
|
|
# destination property (wraps db_destination)
|
|
#@property
|
|
def __destination_get(self):
|
|
"Getter. Allows for value = self.destination."
|
|
dest = _get_cache(self, "destination")
|
|
if dest:
|
|
return dest.typeclass
|
|
return None
|
|
#@destination.setter
|
|
def __destination_set(self, destination):
|
|
"Setter. Allows for self.destination = destination"
|
|
try:
|
|
if destination == None or type(destination) == ObjectDB:
|
|
# destination is None or a valid object
|
|
dest = destination
|
|
elif ObjectDB.objects.dbref(destination):
|
|
# destination is a dbref; search
|
|
dest = ObjectDB.objects.dbref_search(destination)
|
|
if dest and hasattr(dest,'dbobj'):
|
|
dest = dest.dbobj
|
|
else:
|
|
dest = destination.dbobj
|
|
else:
|
|
dest = destination.dbobj
|
|
_set_cache(self, "destination", dest)
|
|
except Exception:
|
|
string = "Cannot set destination: "
|
|
string += "%s is not a valid destination."
|
|
self.msg(_(string) % destination)
|
|
logger.log_trace(string)
|
|
raise
|
|
#@destination.deleter
|
|
def __destination_del(self):
|
|
"Deleter. Allows for del self.destination"
|
|
self.db_destination = None
|
|
self.save()
|
|
_del_cache(self, "destination")
|
|
destination = property(__destination_get, __destination_set, __destination_del)
|
|
|
|
# cmdset_storage property.
|
|
# This seems very sensitive to caching, so leaving it be for now. /Griatch
|
|
#@property
|
|
def __cmdset_storage_get(self):
|
|
"Getter. Allows for value = self.name. Returns a list of cmdset_storage."
|
|
if self.db_cmdset_storage:
|
|
return [path.strip() for path in self.db_cmdset_storage.split(',')]
|
|
return []
|
|
#@cmdset_storage.setter
|
|
def __cmdset_storage_set(self, value):
|
|
"Setter. Allows for self.name = value. Stores as a comma-separated string."
|
|
value = ",".join(str(val).strip() for val in make_iter(value))
|
|
self.db_cmdset_storage = value
|
|
self.save()
|
|
#@cmdset_storage.deleter
|
|
def __cmdset_storage_del(self):
|
|
"Deleter. Allows for del self.name"
|
|
self.db_cmdset_storage = ""
|
|
self.save()
|
|
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del)
|
|
|
|
class Meta:
|
|
"Define Django meta options"
|
|
verbose_name = "Object"
|
|
verbose_name_plural = "Objects"
|
|
|
|
#
|
|
# ObjectDB class access methods/properties
|
|
#
|
|
|
|
# this is required to properly handle attributes and typeclass loading.
|
|
_typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
|
|
_attribute_class = ObjAttribute
|
|
_db_model_name = "objectdb" # used by attributes to safely store objects
|
|
_default_typeclass_path = settings.BASE_OBJECT_TYPECLASS or "src.objects.objects.Object"
|
|
|
|
#@property
|
|
def __sessions_get(self):
|
|
"""
|
|
Retrieve sessions connected to this object.
|
|
"""
|
|
# if the player is not connected, this will simply be an empty list.
|
|
if self.player:
|
|
return self.player.sessions
|
|
return []
|
|
sessions = property(__sessions_get)
|
|
|
|
#@property
|
|
def __has_player_get(self):
|
|
"""
|
|
Convenience function for checking if an active player is
|
|
currently connected to this object
|
|
"""
|
|
return any(self.sessions)
|
|
has_player = property(__has_player_get)
|
|
is_player = property(__has_player_get)
|
|
|
|
#@property
|
|
def __is_superuser_get(self):
|
|
"Check if user has a player, and if so, if it is a superuser."
|
|
return any(self.sessions) and self.player.is_superuser
|
|
is_superuser = property(__is_superuser_get)
|
|
|
|
# contents
|
|
|
|
_contents_cache = None
|
|
#@property
|
|
def contents_get(self, exclude=None):
|
|
"""
|
|
Returns the contents of this object, i.e. all
|
|
objects that has this object set as its location.
|
|
This should be publically available.
|
|
|
|
exclude is one or more objects to not return
|
|
"""
|
|
if _GA(self, "_contents_cache") == None:
|
|
# create the cache
|
|
_SA(self, "_contents_cache", dict((obj.id, obj) for obj in ObjectDB.objects.get_contents(self)))
|
|
if exclude:
|
|
exclude = make_iter(exclude)
|
|
return [obj for obj in _GA(self, "_contents_cache").values() if obj not in exclude]
|
|
return _GA(self, "_contents_cache").values()
|
|
#return ObjectDB.objects.get_contents(self, excludeobj=exclude)
|
|
contents = property(contents_get)
|
|
|
|
def contents_update(self, obj, remove=False):
|
|
"""
|
|
Updates the contents property of the object. Called by
|
|
self.location_set.
|
|
"""
|
|
# this creates/updates the cache
|
|
_GA(self, "contents")
|
|
# set/remove objects from contents cache
|
|
cache = _GA(self, "_contents_cache")
|
|
if remove and obj.id in cache:
|
|
del cache[obj.id]
|
|
else:
|
|
cache[obj.id] = obj
|
|
|
|
#@property
|
|
def __exits_get(self):
|
|
"""
|
|
Returns all exits from this object, i.e. all objects
|
|
at this location having the property destination != None.
|
|
"""
|
|
return [exi for exi in self.contents
|
|
if exi.destination]
|
|
exits = property(__exits_get)
|
|
|
|
|
|
#
|
|
# Main Search method
|
|
#
|
|
|
|
def search(self, ostring,
|
|
global_search=False,
|
|
attribute_name=None,
|
|
use_nicks=False, location=None,
|
|
player=False,
|
|
ignore_errors=False, exact=False):
|
|
"""
|
|
Perform a standard object search in the database, handling
|
|
multiple results and lack thereof gracefully.
|
|
|
|
ostring: (str) The string to match object names against.
|
|
Obs - To find a player, append * to the
|
|
start of ostring.
|
|
global_search: Search all objects, not just the current
|
|
location/inventory. This is overruled if location keyword is given.
|
|
attribute_name: (string) Which attribute to match (if None, uses default 'name')
|
|
use_nicks : Use nickname replace (off by default)
|
|
location : If None, use caller's current location
|
|
player: return the Objects' controlling Player, instead, if available
|
|
ignore_errors : Don't display any error messages even
|
|
if there are none/multiple matches -
|
|
just return the result as a list.
|
|
exact: Determines if the search must exactly match the key/alias of the
|
|
given object or if partial matches the beginnings of one or more
|
|
words in the name is enough. Exact matching is faster if using
|
|
global search. Also, if attribute_name is set, matching is always exact.
|
|
|
|
Returns - a unique Object/Player match or None. All error
|
|
messages are handled by system-commands and the parser-handlers
|
|
specified in settings.
|
|
|
|
Use *<string> to search for objects controlled by a specific
|
|
player. Note that the object controlled by the player will be
|
|
returned, not the player object itself. This also means that
|
|
this will not find Players without a character. Use the keyword
|
|
player=True to find player objects.
|
|
|
|
Note - for multiple matches, the engine accepts a number
|
|
linked to the key in order to separate the matches from
|
|
each other without showing the dbref explicitly. Default
|
|
syntax for this is 'N-searchword'. So for example, if there
|
|
are three objects in the room all named 'ball', you could
|
|
address the individual ball as '1-ball', '2-ball', '3-ball'
|
|
etc.
|
|
"""
|
|
# handle some common self-references:
|
|
if ostring == _HERE:
|
|
return self.location
|
|
if ostring in (_ME, _SELF, '*' + _ME, '*' + _SELF):
|
|
return self
|
|
|
|
if use_nicks:
|
|
if ostring.startswith('*') or player:
|
|
# player nick replace
|
|
ostring = self.nicks.get(ostring.lstrip('*'), nick_type="player")
|
|
if not player:
|
|
ostring = "*%s" % ostring
|
|
else:
|
|
# object nick replace
|
|
ostring = self.nicks.get(ostring, nick_type="object")
|
|
|
|
candidates=None
|
|
if global_search:
|
|
# only allow exact matching if searching the entire database
|
|
exact = True
|
|
else:
|
|
# local search. Candidates are self.contents, self.location and self.location.contents
|
|
if not location:
|
|
location = self.location
|
|
candidates = self.contents
|
|
if location:
|
|
candidates = candidates + [location] + location.contents
|
|
else:
|
|
candidates.append(self) # normally we are included in location.contents
|
|
# db manager expects database objects
|
|
candidates = [obj.dbobj for obj in candidates]
|
|
|
|
results = ObjectDB.objects.object_search(ostring, caller=self,
|
|
attribute_name=attribute_name,
|
|
candidates=candidates,
|
|
exact=exact)
|
|
if not ignore_errors:
|
|
result = _AT_SEARCH_RESULT(self, ostring, results, global_search)
|
|
if player and result:
|
|
return result.player
|
|
return result
|
|
|
|
|
|
#
|
|
# Execution/action methods
|
|
#
|
|
|
|
def execute_cmd(self, raw_string):
|
|
"""
|
|
Do something as this object. This command transparently
|
|
lets its typeclass execute the command. Evennia also calls
|
|
this method whenever the player sends a command on the command line.
|
|
|
|
Argument:
|
|
raw_string (string) - raw command input
|
|
|
|
Returns Deferred - this is an asynchronous Twisted object that will
|
|
not fire until the command has actually finished executing. To overload
|
|
this one needs to attach callback functions to it, with addCallback(function).
|
|
This function will be called with an eventual return value from the command
|
|
execution.
|
|
|
|
This return is not used at all by Evennia by default, but might be useful
|
|
for coders intending to implement some sort of nested command structure.
|
|
"""
|
|
# nick replacement - we require full-word matching.
|
|
|
|
# do text encoding conversion
|
|
raw_string = to_unicode(raw_string)
|
|
|
|
raw_list = raw_string.split(None)
|
|
raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]]
|
|
for nick in ObjectNick.objects.filter(db_obj=self, db_type__in=("inputline","channel")):
|
|
if nick.db_nick in raw_list:
|
|
raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1)
|
|
break
|
|
return cmdhandler.cmdhandler(self.typeclass, raw_string)
|
|
|
|
def msg(self, message, from_obj=None, data=None):
|
|
"""
|
|
Emits something to any sessions attached to the object.
|
|
|
|
message (str): The message to send
|
|
from_obj (obj): object that is sending.
|
|
data (object): an optional data object that may or may not
|
|
be used by the protocol.
|
|
"""
|
|
# This is an important function that must always work.
|
|
# we use a different __getattribute__ to avoid recursive loops.
|
|
|
|
if object.__getattribute__(self, 'player'):
|
|
object.__getattribute__(self, 'player').msg(message, from_obj=from_obj, data=data)
|
|
|
|
def emit_to(self, message, from_obj=None, data=None):
|
|
"Deprecated. Alias for msg"
|
|
logger.log_depmsg("emit_to() is deprecated. Use msg() instead.")
|
|
self.msg(message, from_obj, data)
|
|
|
|
def msg_contents(self, message, exclude=None, from_obj=None, data=None):
|
|
"""
|
|
Emits something to all objects inside an object.
|
|
|
|
exclude is a list of objects not to send to. See self.msg() for more info.
|
|
"""
|
|
contents = self.contents
|
|
if exclude:
|
|
exclude = make_iter(exclude)
|
|
contents = [obj for obj in contents
|
|
if (obj not in exclude and obj not in exclude)]
|
|
for obj in contents:
|
|
obj.msg(message, from_obj=from_obj, data=data)
|
|
|
|
def emit_to_contents(self, message, exclude=None, from_obj=None, data=None):
|
|
"Deprecated. Alias for msg_contents"
|
|
logger.log_depmsg("emit_to_contents() is deprecated. Use msg_contents() instead.")
|
|
self.msg_contents(message, exclude=exclude, from_obj=from_obj, data=data)
|
|
|
|
def move_to(self, destination, quiet=False,
|
|
emit_to_obj=None, use_destination=True):
|
|
"""
|
|
Moves this object to a new location.
|
|
|
|
Moves this object to a new location. Note that if <destination> is an
|
|
exit object (i.e. it has "destination"!=None), the move_to will
|
|
happen to this destination and -not- into the exit object itself, unless
|
|
use_destination=False. Note that no lock checks are done by this function,
|
|
such things are assumed to have been handled before calling move_to.
|
|
|
|
destination: (Object) Reference to the object to move to. This
|
|
can also be an exit object, in which case the destination
|
|
property is used as destination.
|
|
quiet: (bool) If true, don't emit left/arrived messages.
|
|
emit_to_obj: (Object) object to receive error messages
|
|
use_destination (bool): Default is for objects to use the "destination" property
|
|
of destinations as the target to move to. Turning off this
|
|
keyword allows objects to move "inside" exit objects.
|
|
|
|
Returns True/False depending on if there were problems with the move. This method
|
|
may also return various error messages to the emit_to_obj.
|
|
"""
|
|
def logerr(string=""):
|
|
trc = traceback.format_exc()
|
|
errstring = "%s%s" % (trc, string)
|
|
logger.log_trace()
|
|
self.msg(errstring)
|
|
|
|
errtxt = _("Couldn't perform move ('%s'). Contact an admin.")
|
|
if not emit_to_obj:
|
|
emit_to_obj = self
|
|
|
|
if not destination:
|
|
emit_to_obj.msg(_("The destination doesn't exist."))
|
|
return
|
|
if destination.destination and use_destination:
|
|
# traverse exits
|
|
destination = destination.destination
|
|
|
|
# Before the move, call eventual pre-commands.
|
|
try:
|
|
if not self.at_before_move(destination):
|
|
return
|
|
except Exception:
|
|
logerr(errtxt % "at_before_move()")
|
|
#emit_to_obj.msg(errtxt % "at_before_move()")
|
|
#logger.log_trace()
|
|
return False
|
|
|
|
# Save the old location
|
|
source_location = self.location
|
|
if not source_location:
|
|
# there was some error in placing this room.
|
|
# we have to set one or we won't be able to continue
|
|
if self.home:
|
|
source_location = self.home
|
|
else:
|
|
default_home = ObjectDB.objects.get_id(settings.CHARACTER_DEFAULT_HOME)
|
|
source_location = default_home
|
|
|
|
# Call hook on source location
|
|
try:
|
|
source_location.at_object_leave(self, destination)
|
|
except Exception:
|
|
logerr(errtxt % "at_object_leave()")
|
|
#emit_to_obj.msg(errtxt % "at_object_leave()")
|
|
#logger.log_trace()
|
|
return False
|
|
|
|
if not quiet:
|
|
#tell the old room we are leaving
|
|
try:
|
|
self.announce_move_from(destination)
|
|
except Exception:
|
|
logerr(errtxt % "at_announce_move()")
|
|
#emit_to_obj.msg(errtxt % "at_announce_move()" )
|
|
#logger.log_trace()
|
|
return False
|
|
|
|
# Perform move
|
|
try:
|
|
self.location = destination
|
|
except Exception:
|
|
emit_to_obj.msg(errtxt % "location change")
|
|
logger.log_trace()
|
|
return False
|
|
|
|
if not quiet:
|
|
# Tell the new room we are there.
|
|
try:
|
|
self.announce_move_to(source_location)
|
|
except Exception:
|
|
logerr(errtxt % "announce_move_to()")
|
|
#emit_to_obj.msg(errtxt % "announce_move_to()")
|
|
#logger.log_trace()
|
|
return False
|
|
|
|
# Perform eventual extra commands on the receiving location
|
|
# (the object has already arrived at this point)
|
|
try:
|
|
destination.at_object_receive(self, source_location)
|
|
except Exception:
|
|
logerr(errtxt % "at_object_receive()")
|
|
#emit_to_obj.msg(errtxt % "at_object_receive()")
|
|
#logger.log_trace()
|
|
return False
|
|
|
|
# Execute eventual extra commands on this object after moving it
|
|
# (usually calling 'look')
|
|
try:
|
|
self.at_after_move(source_location)
|
|
except Exception:
|
|
logerr(errtxt % "at_after_move")
|
|
#emit_to_obj.msg(errtxt % "at_after_move()")
|
|
#logger.log_trace()
|
|
return False
|
|
return True
|
|
|
|
#
|
|
# Object Swap, Delete and Cleanup methods
|
|
#
|
|
|
|
def clear_exits(self):
|
|
"""
|
|
Destroys all of the exits and any exits pointing to this
|
|
object as a destination.
|
|
"""
|
|
for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]:
|
|
out_exit.delete()
|
|
for in_exit in ObjectDB.objects.filter(db_destination=self):
|
|
in_exit.delete()
|
|
|
|
def clear_contents(self):
|
|
"""
|
|
Moves all objects (players/things) to their home
|
|
location or to default home.
|
|
"""
|
|
# Gather up everything that thinks this is its location.
|
|
objs = ObjectDB.objects.filter(db_location=self)
|
|
default_home_id = int(settings.CHARACTER_DEFAULT_HOME)
|
|
try:
|
|
default_home = ObjectDB.objects.get(id=default_home_id)
|
|
if default_home.dbid == self.dbid:
|
|
# we are deleting default home!
|
|
default_home = None
|
|
except Exception:
|
|
string = _("Could not find default home '(#%d)'.")
|
|
logger.log_errmsg(string % default_home_id)
|
|
default_home = None
|
|
|
|
for obj in objs:
|
|
home = obj.home
|
|
# Obviously, we can't send it back to here.
|
|
if not home or (home and home.dbid == self.dbid):
|
|
obj.home = default_home
|
|
|
|
# If for some reason it's still None...
|
|
if not obj.home:
|
|
string = "Missing default home, '%s(#%d)' "
|
|
string += "now has a null location."
|
|
obj.location = None
|
|
obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin."))
|
|
logger.log_errmsg(string % (obj.name, obj.dbid))
|
|
return
|
|
|
|
if obj.has_player:
|
|
if home:
|
|
string = "Your current location has ceased to exist,"
|
|
string += " moving you to %s(#%d)."
|
|
obj.msg(_(string) % (home.name, home.dbid))
|
|
else:
|
|
# Famous last words: The player should never see this.
|
|
string = "This place should not exist ... contact an admin."
|
|
obj.msg(_(string))
|
|
obj.move_to(home)
|
|
|
|
def copy(self, new_key=None):
|
|
"""
|
|
Makes an identical copy of this object. If you want to customize the copy by
|
|
changing some settings, use ObjectDB.object.copy_object() directly.
|
|
|
|
new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named
|
|
<old_key>_copy by default.
|
|
Returns: Object (copy of this one)
|
|
"""
|
|
def find_clone_key():
|
|
"""
|
|
Append 01, 02 etc to obj.key. Checks next higher number in the
|
|
same location, then adds the next number available
|
|
|
|
returns the new clone name on the form keyXX
|
|
"""
|
|
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()):
|
|
num += 1
|
|
return "%s%02i" % (key, num)
|
|
new_key = new_key or find_clone_key(self)
|
|
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 to move all contained
|
|
objects to their respective home locations, as well as clean
|
|
up all exits to/from the object.
|
|
"""
|
|
if self.delete_iter > 0:
|
|
# make sure to only call delete once on this object
|
|
# (avoid recursive loops)
|
|
return False
|
|
|
|
if not self.at_object_delete():
|
|
# this is an extra pre-check
|
|
# run before deletion mechanism
|
|
# is kicked into gear.
|
|
self.delete_iter == 0
|
|
return False
|
|
|
|
self.delete_iter += 1
|
|
|
|
# See if we need to kick the player off.
|
|
|
|
for session in self.sessions:
|
|
session.msg(_("Your character %s has been destroyed.") % self.name)
|
|
# no need to disconnect, Player just jumps to OOC mode.
|
|
# sever the connection (important!)
|
|
if object.__getattribute__(self, 'player') and self.player:
|
|
self.player.character = None
|
|
self.player = None
|
|
|
|
for script in self.scripts.all():
|
|
script.stop()
|
|
|
|
# if self.player:
|
|
# self.player.user.is_active = False
|
|
# self.player.user.save(
|
|
|
|
# Destroy any exits to and from this room, if any
|
|
self.clear_exits()
|
|
# Clear out any non-exit objects located within the object
|
|
self.clear_contents()
|
|
# clear current location's content cache of this object
|
|
if self.location:
|
|
self.location.contents_update(self, remove=True)
|
|
# Perform the deletion of the object
|
|
super(ObjectDB, self).delete()
|
|
return True
|