Made a new version of create_object function, and made Objects creatable using o=Object().

This commit is contained in:
Griatch 2014-12-23 21:33:03 +01:00
parent 969b947ba0
commit 24764743ff
8 changed files with 858 additions and 1173 deletions

View file

@ -7,6 +7,5 @@ Also, the initiated object manager is available as src.objects.manager.
""" """
#from src.objects.objects import * #from src.objects.objects import *
from src.objects.models import ObjectDB #from src.objects.models import ObjectDB
#manager = ObjectDB.objects
manager = ObjectDB.objects

View file

@ -14,81 +14,14 @@ the database object. Like everything else, they can be accessed
transparently through the decorating TypeClass. transparently through the decorating TypeClass.
""" """
import traceback
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from src.typeclasses.models import TypedObject, NickHandler from src.typeclasses.models import TypedObject
from src.objects.manager import ObjectDBManager from src.objects.manager import ObjectDBManager
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 import logger
from src.utils.utils import (make_iter, to_str, to_unicode, lazy_property, from src.utils.utils import (make_iter, dbref)
variable_from_module, dbref)
MULTISESSION_MODE = settings.MULTISESSION_MODE
from django.utils.translation import ugettext as _
#__all__ = ("ObjectDB", )
_ScriptDB = None
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
_SESSIONS = None
_GA = object.__getattribute__
_SA = object.__setattr__
_DA = object.__delattr__
# the sessid_max is based on the length of the db_sessid csv field (excluding commas)
_SESSID_MAX = 16 if MULTISESSION_MODE in (1, 3) else 1
class SessidHandler(object):
"""
Handles the get/setting of the sessid
comma-separated integer field
"""
def __init__(self, obj):
self.obj = obj
self._cache = set()
self._recache()
def _recache(self):
self._cache = list(set(int(val) for val in (_GA(self.obj, "db_sessid") or "").split(",") if val))
def get(self):
"Returns a list of one or more session ids"
return self._cache
def add(self, sessid):
"Add sessid to handler"
_cache = self._cache
if sessid not in _cache:
if len(_cache) >= _SESSID_MAX:
return
_cache.append(sessid)
_SA(self.obj, "db_sessid", ",".join(str(val) for val in _cache))
_GA(self.obj, "save")(update_fields=["db_sessid"])
def remove(self, sessid):
"Remove sessid from handler"
_cache = self._cache
if sessid in _cache:
_cache.remove(sessid)
_SA(self.obj, "db_sessid", ",".join(str(val) for val in _cache))
_GA(self.obj, "save")(update_fields=["db_sessid"])
def clear(self):
"Clear sessids"
self._cache = []
_SA(self.obj, "db_sessid", None)
_GA(self.obj, "save")(update_fields=["db_sessid"])
def count(self):
"Return amount of sessions connected"
return len(self._cache)
#------------------------------------------------------------ #------------------------------------------------------------
@ -173,27 +106,7 @@ class ObjectDB(TypedObject):
# Database manager # Database manager
objects = ObjectDBManager() objects = ObjectDBManager()
# caches for quick lookups of typeclass loading. # field-related field-related properties
_typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
_default_typeclass_path = settings.BASE_OBJECT_TYPECLASS or "src.objects.objects.Object"
# lazy-load handlers
@lazy_property
def cmdset(self):
return CmdSetHandler(self, True)
@lazy_property
def scripts(self):
return ScriptHandler(self)
@lazy_property
def nicks(self):
return NickHandler(self)
@lazy_property
def sessid(self):
return SessidHandler(self)
def _at_db_player_postsave(self): def _at_db_player_postsave(self):
""" """
This hook is called automatically after the player field is saved. This hook is called automatically after the player field is saved.
@ -201,30 +114,21 @@ class ObjectDB(TypedObject):
# we need to re-cache this for superusers to bypass. # we need to re-cache this for superusers to bypass.
self.locks.cache_lock_bypass(self) self.locks.cache_lock_bypass(self)
# cmdset_storage property. We use a custom wrapper to manage this. This also # cmdset_storage property handling
# seems very sensitive to caching, so leaving it be for now. /Griatch
#@property
def __cmdset_storage_get(self): def __cmdset_storage_get(self):
""" "getter"
Getter. Allows for value = self.name. storage = self.db_cmdset_storage
Returns a list of cmdset_storage.
"""
storage = _GA(self, "db_cmdset_storage")
# we need to check so storage is not None
return [path.strip() for path in storage.split(',')] if storage else [] return [path.strip() for path in storage.split(',')] if storage else []
#@cmdset_storage.setter
def __cmdset_storage_set(self, value): def __cmdset_storage_set(self, value):
""" "setter"
Setter. Allows for self.name = value. self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value))
Stores as a comma-separated string. self.save(update_fields=["db_cmdset_storage"])
"""
_SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value)))
_GA(self, "save")()
#@cmdset_storage.deleter
def __cmdset_storage_del(self): def __cmdset_storage_del(self):
"Deleter. Allows for del self.name" "deleter"
_SA(self, "db_cmdset_storage", None) self.db_cmdset_storage = None
_GA(self, "save")() self.save(update_fields=["db_cmdset_storage"])
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del) cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del)
# location getsetter # location getsetter
@ -280,567 +184,3 @@ class ObjectDB(TypedObject):
verbose_name = "Object" verbose_name = "Object"
verbose_name_plural = "Objects" 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"), "attributes").get("_quell"))
is_superuser = property(__is_superuser_get)
# contents
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 exclude:
return ObjectDB.objects.get_contents(self, excludeobj=make_iter(exclude))
return ObjectDB.objects.get_contents(self)
contents = property(contents_get)
#@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=True, # should this default to off?
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 - this tells the
search method that the user wants to handle all errors
themselves. It also changes the return value type, see
below.
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 settings.SEARCH_AT_RESULT
and settings.SEARCH_AT_MULTIMATCH_INPUT)
match:
a unique object match
quiet=True:
returns a list of 0, 1 or more matches
"""
is_string = isinstance(searchdata, basestring)
if use_nicks:
# do nick-replacement on search
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
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(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:
# normally we are included in location.contents
candidates.append(self)
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 shortcut wrapper to search for players, not characters.
searchdata - search criterion - the key or dbref of the player
to search for. If this is "here" or "me", search
for the player connected to this object.
quiet - return the results as a list rather than echo eventual
standard error messages.
Returns:
quiet=False (default):
no match or multimatch:
auto-echoes errors to self.msg, then returns None
(results are handled by settings.SEARCH_AT_RESULT
and settings.SEARCH_AT_MULTIMATCH_INPUT)
match:
a unique player match
quiet=True:
no match or multimatch:
returns None or list of multi-matches
match:
a unique object match
"""
results = PlayerDB.objects.player_search(searchdata)
if quiet:
return results
return _AT_SEARCH_RESULT(self, searchdata, results, global_search=True)
#
# Execution/action methods
#
def execute_cmd(self, raw_string, sessid=None, **kwargs):
"""
Do something as this object. This method is a copy of the execute_
cmd method on the session. This is never called normally, it's only
used when wanting specifically to let an object be the caller of a
command. It makes use of nicks of eventual connected players as well.
Argument:
raw_string (string) - raw command input
sessid (int) - optional session id to return results to
**kwargs - other keyword arguments will be added to the found command
object instace as variables before it executes. This is
unused by default Evennia but may be used to set flags and
change operating paramaters for commands at run-time.
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_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=True)
return cmdhandler.cmdhandler(self, raw_string, callertype="object", sessid=sessid, **kwargs)
def msg(self, text=None, from_obj=None, sessid=0, **kwargs):
"""
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
When this message is called, from_obj.at_msg_send and self.at_msg_receive are called.
"""
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
text = to_str(text, force_string=True) if text else ""
if "data" in kwargs:
# deprecation warning
logger.log_depmsg("ObjectDB.msg(): 'data'-dict keyword is deprecated. Use **kwargs instead.")
data = kwargs.pop("data")
if isinstance(data, dict):
kwargs.update(data)
if from_obj:
# call hook
try:
from_obj.at_msg_send(text=text, to_obj=self, **kwargs)
except Exception:
logger.log_trace()
try:
if not self.at_msg_receive(text=text, **kwargs):
# if at_msg_receive returns false, we abort message to this object
return
except Exception:
logger.log_trace()
sessions = _SESSIONS.session_from_sessid([sessid] if sessid else make_iter(_GA(self, "sessid").get()))
for session in sessions:
session.msg(text=text, **kwargs)
def msg_contents(self, message, exclude=None, from_obj=None, **kwargs):
"""
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]
for obj in contents:
obj.msg(message, from_obj=from_obj, **kwargs)
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 (and make sure they can manage None
locations).
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.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:
#print "move_to location:", destination
_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.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
home = default_home
# If for some reason it's still None...
if not 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")()
_GA(self, "attributes").clear()
_GA(self, "nicks").clear()
_GA(self, "aliases").clear()
# Perform the deletion of the object
super(ObjectDB, self).delete()
return True

View file

@ -15,18 +15,76 @@ That an object is controlled by a player/user is just defined by its
they control by simply linking to a new object's user property. they control by simply linking to a new object's user property.
""" """
import traceback
from django.conf import settings from django.conf import settings
from src.typeclasses.models import TypeclassBase
from src.typeclasses.models import TypeclassBase, NickHandler
from src.objects.manager import ObjectManager from src.objects.manager import ObjectManager
from src.objects.models import ObjectDB from src.objects.models import ObjectDB
from src.scripts.scripthandler import ScriptHandler
from src.commands import cmdset, command from src.commands import cmdset, command
from src.utils.logger import log_depmsg from src.commands.cmdsethandler import CmdSetHandler
from src.commands import cmdhandler
from src.utils.logger import log_depmsg, log_trace, log_errmsg
from src.utils.utils import (variable_from_module, lazy_property,
make_iter, to_str, to_unicode)
__all__ = ("Object", "Character", "Room", "Exit") MULTISESSION_MODE = settings.MULTISESSION_MODE
_ScriptDB = None
_SESSIONS = None
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
# the sessid_max is based on the length of the db_sessid csv field (excluding commas)
_SESSID_MAX = 16 if MULTISESSION_MODE in (1, 3) else 1
from django.utils.translation import ugettext as _
class SessidHandler(object):
"""
Handles the get/setting of the sessid
comma-separated integer field
"""
def __init__(self, obj):
self.obj = obj
self._cache = set()
self._recache()
def _recache(self):
self._cache = list(set(int(val) for val in (self.obj.db_sessid or "").split(",") if val))
def get(self):
"Returns a list of one or more session ids"
return self._cache
def add(self, sessid):
"Add sessid to handler"
_cache = self._cache
if sessid not in _cache:
if len(_cache) >= _SESSID_MAX:
return
_cache.append(sessid)
self.obj.db_sessid = ",".join(str(val) for val in _cache)
self.obj.save(update_fields=["db_sessid"])
def remove(self, sessid):
"Remove sessid from handler"
_cache = self._cache
if sessid in _cache:
_cache.remove(sessid)
self.obj.db_sessid = ",".join(str(val) for val in _cache)
self.obj.save(update_fields=["db_sessid"])
def clear(self):
"Clear sessids"
self._cache = []
self.obj.db_sessid = None
self.obj.save(update_fields=["db_sessid"])
def count(self):
"Return amount of sessions connected"
return len(self._cache)
_GA = object.__getattribute__
_SA = object.__setattr__
_DA = object.__delattr__
# #
@ -34,16 +92,6 @@ _DA = object.__delattr__
# #
class DefaultObject(ObjectDB): class DefaultObject(ObjectDB):
"""
This is the base class for all in-game objects. Inherit from this
to create different types of objects in the game.
"""
__metaclass__ = TypeclassBase
objects = ObjectManager()
# __init__ is only defined here in order to present docstring to API.
def __init__(self, *args, **kwargs):
""" """
This is the root typeclass object, representing all entities This is the root typeclass object, representing all entities
that have an actual presence in-game. Objects generally have a that have an actual presence in-game. Objects generally have a
@ -194,13 +242,78 @@ class DefaultObject(ObjectDB):
this object speaks this object speaks
""" """
super(DefaultObject, self).__init__(*args, **kwargs) # typeclass setup
__metaclass__ = TypeclassBase
objects = ObjectManager()
# on-object properties
@lazy_property
def cmdset(self):
return CmdSetHandler(self, True)
@lazy_property
def scripts(self):
return ScriptHandler(self)
@lazy_property
def nicks(self):
return NickHandler(self)
@lazy_property
def sessid(self):
return SessidHandler(self)
@property
def sessions(self):
"""
Retrieve sessions connected to this object.
"""
# if the player is not connected, this will simply be an empty list.
if self.db_player:
return self.db_player.get_all_sessions()
return []
@property
def has_player(self):
"""
Convenience function for checking if an active player is
currently connected to this object
"""
return any(self.sessions)
@property
def is_superuser(self):
"Check if user has a player, and if so, if it is a superuser."
return self.db_player and self.db_player.is_superuser \
and not self.db_player.attributes.get("_quell")
@property
def contents(self):
"""
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
"""
return ObjectDB.objects.get_contents(self)
@property
def exits(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]
# main methods
## methods inherited from the database object (overload them here) ## methods inherited from the database object (overload them here)
def search(self, searchdata, def search(self, searchdata,
global_search=False, global_search=False,
use_nicks=True, use_nicks=True, # should this default to off?
typeclass=None, typeclass=None,
location=None, location=None,
attribute_name=None, attribute_name=None,
@ -216,37 +329,42 @@ class DefaultObject(ObjectDB):
Inputs: Inputs:
searchdata (str): Primary search criterion. Will be matched against searchdata (str or obj): Primary search criterion. Will be matched
object.key (with object.aliases second) against object.key (with object.aliases second) unless
unless the keyword attribute_name specifies otherwise. the keyword attribute_name specifies otherwise.
Special strings: Special strings:
#<num> - search by unique dbref. This is always a #<num> - search by unique dbref. This is always
global search. a global search.
me,self - self-reference to this object me,self - self-reference to this object
here - current location <num>-<string> - can be used to differentiate
<num>-<string> - can be used to differentiate between between multiple same-named matches
multiple same-named matches
global_search (bool): Search all objects globally. This is overruled global_search (bool): Search all objects globally. This is overruled
by "location" keyword. by "location" keyword.
use_nicks (bool): Use nickname-replace (nicktype "object") on the use_nicks (bool): Use nickname-replace (nicktype "object") on the
search string search string
typeclass (str or Typeclass): Limit search only to Objects with this typeclass (str or Typeclass, or list of either): Limit search only
typeclass. May be a list of typeclasses for a to Objects with this typeclass. May be a list of typeclasses
broader search. for a broader search.
location (Object): Specify a location to search, if different from the location (Object): Specify a location to search, if different from the
self's given location self's given location plus its contents. This can also
plus its contents. This can also be a list of locations. be a list of locations.
attribute_name (str): Use this named Attribute to match ostring against, attribute_name (str): Define which property to search. If set, no
instead of object.key. key+alias search will be performed. This can be used to
quiet (bool) - don't display default error messages - return multiple search database fields (db_ will be automatically
matches as a list and no matches as None. If not appended), and if that fails, it will try to return
set (default), will echo error messages and return None. 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 - this tells the
search method that the user wants to handle all errors
themselves. It also changes the return value type, see
below.
exact (bool) - if unset (default) - prefers to match to beginning of exact (bool) - if unset (default) - prefers to match to beginning of
string rather than not matching at all. If set, string rather than not matching at all. If set, requires
requires exact mathing of entire string. exact mathing of entire string.
Returns: Returns:
quiet=False (default): quiet=False (default):
no match or multimatch: no match or multimatch:
auto-echoes errors to self.msg, then returns None auto-echoes errors to self.msg, then returns None
@ -255,27 +373,52 @@ class DefaultObject(ObjectDB):
match: match:
a unique object match a unique object match
quiet=True: quiet=True:
no match or multimatch: returns a list of 0, 1 or more matches
returns None or list of multi-matches
match:
a unique object match
""" """
if isinstance(searchdata, basestring): is_string = isinstance(searchdata, basestring)
if is_string:
# searchdata is a string; wrap some common self-references # searchdata is a string; wrap some common self-references
if searchdata.lower() in ("here", ): if searchdata.lower() in ("here", ):
return self.location return self.location
if searchdata.lower() in ("me", "self",): if searchdata.lower() in ("me", "self",):
return self return self
return super(DefaultObject, self).search(searchdata, if use_nicks:
global_search=global_search, # do nick-replacement on search
use_nicks=use_nicks, searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
typeclass=typeclass,
location=location, 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(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:
# normally we are included in location.contents
candidates.append(self)
results = ObjectDB.objects.object_search(searchdata,
attribute_name=attribute_name, attribute_name=attribute_name,
quiet=quiet, typeclass=typeclass,
candidates=candidates,
exact=exact) exact=exact)
if quiet:
return results
return _AT_SEARCH_RESULT(self, searchdata, results, global_search)
def search_player(self, searchdata, quiet=False): def search_player(self, searchdata, quiet=False):
""" """
@ -305,19 +448,23 @@ class DefaultObject(ObjectDB):
# searchdata is a string; wrap some common self-references # searchdata is a string; wrap some common self-references
if searchdata.lower() in ("me", "self",): if searchdata.lower() in ("me", "self",):
return self.player return self.player
return super(DefaultObject, self).search_player(searchdata, quiet=quiet)
results = self.player.__class__.objects.player_search(searchdata)
if quiet:
return results
return _AT_SEARCH_RESULT(self, searchdata, results, global_search=True)
def execute_cmd(self, raw_string, sessid=None, **kwargs): def execute_cmd(self, raw_string, sessid=None, **kwargs):
""" """
Do something as this object. This command transparently Do something as this object. This method is a copy of the execute_
lets its typeclass execute the command. This method is cmd method on the session. This is never called normally, it's only
never called normally, it is only called explicitly in used when wanting specifically to let an object be the caller of a
code. command. It makes use of nicks of eventual connected players as well.
Argument: Argument:
raw_string (string) - raw command input raw_string (string) - raw command input
sessid (int) - id of session executing the command. This sets the sessid (int) - optional session id to return results to
sessid property on the command.
**kwargs - other keyword arguments will be added to the found command **kwargs - other keyword arguments will be added to the found command
object instace as variables before it executes. This is object instace as variables before it executes. This is
unused by default Evennia but may be used to set flags and unused by default Evennia but may be used to set flags and
@ -329,45 +476,89 @@ class DefaultObject(ObjectDB):
addCallback(function). This function will be called with an addCallback(function). This function will be called with an
eventual return value from the command execution. eventual return value from the command execution.
This return is not used at all by Evennia by default, but might be This return is not used at all by Evennia by default, but might
useful for coders intending to implement some sort of nested be useful for coders intending to implement some sort of nested
command structure. command structure.
""" """
return super(DefaultObject, self).execute_cmd(raw_string, sessid=sessid, **kwargs) # nick replacement - we require full-word matching.
def msg(self, text=None, from_obj=None, sessid=None, **kwargs): # do text encoding conversion
raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=True)
return cmdhandler.cmdhandler(self, raw_string, callertype="object", sessid=sessid, **kwargs)
def msg(self, text=None, from_obj=None, sessid=0, **kwargs):
""" """
Emits something to any sessions attached to the object. Emits something to a session attached to the object.
message (str): The message to send message (str): The message to send
from_obj (obj): object that is sending. from_obj (obj): object that is sending.
data (object): an optional data object that may or may not data (object): an optional data object that may or may not
be used by the protocol. be used by the protocol.
sessid: optional session target. If sessid=0, the session will sessid (int): sessid to relay to, if any.
default to self.sessid or from_obj.sessid. If set to 0 (default), use either from_obj.sessid (if set) or self.sessid automatically
If None, echo to all connected sessions
When this message is called, from_obj.at_msg_send and self.at_msg_receive are called.
""" """
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
super(DefaultObject, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) text = to_str(text, force_string=True) if text else ""
def msg_contents(self, text=None, exclude=None, from_obj=None, **kwargs): if "data" in kwargs:
# deprecation warning
log_depmsg("ObjectDB.msg(): 'data'-dict keyword is deprecated. Use **kwargs instead.")
data = kwargs.pop("data")
if isinstance(data, dict):
kwargs.update(data)
if from_obj:
# call hook
try:
from_obj.at_msg_send(text=text, to_obj=self, **kwargs)
except Exception:
log_trace()
try:
if not self.at_msg_receive(text=text, **kwargs):
# if at_msg_receive returns false, we abort message to this object
return
except Exception:
log_trace()
sessions = _SESSIONS.session_from_sessid([sessid] if sessid else make_iter(self.sessid.get()))
for session in sessions:
session.msg(text=text, **kwargs)
def msg_contents(self, message, exclude=None, from_obj=None, **kwargs):
""" """
Emits something to all objects inside an object. Emits something to all objects inside an object.
exclude is a list of objects not to send to. See self.msg() for exclude is a list of objects not to send to. See self.msg() for
more info. more info.
""" """
super(DefaultObject, self).msg_contents(text, exclude=exclude, contents = self.contents
from_obj=from_obj, **kwargs) if exclude:
exclude = make_iter(exclude)
contents = [obj for obj in contents if obj not in exclude]
for obj in contents:
obj.msg(message, from_obj=from_obj, **kwargs)
def move_to(self, destination, quiet=False, def move_to(self, destination, quiet=False,
emit_to_obj=None, use_destination=True, to_none=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 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 exit object (i.e. it has "destination"!=None), the move_to will
happen to this destination and -not- into the exit object itself, happen to this destination and -not- into the exit object itself, unless
unless use_destination=False. Note that no lock checks are done by use_destination=False. Note that no lock checks are done by this
this function, such things are assumed to have been handled before function, such things are assumed to have been handled before calling
calling move_to. move_to.
destination: (Object) Reference to the object to move to. This destination: (Object) Reference to the object to move to. This
can also be an exit object, in which case the destination can also be an exit object, in which case the destination
@ -378,18 +569,172 @@ class DefaultObject(ObjectDB):
property of destinations as the target to move to. property of destinations as the target to move to.
Turning off this keyword allows objects to move Turning off this keyword allows objects to move
"inside" exit objects. "inside" exit objects.
to_none - allow destination to be None. Note that no hooks are run to_none - allow destination to be None. Note that no hooks are run when
when moving to a None location. If you want to run hooks, run moving to a None location. If you want to run hooks,
them manually (and make sure the hooks can handle a None run them manually (and make sure they can manage None
location). locations).
Returns True/False depending on if there were problems with the move. Returns True/False depending on if there were problems with the move.
This method may also return various error messages to the This method may also return various error messages to the
emit_to_obj. emit_to_obj.
""" """
return super(DefaultObject, self).move_to(destination, quiet=quiet, def logerr(string=""):
emit_to_obj=emit_to_obj, trc = traceback.format_exc()
use_destination=use_destination) errstring = "%s%s" % (trc, string)
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:
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 = 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.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:
#print "move_to location:", destination
self.location = destination
except Exception:
emit_to_obj.msg(errtxt % "location change")
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
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.DEFAULT_HOME.lstrip("#"))
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)'.")
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
home = default_home
# If for some reason it's still None...
if not 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."))
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): def copy(self, new_key=None):
""" """
@ -398,113 +743,84 @@ class DefaultObject(ObjectDB):
directly. directly.
new_key (string) - new key/name of copied object. If new_key is not new_key (string) - new key/name of copied object. If new_key is not
specified, the copy will be named specified, the copy will be named <old_key>_copy
<old_key>_copy by default. by default.
Returns: DefaultObject (copy of this one) Returns: Object (copy of this one)
""" """
return super(DefaultObject, self).copy(new_key=new_key) 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%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): def delete(self):
""" """
Deletes this object. Deletes this object.
Before deletion, this method makes sure to move all contained Before deletion, this method makes sure to move all contained
objects to their respective home locations, as well as clean objects to their respective home locations, as well as clean
up all exits to/from the object. up all exits to/from the object.
Returns: boolean True if deletion succeded, False if there
were errors during deletion or deletion otherwise
failed.
""" """
return super(DefaultObject, self).delete() global _ScriptDB
if not _ScriptDB:
from src.scripts.models import ScriptDB as _ScriptDB
# methods inherited from the typeclass system if self.delete_iter > 0:
# make sure to only call delete once on this object
def is_typeclass(self, typeclass, exact=False): # (avoid recursive loops)
"""
Returns true if this object has this type
OR has a typeclass which is an subclass of
the given typeclass.
typeclass - can be a class object or the
python path to such an object to match against.
exact - returns true only if the object's
type is exactly this typeclass, ignoring
parents.
Returns: Boolean
"""
return super(DefaultObject, self).is_typeclass(typeclass, exact=exact)
def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True):
"""
This performs an in-situ swap of the typeclass. This means
that in-game, this object will suddenly be something else.
Player will not be affected. To 'move' a player to a different
object entirely (while retaining this object's type), use
self.player.swap_object().
Note that this might be an error prone operation if the
old/new typeclass was heavily customized - your code
might expect one and not the other, so be careful to
bug test your code if using this feature! Often its easiest
to create a new object and just swap the player over to
that one instead.
Arguments:
new_typeclass (path/classobj) - type to switch to
clean_attributes (bool/list) - will delete all attributes
stored on this object (but not any
of the database fields such as name or
location). You can't get attributes back,
but this is often the safest bet to make
sure nothing in the new typeclass clashes
with the old one. If you supply a list,
only those named attributes will be cleared.
no_default - if this is active, the swapper will not allow for
swapping to a default typeclass in case the given
one fails for some reason. Instead the old one
will be preserved.
Returns:
boolean True/False depending on if the swap worked or not.
"""
return super(DefaultObject, self).swap_typeclass(new_typeclass,
clean_attributes=clean_attributes, no_default=no_default)
def access(self, accessing_obj, access_type='read', default=False, **kwargs):
"""
Determines if another object has permission to access this object in
whatever way.
accessing_obj (Object)- object trying to access this one
access_type (string) - type of access sought
default (bool) - what to return if no lock of access_type was found
**kwargs - passed to at_access hook along with result,accessing_obj and access_type
"""
result = super(DefaultObject, self).access(accessing_obj, access_type=access_type, default=default)
self.at_access(result, accessing_obj, access_type, **kwargs)
return result
# OBS: DEPRECATED!
if result:
self.at_access_success(accessing_obj, access_type)
return True
else:
self.at_access_failure(accessing_obj, access_type)
return False return False
def check_permstring(self, permstring): if not self.at_object_delete():
""" # this is an extra pre-check
This explicitly checks the given string against this object's # run before deletio field-related properties
'permissions' property without involving any locks. # is kicked into gear.
self.delete_iter = 0
return False
permstring (string) - permission string that need to match a self.delete_iter += 1
permission on the object.
(example: 'Builders') # See if we need to kick the player off.
"""
return super(DefaultObject, self).check_permstring(permstring) for session in self.sessions:
session.msg(_("Your character %s has been destroyed.") % self.key)
# no need to disconnect, Player just jumps to OOC mode.
# sever the connection (important!)
if self.player:
self.player.character = None
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
self.clear_exits()
# Clear out any non-exit objects located within the object
self.clear_contents()
self.attributes.clear()
self.nicks.clear()
self.aliases.clear()
# Perform the deletion of the object
super(ObjectDB, self).delete()
return True
# methods inherited from the typeclass system
def __eq__(self, other): def __eq__(self, other):
""" """
@ -514,14 +830,61 @@ class DefaultObject(ObjectDB):
parent doesn't work. parent doesn't work.
""" """
try: try:
return _GA(_GA(self, "dbobj"), "dbid") == _GA(_GA(other, "dbobj"), "dbid") return self.dbid == other.dbid
except AttributeError: except AttributeError:
# compare players instead # compare players instead
try: try:
return _GA(_GA(_GA(self, "dbobj"), "player"), "uid") == _GA(_GA(other, "player"), "uid") return self.player.uid == other.player.uid
except AttributeError: except AttributeError:
return False return False
def at_instance_creation(self):
"""
This is called by the typeclass system whenever an instance of
this class is saved for the first time. It is a generic hook
for calling the startup hooks for the various game entities.
When overloading you generally don't overload this but
overload the hooks called by this method.
"""
self.basetype_setup()
self.at_object_creation()
if hasattr(self, "_createdict"):
# this will only be set if the utils.create function
# was used to create the object. We want the create
# call's kwargs to override the values set by hooks.
cdict = self._createdict
updates = []
if not cdict["key"]:
self.db_key = "#%i" % self.dbid
updates.append("db_key")
elif self.key != cdict["key"]:
updates.append("db_key")
self.db_key = cdict["key"]
if cdict["location"] and self.location != cdict["location"]:
self.db_location = cdict["location"]
updates.append("db_location")
if cdict["home"] and self.home != cdict["home"]:
self.home = cdict["home"]
updates.append("db_home")
if cdict["destination"] and self.destination != cdict["destination"]:
self.destination = cdict["destination"]
updates.append("db_destination")
if updates:
self.save(update_fields=updates)
if cdict["permissions"]:
self.permissions.add(cdict["permissions"])
if cdict["locks"]:
self.locks.add(cdict["locks"])
if cdict["aliases"]:
self.aliases.add(cdict["aliases"])
if cdict["location"]:
cdict["location"].at_object_receive(self, None)
self.at_after_move(None)
self.basetype_posthook_setup()
## hooks called by the game engine ## hooks called by the game engine
def basetype_setup(self): def basetype_setup(self):
@ -550,17 +913,19 @@ class DefaultObject(ObjectDB):
def basetype_posthook_setup(self): def basetype_posthook_setup(self):
""" """
Called once, after basetype_setup and at_object_creation. This should Called once, after basetype_setup and at_object_creation. This
generally not be overloaded unless you are redefining how a should generally not be overloaded unless you are redefining
room/exit/object works. It allows for basetype-like setup after the how a room/exit/object works. It allows for basetype-like
object is created. An example of this is EXITs, who need to know keys, setup after the object is created. An example of this is
aliases, locks etc to set up their exit-cmdsets. EXITs, who need to know keys, aliases, locks etc to set up
their exit-cmdsets.
""" """
pass pass
def at_object_creation(self): def at_object_creation(self):
""" """
Called once, when this object is first created. Called once, when this object is first created. This is
the normal hook to overload for most object types.
""" """
pass pass
@ -574,6 +939,8 @@ class DefaultObject(ObjectDB):
def at_init(self): def at_init(self):
""" """
DEPRECATED: Use __init__ instead.
This is always called whenever this object is initiated -- This is always called whenever this object is initiated --
that is, whenever it its typeclass is cached from memory. This that is, whenever it its typeclass is cached from memory. This
happens on-demand first time the object is used or activated happens on-demand first time the object is used or activated
@ -582,7 +949,6 @@ class DefaultObject(ObjectDB):
""" """
pass pass
def at_cmdset_get(self, **kwargs): def at_cmdset_get(self, **kwargs):
""" """
Called just before cmdsets on this object are requested by the Called just before cmdsets on this object are requested by the
@ -655,29 +1021,6 @@ class DefaultObject(ObjectDB):
""" """
pass pass
def at_access_success(self, accessing_obj, access_type):
"""
OBS: DEPRECATED. Use at_access instead
This hook is called whenever accessing_obj succeed a lock check of
type access_type on this object, for whatever reason. The return value
of this hook is not used, the lock will still pass regardless of what
this hook does (use lockstring/funcs to tweak the lock result).
"""
log_depmsg("at_access_success is deprecated. Use at_access(result,**kwargs) instead.")
pass
def at_access_failure(self, accessing_obj, access_type):
"""
OBS: DEPRECATED. Use at_access instead
This hook is called whenever accessing_obj fails a lock check of type
access_type on this object, for whatever reason. The return value of
this hook is not used, the lock will still fail regardless of what
this hook does (use lockstring/funcs to tweak the lock result).
"""
log_depmsg("at_access_failure is deprecated. Use at_access(result,**kwargs) instead.")
pass
# hooks called when moving the object # hooks called when moving the object

View file

@ -5,7 +5,7 @@ Player that are controlled by the server.
""" """
from django.conf import settings from django.conf import settings
from src.players.player import Player from src.players.player import DefaultPlayer
from src.scripts.scripts import Script from src.scripts.scripts import Script
from src.commands.command import Command from src.commands.command import Command
from src.commands.cmdset import CmdSet from src.commands.cmdset import CmdSet
@ -87,7 +87,7 @@ class BotCmdSet(CmdSet):
# Bot base class # Bot base class
class Bot(Player): class Bot(DefaultPlayer):
""" """
A Bot will start itself when the server A Bot will start itself when the server
starts (it will generally not do so starts (it will generally not do so

View file

@ -9,10 +9,10 @@ Everything starts at handle_setup()
import django import django
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _
from src.server.models import ServerConfig from src.server.models import ServerConfig
from src.utils import create from src.utils import create
from django.utils.translation import ugettext as _ from src.utils.utils import class_from_module
def create_config_values(): def create_config_values():
""" """
@ -26,10 +26,10 @@ def get_god_player():
""" """
Creates the god user. Creates the god user.
""" """
PlayerDB = get_user_model() Player = class_from_module(settings.BASE_PLAYER_TYPECLASS)
try: try:
god_player = PlayerDB.objects.get(id=1) god_player = Player.objects.get(id=1)
except PlayerDB.DoesNotExist: except Player.DoesNotExist:
txt = "\n\nNo superuser exists yet. The superuser is the 'owner'" txt = "\n\nNo superuser exists yet. The superuser is the 'owner'"
txt += "\account on the Evennia server. Create a new superuser using" txt += "\account on the Evennia server. Create a new superuser using"
txt += "\nthe command" txt += "\nthe command"
@ -78,6 +78,7 @@ def create_objects():
god_character.save() god_character.save()
god_player.attributes.add("_first_login", True) god_player.attributes.add("_first_login", True)
print god_character
god_player.attributes.add("_last_puppet", god_character) god_player.attributes.add("_last_puppet", god_character)
god_player.db._playable_characters.append(god_character) god_player.db._playable_characters.append(god_character)

View file

@ -30,7 +30,9 @@ import sys
import re import re
import traceback import traceback
import weakref import weakref
from importlib import import_module
from django.db.models import signals
from django.dispatch import dispatcher
from django.db import models from django.db import models
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -38,6 +40,7 @@ from django.conf import settings
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.utils.idmapper.base import SharedMemoryModelBase
from src.server.caches import get_prop_cache, set_prop_cache from src.server.caches import get_prop_cache, set_prop_cache
#from src.server.caches import set_attr_cache #from src.server.caches import set_attr_cache
@ -48,7 +51,8 @@ from src.locks.lockhandler import LockHandler
#from src.utils import logger #from src.utils import logger
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from src.utils.utils import ( from src.utils.utils import (
make_iter, is_iter, to_str, inherits_from, lazy_property) make_iter, is_iter, to_str, inherits_from, lazy_property,
class_from_module)
from src.utils.dbserialize import to_pickle, from_pickle from src.utils.dbserialize import to_pickle, from_pickle
from src.utils.picklefield import PickledObjectField from src.utils.picklefield import PickledObjectField
@ -56,6 +60,8 @@ __all__ = ("Attribute", "TypeNick", "TypedObject")
TICKER_HANDLER = None TICKER_HANDLER = None
_DB_REGEX = re.compile(r"db$")
_PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] _PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
@ -757,7 +763,16 @@ from django.apps.config import MODELS_MODULE_NAME
from django.db.models.fields.related import OneToOneField from django.db.models.fields.related import OneToOneField
#/ django patch imports #/ django patch imports
from src.utils.idmapper.base import SharedMemoryModelBase
# signal receivers. Assigned in __new__
def post_save(sender, instance, created, **kwargs):
"""
Is called Receive a signal just after the object is saved.
"""
if created:
instance.at_instance_creation()
#TODO - put OOB handler here?
class TypeclassBase(SharedMemoryModelBase): class TypeclassBase(SharedMemoryModelBase):
""" """
@ -1013,10 +1028,14 @@ class TypeclassBase(SharedMemoryModelBase):
new_class._prepare() new_class._prepare()
new_class._meta.apps.register_model(new_class._meta.app_label, new_class) new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
return new_class
#return new_class
# /patch end # /patch end
# attach signal
signals.post_save.connect(post_save, sender=new_class)
return new_class
# #
@ -1082,22 +1101,14 @@ class TypedObject(SharedMemoryModel):
# typeclass mechanism # typeclass mechanism
def _import_class(self, path):
path, clsname = path.rsplit(".", 1)
mod = import_module(path)
try:
return getattr(mod, clsname)
except AttributeError:
raise AttributeError("module '%s' has no attribute '%s'" % (path, clsname))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
typeclass_path = kwargs.pop("typeclass", None) typeclass_path = kwargs.pop("typeclass", None)
super(TypedObject, self).__init__(*args, **kwargs) super(TypedObject, self).__init__(*args, **kwargs)
if typeclass_path: if typeclass_path:
self.__class__ = self._import_class(typeclass_path) self.__class__ = class_from_module(typeclass_path)
self.db_typclass_path = typeclass_path self.db_typclass_path = typeclass_path
elif self.db_typeclass_path: elif self.db_typeclass_path:
self.__class__ = self._import_class(self.db_typeclass_path) self.__class__ = class_from_module(self.db_typeclass_path)
else: else:
self.db_typeclass_path = "%s.%s" % (self.__module__, self.__class__.__name__) self.db_typeclass_path = "%s.%s" % (self.__module__, self.__class__.__name__)
# important to put this at the end since _meta is based on the set __class__ # important to put this at the end since _meta is based on the set __class__
@ -1372,12 +1383,12 @@ class TypedObject(SharedMemoryModel):
self._is_deleted = True self._is_deleted = True
super(TypedObject, self).delete() super(TypedObject, self).delete()
def save(self, *args, **kwargs): #def save(self, *args, **kwargs):
"Block saving non-proxy typeclassed objects" # "Block saving non-proxy typeclassed objects"
if not self._meta.proxy: # if not self._meta.proxy:
raise RuntimeError("Don't create instances of %s, " # raise RuntimeError("Don't create instances of %s, "
"use its child typeclasses instead." % self.__class__.__name__) # "use its child typeclasses instead." % self.__class__.__name__)
super(TypedObject, self).save(*args, **kwargs) # super(TypedObject, self).save(*args, **kwargs)
# #
# Memory management # Memory management

View file

@ -25,12 +25,12 @@ from django.conf import settings
from django.db import IntegrityError from django.db import IntegrityError
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.utils import utils, logger from src.utils import utils, logger
from src.utils.utils import make_iter from src.utils.utils import make_iter, class_from_module, dbid_to_obj
# delayed imports # delayed imports
_User = None _User = None
_Object = None
_ObjectDB = None _ObjectDB = None
_Object = None
_Script = None _Script = None
_ScriptDB = None _ScriptDB = None
_HelpEntry = None _HelpEntry = None
@ -58,11 +58,7 @@ def handle_dbref(inp, objclass, raise_errors=True):
objects. objects.
""" """
if not (isinstance(inp, basestring) and inp.startswith("#")): if not (isinstance(inp, basestring) and inp.startswith("#")):
try:
return inp.dbobj
except AttributeError:
return inp return inp
# a string, analyze it # a string, analyze it
inp = inp.lstrip('#') inp = inp.lstrip('#')
try: try:
@ -87,119 +83,54 @@ def create_object(typeclass=None, key=None, location=None,
home=None, permissions=None, locks=None, home=None, permissions=None, locks=None,
aliases=None, destination=None, report_to=None, nohome=False): aliases=None, destination=None, report_to=None, nohome=False):
""" """
Create a new in-game object. Any game object is a combination
of a database object that stores data persistently to
the database, and a typeclass, which on-the-fly 'decorates'
the database object into whataver different type of object
it is supposed to be in the game.
See src.objects.managers for methods to manipulate existing objects Create a new in-game object.
in the database. src.objects.objects holds the base typeclasses
and src.objects.models hold the database model. keywords:
typeclass - class or python path to a typeclass
key - name of the new object. If not set, a name of #dbref will be set.
home - obj or #dbref to use as the object's home location
permissions - a comma-separated string of permissions
locks - one or more lockstrings, separated by semicolons
aliases - a list of alternative keys
destination - obj or #dbref to use as an Exit's target
report_to is an optional object for reporting errors to in string form.
If report_to is not set, errors will be raised as en Exception
containing the error message. If set, this method will return
None upon errors.
nohome - this allows the creation of objects without a default home location; nohome - this allows the creation of objects without a default home location;
this only used when creating the default location itself or during unittests only used when creating the default location itself or during unittests
""" """
global _Object, _ObjectDB typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS
if not _Object:
from src.objects.objects import Object as _Object
if not _ObjectDB:
from src.objects.models import ObjectDB as _ObjectDB
# input validation if isinstance(typeclass, basestring):
# a path is given. Load the actual typeclass
typeclass = class_from_module(typeclass, settings.OBJECT_TYPECLASS_PATHS)
if not typeclass: # Setup input for the create command. We use ObjectDB as baseclass here
typeclass = settings.BASE_OBJECT_TYPECLASS # to give us maximum freedom (the typeclasses will load
elif isinstance(typeclass, _ObjectDB): # correctly when each object is recovered).
# this is already an objectdb instance, extract its typeclass
typeclass = typeclass.typeclass.path
elif isinstance(typeclass, _Object) or utils.inherits_from(typeclass, _Object):
# this is already an object typeclass, extract its path
typeclass = typeclass.path
typeclass = utils.to_unicode(typeclass)
# Setup input for the create command location = dbid_to_obj(location, _ObjectDB)
destination = dbid_to_obj(destination, _ObjectDB)
location = handle_dbref(location, _ObjectDB) home = dbid_to_obj(home, _ObjectDB)
destination = handle_dbref(destination, _ObjectDB)
home = handle_dbref(home, _ObjectDB)
if not home: if not home:
try: try:
home = handle_dbref(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None home = dbid_to_obj(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None
except _ObjectDB.DoesNotExist: except _ObjectDB.DoesNotExist:
raise _ObjectDB.DoesNotExist("settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." % raise _ObjectDB.DoesNotExist("settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." %
settings.DEFAULT_HOME) settings.DEFAULT_HOME)
# create new database object all in one go # create new instance
new_db_object = _ObjectDB(db_key=key, db_location=location, new_object = typeclass(db_key=key, db_location=location,
db_destination=destination, db_home=home, db_destination=destination, db_home=home,
db_typeclass_path=typeclass) db_typeclass_path=typeclass.path)
# store the call signature for the signal
if not key: new_object._createdict = {"key":key, "location":location, "destination":destination,
# the object should always have a key, so if not set we give a default "home":home, "typeclass":typeclass.path, "permissions":permissions,
new_db_object.key = "#%i" % new_db_object.dbid "locks":locks, "aliases":aliases, "destination":destination,
"report_to":report_to, "nohome":nohome}
# this will either load the typeclass or the default one (will also save object) # this will trigger the save signal which in turn calls the
new_object = new_db_object.typeclass # at_instance_creation hook on the typeclass, where the _createdict can be
# used.
if not _GA(new_object, "is_typeclass")(typeclass, exact=True): new_object.save()
# this will fail if we gave a typeclass as input and it still
# gave us a default
try:
SharedMemoryModel.delete(new_db_object)
except AssertionError:
# this happens if object was never created
pass
if report_to:
report_to = handle_dbref(report_to, _ObjectDB)
_GA(report_to, "msg")("Error creating %s (%s).\n%s" % (new_db_object.key, typeclass,
_GA(new_db_object, "typeclass_last_errmsg")))
return None
else:
raise Exception(_GA(new_db_object, "typeclass_last_errmsg"))
# from now on we can use the typeclass object
# as if it was the database object.
# call the hook methods. This is where all at_creation
# customization happens as the typeclass stores custom
# things on its database object.
# note - this may override input keys, locations etc!
new_object.basetype_setup() # setup the basics of Exits, Characters etc.
new_object.at_object_creation()
# we want the input to override that set in the hooks, so
# we re-apply those if needed
if new_object.key != key:
new_object.key = key
if new_object.location != location:
new_object.location = location
if new_object.home != home:
new_object.home = home
if new_object.destination != destination:
new_object.destination = destination
# custom-given perms/locks do overwrite hooks
if permissions:
new_object.permissions.add(permissions)
if locks:
new_object.locks.add(locks)
if aliases:
new_object.aliases.add(aliases)
# trigger relevant move_to hooks in order to display messages.
if location:
location.at_object_receive(new_object, None)
new_object.at_after_move(None)
# post-hook setup (mainly used by Exits)
new_object.basetype_posthook_setup()
return new_object return new_object
#alias for create_object #alias for create_object

View file

@ -12,11 +12,11 @@ import imp
import types import types
import math import math
import re import re
import importlib
import textwrap import textwrap
import datetime import datetime
import random import random
import traceback import traceback
from importlib import import_module
from inspect import ismodule from inspect import ismodule
from collections import defaultdict from collections import defaultdict
from twisted.internet import threads, defer, reactor from twisted.internet import threads, defer, reactor
@ -347,16 +347,43 @@ def dbref(dbref, reqhash=True):
Output is the integer part. Output is the integer part.
""" """
if reqhash: if reqhash:
return (int(dbref.lstrip('#')) if (isinstance(dbref, basestring) and num = (int(dbref.lstrip('#')) if (isinstance(dbref, basestring) and
dbref.startswith("#") and dbref.startswith("#") and
dbref.lstrip('#').isdigit()) dbref.lstrip('#').isdigit())
else None) else None)
return num if num > 0 else None
elif isinstance(dbref, basestring): elif isinstance(dbref, basestring):
dbref = dbref.lstrip('#') dbref = dbref.lstrip('#')
return int(dbref) if dbref.isdigit() else None return int(dbref) if dbref.isdigit() and int(dbref) > 0 else None
else:
return dbref if isinstance(dbref, int) else None return dbref if isinstance(dbref, int) else None
def dbid_to_obj(inp, objclass, raise_errors=True):
"""
Convert a #dbid to a valid object of objclass. objclass
should be a valid object class to filter against (objclass.filter ...)
If not raise_errors is set, this will swallow errors of non-existing
objects.
"""
dbid = dbref(inp)
if not dbid:
# we only convert #dbrefs
return inp
try:
if int(inp) < 0:
return None
except ValueError:
return None
# if we get to this point, inp is an integer dbref; get the matching object
try:
return objclass.objects.get(id=inp)
except Exception:
if raise_errors:
raise
return inp
def to_unicode(obj, encoding='utf-8', force_string=False): def to_unicode(obj, encoding='utf-8', force_string=False):
""" """
This decodes a suitable object to the unicode format. Note that This decodes a suitable object to the unicode format. Note that
@ -887,15 +914,48 @@ def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None):
paths = [path] + make_iter(defaultpaths) paths = [path] + make_iter(defaultpaths)
for modpath in paths: for modpath in paths:
try: try:
mod = importlib.import_module(path) mod = import_module(path)
except ImportError, ex: except ImportError, ex:
if not str(ex) == "No module named %s" % path: if not str(ex).startswith ("No module named %s" % path):
# this means the module was found but it # this means the module was found but it
# triggers an ImportError on import. # triggers an ImportError on import.
raise ex raise ex
return getattr(mod, variable, default) return getattr(mod, variable, default)
return default return default
def class_from_module(path, defaultpaths=None):
"""
Return a class from a module, given the module's path. This is
primarily used to convert db_typeclass_path:s to classes.
if a list of defaultpaths is given, try subsequent runs by
prepending those paths to the given path.
"""
cls = None
if defaultpaths:
paths = [path] + make_iter(defaultpaths) if defaultpaths else []
else:
paths = [path]
for path in paths:
path, clsname = path.rsplit(".", 1)
try:
mod = import_module(path)
except ImportError, ex:
# normally this is due to a not-found property
if not str(ex).startswith ("No module named %s" % path):
raise ex
try:
cls = getattr(mod, clsname)
break
except AttributeError, ex:
if not str(ex).startswith("Object 'module' has no attribute '%s'" % clsname):
raise ex
if not cls:
raise ImportError("Could not load typeclass '%s'." % path)
return cls
def init_new_player(player): def init_new_player(player):
""" """
Helper method to call all hooks, set flags etc on a newly created Helper method to call all hooks, set flags etc on a newly created