evennia/lib/objects/models.py

179 lines
7.5 KiB
Python
Raw Normal View History

"""
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.
"""
2006-11-20 18:54:10 +00:00
from django.db import models
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from src.typeclasses.models import TypedObject
from src.objects.manager import ObjectDBManager
from src.utils import logger
from src.utils.utils import (make_iter, dbref)
#------------------------------------------------------------
#
# ObjectDB
#
#------------------------------------------------------------
class ObjectDB(TypedObject):
basicobject.py --------------- - Checks for NULL description on objects- if Null, it doesn't print the extra line any more. - Made the checks for contents a little less ambiguous cmdhandler.py -------------- - Added new method 'parse_command' which takes a command string and tries to break it up based on common command parsing rules. Mostly complete, but could use some work on the edge cases. Check out the docstring on the function- I tried to make it fairly well documented. - Changed the check for 'non-standard characters' to just return, rather than throw an Exception. Not sure if this causes any issues, but I noticed that when you hit enter without entering a command it would trigger this code. Now it just fails silently. - The handle function now calls the parse_command function now and stores the results in parsed_input['parsed_command']. This then gets put into cdat['uinput'] at the end of handle() like before. The old data in parsed_input is still there, this is just a new field. - Added cdat['raw_input'] to pass the full, untouched command string on. This is also stored in parsed_input['parsed_command']['raw_command'] so not sure fi this is necessary any longer, probably not. cmdtable.py ------------ - Just cleaned it up a bit and straightened out the columns after changing 3-4 space indentation. apps/objects/models.py ----------------------- - set_description now sets the description attribute to 'None' (or Null in the db) when given a blank description. This is used for the change mentioned above in basicobject.py - get_description now returns None if self.description is None - used defines_global in the comparison methods like is_player functions_db.py ---------------- - Changed import defines_global as defines_global to just 'import defines_global'- wasn't sure why this was this way, if I broke something (I didn't seem to) let me know. - renamed player_search to player_name_search. Removed the use of local_and_global_search inside of it. local_and_global_search now calls it when it receives a search_string that starts with *. - alias_search now only looks at attributes with attr_name == ALIAS. It used to just look at attr_value, which could match anything, it seemed. - added 'dbref_search' - local_and_global_search changes: - Now uses dbref_search & player_search if the string starts with "#" or "*" respectively - Changed when it uses dbref_search to whenever the search_string is a dbref. It used to check that it was a dbref, and that search_contents & search_location were set, but I *believe* in most MU*'s when you supply a dbref it never fails to find the object. commands/unloggedin.py ----------------------- - removed hardcoded object type #'s and started using defines_global instead - when creating a new account, made sure that no object with an alias matching the player name requested exists. This is behavior from TinyMUSH, and I think most MUSHs follow this, but if not this is easy enough to change back. commands/general.py -------------------- - Rewrote cmd_page: - New Features - Page by dbref - Page multiple people - pose (:) and no space pose (;) pages - When someone hits page without a target or data, it now will tell the player who they last paged, or say they haven't paged anyone if they don't have a LASTPAGED - uses parse_command, made it a lot easier to work through the extra functionality added above - When there are multiple words in a page target, it first tries to find a player that matches the entire string. If that fails, then it goes through each word, assuming each is a separate target, and works out paging them. commands/objmanip.py --------------------- - I started to muck with cmd_name & cmd_page, but decided to hold off for now. Largely, if everyone is cool with the idea that names & aliases should be totally unique, then we need to go ahead and re-write these. I'll do that if everyone is cool with it.
2008-06-13 18:15:54 +00:00
"""
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
basicobject.py --------------- - Checks for NULL description on objects- if Null, it doesn't print the extra line any more. - Made the checks for contents a little less ambiguous cmdhandler.py -------------- - Added new method 'parse_command' which takes a command string and tries to break it up based on common command parsing rules. Mostly complete, but could use some work on the edge cases. Check out the docstring on the function- I tried to make it fairly well documented. - Changed the check for 'non-standard characters' to just return, rather than throw an Exception. Not sure if this causes any issues, but I noticed that when you hit enter without entering a command it would trigger this code. Now it just fails silently. - The handle function now calls the parse_command function now and stores the results in parsed_input['parsed_command']. This then gets put into cdat['uinput'] at the end of handle() like before. The old data in parsed_input is still there, this is just a new field. - Added cdat['raw_input'] to pass the full, untouched command string on. This is also stored in parsed_input['parsed_command']['raw_command'] so not sure fi this is necessary any longer, probably not. cmdtable.py ------------ - Just cleaned it up a bit and straightened out the columns after changing 3-4 space indentation. apps/objects/models.py ----------------------- - set_description now sets the description attribute to 'None' (or Null in the db) when given a blank description. This is used for the change mentioned above in basicobject.py - get_description now returns None if self.description is None - used defines_global in the comparison methods like is_player functions_db.py ---------------- - Changed import defines_global as defines_global to just 'import defines_global'- wasn't sure why this was this way, if I broke something (I didn't seem to) let me know. - renamed player_search to player_name_search. Removed the use of local_and_global_search inside of it. local_and_global_search now calls it when it receives a search_string that starts with *. - alias_search now only looks at attributes with attr_name == ALIAS. It used to just look at attr_value, which could match anything, it seemed. - added 'dbref_search' - local_and_global_search changes: - Now uses dbref_search & player_search if the string starts with "#" or "*" respectively - Changed when it uses dbref_search to whenever the search_string is a dbref. It used to check that it was a dbref, and that search_contents & search_location were set, but I *believe* in most MU*'s when you supply a dbref it never fails to find the object. commands/unloggedin.py ----------------------- - removed hardcoded object type #'s and started using defines_global instead - when creating a new account, made sure that no object with an alias matching the player name requested exists. This is behavior from TinyMUSH, and I think most MUSHs follow this, but if not this is easy enough to change back. commands/general.py -------------------- - Rewrote cmd_page: - New Features - Page by dbref - Page multiple people - pose (:) and no space pose (;) pages - When someone hits page without a target or data, it now will tell the player who they last paged, or say they haven't paged anyone if they don't have a LASTPAGED - uses parse_command, made it a lot easier to work through the extra functionality added above - When there are multiple words in a page target, it first tries to find a player that matches the entire string. If that fails, then it goes through each word, assuming each is a separate target, and works out paging them. commands/objmanip.py --------------------- - I started to muck with cmd_name & cmd_page, but decided to hold off for now. Largely, if everyone is cool with the idea that names & aliases should be totally unique, then we need to go ahead and re-write these. I'll do that if everyone is cool with it.
2008-06-13 18:15:54 +00:00
"""
#
# 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
# managed by 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 are created at the metaclass level and
# 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", null=True, verbose_name='player', on_delete=models.SET_NULL,
help_text='a Player connected to this object, if any.')
2013-02-01 22:03:55 +01:00
# the session id associated with this player, if any
db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id",
help_text="csv list of session ids of connected Player, if any.")
# The location in the game world. Since this one is likely
# to change often, we set this with the 'location' property
# to transparently handle Typeclassing.
db_location = models.ForeignKey('self', related_name="locations_set", db_index=True, on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='game location')
# a safety location, this usually don't change much.
db_home = models.ForeignKey('self', related_name="homes_set", on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='home location')
# destination of this object - primarily used by exits.
db_destination = models.ForeignKey('self', related_name="destinations_set", db_index=True, on_delete=models.SET_NULL,
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 = ObjectDBManager()
# cmdset_storage property handling
def __cmdset_storage_get(self):
"getter"
storage = self.db_cmdset_storage
return [path.strip() for path in storage.split(',')] if storage else []
def __cmdset_storage_set(self, value):
"setter"
self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value))
self.save(update_fields=["db_cmdset_storage"])
def __cmdset_storage_del(self):
"deleter"
self.db_cmdset_storage = None
self.save(update_fields=["db_cmdset_storage"])
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del)
# location getsetter
def __location_get(self):
"Get location"
return self.db_location
def __location_set(self, location):
"Set location, checking for loops and allowing dbref"
if isinstance(location, (basestring, int)):
# allow setting of #dbref
dbid = dbref(location, reqhash=False)
if dbid:
try:
location = ObjectDB.objects.get(id=dbid)
except ObjectDoesNotExist:
# maybe it is just a name that happens to look like a dbid
pass
try:
def is_loc_loop(loc, depth=0):
"Recursively traverse target location, trying to catch a loop."
if depth > 10:
return
elif loc == self:
raise RuntimeError
elif loc == None:
raise RuntimeWarning
return is_loc_loop(loc.db_location, depth + 1)
try:
is_loc_loop(location)
except RuntimeWarning:
pass
# actually set the field
self.db_location = location
self.save(update_fields=["db_location"])
except RuntimeError:
errmsg = "Error: %s.location = %s creates a location loop." % (self.key, location)
logger.log_errmsg(errmsg)
raise RuntimeError(errmsg)
except Exception, e:
errmsg = "Error (%s): %s is not a valid location." % (str(e), location)
logger.log_errmsg(errmsg)
raise Exception(errmsg)
def __location_del(self):
"Cleanly delete the location reference"
self.db_location = None
self.save(update_fields=["db_location"])
location = property(__location_get, __location_set, __location_del)
class Meta:
"Define Django meta options"
verbose_name = "Object"
verbose_name_plural = "Objects"