mirror of
https://github.com/evennia/evennia.git
synced 2026-03-18 13:56:30 +01:00
967 lines
39 KiB
Python
967 lines
39 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.typeclasses.models import TypedObject, TagHandler, NickHandler, AliasHandler
|
|
from src.server.caches import get_field_cache, set_field_cache, del_field_cache
|
|
from src.server.caches import get_prop_cache, set_prop_cache
|
|
|
|
from src.typeclasses.typeclass import TypeClass
|
|
from src.objects.manager import ObjectManager
|
|
from src.players.models import PlayerDB
|
|
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__ = ("ObjectDB", )
|
|
|
|
_ScriptDB = None
|
|
_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")
|
|
|
|
#------------------------------------------------------------
|
|
#
|
|
# 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 (always together with sessid)
|
|
sessid - optional connection session id (always together with 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) should normally be set
|
|
# using their corresponding wrapper properties, named same as the field, but without
|
|
# the db_* prefix (e.g. the db_key field is set with self.key instead). The wrappers
|
|
# will automatically save and cache the data more efficiently.
|
|
|
|
# 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 session id associated with this player, if any
|
|
db_sessid = models.IntegerField(null=True, verbose_name="session id",
|
|
help_text="unique session id 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,
|
|
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()
|
|
|
|
# caches for quick lookups of typeclass loading.
|
|
_typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
|
|
_default_typeclass_path = settings.BASE_OBJECT_TYPECLASS or "src.objects.objects.Object"
|
|
|
|
# Add the object-specific handlers
|
|
def __init__(self, *args, **kwargs):
|
|
"Parent must be initialized first."
|
|
TypedObject.__init__(self, *args, **kwargs)
|
|
# handlers
|
|
_SA(self, "cmdset", CmdSetHandler(self))
|
|
_GA(self, "cmdset").update(init_mode=True)
|
|
_SA(self, "scripts", ScriptHandler(self))
|
|
_SA(self, "tags", TagHandler(self, category_prefix="object_"))
|
|
_SA(self, "aliases", AliasHandler(self, category_prefix="object_"))
|
|
_SA(self, "nicks", NickHandler(self, category_prefix="object_"))
|
|
|
|
# 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).
|
|
|
|
# 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.
|
|
"""
|
|
player = get_field_cache(self, "player")
|
|
if player:
|
|
try:
|
|
return player.typeclass
|
|
except Exception,e:
|
|
print "player_get:", e
|
|
return player
|
|
|
|
#@player.setter
|
|
def __player_set(self, player):
|
|
"Setter. Allows for self.player = value"
|
|
if inherits_from(player, TypeClass):
|
|
player = player.dbobj
|
|
set_field_cache(self, "player", player)
|
|
# we must set this here or superusers won't be able to
|
|
# bypass lockchecks unless they start the game connected
|
|
# to the character in question.
|
|
self.locks.cache_lock_bypass(self)
|
|
|
|
#@player.deleter
|
|
def __player_del(self):
|
|
"Deleter. Allows for del self.player"
|
|
del_field_cache(self, "player")
|
|
player = property(__player_get, __player_set, __player_del)
|
|
|
|
# sessid property (wraps db_sessid)
|
|
#@property
|
|
def __sessid_get(self):
|
|
"""
|
|
Getter. Allows for value = self.sessid. Since sessid
|
|
is directly related to self.player, we cannot have
|
|
a sessid without a player being connected (but the
|
|
opposite could be true).
|
|
"""
|
|
if not get_field_cache(self, "sessid"):
|
|
del_field_cache(self, "sessid")
|
|
return get_field_cache(self, "sessid")
|
|
#@sessid.setter
|
|
def __sessid_set(self, sessid):
|
|
"Setter. Allows for self.player = value"
|
|
set_field_cache(self, "sessid", sessid)
|
|
#@sessid.deleter
|
|
def __sessid_del(self):
|
|
"Deleter. Allows for del self.player"
|
|
del_field_cache(self, "sessid")
|
|
sessid = property(__sessid_get, __sessid_set, __sessid_del)
|
|
|
|
def _db_location_handler(self, loc, old_value=None):
|
|
"This handles changes to the db_location field."
|
|
#print "db_location_handler:", loc, old_value
|
|
try:
|
|
old_loc = old_value
|
|
# new_value can be dbref, typeclass or dbmodel
|
|
if ObjectDB.objects.dbref(loc, reqhash=False):
|
|
loc = ObjectDB.objects.dbref_search(loc)
|
|
if loc and type(loc) != ObjectDB:
|
|
# this should not fail if new_value is valid.
|
|
loc = _GA(loc, "dbobj")
|
|
|
|
# recursive location check
|
|
def is_loc_loop(loc, depth=0):
|
|
"Recursively traverse the target location to make sure we are not in it."
|
|
if depth > 10: return
|
|
elif loc == self: raise RuntimeError
|
|
elif loc == None: raise RuntimeWarning # just to quickly get out
|
|
return is_loc_loop(_GA(loc, "db_location"), depth+1)
|
|
# check so we don't create a location loop - if so, RuntimeError will be raised.
|
|
try: is_loc_loop(loc)
|
|
except RuntimeWarning: pass
|
|
|
|
#print "db_location_handler2:", _GA(loc, "db_key") if loc else loc, type(loc)
|
|
# update the contents of each location
|
|
if old_loc:
|
|
_GA(_GA(old_loc, "dbobj"), "contents_update")(self, remove=True)
|
|
if loc:
|
|
_GA(loc, "contents_update")(self)
|
|
return loc
|
|
except RuntimeError:
|
|
string = "Cannot set location, "
|
|
string += "%s.location = %s would create a location-loop." % (self.key, loc)
|
|
_GA(self, "msg")(_(string))
|
|
logger.log_trace(string)
|
|
raise RuntimeError(string)
|
|
except Exception, e:
|
|
string = "Cannot set location (%s): " % str(e)
|
|
string += "%s is not a valid location." % loc
|
|
_GA(self, "msg")(_(string))
|
|
logger.log_trace(string)
|
|
raise Exception(string)
|
|
|
|
## location property (wraps db_location)
|
|
##@property
|
|
#def __location_get(self):
|
|
# "Getter. Allows for value = self.location."
|
|
# loc = get_field_cache(self, "location")
|
|
# if loc:
|
|
# return _GA(loc, "typeclass")
|
|
# return None
|
|
##@location.setter
|
|
#def __location_set(self, location):
|
|
# "Setter. Allows for self.location = location"
|
|
# try:
|
|
# old_loc = _GA(self, "location")
|
|
# if ObjectDB.objects.dbref(location):
|
|
# # dbref search
|
|
# loc = ObjectDB.objects.dbref_search(location)
|
|
# loc = loc and _GA(loc, "dbobj")
|
|
# elif location and type(location) != ObjectDB:
|
|
# loc = _GA(location, "dbobj")
|
|
# else:
|
|
# loc = location
|
|
|
|
# # recursive location check
|
|
# def is_loc_loop(loc, depth=0):
|
|
# "Recursively traverse the target location to make sure we are not in it."
|
|
# if depth > 10: return
|
|
# elif loc == self: raise RuntimeError
|
|
# elif loc == None: raise RuntimeWarning # just to quickly get out
|
|
# return is_loc_loop(_GA(loc, "db_location"), depth+1)
|
|
# # check so we don't create a location loop - if so, RuntimeError will be raised.
|
|
# try: is_loc_loop(loc)
|
|
# except RuntimeWarning: pass
|
|
|
|
# # set the location
|
|
# set_field_cache(self, "location", loc)
|
|
# # update the contents of each location
|
|
# if old_loc:
|
|
# _GA(_GA(old_loc, "dbobj"), "contents_update")()
|
|
# if loc:
|
|
# _GA(loc, "contents_update")()
|
|
# except RuntimeError:
|
|
# string = "Cannot set location, "
|
|
# string += "%s.location = %s would create a location-loop." % (self.key, loc)
|
|
# _GA(self, "msg")(_(string))
|
|
# logger.log_trace(string)
|
|
# raise RuntimeError(string)
|
|
# except Exception, e:
|
|
# string = "Cannot set location (%s): " % str(e)
|
|
# string += "%s is not a valid location." % location
|
|
# _GA(self, "msg")(_(string))
|
|
# logger.log_trace(string)
|
|
# raise Exception(string)
|
|
##@location.deleter
|
|
#def __location_del(self):
|
|
# "Deleter. Allows for del self.location"
|
|
# _GA(self, "location").contents_update()
|
|
# _SA(self, "db_location", None)
|
|
# _GA(self, "save")()
|
|
# del_field_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_field_cache(self, "home")
|
|
if home:
|
|
return _GA(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 = _GA(hom, "dbobj")
|
|
else:
|
|
hom = _GA(home, "dbobj")
|
|
else:
|
|
hom = _GA(home, "dbobj")
|
|
set_field_cache(self, "home", hom)
|
|
except Exception:
|
|
string = "Cannot set home: "
|
|
string += "%s is not a valid home."
|
|
_GA(self, "msg")(_(string) % home)
|
|
logger.log_trace(string)
|
|
#raise
|
|
#@home.deleter
|
|
def __home_del(self):
|
|
"Deleter. Allows for del self.home."
|
|
_SA(self, "db_home", None)
|
|
_GA(self, "save")()
|
|
del_field_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_field_cache(self, "destination")
|
|
if dest:
|
|
return _GA(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 _GA(self, "_hasattr")(dest,'dbobj'):
|
|
dest = _GA(dest, "dbobj")
|
|
else:
|
|
dest = _GA(destination, "dbobj")
|
|
else:
|
|
dest = destination.dbobj
|
|
set_field_cache(self, "destination", dest)
|
|
except Exception:
|
|
string = "Cannot set destination: "
|
|
string += "%s is not a valid destination." % destination
|
|
_GA(self, "msg")(string)
|
|
logger.log_trace(string)
|
|
raise
|
|
#@destination.deleter
|
|
def __destination_del(self):
|
|
"Deleter. Allows for del self.destination"
|
|
_SA(self, "db_destination", None)
|
|
_GA(self, "save")()
|
|
del_field_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 _GA(self, "db_cmdset_storage"):
|
|
return [path.strip() for path in _GA(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))
|
|
_SA(self, "db_cmdset_storage", value)
|
|
_GA(self, "save")()
|
|
#@cmdset_storage.deleter
|
|
def __cmdset_storage_del(self):
|
|
"Deleter. Allows for del self.name"
|
|
_SA(self, "db_cmdset_storage", "")
|
|
_GA(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
|
|
#
|
|
|
|
|
|
#@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 _GA(self, "db_player"):
|
|
return _GA(_GA(self, "db_player"), "get_all_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(_GA(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 (_GA(self, "db_player") and _GA(_GA(self, "db_player"), "is_superuser")
|
|
and not _GA(_GA(self, "db_player"), "get_attribute")("_quell"))
|
|
is_superuser = property(__is_superuser_get)
|
|
|
|
# contents
|
|
|
|
#@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
|
|
"""
|
|
cont = get_prop_cache(self, "_contents")
|
|
exclude = make_iter(exclude)
|
|
if cont == None:
|
|
cont = _GA(self, "contents_update")()
|
|
return [obj for obj in cont.values() if obj not in exclude]
|
|
contents = property(contents_get)
|
|
|
|
def contents_update(self, obj=None, remove=False):
|
|
"""
|
|
Updates the contents property of the object
|
|
|
|
add - object to add to content list
|
|
remove object to remove from content list
|
|
"""
|
|
cont = get_prop_cache(self, "_contents")
|
|
if not cont:
|
|
cont = {}
|
|
if obj:
|
|
if remove:
|
|
cont.pop(self.dbid, None)
|
|
else:
|
|
cont[self.dbid] = obj
|
|
else:
|
|
cont = dict((o.dbid, o) for o in ObjectDB.objects.get_contents(self))
|
|
set_prop_cache(self, "_contents", cont)
|
|
return cont
|
|
|
|
#@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 _GA(self, "contents")
|
|
if exi.destination]
|
|
exits = property(__exits_get)
|
|
|
|
|
|
#
|
|
# Main Search method
|
|
#
|
|
|
|
def search(self, searchdata,
|
|
global_search=False,
|
|
use_nicks=False,
|
|
typeclass=None,
|
|
location=None,
|
|
attribute_name=None,
|
|
quiet=False,
|
|
exact=False):
|
|
"""
|
|
Returns the typeclass of an Object matching a search string/condition
|
|
|
|
Perform a standard object search in the database, handling
|
|
multiple results and lack thereof gracefully. By default, only
|
|
objects in self's current location or inventory is searched.
|
|
Note: to find Players, use eg. ev.player_search.
|
|
|
|
Inputs:
|
|
|
|
searchdata (str or obj): Primary search criterion. Will be matched against object.key (with object.aliases second)
|
|
unless the keyword attribute_name specifies otherwise. Special strings:
|
|
#<num> - search by unique dbref. This is always a global search.
|
|
me,self - self-reference to this object
|
|
<num>-<string> - can be used to differentiate between multiple same-named matches
|
|
global_search (bool): Search all objects globally. This is overruled by "location" keyword.
|
|
use_nicks (bool): Use nickname-replace (nicktype "object") on the search string
|
|
typeclass (str or Typeclass, or list of either): Limit search only to Objects with this typeclass. May
|
|
be a list of typeclasses for a broader search.
|
|
location (Object): Specify a location to search, if different from the self's given location
|
|
plus its contents. This can also be a list of locations.
|
|
attribute_name (str): Define which property to search. If set, no key+alias search will be performed. This can be used to
|
|
search database fields (db_ will be automatically appended), and if that fails, it will try to
|
|
return objects having Attributes with this name and value equal to searchdata. A special
|
|
use is to search for "key" here if you want to do a key-search without including aliases.
|
|
quiet (bool) - don't display default error messages - return multiple matches as a list and
|
|
no matches as None. If not set (default), will echo error messages and return None.
|
|
exact (bool) - if unset (default) - prefers to match to beginning of string rather than not matching
|
|
at all. If set, requires exact mathing of entire string.
|
|
|
|
Returns:
|
|
|
|
quiet=False (default):
|
|
no match or multimatch:
|
|
auto-echoes errors to self.msg, then returns None
|
|
(results are handled by modules set by settings.SEARCH_AT_RESULT
|
|
and settings.SEARCH_AT_MULTIMATCH_INPUT)
|
|
match:
|
|
a unique object match
|
|
quiet=True:
|
|
no match or multimatch:
|
|
returns None or list of multi-matches
|
|
match:
|
|
a unique object match
|
|
|
|
"""
|
|
is_string = isinstance(searchdata, basestring)
|
|
|
|
# handle some common self-references:
|
|
if searchdata == _HERE:
|
|
return self.location
|
|
if searchdata in (_ME, _SELF):
|
|
return self.typeclass
|
|
|
|
if use_nicks:
|
|
nicktype = "object"
|
|
# get all valid nicks to search
|
|
nicks = self.nicks.get(category="object_nick_%s" % nicktype)
|
|
if self.has_player:
|
|
pnicks = self.nicks.get(category="player_nick_%s" % nicktype)
|
|
nicks = nicks + pnicks
|
|
for nick in nicks:
|
|
if searchdata == nick.db_key:
|
|
searchdata = nick.db_data
|
|
break
|
|
|
|
candidates=None
|
|
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
|
|
elif location:
|
|
# location(s) were given
|
|
candidates = []
|
|
for obj in make_iter(location):
|
|
candidates.extend([o.dbobj for o in obj.contents])
|
|
else:
|
|
# local search. Candidates are self.contents, self.location and self.location.contents
|
|
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(searchdata,
|
|
attribute_name=attribute_name,
|
|
typeclass=typeclass,
|
|
candidates=candidates,
|
|
exact=exact)
|
|
if quiet:
|
|
return results
|
|
return _AT_SEARCH_RESULT(self, searchdata, results, global_search)
|
|
|
|
def search_player(self, searchdata, quiet=False):
|
|
"""
|
|
Simple wrapper of the player search also handling me, self
|
|
"""
|
|
if searchdata in (_ME, _SELF) and _GA(self, "db_player"):
|
|
return _GA(self, "db_player")
|
|
results = PlayerDB.objects.player_search(searchdata)
|
|
if quiet:
|
|
return results
|
|
return _AT_SEARCH_RESULT(self, searchdata, results, True)
|
|
|
|
#
|
|
# Execution/action methods
|
|
#
|
|
|
|
def execute_cmd(self, raw_string, sessid=None):
|
|
"""
|
|
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]]
|
|
# fetch the nick data efficiently
|
|
nicks = self.db_liteattributes.filter(db_category__in=("object_nick_inputline", "object_nick_channel")).prefetch_related("db_key","db_data")
|
|
if self.has_player:
|
|
pnicks = self.player.db_liteattributes.filter(
|
|
db_category__in=("player_nick_inputline", "player_nick_channel")).prefetch_related("db_key","db_data")
|
|
nicks = list(nicks) + list(pnicks)
|
|
for nick in nicks:
|
|
if nick.db_key in raw_list:
|
|
raw_string = raw_string.replace(nick.db_key, nick.db_data, 1)
|
|
break
|
|
return cmdhandler.cmdhandler(_GA(self, "typeclass"), raw_string, sessid=sessid)
|
|
|
|
def msg(self, msg=None, from_obj=None, data=None, sessid=0):
|
|
"""
|
|
Emits something to a session 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.
|
|
sessid (int): sessid to relay to, if any.
|
|
If set to 0 (default), use either from_obj.sessid (if set) or self.sessid automatically
|
|
If None, echo to all connected sessions
|
|
"""
|
|
if _GA(self, 'player'):
|
|
# note that we must call the player *typeclass'* msg(), otherwise one couldn't overload it.
|
|
if sessid == 0:
|
|
sessid = None
|
|
if from_obj and hasattr(from_obj, "sessid"):
|
|
sessid = from_obj.sessid
|
|
elif hasattr(self, "sessid"):
|
|
sessid = self.sessid
|
|
_GA(_GA(self, 'player'), "typeclass").msg(msg, from_obj=from_obj, data=data, sessid=sessid)
|
|
|
|
def emit_to(self, message, from_obj=None, data=None):
|
|
"Deprecated. Alias for msg"
|
|
logger.log_depmsg("emit_to() is deprecated. Use msg() instead.")
|
|
_GA(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 = _GA(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, to_none=False):
|
|
"""
|
|
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.
|
|
to_none - allow destination to be None. Note that no hooks are run when moving
|
|
to a None location. If you want to run hooks, run them manually.
|
|
|
|
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()
|
|
_GA(self, "msg")(errstring)
|
|
|
|
errtxt = _("Couldn't perform move ('%s'). Contact an admin.")
|
|
if not emit_to_obj:
|
|
emit_to_obj = self
|
|
|
|
if not destination:
|
|
if to_none:
|
|
# immediately move to None. There can be no hooks called since
|
|
# there is no destination to call them with.
|
|
self.location = None
|
|
return True
|
|
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 = _GA(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 _GA(self, "home"):
|
|
source_location = _GA(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:
|
|
_SA(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.lstrip("#"))
|
|
try:
|
|
default_home = ObjectDB.objects.get(id=default_home_id)
|
|
if default_home.dbid == _GA(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 == _GA(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 = _GA(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%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 to move all contained
|
|
objects to their respective home locations, as well as clean
|
|
up all exits to/from the object.
|
|
"""
|
|
global _ScriptDB
|
|
if not _ScriptDB:
|
|
from src.scripts.models import ScriptDB as _ScriptDB
|
|
|
|
if _GA(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.
|
|
_SA(self, "delete_iter", 0)
|
|
return False
|
|
|
|
self.delete_iter += 1
|
|
|
|
# See if we need to kick the player off.
|
|
|
|
for session in _GA(self, "sessions"):
|
|
session.msg(_("Your character %s has been destroyed.") % _GA(self, "key"))
|
|
# no need to disconnect, Player just jumps to OOC mode.
|
|
# sever the connection (important!)
|
|
if _GA(self, 'player'):
|
|
_SA(_GA(self, "player"), "character", None)
|
|
_SA(self, "player", None)
|
|
|
|
for script in _ScriptDB.objects.get_all_scripts_on_obj(self):
|
|
script.stop()
|
|
#for script in _GA(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
|
|
_GA(self, "clear_exits")()
|
|
# Clear out any non-exit objects located within the object
|
|
_GA(self, "clear_contents")()
|
|
old_loc = _GA(self, "location")
|
|
# Perform the deletion of the object
|
|
super(ObjectDB, self).delete()
|
|
# clear object's old location's content cache of this object
|
|
if old_loc:
|
|
old_loc.contents_update()
|
|
return True
|