Merge pull request #8 from evennia/master

Update from original.
This commit is contained in:
n0q 2014-07-06 15:08:46 -04:00
commit 16b1aeffc2
31 changed files with 953 additions and 353 deletions

1
ev.py
View file

@ -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
######################################################################

View 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.

View 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")
}

View file

@ -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")):

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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())

View file

@ -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)])

View file

@ -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.")

View file

@ -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"

View file

@ -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"

View file

@ -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):
"""

View file

@ -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)

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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']

View file

@ -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")()

View file

@ -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

View file

@ -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
View 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)]

View file

@ -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

View file

@ -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})