mirror of
https://github.com/evennia/evennia.git
synced 2026-03-29 12:07:17 +02:00
commit
16b1aeffc2
31 changed files with 953 additions and 353 deletions
1
ev.py
1
ev.py
|
|
@ -132,6 +132,7 @@ from src.utils import logger
|
|||
from src.utils import utils
|
||||
from src.utils import gametime
|
||||
from src.utils import ansi
|
||||
from src.utils.spawner import spawn
|
||||
|
||||
|
||||
######################################################################
|
||||
|
|
|
|||
4
game/gamesrc/web/template_overrides/README.md
Normal file
4
game/gamesrc/web/template_overrides/README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
Place your own version of templates into this file to override the default ones.
|
||||
For instance, if there's a template at: `src/web/templates/evennia_general/index.html`
|
||||
and you want to replace it, create the file `game/gamesrc/web/template_overrides/evennia_general/index.html`
|
||||
and it will be loaded instead.
|
||||
56
game/gamesrc/world/examples/prototypes.py
Normal file
56
game/gamesrc/world/examples/prototypes.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"""
|
||||
Example prototypes read by the @spawn command but is also easily
|
||||
available to use from code. Each prototype should be a dictionary. Use
|
||||
the same name as the variable to refer to other prototypes.
|
||||
|
||||
Possible keywords are:
|
||||
prototype - string pointing to parent prototype of this structure
|
||||
key - string, the main object identifier
|
||||
typeclass - string, if not set, will use settings.BASE_OBJECT_TYPECLASS
|
||||
location - this should be a valid object or #dbref
|
||||
home - valid object or #dbref
|
||||
destination - only valid for exits (object or dbref)
|
||||
|
||||
permissions - string or list of permission strings
|
||||
locks - a lock-string
|
||||
aliases - string or list of strings
|
||||
|
||||
ndb_<name> - value of a nattribute (ndb_ is stripped)
|
||||
any other keywords are interpreted as Attributes and their values.
|
||||
|
||||
See the @spawn command and src.utils.spawner for more info.
|
||||
|
||||
"""
|
||||
|
||||
from random import randint
|
||||
|
||||
NOBODY = {}
|
||||
|
||||
GOBLIN = {
|
||||
"key": "goblin grunt",
|
||||
"health": lambda: randint(20,30),
|
||||
"resists": ["cold", "poison"],
|
||||
"attacks": ["fists"],
|
||||
"weaknesses": ["fire", "light"]
|
||||
}
|
||||
|
||||
GOBLIN_WIZARD = {
|
||||
"prototype": "GOBLIN",
|
||||
"key": "goblin wizard",
|
||||
"spells": ["fire ball", "lighting bolt"]
|
||||
}
|
||||
|
||||
GOBLIN_ARCHER = {
|
||||
"prototype": "GOBLIN",
|
||||
"key": "goblin archer",
|
||||
"attacks": ["short bow"]
|
||||
}
|
||||
|
||||
ARCHWIZARD = {
|
||||
"attacks": ["archwizard staff"],
|
||||
}
|
||||
|
||||
GOBLIN_ARCHWIZARD = {
|
||||
"key": "goblin archwizard",
|
||||
"prototype" : ("GOBLIN_WIZARD", "ARCHWIZARD")
|
||||
}
|
||||
|
|
@ -262,7 +262,6 @@ def at_multimatch_cmd(caller, matches):
|
|||
|
||||
id1 = ""
|
||||
id2 = ""
|
||||
print "cmd.obj:", cmd, cmd.obj
|
||||
if (not (is_channel or is_exit) and
|
||||
(hasattr(cmd, 'obj') and cmd.obj != caller) and
|
||||
hasattr(cmd.obj, "key")):
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
|||
"""
|
||||
|
||||
python_paths = [path] + ["%s.%s" % (prefix, path)
|
||||
for prefix in _CMDSET_PATHS]
|
||||
for prefix in _CMDSET_PATHS if not path.startswith(prefix)]
|
||||
errstring = ""
|
||||
for python_path in python_paths:
|
||||
try:
|
||||
|
|
@ -123,16 +123,19 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
|||
cmdsetclass = cmdsetclass(cmdsetobj)
|
||||
return cmdsetclass
|
||||
|
||||
except ImportError:
|
||||
errstring += _("Error loading cmdset: Couldn't import module '%s'.")
|
||||
errstring = errstring % modulepath
|
||||
except ImportError, e:
|
||||
errstring += _("Error loading cmdset: Couldn't import module '%s': %s.")
|
||||
errstring = errstring % (modulepath, e)
|
||||
except KeyError:
|
||||
errstring += _("Error in loading cmdset: No cmdset class '%(classname)s' in %(modulepath)s.")
|
||||
errstring = errstring % {"classname": classname,
|
||||
"modulepath": modulepath}
|
||||
except SyntaxError, e:
|
||||
errstring += _("SyntaxError encountered when loading cmdset '%s': %s.")
|
||||
errstring = errstring % (modulepath, e)
|
||||
except Exception:
|
||||
errstring += _("Compile/Run error when loading cmdset '%s'. Error was logged.")
|
||||
errstring = errstring % (python_path)
|
||||
errstring += _("Compile/Run error when loading cmdset '%s': %s.")
|
||||
errstring = errstring % (python_path, e)
|
||||
|
||||
if errstring:
|
||||
# returning an empty error cmdset
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ All commands in Evennia inherit from the 'Command' class in this module.
|
|||
|
||||
import re
|
||||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils.utils import is_iter, fill, LazyLoadHandler
|
||||
from src.utils.utils import is_iter, fill, lazy_property
|
||||
|
||||
|
||||
def _init_command(mcs, **kwargs):
|
||||
|
|
@ -155,7 +155,10 @@ class Command(object):
|
|||
overloading evential same-named class properties."""
|
||||
if kwargs:
|
||||
_init_command(self, **kwargs)
|
||||
self.lockhandler = LazyLoadHandler(self, "lockhandler", LockHandler)
|
||||
|
||||
@lazy_property
|
||||
def lockhandler(self):
|
||||
return LockHandler(self)
|
||||
|
||||
def __str__(self):
|
||||
"Print the command"
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ Building and world design commands
|
|||
"""
|
||||
from django.conf import settings
|
||||
from src.objects.models import ObjectDB
|
||||
from src.utils import create, utils, search
|
||||
from src.utils.ansi import raw
|
||||
from src.locks.lockhandler import LockException
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
from src.commands.cmdhandler import get_and_merge_cmdsets
|
||||
from src.utils import create, utils, search
|
||||
from src.utils.spawner import spawn
|
||||
from src.utils.ansi import raw
|
||||
|
||||
# limit symbol import for API
|
||||
__all__ = ("ObjManipCommand", "CmdSetObjAlias", "CmdCopy",
|
||||
|
|
@ -19,7 +20,7 @@ __all__ = ("ObjManipCommand", "CmdSetObjAlias", "CmdCopy",
|
|||
"CmdUnLink", "CmdSetHome", "CmdListCmdSets", "CmdName",
|
||||
"CmdOpen", "CmdSetAttribute", "CmdTypeclass", "CmdWipe",
|
||||
"CmdLock", "CmdExamine", "CmdFind", "CmdTeleport",
|
||||
"CmdScript", "CmdTag")
|
||||
"CmdScript", "CmdTag", "CmdSpawn")
|
||||
|
||||
try:
|
||||
# used by @set
|
||||
|
|
@ -30,7 +31,7 @@ except ImportError:
|
|||
|
||||
# used by @find
|
||||
CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
|
||||
|
||||
_PROTOTYPE_PARENTS = None
|
||||
|
||||
class ObjManipCommand(MuxCommand):
|
||||
"""
|
||||
|
|
@ -1169,6 +1170,74 @@ class CmdOpen(ObjManipCommand):
|
|||
back_exit_typeclass)
|
||||
|
||||
|
||||
def _convert_from_string(cmd, strobj):
|
||||
"""
|
||||
Converts a single object in *string form* to its equivalent python
|
||||
type.
|
||||
|
||||
Python earlier than 2.6:
|
||||
Handles floats, ints, and limited nested lists and dicts
|
||||
(can't handle lists in a dict, for example, this is mainly due to
|
||||
the complexity of parsing this rather than any technical difficulty -
|
||||
if there is a need for @set-ing such complex structures on the
|
||||
command line we might consider adding it).
|
||||
Python 2.6 and later:
|
||||
Supports all Python structures through literal_eval as long as they
|
||||
are valid Python syntax. If they are not (such as [test, test2], ie
|
||||
withtout the quotes around the strings), the entire structure will
|
||||
be converted to a string and a warning will be given.
|
||||
|
||||
We need to convert like this since all data being sent over the
|
||||
telnet connection by the Player is text - but we will want to
|
||||
store it as the "real" python type so we can do convenient
|
||||
comparisons later (e.g. obj.db.value = 2, if value is stored as a
|
||||
string this will always fail).
|
||||
"""
|
||||
|
||||
def rec_convert(obj):
|
||||
"""
|
||||
Helper function of recursive conversion calls. This is only
|
||||
used for Python <=2.5. After that literal_eval is available.
|
||||
"""
|
||||
# simple types
|
||||
try:
|
||||
return int(obj)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return float(obj)
|
||||
except ValueError:
|
||||
pass
|
||||
# iterables
|
||||
if obj.startswith('[') and obj.endswith(']'):
|
||||
"A list. Traverse recursively."
|
||||
return [rec_convert(val) for val in obj[1:-1].split(',')]
|
||||
if obj.startswith('(') and obj.endswith(')'):
|
||||
"A tuple. Traverse recursively."
|
||||
return tuple([rec_convert(val) for val in obj[1:-1].split(',')])
|
||||
if obj.startswith('{') and obj.endswith('}') and ':' in obj:
|
||||
"A dict. Traverse recursively."
|
||||
return dict([(rec_convert(pair.split(":", 1)[0]),
|
||||
rec_convert(pair.split(":", 1)[1]))
|
||||
for pair in obj[1:-1].split(',') if ":" in pair])
|
||||
# if nothing matches, return as-is
|
||||
return obj
|
||||
|
||||
if _LITERAL_EVAL:
|
||||
# Use literal_eval to parse python structure exactly.
|
||||
try:
|
||||
return _LITERAL_EVAL(strobj)
|
||||
except (SyntaxError, ValueError):
|
||||
# treat as string
|
||||
string = "{RNote: Value was converted to string. If you don't want this, "
|
||||
string += "use proper Python syntax, like enclosing strings in quotes.{n"
|
||||
cmd.caller.msg(string)
|
||||
return utils.to_str(strobj)
|
||||
else:
|
||||
# fall back to old recursive solution (does not support
|
||||
# nested lists/dicts)
|
||||
return rec_convert(strobj.strip())
|
||||
|
||||
class CmdSetAttribute(ObjManipCommand):
|
||||
"""
|
||||
set attribute on an object or player
|
||||
|
|
@ -1202,73 +1271,6 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
locks = "cmd:perm(set) or perm(Builders)"
|
||||
help_category = "Building"
|
||||
|
||||
def convert_from_string(self, strobj):
|
||||
"""
|
||||
Converts a single object in *string form* to its equivalent python
|
||||
type.
|
||||
|
||||
Python earlier than 2.6:
|
||||
Handles floats, ints, and limited nested lists and dicts
|
||||
(can't handle lists in a dict, for example, this is mainly due to
|
||||
the complexity of parsing this rather than any technical difficulty -
|
||||
if there is a need for @set-ing such complex structures on the
|
||||
command line we might consider adding it).
|
||||
Python 2.6 and later:
|
||||
Supports all Python structures through literal_eval as long as they
|
||||
are valid Python syntax. If they are not (such as [test, test2], ie
|
||||
withtout the quotes around the strings), the entire structure will
|
||||
be converted to a string and a warning will be given.
|
||||
|
||||
We need to convert like this since all data being sent over the
|
||||
telnet connection by the Player is text - but we will want to
|
||||
store it as the "real" python type so we can do convenient
|
||||
comparisons later (e.g. obj.db.value = 2, if value is stored as a
|
||||
string this will always fail).
|
||||
"""
|
||||
|
||||
def rec_convert(obj):
|
||||
"""
|
||||
Helper function of recursive conversion calls. This is only
|
||||
used for Python <=2.5. After that literal_eval is available.
|
||||
"""
|
||||
# simple types
|
||||
try:
|
||||
return int(obj)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return float(obj)
|
||||
except ValueError:
|
||||
pass
|
||||
# iterables
|
||||
if obj.startswith('[') and obj.endswith(']'):
|
||||
"A list. Traverse recursively."
|
||||
return [rec_convert(val) for val in obj[1:-1].split(',')]
|
||||
if obj.startswith('(') and obj.endswith(')'):
|
||||
"A tuple. Traverse recursively."
|
||||
return tuple([rec_convert(val) for val in obj[1:-1].split(',')])
|
||||
if obj.startswith('{') and obj.endswith('}') and ':' in obj:
|
||||
"A dict. Traverse recursively."
|
||||
return dict([(rec_convert(pair.split(":", 1)[0]),
|
||||
rec_convert(pair.split(":", 1)[1]))
|
||||
for pair in obj[1:-1].split(',') if ":" in pair])
|
||||
# if nothing matches, return as-is
|
||||
return obj
|
||||
|
||||
if _LITERAL_EVAL:
|
||||
# Use literal_eval to parse python structure exactly.
|
||||
try:
|
||||
return _LITERAL_EVAL(strobj)
|
||||
except (SyntaxError, ValueError):
|
||||
# treat as string
|
||||
string = "{RNote: Value was converted to string. If you don't want this, "
|
||||
string += "use proper Python syntax, like enclosing strings in quotes.{n"
|
||||
self.caller.msg(string)
|
||||
return utils.to_str(strobj)
|
||||
else:
|
||||
# fall back to old recursive solution (does not support
|
||||
# nested lists/dicts)
|
||||
return rec_convert(strobj.strip())
|
||||
|
||||
def func(self):
|
||||
"Implement the set attribute - a limited form of @py."
|
||||
|
|
@ -1295,7 +1297,7 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
if self.rhs is None:
|
||||
# no = means we inspect the attribute(s)
|
||||
if not attrs:
|
||||
attrs = [attr.key for attr in obj.get_all_attributes()]
|
||||
attrs = [attr.key for attr in obj.attributes.all()]
|
||||
for attr in attrs:
|
||||
if obj.attributes.has(attr):
|
||||
string += "\nAttribute %s/%s = %s" % (obj.name, attr,
|
||||
|
|
@ -1303,7 +1305,7 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
else:
|
||||
string += "\n%s has no attribute '%s'." % (obj.name, attr)
|
||||
# we view it without parsing markup.
|
||||
self.caller.msg(string.strip(), data={"raw": True})
|
||||
self.caller.msg(string.strip(), raw=True)
|
||||
return
|
||||
else:
|
||||
# deleting the attribute(s)
|
||||
|
|
@ -1318,7 +1320,7 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
# setting attribute(s). Make sure to convert to real Python type before saving.
|
||||
for attr in attrs:
|
||||
try:
|
||||
obj.attributes.add(attr, self.convert_from_string(value))
|
||||
obj.attributes.add(attr, _convert_from_string(self, value))
|
||||
string += "\nCreated attribute %s/%s = %s" % (obj.name, attr, value)
|
||||
except SyntaxError:
|
||||
# this means literal_eval tried to parse a faulty string
|
||||
|
|
@ -2242,3 +2244,100 @@ class CmdTag(MuxCommand):
|
|||
else:
|
||||
string = "No tags attached to %s." % obj
|
||||
self.caller.msg(string)
|
||||
|
||||
#
|
||||
# To use the prototypes with the @spawn function, copy
|
||||
# game/gamesrc/world/examples/prototypes.py up one level
|
||||
# to game/gamesrc/world. Then add to game/settings.py the
|
||||
# line
|
||||
# PROTOTYPE_MODULES = ["game.gamesrc.commands.prototypes"]
|
||||
# Reload the server and the prototypes should be available.
|
||||
#
|
||||
|
||||
class CmdSpawn(MuxCommand):
|
||||
"""
|
||||
spawn objects from prototype
|
||||
|
||||
Usage:
|
||||
@spawn
|
||||
@spawn[/switch] prototype_name
|
||||
@spawn[/switch] {prototype dictionary}
|
||||
|
||||
Switch:
|
||||
noloc - allow location to be None if not specified explicitly. Otherwise,
|
||||
location will default to caller's current location.
|
||||
|
||||
Example:
|
||||
@spawn GOBLIN
|
||||
@spawn {"key":"goblin", "typeclass":"monster.Monster", "location":"#2"}
|
||||
|
||||
Dictionary keys:
|
||||
{wprototype {n - name of parent prototype to use. Can be a list for
|
||||
multiple inheritance (inherits left to right)
|
||||
{wkey {n - string, the main object identifier
|
||||
{wtypeclass {n - string, if not set, will use settings.BASE_OBJECT_TYPECLASS
|
||||
{wlocation {n - this should be a valid object or #dbref
|
||||
{whome {n - valid object or #dbref
|
||||
{wdestination{n - only valid for exits (object or dbref)
|
||||
{wpermissions{n - string or list of permission strings
|
||||
{wlocks {n - a lock-string
|
||||
{waliases {n - string or list of strings
|
||||
{wndb_{n<name> - value of a nattribute (ndb_ is stripped)
|
||||
any other keywords are interpreted as Attributes and their values.
|
||||
|
||||
The available prototypes are defined globally in modules set in
|
||||
settings.PROTOTYPE_MODULES. If @spawn is used without arguments it
|
||||
displays a list of available prototypes.
|
||||
"""
|
||||
|
||||
key = "@spawn"
|
||||
aliases = ["spawn"]
|
||||
locks = "cmd:perm(spawn) or perm(Builders)"
|
||||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
"Implements the spawner"
|
||||
|
||||
def _show_prototypes(prototypes):
|
||||
"Helper to show a list of available prototypes"
|
||||
string = "\nAvailable prototypes:\n %s"
|
||||
string = string % utils.fill(", ".join(sorted(prototypes.keys())))
|
||||
return string
|
||||
|
||||
prototypes = spawn(return_prototypes=True)
|
||||
if not self.args:
|
||||
string = "Usage: @spawn {key:value, key, value, ... }"
|
||||
self.caller.msg(string + _show_prototypes(prototypes))
|
||||
return
|
||||
try:
|
||||
# make use of _convert_from_string from the SetAttribute command
|
||||
prototype = _convert_from_string(self, self.args)
|
||||
except SyntaxError:
|
||||
# this means literal_eval tried to parse a faulty string
|
||||
string = "{RCritical Python syntax error in argument. "
|
||||
string += "Only primitive Python structures are allowed. "
|
||||
string += "\nYou also need to use correct Python syntax. "
|
||||
string += "Remember especially to put quotes around all "
|
||||
string += "strings inside lists and dicts.{n"
|
||||
self.caller.msg(string)
|
||||
return
|
||||
|
||||
if isinstance(prototype, basestring):
|
||||
# A prototype key
|
||||
keystr = prototype
|
||||
prototype = prototypes.get(prototype, None)
|
||||
if not prototype:
|
||||
string = "No prototype named '%s'." % keystr
|
||||
self.caller.msg(string + _show_prototypes(prototypes))
|
||||
return
|
||||
elif not isinstance(prototype, dict):
|
||||
self.caller.msg("The prototype must be a prototype key or a Python dictionary.")
|
||||
return
|
||||
|
||||
if not "noloc" in self.switches and not "location" in prototype:
|
||||
prototype["location"] = self.caller.location
|
||||
|
||||
for obj in spawn(prototype):
|
||||
self.caller.msg("Spawned %s." % obj.key)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ class CharacterCmdSet(CmdSet):
|
|||
self.add(building.CmdScript())
|
||||
self.add(building.CmdSetHome())
|
||||
self.add(building.CmdTag())
|
||||
self.add(building.CmdSpawn())
|
||||
|
||||
# Batchprocessor commands
|
||||
self.add(batchprocess.CmdBatchCommands())
|
||||
|
|
|
|||
|
|
@ -378,9 +378,11 @@ class CmdWho(MuxPlayerCommand):
|
|||
|
||||
nplayers = (SESSIONS.player_count())
|
||||
if show_session_data:
|
||||
# privileged info
|
||||
table = prettytable.PrettyTable(["{wPlayer Name",
|
||||
"{wOn for",
|
||||
"{wIdle",
|
||||
"{wPuppeting",
|
||||
"{wRoom",
|
||||
"{wCmds",
|
||||
"{wProtocol",
|
||||
|
|
@ -389,25 +391,27 @@ class CmdWho(MuxPlayerCommand):
|
|||
if not session.logged_in: continue
|
||||
delta_cmd = time.time() - session.cmd_last_visible
|
||||
delta_conn = time.time() - session.conn_time
|
||||
plr_pobject = session.get_puppet()
|
||||
plr_pobject = plr_pobject or session.get_player()
|
||||
table.add_row([utils.crop(plr_pobject.name, width=25),
|
||||
player = session.get_player()
|
||||
puppet = session.get_puppet()
|
||||
location = puppet.location.key if puppet else "None"
|
||||
table.add_row([utils.crop(player.name, width=25),
|
||||
utils.time_format(delta_conn, 0),
|
||||
utils.time_format(delta_cmd, 1),
|
||||
hasattr(plr_pobject, "location") and plr_pobject.location and plr_pobject.location.key or "None",
|
||||
utils.crop(puppet.key if puppet else "None", width=25),
|
||||
utils.crop(location, width=25),
|
||||
session.cmd_total,
|
||||
session.protocol_key,
|
||||
isinstance(session.address, tuple) and session.address[0] or session.address])
|
||||
else:
|
||||
# unprivileged
|
||||
table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"])
|
||||
for session in session_list:
|
||||
if not session.logged_in:
|
||||
continue
|
||||
delta_cmd = time.time() - session.cmd_last_visible
|
||||
delta_conn = time.time() - session.conn_time
|
||||
plr_pobject = session.get_puppet()
|
||||
plr_pobject = plr_pobject or session.get_player()
|
||||
table.add_row([utils.crop(plr_pobject.name, width=25),
|
||||
player = session.get_player()
|
||||
table.add_row([utils.crop(player.key, width=25),
|
||||
utils.time_format(delta_conn, 0),
|
||||
utils.time_format(delta_cmd, 1)])
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ from src.players.player import Player
|
|||
from src.utils import create, ansi
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
|
||||
from django.db.models.signals import pre_save
|
||||
from src.server.caches import field_pre_save
|
||||
pre_save.connect(field_pre_save, dispatch_uid="fieldcache")
|
||||
from django.db.models.signals import post_save
|
||||
from src.server.caches import field_post_save
|
||||
post_save.connect(field_post_save, dispatch_uid="fieldcache")
|
||||
|
||||
# set up signal here since we are not starting the server
|
||||
|
||||
|
|
@ -78,12 +78,12 @@ class CommandTest(TestCase):
|
|||
CID = 0 # we must set a different CID in every test to avoid unique-name collisions creating the objects
|
||||
def setUp(self):
|
||||
"sets up testing environment"
|
||||
settings.DEFAULT_HOME = "#2"
|
||||
#print "creating player %i: %s" % (self.CID, self.__class__.__name__)
|
||||
self.player = create.create_player("TestPlayer%i" % self.CID, "test@test.com", "testpassword", typeclass=TestPlayerClass)
|
||||
self.player2 = create.create_player("TestPlayer%ib" % self.CID, "test@test.com", "testpassword", typeclass=TestPlayerClass)
|
||||
self.room1 = create.create_object("src.objects.objects.Room", key="Room%i"%self.CID, nohome=True)
|
||||
self.room1.db.desc = "room_desc"
|
||||
settings.DEFAULT_HOME = "#%i" % self.room1.id # we must have a default home
|
||||
self.room2 = create.create_object("src.objects.objects.Room", key="Room%ib" % self.CID)
|
||||
self.obj1 = create.create_object(TestObjectClass, key="Obj%i" % self.CID, location=self.room1, home=self.room1)
|
||||
self.obj2 = create.create_object(TestObjectClass, key="Obj%ib" % self.CID, location=self.room1, home=self.room1)
|
||||
|
|
@ -272,7 +272,7 @@ class TestComms(CommandTest):
|
|||
self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.")
|
||||
self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message")
|
||||
self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestPlayer7")
|
||||
self.call(comms.CmdPage(), "TestPlayer7b = Test", "You paged TestPlayer7b with: 'Test'.")
|
||||
self.call(comms.CmdPage(), "TestPlayer7b = Test", "TestPlayer7b is offline. They will see your message if they list their pages later.|You paged TestPlayer7b with: 'Test'.")
|
||||
self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] <channel> = <player> [:reason]") # noone else connected to boot
|
||||
self.call(comms.CmdCdestroy(), "testchan" ,"[testchan] TestPlayer7: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.")
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from src.utils.idmapper.models import SharedMemoryModel
|
|||
from src.comms import managers
|
||||
from src.comms.managers import identify_object
|
||||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils.utils import crop, make_iter, LazyLoadHandler
|
||||
from src.utils.utils import crop, make_iter, lazy_property
|
||||
|
||||
__all__ = ("Msg", "TempMsg", "ChannelDB")
|
||||
|
||||
|
|
@ -103,7 +103,6 @@ class Msg(SharedMemoryModel):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
SharedMemoryModel.__init__(self, *args, **kwargs)
|
||||
#_SA(self, "locks", LazyLoadHandler(self, "locks", LockHandler))
|
||||
self.extra_senders = []
|
||||
|
||||
class Meta:
|
||||
|
|
@ -299,10 +298,13 @@ class TempMsg(object):
|
|||
self.header = header
|
||||
self.message = message
|
||||
self.lock_storage = lockstring
|
||||
self.locks = LazyLoadHandler(self, "locks", LockHandler)
|
||||
self.hide_from = hide_from and make_iter(hide_from) or []
|
||||
self.date_sent = datetime.now()
|
||||
|
||||
@lazy_property
|
||||
def locks(self):
|
||||
return LockHandler(self)
|
||||
|
||||
def __str__(self):
|
||||
"This handles what is shown when e.g. printing the message"
|
||||
senders = ",".join(obj.key for obj in self.senders)
|
||||
|
|
@ -359,12 +361,6 @@ class ChannelDB(TypedObject):
|
|||
_typeclass_paths = settings.CHANNEL_TYPECLASS_PATHS
|
||||
_default_typeclass_path = settings.BASE_CHANNEL_TYPECLASS or "src.comms.comms.Channel"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
TypedObject.__init__(self, *args, **kwargs)
|
||||
_SA(self, "tags", LazyLoadHandler(self, "tags", TagHandler))
|
||||
_SA(self, "attributes", LazyLoadHandler(self, "attributes", AttributeHandler))
|
||||
_SA(self, "aliases", LazyLoadHandler(self, "aliases", AliasHandler))
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Channel"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from src.utils.idmapper.models import SharedMemoryModel
|
|||
from src.help.manager import HelpEntryManager
|
||||
from src.typeclasses.models import Tag, TagHandler
|
||||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils.utils import LazyLoadHandler
|
||||
from src.utils.utils import lazy_property
|
||||
__all__ = ("HelpEntry",)
|
||||
|
||||
|
||||
|
|
@ -66,10 +66,16 @@ class HelpEntry(SharedMemoryModel):
|
|||
objects = HelpEntryManager()
|
||||
_is_deleted = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
SharedMemoryModel.__init__(self, *args, **kwargs)
|
||||
self.locks = LazyLoadHandler(self, "locks", LockHandler)
|
||||
self.tags = LazyLoadHandler(self, "tags", TagHandler)
|
||||
# lazy-loaded handlers
|
||||
|
||||
@lazy_property
|
||||
def locks(self):
|
||||
return LockHandler(self)
|
||||
|
||||
@lazy_property
|
||||
def tags(self):
|
||||
return TagHandler(self)
|
||||
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
|
|
|
|||
|
|
@ -19,16 +19,15 @@ from django.db import models
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from src.typeclasses.models import (TypedObject, TagHandler, NickHandler,
|
||||
AliasHandler, AttributeHandler)
|
||||
from src.typeclasses.models import TypedObject, NickHandler
|
||||
from src.objects.manager import ObjectManager
|
||||
from src.players.models import PlayerDB
|
||||
from src.commands.cmdsethandler import CmdSetHandler
|
||||
from src.commands import cmdhandler
|
||||
from src.scripts.scripthandler import ScriptHandler
|
||||
from src.utils import logger
|
||||
from src.utils.utils import (make_iter, to_str, to_unicode,
|
||||
variable_from_module, dbref, LazyLoadHandler)
|
||||
from src.utils.utils import (make_iter, to_str, to_unicode, lazy_property,
|
||||
variable_from_module, dbref)
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
|
@ -130,19 +129,18 @@ class ObjectDB(TypedObject):
|
|||
_typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
|
||||
_default_typeclass_path = settings.BASE_OBJECT_TYPECLASS or "src.objects.objects.Object"
|
||||
|
||||
# Add the object-specific handlers
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Parent must be initialized first."
|
||||
TypedObject.__init__(self, *args, **kwargs)
|
||||
# handlers
|
||||
_SA(self, "cmdset", LazyLoadHandler(self, "cmdset", CmdSetHandler, True))
|
||||
_SA(self, "scripts", LazyLoadHandler(self, "scripts", ScriptHandler))
|
||||
_SA(self, "nicks", LazyLoadHandler(self, "nicks", NickHandler))
|
||||
#_SA(self, "attributes", LazyLoadHandler(self, "attributes", AttributeHandler))
|
||||
#_SA(self, "tags", LazyLoadHandler(self, "tags", TagHandler))
|
||||
#_SA(self, "aliases", LazyLoadHandler(self, "aliases", AliasHandler))
|
||||
# make sure to sync the contents cache when initializing
|
||||
#_GA(self, "contents_update")()
|
||||
# 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)
|
||||
|
||||
def _at_db_player_postsave(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -197,23 +197,31 @@ class PlayerDBAdmin(BaseUserAdmin):
|
|||
'description': "<i>These account details are shared by the admin "
|
||||
"system and the game.</i>"},),)
|
||||
|
||||
# TODO! Remove User reference!
|
||||
def save_formset(self, request, form, formset, change):
|
||||
"""
|
||||
Run all hooks on the player object
|
||||
"""
|
||||
super(PlayerDBAdmin, self).save_formset(request, form, formset, change)
|
||||
userobj = form.instance
|
||||
userobj.name = userobj.username
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.save()
|
||||
if not change:
|
||||
# uname, passwd, email = str(request.POST.get(u"username")), \
|
||||
# str(request.POST.get(u"password1")), \
|
||||
# str(request.POST.get(u"email"))
|
||||
typeclass = str(request.POST.get(
|
||||
u"playerdb_set-0-db_typeclass_path"))
|
||||
create.create_player("", "", "",
|
||||
user=userobj,
|
||||
typeclass=typeclass,
|
||||
player_dbobj=userobj)
|
||||
#calling hooks for new player
|
||||
ply = obj.typeclass
|
||||
ply.basetype_setup()
|
||||
ply.at_player_creation()
|
||||
|
||||
## TODO! Remove User reference!
|
||||
#def save_formset(self, request, form, formset, change):
|
||||
# """
|
||||
# Run all hooks on the player object
|
||||
# """
|
||||
# super(PlayerDBAdmin, self).save_formset(request, form, formset, change)
|
||||
# userobj = form.instance
|
||||
# userobj.name = userobj.username
|
||||
# if not change:
|
||||
# # uname, passwd, email = str(request.POST.get(u"username")), \
|
||||
# # str(request.POST.get(u"password1")), \
|
||||
# # str(request.POST.get(u"email"))
|
||||
# typeclass = str(request.POST.get(
|
||||
# u"playerdb_set-0-db_typeclass_path"))
|
||||
# create.create_player("", "", "",
|
||||
# user=userobj,
|
||||
# typeclass=typeclass,
|
||||
# player_dbobj=userobj)
|
||||
|
||||
admin.site.register(PlayerDB, PlayerDBAdmin)
|
||||
|
|
|
|||
|
|
@ -23,13 +23,12 @@ from django.utils.encoding import smart_str
|
|||
|
||||
from src.players import manager
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.typeclasses.models import (TypedObject, TagHandler, NickHandler,
|
||||
AliasHandler, AttributeHandler)
|
||||
from src.typeclasses.models import (TypedObject, NickHandler)
|
||||
from src.scripts.scripthandler import ScriptHandler
|
||||
from src.commands.cmdsethandler import CmdSetHandler
|
||||
from src.commands import cmdhandler
|
||||
from src.utils import utils, logger
|
||||
from src.utils.utils import to_str, make_iter, LazyLoadHandler
|
||||
from src.utils.utils import to_str, make_iter, lazy_property
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
|
@ -111,15 +110,19 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
app_label = 'players'
|
||||
verbose_name = 'Player'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Parent must be initiated first"
|
||||
TypedObject.__init__(self, *args, **kwargs)
|
||||
# handlers
|
||||
_SA(self, "cmdset", LazyLoadHandler(self, "cmdset", CmdSetHandler, True))
|
||||
_SA(self, "scripts", LazyLoadHandler(self, "scripts", ScriptHandler))
|
||||
_SA(self, "nicks", LazyLoadHandler(self, "nicks", NickHandler))
|
||||
#_SA(self, "tags", LazyLoadHandler(self, "tags", TagHandler))
|
||||
#_SA(self, "aliases", LazyLoadHandler(self, "aliases", AliasHandler))
|
||||
# lazy-loading of 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)
|
||||
|
||||
|
||||
# alias to the objs property
|
||||
def __characters_get(self):
|
||||
|
|
|
|||
|
|
@ -383,6 +383,16 @@ class Player(TypeClass):
|
|||
reason = reason and "(%s)" % reason or ""
|
||||
self._send_to_connect_channel("{R%s disconnected %s{n" % (self.key, reason))
|
||||
|
||||
def at_post_disconnect(self):
|
||||
"""
|
||||
This is called after disconnection is complete. No messages
|
||||
can be relayed to the player from here. After this call, the
|
||||
player should not be accessed any more, making this a good
|
||||
spot for deleting it (in the case of a guest player account,
|
||||
for example).
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_message_receive(self, message, from_obj=None):
|
||||
"""
|
||||
Called when any text is emitted to this
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ Common examples of uses of Scripts:
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from src.typeclasses.models import TypedObject, TagHandler, AttributeHandler
|
||||
from src.typeclasses.models import TypedObject
|
||||
from src.scripts.manager import ScriptManager
|
||||
from src.utils.utils import dbref, to_str, LazyLoadHandler
|
||||
from src.utils.utils import dbref, to_str
|
||||
|
||||
__all__ = ("ScriptDB",)
|
||||
_GA = object.__getattribute__
|
||||
|
|
@ -108,13 +108,6 @@ class ScriptDB(TypedObject):
|
|||
"Define Django meta options"
|
||||
verbose_name = "Script"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ScriptDB, self).__init__(*args, **kwargs)
|
||||
_SA(self, "attributes", LazyLoadHandler(self, "attributes", AttributeHandler))
|
||||
_SA(self, "tags", LazyLoadHandler(self, "tags", TagHandler))
|
||||
#_SA(self, "aliases", AliasHandler(self))
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
# ScriptDB class properties
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ from django.conf import settings
|
|||
#from src.scripts.models import ScriptDB
|
||||
from src.comms.models import ChannelDB
|
||||
from src.utils import logger, utils
|
||||
from src.utils.utils import make_iter, to_unicode, LazyLoadHandler
|
||||
from src.commands import cmdhandler, cmdsethandler
|
||||
from src.utils.utils import make_iter, to_unicode
|
||||
from src.commands.cmdhandler import cmdhandler
|
||||
from src.commands.cmdsethandler import CmdSetHandler
|
||||
from src.server.session import Session
|
||||
|
||||
IDLE_COMMAND = settings.IDLE_COMMAND
|
||||
|
|
@ -49,7 +50,7 @@ class ServerSession(Session):
|
|||
self.puppet = None
|
||||
self.player = None
|
||||
self.cmdset_storage_string = ""
|
||||
self.cmdset = LazyLoadHandler(self, "cmdset", cmdsethandler.CmdSetHandler, True)
|
||||
self.cmdset = CmdSetHandler(self, True)
|
||||
|
||||
def __cmdset_storage_get(self):
|
||||
return [path.strip() for path in self.cmdset_storage_string.split(',')]
|
||||
|
|
@ -103,7 +104,7 @@ class ServerSession(Session):
|
|||
self.player.save()
|
||||
|
||||
# add the session-level cmdset
|
||||
self.cmdset = LazyLoadHandler(self, "cmdset", cmdsethandler.CmdSetHandler, True)
|
||||
self.cmdset = CmdSetHandler(self, True)
|
||||
|
||||
def at_disconnect(self):
|
||||
"""
|
||||
|
|
@ -122,6 +123,8 @@ class ServerSession(Session):
|
|||
if not self.sessionhandler.sessions_from_player(player):
|
||||
# no more sessions connected to this player
|
||||
player.is_connected = False
|
||||
# this may be used to e.g. delete player after disconnection etc
|
||||
_GA(player.typeclass, "at_post_disconnect")()
|
||||
|
||||
def get_player(self):
|
||||
"""
|
||||
|
|
@ -198,7 +201,7 @@ class ServerSession(Session):
|
|||
else:
|
||||
text = self.player.nicks.nickreplace(text,
|
||||
categories=("inputline", "channels"), include_player=False)
|
||||
cmdhandler.cmdhandler(self, text, callertype="session", sessid=self.sessid)
|
||||
cmdhandler(self, text, callertype="session", sessid=self.sessid)
|
||||
self.update_session_counters()
|
||||
if "oob" in kwargs:
|
||||
# handle oob instructions
|
||||
|
|
|
|||
|
|
@ -514,7 +514,7 @@ STATICFILES_IGNORE_PATTERNS = ('README.md',)
|
|||
ACTIVE_TEMPLATE = 'prosimii'
|
||||
# We setup the location of the website template as well as the admin site.
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(GAME_DIR, "gamesrc", "web", "templates"),
|
||||
os.path.join(GAME_DIR, "gamesrc", "web", "template_overrides"),
|
||||
os.path.join(SRC_DIR, "web", "templates", ACTIVE_TEMPLATE),
|
||||
os.path.join(SRC_DIR, "web", "templates"),)
|
||||
# List of callables that know how to import templates from various sources.
|
||||
|
|
|
|||
|
|
@ -70,24 +70,24 @@ class AttributeManager(models.Manager):
|
|||
@_attr_pickled
|
||||
def get(self, *args, **kwargs):
|
||||
return super(AttributeManager, self).get(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
@_attr_pickled
|
||||
def filter(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).filter(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
@_attr_pickled
|
||||
def exclude(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).exclude(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
@_attr_pickled
|
||||
def values(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).values(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
@_attr_pickled
|
||||
def values_list(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).values_list(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
@_attr_pickled
|
||||
def exists(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).exists(*args, **kwargs)
|
||||
|
||||
|
|
@ -217,6 +217,56 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
|||
Common ObjectManager for all dbobjects.
|
||||
"""
|
||||
|
||||
# Attribute manager methods
|
||||
|
||||
# Tag manager methods
|
||||
|
||||
def get_tag(self, key=None, category=None, obj=None, tagtype=None):
|
||||
"""
|
||||
Return Tag objects by key, by category, by object or
|
||||
with a combination of those criteria.
|
||||
|
||||
tagtype - one of None (normal tags), "alias" or "permission"
|
||||
"""
|
||||
query = [("tag__db_tagtype", tagtype)]
|
||||
if obj:
|
||||
query.append(("%s__id" % self.model.__name__.lower(), obj.id))
|
||||
if key:
|
||||
query.append(("tag__db_key", key))
|
||||
if category:
|
||||
query.append(("tag__db_category", category))
|
||||
return self.model.db_tags.through.objects.filter(**dict(query))
|
||||
|
||||
def get_permission(self, key=None, category=None, obj=None):
|
||||
return self.get_tag(key=key, category=category, obj=obj, tagtype="permission")
|
||||
|
||||
def get_alias(self, key=None, category=None, obj=None):
|
||||
return self.get_tag(key=key, category=category, obj=obj, tagtype="alias")
|
||||
|
||||
@returns_typeclass
|
||||
def get_by_tag(self, key=None, category=None, tagtype=None):
|
||||
"""
|
||||
Return objects having tags with a given key or category or
|
||||
combination of the two.
|
||||
|
||||
tagtype = None, alias or permission
|
||||
"""
|
||||
query = [("db_tags__db_tagtype", tagtype)]
|
||||
if key:
|
||||
query.append(("db_tags__db_key", key))
|
||||
if category:
|
||||
query.append(("db_tags__db_category", category))
|
||||
return self.filter(**dict(query))
|
||||
|
||||
def get_by_permission(self, key=None, category=None):
|
||||
return self.get_by_tag(key=key, category=category, tagtype="permission")
|
||||
|
||||
def get_by_alias(self, key=None, category=None):
|
||||
return self.get_by_tag(key=key, category=category, tagtype="alias")
|
||||
|
||||
|
||||
# object-manager methods
|
||||
|
||||
def dbref(self, dbref, reqhash=True):
|
||||
"""
|
||||
Valid forms of dbref (database reference number)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Removing unique constraint on 'Tag', fields ['db_key', 'db_category']
|
||||
db.delete_unique(u'typeclasses_tag', ['db_key', 'db_category'])
|
||||
|
||||
# Adding unique constraint on 'Tag', fields ['db_key', 'db_category', 'db_tagtype']
|
||||
db.create_unique(u'typeclasses_tag', ['db_key', 'db_category', 'db_tagtype'])
|
||||
|
||||
# Removing index on 'Tag', fields ['db_key', 'db_category']
|
||||
db.delete_index(u'typeclasses_tag', ['db_key', 'db_category'])
|
||||
|
||||
# Adding index on 'Tag', fields ['db_key', 'db_category', 'db_tagtype']
|
||||
db.create_index(u'typeclasses_tag', ['db_key', 'db_category', 'db_tagtype'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing index on 'Tag', fields ['db_key', 'db_category', 'db_tagtype']
|
||||
db.delete_index(u'typeclasses_tag', ['db_key', 'db_category', 'db_tagtype'])
|
||||
|
||||
# Adding index on 'Tag', fields ['db_key', 'db_category']
|
||||
db.create_index(u'typeclasses_tag', ['db_key', 'db_category'])
|
||||
|
||||
# Removing unique constraint on 'Tag', fields ['db_key', 'db_category', 'db_tagtype']
|
||||
db.delete_unique(u'typeclasses_tag', ['db_key', 'db_category', 'db_tagtype'])
|
||||
|
||||
# Adding unique constraint on 'Tag', fields ['db_key', 'db_category']
|
||||
db.create_unique(u'typeclasses_tag', ['db_key', 'db_category'])
|
||||
|
||||
|
||||
models = {
|
||||
u'typeclasses.attribute': {
|
||||
'Meta': {'object_name': 'Attribute'},
|
||||
'db_attrtype': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'null': 'True', 'blank': 'True'}),
|
||||
'db_category': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
|
||||
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'db_model': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '32', 'null': 'True', 'blank': 'True'}),
|
||||
'db_strvalue': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'db_value': ('src.utils.picklefield.PickledObjectField', [], {'null': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
u'typeclasses.tag': {
|
||||
'Meta': {'unique_together': "(('db_key', 'db_category', 'db_tagtype'),)", 'object_name': 'Tag', 'index_together': "(('db_key', 'db_category', 'db_tagtype'),)"},
|
||||
'db_category': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'db_index': 'True'}),
|
||||
'db_data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_index': 'True'}),
|
||||
'db_model': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'db_index': 'True'}),
|
||||
'db_tagtype': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'db_index': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['typeclasses']
|
||||
|
|
@ -34,11 +34,9 @@ import weakref
|
|||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.utils.encoding import smart_str
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from src.utils.idmapper.models import SharedMemoryModel, WeakSharedMemoryModel
|
||||
from src.utils.idmapper.models import SharedMemoryModel
|
||||
from src.server.caches import get_prop_cache, set_prop_cache
|
||||
#from src.server.caches import set_attr_cache
|
||||
|
||||
|
|
@ -48,7 +46,7 @@ from src.typeclasses import managers
|
|||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils import logger
|
||||
from src.utils.utils import (
|
||||
make_iter, is_iter, to_str, inherits_from, LazyLoadHandler)
|
||||
make_iter, is_iter, to_str, inherits_from, lazy_property)
|
||||
from src.utils.dbserialize import to_pickle, from_pickle
|
||||
from src.utils.picklefield import PickledObjectField
|
||||
|
||||
|
|
@ -59,7 +57,6 @@ TICKER_HANDLER = None
|
|||
_PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
|
||||
_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
|
||||
|
||||
_CTYPEGET = ContentType.objects.get
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
_DA = object.__delattr__
|
||||
|
|
@ -132,12 +129,9 @@ class Attribute(SharedMemoryModel):
|
|||
# Database manager
|
||||
objects = managers.AttributeManager()
|
||||
|
||||
# Lock handler self.locks
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Initializes the parent first -important!"
|
||||
#SharedMemoryModel.__init__(self, *args, **kwargs)
|
||||
super(Attribute, self).__init__(*args, **kwargs)
|
||||
self.locks = LazyLoadHandler(self, "locks", LockHandler)
|
||||
@lazy_property
|
||||
def locks(self):
|
||||
return LockHandler(self)
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
|
|
@ -237,19 +231,18 @@ class AttributeHandler(object):
|
|||
def __init__(self, obj):
|
||||
"Initialize handler"
|
||||
self.obj = obj
|
||||
self._model = "%s.%s" % ContentType.objects.get_for_model(obj).natural_key()
|
||||
self._objid = obj.id
|
||||
self._model = to_str(obj.__class__.__name__.lower())
|
||||
self._cache = None
|
||||
|
||||
def _recache(self):
|
||||
if not self._attrtype:
|
||||
attrtype = Q(db_attrtype=None) | Q(db_attrtype='')
|
||||
else:
|
||||
attrtype = Q(db_attrtype=self._attrtype)
|
||||
"Cache all attributes of this object"
|
||||
query = {"%s__id" % self._model : self._objid,
|
||||
"attribute__db_attrtype" : self._attrtype}
|
||||
attrs = [conn.attribute for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
|
||||
self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(),
|
||||
attr.db_category.lower() if attr.db_category else None), attr)
|
||||
for attr in getattr(self.obj, self._m2m_fieldname).filter(
|
||||
db_model=self._model).filter(attrtype))
|
||||
#set_attr_cache(self.obj, self._cache) # currently only for testing
|
||||
attr.db_category.lower() if conn.attribute.db_category else None),
|
||||
attr) for attr in attrs)
|
||||
|
||||
def has(self, key, category=None):
|
||||
"""
|
||||
|
|
@ -321,6 +314,7 @@ class AttributeHandler(object):
|
|||
return ret if len(key) > 1 else default
|
||||
return ret[0] if len(ret)==1 else ret
|
||||
|
||||
|
||||
def add(self, key, value, category=None, lockstring="",
|
||||
strattr=False, accessing_obj=None, default_access=True):
|
||||
"""
|
||||
|
|
@ -341,28 +335,87 @@ class AttributeHandler(object):
|
|||
self._recache()
|
||||
if not key:
|
||||
return
|
||||
key = key.strip().lower()
|
||||
|
||||
category = category.strip().lower() if category is not None else None
|
||||
cachekey = "%s-%s" % (key, category)
|
||||
keystr = key.strip().lower()
|
||||
cachekey = "%s-%s" % (keystr, category)
|
||||
attr_obj = self._cache.get(cachekey)
|
||||
if not attr_obj:
|
||||
# no old attr available; create new.
|
||||
attr_obj = Attribute(db_key=key, db_category=category,
|
||||
db_model=self._model, db_attrtype=self._attrtype)
|
||||
attr_obj.save() # important
|
||||
getattr(self.obj, self._m2m_fieldname).add(attr_obj)
|
||||
self._cache[cachekey] = attr_obj
|
||||
if lockstring:
|
||||
attr_obj.locks.add(lockstring)
|
||||
# we shouldn't need to fear stale objects, the field signalling
|
||||
# should catch all cases
|
||||
if strattr:
|
||||
# store as a simple string
|
||||
attr_obj.db_strvalue = value
|
||||
attr_obj.save(update_fields=["db_strvalue"])
|
||||
|
||||
if attr_obj:
|
||||
# update an existing attribute object
|
||||
if strattr:
|
||||
# store as a simple string (will not notify OOB handlers)
|
||||
attr_obj.db_strvalue = value
|
||||
attr_obj.save(update_fields=["db_strvalue"])
|
||||
else:
|
||||
# store normally (this will also notify OOB handlers)
|
||||
attr_obj.value = value
|
||||
else:
|
||||
# pickle arbitrary data
|
||||
attr_obj.value = value
|
||||
# create a new Attribute (no OOB handlers can be notified)
|
||||
kwargs = {"db_key" : keystr, "db_category" : category,
|
||||
"db_model" : self._model, "db_attrtype" : self._attrtype,
|
||||
"db_value" : None if strattr else to_pickle(value),
|
||||
"db_strvalue" : value if strattr else None}
|
||||
new_attr = Attribute(**kwargs)
|
||||
new_attr.save()
|
||||
getattr(self.obj, self._m2m_fieldname).add(new_attr)
|
||||
self._cache[cachekey] = new_attr
|
||||
|
||||
|
||||
def batch_add(self, key, value, category=None, lockstring="",
|
||||
strattr=False, accessing_obj=None, default_access=True):
|
||||
"""
|
||||
Batch-version of add(). This is more efficient than
|
||||
repeat-calling add.
|
||||
|
||||
key and value must be sequences of the same length, each
|
||||
representing a key-value pair.
|
||||
|
||||
"""
|
||||
if accessing_obj and not self.obj.access(accessing_obj,
|
||||
self._attrcreate, default=default_access):
|
||||
# check create access
|
||||
return
|
||||
if self._cache is None:
|
||||
self._recache()
|
||||
if not key:
|
||||
return
|
||||
|
||||
keys, values= make_iter(key), make_iter(value)
|
||||
|
||||
if len(keys) != len(values):
|
||||
raise RuntimeError("AttributeHandler.add(): key and value of different length: %s vs %s" % key, value)
|
||||
category = category.strip().lower() if category is not None else None
|
||||
new_attrobjs = []
|
||||
for ikey, keystr in enumerate(keys):
|
||||
keystr = keystr.strip().lower()
|
||||
new_value = values[ikey]
|
||||
cachekey = "%s-%s" % (keystr, category)
|
||||
attr_obj = self._cache.get(cachekey)
|
||||
|
||||
if attr_obj:
|
||||
# update an existing attribute object
|
||||
if strattr:
|
||||
# store as a simple string (will not notify OOB handlers)
|
||||
attr_obj.db_strvalue = new_value
|
||||
attr_obj.save(update_fields=["db_strvalue"])
|
||||
else:
|
||||
# store normally (this will also notify OOB handlers)
|
||||
attr_obj.value = new_value
|
||||
else:
|
||||
# create a new Attribute (no OOB handlers can be notified)
|
||||
kwargs = {"db_key" : keystr, "db_category" : category,
|
||||
"db_attrtype" : self._attrtype,
|
||||
"db_value" : None if strattr else to_pickle(new_value),
|
||||
"db_strvalue" : value if strattr else None}
|
||||
new_attr = Attribute(**kwargs)
|
||||
new_attr.save()
|
||||
new_attrobjs.append(new_attr)
|
||||
if new_attrobjs:
|
||||
# Add new objects to m2m field all at once
|
||||
getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs)
|
||||
self._recache()
|
||||
|
||||
|
||||
def remove(self, key, raise_exception=False, category=None,
|
||||
accessing_obj=None, default_access=True):
|
||||
|
|
@ -416,6 +469,7 @@ class AttributeHandler(object):
|
|||
else:
|
||||
return self._cache.values()
|
||||
|
||||
|
||||
class NickHandler(AttributeHandler):
|
||||
"""
|
||||
Handles the addition and removal of Nicks
|
||||
|
|
@ -539,8 +593,8 @@ class Tag(models.Model):
|
|||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Tag"
|
||||
unique_together = (('db_key', 'db_category'),)
|
||||
index_together = (('db_key', 'db_category'),)
|
||||
unique_together = (('db_key', 'db_category', 'db_tagtype'),)
|
||||
index_together = (('db_key', 'db_category', 'db_tagtype'),)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s" % self.db_key
|
||||
|
|
@ -567,22 +621,23 @@ class TagHandler(object):
|
|||
and with a tagtype given by self.handlertype
|
||||
"""
|
||||
self.obj = obj
|
||||
self._model = "%s.%s" % ContentType.objects.get_for_model(obj).natural_key()
|
||||
self._objid = obj.id
|
||||
self._model = obj.__class__.__name__.lower()
|
||||
self._cache = None
|
||||
|
||||
def _recache(self):
|
||||
"Update cache from database field"
|
||||
if not self._tagtype:
|
||||
tagtype = Q(db_tagtype='') | Q(db_tagtype__isnull=True)
|
||||
else:
|
||||
tagtype = Q(db_tagtype=self._tagtype)
|
||||
self._cache = dict(("%s-%s" % (tag.db_key, tag.db_category), tag)
|
||||
for tag in getattr(
|
||||
self.obj, self._m2m_fieldname).filter(
|
||||
db_model=self._model).filter(tagtype))
|
||||
"Cache all tags of this object"
|
||||
query = {"%s__id" % self._model : self._objid,
|
||||
"tag__db_tagtype" : self._tagtype}
|
||||
tagobjs = [conn.tag for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
|
||||
self._cache = dict(("%s-%s" % (to_str(tagobj.db_key).lower(),
|
||||
tagobj.db_category.lower() if tagobj.db_category else None),
|
||||
tagobj) for tagobj in tagobjs)
|
||||
|
||||
def add(self, tag, category=None, data=None):
|
||||
def add(self, tag=None, category=None, data=None):
|
||||
"Add a new tag to the handler. Tag is a string or a list of strings."
|
||||
if not tag:
|
||||
return
|
||||
for tagstr in make_iter(tag):
|
||||
if not tagstr:
|
||||
continue
|
||||
|
|
@ -593,7 +648,7 @@ class TagHandler(object):
|
|||
# will overload data on an existing tag since that is not
|
||||
# considered part of making the tag unique)
|
||||
tagobj = Tag.objects.create_tag(key=tagstr, category=category, data=data,
|
||||
model=self._model, tagtype=self._tagtype)
|
||||
tagtype=self._tagtype)
|
||||
getattr(self.obj, self._m2m_fieldname).add(tagobj)
|
||||
if self._cache is None:
|
||||
self._recache()
|
||||
|
|
@ -646,11 +701,10 @@ class TagHandler(object):
|
|||
self._recache()
|
||||
if category:
|
||||
category = category.strip().lower() if category is not None else None
|
||||
matches = getattr(self.obj, self._m2m_fieldname).filter(db_category=category,
|
||||
db_tagtype=self._tagtype,
|
||||
db_model=self._model)
|
||||
matches = [tag for tag in self._cache.values() if tag.db_category == category]
|
||||
else:
|
||||
matches = self._cache.values()
|
||||
|
||||
if matches:
|
||||
if return_key_and_category:
|
||||
# return tuple (key, category)
|
||||
|
|
@ -741,15 +795,33 @@ class TypedObject(SharedMemoryModel):
|
|||
def __init__(self, *args, **kwargs):
|
||||
"We must initialize the parent first - important!"
|
||||
super(TypedObject, self).__init__(*args, **kwargs)
|
||||
#SharedMemoryModel.__init__(self, *args, **kwargs)
|
||||
_SA(self, "dbobj", self) # this allows for self-reference
|
||||
_SA(self, "locks", LazyLoadHandler(self, "locks", LockHandler))
|
||||
_SA(self, "tags", LazyLoadHandler(self, "tags", TagHandler))
|
||||
_SA(self, "aliases", LazyLoadHandler(self, "aliases", AliasHandler))
|
||||
_SA(self, "permissions", LazyLoadHandler(self, "permissions", PermissionHandler))
|
||||
_SA(self, "attributes", LazyLoadHandler(self, "attributes", AttributeHandler))
|
||||
_SA(self, "nattributes", NAttributeHandler(self))
|
||||
#_SA(self, "nattributes", LazyLoadHandler(self, "nattributes", NAttributeHandler))
|
||||
|
||||
# initialize all handlers in a lazy fashion
|
||||
@lazy_property
|
||||
def attributes(self):
|
||||
return AttributeHandler(self)
|
||||
|
||||
@lazy_property
|
||||
def locks(self):
|
||||
return LockHandler(self)
|
||||
|
||||
@lazy_property
|
||||
def tags(self):
|
||||
return TagHandler(self)
|
||||
|
||||
@lazy_property
|
||||
def aliases(self):
|
||||
return AliasHandler(self)
|
||||
|
||||
@lazy_property
|
||||
def permissions(self):
|
||||
return PermissionHandler(self)
|
||||
|
||||
@lazy_property
|
||||
def nattributes(self):
|
||||
return NAttributeHandler(self)
|
||||
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
|
|
@ -1216,13 +1288,10 @@ class TypedObject(SharedMemoryModel):
|
|||
if not TICKER_HANDLER:
|
||||
from src.scripts.tickerhandler import TICKER_HANDLER
|
||||
TICKER_HANDLER.remove(self) # removes objects' all ticker subscriptions
|
||||
if not isinstance(_GA(self, "permissions"), LazyLoadHandler):
|
||||
_GA(self, "permissions").clear()
|
||||
if not isinstance(_GA(self, "attributes"), LazyLoadHandler):
|
||||
_GA(self, "attributes").clear()
|
||||
if not isinstance(_GA(self, "aliases"), LazyLoadHandler):
|
||||
_GA(self, "aliases").clear()
|
||||
if hasattr(self, "nicks") and not isinstance(_GA(self, "nicks"), LazyLoadHandler):
|
||||
_GA(self, "permissions").clear()
|
||||
_GA(self, "attributes").clear()
|
||||
_GA(self, "aliases").clear()
|
||||
if hasattr(self, "nicks"):
|
||||
_GA(self, "nicks").clear()
|
||||
_SA(self, "_cached_typeclass", None)
|
||||
_GA(self, "flush_from_cache")()
|
||||
|
|
|
|||
|
|
@ -59,7 +59,12 @@ def handle_dbref(inp, objclass, raise_errors=True):
|
|||
objects.
|
||||
"""
|
||||
if not (isinstance(inp, basestring) and inp.startswith("#")):
|
||||
return inp
|
||||
try:
|
||||
return inp.dbobj
|
||||
except AttributeError:
|
||||
return inp
|
||||
|
||||
# a string, analyze it
|
||||
inp = inp.lstrip('#')
|
||||
try:
|
||||
if int(inp) < 0:
|
||||
|
|
@ -67,7 +72,7 @@ def handle_dbref(inp, objclass, raise_errors=True):
|
|||
except ValueError:
|
||||
return None
|
||||
|
||||
# if we get to this point, inp is an integer dbref
|
||||
# if we get to this point, inp is an integer dbref; get the matching object
|
||||
try:
|
||||
return objclass.objects.get(id=inp)
|
||||
except Exception:
|
||||
|
|
@ -98,7 +103,7 @@ def create_object(typeclass=None, key=None, location=None,
|
|||
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;
|
||||
this only used when creating default location itself or during unittests
|
||||
this only used when creating the default location itself or during unittests
|
||||
"""
|
||||
global _Object, _ObjectDB
|
||||
if not _Object:
|
||||
|
|
@ -116,28 +121,30 @@ def create_object(typeclass=None, key=None, location=None,
|
|||
elif isinstance(typeclass, _Object) or utils.inherits_from(typeclass, _Object):
|
||||
# this is already an object typeclass, extract its path
|
||||
typeclass = typeclass.path
|
||||
|
||||
# handle eventual #dbref input
|
||||
location = handle_dbref(location, _ObjectDB)
|
||||
home = handle_dbref(home, _ObjectDB)
|
||||
destination = handle_dbref(destination, _ObjectDB)
|
||||
report_to = handle_dbref(report_to, _ObjectDB)
|
||||
|
||||
# create new database object
|
||||
new_db_object = _ObjectDB()
|
||||
|
||||
# assign the typeclass
|
||||
typeclass = utils.to_unicode(typeclass)
|
||||
new_db_object.typeclass_path = typeclass
|
||||
|
||||
# the name/key is often set later in the typeclass. This
|
||||
# is set here as a failsafe.
|
||||
if key:
|
||||
new_db_object.key = key
|
||||
else:
|
||||
# Setup input for the create command
|
||||
|
||||
location = handle_dbref(location, _ObjectDB)
|
||||
destination = handle_dbref(destination, _ObjectDB)
|
||||
home = handle_dbref(home, _ObjectDB)
|
||||
if not home:
|
||||
try:
|
||||
home = handle_dbref(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None
|
||||
except _ObjectDB.DoesNotExist:
|
||||
raise _ObjectDB.DoesNotExist("settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." %
|
||||
settings.DEFAULT_HOME)
|
||||
|
||||
# create new database object all in one go
|
||||
new_db_object = _ObjectDB(db_key=key, db_location=location,
|
||||
db_destination=destination, db_home=home,
|
||||
db_typeclass_path=typeclass)
|
||||
|
||||
if not key:
|
||||
# the object should always have a key, so if not set we give a default
|
||||
new_db_object.key = "#%i" % new_db_object.dbid
|
||||
|
||||
# this will either load the typeclass or the default one
|
||||
# this will either load the typeclass or the default one (will also save object)
|
||||
new_object = new_db_object.typeclass
|
||||
|
||||
if not _GA(new_object, "is_typeclass")(typeclass, exact=True):
|
||||
|
|
@ -145,6 +152,7 @@ def create_object(typeclass=None, key=None, location=None,
|
|||
# gave us a default
|
||||
SharedMemoryModel.delete(new_db_object)
|
||||
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
|
||||
|
|
@ -154,15 +162,26 @@ def create_object(typeclass=None, key=None, location=None,
|
|||
# from now on we can use the typeclass object
|
||||
# as if it was the database object.
|
||||
|
||||
new_object.destination = destination
|
||||
|
||||
# call the hook method. This is where all at_creation
|
||||
# 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()
|
||||
|
||||
# custom-given perms/locks overwrite hooks
|
||||
# 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:
|
||||
|
|
@ -170,28 +189,14 @@ def create_object(typeclass=None, key=None, location=None,
|
|||
if aliases:
|
||||
new_object.aliases.add(aliases)
|
||||
|
||||
if home:
|
||||
new_object.home = home
|
||||
else:
|
||||
# we shouldn't need to handle dbref here (home handler should fix it), but some have
|
||||
# reported issues here (issue 446).
|
||||
try:
|
||||
new_object.home = handle_dbref(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None
|
||||
except _ObjectDB.DoesNotExist:
|
||||
raise _ObjectDB.DoesNotExist("settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." %
|
||||
settings.DEFAULT_HOME)
|
||||
|
||||
# perform a move_to in order to display eventual messages.
|
||||
# trigger relevant move_to hooks in order to display messages.
|
||||
if location:
|
||||
new_object.move_to(location, quiet=True)
|
||||
else:
|
||||
# rooms would have location=None.
|
||||
new_object.location = None
|
||||
new_object.at_object_receive(new_object, None)
|
||||
new_object.at_after_move(new_object)
|
||||
|
||||
# post-hook setup (mainly used by Exits)
|
||||
new_object.basetype_posthook_setup()
|
||||
|
||||
new_object.save()
|
||||
return new_object
|
||||
|
||||
#alias for create_object
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ from django.forms import CharField, Textarea
|
|||
from django.forms.util import flatatt
|
||||
from django.utils.html import format_html
|
||||
|
||||
from src.utils.dbserialize import from_pickle, to_pickle
|
||||
|
||||
try:
|
||||
from django.utils.encoding import force_text
|
||||
except ImportError:
|
||||
|
|
|
|||
253
src/utils/spawner.py
Normal file
253
src/utils/spawner.py
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
"""
|
||||
Spawner
|
||||
|
||||
The spawner takes input files containing object definitions in
|
||||
dictionary forms. These use a prototype architechture to define
|
||||
unique objects without having to make a Typeclass for each.
|
||||
|
||||
The main function is spawn(*prototype), where the prototype
|
||||
is a dictionary like this:
|
||||
|
||||
GOBLIN = {
|
||||
"typeclass": "game.gamesrc.objects.objects.Monster",
|
||||
"key": "goblin grunt",
|
||||
"health": lambda: randint(20,30),
|
||||
"resists": ["cold", "poison"],
|
||||
"attacks": ["fists"],
|
||||
"weaknesses": ["fire", "light"]
|
||||
}
|
||||
|
||||
Possible keywords are:
|
||||
prototype - string parent prototype
|
||||
key - string, the main object identifier
|
||||
typeclass - string, if not set, will use settings.BASE_OBJECT_TYPECLASS
|
||||
location - this should be a valid object or #dbref
|
||||
home - valid object or #dbref
|
||||
destination - only valid for exits (object or dbref)
|
||||
|
||||
permissions - string or list of permission strings
|
||||
locks - a lock-string
|
||||
aliases - string or list of strings
|
||||
|
||||
ndb_<name> - value of a nattribute (ndb_ is stripped)
|
||||
any other keywords are interpreted as Attributes and their values.
|
||||
|
||||
Each value can also be a callable that takes no arguments. It should
|
||||
return the value to enter into the field and will be called every time
|
||||
the prototype is used to spawn an object.
|
||||
|
||||
By specifying a prototype, the child will inherit all prototype slots
|
||||
it does not explicitly define itself, while overloading those that it
|
||||
does specify.
|
||||
|
||||
GOBLIN_WIZARD = {
|
||||
"prototype": GOBLIN,
|
||||
"key": "goblin wizard",
|
||||
"spells": ["fire ball", "lighting bolt"]
|
||||
}
|
||||
|
||||
GOBLIN_ARCHER = {
|
||||
"prototype": GOBLIN,
|
||||
"key": "goblin archer",
|
||||
"attacks": ["short bow"]
|
||||
}
|
||||
|
||||
One can also have multiple prototypes. These are inherited from the
|
||||
left, with the ones further to the right taking precedence.
|
||||
|
||||
ARCHWIZARD = {
|
||||
"attack": ["archwizard staff", "eye of doom"]
|
||||
|
||||
GOBLIN_ARCHWIZARD = {
|
||||
"key" : "goblin archwizard"
|
||||
"prototype": (GOBLIN_WIZARD, ARCHWIZARD),
|
||||
}
|
||||
|
||||
The goblin archwizard will have some different attacks, but will
|
||||
otherwise have the same spells as a goblin wizard who in turn shares
|
||||
many traits with a normal goblin.
|
||||
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
|
||||
from django.conf import settings
|
||||
from random import randint
|
||||
from src.objects.models import ObjectDB
|
||||
from src.utils.create import handle_dbref
|
||||
from src.utils.utils import make_iter, all_from_module
|
||||
|
||||
_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
|
||||
|
||||
_handle_dbref = lambda inp: handle_dbref(inp, ObjectDB)
|
||||
|
||||
|
||||
def _get_prototype(dic, prot, protparents, visited):
|
||||
"""
|
||||
Recursively traverse a prototype dictionary,
|
||||
including multiple inheritance and self-reference
|
||||
detection
|
||||
"""
|
||||
visited.append(id(dic))
|
||||
if "prototype" in dic:
|
||||
# move backwards through the inheritance
|
||||
for prototype in make_iter(dic["prototype"]):
|
||||
if id(prototype) in visited:
|
||||
# a loop was detected. Don't self-reference.
|
||||
continue
|
||||
# Build the prot dictionary in reverse order, overloading
|
||||
new_prot = _get_prototype(protparents.get(prototype, {}), prot, protparents, visited)
|
||||
prot.update(new_prot)
|
||||
prot.update(dic)
|
||||
prot.pop("prototype", None) # we don't need this anymore
|
||||
return prot
|
||||
|
||||
|
||||
def _batch_create_object(*objparams):
|
||||
"""
|
||||
This is a cut-down version of the create_object() function,
|
||||
optimized for speed. It does NOT check and convert various input
|
||||
so make sure the spawned Typeclass works before using this!
|
||||
|
||||
Input:
|
||||
objsparams - each argument should be a tuple of arguments for the respective
|
||||
creation/add handlers in the following order:
|
||||
(create, permissions, locks, aliases, nattributes, attributes)
|
||||
Returns:
|
||||
A list of created objects
|
||||
"""
|
||||
|
||||
# bulk create all objects in one go
|
||||
dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams]
|
||||
# unfortunately this doesn't work since bulk_create don't creates pks;
|
||||
# the result are double objects at the next stage
|
||||
#dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
|
||||
|
||||
objs = []
|
||||
for iobj, dbobj in enumerate(dbobjs):
|
||||
# call all setup hooks on each object
|
||||
objparam = objparams[iobj]
|
||||
obj = dbobj.typeclass # this saves dbobj if not done already
|
||||
obj.basetype_setup()
|
||||
obj.at_object_creation()
|
||||
|
||||
if objparam[1]:
|
||||
# permissions
|
||||
obj.permissions.add(objparam[1])
|
||||
if objparam[2]:
|
||||
# locks
|
||||
obj.locks.add(objparam[2])
|
||||
if objparam[3]:
|
||||
# aliases
|
||||
obj.aliases.add(objparam[3])
|
||||
if objparam[4]:
|
||||
# nattributes
|
||||
for key, value in objparam[4].items():
|
||||
obj.nattributes.add(key, value)
|
||||
if objparam[5]:
|
||||
# attributes
|
||||
keys, values = objparam[5].keys(), objparam[5].values()
|
||||
obj.attributes.batch_add(keys, values)
|
||||
|
||||
obj.basetype_posthook_setup()
|
||||
objs.append(obj)
|
||||
return objs
|
||||
|
||||
|
||||
def spawn(*prototypes, **kwargs):
|
||||
"""
|
||||
Spawn a number of prototyped objects. Each argument should be a
|
||||
prototype dictionary.
|
||||
|
||||
keyword args:
|
||||
prototype_modules - a python-path to a
|
||||
prototype module, or a list of such paths. These will be used
|
||||
to build the global protparents dictionary accessible by the
|
||||
input prototypes. If not given, it will instead look for modules
|
||||
defined by settings.PROTOTYPE_MODULES.
|
||||
return_prototypes - only return a list of the prototype-parents
|
||||
(no object creation happens)
|
||||
"""
|
||||
|
||||
protparents = {}
|
||||
protmodules = make_iter(kwargs.get("prototype_modules", []))
|
||||
if not protmodules and hasattr(settings, "PROTOTYPE_MODULES"):
|
||||
protmodules = make_iter(settings.PROTOTYPE_MODULES)
|
||||
for prototype_module in protmodules:
|
||||
protparents.update(dict((key, val)
|
||||
for key, val in all_from_module(prototype_module).items() if isinstance(val, dict)))
|
||||
|
||||
if "return_prototypes" in kwargs:
|
||||
# only return the parents
|
||||
return protparents
|
||||
|
||||
objsparams = []
|
||||
for prototype in prototypes:
|
||||
|
||||
prot = _get_prototype(prototype, {}, protparents, [])
|
||||
if not prot:
|
||||
continue
|
||||
|
||||
# extract the keyword args we need to create the object itself
|
||||
create_kwargs = {}
|
||||
create_kwargs["db_key"] = prot.pop("key", "Spawned Object %06i" % randint(1,100000))
|
||||
create_kwargs["db_location"] = _handle_dbref(prot.pop("location", None))
|
||||
create_kwargs["db_home"] = _handle_dbref(prot.pop("home", settings.DEFAULT_HOME))
|
||||
create_kwargs["db_destination"] = _handle_dbref(prot.pop("destination", None))
|
||||
create_kwargs["db_typeclass_path"] = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
||||
|
||||
# extract calls to handlers
|
||||
permission_string = prot.pop("permissions", "")
|
||||
lock_string = prot.pop("locks", "")
|
||||
alias_string = prot.pop("aliases", "")
|
||||
|
||||
# extract ndb assignments
|
||||
nattributes = dict((key.split("_", 1)[1], value if callable(value) else value)
|
||||
for key, value in prot.items() if key.startswith("ndb_"))
|
||||
|
||||
# the rest are attributes
|
||||
attributes = dict((key, value() if callable(value) else value)
|
||||
for key, value in prot.items()
|
||||
if not (key in _CREATE_OBJECT_KWARGS or key in nattributes))
|
||||
|
||||
# pack for call into _batch_create_object
|
||||
objsparams.append( (create_kwargs, permission_string, lock_string,
|
||||
alias_string, nattributes, attributes) )
|
||||
|
||||
return _batch_create_object(*objsparams)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# testing
|
||||
|
||||
protparents = {
|
||||
"NOBODY": {},
|
||||
"GOBLIN" : {
|
||||
"key": "goblin grunt",
|
||||
"health": lambda: randint(20,30),
|
||||
"resists": ["cold", "poison"],
|
||||
"attacks": ["fists"],
|
||||
"weaknesses": ["fire", "light"]
|
||||
},
|
||||
"GOBLIN_WIZARD" : {
|
||||
"prototype": "GOBLIN",
|
||||
"key": "goblin wizard",
|
||||
"spells": ["fire ball", "lighting bolt"]
|
||||
},
|
||||
"GOBLIN_ARCHER" : {
|
||||
"prototype": "GOBLIN",
|
||||
"key": "goblin archer",
|
||||
"attacks": ["short bow"]
|
||||
},
|
||||
"ARCHWIZARD" : {
|
||||
"attacks": ["archwizard staff"],
|
||||
},
|
||||
"GOBLIN_ARCHWIZARD" : {
|
||||
"key": "goblin archwizard",
|
||||
"prototype" : ("GOBLIN_WIZARD", "ARCHWIZARD")
|
||||
}
|
||||
}
|
||||
# test
|
||||
print [o.key for o in spawn(protparents["GOBLIN"], protparents["GOBLIN_ARCHWIZARD"], prototype_parents=protparents)]
|
||||
|
|
@ -792,6 +792,8 @@ def all_from_module(module):
|
|||
Return all global-level variables from a module as a dict
|
||||
"""
|
||||
mod = mod_import(module)
|
||||
if not mod:
|
||||
return {}
|
||||
return dict((key, val) for key, val in mod.__dict__.items()
|
||||
if not (key.startswith("_") or ismodule(val)))
|
||||
|
||||
|
|
@ -1058,67 +1060,38 @@ def deepsize(obj, max_depth=4):
|
|||
size = getsizeof(obj) + sum([p[1] for p in sizedict.values()])
|
||||
return size
|
||||
|
||||
# lazy load handlers
|
||||
|
||||
import weakref
|
||||
class LazyLoadHandler(object):
|
||||
# lazy load handler
|
||||
_missing = object()
|
||||
class lazy_property(object):
|
||||
"""
|
||||
Load handlers only when they are actually accessed
|
||||
Delays loading of property until first access. Credit goes to
|
||||
the Implementation in the werkzeug suite:
|
||||
http://werkzeug.pocoo.org/docs/utils/#werkzeug.utils.cached_property
|
||||
|
||||
This should be used as a decorator in a class and is in Evennia
|
||||
mainly used to lazy-load handlers:
|
||||
|
||||
@lazy_property
|
||||
def attributes(self):
|
||||
return AttributeHandler(self)
|
||||
|
||||
Once initialized, the AttributeHandler will be available
|
||||
as a property "attributes" on the object.
|
||||
|
||||
"""
|
||||
def __init__(self, obj, name, cls, *args):
|
||||
"""
|
||||
Set up a delayed load of a class. The 'name' must be named the
|
||||
same as the variable to which the LazyLoadHandler is assigned.
|
||||
"""
|
||||
_SA(self, "obj", weakref.ref(obj))
|
||||
_SA(self, "name", name)
|
||||
_SA(self, "cls", cls)
|
||||
_SA(self, "args", args)
|
||||
def __init__(self, func, name=None, doc=None):
|
||||
"Store all properties for now"
|
||||
self.__name__ = name or func.__name__
|
||||
self.__module__ = func.__module__
|
||||
self.__doc__ = doc or func.__doc__
|
||||
self.func = func
|
||||
|
||||
def _instantiate(self):
|
||||
"""
|
||||
Initialize handler as cls(obj, *args)
|
||||
"""
|
||||
obj = _GA(self, "obj")()
|
||||
instance = _GA(self, "cls")(weakref.proxy(obj), *_GA(self, "args"))
|
||||
_SA(obj, _GA(self, "name"), instance)
|
||||
return instance
|
||||
|
||||
def __getattribute__(self, name):
|
||||
"""
|
||||
Access means loading the handler
|
||||
"""
|
||||
return getattr(_GA(self, "_instantiate")(), name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""
|
||||
Setting means loading the handler
|
||||
"""
|
||||
setattr(_GA(self, "_instantiate")(), name, value)
|
||||
|
||||
def __delattr__(self, name):
|
||||
"""
|
||||
Deleting also triggers loading of handler
|
||||
"""
|
||||
delattr(_GA(self, "_instantiate")(), name)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(_GA(self, "_instantiate")())
|
||||
def __str__(self):
|
||||
return str(_GA(self, "_instantiate")())
|
||||
def __unicode__(self):
|
||||
return str(_GA(self, "_instantiate")())
|
||||
|
||||
class NonWeakLazyLoadHandler(LazyLoadHandler):
|
||||
"""
|
||||
Variation of LazyLoadHandler that does not
|
||||
create a weak reference when initiating.
|
||||
"""
|
||||
def _instantiate(self):
|
||||
"""
|
||||
Initialize handler as cls(obj, *args)
|
||||
"""
|
||||
obj = _GA(self, "obj")()
|
||||
instance = _GA(self, "cls")(obj, *_GA(self, "args"))
|
||||
_SA(obj, _GA(self, "name"), instance)
|
||||
return instance
|
||||
def __get__(self, obj, type=None):
|
||||
"Triggers initialization"
|
||||
if obj is None:
|
||||
return self
|
||||
value = obj.__dict__.get(self.__name__, _missing)
|
||||
if value is _missing:
|
||||
value = self.func(obj)
|
||||
obj.__dict__[self.__name__] = value
|
||||
return value
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ def page_index(request):
|
|||
# Some misc. configurable stuff.
|
||||
# TODO: Move this to either SQL or settings.py based configuration.
|
||||
fpage_player_limit = 4
|
||||
fpage_news_entries = 2
|
||||
|
||||
# A QuerySet of the most recently connected players.
|
||||
recent_users = PlayerDB.objects.get_recently_connected_players()[:fpage_player_limit]
|
||||
|
|
@ -52,7 +51,7 @@ def page_index(request):
|
|||
"num_others": nothers or "no"
|
||||
}
|
||||
|
||||
return render(request, 'index.html', pagevars)
|
||||
return render(request, 'evennia_general/index.html', pagevars)
|
||||
|
||||
|
||||
def to_be_implemented(request):
|
||||
|
|
@ -65,7 +64,7 @@ def to_be_implemented(request):
|
|||
"page_title": "To Be Implemented...",
|
||||
}
|
||||
|
||||
return render(request, 'tbi.html', pagevars)
|
||||
return render(request, 'evennia_general/tbi.html', pagevars)
|
||||
|
||||
|
||||
@staff_member_required
|
||||
|
|
@ -74,7 +73,7 @@ def evennia_admin(request):
|
|||
Helpful Evennia-specific admin page.
|
||||
"""
|
||||
return render(
|
||||
request, 'evennia_admin.html', {
|
||||
request, 'evennia_general/evennia_admin.html', {
|
||||
'playerdb': PlayerDB})
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue