mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 21:36:30 +01:00
178 lines
7.5 KiB
Python
178 lines
7.5 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.
|
|
"""
|
|
|
|
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):
|
|
"""
|
|
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
|
|
# 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.')
|
|
# 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"
|
|
|