mirror of
https://github.com/evennia/evennia.git
synced 2026-04-01 21:47:17 +02:00
PEP8 cleanup of the entire codebase. Unchanged are many cases of too-long lines, partly because of the rewrite they would require but also because splitting many lines up would make the code harder to read. Also the third-party libraries (idmapper, prettytable etc) were not cleaned.
This commit is contained in:
parent
30b7d2a405
commit
1ae17bcbe4
154 changed files with 5613 additions and 4054 deletions
|
|
@ -1,2 +1,3 @@
|
|||
# experimental central dictionary for models in subprocesses to report they have been changed.
|
||||
# experimental central dictionary for models in
|
||||
# subprocesses to report they have been changed.
|
||||
PROC_MODIFIED_OBJS = []
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -72,20 +72,25 @@ CMD_LOGINSTART = "__unloggedin_look_command"
|
|||
|
||||
# custom Exceptions
|
||||
|
||||
|
||||
class NoCmdSets(Exception):
|
||||
"No cmdsets found. Critical error."
|
||||
pass
|
||||
|
||||
|
||||
class ExecSystemCommand(Exception):
|
||||
"Run a system command"
|
||||
def __init__(self, syscmd, sysarg):
|
||||
self.args = (syscmd, sysarg) # needed by exception error handling
|
||||
self.args = (syscmd, sysarg) # needed by exception error handling
|
||||
self.syscmd = syscmd
|
||||
self.sysarg = sysarg
|
||||
|
||||
# Helper function
|
||||
|
||||
|
||||
@inlineCallbacks
|
||||
def get_and_merge_cmdsets(caller, session, player, obj, callertype, sessid=None):
|
||||
def get_and_merge_cmdsets(caller, session, player, obj,
|
||||
callertype, sessid=None):
|
||||
"""
|
||||
Gather all relevant cmdsets and merge them.
|
||||
|
||||
|
|
@ -124,20 +129,25 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, sessid=None)
|
|||
if location and not obj_cmdset.no_objs:
|
||||
# Gather all cmdsets stored on objects in the room and
|
||||
# also in the caller's inventory and the location itself
|
||||
local_objlist = yield location.contents_get(exclude=obj.dbobj) + obj.contents + [location]
|
||||
local_objlist = yield (location.contents_get(exclude=obj.dbobj) +
|
||||
obj.contents +
|
||||
[location])
|
||||
for lobj in local_objlist:
|
||||
try:
|
||||
# call hook in case we need to do dynamic changing to cmdset
|
||||
_GA(lobj, "at_cmdset_get")()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
# the call-type lock is checked here, it makes sure a player is not seeing e.g. the commands
|
||||
# on a fellow player (which is why the no_superuser_bypass must be True)
|
||||
local_obj_cmdsets = yield [lobj.cmdset.current for lobj in local_objlist
|
||||
if (lobj.cmdset.current and lobj.locks.check(caller, 'call', no_superuser_bypass=True))]
|
||||
# the call-type lock is checked here, it makes sure a player
|
||||
# is not seeing e.g. the commands on a fellow player (which is why
|
||||
# the no_superuser_bypass must be True)
|
||||
local_obj_cmdsets = \
|
||||
yield [lobj.cmdset.current for lobj in local_objlist
|
||||
if (lobj.cmdset.current and
|
||||
lobj.locks.check(caller, 'call', no_superuser_bypass=True))]
|
||||
for cset in local_obj_cmdsets:
|
||||
#This is necessary for object sets, or we won't be able to separate
|
||||
#the command sets from each other in a busy room.
|
||||
#This is necessary for object sets, or we won't be able to
|
||||
# separate the command sets from each other in a busy room.
|
||||
cset.old_duplicates = cset.duplicates
|
||||
cset.duplicates = True
|
||||
returnValue(local_obj_cmdsets)
|
||||
|
|
@ -159,8 +169,8 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, sessid=None)
|
|||
report_to = session
|
||||
session_cmdset = yield _get_cmdset(session)
|
||||
cmdsets = [session_cmdset]
|
||||
if player: # this automatically implies logged-in
|
||||
player_cmdset = yield _get_cmdset(player)
|
||||
if player: # this automatically implies logged-in
|
||||
player_cmdset = yield _get_cmdset(player)
|
||||
channel_cmdset = yield _get_channel_cmdsets(player, player_cmdset)
|
||||
cmdsets.extend([player_cmdset, channel_cmdset])
|
||||
if obj:
|
||||
|
|
@ -185,21 +195,26 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, sessid=None)
|
|||
cmdsets = [obj_cmdset] + local_obj_cmdsets
|
||||
else:
|
||||
raise Exception("get_and_merge_cmdsets: callertype %s is not valid." % callertype)
|
||||
#cmdsets = yield [caller_cmdset] + [player_cmdset] + [channel_cmdset] + local_obj_cmdsets
|
||||
#cmdsets = yield [caller_cmdset] + [player_cmdset] +
|
||||
# [channel_cmdset] + local_obj_cmdsets
|
||||
|
||||
# weed out all non-found sets
|
||||
cmdsets = yield [cmdset for cmdset in cmdsets if cmdset and cmdset.key!="Empty"]
|
||||
cmdsets = yield [cmdset for cmdset in cmdsets
|
||||
if cmdset and cmdset.key != "Empty"]
|
||||
# report cmdset errors to user (these should already have been logged)
|
||||
yield [report_to.msg(cmdset.errmessage) for cmdset in cmdsets if cmdset.key == "_CMDSET_ERROR"]
|
||||
yield [report_to.msg(cmdset.errmessage) for cmdset in cmdsets
|
||||
if cmdset.key == "_CMDSET_ERROR"]
|
||||
|
||||
if cmdsets:
|
||||
mergehash = tuple([id(cmdset) for cmdset in cmdsets]) # faster to do tuple on list than to build tuple directly
|
||||
# faster to do tuple on list than to build tuple directly
|
||||
mergehash = tuple([id(cmdset) for cmdset in cmdsets])
|
||||
if mergehash in _CMDSET_MERGE_CACHE:
|
||||
# cached merge exist; use that
|
||||
cmdset = _CMDSET_MERGE_CACHE[mergehash]
|
||||
else:
|
||||
# we group and merge all same-prio cmdsets separately (this avoids order-dependent
|
||||
# clashes in certain cases, such as when duplicates=True)
|
||||
# we group and merge all same-prio cmdsets separately (this avoids
|
||||
# order-dependent clashes in certain cases, such as
|
||||
# when duplicates=True)
|
||||
tempmergers = {}
|
||||
for cmdset in cmdsets:
|
||||
prio = cmdset.priority
|
||||
|
|
@ -241,13 +256,13 @@ def cmdhandler(called_by, raw_string, testing=False, callertype="session", sessi
|
|||
if True, the command instance will be returned instead.
|
||||
callertype - this is one of "session", "player" or "object", in decending
|
||||
order. So when the Session is the caller, it will merge its
|
||||
own cmdset into cmdsets from both Player and eventual puppeted Object (and
|
||||
cmdsets in its room etc). A Player will only include its
|
||||
own cmdset and the Objects and so on. Merge order is the
|
||||
same order, so that Object cmdsets are merged in last, giving
|
||||
them precendence for same-name and same-prio commands.
|
||||
sessid - Relevant if callertype is "player" - the session id will help retrieve the
|
||||
correct cmdsets from puppeted objects.
|
||||
own cmdset into cmdsets from both Player and eventual puppeted
|
||||
Object (and cmdsets in its room etc). A Player will only
|
||||
include its own cmdset and the Objects and so on. Merge order
|
||||
is the same order, so that Object cmdsets are merged in last,
|
||||
giving them precendence for same-name and same-prio commands.
|
||||
sessid - Relevant if callertype is "player" - the session id will help
|
||||
retrieve the correct cmdsets from puppeted objects.
|
||||
|
||||
Note that this function returns a deferred!
|
||||
"""
|
||||
|
|
@ -270,10 +285,11 @@ def cmdhandler(called_by, raw_string, testing=False, callertype="session", sessi
|
|||
# we assign the caller with preference 'bottom up'
|
||||
caller = obj or player or session
|
||||
|
||||
try: # catch bugs in cmdhandler itself
|
||||
try: # catch special-type commands
|
||||
try: # catch bugs in cmdhandler itself
|
||||
try: # catch special-type commands
|
||||
|
||||
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj, callertype, sessid)
|
||||
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
|
||||
callertype, sessid)
|
||||
if not cmdset:
|
||||
# this is bad and shouldn't happen.
|
||||
raise NoCmdSets
|
||||
|
|
@ -323,14 +339,15 @@ def cmdhandler(called_by, raw_string, testing=False, callertype="session", sessi
|
|||
else:
|
||||
# fallback to default error text
|
||||
sysarg = _("Command '%s' is not available.") % raw_string
|
||||
suggestions = string_suggestions(raw_string, cmdset.get_all_cmd_keys_and_aliases(caller), cutoff=0.7, maxnum=3)
|
||||
suggestions = string_suggestions(raw_string,
|
||||
cmdset.get_all_cmd_keys_and_aliases(caller),
|
||||
cutoff=0.7, maxnum=3)
|
||||
if suggestions:
|
||||
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True)
|
||||
else:
|
||||
sysarg += _(" Type \"help\" for help.")
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
|
||||
|
||||
# Check if this is a Channel-cmd match.
|
||||
if hasattr(cmd, 'is_channel') and cmd.is_channel:
|
||||
# even if a user-defined syscmd is not defined, the
|
||||
|
|
@ -380,7 +397,8 @@ def cmdhandler(called_by, raw_string, testing=False, callertype="session", sessi
|
|||
for func_part in make_iter(cmd.func_parts):
|
||||
err = yield func_part()
|
||||
# returning anything but a deferred/None will kill the chain
|
||||
if err: break
|
||||
if err:
|
||||
break
|
||||
|
||||
# post-command hook
|
||||
yield cmd.at_post_cmd()
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
for cmd in cmdset:
|
||||
try:
|
||||
matches.extend([create_match(cmdname, raw_string, cmd)
|
||||
for cmdname in [cmd.key] + cmd.aliases
|
||||
if cmdname and l_raw_string.startswith(cmdname.lower())
|
||||
and (not cmd.arg_regex or
|
||||
cmd.arg_regex.match(l_raw_string[len(cmdname):]))])
|
||||
for cmdname in [cmd.key] + cmd.aliases
|
||||
if cmdname and l_raw_string.startswith(cmdname.lower())
|
||||
and (not cmd.arg_regex or
|
||||
cmd.arg_regex.match(l_raw_string[len(cmdname):]))])
|
||||
except Exception:
|
||||
log_trace("cmdhandler error. raw_input:%s" % raw_string)
|
||||
|
||||
|
|
@ -67,7 +67,8 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
if mindex.isdigit():
|
||||
mindex = int(mindex) - 1
|
||||
# feed result back to parser iteratively
|
||||
return cmdparser(new_raw_string, cmdset, caller, match_index=mindex)
|
||||
return cmdparser(new_raw_string, cmdset,
|
||||
caller, match_index=mindex)
|
||||
|
||||
# only select command matches we are actually allowed to call.
|
||||
matches = [match for match in matches if match[2].access(caller, 'cmd')]
|
||||
|
|
@ -75,7 +76,8 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
if len(matches) > 1:
|
||||
# See if it helps to analyze the match with preserved case but only if
|
||||
# it leaves at least one match.
|
||||
trimmed = [match for match in matches if raw_string.startswith(match[0])]
|
||||
trimmed = [match for match in matches
|
||||
if raw_string.startswith(match[0])]
|
||||
if trimmed:
|
||||
matches = trimmed
|
||||
|
||||
|
|
@ -94,15 +96,13 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
matches = matches[-quality.count(quality[-1]):]
|
||||
|
||||
if len(matches) > 1 and match_index != None and 0 <= match_index < len(matches):
|
||||
# We couldn't separate match by quality, but we have an index argument to
|
||||
# tell us which match to use.
|
||||
# We couldn't separate match by quality, but we have an
|
||||
# index argument to tell us which match to use.
|
||||
matches = [matches[match_index]]
|
||||
|
||||
# no matter what we have at this point, we have to return it.
|
||||
return matches
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Search parsers and support methods
|
||||
#------------------------------------------------------------
|
||||
|
|
@ -118,7 +118,6 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
# The the replacing modules must have the same inputs and outputs as
|
||||
# those in this module.
|
||||
#
|
||||
|
||||
def at_search_result(msg_obj, ostring, results, global_search=False,
|
||||
nofound_string=None, multimatch_string=None):
|
||||
"""
|
||||
|
|
@ -176,7 +175,7 @@ def at_search_result(msg_obj, ostring, results, global_search=False,
|
|||
invtext = _(" (carried)")
|
||||
if show_dbref:
|
||||
dbreftext = "(#%i)" % result.dbid
|
||||
string += "\n %i-%s%s%s" % (num+1, result.name,
|
||||
string += "\n %i-%s%s%s" % (num + 1, result.name,
|
||||
dbreftext, invtext)
|
||||
results = None
|
||||
else:
|
||||
|
|
@ -187,6 +186,7 @@ def at_search_result(msg_obj, ostring, results, global_search=False,
|
|||
msg_obj.msg(string.strip())
|
||||
return results
|
||||
|
||||
|
||||
def at_multimatch_input(ostring):
|
||||
"""
|
||||
Parse number-identifiers.
|
||||
|
|
@ -231,9 +231,9 @@ def at_multimatch_input(ostring):
|
|||
if not '-' in ostring:
|
||||
return (None, ostring)
|
||||
try:
|
||||
index = ostring.find('-')
|
||||
number = int(ostring[:index])-1
|
||||
return (number, ostring[index+1:])
|
||||
index = ostring.find('-')
|
||||
number = int(ostring[:index]) - 1
|
||||
return (number, ostring[index + 1:])
|
||||
except ValueError:
|
||||
#not a number; this is not an identifier.
|
||||
return (None, ostring)
|
||||
|
|
@ -256,13 +256,15 @@ def at_multimatch_cmd(caller, matches):
|
|||
else:
|
||||
is_channel = ""
|
||||
if cmd.is_exit and cmd.destination:
|
||||
is_exit = _(" (exit to %s)") % cmd.destination
|
||||
is_exit = (" (exit to %s)") % cmd.destination
|
||||
else:
|
||||
is_exit = ""
|
||||
|
||||
id1 = ""
|
||||
id2 = ""
|
||||
if not (is_channel or is_exit) and (hasattr(cmd, 'obj') and cmd.obj != caller) and hasattr(cmd.obj, "key"):
|
||||
if (not (is_channel or is_exit) and
|
||||
(hasattr(cmd, 'obj') and cmd.obj != caller) and
|
||||
hasattr(cmd.obj, "key")):
|
||||
# the command is defined on some other object
|
||||
id1 = "%s-%s" % (num + 1, cmdname)
|
||||
id2 = " (%s)" % (cmd.obj.key)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from django.utils.translation import ugettext as _
|
|||
from src.utils.utils import inherits_from, is_iter
|
||||
__all__ = ("CmdSet",)
|
||||
|
||||
|
||||
class _CmdSetMeta(type):
|
||||
"""
|
||||
This metaclass makes some minor on-the-fly convenience fixes to
|
||||
|
|
@ -38,15 +39,18 @@ class _CmdSetMeta(type):
|
|||
|
||||
super(_CmdSetMeta, mcs).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CmdSet(object):
|
||||
"""
|
||||
This class describes a unique cmdset that understands priorities. CmdSets
|
||||
can be merged and made to perform various set operations on each other.
|
||||
CmdSets have priorities that affect which of their ingoing commands gets used.
|
||||
CmdSets have priorities that affect which of their ingoing commands
|
||||
gets used.
|
||||
|
||||
In the examples, cmdset A always have higher priority than cmdset B.
|
||||
|
||||
key - the name of the cmdset. This can be used on its own for game operations
|
||||
key - the name of the cmdset. This can be used on its own for game
|
||||
operations
|
||||
|
||||
mergetype (partly from Set theory):
|
||||
|
||||
|
|
@ -80,8 +84,9 @@ class CmdSet(object):
|
|||
priority- All cmdsets are always merged in pairs of two so that
|
||||
the higher set's mergetype is applied to the
|
||||
lower-priority cmdset. Default commands have priority 0,
|
||||
high-priority ones like Exits and Channels have 10 and 9. Priorities
|
||||
can be negative as well to give default commands preference.
|
||||
high-priority ones like Exits and Channels have 10 and 9.
|
||||
Priorities can be negative as well to give default
|
||||
commands preference.
|
||||
|
||||
duplicates - determines what happens when two sets of equal
|
||||
priority merge. Default has the first of them in the
|
||||
|
|
@ -130,16 +135,17 @@ class CmdSet(object):
|
|||
permanent = False
|
||||
errmessage = ""
|
||||
# pre-store properties to duplicate straight off
|
||||
to_duplicate = ("key", "cmdsetobj", "no_exits", "no_objs", "no_channels", "permanent",
|
||||
"mergetype", "priority", "duplicates", "errmessage")
|
||||
to_duplicate = ("key", "cmdsetobj", "no_exits", "no_objs",
|
||||
"no_channels", "permanent", "mergetype",
|
||||
"priority", "duplicates", "errmessage")
|
||||
|
||||
def __init__(self, cmdsetobj=None, key=None):
|
||||
"""
|
||||
Creates a new CmdSet instance.
|
||||
|
||||
cmdsetobj - this is the database object to which this particular
|
||||
instance of cmdset is related. It is often a player but may also be a
|
||||
regular object.
|
||||
instance of cmdset is related. It is often a character but
|
||||
may also be a regular object.
|
||||
"""
|
||||
if key:
|
||||
self.key = key
|
||||
|
|
@ -161,7 +167,8 @@ class CmdSet(object):
|
|||
if cmdset_a.duplicates and cmdset_a.priority == cmdset_b.priority:
|
||||
cmdset_c.commands.extend(cmdset_b.commands)
|
||||
else:
|
||||
cmdset_c.commands.extend([cmd for cmd in cmdset_b if not cmd in cmdset_a])
|
||||
cmdset_c.commands.extend([cmd for cmd in cmdset_b
|
||||
if not cmd in cmdset_a])
|
||||
return cmdset_c
|
||||
|
||||
def _intersect(self, cmdset_a, cmdset_b):
|
||||
|
|
@ -206,7 +213,8 @@ class CmdSet(object):
|
|||
cmdset = CmdSet()
|
||||
for key, val in ((key, getattr(self, key)) for key in self.to_duplicate):
|
||||
if val != getattr(cmdset, key):
|
||||
# only copy if different from default; avoid turning class-vars into instance vars
|
||||
# only copy if different from default; avoid turning
|
||||
# class-vars into instance vars
|
||||
setattr(cmdset, key, val)
|
||||
cmdset.key_mergetypes = self.key_mergetypes.copy()
|
||||
return cmdset
|
||||
|
|
@ -230,10 +238,11 @@ class CmdSet(object):
|
|||
def __contains__(self, othercmd):
|
||||
"""
|
||||
Returns True if this cmdset contains the given command (as defined
|
||||
by command name and aliases). This allows for things like 'if cmd in cmdset'
|
||||
by command name and aliases). This allows for things
|
||||
like 'if cmd in cmdset'
|
||||
"""
|
||||
ret = self._contains_cache.get(othercmd)
|
||||
if ret == None:
|
||||
if ret is None:
|
||||
ret = othercmd in self.commands
|
||||
self._contains_cache[othercmd] = ret
|
||||
return ret
|
||||
|
|
@ -264,7 +273,8 @@ class CmdSet(object):
|
|||
# A higher or equal priority than B
|
||||
|
||||
# preserve system __commands
|
||||
sys_commands = sys_commands_a + [cmd for cmd in sys_commands_b if cmd not in sys_commands_a]
|
||||
sys_commands = sys_commands_a + [cmd for cmd in sys_commands_b
|
||||
if cmd not in sys_commands_a]
|
||||
|
||||
mergetype = self.key_mergetypes.get(cmdset_b.key, self.mergetype)
|
||||
if mergetype == "Intersect":
|
||||
|
|
@ -286,7 +296,8 @@ class CmdSet(object):
|
|||
# B higher priority than A
|
||||
|
||||
# preserver system __commands
|
||||
sys_commands = sys_commands_b + [cmd for cmd in sys_commands_a if cmd not in sys_commands_b]
|
||||
sys_commands = sys_commands_b + [cmd for cmd in sys_commands_a
|
||||
if cmd not in sys_commands_b]
|
||||
|
||||
mergetype = cmdset_b.key_mergetypes.get(self.key, cmdset_b.mergetype)
|
||||
if mergetype == "Intersect":
|
||||
|
|
@ -295,7 +306,7 @@ class CmdSet(object):
|
|||
cmdset_c = self._replace(cmdset_b, self)
|
||||
elif mergetype == "Remove":
|
||||
cmdset_c = self._remove(self, cmdset_b)
|
||||
else: # Union
|
||||
else: # Union
|
||||
cmdset_c = self._union(cmdset_b, self)
|
||||
cmdset_c.no_channels = cmdset_b.no_channels
|
||||
cmdset_c.no_exits = cmdset_b.no_exits
|
||||
|
|
@ -311,10 +322,8 @@ class CmdSet(object):
|
|||
|
||||
# return the system commands to the cmdset
|
||||
cmdset_c.add(sys_commands)
|
||||
|
||||
return cmdset_c
|
||||
|
||||
|
||||
def add(self, cmd):
|
||||
"""
|
||||
Add a command, a list of commands or a cmdset to this cmdset.
|
||||
|
|
@ -338,9 +347,12 @@ class CmdSet(object):
|
|||
try:
|
||||
cmd = self._instantiate(cmd)
|
||||
except RuntimeError:
|
||||
string = "Adding cmdset %(cmd)s to %(class)s lead to an infinite loop. When adding a cmdset to another, "
|
||||
string += "make sure they are not themself cyclically added to the new cmdset somewhere in the chain."
|
||||
raise RuntimeError(_(string) % {"cmd":cmd, "class":self.__class__})
|
||||
string = "Adding cmdset %(cmd)s to %(class)s lead to an "
|
||||
string += "infinite loop. When adding a cmdset to another, "
|
||||
string += "make sure they are not themself cyclically added to "
|
||||
string += "the new cmdset somewhere in the chain."
|
||||
raise RuntimeError(_(string) % {"cmd": cmd,
|
||||
"class": self.__class__})
|
||||
cmds = cmd.commands
|
||||
elif is_iter(cmd):
|
||||
cmds = [self._instantiate(c) for c in cmd]
|
||||
|
|
@ -354,7 +366,7 @@ class CmdSet(object):
|
|||
cmd.obj = self.cmdsetobj
|
||||
try:
|
||||
ic = commands.index(cmd)
|
||||
commands[ic] = cmd # replace
|
||||
commands[ic] = cmd # replace
|
||||
except ValueError:
|
||||
commands.append(cmd)
|
||||
# extra run to make sure to avoid doublets
|
||||
|
|
@ -365,11 +377,10 @@ class CmdSet(object):
|
|||
if cmd.key.startswith("__"):
|
||||
try:
|
||||
ic = system_commands.index(cmd)
|
||||
system_commands[ic] = cmd # replace
|
||||
system_commands[ic] = cmd # replace
|
||||
except ValueError:
|
||||
system_commands.append(cmd)
|
||||
|
||||
|
||||
def remove(self, cmd):
|
||||
"""
|
||||
Remove a command instance from the cmdset.
|
||||
|
|
@ -431,7 +442,8 @@ class CmdSet(object):
|
|||
"""
|
||||
names = []
|
||||
if caller:
|
||||
[names.extend(cmd._keyaliases) for cmd in self.commands if cmd.access(caller)]
|
||||
[names.extend(cmd._keyaliases) for cmd in self.commands
|
||||
if cmd.access(caller)]
|
||||
else:
|
||||
[names.extend(cmd._keyaliases) for cmd in self.commands]
|
||||
return names
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ can then implement separate sets for different situations. For
|
|||
example, you can have a 'On a boat' set, onto which you then tack on
|
||||
the 'Fishing' set. Fishing from a boat? No problem!
|
||||
"""
|
||||
import traceback
|
||||
from src.utils import logger, utils
|
||||
from src.commands.cmdset import CmdSet
|
||||
from src.server.models import ServerConfig
|
||||
|
|
@ -73,23 +72,27 @@ __all__ = ("import_cmdset", "CmdSetHandler")
|
|||
|
||||
_CACHED_CMDSETS = {}
|
||||
|
||||
|
||||
class _ErrorCmdSet(CmdSet):
|
||||
"This is a special cmdset used to report errors"
|
||||
key = "_CMDSET_ERROR"
|
||||
errmessage = "Error when loading cmdset."
|
||||
|
||||
|
||||
def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||
"""
|
||||
This helper function is used by the cmdsethandler to load a cmdset
|
||||
instance from a python module, given a python_path. It's usually accessed
|
||||
through the cmdsethandler's add() and add_default() methods.
|
||||
python_path - This is the full path to the cmdset object.
|
||||
cmdsetobj - the database object/typeclass on which this cmdset is to be assigned
|
||||
(this can be also channels and exits, as well as players but there will
|
||||
always be such an object)
|
||||
emit_to_obj - if given, error is emitted to this object (in addition to logging)
|
||||
no_logging - don't log/send error messages. This can be useful if import_cmdset is just
|
||||
used to check if this is a valid python path or not.
|
||||
cmdsetobj - the database object/typeclass on which this cmdset is to be
|
||||
assigned (this can be also channels and exits, as well as players
|
||||
but there will always be such an object)
|
||||
emit_to_obj - if given, error is emitted to this object (in addition
|
||||
to logging)
|
||||
no_logging - don't log/send error messages. This can be useful
|
||||
if import_cmdset is just used to check if this is a
|
||||
valid python path or not.
|
||||
function returns None if an error was encountered or path not found.
|
||||
"""
|
||||
|
||||
|
|
@ -117,7 +120,8 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
|||
raise
|
||||
except KeyError:
|
||||
errstring = _("Error in loading cmdset: No cmdset class '%(classname)s' in %(modulepath)s.")
|
||||
errstring = errstring % {"classname":classname, "modulepath":modulepath}
|
||||
errstring = errstring % {"classname": classname,
|
||||
"modulepath": modulepath}
|
||||
raise
|
||||
except Exception:
|
||||
errstring = _("Compile/Run error when loading cmdset '%s'. Error was logged.")
|
||||
|
|
@ -135,15 +139,17 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
|||
|
||||
# classes
|
||||
|
||||
|
||||
class CmdSetHandler(object):
|
||||
"""
|
||||
The CmdSetHandler is always stored on an object, this object is supplied as an argument.
|
||||
The CmdSetHandler is always stored on an object, this object is supplied
|
||||
as an argument.
|
||||
|
||||
The 'current' cmdset is the merged set currently active for this object.
|
||||
This is the set the game engine will retrieve when determining which
|
||||
commands are available to the object. The cmdset_stack holds a history of all CmdSets
|
||||
to allow the handler to remove/add cmdsets at will. Doing so will re-calculate
|
||||
the 'current' cmdset.
|
||||
commands are available to the object. The cmdset_stack holds a history of
|
||||
all CmdSets to allow the handler to remove/add cmdsets at will. Doing so
|
||||
will re-calculate the 'current' cmdset.
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
|
|
@ -176,10 +182,8 @@ class CmdSetHandler(object):
|
|||
mergelist = []
|
||||
if len(self.cmdset_stack) > 1:
|
||||
# We have more than one cmdset in stack; list them all
|
||||
num = 0
|
||||
#print self.cmdset_stack, self.mergetype_stack
|
||||
for snum, cmdset in enumerate(self.cmdset_stack):
|
||||
num = snum
|
||||
mergetype = self.mergetype_stack[snum]
|
||||
permstring = "non-perm"
|
||||
if cmdset.permanent:
|
||||
|
|
@ -196,17 +200,21 @@ class CmdSetHandler(object):
|
|||
mergetype = self.mergetype_stack[-1]
|
||||
if mergetype != self.current.mergetype:
|
||||
merged_on = self.cmdset_stack[-2].key
|
||||
mergetype = _("custom %(mergetype)s on cmdset '%(merged_on)s'") % {"mergetype":mergetype, "merged_on":merged_on}
|
||||
mergetype = _("custom %(mergetype)s on cmdset '%(merged_on)s'") % \
|
||||
{"mergetype": mergetype, "merged_on":merged_on}
|
||||
if mergelist:
|
||||
string += _(" <Merged %(mergelist)s (%(mergetype)s, prio %(prio)i)>: %(current)s") % \
|
||||
{"mergelist": "+".join(mergelist), "mergetype":mergetype, "prio":self.current.priority, "current":self.current}
|
||||
{"mergelist": "+".join(mergelist),
|
||||
"mergetype": mergetype, "prio": self.current.priority,
|
||||
"current":self.current}
|
||||
else:
|
||||
permstring = "non-perm"
|
||||
if self.current.permanent:
|
||||
permstring = "perm"
|
||||
string += _(" <%(key)s (%(mergetype)s, prio %(prio)i, %(permstring)s)>: %(keylist)s") % \
|
||||
{"key":self.current.key, "mergetype":mergetype, "prio":self.current.priority, "permstring":permstring,
|
||||
"keylist":", ".join(cmd.key for cmd in sorted(self.current, key=lambda o:o.key))}
|
||||
{"key": self.current.key, "mergetype": mergetype,
|
||||
"prio": self.current.priority, "permstring": permstring,
|
||||
"keylist": ", ".join(cmd.key for cmd in sorted(self.current, key=lambda o: o.key))}
|
||||
return string.strip()
|
||||
|
||||
def _import_cmdset(self, cmdset_path, emit_to_obj=None):
|
||||
|
|
@ -362,10 +370,12 @@ class CmdSetHandler(object):
|
|||
else:
|
||||
# try it as a callable
|
||||
if callable(cmdset) and hasattr(cmdset, 'path'):
|
||||
delcmdsets = [cset for cset in self.cmdset_stack[1:] if cset.path == cmdset.path]
|
||||
delcmdsets = [cset for cset in self.cmdset_stack[1:]
|
||||
if cset.path == cmdset.path]
|
||||
else:
|
||||
# try it as a path or key
|
||||
delcmdsets = [cset for cset in self.cmdset_stack[1:] if cset.path == cmdset or cset.key == cmdset]
|
||||
delcmdsets = [cset for cset in self.cmdset_stack[1:]
|
||||
if cset.path == cmdset or cset.key == cmdset]
|
||||
storage = []
|
||||
|
||||
if any(cset.permanent for cset in delcmdsets):
|
||||
|
|
@ -387,7 +397,10 @@ class CmdSetHandler(object):
|
|||
self.update()
|
||||
|
||||
def delete_default(self):
|
||||
"This explicitly deletes the default cmdset. It's the only command that can."
|
||||
"""
|
||||
This explicitly deletes the default cmdset. It's the
|
||||
only command that can.
|
||||
"""
|
||||
if self.cmdset_stack:
|
||||
cmdset = self.cmdset_stack[0]
|
||||
if cmdset.permanent:
|
||||
|
|
@ -404,7 +417,8 @@ class CmdSetHandler(object):
|
|||
|
||||
def all(self):
|
||||
"""
|
||||
Returns the list of cmdsets. Mostly useful to check if stack if empty or not.
|
||||
Returns the list of cmdsets. Mostly useful to check
|
||||
if stack if empty or not.
|
||||
"""
|
||||
return self.cmdset_stack
|
||||
|
||||
|
|
@ -431,13 +445,6 @@ class CmdSetHandler(object):
|
|||
else:
|
||||
return any([cmdset.key == cmdset_key for cmdset in self.cmdset_stack])
|
||||
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Returns all cmdsets.
|
||||
"""
|
||||
return self.cmdset_stack
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Force reload of all cmdsets in handler. This should be called
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import re
|
|||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils.utils import is_iter, fill
|
||||
|
||||
|
||||
def _init_command(mcs, **kwargs):
|
||||
"""
|
||||
Helper command.
|
||||
|
|
@ -17,20 +18,23 @@ def _init_command(mcs, **kwargs):
|
|||
Sets up locks to be more forgiving. This is used both by the metaclass
|
||||
and (optionally) at instantiation time.
|
||||
|
||||
If kwargs are given, these are set as instance-specific properties on the command.
|
||||
If kwargs are given, these are set as instance-specific properties
|
||||
on the command.
|
||||
"""
|
||||
for i in range(len(kwargs)):
|
||||
# used for dynamic creation of commands
|
||||
key, value = kwargs.popitem()
|
||||
setattr(mcs, key, value)
|
||||
# used for dynamic creation of commands
|
||||
key, value = kwargs.popitem()
|
||||
setattr(mcs, key, value)
|
||||
|
||||
mcs.key = mcs.key.lower()
|
||||
if mcs.aliases and not is_iter(mcs.aliases):
|
||||
try:
|
||||
mcs.aliases = [str(alias).strip().lower() for alias in mcs.aliases.split(',')]
|
||||
mcs.aliases = [str(alias).strip().lower()
|
||||
for alias in mcs.aliases.split(',')]
|
||||
except Exception:
|
||||
mcs.aliases = []
|
||||
mcs.aliases = list(set(alias for alias in mcs.aliases if alias and alias != mcs.key))
|
||||
mcs.aliases = list(set(alias for alias in mcs.aliases
|
||||
if alias and alias != mcs.key))
|
||||
|
||||
# optimization - a set is much faster to match against than a list
|
||||
mcs._matchset = set([mcs.key] + mcs.aliases)
|
||||
|
|
@ -84,6 +88,7 @@ class CommandMeta(type):
|
|||
# structure can parse the input string the same way, minimizing
|
||||
# parsing errors.
|
||||
|
||||
|
||||
class Command(object):
|
||||
"""
|
||||
Base command
|
||||
|
|
@ -112,13 +117,16 @@ class Command(object):
|
|||
key - identifier for command (e.g. "look")
|
||||
aliases - (optional) list of aliases (e.g. ["l", "loo"])
|
||||
locks - lock string (default is "cmd:all()")
|
||||
help_category - how to organize this help entry in help system (default is "General")
|
||||
help_category - how to organize this help entry in help system
|
||||
(default is "General")
|
||||
auto_help - defaults to True. Allows for turning off auto-help generation
|
||||
arg_regex - (optional) raw string regex defining how the argument part of the command should look
|
||||
in order to match for this command (e.g. must it be a space between cmdname and arg?)
|
||||
arg_regex - (optional) raw string regex defining how the argument part of
|
||||
the command should look in order to match for this command
|
||||
(e.g. must it be a space between cmdname and arg?)
|
||||
|
||||
(Note that if auto_help is on, this initial string is also used by the system
|
||||
to create the help entry for the command, so it's a good idea to format it similar to this one)
|
||||
(Note that if auto_help is on, this initial string is also used by the
|
||||
system to create the help entry for the command, so it's a good idea to
|
||||
format it similar to this one)
|
||||
"""
|
||||
# Tie our metaclass, for some convenience cleanup
|
||||
__metaclass__ = CommandMeta
|
||||
|
|
@ -127,7 +135,8 @@ class Command(object):
|
|||
key = "command"
|
||||
# alternative ways to call the command (e.g. 'l', 'glance', 'examine')
|
||||
aliases = []
|
||||
# a list of lock definitions on the form cmd:[NOT] func(args) [ AND|OR][ NOT] func2(args)
|
||||
# a list of lock definitions on the form
|
||||
# cmd:[NOT] func(args) [ AND|OR][ NOT] func2(args)
|
||||
locks = ""
|
||||
# used by the help system to group commands in lists.
|
||||
help_category = "general"
|
||||
|
|
@ -136,7 +145,8 @@ class Command(object):
|
|||
auto_help = True
|
||||
# auto-set (by Evennia on command instantiation) are:
|
||||
# obj - which object this command is defined on
|
||||
# sessid - which session-id (if any) is responsible for triggering this command
|
||||
# sessid - which session-id (if any) is responsible for
|
||||
# triggering this command
|
||||
#
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
|
@ -206,20 +216,22 @@ class Command(object):
|
|||
"""
|
||||
return self.lockhandler.check(srcobj, access_type, default=default)
|
||||
|
||||
def msg(self, msg="", to_obj=None, from_obj=None, sessid=None, all_sessions=False, **kwargs):
|
||||
def msg(self, msg="", to_obj=None, from_obj=None,
|
||||
sessid=None, all_sessions=False, **kwargs):
|
||||
"""
|
||||
This is a shortcut instad of calling msg() directly on an object - it will
|
||||
detect if caller is an Object or a Player and also appends self.sessid
|
||||
automatically.
|
||||
This is a shortcut instad of calling msg() directly on an object - it
|
||||
will detect if caller is an Object or a Player and also appends
|
||||
self.sessid automatically.
|
||||
|
||||
msg - text string of message to send
|
||||
to_obj - target object of message. Defaults to self.caller
|
||||
from_obj - source of message. Defaults to to_obj
|
||||
data - optional dictionary of data
|
||||
sessid - supply data only to a unique sessid (normally not used - this is only potentially useful if
|
||||
to_obj is a Player object different from self.caller or self.caller.player)
|
||||
all_sessions (bool) - default is to send only to the session connected to
|
||||
the target object
|
||||
sessid - supply data only to a unique sessid (normally not used -
|
||||
this is only potentially useful if to_obj is a Player object
|
||||
different from self.caller or self.caller.player)
|
||||
all_sessions (bool) - default is to send only to the session
|
||||
connected to the target object
|
||||
"""
|
||||
from_obj = from_obj or self.caller
|
||||
to_obj = to_obj or from_obj
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ Admin commands
|
|||
|
||||
"""
|
||||
|
||||
import time, re
|
||||
import time
|
||||
import re
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
|
|
@ -15,8 +16,8 @@ from src.commands.default.muxcommand import MuxCommand
|
|||
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
|
||||
|
||||
# limit members for API inclusion
|
||||
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer", "CmdEmit", "CmdNewPassword",
|
||||
"CmdPerm", "CmdWall")
|
||||
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer",
|
||||
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall")
|
||||
|
||||
|
||||
class CmdBoot(MuxCommand):
|
||||
|
|
@ -98,6 +99,7 @@ class CmdBoot(MuxCommand):
|
|||
# regex matching IP addresses with wildcards, eg. 233.122.4.*
|
||||
IPREGEX = re.compile(r"[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}")
|
||||
|
||||
|
||||
def list_bans(banlist):
|
||||
"""
|
||||
Helper function to display a list of active bans. Input argument
|
||||
|
|
@ -108,12 +110,13 @@ def list_bans(banlist):
|
|||
|
||||
table = prettytable.PrettyTable(["{wid", "{wname/ip", "{wdate", "{wreason"])
|
||||
for inum, ban in enumerate(banlist):
|
||||
table.add_row([str(inum+1),
|
||||
table.add_row([str(inum + 1),
|
||||
ban[0] and ban[0] or ban[1],
|
||||
ban[3], ban[4]])
|
||||
string = "{wActive bans:{n\n%s" % table
|
||||
return string
|
||||
|
||||
|
||||
class CmdBan(MuxCommand):
|
||||
"""
|
||||
ban a player from the server
|
||||
|
|
@ -152,7 +155,7 @@ class CmdBan(MuxCommand):
|
|||
key = "@ban"
|
||||
aliases = ["@bans"]
|
||||
locks = "cmd:perm(ban) or perm(Immortals)"
|
||||
help_category="Admin"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
|
|
@ -172,14 +175,15 @@ class CmdBan(MuxCommand):
|
|||
banlist = []
|
||||
|
||||
if not self.args or (self.switches
|
||||
and not any(switch in ('ip', 'name') for switch in self.switches)):
|
||||
and not any(switch in ('ip', 'name')
|
||||
for switch in self.switches)):
|
||||
self.caller.msg(list_bans(banlist))
|
||||
return
|
||||
|
||||
now = time.ctime()
|
||||
reason = ""
|
||||
if ':' in self.args:
|
||||
ban, reason = self.args.rsplit(':',1)
|
||||
ban, reason = self.args.rsplit(':', 1)
|
||||
else:
|
||||
ban = self.args
|
||||
ban = ban.lower()
|
||||
|
|
@ -193,7 +197,7 @@ class CmdBan(MuxCommand):
|
|||
typ = "IP"
|
||||
ban = ipban[0]
|
||||
# replace * with regex form and compile it
|
||||
ipregex = ban.replace('.','\.')
|
||||
ipregex = ban.replace('.', '\.')
|
||||
ipregex = ipregex.replace('*', '[0-9]{1,3}')
|
||||
#print "regex:",ipregex
|
||||
ipregex = re.compile(r"%s" % ipregex)
|
||||
|
|
@ -203,6 +207,7 @@ class CmdBan(MuxCommand):
|
|||
ServerConfig.objects.conf('server_bans', banlist)
|
||||
self.caller.msg("%s-Ban {w%s{x was added." % (typ, ban))
|
||||
|
||||
|
||||
class CmdUnban(MuxCommand):
|
||||
"""
|
||||
remove a ban
|
||||
|
|
@ -218,7 +223,7 @@ class CmdUnban(MuxCommand):
|
|||
"""
|
||||
key = "@unban"
|
||||
locks = "cmd:perm(unban) or perm(Immortals)"
|
||||
help_category="Admin"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implement unbanning"
|
||||
|
|
@ -241,10 +246,11 @@ class CmdUnban(MuxCommand):
|
|||
self.caller.msg("Ban id {w%s{x was not found." % self.args)
|
||||
else:
|
||||
# all is ok, clear ban
|
||||
ban = banlist[num-1]
|
||||
del banlist[num-1]
|
||||
ban = banlist[num - 1]
|
||||
del banlist[num - 1]
|
||||
ServerConfig.objects.conf('server_bans', banlist)
|
||||
self.caller.msg("Cleared ban %s: %s" % (num, " ".join([s for s in ban[:2]])))
|
||||
self.caller.msg("Cleared ban %s: %s" %
|
||||
(num, " ".join([s for s in ban[:2]])))
|
||||
|
||||
|
||||
class CmdDelPlayer(MuxCommand):
|
||||
|
|
@ -408,7 +414,7 @@ class CmdEmit(MuxCommand):
|
|||
obj = caller.search(objname, global_search=True)
|
||||
if not obj:
|
||||
return
|
||||
if rooms_only and not obj.location == None:
|
||||
if rooms_only and not obj.location is None:
|
||||
caller.msg("%s is not a room. Ignored." % objname)
|
||||
continue
|
||||
if players_only and not obj.has_player:
|
||||
|
|
@ -425,7 +431,6 @@ class CmdEmit(MuxCommand):
|
|||
caller.msg("You are not allowed to emit to %s." % objname)
|
||||
|
||||
|
||||
|
||||
class CmdNewPassword(MuxCommand):
|
||||
"""
|
||||
@userpassword
|
||||
|
|
@ -457,7 +462,8 @@ class CmdNewPassword(MuxCommand):
|
|||
player.user.save()
|
||||
self.msg("%s - new password set to '%s'." % (player.name, self.rhs))
|
||||
if player.character != caller:
|
||||
player.msg("%s has changed your password to '%s'." % (caller.name, self.rhs))
|
||||
player.msg("%s has changed your password to '%s'." % (caller.name,
|
||||
self.rhs))
|
||||
|
||||
|
||||
class CmdPerm(MuxCommand):
|
||||
|
|
@ -497,7 +503,7 @@ class CmdPerm(MuxCommand):
|
|||
if playermode:
|
||||
obj = caller.search_player(lhs)
|
||||
else:
|
||||
obj = caller.search(lhs, global_search=True)
|
||||
obj = caller.search(lhs, global_search=True)
|
||||
if not obj:
|
||||
return
|
||||
|
||||
|
|
@ -511,7 +517,9 @@ class CmdPerm(MuxCommand):
|
|||
string += "<None>"
|
||||
else:
|
||||
string += ", ".join(obj.permissions.all())
|
||||
if hasattr(obj, 'player') and hasattr(obj.player, 'is_superuser') and obj.player.is_superuser:
|
||||
if (hasattr(obj, 'player') and
|
||||
hasattr(obj.player, 'is_superuser') and
|
||||
obj.player.is_superuser):
|
||||
string += "\n(... but this object is currently controlled by a SUPERUSER! "
|
||||
string += "All access checks are passed automatically.)"
|
||||
caller.msg(string)
|
||||
|
|
@ -539,9 +547,10 @@ class CmdPerm(MuxCommand):
|
|||
|
||||
for perm in self.rhslist:
|
||||
|
||||
# don't allow to set a permission higher in the hierarchy than the one the
|
||||
# caller has (to prevent self-escalation)
|
||||
if perm.lower() in PERMISSION_HIERARCHY and not obj.locks.check_lockstring(caller, "dummy:perm(%s)" % perm):
|
||||
# don't allow to set a permission higher in the hierarchy than
|
||||
# the one the caller has (to prevent self-escalation)
|
||||
if (perm.lower() in PERMISSION_HIERARCHY and not
|
||||
obj.locks.check_lockstring(caller, "dummy:perm(%s)" % perm)):
|
||||
caller.msg("You cannot assign a permission higher than the one you have yourself.")
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -95,11 +95,12 @@ def format_header(caller, entry):
|
|||
stacklen = len(caller.ndb.batch_stack)
|
||||
header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header)
|
||||
# add extra space to the side for padding.
|
||||
header = "%s%s" % (header, " "*(width - len(header)))
|
||||
header = "%s%s" % (header, " " * (width - len(header)))
|
||||
header = header.replace('\n', '\\n')
|
||||
|
||||
return header
|
||||
|
||||
|
||||
def format_code(entry):
|
||||
"""
|
||||
Formats the viewing of code and errors
|
||||
|
|
@ -109,6 +110,7 @@ def format_code(entry):
|
|||
code += "\n{G>>>{n %s" % line
|
||||
return code.strip()
|
||||
|
||||
|
||||
def batch_cmd_exec(caller):
|
||||
"""
|
||||
Helper function for executing a single batch-command entry
|
||||
|
|
@ -124,6 +126,7 @@ def batch_cmd_exec(caller):
|
|||
return False
|
||||
return True
|
||||
|
||||
|
||||
def batch_code_exec(caller):
|
||||
"""
|
||||
Helper function for executing a single batch-code entry
|
||||
|
|
@ -135,12 +138,13 @@ def batch_code_exec(caller):
|
|||
|
||||
caller.msg(format_header(caller, codedict['code']))
|
||||
err = BATCHCODE.code_exec(codedict,
|
||||
extra_environ={"caller":caller}, debug=debug)
|
||||
extra_environ={"caller": caller}, debug=debug)
|
||||
if err:
|
||||
caller.msg(format_code(err))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def step_pointer(caller, step=1):
|
||||
"""
|
||||
Step in stack, returning the item located.
|
||||
|
|
@ -156,7 +160,8 @@ def step_pointer(caller, step=1):
|
|||
caller.msg("{RBeginning of batch file.")
|
||||
if ptr + step >= nstack:
|
||||
caller.msg("{REnd of batch file.")
|
||||
caller.ndb.batch_stackptr = max(0, min(nstack-1, ptr + step))
|
||||
caller.ndb.batch_stackptr = max(0, min(nstack - 1, ptr + step))
|
||||
|
||||
|
||||
def show_curr(caller, showall=False):
|
||||
"""
|
||||
|
|
@ -186,6 +191,7 @@ def show_curr(caller, showall=False):
|
|||
string += "\n{G|{n %s" % line
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
def purge_processor(caller):
|
||||
"""
|
||||
This purges all effects running
|
||||
|
|
@ -201,12 +207,13 @@ def purge_processor(caller):
|
|||
# clear everything but the default cmdset.
|
||||
caller.cmdset.delete(BatchSafeCmdSet)
|
||||
caller.cmdset.clear()
|
||||
caller.scripts.validate() # this will purge interactive mode
|
||||
caller.scripts.validate() # this will purge interactive mode
|
||||
|
||||
#------------------------------------------------------------
|
||||
# main access commands
|
||||
#------------------------------------------------------------
|
||||
|
||||
|
||||
class CmdBatchCommands(MuxCommand):
|
||||
"""
|
||||
Build from batch-command file
|
||||
|
|
@ -275,19 +282,25 @@ class CmdBatchCommands(MuxCommand):
|
|||
procpool = False
|
||||
if "PythonProcPool" in utils.server_services():
|
||||
if utils.uses_database("sqlite3"):
|
||||
caller.msg("Batchprocessor disabled ProcPool under SQLite3.")
|
||||
caller.msg("Batchprocessor disabled ProcPool under SQLite3.")
|
||||
else:
|
||||
procpool=True
|
||||
procpool = True
|
||||
|
||||
if procpool:
|
||||
# run in parallel process
|
||||
def callback(r):
|
||||
caller.msg(" {GBatchfile '%s' applied." % python_path)
|
||||
purge_processor(caller)
|
||||
|
||||
def errback(e):
|
||||
caller.msg(" {RError from processor: '%s'" % e)
|
||||
purge_processor(caller)
|
||||
utils.run_async(_PROCPOOL_BATCHCMD_SOURCE, commands=commands, caller=caller, at_return=callback, at_err=errback)
|
||||
|
||||
utils.run_async(_PROCPOOL_BATCHCMD_SOURCE,
|
||||
commands=commands,
|
||||
caller=caller,
|
||||
at_return=callback,
|
||||
at_err=errback)
|
||||
else:
|
||||
# run in-process (might block)
|
||||
for inum in range(len(commands)):
|
||||
|
|
@ -295,11 +308,13 @@ class CmdBatchCommands(MuxCommand):
|
|||
if not batch_cmd_exec(caller):
|
||||
return
|
||||
step_pointer(caller, 1)
|
||||
# clean out the safety cmdset and clean out all other temporary attrs.
|
||||
# clean out the safety cmdset and clean out all other
|
||||
# temporary attrs.
|
||||
string = " Batchfile '%s' applied." % python_path
|
||||
caller.msg("{G%s" % string)
|
||||
purge_processor(caller)
|
||||
|
||||
|
||||
class CmdBatchCode(MuxCommand):
|
||||
"""
|
||||
Build from batch-code file
|
||||
|
|
@ -352,7 +367,7 @@ class CmdBatchCode(MuxCommand):
|
|||
|
||||
debug = False
|
||||
if 'debug' in switches:
|
||||
debug = True
|
||||
debug = True
|
||||
|
||||
# Store work data in cache
|
||||
caller.ndb.batch_stack = codes
|
||||
|
|
@ -376,18 +391,23 @@ class CmdBatchCode(MuxCommand):
|
|||
procpool = False
|
||||
if "PythonProcPool" in utils.server_services():
|
||||
if utils.uses_database("sqlite3"):
|
||||
caller.msg("Batchprocessor disabled ProcPool under SQLite3.")
|
||||
caller.msg("Batchprocessor disabled ProcPool under SQLite3.")
|
||||
else:
|
||||
procpool=True
|
||||
procpool = True
|
||||
if procpool:
|
||||
# run in parallel process
|
||||
def callback(r):
|
||||
caller.msg(" {GBatchfile '%s' applied." % python_path)
|
||||
purge_processor(caller)
|
||||
|
||||
def errback(e):
|
||||
caller.msg(" {RError from processor: '%s'" % e)
|
||||
purge_processor(caller)
|
||||
utils.run_async(_PROCPOOL_BATCHCODE_SOURCE, codes=codes, caller=caller, at_return=callback, at_err=errback)
|
||||
utils.run_async(_PROCPOOL_BATCHCODE_SOURCE,
|
||||
codes=codes,
|
||||
caller=caller,
|
||||
at_return=callback,
|
||||
at_err=errback)
|
||||
else:
|
||||
# un in-process (will block)
|
||||
for inum in range(len(codes)):
|
||||
|
|
@ -395,7 +415,8 @@ class CmdBatchCode(MuxCommand):
|
|||
if not batch_code_exec(caller):
|
||||
return
|
||||
step_pointer(caller, 1)
|
||||
# clean out the safety cmdset and clean out all other temporary attrs.
|
||||
# clean out the safety cmdset and clean out all other
|
||||
# temporary attrs.
|
||||
string = " Batchfile '%s' applied." % python_path
|
||||
caller.msg("{G%s" % string)
|
||||
purge_processor(caller)
|
||||
|
|
@ -423,6 +444,7 @@ class CmdStateAbort(MuxCommand):
|
|||
purge_processor(self.caller)
|
||||
self.caller.msg("Exited processor and reset out active cmdset back to the default one.")
|
||||
|
||||
|
||||
class CmdStateLL(MuxCommand):
|
||||
"""
|
||||
ll
|
||||
|
|
@ -479,6 +501,7 @@ class CmdStateRR(MuxCommand):
|
|||
caller.msg(format_code("File reloaded. Staying on same command."))
|
||||
show_curr(caller)
|
||||
|
||||
|
||||
class CmdStateRRR(MuxCommand):
|
||||
"""
|
||||
rrr
|
||||
|
|
@ -500,6 +523,7 @@ class CmdStateRRR(MuxCommand):
|
|||
caller.msg(format_code("File reloaded. Restarting from top."))
|
||||
show_curr(caller)
|
||||
|
||||
|
||||
class CmdStateNN(MuxCommand):
|
||||
"""
|
||||
nn
|
||||
|
|
@ -520,6 +544,7 @@ class CmdStateNN(MuxCommand):
|
|||
step_pointer(caller, step)
|
||||
show_curr(caller)
|
||||
|
||||
|
||||
class CmdStateNL(MuxCommand):
|
||||
"""
|
||||
nl
|
||||
|
|
@ -541,6 +566,7 @@ class CmdStateNL(MuxCommand):
|
|||
step_pointer(caller, step)
|
||||
show_curr(caller, showall=True)
|
||||
|
||||
|
||||
class CmdStateBB(MuxCommand):
|
||||
"""
|
||||
bb
|
||||
|
|
@ -562,6 +588,7 @@ class CmdStateBB(MuxCommand):
|
|||
step_pointer(caller, step)
|
||||
show_curr(caller)
|
||||
|
||||
|
||||
class CmdStateBL(MuxCommand):
|
||||
"""
|
||||
bl
|
||||
|
|
@ -583,6 +610,7 @@ class CmdStateBL(MuxCommand):
|
|||
step_pointer(caller, step)
|
||||
show_curr(caller, showall=True)
|
||||
|
||||
|
||||
class CmdStateSS(MuxCommand):
|
||||
"""
|
||||
ss [steps]
|
||||
|
|
@ -611,6 +639,7 @@ class CmdStateSS(MuxCommand):
|
|||
step_pointer(caller, 1)
|
||||
show_curr(caller)
|
||||
|
||||
|
||||
class CmdStateSL(MuxCommand):
|
||||
"""
|
||||
sl [steps]
|
||||
|
|
@ -639,6 +668,7 @@ class CmdStateSL(MuxCommand):
|
|||
step_pointer(caller, 1)
|
||||
show_curr(caller)
|
||||
|
||||
|
||||
class CmdStateCC(MuxCommand):
|
||||
"""
|
||||
cc
|
||||
|
|
@ -670,6 +700,7 @@ class CmdStateCC(MuxCommand):
|
|||
del caller.ndb.batch_batchmode
|
||||
caller.msg(format_code("Finished processing batch file."))
|
||||
|
||||
|
||||
class CmdStateJJ(MuxCommand):
|
||||
"""
|
||||
j <command number>
|
||||
|
|
@ -684,7 +715,7 @@ class CmdStateJJ(MuxCommand):
|
|||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
number = int(self.args)-1
|
||||
number = int(self.args) - 1
|
||||
else:
|
||||
caller.msg(format_code("You must give a number index."))
|
||||
return
|
||||
|
|
@ -693,6 +724,7 @@ class CmdStateJJ(MuxCommand):
|
|||
step_pointer(caller, step)
|
||||
show_curr(caller)
|
||||
|
||||
|
||||
class CmdStateJL(MuxCommand):
|
||||
"""
|
||||
jl <command number>
|
||||
|
|
@ -707,7 +739,7 @@ class CmdStateJL(MuxCommand):
|
|||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
number = int(self.args)-1
|
||||
number = int(self.args) - 1
|
||||
else:
|
||||
caller.msg(format_code("You must give a number index."))
|
||||
return
|
||||
|
|
@ -716,6 +748,7 @@ class CmdStateJL(MuxCommand):
|
|||
step_pointer(caller, step)
|
||||
show_curr(caller, showall=True)
|
||||
|
||||
|
||||
class CmdStateQQ(MuxCommand):
|
||||
"""
|
||||
qq
|
||||
|
|
@ -730,6 +763,7 @@ class CmdStateQQ(MuxCommand):
|
|||
purge_processor(self.caller)
|
||||
self.caller.msg("Aborted interactive batch mode.")
|
||||
|
||||
|
||||
class CmdStateHH(MuxCommand):
|
||||
"Help command"
|
||||
|
||||
|
|
@ -766,7 +800,6 @@ class CmdStateHH(MuxCommand):
|
|||
self.caller.msg(string)
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Defining the cmdsets for the interactive batchprocessor
|
||||
|
|
@ -781,12 +814,13 @@ class BatchSafeCmdSet(CmdSet):
|
|||
always be available to get out of everything.
|
||||
"""
|
||||
key = "Batch_default"
|
||||
priority = 104 # override other cmdsets.
|
||||
priority = 104 # override other cmdsets.
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"Init the cmdset"
|
||||
self.add(CmdStateAbort())
|
||||
|
||||
|
||||
class BatchInteractiveCmdSet(CmdSet):
|
||||
"""
|
||||
The cmdset for the interactive batch processor mode.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ except ImportError:
|
|||
# used by @find
|
||||
CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
|
||||
|
||||
|
||||
class ObjManipCommand(MuxCommand):
|
||||
"""
|
||||
This is a parent class for some of the defining objmanip commands
|
||||
|
|
@ -60,7 +61,7 @@ class ObjManipCommand(MuxCommand):
|
|||
# get all the normal parsing done (switches etc)
|
||||
super(ObjManipCommand, self).parse()
|
||||
|
||||
obj_defs = ([],[]) # stores left- and right-hand side of '='
|
||||
obj_defs = ([], []) # stores left- and right-hand side of '='
|
||||
obj_attrs = ([], []) # "
|
||||
|
||||
for iside, arglist in enumerate((self.lhslist, self.rhslist)):
|
||||
|
|
@ -101,7 +102,7 @@ class CmdSetObjAlias(MuxCommand):
|
|||
by everyone.
|
||||
"""
|
||||
|
||||
key = "@alias"
|
||||
key = "@alias"
|
||||
aliases = "@setobjalias"
|
||||
locks = "cmd:perm(setobjalias) or perm(Builders)"
|
||||
help_category = "Building"
|
||||
|
|
@ -121,7 +122,7 @@ class CmdSetObjAlias(MuxCommand):
|
|||
obj = caller.search(objname)
|
||||
if not obj:
|
||||
return
|
||||
if self.rhs == None:
|
||||
if self.rhs is None:
|
||||
# no =, so we just list aliases on object.
|
||||
aliases = obj.aliases.all()
|
||||
if aliases:
|
||||
|
|
@ -146,15 +147,18 @@ class CmdSetObjAlias(MuxCommand):
|
|||
|
||||
# merge the old and new aliases (if any)
|
||||
old_aliases = obj.aliases.all()
|
||||
new_aliases = [alias.strip().lower() for alias in self.rhs.split(',') if alias.strip()]
|
||||
new_aliases = [alias.strip().lower() for alias in self.rhs.split(',')
|
||||
if alias.strip()]
|
||||
# make the aliases only appear once
|
||||
old_aliases.extend(new_aliases)
|
||||
aliases = list(set(old_aliases))
|
||||
# save back to object.
|
||||
obj.aliases.add(aliases)
|
||||
# we treat this as a re-caching (relevant for exits to re-build their exit commands with the correct aliases)
|
||||
# we treat this as a re-caching (relevant for exits to re-build their
|
||||
# exit commands with the correct aliases)
|
||||
caller.msg("Alias(es) for '%s' set to %s." % (obj.key, str(obj.aliases)))
|
||||
|
||||
|
||||
class CmdCopy(ObjManipCommand):
|
||||
"""
|
||||
@copy - copy objects
|
||||
|
|
@ -167,8 +171,8 @@ class CmdCopy(ObjManipCommand):
|
|||
removing any changes that might have been made to the original
|
||||
since it was first created.
|
||||
|
||||
Create one or more copies of an object. If you don't supply any targets, one exact copy
|
||||
of the original object will be created with the name *_copy.
|
||||
Create one or more copies of an object. If you don't supply any targets,
|
||||
one exact copt of the original object will be created with the name *_copy.
|
||||
"""
|
||||
|
||||
key = "@copy"
|
||||
|
|
@ -210,12 +214,15 @@ class CmdCopy(ObjManipCommand):
|
|||
to_obj_aliases = objdef['aliases']
|
||||
to_obj_location = objdef['option']
|
||||
if to_obj_location:
|
||||
to_obj_location = caller.search(to_obj_location, global_search=True)
|
||||
to_obj_location = caller.search(to_obj_location,
|
||||
global_search=True)
|
||||
if not to_obj_location:
|
||||
return
|
||||
|
||||
copiedobj = ObjectDB.objects.copy_object(from_obj, new_key=to_obj_name,
|
||||
new_location=to_obj_location, new_aliases=to_obj_aliases)
|
||||
copiedobj = ObjectDB.objects.copy_object(from_obj,
|
||||
new_key=to_obj_name,
|
||||
new_location=to_obj_location,
|
||||
new_aliases=to_obj_aliases)
|
||||
if copiedobj:
|
||||
string = "Copied %s to '%s' (aliases: %s)." % (from_obj_name, to_obj_name,
|
||||
to_obj_aliases)
|
||||
|
|
@ -225,6 +232,7 @@ class CmdCopy(ObjManipCommand):
|
|||
# we are done, echo to user
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdCpAttr(ObjManipCommand):
|
||||
"""
|
||||
@cpattr - copy attributes
|
||||
|
|
@ -244,8 +252,8 @@ class CmdCpAttr(ObjManipCommand):
|
|||
copies the coolness attribute (defined on yourself), to attributes
|
||||
on Anna and Tom.
|
||||
|
||||
Copy the attribute one object to one or more attributes on another object. If
|
||||
you don't supply a source object, yourself is used.
|
||||
Copy the attribute one object to one or more attributes on another object.
|
||||
If you don't supply a source object, yourself is used.
|
||||
"""
|
||||
key = "@cpattr"
|
||||
locks = "cmd:perm(cpattr) or perm(Builders)"
|
||||
|
|
@ -272,7 +280,8 @@ class CmdCpAttr(ObjManipCommand):
|
|||
from_obj_attrs = lhs_objattr[0]['attrs']
|
||||
|
||||
if not from_obj_attrs:
|
||||
# this means the from_obj_name is actually an attribute name on self.
|
||||
# this means the from_obj_name is actually an attribute
|
||||
# name on self.
|
||||
from_obj_attrs = [from_obj_name]
|
||||
from_obj = self.caller
|
||||
from_obj_name = self.caller.name
|
||||
|
|
@ -282,7 +291,8 @@ class CmdCpAttr(ObjManipCommand):
|
|||
caller.msg("You have to supply both source object and target(s).")
|
||||
return
|
||||
if not from_obj.attributes.has(from_obj_attrs[0]):
|
||||
caller.msg("%s doesn't have an attribute %s." % (from_obj_name, from_obj_attrs[0]))
|
||||
caller.msg("%s doesn't have an attribute %s." % (from_obj_name,
|
||||
from_obj_attrs[0]))
|
||||
return
|
||||
srcvalue = from_obj.attributes.get(from_obj_attrs[0])
|
||||
|
||||
|
|
@ -291,7 +301,8 @@ class CmdCpAttr(ObjManipCommand):
|
|||
string = "Moving "
|
||||
else:
|
||||
string = "Copying "
|
||||
string += "%s/%s (with value %s) ..." % (from_obj_name, from_obj_attrs[0], srcvalue)
|
||||
string += "%s/%s (with value %s) ..." % (from_obj_name,
|
||||
from_obj_attrs[0], srcvalue)
|
||||
|
||||
for to_obj in to_objs:
|
||||
to_obj_name = to_obj['name']
|
||||
|
|
@ -308,15 +319,20 @@ class CmdCpAttr(ObjManipCommand):
|
|||
# on the to_obj, we copy the original name instead.
|
||||
to_attr = from_attr
|
||||
to_obj.attributes.add(to_attr, srcvalue)
|
||||
if "move" in self.switches and not (from_obj == to_obj and from_attr == to_attr):
|
||||
if ("move" in self.switches and not (from_obj == to_obj and
|
||||
from_attr == to_attr)):
|
||||
from_obj.del_attribute(from_attr)
|
||||
string += "\nMoved %s.%s -> %s.%s." % (from_obj.name, from_attr,
|
||||
string += "\nMoved %s.%s -> %s.%s." % (from_obj.name,
|
||||
from_attr,
|
||||
to_obj_name, to_attr)
|
||||
else:
|
||||
string += "\nCopied %s.%s -> %s.%s." % (from_obj.name, from_attr,
|
||||
to_obj_name, to_attr)
|
||||
string += "\nCopied %s.%s -> %s.%s." % (from_obj.name,
|
||||
from_attr,
|
||||
to_obj_name,
|
||||
to_attr)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdMvAttr(ObjManipCommand):
|
||||
"""
|
||||
@mvattr - move attributes
|
||||
|
|
@ -330,8 +346,8 @@ class CmdMvAttr(ObjManipCommand):
|
|||
Switches:
|
||||
copy - Don't delete the original after moving.
|
||||
|
||||
Move an attribute from one object to one or more attributes on another object. If
|
||||
you don't supply a source object, yourself is used.
|
||||
Move an attribute from one object to one or more attributes on another
|
||||
object. If you don't supply a source object, yourself is used.
|
||||
"""
|
||||
key = "@mvattr"
|
||||
locks = "cmd:perm(mvattr) or perm(Builders)"
|
||||
|
|
@ -356,6 +372,7 @@ class CmdMvAttr(ObjManipCommand):
|
|||
else:
|
||||
self.caller.execute_cmd("@cpattr/move %s" % self.args)
|
||||
|
||||
|
||||
class CmdCreate(ObjManipCommand):
|
||||
"""
|
||||
@create - create new objects
|
||||
|
|
@ -364,8 +381,9 @@ class CmdCreate(ObjManipCommand):
|
|||
@create[/drop] objname[;alias;alias...][:typeclass], objname...
|
||||
|
||||
switch:
|
||||
drop - automatically drop the new object into your current location (this is not echoed)
|
||||
this also sets the new object's home to the current location rather than to you.
|
||||
drop - automatically drop the new object into your current
|
||||
location (this is not echoed). This also sets the new
|
||||
object's home to the current location rather than to you.
|
||||
|
||||
Creates one or more new objects. If typeclass is given, the object
|
||||
is created as a child of this typeclass. The typeclass script is
|
||||
|
|
@ -406,7 +424,8 @@ class CmdCreate(ObjManipCommand):
|
|||
# object typeclass will automatically be used)
|
||||
lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id)
|
||||
obj = create.create_object(typeclass, name, caller,
|
||||
home=caller, aliases=aliases, locks=lockstring, report_to=caller)
|
||||
home=caller, aliases=aliases,
|
||||
locks=lockstring, report_to=caller)
|
||||
if not obj:
|
||||
continue
|
||||
if aliases:
|
||||
|
|
@ -423,7 +442,8 @@ class CmdCreate(ObjManipCommand):
|
|||
obj.home = caller.location
|
||||
obj.move_to(caller.location, quiet=True)
|
||||
if string:
|
||||
caller.msg(string)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdDesc(MuxCommand):
|
||||
"""
|
||||
|
|
@ -471,8 +491,8 @@ class CmdDestroy(MuxCommand):
|
|||
@destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]
|
||||
|
||||
switches:
|
||||
override - The @destroy command will usually avoid accidentally destroying
|
||||
player objects. This switch overrides this safety.
|
||||
override - The @destroy command will usually avoid accidentally
|
||||
destroying player objects. This switch overrides this safety.
|
||||
examples:
|
||||
@destroy house, roof, door, 44-78
|
||||
@destroy 5-10, flower, 45
|
||||
|
|
@ -502,7 +522,8 @@ class CmdDestroy(MuxCommand):
|
|||
if not obj:
|
||||
self.caller.msg(" (Objects to destroy must either be local or specified with a unique #dbref.)")
|
||||
return ""
|
||||
if not "override" in self.switches and obj.dbid == int(settings.CHARACTER_DEFAULT_HOME.lstrip("#")):
|
||||
if (not "override" in self.switches and
|
||||
obj.dbid == int(settings.CHARACTER_DEFAULT_HOME.lstrip("#"))):
|
||||
return "\nYou are trying to delete CHARACTER_DEFAULT_HOME. If you want to do this, use the /override switch."
|
||||
objname = obj.name
|
||||
if not obj.access(caller, 'delete'):
|
||||
|
|
@ -529,9 +550,10 @@ class CmdDestroy(MuxCommand):
|
|||
for objname in self.lhslist:
|
||||
if '-' in objname:
|
||||
# might be a range of dbrefs
|
||||
dmin, dmax = [utils.dbref(part, reqhash=False) for part in objname.split('-', 1)]
|
||||
dmin, dmax = [utils.dbref(part, reqhash=False)
|
||||
for part in objname.split('-', 1)]
|
||||
if dmin and dmax:
|
||||
for dbref in range(int(dmin),int(dmax+1)):
|
||||
for dbref in range(int(dmin), int(dmax + 1)):
|
||||
string += delobj("#" + str(dbref), True)
|
||||
else:
|
||||
string += delobj(objname)
|
||||
|
|
@ -558,9 +580,11 @@ class CmdDig(ObjManipCommand):
|
|||
@dig house:myrooms.MyHouseTypeclass
|
||||
@dig sheer cliff;cliff;sheer = climb up, climb down
|
||||
|
||||
This command is a convenient way to build rooms quickly; it creates the new room and you can optionally
|
||||
set up exits back and forth between your current room and the new one. You can add as many aliases as you
|
||||
like to the name of the room and the exits in question; an example would be 'north;no;n'.
|
||||
This command is a convenient way to build rooms quickly; it creates the
|
||||
new room and you can optionally set up exits back and forth between your
|
||||
current room and the new one. You can add as many aliases as you
|
||||
like to the name of the room and the exits in question; an example
|
||||
would be 'north;no;n'.
|
||||
"""
|
||||
key = "@dig"
|
||||
locks = "cmd:perm(dig) or perm(Builders)"
|
||||
|
|
@ -595,13 +619,14 @@ class CmdDig(ObjManipCommand):
|
|||
lockstring = lockstring % (caller.dbref, caller.dbref, caller.dbref)
|
||||
|
||||
new_room = create.create_object(typeclass, room["name"],
|
||||
aliases=room["aliases"], report_to=caller)
|
||||
aliases=room["aliases"],
|
||||
report_to=caller)
|
||||
new_room.locks.add(lockstring)
|
||||
alias_string = ""
|
||||
if new_room.aliases.all():
|
||||
alias_string = " (%s)" % ", ".join(new_room.aliases.all())
|
||||
room_string = "Created room %s(%s)%s of type %s." % (new_room, new_room.dbref, alias_string, typeclass)
|
||||
|
||||
room_string = "Created room %s(%s)%s of type %s." % (new_room,
|
||||
new_room.dbref, alias_string, typeclass)
|
||||
|
||||
# create exit to room
|
||||
|
||||
|
|
@ -622,15 +647,21 @@ class CmdDig(ObjManipCommand):
|
|||
if not typeclass:
|
||||
typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
|
||||
new_to_exit = create.create_object(typeclass, to_exit["name"], location,
|
||||
new_to_exit = create.create_object(typeclass, to_exit["name"],
|
||||
location,
|
||||
aliases=to_exit["aliases"],
|
||||
locks=lockstring, destination=new_room, report_to=caller)
|
||||
locks=lockstring,
|
||||
destination=new_room,
|
||||
report_to=caller)
|
||||
alias_string = ""
|
||||
if new_to_exit.aliases.all():
|
||||
alias_string = " (%s)" % ", ".join(new_to_exit.aliases.all())
|
||||
exit_to_string = "\nCreated Exit from %s to %s: %s(%s)%s."
|
||||
exit_to_string = exit_to_string % (location.name, new_room.name, new_to_exit,
|
||||
new_to_exit.dbref, alias_string)
|
||||
exit_to_string = exit_to_string % (location.name,
|
||||
new_room.name,
|
||||
new_to_exit,
|
||||
new_to_exit.dbref,
|
||||
alias_string)
|
||||
|
||||
# Create exit back from new room
|
||||
|
||||
|
|
@ -647,15 +678,22 @@ class CmdDig(ObjManipCommand):
|
|||
typeclass = back_exit["option"]
|
||||
if not typeclass:
|
||||
typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
new_back_exit = create.create_object(typeclass, back_exit["name"],
|
||||
new_room, aliases=back_exit["aliases"],
|
||||
locks=lockstring, destination=location, report_to=caller)
|
||||
new_back_exit = create.create_object(typeclass,
|
||||
back_exit["name"],
|
||||
new_room,
|
||||
aliases=back_exit["aliases"],
|
||||
locks=lockstring,
|
||||
destination=location,
|
||||
report_to=caller)
|
||||
alias_string = ""
|
||||
if new_back_exit.aliases.all():
|
||||
alias_string = " (%s)" % ", ".join(new_back_exit.aliases.all())
|
||||
exit_back_string = "\nCreated Exit back from %s to %s: %s(%s)%s."
|
||||
exit_back_string = exit_back_string % (new_room.name, location.name,
|
||||
new_back_exit, new_back_exit.dbref, alias_string)
|
||||
exit_back_string = exit_back_string % (new_room.name,
|
||||
location.name,
|
||||
new_back_exit,
|
||||
new_back_exit.dbref,
|
||||
alias_string)
|
||||
caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string))
|
||||
if new_room and ('teleport' in self.switches or "tel" in self.switches):
|
||||
caller.move_to(new_room)
|
||||
|
|
@ -693,18 +731,18 @@ class CmdTunnel(MuxCommand):
|
|||
help_category = "Building"
|
||||
|
||||
# store the direction, full name and its opposite
|
||||
directions = {"n" : ("north", "s"),
|
||||
directions = {"n": ("north", "s"),
|
||||
"ne": ("northeast", "sw"),
|
||||
"e" : ("east", "w"),
|
||||
"e": ("east", "w"),
|
||||
"se": ("southeast", "nw"),
|
||||
"s" : ("south", "n"),
|
||||
"s": ("south", "n"),
|
||||
"sw": ("southwest", "ne"),
|
||||
"w" : ("west", "e"),
|
||||
"w": ("west", "e"),
|
||||
"nw": ("northwest", "se"),
|
||||
"u" : ("up", "d"),
|
||||
"d" : ("down", "u"),
|
||||
"i" : ("in", "o"),
|
||||
"o" : ("out", "i")}
|
||||
"u": ("up", "d"),
|
||||
"d": ("down", "u"),
|
||||
"i": ("in", "o"),
|
||||
"o": ("out", "i")}
|
||||
|
||||
def func(self):
|
||||
"Implements the tunnel command"
|
||||
|
|
@ -725,7 +763,7 @@ class CmdTunnel(MuxCommand):
|
|||
|
||||
roomname = "Some place"
|
||||
if self.rhs:
|
||||
roomname = self.rhs # this may include aliases; that's fine.
|
||||
roomname = self.rhs # this may include aliases; that's fine.
|
||||
|
||||
telswitch = ""
|
||||
if "tel" in self.switches:
|
||||
|
|
@ -735,9 +773,11 @@ class CmdTunnel(MuxCommand):
|
|||
backstring = ", %s;%s" % (backname, backshort)
|
||||
|
||||
# build the string we will use to call @dig
|
||||
digstring = "@dig%s %s = %s;%s%s" % (telswitch, roomname, exitname, exitshort, backstring)
|
||||
digstring = "@dig%s %s = %s;%s%s" % (telswitch, roomname,
|
||||
exitname, exitshort, backstring)
|
||||
self.caller.execute_cmd(digstring)
|
||||
|
||||
|
||||
class CmdLink(MuxCommand):
|
||||
"""
|
||||
@link - connect objects
|
||||
|
|
@ -754,8 +794,9 @@ class CmdLink(MuxCommand):
|
|||
If <object> is an exit, set its destination to <target>. Two-way operation
|
||||
instead sets the destination to the *locations* of the respective given
|
||||
arguments.
|
||||
The second form (a lone =) sets the destination to None (same as the @unlink command)
|
||||
and the third form (without =) just shows the currently set destination.
|
||||
The second form (a lone =) sets the destination to None (same as
|
||||
the @unlink command) and the third form (without =) just shows the
|
||||
currently set destination.
|
||||
"""
|
||||
|
||||
key = "@link"
|
||||
|
|
@ -802,7 +843,7 @@ class CmdLink(MuxCommand):
|
|||
obj.destination = target
|
||||
string += "\nLink created %s -> %s (one way)." % (obj.name, target)
|
||||
|
||||
elif self.rhs == None:
|
||||
elif self.rhs is None:
|
||||
# this means that no = was given (otherwise rhs
|
||||
# would have been an empty string). So we inspect
|
||||
# the home/destination on object
|
||||
|
|
@ -823,6 +864,7 @@ class CmdLink(MuxCommand):
|
|||
# give feedback
|
||||
caller.msg(string.strip())
|
||||
|
||||
|
||||
class CmdUnLink(CmdLink):
|
||||
"""
|
||||
@unlink - unconnect objects
|
||||
|
|
@ -857,6 +899,7 @@ class CmdUnLink(CmdLink):
|
|||
# call the @link functionality
|
||||
super(CmdUnLink, self).func()
|
||||
|
||||
|
||||
class CmdSetHome(CmdLink):
|
||||
"""
|
||||
@home - control an object's home location
|
||||
|
|
@ -893,7 +936,8 @@ class CmdSetHome(CmdLink):
|
|||
if not home:
|
||||
string = "This object has no home location set!"
|
||||
else:
|
||||
string = "%s's current home is %s(%s)." % (obj, home, home.dbref)
|
||||
string = "%s's current home is %s(%s)." % (obj, home,
|
||||
home.dbref)
|
||||
else:
|
||||
# set a home location
|
||||
new_home = self.caller.search(self.rhs, global_search=True)
|
||||
|
|
@ -907,6 +951,7 @@ class CmdSetHome(CmdLink):
|
|||
string = "%s' home location was set to %s(%s)." % (obj, new_home, new_home.dbref)
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
class CmdListCmdSets(MuxCommand):
|
||||
"""
|
||||
list command sets on an object
|
||||
|
|
@ -935,6 +980,7 @@ class CmdListCmdSets(MuxCommand):
|
|||
string = "%s" % obj.cmdset
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdName(ObjManipCommand):
|
||||
"""
|
||||
cname - change the name and/or aliases of an object
|
||||
|
|
@ -1006,7 +1052,8 @@ class CmdOpen(ObjManipCommand):
|
|||
help_category = "Building"
|
||||
|
||||
# a custom member method to chug out exits and do checks
|
||||
def create_exit(self, exit_name, location, destination, exit_aliases=None, typeclass=None):
|
||||
def create_exit(self, exit_name, location, destination,
|
||||
exit_aliases=None, typeclass=None):
|
||||
"""
|
||||
Helper function to avoid code duplication.
|
||||
At this point we know destination is a valid location
|
||||
|
|
@ -1047,9 +1094,11 @@ class CmdOpen(ObjManipCommand):
|
|||
# exit does not exist before. Create a new one.
|
||||
if not typeclass:
|
||||
typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
exit_obj = create.create_object(typeclass, key=exit_name,
|
||||
exit_obj = create.create_object(typeclass,
|
||||
key=exit_name,
|
||||
location=location,
|
||||
aliases=exit_aliases, report_to=caller)
|
||||
aliases=exit_aliases,
|
||||
report_to=caller)
|
||||
if exit_obj:
|
||||
# storing a destination is what makes it an exit!
|
||||
exit_obj.destination = destination
|
||||
|
|
@ -1095,7 +1144,11 @@ class CmdOpen(ObjManipCommand):
|
|||
return
|
||||
|
||||
# Create exit
|
||||
ok = self.create_exit(exit_name, location, destination, exit_aliases, exit_typeclass)
|
||||
ok = self.create_exit(exit_name,
|
||||
location,
|
||||
destination,
|
||||
exit_aliases,
|
||||
exit_typeclass)
|
||||
if not ok:
|
||||
# an error; the exit was not created, so we quit.
|
||||
return
|
||||
|
|
@ -1104,7 +1157,11 @@ class CmdOpen(ObjManipCommand):
|
|||
back_exit_name = self.lhs_objs[1]['name']
|
||||
back_exit_aliases = self.lhs_objs[1]['aliases']
|
||||
back_exit_typeclass = self.lhs_objs[1]['option']
|
||||
ok = self.create_exit(back_exit_name, destination, location, back_exit_aliases, back_exit_typeclass)
|
||||
ok = self.create_exit(back_exit_name,
|
||||
destination,
|
||||
location,
|
||||
back_exit_aliases,
|
||||
back_exit_typeclass)
|
||||
|
||||
|
||||
class CmdSetAttribute(ObjManipCommand):
|
||||
|
|
@ -1126,7 +1183,8 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
numbers. You can however also set Python primities such as lists,
|
||||
dictionaries and tuples on objects (this might be important for
|
||||
the functionality of certain custom objects). This is indicated
|
||||
by you starting your value with one of {c'{n, {c"{n, {c({n, {c[{n or {c{ {n.
|
||||
by you starting your value with one of {c'{n, {c"{n, {c({n, {c[{n
|
||||
or {c{ {n.
|
||||
Note that you should leave a space after starting a dictionary ('{ ')
|
||||
so as to not confuse the dictionary start with a colour code like \{g.
|
||||
Remember that if you use Python primitives like this, you must
|
||||
|
|
@ -1169,10 +1227,14 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
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
|
||||
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."
|
||||
|
|
@ -1182,7 +1244,8 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
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]))
|
||||
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
|
||||
|
|
@ -1198,7 +1261,8 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
self.caller.msg(string)
|
||||
return utils.to_str(strobj)
|
||||
else:
|
||||
# fall back to old recursive solution (does not support nested lists/dicts)
|
||||
# fall back to old recursive solution (does not support
|
||||
# nested lists/dicts)
|
||||
return rec_convert(strobj.strip())
|
||||
|
||||
def func(self):
|
||||
|
|
@ -1223,17 +1287,18 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
|
||||
string = ""
|
||||
if not value:
|
||||
if self.rhs == None:
|
||||
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()]
|
||||
for attr in attrs:
|
||||
if obj.attributes.has(attr):
|
||||
string += "\nAttribute %s/%s = %s" % (obj.name, attr, obj.attributes.get(attr))
|
||||
string += "\nAttribute %s/%s = %s" % (obj.name, attr,
|
||||
obj.attributes.get(attr))
|
||||
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(), data={"raw": True})
|
||||
return
|
||||
else:
|
||||
# deleting the attribute(s)
|
||||
|
|
@ -1252,9 +1317,11 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
string += "\nCreated attribute %s/%s = %s" % (obj.name, attr, value)
|
||||
except SyntaxError:
|
||||
# this means literal_eval tried to parse a faulty string
|
||||
string = "{RCritical Python syntax error in your value. Only primitive Python structures"
|
||||
string += "\nare allowed. You also need to use correct Python syntax. Remember especially"
|
||||
string += "\nto put quotes around all strings inside lists and dicts.{n"
|
||||
string = "{RCritical Python syntax error in your value. "
|
||||
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"
|
||||
# send feedback
|
||||
caller.msg(string.strip('\n'))
|
||||
|
||||
|
|
@ -1314,7 +1381,8 @@ class CmdTypeclass(MuxCommand):
|
|||
# we did not supply a new typeclass, view the
|
||||
# current one instead.
|
||||
if hasattr(obj, "typeclass"):
|
||||
string = "%s's current typeclass is '%s' (%s)." % (obj.name, obj.typeclass.typename, obj.typeclass.path)
|
||||
string = "%s's current typeclass is '%s' (%s)." % (obj.name,
|
||||
obj.typeclass.typename, obj.typeclass.path)
|
||||
else:
|
||||
string = "%s is not a typed object." % obj.name
|
||||
caller.msg(string)
|
||||
|
|
@ -1343,8 +1411,8 @@ class CmdTypeclass(MuxCommand):
|
|||
string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.typeclass.path)
|
||||
else:
|
||||
string = "%s changed typeclass from %s to %s.\n" % (obj.name,
|
||||
old_typeclass_path,
|
||||
obj.typeclass_path)
|
||||
old_typeclass_path,
|
||||
obj.typeclass_path)
|
||||
string += "Creation hooks were run."
|
||||
if reset:
|
||||
string += " All old attributes where deleted before the swap."
|
||||
|
|
@ -1354,8 +1422,8 @@ class CmdTypeclass(MuxCommand):
|
|||
else:
|
||||
string = obj.typeclass_last_errmsg
|
||||
string += "\nCould not swap '%s' (%s) to typeclass '%s'." % (obj.name,
|
||||
old_typeclass_path,
|
||||
typeclass)
|
||||
old_typeclass_path,
|
||||
typeclass)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
|
|
@ -1410,6 +1478,7 @@ class CmdWipe(ObjManipCommand):
|
|||
string = string % (",".join(attrs), obj.name)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdLock(ObjManipCommand):
|
||||
"""
|
||||
lock - assign a lock definition to an object
|
||||
|
|
@ -1493,6 +1562,7 @@ class CmdLock(ObjManipCommand):
|
|||
return
|
||||
caller.msg(obj.locks)
|
||||
|
||||
|
||||
class CmdExamine(ObjManipCommand):
|
||||
"""
|
||||
examine - detailed info on objects
|
||||
|
|
@ -1545,7 +1615,9 @@ class CmdExamine(ObjManipCommand):
|
|||
else:
|
||||
db_attr = [(attr.key, attr.value) for attr in obj.db_attributes.all()]
|
||||
try:
|
||||
ndb_attr = [(aname, avalue) for aname, avalue in obj.ndb.__dict__.items() if not aname.startswith("_")]
|
||||
ndb_attr = [(aname, avalue)
|
||||
for aname, avalue in obj.ndb.__dict__.items()
|
||||
if not aname.startswith("_")]
|
||||
except Exception:
|
||||
ndb_attr = None
|
||||
string = ""
|
||||
|
|
@ -1572,7 +1644,8 @@ class CmdExamine(ObjManipCommand):
|
|||
if hasattr(obj, "sessid") and obj.sessid:
|
||||
string += "\n{wsession{n: %s" % obj.sessid
|
||||
elif hasattr(obj, "sessions") and obj.sessions:
|
||||
string += "\n{wsession(s){n: %s" % (", ".join(str(sess.sessid) for sess in obj.sessions))
|
||||
string += "\n{wsession(s){n: %s" % (", ".join(str(sess.sessid)
|
||||
for sess in obj.sessions))
|
||||
if hasattr(obj, "has_player") and obj.has_player:
|
||||
string += "\n{wPlayer{n: {c%s{n" % obj.player.name
|
||||
perms = obj.player.permissions.all()
|
||||
|
|
@ -1581,7 +1654,8 @@ class CmdExamine(ObjManipCommand):
|
|||
elif not perms:
|
||||
perms = ["<None>"]
|
||||
string += "\n{wPlayer Perms{n: %s" % (", ".join(perms))
|
||||
string += "\n{wTypeclass{n: %s (%s)" % (obj.typeclass.typename, obj.typeclass_path)
|
||||
string += "\n{wTypeclass{n: %s (%s)" % (obj.typeclass.typename,
|
||||
obj.typeclass_path)
|
||||
if hasattr(obj, "location"):
|
||||
string += "\n{wLocation{n: %s" % obj.location
|
||||
if obj.location:
|
||||
|
|
@ -1610,14 +1684,20 @@ class CmdExamine(ObjManipCommand):
|
|||
|
||||
if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "Empty"):
|
||||
# list the current cmdsets
|
||||
all_cmdsets = obj.cmdset.all() + (hasattr(obj, "player") and obj.player and obj.player.cmdset.all() or [])
|
||||
all_cmdsets += hasattr(obj, "sessid") and hasattr(obj, "player") and obj.player.get_session(obj.sessid).cmdset.all()
|
||||
all_cmdsets.sort(key=lambda x:x.priority, reverse=True)
|
||||
string += "\n{wStored Cmdset(s){n:\n %s" % ("\n ".join("%s [%s] (prio %s)" %
|
||||
(cmdset.path, cmdset.key, cmdset.priority) for cmdset in all_cmdsets))
|
||||
all_cmdsets = (obj.cmdset.all() +
|
||||
(hasattr(obj, "player") and
|
||||
obj.player and obj.player.cmdset.all() or []))
|
||||
all_cmdsets += (hasattr(obj, "sessid") and
|
||||
hasattr(obj, "player") and
|
||||
obj.player.get_session(obj.sessid).cmdset.all())
|
||||
all_cmdsets.sort(key=lambda x: x.priority, reverse=True)
|
||||
string += "\n{wStored Cmdset(s){n:\n %s" % ("\n ".join("%s [%s] (prio %s)" % \
|
||||
(cmdset.path, cmdset.key, cmdset.priority)
|
||||
for cmdset in all_cmdsets))
|
||||
|
||||
# list the commands available to this object
|
||||
avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")])
|
||||
avail_cmdset = sorted([cmd.key for cmd in avail_cmdset
|
||||
if cmd.access(obj, "cmd")])
|
||||
|
||||
cmdsetstr = utils.fill(", ".join(avail_cmdset), indent=2)
|
||||
string += "\n{wCommands available to %s (all cmdsets + exits and external cmds){n:\n %s" % (obj.key, cmdsetstr)
|
||||
|
|
@ -1644,10 +1724,10 @@ class CmdExamine(ObjManipCommand):
|
|||
string += "\n{wCharacters{n: %s" % ", ".join(["{c%s{n" % pobj.name for pobj in pobjs])
|
||||
if things:
|
||||
string += "\n{wContents{n: %s" % ", ".join([cont.name for cont in obj.contents
|
||||
if cont not in exits and cont not in pobjs])
|
||||
separator = "-"*78
|
||||
if cont not in exits and cont not in pobjs])
|
||||
separator = "-" * 78
|
||||
#output info
|
||||
return '%s\n%s\n%s' % ( separator, string.strip(), separator )
|
||||
return '%s\n%s\n%s' % (separator, string.strip(), separator)
|
||||
|
||||
def func(self):
|
||||
"Process command"
|
||||
|
|
@ -1686,7 +1766,7 @@ class CmdExamine(ObjManipCommand):
|
|||
obj_attrs = objdef['attrs']
|
||||
|
||||
self.player_mode = utils.inherits_from(caller, "src.players.player.Player") or \
|
||||
"player" in self.switches or obj_name.startswith('*')
|
||||
"player" in self.switches or obj_name.startswith('*')
|
||||
if self.player_mode:
|
||||
try:
|
||||
obj = caller.search_player(obj_name.lstrip('*'))
|
||||
|
|
@ -1699,7 +1779,8 @@ class CmdExamine(ObjManipCommand):
|
|||
continue
|
||||
|
||||
if not obj.access(caller, 'examine'):
|
||||
#If we don't have special info access, just look at the object instead.
|
||||
#If we don't have special info access, just look
|
||||
# at the object instead.
|
||||
caller.execute_cmd('look %s' % obj_name)
|
||||
continue
|
||||
|
||||
|
|
@ -1727,7 +1808,8 @@ class CmdFind(MuxCommand):
|
|||
Searches the database for an object of a particular name or dbref.
|
||||
Use *playername to search for a player. The switches allows for
|
||||
limiting object matches to certain game entities. Dbrefmin and dbrefmax
|
||||
limits matches to within the given dbrefs, or above/below if only one is given.
|
||||
limits matches to within the given dbrefs, or above/below if only
|
||||
one is given.
|
||||
"""
|
||||
|
||||
key = "@find"
|
||||
|
|
@ -1772,11 +1854,13 @@ class CmdFind(MuxCommand):
|
|||
if not low <= int(result.id) <= high:
|
||||
string += "\n {RNo match found for '%s' within the given dbref limits.{n" % searchstring
|
||||
else:
|
||||
string += "\n{g %s(%s) - %s{n" % (result.key, result.dbref, result.typeclass.path)
|
||||
string += "\n{g %s(%s) - %s{n" % (result.key, result.dbref,
|
||||
result.typeclass.path)
|
||||
else:
|
||||
# Not a player/dbref search but a wider search; build a queryset.
|
||||
|
||||
results = ObjectDB.objects.filter(db_key__istartswith=searchstring, id__gte=low, id__lte=high)
|
||||
results = ObjectDB.objects.filter(db_key__istartswith=searchstring,
|
||||
id__gte=low, id__lte=high)
|
||||
if "room" in switches:
|
||||
results = results.filter(db_location__isnull=True)
|
||||
if "exit" in switches:
|
||||
|
|
@ -1909,12 +1993,14 @@ class CmdTeleport(MuxCommand):
|
|||
use_destination = False
|
||||
|
||||
# try the teleport
|
||||
if obj_to_teleport.move_to(destination, quiet=tel_quietly, emit_to_obj=caller,
|
||||
if obj_to_teleport.move_to(destination, quiet=tel_quietly,
|
||||
emit_to_obj=caller,
|
||||
use_destination=use_destination):
|
||||
if obj_to_teleport == caller:
|
||||
caller.msg("Teleported to %s." % destination)
|
||||
else:
|
||||
caller.msg("Teleported %s -> %s." % (obj_to_teleport, destination))
|
||||
caller.msg("Teleported %s -> %s." % (obj_to_teleport,
|
||||
destination))
|
||||
|
||||
|
||||
class CmdScript(MuxCommand):
|
||||
|
|
@ -1931,9 +2017,10 @@ class CmdScript(MuxCommand):
|
|||
If no script path/key is given, lists all scripts active on the given
|
||||
object.
|
||||
Script path can be given from the base location for scripts as given in
|
||||
settings. If adding a new script, it will be started automatically (no /start
|
||||
switch is needed). Using the /start or /stop switches on an object without
|
||||
specifying a script key/path will start/stop ALL scripts on the object.
|
||||
settings. If adding a new script, it will be started automatically
|
||||
(no /start switch is needed). Using the /start or /stop switches on an
|
||||
object without specifying a script key/path will start/stop ALL scripts on
|
||||
the object.
|
||||
"""
|
||||
|
||||
key = "@script"
|
||||
|
|
@ -1970,7 +2057,8 @@ class CmdScript(MuxCommand):
|
|||
string += "%s scripts started on %s." % (num, obj.key)
|
||||
elif "stop" in self.switches:
|
||||
for script in scripts:
|
||||
string += "Stopping script %s on %s." % (script.key, obj.key)
|
||||
string += "Stopping script %s on %s." % (script.key,
|
||||
obj.key)
|
||||
script.stop()
|
||||
string = string.strip()
|
||||
obj.scripts.validate()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
"""
|
||||
This module ties together all the commands default Character objects have
|
||||
available (i.e. IC commands). Note that some commands, such as communication-commands are
|
||||
instead put on the player level, in the Player cmdset. Player commands remain
|
||||
available also to Characters.
|
||||
available (i.e. IC commands). Note that some commands, such as
|
||||
communication-commands are instead put on the player level, in the
|
||||
Player cmdset. Player commands remain available also to Characters.
|
||||
"""
|
||||
from src.commands.cmdset import CmdSet
|
||||
from src.commands.default import general, help, admin, system
|
||||
from src.commands.default import building
|
||||
from src.commands.default import batchprocess
|
||||
|
||||
|
||||
class CharacterCmdSet(CmdSet):
|
||||
"""
|
||||
Implements the default command set.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from src.commands.cmdset import CmdSet
|
|||
from src.commands.default import help, comms, admin, system
|
||||
from src.commands.default import building, player
|
||||
|
||||
|
||||
class PlayerCmdSet(CmdSet):
|
||||
"""
|
||||
Implements the player command set.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ of the state instance in this module.
|
|||
from src.commands.cmdset import CmdSet
|
||||
from src.commands.default import unloggedin
|
||||
|
||||
|
||||
class UnloggedinCmdSet(CmdSet):
|
||||
"""
|
||||
Sets up the unlogged cmdset.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ __all__ = ("CmdAddCom", "CmdDelCom", "CmdAllCom",
|
|||
"CmdPage", "CmdIRC2Chan", "CmdIMC2Chan", "CmdIMCInfo",
|
||||
"CmdIMCTell", "CmdRSS2Chan")
|
||||
|
||||
|
||||
def find_channel(caller, channelname, silent=False, noaliases=False):
|
||||
"""
|
||||
Helper function for searching for a single channel with
|
||||
|
|
@ -30,7 +31,8 @@ def find_channel(caller, channelname, silent=False, noaliases=False):
|
|||
channels = ChannelDB.objects.channel_search(channelname)
|
||||
if not channels:
|
||||
if not noaliases:
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels() if channelname in chan.aliases.all()]
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels()
|
||||
if channelname in chan.aliases.all()]
|
||||
if channels:
|
||||
return channels[0]
|
||||
if not silent:
|
||||
|
|
@ -43,6 +45,7 @@ def find_channel(caller, channelname, silent=False, noaliases=False):
|
|||
return None
|
||||
return channels[0]
|
||||
|
||||
|
||||
class CmdAddCom(MuxPlayerCommand):
|
||||
"""
|
||||
addcom - subscribe to a channel with optional alias
|
||||
|
|
@ -57,7 +60,7 @@ class CmdAddCom(MuxPlayerCommand):
|
|||
"""
|
||||
|
||||
key = "addcom"
|
||||
aliases = ["aliaschan","chanalias"]
|
||||
aliases = ["aliaschan", "chanalias"]
|
||||
help_category = "Comms"
|
||||
locks = "cmd:not pperm(channel_banned)"
|
||||
|
||||
|
|
@ -168,6 +171,7 @@ class CmdDelCom(MuxPlayerCommand):
|
|||
else:
|
||||
self.msg("You had no such alias defined for this channel.")
|
||||
|
||||
|
||||
class CmdAllCom(MuxPlayerCommand):
|
||||
"""
|
||||
allcom - operate on all channels
|
||||
|
|
@ -197,8 +201,10 @@ class CmdAllCom(MuxPlayerCommand):
|
|||
return
|
||||
|
||||
if args == "on":
|
||||
# get names of all channels available to listen to and activate them all
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels() if chan.access(caller, 'listen')]
|
||||
# get names of all channels available to listen to
|
||||
# and activate them all
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels()
|
||||
if chan.access(caller, 'listen')]
|
||||
for channel in channels:
|
||||
caller.execute_cmd("addcom %s" % channel.key)
|
||||
elif args == "off":
|
||||
|
|
@ -208,13 +214,15 @@ class CmdAllCom(MuxPlayerCommand):
|
|||
caller.execute_cmd("delcom %s" % channel.key)
|
||||
elif args == "destroy":
|
||||
# destroy all channels you control
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels() if chan.access(caller, 'control')]
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels()
|
||||
if chan.access(caller, 'control')]
|
||||
for channel in channels:
|
||||
caller.execute_cmd("@cdestroy %s" % channel.key)
|
||||
elif args == "who":
|
||||
# run a who, listing the subscribers on visible channels.
|
||||
string = "\n{CChannel subscriptions{n"
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels() if chan.access(caller, 'listen')]
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels()
|
||||
if chan.access(caller, 'listen')]
|
||||
if not channels:
|
||||
string += "No channels."
|
||||
for channel in channels:
|
||||
|
|
@ -229,6 +237,7 @@ class CmdAllCom(MuxPlayerCommand):
|
|||
# wrong input
|
||||
self.msg("Usage: allcom on | off | who | clear")
|
||||
|
||||
|
||||
class CmdChannels(MuxPlayerCommand):
|
||||
"""
|
||||
@clist
|
||||
|
|
@ -253,7 +262,8 @@ class CmdChannels(MuxPlayerCommand):
|
|||
caller = self.caller
|
||||
|
||||
# all channels we have available to listen to
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels() if chan.access(caller, 'listen')]
|
||||
channels = [chan for chan in ChannelDB.objects.get_all_channels()
|
||||
if chan.access(caller, 'listen')]
|
||||
#print channels
|
||||
if not channels:
|
||||
self.msg("No channels available.")
|
||||
|
|
@ -264,28 +274,39 @@ class CmdChannels(MuxPlayerCommand):
|
|||
|
||||
if self.cmdstring == "comlist":
|
||||
# just display the subscribed channels with no extra info
|
||||
comtable = prettytable.PrettyTable(["{wchannel","{wmy aliases", "{wdescription"])
|
||||
comtable = prettytable.PrettyTable(["{wchannel",
|
||||
"{wmy aliases",
|
||||
"{wdescription"])
|
||||
for chan in subs:
|
||||
clower = chan.key.lower()
|
||||
nicks = caller.nicks.get(category="channel")
|
||||
comtable.add_row(["%s%s" % (chan.key, chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or ""),
|
||||
"%s".join(nick for nick in make_iter(nicks) if nick and nick.lower()==clower),
|
||||
comtable.add_row(["%s%s" % (chan.key, chan.aliases.all() and
|
||||
"(%s)" % ",".join(chan.aliases.all()) or ""),
|
||||
"%s".join(nick for nick in make_iter(nicks)
|
||||
if nick and nick.lower() == clower),
|
||||
chan.db.desc])
|
||||
caller.msg("\n{wChannel subscriptions{n (use {w@channels{n to list all, {waddcom{n/{wdelcom{n to sub/unsub):{n\n%s" % comtable)
|
||||
else:
|
||||
# full listing (of channels caller is able to listen to)
|
||||
comtable = prettytable.PrettyTable(["{wsub","{wchannel","{wmy aliases","{wlocks","{wdescription"])
|
||||
comtable = prettytable.PrettyTable(["{wsub",
|
||||
"{wchannel",
|
||||
"{wmy aliases",
|
||||
"{wlocks",
|
||||
"{wdescription"])
|
||||
for chan in channels:
|
||||
clower = chan.key.lower()
|
||||
nicks = caller.nicks.get(category="channel")
|
||||
nicks = nicks or []
|
||||
comtable.add_row([chan in subs and "{gYes{n" or "{rNo{n",
|
||||
"%s%s" % (chan.key, chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or ""),
|
||||
"%s".join(nick for nick in make_iter(nicks) if nick.lower()==clower),
|
||||
"%s%s" % (chan.key, chan.aliases.all() and
|
||||
"(%s)" % ",".join(chan.aliases.all()) or ""),
|
||||
"%s".join(nick for nick in make_iter(nicks)
|
||||
if nick.lower() == clower),
|
||||
str(chan.locks),
|
||||
chan.db.desc])
|
||||
caller.msg("\n{wAvailable channels{n (use {wcomlist{n,{waddcom{n and {wdelcom{n to manage subscriptions):\n%s" % comtable)
|
||||
|
||||
|
||||
class CmdCdestroy(MuxPlayerCommand):
|
||||
"""
|
||||
@cdestroy
|
||||
|
|
@ -322,6 +343,7 @@ class CmdCdestroy(MuxPlayerCommand):
|
|||
CHANNELHANDLER.update()
|
||||
self.msg("Channel '%s' was destroyed." % channel)
|
||||
|
||||
|
||||
class CmdCBoot(MuxPlayerCommand):
|
||||
"""
|
||||
@cboot
|
||||
|
|
@ -382,6 +404,7 @@ class CmdCBoot(MuxPlayerCommand):
|
|||
channel.disconnect_from(player)
|
||||
CHANNELHANDLER.update()
|
||||
|
||||
|
||||
class CmdCemit(MuxPlayerCommand):
|
||||
"""
|
||||
@cemit - send a message to channel
|
||||
|
|
@ -429,6 +452,7 @@ class CmdCemit(MuxPlayerCommand):
|
|||
string = "Sent to channel %s: %s" % (channel.key, message)
|
||||
self.msg(string)
|
||||
|
||||
|
||||
class CmdCWho(MuxPlayerCommand):
|
||||
"""
|
||||
@cwho
|
||||
|
|
@ -466,6 +490,7 @@ class CmdCWho(MuxPlayerCommand):
|
|||
string += " <None>"
|
||||
self.msg(string.strip())
|
||||
|
||||
|
||||
class CmdChannelCreate(MuxPlayerCommand):
|
||||
"""
|
||||
@ccreate
|
||||
|
|
@ -508,7 +533,10 @@ class CmdChannelCreate(MuxPlayerCommand):
|
|||
return
|
||||
# Create and set the channel up
|
||||
lockstring = "send:all();listen:all();control:id(%s)" % caller.id
|
||||
new_chan = create.create_channel(channame, aliases, description, locks=lockstring)
|
||||
new_chan = create.create_channel(channame,
|
||||
aliases,
|
||||
description,
|
||||
locks=lockstring)
|
||||
new_chan.connect_to(caller)
|
||||
self.msg("Created channel %s and connected to it." % new_chan.key)
|
||||
|
||||
|
|
@ -593,7 +621,9 @@ class CmdCdesc(MuxPlayerCommand):
|
|||
# set the description
|
||||
channel.db.desc = self.rhs
|
||||
channel.save()
|
||||
self.msg("Description of channel '%s' set to '%s'." % (channel.key, self.rhs))
|
||||
self.msg("Description of channel '%s' set to '%s'." % (channel.key,
|
||||
self.rhs))
|
||||
|
||||
|
||||
class CmdPage(MuxPlayerCommand):
|
||||
"""
|
||||
|
|
@ -624,15 +654,16 @@ class CmdPage(MuxPlayerCommand):
|
|||
caller = self.caller
|
||||
|
||||
# get the messages we've sent (not to channels)
|
||||
pages_we_sent = Msg.objects.get_messages_by_sender(caller, exclude_channel_messages=True)
|
||||
pages_we_sent = Msg.objects.get_messages_by_sender(caller,
|
||||
exclude_channel_messages=True)
|
||||
# get last messages we've got
|
||||
pages_we_got = Msg.objects.get_messages_by_receiver(caller)
|
||||
|
||||
|
||||
if 'last' in self.switches:
|
||||
if pages_we_sent:
|
||||
recv = ",".join(obj.key for obj in pages_we_sent[-1].receivers)
|
||||
self.msg("You last paged {c%s{n:%s" % (recv, pages_we_sent[-1].message))
|
||||
self.msg("You last paged {c%s{n:%s" % (recv,
|
||||
pages_we_sent[-1].message))
|
||||
return
|
||||
else:
|
||||
self.msg("You haven't paged anyone yet.")
|
||||
|
|
@ -654,12 +685,12 @@ class CmdPage(MuxPlayerCommand):
|
|||
lastpages = pages[-number:]
|
||||
else:
|
||||
lastpages = pages
|
||||
|
||||
lastpages = "\n ".join("{w%s{n {c%s{n to {c%s{n: %s" % (utils.datetime_format(page.date_sent),
|
||||
",".join(obj.key for obj in page.senders),
|
||||
"{n,{c ".join([obj.name for obj in page.receivers]),
|
||||
page.message)
|
||||
for page in lastpages)
|
||||
template = "{w%s{n {c%s{n to {c%s{n: %s"
|
||||
lastpages = "\n ".join(template %
|
||||
(utils.datetime_format(page.date_sent),
|
||||
",".join(obj.key for obj in page.senders),
|
||||
"{n,{c ".join([obj.name for obj in page.receivers]),
|
||||
page.message) for page in lastpages)
|
||||
|
||||
if lastpages:
|
||||
string = "Your latest pages:\n %s" % lastpages
|
||||
|
|
@ -668,7 +699,6 @@ class CmdPage(MuxPlayerCommand):
|
|||
self.msg(string)
|
||||
return
|
||||
|
||||
|
||||
# We are sending. Build a list of targets
|
||||
|
||||
if not self.lhs:
|
||||
|
|
@ -722,7 +752,7 @@ class CmdPage(MuxPlayerCommand):
|
|||
else:
|
||||
received.append("{c%s{n" % pobj.name)
|
||||
if rstrings:
|
||||
self.msg(rstrings = "\n".join(rstrings))
|
||||
self.msg(rstrings="\n".join(rstrings))
|
||||
self.msg("You paged %s with: '%s'." % (", ".join(received), message))
|
||||
|
||||
|
||||
|
|
@ -734,18 +764,20 @@ class CmdIRC2Chan(MuxCommand):
|
|||
@irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>
|
||||
|
||||
Switches:
|
||||
/disconnect - this will delete the bot and remove the irc connection to the channel.
|
||||
/disconnect - this will delete the bot and remove the irc connection
|
||||
to the channel.
|
||||
/remove - "
|
||||
/list - show all irc<->evennia mappings
|
||||
|
||||
Example:
|
||||
@irc2chan myircchan = irc.dalnet.net 6667 myevennia-channel evennia-bot
|
||||
|
||||
This creates an IRC bot that connects to a given IRC network and channel. It will
|
||||
relay everything said in the evennia channel to the IRC channel and vice versa. The
|
||||
bot will automatically connect at server start, so this comman need only be given once.
|
||||
The /disconnect switch will permanently delete the bot. To only temporarily deactivate it,
|
||||
use the @services command instead.
|
||||
This creates an IRC bot that connects to a given IRC network and channel.
|
||||
It will relay everything said in the evennia channel to the IRC channel and
|
||||
vice versa. The bot will automatically connect at server start, so this
|
||||
comman need only be given once. The /disconnect switch will permanently
|
||||
delete the bot. To only temporarily deactivate it, use the {w@services{n
|
||||
command instead.
|
||||
"""
|
||||
|
||||
key = "@irc2chan"
|
||||
|
|
@ -780,19 +812,25 @@ class CmdIRC2Chan(MuxCommand):
|
|||
channel = self.lhs
|
||||
self.rhs = self.rhs.replace('#', ' ') # to avoid Python comment issues
|
||||
try:
|
||||
irc_network, irc_port, irc_channel, irc_botname = [part.strip() for part in self.rhs.split(None, 3)]
|
||||
irc_network, irc_port, irc_channel, irc_botname = \
|
||||
[part.strip() for part in self.rhs.split(None, 3)]
|
||||
irc_channel = "#%s" % irc_channel
|
||||
except Exception:
|
||||
string = "IRC bot definition '%s' is not valid." % self.rhs
|
||||
self.msg(string)
|
||||
return
|
||||
|
||||
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
||||
if('disconnect' in self.switches or 'remove' in self.switches or
|
||||
'delete' in self.switches):
|
||||
chanmatch = find_channel(self.caller, channel, silent=True)
|
||||
if chanmatch:
|
||||
channel = chanmatch.key
|
||||
|
||||
ok = irc.delete_connection(channel, irc_network, irc_port, irc_channel, irc_botname)
|
||||
ok = irc.delete_connection(channel,
|
||||
irc_network,
|
||||
irc_port,
|
||||
irc_channel,
|
||||
irc_botname)
|
||||
if not ok:
|
||||
self.msg("IRC connection/bot could not be removed, does it exist?")
|
||||
else:
|
||||
|
|
@ -802,12 +840,17 @@ class CmdIRC2Chan(MuxCommand):
|
|||
channel = find_channel(self.caller, channel)
|
||||
if not channel:
|
||||
return
|
||||
ok = irc.create_connection(channel, irc_network, irc_port, irc_channel, irc_botname)
|
||||
ok = irc.create_connection(channel,
|
||||
irc_network,
|
||||
irc_port,
|
||||
irc_channel,
|
||||
irc_botname)
|
||||
if not ok:
|
||||
self.msg("This IRC connection already exists.")
|
||||
return
|
||||
self.msg("Connection created. Starting IRC bot.")
|
||||
|
||||
|
||||
class CmdIMC2Chan(MuxCommand):
|
||||
"""
|
||||
imc2chan - link an evennia channel to imc2
|
||||
|
|
@ -863,9 +906,10 @@ class CmdIMC2Chan(MuxCommand):
|
|||
channel = self.lhs
|
||||
imc2_channel = self.rhs
|
||||
|
||||
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
||||
# we don't search for channels before this since we want to clear the link
|
||||
# also if the channel no longer exists.
|
||||
if('disconnect' in self.switches or 'remove' in self.switches or
|
||||
'delete' in self.switches):
|
||||
# we don't search for channels before this since we want
|
||||
# to clear the link also if the channel no longer exists.
|
||||
ok = imc2.delete_connection(channel, imc2_channel)
|
||||
if not ok:
|
||||
self.msg("IMC2 connection could not be removed, does it exist?")
|
||||
|
|
@ -932,7 +976,8 @@ class CmdIMCInfo(MuxCommand):
|
|||
IMC2_CLIENT.send_packet(pck.IMC2PacketIceRefresh())
|
||||
self.msg("IMC2 lists were re-synced.")
|
||||
|
||||
elif "games" in self.switches or "muds" in self.switches or self.cmdstring == "@imclist":
|
||||
elif("games" in self.switches or "muds" in self.switches
|
||||
or self.cmdstring == "@imclist"):
|
||||
# list muds
|
||||
from src.comms.imc2 import IMC2_MUDLIST
|
||||
|
||||
|
|
@ -956,9 +1001,13 @@ class CmdIMCInfo(MuxCommand):
|
|||
return
|
||||
from src.comms.imc2 import IMC2_CLIENT
|
||||
self.msg("Sending IMC whois request. If you receive no response, no matches were found.")
|
||||
IMC2_CLIENT.msg_imc2(None, from_obj=self.caller, packet_type="imcwhois", target=self.args)
|
||||
IMC2_CLIENT.msg_imc2(None,
|
||||
from_obj=self.caller,
|
||||
packet_type="imcwhois",
|
||||
target=self.args)
|
||||
|
||||
elif not self.switches or "channels" in self.switches or self.cmdstring == "@imcchanlist":
|
||||
elif(not self.switches or "channels" in self.switches or
|
||||
self.cmdstring == "@imcchanlist"):
|
||||
# show channels
|
||||
from src.comms.imc2 import IMC2_CHANLIST, IMC2_CLIENT
|
||||
|
||||
|
|
@ -968,7 +1017,8 @@ class CmdIMCInfo(MuxCommand):
|
|||
table = prettytable.PrettyTable(["Full name", "Name", "Owner", "Perm", "Policy"])
|
||||
for chan in channels:
|
||||
nchans += 1
|
||||
table.add_row([chan.name, chan.localname, chan.owner, chan.level, chan.policy])
|
||||
table.add_row([chan.name, chan.localname, chan.owner,
|
||||
chan.level, chan.policy])
|
||||
string += "\n{wChannels on %s:{n\n%s" % (IMC2_CLIENT.factory.network, table)
|
||||
string += "\n%i Channels found." % nchans
|
||||
self.msg(string)
|
||||
|
|
@ -977,6 +1027,7 @@ class CmdIMCInfo(MuxCommand):
|
|||
string = "Usage: imcinfo|imcchanlist|imclist"
|
||||
self.msg(string)
|
||||
|
||||
|
||||
# unclear if this is working ...
|
||||
class CmdIMCTell(MuxCommand):
|
||||
"""
|
||||
|
|
@ -1028,19 +1079,21 @@ class CmdRSS2Chan(MuxCommand):
|
|||
@rss2chan[/switches] <evennia_channel> = <rss_url>
|
||||
|
||||
Switches:
|
||||
/disconnect - this will stop the feed and remove the connection to the channel.
|
||||
/disconnect - this will stop the feed and remove the connection to the
|
||||
channel.
|
||||
/remove - "
|
||||
/list - show all rss->evennia mappings
|
||||
|
||||
Example:
|
||||
@rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic
|
||||
|
||||
This creates an RSS reader that connects to a given RSS feed url. Updates will be
|
||||
echoed as a title and news link to the given channel. The rate of updating is set
|
||||
with the RSS_UPDATE_INTERVAL variable in settings (default is every 10 minutes).
|
||||
This creates an RSS reader that connects to a given RSS feed url. Updates
|
||||
will be echoed as a title and news link to the given channel. The rate of
|
||||
updating is set with the RSS_UPDATE_INTERVAL variable in settings (default
|
||||
is every 10 minutes).
|
||||
|
||||
When disconnecting you need to supply both the channel and url again so as to identify
|
||||
the connection uniquely.
|
||||
When disconnecting you need to supply both the channel and url again so as
|
||||
to identify the connection uniquely.
|
||||
"""
|
||||
|
||||
key = "@rss2chan"
|
||||
|
|
@ -1075,7 +1128,8 @@ class CmdRSS2Chan(MuxCommand):
|
|||
channel = self.lhs
|
||||
url = self.rhs
|
||||
|
||||
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
||||
if('disconnect' in self.switches or 'remove' in self.switches or
|
||||
'delete' in self.switches):
|
||||
chanmatch = find_channel(self.caller, channel, silent=True)
|
||||
if chanmatch:
|
||||
channel = chanmatch.key
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ __all__ = ("CmdHome", "CmdLook", "CmdNick",
|
|||
|
||||
AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
||||
|
||||
|
||||
class CmdHome(MuxCommand):
|
||||
"""
|
||||
home
|
||||
|
|
@ -38,6 +39,7 @@ class CmdHome(MuxCommand):
|
|||
caller.move_to(home)
|
||||
caller.msg("There's no place like home ...")
|
||||
|
||||
|
||||
class CmdLook(MuxCommand):
|
||||
"""
|
||||
look
|
||||
|
|
@ -126,7 +128,9 @@ class CmdNick(MuxCommand):
|
|||
nicks = caller.nicks.get(category="channel")
|
||||
|
||||
if 'list' in switches:
|
||||
table = prettytable.PrettyTable(["{wNickType", "{wNickname", "{wTranslates-to"])
|
||||
table = prettytable.PrettyTable(["{wNickType",
|
||||
"{wNickname",
|
||||
"{wTranslates-to"])
|
||||
for nick in nicks:
|
||||
table.add_row([nick.db_category, nick.db_key, nick.db_data])
|
||||
string = "{wDefined Nicks:{n\n%s" % table
|
||||
|
|
@ -170,6 +174,7 @@ class CmdNick(MuxCommand):
|
|||
caller.nicks.add(nick, real, category=switch)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdInventory(MuxCommand):
|
||||
"""
|
||||
inventory
|
||||
|
|
@ -198,6 +203,7 @@ class CmdInventory(MuxCommand):
|
|||
string = "{wYou are carrying:\n%s" % table
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
class CmdGet(MuxCommand):
|
||||
"""
|
||||
get
|
||||
|
|
@ -327,7 +333,6 @@ class CmdGive(MuxCommand):
|
|||
target.msg("%s gives you %s." % (caller.key, to_give.key))
|
||||
|
||||
|
||||
|
||||
class CmdSay(MuxCommand):
|
||||
"""
|
||||
say
|
||||
|
|
@ -365,6 +370,7 @@ class CmdSay(MuxCommand):
|
|||
caller.location.msg_contents(emit_string,
|
||||
exclude=caller)
|
||||
|
||||
|
||||
class CmdPose(MuxCommand):
|
||||
"""
|
||||
pose - strike a pose
|
||||
|
|
@ -407,6 +413,7 @@ class CmdPose(MuxCommand):
|
|||
msg = "%s%s" % (self.caller.name, self.args)
|
||||
self.caller.location.msg_contents(msg)
|
||||
|
||||
|
||||
class CmdAccess(MuxCommand):
|
||||
"""
|
||||
access - show access groups
|
||||
|
|
@ -440,5 +447,4 @@ class CmdAccess(MuxCommand):
|
|||
string += "\nCharacter {c%s{n: %s" % (caller.key, cperms)
|
||||
if hasattr(caller, 'player'):
|
||||
string += "\nPlayer {c%s{n: %s" % (caller.player.key, pperms)
|
||||
caller.msg(string)
|
||||
|
||||
caller.msg(string)
|
||||
|
|
@ -18,7 +18,8 @@ from src.commands.default.muxcommand import MuxCommand
|
|||
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||
|
||||
|
||||
SEP = "{C" + "-"*78 + "{n"
|
||||
SEP = "{C" + "-" * 78 + "{n"
|
||||
|
||||
|
||||
def format_help_entry(title, help_text, aliases=None, suggested=None):
|
||||
"""
|
||||
|
|
@ -38,6 +39,7 @@ def format_help_entry(title, help_text, aliases=None, suggested=None):
|
|||
string += "\n" + SEP
|
||||
return string
|
||||
|
||||
|
||||
def format_help_list(hdict_cmds, hdict_db):
|
||||
"""
|
||||
Output a category-ordered list. The input are the
|
||||
|
|
@ -57,6 +59,7 @@ def format_help_list(hdict_cmds, hdict_db):
|
|||
string += "{G" + fill(", ".join(sorted([str(topic) for topic in hdict_db[category]]))) + "{n"
|
||||
return string
|
||||
|
||||
|
||||
class CmdHelp(Command):
|
||||
"""
|
||||
The main help command
|
||||
|
|
@ -129,13 +132,18 @@ class CmdHelp(Command):
|
|||
# try an exact command auto-help match
|
||||
match = [cmd for cmd in all_cmds if cmd == query]
|
||||
if len(match) == 1:
|
||||
self.msg(format_help_entry(match[0].key, match[0].__doc__, aliases=match[0].aliases, suggested=suggestions))
|
||||
self.msg(format_help_entry(match[0].key,
|
||||
match[0].__doc__,
|
||||
aliases=match[0].aliases,
|
||||
suggested=suggestions))
|
||||
return
|
||||
|
||||
# try an exact database help entry match
|
||||
match = list(HelpEntry.objects.find_topicmatch(query, exact=True))
|
||||
if len(match) == 1:
|
||||
self.msg(format_help_entry(match[0].key, match[0].entrytext, suggested=suggestions))
|
||||
self.msg(format_help_entry(match[0].key,
|
||||
match[0].entrytext,
|
||||
suggested=suggestions))
|
||||
return
|
||||
|
||||
# try to see if a category name was entered
|
||||
|
|
@ -147,6 +155,7 @@ class CmdHelp(Command):
|
|||
# no exact matches found. Just give suggestions.
|
||||
self.msg(format_help_entry("", "No help entry found for '%s'" % query, None, suggested=suggestions))
|
||||
|
||||
|
||||
class CmdSetHelp(MuxCommand):
|
||||
"""
|
||||
@help - edit the help database
|
||||
|
|
@ -169,9 +178,9 @@ class CmdSetHelp(MuxCommand):
|
|||
@sethelp/append pickpocketing, ,attr(is_thief) = This steals ...
|
||||
|
||||
This command manipulates the help database. A help entry can be created,
|
||||
appended/merged to and deleted. If you don't assign a category, the "General"
|
||||
category will be used. If no lockstring is specified, default is to let everyone read
|
||||
the help file.
|
||||
appended/merged to and deleted. If you don't assign a category, the
|
||||
"General" category will be used. If no lockstring is specified, default
|
||||
is to let everyone read the help file.
|
||||
|
||||
"""
|
||||
key = "@help"
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@ class MuxCommand(Command):
|
|||
|
||||
The 'name[ with several words]' part is already dealt with by the
|
||||
cmdhandler at this point, and stored in self.cmdname (we don't use
|
||||
it here). The rest of the command is stored in self.args, which can start
|
||||
with the switch indicator /.
|
||||
it here). The rest of the command is stored in self.args, which can
|
||||
start with the switch indicator /.
|
||||
|
||||
This parser breaks self.args into its constituents and stores them in the
|
||||
following variables:
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ from src.utils import utils, create, search, prettytable
|
|||
|
||||
from settings import MAX_NR_CHARACTERS, MULTISESSION_MODE
|
||||
# limit symbol import for API
|
||||
__all__ = ("CmdOOCLook", "CmdIC", "CmdOOC", "CmdPassword", "CmdQuit", "CmdCharCreate",
|
||||
"CmdEncoding", "CmdSessions", "CmdWho", "CmdColorTest", "CmdQuell")
|
||||
__all__ = ("CmdOOCLook", "CmdIC", "CmdOOC", "CmdPassword", "CmdQuit",
|
||||
"CmdCharCreate", "CmdEncoding", "CmdSessions", "CmdWho",
|
||||
"CmdColorTest", "CmdQuell")
|
||||
|
||||
# force max nr chars to 1 if mode is 0 or 1
|
||||
MAX_NR_CHARACTERS = MULTISESSION_MODE < 2 and 1 or MAX_NR_CHARACTERS
|
||||
|
|
@ -58,7 +59,8 @@ class CmdOOCLook(MuxPlayerCommand):
|
|||
"Hook method for when an argument is given."
|
||||
player = self.caller
|
||||
key = self.args.lower()
|
||||
chars = dict((utils.to_str(char.key.lower()), char) for char in player.db._playable_characters)
|
||||
chars = dict((utils.to_str(char.key.lower()), char)
|
||||
for char in player.db._playable_characters)
|
||||
looktarget = chars.get(key)
|
||||
if looktarget:
|
||||
self.msg(looktarget.return_appearance(player))
|
||||
|
|
@ -101,7 +103,7 @@ class CmdOOCLook(MuxPlayerCommand):
|
|||
string += "\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters))
|
||||
else:
|
||||
string += "\n\nAvailable character%s%s:" % (string_s_ending,
|
||||
MAX_NR_CHARACTERS > 1 and " (%i/%i)" % (len(characters), MAX_NR_CHARACTERS) or "")
|
||||
MAX_NR_CHARACTERS > 1 and " (%i/%i)" % (len(characters), MAX_NR_CHARACTERS) or "")
|
||||
|
||||
for char in characters:
|
||||
csessid = char.sessid
|
||||
|
|
@ -171,8 +173,10 @@ class CmdCharCreate(MuxPlayerCommand):
|
|||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
||||
|
||||
new_character = create.create_object(typeclass, key=key, location=default_home,
|
||||
home=default_home, permissions=permissions)
|
||||
new_character = create.create_object(typeclass, key=key,
|
||||
location=default_home,
|
||||
home=default_home,
|
||||
permissions=permissions)
|
||||
# only allow creator (and immortals) to puppet this char
|
||||
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
|
||||
(new_character.id, player.id))
|
||||
|
|
@ -203,7 +207,8 @@ class CmdIC(MuxPlayerCommand):
|
|||
"""
|
||||
|
||||
key = "@ic"
|
||||
locks = "cmd:all()" # must be all() or different puppeted objects won't be able to access it.
|
||||
# lockmust be all() for different puppeted objects to access it.
|
||||
locks = "cmd:all()"
|
||||
aliases = "@puppet"
|
||||
help_category = "General"
|
||||
|
||||
|
|
@ -235,7 +240,8 @@ class CmdIC(MuxPlayerCommand):
|
|||
if new_character.player:
|
||||
# may not puppet an already puppeted character
|
||||
if new_character.sessid and new_character.player == player:
|
||||
# as a safeguard we allow "taking over chars from your own sessions.
|
||||
# as a safeguard we allow "taking over chars from
|
||||
# your own sessions.
|
||||
player.msg("{c%s{n{R is now acted from another of your sessions.{n" % (new_character.name), sessid=new_character.sessid)
|
||||
player.unpuppet_object(new_character.sessid)
|
||||
self.msg("Taking over {c%s{n from another of your sessions." % new_character.name)
|
||||
|
|
@ -251,6 +257,7 @@ class CmdIC(MuxPlayerCommand):
|
|||
else:
|
||||
self.msg("{rYou cannot become {C%s{n." % new_character.name)
|
||||
|
||||
|
||||
class CmdOOC(MuxPlayerCommand):
|
||||
"""
|
||||
go ooc
|
||||
|
|
@ -264,7 +271,8 @@ class CmdOOC(MuxPlayerCommand):
|
|||
"""
|
||||
|
||||
key = "@ooc"
|
||||
locks = "cmd:all()" # this must be all(), or different puppeted objects won't be able to access it.
|
||||
# lock must be all(), for different puppeted objects to access it.
|
||||
locks = "cmd:all()"
|
||||
aliases = "@unpuppet"
|
||||
help_category = "General"
|
||||
|
||||
|
|
@ -311,17 +319,22 @@ class CmdSessions(MuxPlayerCommand):
|
|||
player = self.caller
|
||||
sessions = player.get_all_sessions()
|
||||
|
||||
table = prettytable.PrettyTable(["{wsessid", "{wprotocol", "{whost", "{wpuppet/character", "{wlocation"])
|
||||
for sess in sorted(sessions, key=lambda x:x.sessid):
|
||||
table = prettytable.PrettyTable(["{wsessid",
|
||||
"{wprotocol",
|
||||
"{whost",
|
||||
"{wpuppet/character",
|
||||
"{wlocation"])
|
||||
for sess in sorted(sessions, key=lambda x: x.sessid):
|
||||
sessid = sess.sessid
|
||||
char = player.get_puppet(sessid)
|
||||
table.add_row([str(sessid), str(sess.protocol_key),
|
||||
type(sess.address)==tuple and sess.address[0] or sess.address,
|
||||
type(sess.address) == tuple and sess.address[0] or sess.address,
|
||||
char and str(char) or "None",
|
||||
char and str(char.location) or "N/A"])
|
||||
string = "{wYour current session(s):{n\n%s" % table
|
||||
self.msg(string)
|
||||
|
||||
|
||||
class CmdWho(MuxPlayerCommand):
|
||||
"""
|
||||
who
|
||||
|
|
@ -355,7 +368,13 @@ class CmdWho(MuxPlayerCommand):
|
|||
|
||||
nplayers = (SESSIONS.player_count())
|
||||
if show_session_data:
|
||||
table = prettytable.PrettyTable(["{wPlayer Name","{wOn for", "{wIdle", "{wRoom", "{wCmds", "{wProtocol", "{wHost"])
|
||||
table = prettytable.PrettyTable(["{wPlayer Name",
|
||||
"{wOn for",
|
||||
"{wIdle",
|
||||
"{wRoom",
|
||||
"{wCmds",
|
||||
"{wProtocol",
|
||||
"{wHost"])
|
||||
for session in session_list:
|
||||
if not session.logged_in: continue
|
||||
delta_cmd = time.time() - session.cmd_last_visible
|
||||
|
|
@ -372,7 +391,8 @@ class CmdWho(MuxPlayerCommand):
|
|||
else:
|
||||
table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"])
|
||||
for session in session_list:
|
||||
if not session.logged_in: continue
|
||||
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()
|
||||
|
|
@ -397,14 +417,16 @@ class CmdEncoding(MuxPlayerCommand):
|
|||
clear - clear your custom encoding
|
||||
|
||||
|
||||
This sets the text encoding for communicating with Evennia. This is mostly an issue only if
|
||||
you want to use non-ASCII characters (i.e. letters/symbols not found in English). If you see
|
||||
that your characters look strange (or you get encoding errors), you should use this command
|
||||
to set the server encoding to be the same used in your client program.
|
||||
This sets the text encoding for communicating with Evennia. This is mostly
|
||||
an issue only if you want to use non-ASCII characters (i.e. letters/symbols
|
||||
not found in English). If you see that your characters look strange (or you
|
||||
get encoding errors), you should use this command to set the server
|
||||
encoding to be the same used in your client program.
|
||||
|
||||
Common encodings are utf-8 (default), latin-1, ISO-8859-1 etc.
|
||||
|
||||
If you don't submit an encoding, the current encoding will be displayed instead.
|
||||
If you don't submit an encoding, the current encoding will be displayed
|
||||
instead.
|
||||
"""
|
||||
|
||||
key = "@encoding"
|
||||
|
|
@ -444,6 +466,7 @@ class CmdEncoding(MuxPlayerCommand):
|
|||
string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding)
|
||||
self.msg(string.strip())
|
||||
|
||||
|
||||
class CmdPassword(MuxPlayerCommand):
|
||||
"""
|
||||
@password - set your password
|
||||
|
|
@ -463,8 +486,8 @@ class CmdPassword(MuxPlayerCommand):
|
|||
if not self.rhs:
|
||||
self.msg("Usage: @password <oldpass> = <newpass>")
|
||||
return
|
||||
oldpass = self.lhslist[0] # this is already stripped by parse()
|
||||
newpass = self.rhslist[0] # ''
|
||||
oldpass = self.lhslist[0] # this is already stripped by parse()
|
||||
newpass = self.rhslist[0] # ''
|
||||
if not player.check_password(oldpass):
|
||||
self.msg("The specified old password isn't correct.")
|
||||
elif len(newpass) < 3:
|
||||
|
|
@ -474,6 +497,7 @@ class CmdPassword(MuxPlayerCommand):
|
|||
player.save()
|
||||
self.msg("Password changed.")
|
||||
|
||||
|
||||
class CmdQuit(MuxPlayerCommand):
|
||||
"""
|
||||
quit
|
||||
|
|
@ -518,10 +542,10 @@ class CmdColorTest(MuxPlayerCommand):
|
|||
Usage:
|
||||
@color ansi|xterm256
|
||||
|
||||
Print a color map along with in-mud color codes, while testing what is supported in your client.
|
||||
Choices are 16-color ansi (supported in most muds) or the 256-color xterm256 standard.
|
||||
No checking is done to determine your client supports color - if not you will
|
||||
see rubbish appear.
|
||||
Print a color map along with in-mud color codes, while testing what is
|
||||
supported in your client. Choices are 16-color ansi (supported in most
|
||||
muds) or the 256-color xterm256 standard. No checking is done to determine
|
||||
your client supports color - if not you will see rubbish appear.
|
||||
"""
|
||||
key = "@color"
|
||||
locks = "cmd:all()"
|
||||
|
|
@ -552,10 +576,10 @@ class CmdColorTest(MuxPlayerCommand):
|
|||
ap = ansi.ANSI_PARSER
|
||||
# ansi colors
|
||||
# show all ansi color-related codes
|
||||
col1 = ["%s%s{n" % (code, code.replace("{","{{")) for code, _ in ap.ext_ansi_map[:-1]]
|
||||
col1 = ["%s%s{n" % (code, code.replace("{", "{{")) for code, _ in ap.ext_ansi_map[:-1]]
|
||||
hi = "%ch"
|
||||
col2 = ["%s%s{n" % (code, code.replace("%", "%%")) for code, _ in ap.mux_ansi_map[3:-2]]
|
||||
col3 = ["%s%s{n" % (hi+code, (hi+code).replace("%", "%%")) for code, _ in ap.mux_ansi_map[3:-2]]
|
||||
col3 = ["%s%s{n" % (hi + code, (hi + code).replace("%", "%%")) for code, _ in ap.mux_ansi_map[3:-2]]
|
||||
table = utils.format_table([col1, col2, col3], extra_space=1)
|
||||
string = "ANSI colors:"
|
||||
for row in table:
|
||||
|
|
@ -566,16 +590,16 @@ class CmdColorTest(MuxPlayerCommand):
|
|||
|
||||
elif self.args.startswith("x"):
|
||||
# show xterm256 table
|
||||
table = [[],[],[],[],[],[],[],[],[],[],[],[]]
|
||||
table = [[], [], [], [], [], [], [], [], [], [], [], []]
|
||||
for ir in range(6):
|
||||
for ig in range(6):
|
||||
for ib in range(6):
|
||||
# foreground table
|
||||
table[ir].append("%%c%i%i%i%s{n" % (ir,ig,ib, "{{%i%i%i" % (ir,ig,ib)))
|
||||
table[ir].append("%%c%i%i%i%s{n" % (ir, ig, ib, "{{%i%i%i" % (ir, ig, ib)))
|
||||
# background table
|
||||
table[6+ir].append("%%cb%i%i%i%%c%i%i%i%s{n" % (ir,ig,ib,
|
||||
5-ir,5-ig,5-ib,
|
||||
"{{b%i%i%i" % (ir,ig,ib)))
|
||||
table[6+ir].append("%%cb%i%i%i%%c%i%i%i%s{n" % (ir, ig, ib,
|
||||
5 - ir, 5 - ig, 5 - ib,
|
||||
"{{b%i%i%i" % (ir, ig, ib)))
|
||||
table = self.table_format(table)
|
||||
string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):"
|
||||
for row in table:
|
||||
|
|
@ -586,6 +610,7 @@ class CmdColorTest(MuxPlayerCommand):
|
|||
# malformed input
|
||||
self.msg("Usage: @color ansi|xterm256")
|
||||
|
||||
|
||||
class CmdQuell(MuxPlayerCommand):
|
||||
"""
|
||||
Quelling permissions
|
||||
|
|
@ -604,7 +629,7 @@ class CmdQuell(MuxPlayerCommand):
|
|||
"""
|
||||
|
||||
key = "@quell"
|
||||
aliases =["@unquell"]
|
||||
aliases = ["@unquell"]
|
||||
locks = "cmd:all()"
|
||||
help_category = "General"
|
||||
|
||||
|
|
@ -613,8 +638,9 @@ class CmdQuell(MuxPlayerCommand):
|
|||
if self.sessid:
|
||||
char = player.get_puppet(self.sessid)
|
||||
if char:
|
||||
# we are already puppeting an object. We need to reset the lock caches
|
||||
# (otherwise the superuser status change won't be visible until repuppet)
|
||||
# we are already puppeting an object. We need to reset
|
||||
# the lock caches (otherwise the superuser status change
|
||||
# won't be visible until repuppet)
|
||||
char.locks.reset()
|
||||
player.locks.reset()
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ from src.commands.default.muxcommand import MuxCommand
|
|||
# Command called when there is no input at line
|
||||
# (i.e. an lone return key)
|
||||
|
||||
|
||||
class SystemNoInput(MuxCommand):
|
||||
"""
|
||||
This is called when there is no input given
|
||||
|
|
@ -44,11 +45,11 @@ class SystemNoInput(MuxCommand):
|
|||
"Do nothing."
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# Command called when there was no match to the
|
||||
# command name
|
||||
#
|
||||
|
||||
class SystemNoMatch(MuxCommand):
|
||||
"""
|
||||
No command was found matching the given input.
|
||||
|
|
@ -62,6 +63,7 @@ class SystemNoMatch(MuxCommand):
|
|||
"""
|
||||
self.caller.msg("Huh?")
|
||||
|
||||
|
||||
#
|
||||
# Command called when there were mulitple matches to the command.
|
||||
#
|
||||
|
|
@ -100,7 +102,7 @@ class SystemMultimatch(MuxCommand):
|
|||
is_channel = ""
|
||||
is_exit = hasattr(cmd, "is_exit") and cmd.is_exit
|
||||
if is_exit and cmd.destination:
|
||||
is_exit = " (exit to %s)" % cmd.destination
|
||||
is_exit = " (exit to %s)" % cmd.destination
|
||||
else:
|
||||
is_exit = ""
|
||||
|
||||
|
|
@ -124,6 +126,7 @@ class SystemMultimatch(MuxCommand):
|
|||
string = self.format_multimatches(self.caller, self.matches)
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
# Command called when the command given at the command line
|
||||
# was identified as a channel name, like there existing a
|
||||
# channel named 'ooc' and the user wrote
|
||||
|
|
@ -167,4 +170,4 @@ class SystemSendToChannel(MuxCommand):
|
|||
return
|
||||
msg = "[%s] %s: %s" % (channel.key, caller.name, msg)
|
||||
msgobj = create.create_message(caller, msg, channels=[channel])
|
||||
channel.msg(msgobj)
|
||||
channel.msg(msgobj)
|
||||
|
|
@ -5,10 +5,13 @@ System commands
|
|||
"""
|
||||
|
||||
import traceback
|
||||
import os, datetime, time
|
||||
from time import time as timemeasure
|
||||
import os
|
||||
import datetime
|
||||
import time
|
||||
import sys
|
||||
import django, twisted
|
||||
import django
|
||||
import twisted
|
||||
from time import time as timemeasure
|
||||
|
||||
from django.conf import settings
|
||||
from src.server.caches import get_cache_sizes
|
||||
|
|
@ -30,6 +33,7 @@ __all__ = ("CmdReload", "CmdReset", "CmdShutdown", "CmdPy",
|
|||
"CmdScripts", "CmdObjects", "CmdService", "CmdAbout",
|
||||
"CmdTime", "CmdServerLoad")
|
||||
|
||||
|
||||
class CmdReload(MuxCommand):
|
||||
"""
|
||||
Reload the system
|
||||
|
|
@ -55,6 +59,7 @@ class CmdReload(MuxCommand):
|
|||
SESSIONS.announce_all(" Server restarting %s..." % reason)
|
||||
SESSIONS.server.shutdown(mode='reload')
|
||||
|
||||
|
||||
class CmdReset(MuxCommand):
|
||||
"""
|
||||
Reset and reboot the system
|
||||
|
|
@ -110,6 +115,7 @@ class CmdShutdown(MuxCommand):
|
|||
SESSIONS.portal_shutdown()
|
||||
SESSIONS.server.shutdown(mode='shutdown')
|
||||
|
||||
|
||||
class CmdPy(MuxCommand):
|
||||
"""
|
||||
Execute a snippet of python code
|
||||
|
|
@ -157,18 +163,17 @@ class CmdPy(MuxCommand):
|
|||
|
||||
# import useful variables
|
||||
import ev
|
||||
available_vars = {'self':caller,
|
||||
'me':caller,
|
||||
'here':hasattr(caller, "location") and caller.location or None,
|
||||
'ev':ev,
|
||||
'inherits_from':utils.inherits_from}
|
||||
available_vars = {'self': caller,
|
||||
'me': caller,
|
||||
'here': hasattr(caller, "location") and caller.location or None,
|
||||
'ev': ev,
|
||||
'inherits_from': utils.inherits_from}
|
||||
|
||||
try:
|
||||
self.msg(">>> %s" % pycode, raw=True, sessid=self.sessid)
|
||||
except TypeError:
|
||||
self.msg(">>> %s" % pycode, raw=True)
|
||||
|
||||
|
||||
mode = "eval"
|
||||
try:
|
||||
try:
|
||||
|
|
@ -195,7 +200,7 @@ class CmdPy(MuxCommand):
|
|||
errlist = errlist[4:]
|
||||
ret = "\n".join("{n<<< %s" % line for line in errlist if line)
|
||||
|
||||
if ret != None:
|
||||
if ret is not None:
|
||||
try:
|
||||
self.msg(ret, sessid=self.sessid)
|
||||
except TypeError:
|
||||
|
|
@ -210,7 +215,16 @@ def format_script_list(scripts):
|
|||
if not scripts:
|
||||
return "<No scripts>"
|
||||
|
||||
table = prettytable.PrettyTable(["{wid","{wobj","{wkey","{wintval","{wnext","{wrept","{wdb"," {wtypeclass","{wdesc"],align='r')
|
||||
table = prettytable.PrettyTable(["{wid",
|
||||
"{wobj",
|
||||
"{wkey",
|
||||
"{wintval",
|
||||
"{wnext",
|
||||
"{wrept",
|
||||
"{wdb",
|
||||
"{wtypeclass",
|
||||
"{wdesc"],
|
||||
align='r')
|
||||
table.align = 'r'
|
||||
for script in scripts:
|
||||
nextrep = script.time_until_next_repeat()
|
||||
|
|
@ -322,7 +336,6 @@ class CmdScripts(MuxCommand):
|
|||
caller.msg(string)
|
||||
|
||||
|
||||
|
||||
class CmdObjects(MuxCommand):
|
||||
"""
|
||||
@objects - Give a summary of object types in database
|
||||
|
|
@ -357,32 +370,37 @@ class CmdObjects(MuxCommand):
|
|||
nother = nobjs - nchars - nrooms - nexits
|
||||
|
||||
# total object sum table
|
||||
totaltable = prettytable.PrettyTable(["{wtype","{wcomment","{wcount", "{w%%"])
|
||||
totaltable = prettytable.PrettyTable(["{wtype", "{wcomment", "{wcount", "{w%%"])
|
||||
totaltable.align = 'l'
|
||||
totaltable.add_row(["Characters", "(BASE_CHARACTER_TYPECLASS)", nchars, "%.2f" % ((float(nchars)/nobjs)*100)])
|
||||
totaltable.add_row(["Rooms", "(location=None)", nrooms, "%.2f" % ((float(nrooms)/nobjs)*100)])
|
||||
totaltable.add_row(["Exits", "(destination!=None)", nexits, "%.2f" % ((float(nexits)/nobjs)*100)])
|
||||
totaltable.add_row(["Other", "", nother, "%.2f" % ((float(nother)/nobjs)*100)])
|
||||
totaltable.add_row(["Characters", "(BASE_CHARACTER_TYPECLASS)", nchars, "%.2f" % ((float(nchars) / nobjs) * 100)])
|
||||
totaltable.add_row(["Rooms", "(location=None)", nrooms, "%.2f" % ((float(nrooms) / nobjs) * 100)])
|
||||
totaltable.add_row(["Exits", "(destination!=None)", nexits, "%.2f" % ((float(nexits) / nobjs) * 100)])
|
||||
totaltable.add_row(["Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100)])
|
||||
|
||||
# typeclass table
|
||||
typetable = prettytable.PrettyTable(["{wtypeclass","{wcount", "{w%%"])
|
||||
typetable = prettytable.PrettyTable(["{wtypeclass", "{wcount", "{w%%"])
|
||||
typetable.align = 'l'
|
||||
dbtotals = ObjectDB.objects.object_totals()
|
||||
for path, count in dbtotals.items():
|
||||
typetable.add_row([path, count, "%.2f" % ((float(count)/nobjs)*100)])
|
||||
typetable.add_row([path, count, "%.2f" % ((float(count) / nobjs) * 100)])
|
||||
|
||||
# last N table
|
||||
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim):]
|
||||
latesttable = prettytable.PrettyTable(["{wcreated","{wdbref","{wname","{wtypeclass"])
|
||||
latesttable = prettytable.PrettyTable(["{wcreated",
|
||||
"{wdbref",
|
||||
"{wname",
|
||||
"{wtypeclass"])
|
||||
latesttable.align = 'l'
|
||||
for obj in objs:
|
||||
latesttable.add_row([utils.datetime_format(obj.date_created), obj.dbref, obj.key, obj.typeclass.path])
|
||||
latesttable.add_row([utils.datetime_format(obj.date_created),
|
||||
obj.dbref, obj.key, obj.typeclass.path])
|
||||
|
||||
string = "\n{wObject subtype totals (out of %i Objects):{n\n%s" % (nobjs, totaltable)
|
||||
string += "\n{wObject typeclass distribution:{n\n%s" % typetable
|
||||
string += "\n{wLast %s Objects created:{n\n%s" % (min(nobjs, nlim), latesttable)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdPlayers(MuxCommand):
|
||||
"""
|
||||
@players - give a summary of all registed Players
|
||||
|
|
@ -397,6 +415,7 @@ class CmdPlayers(MuxCommand):
|
|||
key = "@players"
|
||||
aliases = ["@listplayers"]
|
||||
locks = "cmd:perm(listplayers) or perm(Wizards)"
|
||||
|
||||
def func(self):
|
||||
"List the players"
|
||||
|
||||
|
|
@ -413,10 +432,10 @@ class CmdPlayers(MuxCommand):
|
|||
typetable = prettytable.PrettyTable(["{wtypeclass", "{wcount", "{w%%"])
|
||||
typetable.align = 'l'
|
||||
for path, count in dbtotals.items():
|
||||
typetable.add_row([path, count, "%.2f" % ((float(count)/nplayers)*100)])
|
||||
typetable.add_row([path, count, "%.2f" % ((float(count) / nplayers) * 100)])
|
||||
# last N table
|
||||
plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):]
|
||||
latesttable = prettytable.PrettyTable(["{wcreated", "{wdbref","{wname","{wtypeclass"])
|
||||
latesttable = prettytable.PrettyTable(["{wcreated", "{wdbref", "{wname", "{wtypeclass"])
|
||||
latesttable.align = 'l'
|
||||
for ply in plyrs:
|
||||
latesttable.add_row([utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.typeclass.path])
|
||||
|
|
@ -425,6 +444,7 @@ class CmdPlayers(MuxCommand):
|
|||
string += "\n{wLast %s Players created:{n\n%s" % (min(nplayers, nlim), latesttable)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdService(MuxCommand):
|
||||
"""
|
||||
@service - manage services
|
||||
|
|
@ -441,7 +461,8 @@ class CmdService(MuxCommand):
|
|||
Service management system. Allows for the listing,
|
||||
starting, and stopping of services. If no switches
|
||||
are given, services will be listed. Note that to operate on the
|
||||
service you have to supply the full (green or red) name as given in the list.
|
||||
service you have to supply the full (green or red) name as given
|
||||
in the list.
|
||||
"""
|
||||
|
||||
key = "@service"
|
||||
|
|
@ -520,6 +541,7 @@ class CmdService(MuxCommand):
|
|||
caller.msg("Starting service '%s'." % self.args)
|
||||
service.startService()
|
||||
|
||||
|
||||
class CmdAbout(MuxCommand):
|
||||
"""
|
||||
@about - game engine info
|
||||
|
|
@ -567,6 +589,7 @@ class CmdAbout(MuxCommand):
|
|||
sversion)
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
class CmdTime(MuxCommand):
|
||||
"""
|
||||
@time
|
||||
|
|
@ -591,6 +614,7 @@ class CmdTime(MuxCommand):
|
|||
table.add_row(["Server time stamp", datetime.datetime.now()])
|
||||
self.caller.msg(str(table))
|
||||
|
||||
|
||||
class CmdServerLoad(MuxCommand):
|
||||
"""
|
||||
server load and memory statistics
|
||||
|
|
@ -648,20 +672,20 @@ class CmdServerLoad(MuxCommand):
|
|||
pid = os.getpid()
|
||||
rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1024.0 # resident memory
|
||||
vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1024.0 # virtual memory
|
||||
pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # percent of resident memory to total
|
||||
pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # percent of resident memory to total
|
||||
rusage = resource.getrusage(resource.RUSAGE_SELF)
|
||||
|
||||
# load table
|
||||
loadtable = prettytable.PrettyTable(["property", "statistic"])
|
||||
loadtable.align = 'l'
|
||||
loadtable.add_row(["Server load (1 min)","%g" % loadavg[0]])
|
||||
loadtable.add_row(["Process ID","%g" % pid]),
|
||||
loadtable.add_row(["Bytes per page","%g " % psize])
|
||||
loadtable.add_row(["Server load (1 min)", "%g" % loadavg[0]])
|
||||
loadtable.add_row(["Process ID", "%g" % pid]),
|
||||
loadtable.add_row(["Bytes per page", "%g " % psize])
|
||||
loadtable.add_row(["CPU time used (total)", "%s (%gs)" % (utils.time_format(rusage.ru_utime), rusage.ru_utime)])
|
||||
loadtable.add_row(["CPU time used (user)", "%s (%gs)" % (utils.time_format(rusage.ru_stime), rusage.ru_stime)])
|
||||
loadtable.add_row(["Memory usage","%g MB (%g%%)" % (rmem, pmem)])
|
||||
loadtable.add_row(["Virtual address space\n {x(resident+swap+caching){n", "%g MB" % vmem])
|
||||
loadtable.add_row(["Page faults","%g hard, %g soft, %g swapouts" % (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap)])
|
||||
loadtable.add_row(["Page faults", "%g hard, %g soft, %g swapouts" % (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap)])
|
||||
loadtable.add_row(["Disk I/O", "%g reads, %g writes" % (rusage.ru_inblock, rusage.ru_oublock)])
|
||||
loadtable.add_row(["Network I/O", "%g in, %g out" % (rusage.ru_msgrcv, rusage.ru_msgsnd)])
|
||||
loadtable.add_row(["Context switching", "%g vol, %g forced, %g signals" % (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals)])
|
||||
|
|
@ -669,17 +693,24 @@ class CmdServerLoad(MuxCommand):
|
|||
string = "{wServer CPU and Memory load:{n\n%s" % loadtable
|
||||
|
||||
if not is_pypy:
|
||||
# Cache size measurements are not available on PyPy because it lacks sys.getsizeof
|
||||
# Cache size measurements are not available on PyPy
|
||||
# because it lacks sys.getsizeof
|
||||
|
||||
# object cache size
|
||||
cachedict = _idmapper.cache_size()
|
||||
totcache = cachedict["_total"]
|
||||
sorted_cache = sorted([(key, tup[0], tup[1]) for key, tup in cachedict.items() if key !="_total" and tup[0] > 0],
|
||||
key=lambda tup: tup[2], reverse=True)
|
||||
memtable = prettytable.PrettyTable(["entity name", "number", "cache (MB)", "idmapper %%"])
|
||||
memtable = prettytable.PrettyTable(["entity name",
|
||||
"number",
|
||||
"cache (MB)",
|
||||
"idmapper %%"])
|
||||
memtable.align = 'l'
|
||||
for tup in sorted_cache:
|
||||
memtable.add_row([tup[0], "%i" % tup [1], "%5.2f" % tup[2], "%.2f" % (float(tup[2]/totcache[1])*100)])
|
||||
memtable.add_row([tup[0],
|
||||
"%i" % tup[1],
|
||||
"%5.2f" % tup[2],
|
||||
"%.2f" % (float(tup[2] / totcache[1]) * 100)])
|
||||
|
||||
# get sizes of other caches
|
||||
attr_cache_info, field_cache_info, prop_cache_info = get_cache_sizes()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from django.utils.unittest import TestCase
|
|||
from src.server.serversession import ServerSession
|
||||
from src.objects.objects import Object, Character
|
||||
from src.players.player import Player
|
||||
from src.utils import create, utils, ansi
|
||||
from src.utils import create, ansi
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
|
||||
from django.db.models.signals import pre_save
|
||||
|
|
@ -33,16 +33,20 @@ _RE = re.compile(r"^\+|-+\+|\+-+|--*|\|", re.MULTILINE)
|
|||
# Command testing
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
def dummy(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
SESSIONS.data_out = dummy
|
||||
SESSIONS.disconnect = dummy
|
||||
|
||||
|
||||
class TestObjectClass(Object):
|
||||
def msg(self, text="", **kwargs):
|
||||
"test message"
|
||||
pass
|
||||
|
||||
|
||||
class TestCharacterClass(Character):
|
||||
def msg(self, text="", **kwargs):
|
||||
"test message"
|
||||
|
|
@ -52,17 +56,21 @@ class TestCharacterClass(Character):
|
|||
if not self.ndb.stored_msg:
|
||||
self.ndb.stored_msg = []
|
||||
self.ndb.stored_msg.append(text)
|
||||
|
||||
|
||||
class TestPlayerClass(Player):
|
||||
def msg(self, text="", **kwargs):
|
||||
"test message"
|
||||
if not self.ndb.stored_msg:
|
||||
self.ndb.stored_msg = []
|
||||
self.ndb.stored_msg.append(text)
|
||||
|
||||
def _get_superuser(self):
|
||||
"test with superuser flag"
|
||||
return self.ndb.is_superuser
|
||||
is_superuser = property(_get_superuser)
|
||||
|
||||
|
||||
class CommandTest(TestCase):
|
||||
"""
|
||||
Tests a command
|
||||
|
|
@ -93,6 +101,7 @@ class CommandTest(TestCase):
|
|||
SESSIONS.portal_connect(session.get_sync_data())
|
||||
SESSIONS.login(SESSIONS.session_from_sessid(self.CID), self.player, testmode=True)
|
||||
|
||||
|
||||
def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None):
|
||||
"""
|
||||
Test a command by assigning all the needed
|
||||
|
|
@ -141,6 +150,7 @@ class CommandTest(TestCase):
|
|||
from src.commands.default import general
|
||||
class TestGeneral(CommandTest):
|
||||
CID = 1
|
||||
|
||||
def test_cmds(self):
|
||||
self.call(general.CmdLook(), "here", "Room1\n room_desc")
|
||||
self.call(general.CmdHome(), "", "You are already home")
|
||||
|
|
@ -158,6 +168,7 @@ class TestGeneral(CommandTest):
|
|||
self.call(general.CmdSay(), "Testing", "You say, \"Testing\"")
|
||||
self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):")
|
||||
|
||||
|
||||
from src.commands.default import help
|
||||
from src.commands.default.cmdset_character import CharacterCmdSet
|
||||
class TestHelp(CommandTest):
|
||||
|
|
@ -167,6 +178,7 @@ class TestHelp(CommandTest):
|
|||
self.call(help.CmdSetHelp(), "testhelp, General = This is a test", "Topic 'testhelp' was successfully created.")
|
||||
self.call(help.CmdHelp(), "testhelp", "Help topic for testhelp", cmdset=CharacterCmdSet())
|
||||
|
||||
|
||||
from src.commands.default import system
|
||||
class TestSystem(CommandTest):
|
||||
CID = 3
|
||||
|
|
@ -179,6 +191,7 @@ class TestSystem(CommandTest):
|
|||
self.call(system.CmdAbout(), "", None)
|
||||
self.call(system.CmdServerLoad(), "", "Server CPU and Memory load:")
|
||||
|
||||
|
||||
from src.commands.default import admin
|
||||
class TestAdmin(CommandTest):
|
||||
CID = 4
|
||||
|
|
@ -190,6 +203,7 @@ class TestAdmin(CommandTest):
|
|||
self.call(admin.CmdPerm(), "Char4b = Builders","Permission 'Builders' given to Char4b.")
|
||||
self.call(admin.CmdBan(), "Char4", "NameBan char4 was added.")
|
||||
|
||||
|
||||
from src.commands.default import player
|
||||
class TestPlayer(CommandTest):
|
||||
CID = 5
|
||||
|
|
@ -209,6 +223,7 @@ class TestPlayer(CommandTest):
|
|||
self.call(player.CmdCharCreate(), "Test1=Test char","Created new character Test1. Use @ic Test1 to enter the game", caller=self.player)
|
||||
self.call(player.CmdQuell(), "", "Quelling Player permissions (immortals). Use @unquell to get them back.", caller=self.player)
|
||||
|
||||
|
||||
from src.commands.default import building
|
||||
class TestBuilding(CommandTest):
|
||||
CID = 6
|
||||
|
|
@ -239,6 +254,7 @@ class TestBuilding(CommandTest):
|
|||
self.call(building.CmdScript(), "Obj6 = src.scripts.scripts.Script", "Script src.scripts.scripts.Script successfully added")
|
||||
self.call(building.CmdTeleport(), "TestRoom1", "TestRoom1\nExits: back|Teleported to TestRoom1.")
|
||||
|
||||
|
||||
from src.commands.default import comms
|
||||
class TestComms(CommandTest):
|
||||
CID = 7
|
||||
|
|
@ -257,6 +273,7 @@ class TestComms(CommandTest):
|
|||
self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] <channel> = <player> [:reason]") # noone else connected to boot
|
||||
self.call(comms.CmdCdestroy(), "testchan" ,"Channel 'testchan' was destroyed.")
|
||||
|
||||
|
||||
from src.commands.default import batchprocess
|
||||
class TestBatchProcess(CommandTest):
|
||||
CID = 8
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ from src.commands.default.muxcommand import MuxCommand
|
|||
from src.commands.cmdhandler import CMD_LOGINSTART
|
||||
|
||||
# limit symbol import for API
|
||||
__all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate", "CmdUnconnectedQuit", "CmdUnconnectedLook", "CmdUnconnectedHelp")
|
||||
__all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate",
|
||||
"CmdUnconnectedQuit", "CmdUnconnectedLook", "CmdUnconnectedHelp")
|
||||
|
||||
MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
||||
|
|
@ -26,6 +27,7 @@ except Exception:
|
|||
if not CONNECTION_SCREEN:
|
||||
CONNECTION_SCREEN = "\nEvennia: Error in CONNECTION_SCREEN MODULE (randomly picked connection screen variable is not a string). \nEnter 'help' for aid."
|
||||
|
||||
|
||||
class CmdUnconnectedConnect(MuxCommand):
|
||||
"""
|
||||
Connect to the game.
|
||||
|
|
@ -40,7 +42,7 @@ class CmdUnconnectedConnect(MuxCommand):
|
|||
"""
|
||||
key = "connect"
|
||||
aliases = ["conn", "con", "co"]
|
||||
locks = "cmd:all()" # not really needed
|
||||
locks = "cmd:all()" # not really needed
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
|
|
@ -92,12 +94,13 @@ class CmdUnconnectedConnect(MuxCommand):
|
|||
|
||||
# actually do the login. This will call all other hooks:
|
||||
# session.at_login()
|
||||
# player.at_init() # always called when object is loaded from disk
|
||||
# player.at_init() # always called when object is loaded from disk
|
||||
# player.at_pre_login()
|
||||
# player.at_first_login() # only once
|
||||
# player.at_post_login(sessid=sessid)
|
||||
session.sessionhandler.login(session, player)
|
||||
|
||||
|
||||
class CmdUnconnectedCreate(MuxCommand):
|
||||
"""
|
||||
Create a new account.
|
||||
|
|
@ -134,8 +137,9 @@ class CmdUnconnectedCreate(MuxCommand):
|
|||
|
||||
# sanity checks
|
||||
if not re.findall('^[\w. @+-]+$', playername) or not (0 < len(playername) <= 30):
|
||||
# this echoes the restrictions made by django's auth module (except not
|
||||
# allowing spaces, for convenience of logging in).
|
||||
# this echoes the restrictions made by django's auth
|
||||
# module (except not allowing spaces, for convenience of
|
||||
# logging in).
|
||||
string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only."
|
||||
session.msg(string)
|
||||
return
|
||||
|
|
@ -163,14 +167,14 @@ class CmdUnconnectedCreate(MuxCommand):
|
|||
new_player = create.create_player(playername, None, password,
|
||||
permissions=permissions)
|
||||
|
||||
|
||||
except Exception, e:
|
||||
session.msg("There was an error creating the default Player/Character:\n%s\n If this problem persists, contact an admin." % e)
|
||||
logger.log_trace()
|
||||
return
|
||||
|
||||
# This needs to be called so the engine knows this player is logging in for the first time.
|
||||
# (so it knows to call the right hooks during login later)
|
||||
# This needs to be called so the engine knows this player is
|
||||
# logging in for the first time. (so it knows to call the right
|
||||
# hooks during login later)
|
||||
utils.init_new_player(new_player)
|
||||
|
||||
# join the new player to the public channel
|
||||
|
|
@ -181,7 +185,6 @@ class CmdUnconnectedCreate(MuxCommand):
|
|||
string = "New player '%s' could not connect to public channel!" % new_player.key
|
||||
logger.log_errmsg(string)
|
||||
|
||||
|
||||
if MULTISESSION_MODE < 2:
|
||||
# if we only allow one character, create one with the same name as Player
|
||||
# (in mode 2, the character must be created manually once logging in)
|
||||
|
|
@ -210,12 +213,14 @@ class CmdUnconnectedCreate(MuxCommand):
|
|||
session.msg(string % (playername, playername))
|
||||
|
||||
except Exception:
|
||||
# We are in the middle between logged in and -not, so we have to handle tracebacks
|
||||
# ourselves at this point. If we don't, we won't see any errors at all.
|
||||
# We are in the middle between logged in and -not, so we have
|
||||
# to handle tracebacks ourselves at this point. If we don't,
|
||||
# we won't see any errors at all.
|
||||
string = "%s\nThis is a bug. Please e-mail an admin if the problem persists."
|
||||
session.msg(string % (traceback.format_exc()))
|
||||
logger.log_errmsg(traceback.format_exc())
|
||||
|
||||
|
||||
class CmdUnconnectedQuit(MuxCommand):
|
||||
"""
|
||||
We maintain a different version of the quit command
|
||||
|
|
@ -230,7 +235,8 @@ class CmdUnconnectedQuit(MuxCommand):
|
|||
"Simply close the connection."
|
||||
session = self.caller
|
||||
#session.msg("Good bye! Disconnecting ...")
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting ...")
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||
|
||||
|
||||
class CmdUnconnectedLook(MuxCommand):
|
||||
"""
|
||||
|
|
@ -247,6 +253,7 @@ class CmdUnconnectedLook(MuxCommand):
|
|||
"Show the connect screen."
|
||||
self.caller.msg(CONNECTION_SCREEN)
|
||||
|
||||
|
||||
class CmdUnconnectedHelp(MuxCommand):
|
||||
"""
|
||||
This is an unconnected version of the help command,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
"""
|
||||
Makes it easier to import by grouping all relevant things already at this level.
|
||||
Makes it easier to import by grouping all relevant things already at this
|
||||
level.
|
||||
|
||||
You can henceforth import most things directly from src.comms
|
||||
Also, the initiated object manager is available as src.comms.msgmanager and src.comms.channelmanager.
|
||||
Also, the initiated object manager is available as src.comms.msgmanager and
|
||||
src.comms.channelmanager.
|
||||
|
||||
"""
|
||||
|
||||
from src.comms.models import *
|
||||
from src.comms.models import *
|
||||
|
||||
msgmanager = Msg.objects
|
||||
channelmanager = ChannelDB.objects
|
||||
|
|
|
|||
|
|
@ -1,51 +1,57 @@
|
|||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from django.contrib import admin
|
||||
from src.comms.models import ChannelDB, Msg, PlayerChannelConnection, ExternalChannelConnection
|
||||
|
||||
class MsgAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'db_date_sent', 'db_sender', 'db_receivers', 'db_channels', 'db_message', 'db_lock_storage')
|
||||
list_display_links = ("id",)
|
||||
ordering = ["db_date_sent", 'db_sender', 'db_receivers', 'db_channels']
|
||||
#readonly_fields = ['db_message', 'db_sender', 'db_receivers', 'db_channels']
|
||||
search_fields = ['id', '^db_date_sent', '^db_message']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
#admin.site.register(Msg, MsgAdmin)
|
||||
|
||||
class PlayerChannelConnectionInline(admin.TabularInline):
|
||||
model = PlayerChannelConnection
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields':(('db_player', 'db_channel')),
|
||||
'classes':('collapse',)}),)
|
||||
extra = 1
|
||||
|
||||
class ExternalChannelConnectionInline(admin.StackedInline):
|
||||
model = ExternalChannelConnection
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields':(('db_is_enabled','db_external_key', 'db_channel'), 'db_external_send_code', 'db_external_config'),
|
||||
'classes':('collapse',)
|
||||
}),)
|
||||
extra = 1
|
||||
|
||||
class ChannelAdmin(admin.ModelAdmin):
|
||||
inlines = (PlayerChannelConnectionInline, ExternalChannelConnectionInline)
|
||||
|
||||
list_display = ('id', 'db_key', 'db_lock_storage')
|
||||
list_display_links = ("id", 'db_key')
|
||||
ordering = ["db_key"]
|
||||
search_fields = ['id', 'db_key', 'db_aliases']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
fieldsets = (
|
||||
(None, {'fields':(('db_key',),'db_lock_storage',)}),
|
||||
)
|
||||
|
||||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from django.contrib import admin
|
||||
from src.comms.models import ChannelDB, Msg, PlayerChannelConnection, ExternalChannelConnection
|
||||
|
||||
|
||||
class MsgAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'db_date_sent', 'db_sender', 'db_receivers',
|
||||
'db_channels', 'db_message', 'db_lock_storage')
|
||||
list_display_links = ("id",)
|
||||
ordering = ["db_date_sent", 'db_sender', 'db_receivers', 'db_channels']
|
||||
#readonly_fields = ['db_message', 'db_sender', 'db_receivers', 'db_channels']
|
||||
search_fields = ['id', '^db_date_sent', '^db_message']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
#admin.site.register(Msg, MsgAdmin)
|
||||
|
||||
|
||||
class PlayerChannelConnectionInline(admin.TabularInline):
|
||||
model = PlayerChannelConnection
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields':(('db_player', 'db_channel')),
|
||||
'classes':('collapse',)}),)
|
||||
extra = 1
|
||||
|
||||
|
||||
class ExternalChannelConnectionInline(admin.StackedInline):
|
||||
model = ExternalChannelConnection
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': (('db_is_enabled','db_external_key', 'db_channel'),
|
||||
'db_external_send_code', 'db_external_config'),
|
||||
'classes': ('collapse',)
|
||||
}),)
|
||||
extra = 1
|
||||
|
||||
|
||||
class ChannelAdmin(admin.ModelAdmin):
|
||||
inlines = (PlayerChannelConnectionInline, ExternalChannelConnectionInline)
|
||||
|
||||
list_display = ('id', 'db_key', 'db_lock_storage')
|
||||
list_display_links = ("id", 'db_key')
|
||||
ordering = ["db_key"]
|
||||
search_fields = ['id', 'db_key', 'db_aliases']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
fieldsets = (
|
||||
(None, {'fields': (('db_key',), 'db_lock_storage',)}),
|
||||
)
|
||||
|
||||
admin.site.register(ChannelDB, ChannelAdmin)
|
||||
|
|
@ -23,9 +23,9 @@ update() on the channelhandler. Or use Channel.objects.delete() which
|
|||
does this for you.
|
||||
|
||||
"""
|
||||
from src.comms.models import ChannelDB, Msg
|
||||
from src.comms.models import ChannelDB
|
||||
from src.commands import cmdset, command
|
||||
from src.utils import utils
|
||||
|
||||
|
||||
class ChannelCommand(command.Command):
|
||||
"""
|
||||
|
|
@ -51,7 +51,8 @@ class ChannelCommand(command.Command):
|
|||
"""
|
||||
Simple parser
|
||||
"""
|
||||
channelname, msg = self.args.split(":", 1) # cmdhandler sends channame:msg here.
|
||||
# cmdhandler sends channame:msg here.
|
||||
channelname, msg = self.args.split(":", 1)
|
||||
self.args = (channelname.strip(), msg.strip())
|
||||
|
||||
def func(self):
|
||||
|
|
@ -128,7 +129,7 @@ class ChannelHandler(object):
|
|||
help_category="Channel names",
|
||||
obj=channel,
|
||||
is_channel=True)
|
||||
cmd.__doc__= self._format_help(channel)
|
||||
cmd.__doc__ = self._format_help(channel)
|
||||
self.cached_channel_cmds.append(cmd)
|
||||
self.cached_cmdsets = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ class Comm(TypeClass):
|
|||
else:
|
||||
return '%s: %s' % (sender_string, message)
|
||||
|
||||
|
||||
def format_external(self, msg, senders, emit=False):
|
||||
"""
|
||||
Used for formatting external messages. This is needed as a separate
|
||||
|
|
@ -71,7 +70,6 @@ class Comm(TypeClass):
|
|||
senders = ', '.join(senders)
|
||||
return self.pose_transform(msg, senders)
|
||||
|
||||
|
||||
def format_message(self, msg, emit=False):
|
||||
"""
|
||||
Formats a message body for display.
|
||||
|
|
@ -169,30 +167,39 @@ class Comm(TypeClass):
|
|||
conn.player.msg(msg.message, from_obj=msg.senders)
|
||||
except AttributeError:
|
||||
try:
|
||||
conn.to_external(msg.message, senders=msg.senders, from_channel=self)
|
||||
conn.to_external(msg.message,
|
||||
senders=msg.senders, from_channel=self)
|
||||
except Exception:
|
||||
logger.log_trace("Cannot send msg to connection '%s'" % conn)
|
||||
|
||||
|
||||
def msg(self, msgobj, header=None, senders=None, sender_strings=None,
|
||||
persistent=True, online=False, emit=False, external=False):
|
||||
"""
|
||||
Send the given message to all players connected to channel. Note that
|
||||
no permission-checking is done here; it is assumed to have been
|
||||
done before calling this method. The optional keywords are not used if persistent is False.
|
||||
done before calling this method. The optional keywords are not used if
|
||||
persistent is False.
|
||||
|
||||
msgobj - a Msg/TempMsg instance or a message string. If one of the former, the remaining
|
||||
keywords will be ignored. If a string, this will either be sent as-is (if persistent=False) or
|
||||
it will be used together with header and senders keywords to create a Msg instance on the fly.
|
||||
senders - an object, player or a list of objects or players. Optional if persistent=False.
|
||||
sender_strings - Name strings of senders. Used for external connections where the sender
|
||||
is not a player or object. When this is defined, external will be assumed.
|
||||
msgobj - a Msg/TempMsg instance or a message string. If one of the
|
||||
former, the remaining keywords will be ignored. If a string,
|
||||
this will either be sent as-is (if persistent=False) or it
|
||||
will be used together with header and senders keywords to
|
||||
create a Msg instance on the fly.
|
||||
senders - an object, player or a list of objects or players.
|
||||
Optional if persistent=False.
|
||||
sender_strings - Name strings of senders. Used for external
|
||||
connections where the sender is not a player or object. When
|
||||
this is defined, external will be assumed.
|
||||
external - Treat this message agnostic of its sender.
|
||||
persistent (bool) - ignored if msgobj is a Msg or TempMsg. If True, a Msg will be created, using
|
||||
header and senders keywords. If False, other keywords will be ignored.
|
||||
online (bool) - If this is set true, only messages people who are online. Otherwise, messages all players
|
||||
connected. This can make things faster, but may not trigger listeners on players that are offline.
|
||||
emit (bool) - Signals to the message formatter that this message is not to be directly associated with a name.
|
||||
persistent (bool) - ignored if msgobj is a Msg or TempMsg. If True,
|
||||
a Msg will be created, using header and senders keywords. If
|
||||
False, other keywords will be ignored.
|
||||
online (bool) - If this is set true, only messages people who are
|
||||
online. Otherwise, messages all players connected. This can
|
||||
make things faster, but may not trigger listeners on players
|
||||
that are offline.
|
||||
emit (bool) - Signals to the message formatter that this message is
|
||||
not to be directly associated with a name.
|
||||
"""
|
||||
if senders:
|
||||
senders = make_iter(senders)
|
||||
|
|
@ -209,7 +216,7 @@ class Comm(TypeClass):
|
|||
msgobj = TempMsg()
|
||||
msgobj.header = header
|
||||
msgobj.message = msg
|
||||
msgobj.channels = [self.dbobj] # add this channel
|
||||
msgobj.channels = [self.dbobj] # add this channel
|
||||
|
||||
if not msgobj.senders:
|
||||
msgobj.senders = senders
|
||||
|
|
|
|||
|
|
@ -65,11 +65,14 @@ class Send_IsAlive(Script):
|
|||
self.interval = 900
|
||||
self.desc = _("Send an IMC2 is-alive packet")
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
IMC2_CLIENT.send_packet(pck.IMC2PacketIsAlive())
|
||||
|
||||
def is_valid(self):
|
||||
"Is only valid as long as there are channels to update"
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("imc2_"))
|
||||
return any(service for service in SESSIONS.server.services
|
||||
if service.name.startswith("imc2_"))
|
||||
|
||||
class Send_Keepalive_Request(Script):
|
||||
"""
|
||||
|
|
@ -81,11 +84,14 @@ class Send_Keepalive_Request(Script):
|
|||
self.interval = 3500
|
||||
self.desc = _("Send an IMC2 keepalive-request packet")
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
IMC2_CLIENT.channel.send_packet(pck.IMC2PacketKeepAliveRequest())
|
||||
|
||||
def is_valid(self):
|
||||
"Is only valid as long as there are channels to update"
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("imc2_"))
|
||||
return any(service for service in SESSIONS.server.services
|
||||
if service.name.startswith("imc2_"))
|
||||
|
||||
class Prune_Inactive_Muds(Script):
|
||||
"""
|
||||
|
|
@ -99,13 +105,17 @@ class Prune_Inactive_Muds(Script):
|
|||
self.desc = _("Check IMC2 list for inactive games")
|
||||
self.persistent = True
|
||||
self.inactive_threshold = 3599
|
||||
|
||||
def at_repeat(self):
|
||||
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
|
||||
if time() - mudinfo.last_updated > self.inactive_threshold:
|
||||
del IMC2_MUDLIST.mud_list[name]
|
||||
|
||||
def is_valid(self):
|
||||
"Is only valid as long as there are channels to update"
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("imc2_"))
|
||||
return any(service for service in SESSIONS.server.services
|
||||
if service.name.startswith("imc2_"))
|
||||
|
||||
|
||||
class Sync_Server_Channel_List(Script):
|
||||
"""
|
||||
|
|
@ -116,21 +126,25 @@ class Sync_Server_Channel_List(Script):
|
|||
"""
|
||||
def at_script_creation(self):
|
||||
self.key = "IMC2_Sync_Server_Channel_List"
|
||||
self.interval = 24 * 3600 # once every day
|
||||
self.interval = 24 * 3600 # once every day
|
||||
self.desc = _("Re-sync IMC2 network channel list")
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
checked_networks = []
|
||||
network = IMC2_CLIENT.factory.network
|
||||
if not network in checked_networks:
|
||||
channel.send_packet(pkg.IMC2PacketIceRefresh())
|
||||
checked_networks.append(network)
|
||||
|
||||
def is_valid(self):
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("imc2_"))
|
||||
return any(service for service in SESSIONS.server.services
|
||||
if service.name.startswith("imc2_"))
|
||||
|
||||
|
||||
#
|
||||
# IMC2 protocol
|
||||
#
|
||||
|
||||
class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
||||
"""
|
||||
Provides the abstraction for the IMC2 protocol. Handles connection,
|
||||
|
|
@ -145,7 +159,6 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
self.network_name = None
|
||||
self.sequence = None
|
||||
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
Triggered after connecting to the IMC2 network.
|
||||
|
|
@ -179,8 +192,10 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
# This gets incremented with every command.
|
||||
self.sequence += 1
|
||||
packet.imc2_protocol = self
|
||||
packet_str = utils.to_str(packet.assemble(self.factory.mudname, self.factory.client_pwd, self.factory.server_pwd))
|
||||
if IMC2_DEBUG and not (hasattr(packet, 'packet_type') and packet.packet_type == "is-alive"):
|
||||
packet_str = utils.to_str(packet.assemble(self.factory.mudname,
|
||||
self.factory.client_pwd, self.factory.server_pwd))
|
||||
if IMC2_DEBUG and not (hasattr(packet, 'packet_type') and
|
||||
packet.packet_type == "is-alive"):
|
||||
logger.log_infomsg("IMC2: SENT> %s" % packet_str)
|
||||
logger.log_infomsg(str(packet))
|
||||
self.sendLine(packet_str)
|
||||
|
|
@ -257,9 +272,9 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
"""
|
||||
Handle tells over IMC2 by formatting the text properly
|
||||
"""
|
||||
return _("{c%(sender)s@%(origin)s{n {wpages (over IMC):{n %(msg)s") % {"sender":packet.sender,
|
||||
"origin":packet.origin,
|
||||
"msg":packet.optional_data.get('text', 'ERROR: No text provided.')}
|
||||
return _("{c%(sender)s@%(origin)s{n {wpages (over IMC):{n %(msg)s") % {"sender": packet.sender,
|
||||
"origin": packet.origin,
|
||||
"msg": packet.optional_data.get('text', 'ERROR: No text provided.')}
|
||||
|
||||
def lineReceived(self, line):
|
||||
"""
|
||||
|
|
@ -349,6 +364,7 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
target = data.get("target", "Unknown")
|
||||
self.send_packet(pck.IMC2PacketWhois(from_obj.id, target))
|
||||
|
||||
|
||||
class IMC2Factory(protocol.ClientFactory):
|
||||
"""
|
||||
Creates instances of the IMC2Protocol. Should really only ever
|
||||
|
|
@ -382,7 +398,9 @@ def build_connection_key(channel, imc2_channel):
|
|||
"Build an id hash for the connection"
|
||||
if hasattr(channel, "key"):
|
||||
channel = channel.key
|
||||
return "imc2_%s:%s(%s)%s<>%s" % (IMC2_NETWORK, IMC2_PORT, IMC2_MUDNAME, imc2_channel, channel)
|
||||
return "imc2_%s:%s(%s)%s<>%s" % (IMC2_NETWORK, IMC2_PORT,
|
||||
IMC2_MUDNAME, imc2_channel, channel)
|
||||
|
||||
|
||||
def start_scripts(validate=False):
|
||||
"""
|
||||
|
|
@ -402,6 +420,7 @@ def start_scripts(validate=False):
|
|||
if not search.scripts("IMC2_Sync_Server_Channel_List"):
|
||||
create.create_script(Sync_Server_Channel_List)
|
||||
|
||||
|
||||
def create_connection(channel, imc2_channel):
|
||||
"""
|
||||
This will create a new IMC2<->channel connection.
|
||||
|
|
@ -417,7 +436,8 @@ def create_connection(channel, imc2_channel):
|
|||
|
||||
old_conns = ExternalChannelConnection.objects.filter(db_external_key=key)
|
||||
if old_conns:
|
||||
# this evennia channel is already connected to imc. Check if imc2_channel is different.
|
||||
# this evennia channel is already connected to imc. Check
|
||||
# if imc2_channel is different.
|
||||
# connection already exists. We try to only connect a new channel
|
||||
old_config = old_conns[0].db_external_config.split(",")
|
||||
if imc2_channel in old_config:
|
||||
|
|
@ -432,9 +452,9 @@ def create_connection(channel, imc2_channel):
|
|||
# no old connection found; create a new one.
|
||||
config = imc2_channel
|
||||
# how the evennia channel will be able to contact this protocol in reverse
|
||||
send_code = "from src.comms.imc2 import IMC2_CLIENT\n"
|
||||
send_code += "data={'channel':from_channel}\n"
|
||||
send_code += "IMC2_CLIENT.msg_imc2(message, senders=[self])\n"
|
||||
send_code = "from src.comms.imc2 import IMC2_CLIENT\n"
|
||||
send_code += "data={'channel':from_channel}\n"
|
||||
send_code += "IMC2_CLIENT.msg_imc2(message, senders=[self])\n"
|
||||
conn = ExternalChannelConnection(db_channel=channel, db_external_key=key, db_external_send_code=send_code,
|
||||
db_external_config=config)
|
||||
conn.save()
|
||||
|
|
@ -453,15 +473,22 @@ def delete_connection(channel, imc2_channel):
|
|||
conn.delete()
|
||||
return True
|
||||
|
||||
|
||||
def connect_to_imc2():
|
||||
"Create the imc instance and connect to the IMC2 network."
|
||||
|
||||
# connect
|
||||
imc = internet.TCPClient(IMC2_NETWORK, int(IMC2_PORT), IMC2Factory(IMC2_NETWORK, IMC2_PORT, IMC2_MUDNAME,
|
||||
IMC2_CLIENT_PWD, IMC2_SERVER_PWD))
|
||||
imc = internet.TCPClient(IMC2_NETWORK,
|
||||
int(IMC2_PORT),
|
||||
IMC2Factory(IMC2_NETWORK,
|
||||
IMC2_PORT,
|
||||
IMC2_MUDNAME,
|
||||
IMC2_CLIENT_PWD,
|
||||
IMC2_SERVER_PWD))
|
||||
imc.setName("imc2_%s:%s(%s)" % (IMC2_NETWORK, IMC2_PORT, IMC2_MUDNAME))
|
||||
SESSIONS.server.services.addService(imc)
|
||||
|
||||
|
||||
def connect_all():
|
||||
"""
|
||||
Activates the imc2 system. Called by the server if IMC2_ENABLED=True.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -1,59 +1,60 @@
|
|||
"""
|
||||
ANSI parser - this adds colour to text according to
|
||||
special markup strings.
|
||||
|
||||
This is a IMC2 complacent version.
|
||||
"""
|
||||
|
||||
import re
|
||||
from src.utils import ansi
|
||||
|
||||
class IMCANSIParser(ansi.ANSIParser):
|
||||
"""
|
||||
This parser is per the IMC2 specification.
|
||||
"""
|
||||
def __init__(self):
|
||||
normal = ansi.ANSI_NORMAL
|
||||
hilite = ansi.ANSI_HILITE
|
||||
self.ansi_map = [
|
||||
(r'~Z', normal), # Random
|
||||
(r'~x', normal + ansi.ANSI_BLACK), # Black
|
||||
(r'~D', hilite + ansi.ANSI_BLACK), # Dark Grey
|
||||
(r'~z', hilite + ansi.ANSI_BLACK),
|
||||
(r'~w', normal + ansi.ANSI_WHITE), # Grey
|
||||
(r'~W', hilite + ansi.ANSI_WHITE), # White
|
||||
(r'~g', normal + ansi.ANSI_GREEN), # Dark Green
|
||||
(r'~G', hilite + ansi.ANSI_GREEN), # Green
|
||||
(r'~p', normal + ansi.ANSI_MAGENTA), # Dark magenta
|
||||
(r'~m', normal + ansi.ANSI_MAGENTA),
|
||||
(r'~M', hilite + ansi.ANSI_MAGENTA), # Magenta
|
||||
(r'~P', hilite + ansi.ANSI_MAGENTA),
|
||||
(r'~c', normal + ansi.ANSI_CYAN), # Cyan
|
||||
(r'~y', normal + ansi.ANSI_YELLOW), # Dark Yellow (brown)
|
||||
(r'~Y', hilite + ansi.ANSI_YELLOW), # Yellow
|
||||
(r'~b', normal + ansi.ANSI_BLUE), # Dark Blue
|
||||
(r'~B', hilite + ansi.ANSI_BLUE), # Blue
|
||||
(r'~C', hilite + ansi.ANSI_BLUE),
|
||||
(r'~r', normal + ansi.ANSI_RED), # Dark Red
|
||||
(r'~R', hilite + ansi.ANSI_RED), # Red
|
||||
|
||||
## Formatting
|
||||
(r'~L', hilite), # Bold/hilite
|
||||
(r'~!', normal), # reset
|
||||
(r'\\r', normal),
|
||||
(r'\\n', ansi.ANSI_RETURN),
|
||||
]
|
||||
# prepare regex matching
|
||||
self.ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1])
|
||||
for sub in self.ansi_map]
|
||||
# prepare matching ansi codes overall
|
||||
self.ansi_regex = re.compile("\033\[[0-9;]+m")
|
||||
|
||||
|
||||
ANSI_PARSER = IMCANSIParser()
|
||||
|
||||
def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER):
|
||||
"""
|
||||
Shortcut to use the IMC2 ANSI parser.
|
||||
"""
|
||||
return parser.parse_ansi(string, strip_ansi=strip_ansi)
|
||||
"""
|
||||
ANSI parser - this adds colour to text according to
|
||||
special markup strings.
|
||||
|
||||
This is a IMC2 complacent version.
|
||||
"""
|
||||
|
||||
import re
|
||||
from src.utils import ansi
|
||||
|
||||
|
||||
class IMCANSIParser(ansi.ANSIParser):
|
||||
"""
|
||||
This parser is per the IMC2 specification.
|
||||
"""
|
||||
def __init__(self):
|
||||
normal = ansi.ANSI_NORMAL
|
||||
hilite = ansi.ANSI_HILITE
|
||||
self.ansi_map = [
|
||||
(r'~Z', normal), # Random
|
||||
(r'~x', normal + ansi.ANSI_BLACK), # Black
|
||||
(r'~D', hilite + ansi.ANSI_BLACK), # Dark Grey
|
||||
(r'~z', hilite + ansi.ANSI_BLACK),
|
||||
(r'~w', normal + ansi.ANSI_WHITE), # Grey
|
||||
(r'~W', hilite + ansi.ANSI_WHITE), # White
|
||||
(r'~g', normal + ansi.ANSI_GREEN), # Dark Green
|
||||
(r'~G', hilite + ansi.ANSI_GREEN), # Green
|
||||
(r'~p', normal + ansi.ANSI_MAGENTA), # Dark magenta
|
||||
(r'~m', normal + ansi.ANSI_MAGENTA),
|
||||
(r'~M', hilite + ansi.ANSI_MAGENTA), # Magenta
|
||||
(r'~P', hilite + ansi.ANSI_MAGENTA),
|
||||
(r'~c', normal + ansi.ANSI_CYAN), # Cyan
|
||||
(r'~y', normal + ansi.ANSI_YELLOW), # Dark Yellow (brown)
|
||||
(r'~Y', hilite + ansi.ANSI_YELLOW), # Yellow
|
||||
(r'~b', normal + ansi.ANSI_BLUE), # Dark Blue
|
||||
(r'~B', hilite + ansi.ANSI_BLUE), # Blue
|
||||
(r'~C', hilite + ansi.ANSI_BLUE),
|
||||
(r'~r', normal + ansi.ANSI_RED), # Dark Red
|
||||
(r'~R', hilite + ansi.ANSI_RED), # Red
|
||||
|
||||
## Formatting
|
||||
(r'~L', hilite), # Bold/hilite
|
||||
(r'~!', normal), # reset
|
||||
(r'\\r', normal),
|
||||
(r'\\n', ansi.ANSI_RETURN),
|
||||
]
|
||||
# prepare regex matching
|
||||
self.ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1])
|
||||
for sub in self.ansi_map]
|
||||
# prepare matching ansi codes overall
|
||||
self.ansi_regex = re.compile("\033\[[0-9;]+m")
|
||||
|
||||
ANSI_PARSER = IMCANSIParser()
|
||||
|
||||
|
||||
def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER):
|
||||
"""
|
||||
Shortcut to use the IMC2 ANSI parser.
|
||||
"""
|
||||
return parser.parse_ansi(string, strip_ansi=strip_ansi)
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
"""
|
||||
This module handles some of the -reply packets like whois-reply.
|
||||
|
||||
"""
|
||||
from src.objects.models import ObjectDB
|
||||
from src.comms.imc2lib import imc2_ansi
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
def handle_whois_reply(packet):
|
||||
"""
|
||||
When the player sends an imcwhois <playername> request, the outgoing
|
||||
packet contains the id of the one asking. This handler catches the
|
||||
(possible) reply from the server, parses the id back to the
|
||||
original asker and tells them the result.
|
||||
"""
|
||||
try:
|
||||
pobject = ObjectDB.objects.get(id=packet.target)
|
||||
response_text = imc2_ansi.parse_ansi(packet.optional_data.get('text', 'Unknown'))
|
||||
string = _('Whois reply from %(origin)s: %(msg)s') % {"origin":packet.origin, "msg":response_text}
|
||||
pobject.msg(string.strip())
|
||||
except ObjectDB.DoesNotExist:
|
||||
# No match found for whois sender. Ignore it.
|
||||
pass
|
||||
"""
|
||||
This module handles some of the -reply packets like whois-reply.
|
||||
|
||||
"""
|
||||
from src.objects.models import ObjectDB
|
||||
from src.comms.imc2lib import imc2_ansi
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
def handle_whois_reply(packet):
|
||||
"""
|
||||
When the player sends an imcwhois <playername> request, the outgoing
|
||||
packet contains the id of the one asking. This handler catches the
|
||||
(possible) reply from the server, parses the id back to the
|
||||
original asker and tells them the result.
|
||||
"""
|
||||
try:
|
||||
pobject = ObjectDB.objects.get(id=packet.target)
|
||||
response_text = imc2_ansi.parse_ansi(packet.optional_data.get('text', 'Unknown'))
|
||||
string = _('Whois reply from %(origin)s: %(msg)s') % {"origin":packet.origin, "msg":response_text}
|
||||
pobject.msg(string.strip())
|
||||
except ObjectDB.DoesNotExist:
|
||||
# No match found for whois sender. Ignore it.
|
||||
pass
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,104 +1,108 @@
|
|||
"""
|
||||
Certain periodic packets are sent by connected MUDs (is-alive, user-cache,
|
||||
etc). The IMC2 protocol assumes that each connected MUD will capture these and
|
||||
populate/maintain their own lists of other servers connected. This module
|
||||
contains stuff like this.
|
||||
"""
|
||||
from time import time
|
||||
|
||||
class IMC2Mud(object):
|
||||
"""
|
||||
Stores information about other games connected to our current IMC2 network.
|
||||
"""
|
||||
def __init__(self, packet):
|
||||
self.name = packet.origin
|
||||
self.versionid = packet.optional_data.get('versionid', None)
|
||||
self.networkname = packet.optional_data.get('networkname', None)
|
||||
self.url = packet.optional_data.get('url', None)
|
||||
self.host = packet.optional_data.get('host', None)
|
||||
self.port = packet.optional_data.get('port', None)
|
||||
self.sha256 = packet.optional_data.get('sha256', None)
|
||||
# This is used to determine when a Mud has fallen into inactive status.
|
||||
self.last_updated = time()
|
||||
|
||||
class IMC2MudList(object):
|
||||
"""
|
||||
Keeps track of other MUDs connected to the IMC network.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Mud list is stored in a dict, key being the IMC Mud name.
|
||||
self.mud_list = {}
|
||||
|
||||
def get_mud_list(self):
|
||||
"""
|
||||
Returns a sorted list of connected Muds.
|
||||
"""
|
||||
muds = self.mud_list.items()
|
||||
muds.sort()
|
||||
return [value for key, value in muds]
|
||||
|
||||
def update_mud_from_packet(self, packet):
|
||||
"""
|
||||
This grabs relevant info from the packet and stuffs it in the
|
||||
Mud list for later retrieval.
|
||||
"""
|
||||
mud = IMC2Mud(packet)
|
||||
self.mud_list[mud.name] = mud
|
||||
|
||||
def remove_mud_from_packet(self, packet):
|
||||
"""
|
||||
Removes a mud from the Mud list when given a packet.
|
||||
"""
|
||||
mud = IMC2Mud(packet)
|
||||
try:
|
||||
del self.mud_list[mud.name]
|
||||
except KeyError:
|
||||
# No matching entry, no big deal.
|
||||
pass
|
||||
|
||||
class IMC2Channel(object):
|
||||
"""
|
||||
Stores information about channels available on the network.
|
||||
"""
|
||||
def __init__(self, packet):
|
||||
self.localname = packet.optional_data.get('localname', None)
|
||||
self.name = packet.optional_data.get('channel', None)
|
||||
self.level = packet.optional_data.get('level', None)
|
||||
self.owner = packet.optional_data.get('owner', None)
|
||||
self.policy = packet.optional_data.get('policy', None)
|
||||
self.last_updated = time()
|
||||
|
||||
class IMC2ChanList(object):
|
||||
"""
|
||||
Keeps track of other MUDs connected to the IMC network.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Chan list is stored in a dict, key being the IMC Mud name.
|
||||
self.chan_list = {}
|
||||
|
||||
def get_channel_list(self):
|
||||
"""
|
||||
Returns a sorted list of cached channels.
|
||||
"""
|
||||
channels = self.chan_list.items()
|
||||
channels.sort()
|
||||
return [value for key, value in channels]
|
||||
|
||||
def update_channel_from_packet(self, packet):
|
||||
"""
|
||||
This grabs relevant info from the packet and stuffs it in the
|
||||
channel list for later retrieval.
|
||||
"""
|
||||
channel = IMC2Channel(packet)
|
||||
self.chan_list[channel.name] = channel
|
||||
|
||||
def remove_channel_from_packet(self, packet):
|
||||
"""
|
||||
Removes a channel from the Channel list when given a packet.
|
||||
"""
|
||||
channel = IMC2Channel(packet)
|
||||
try:
|
||||
del self.chan_list[channel.name]
|
||||
except KeyError:
|
||||
# No matching entry, no big deal.
|
||||
pass
|
||||
"""
|
||||
Certain periodic packets are sent by connected MUDs (is-alive, user-cache,
|
||||
etc). The IMC2 protocol assumes that each connected MUD will capture these and
|
||||
populate/maintain their own lists of other servers connected. This module
|
||||
contains stuff like this.
|
||||
"""
|
||||
from time import time
|
||||
|
||||
|
||||
class IMC2Mud(object):
|
||||
"""
|
||||
Stores information about other games connected to our current IMC2 network.
|
||||
"""
|
||||
def __init__(self, packet):
|
||||
self.name = packet.origin
|
||||
self.versionid = packet.optional_data.get('versionid', None)
|
||||
self.networkname = packet.optional_data.get('networkname', None)
|
||||
self.url = packet.optional_data.get('url', None)
|
||||
self.host = packet.optional_data.get('host', None)
|
||||
self.port = packet.optional_data.get('port', None)
|
||||
self.sha256 = packet.optional_data.get('sha256', None)
|
||||
# This is used to determine when a Mud has fallen into inactive status.
|
||||
self.last_updated = time()
|
||||
|
||||
|
||||
class IMC2MudList(object):
|
||||
"""
|
||||
Keeps track of other MUDs connected to the IMC network.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Mud list is stored in a dict, key being the IMC Mud name.
|
||||
self.mud_list = {}
|
||||
|
||||
def get_mud_list(self):
|
||||
"""
|
||||
Returns a sorted list of connected Muds.
|
||||
"""
|
||||
muds = self.mud_list.items()
|
||||
muds.sort()
|
||||
return [value for key, value in muds]
|
||||
|
||||
def update_mud_from_packet(self, packet):
|
||||
"""
|
||||
This grabs relevant info from the packet and stuffs it in the
|
||||
Mud list for later retrieval.
|
||||
"""
|
||||
mud = IMC2Mud(packet)
|
||||
self.mud_list[mud.name] = mud
|
||||
|
||||
def remove_mud_from_packet(self, packet):
|
||||
"""
|
||||
Removes a mud from the Mud list when given a packet.
|
||||
"""
|
||||
mud = IMC2Mud(packet)
|
||||
try:
|
||||
del self.mud_list[mud.name]
|
||||
except KeyError:
|
||||
# No matching entry, no big deal.
|
||||
pass
|
||||
|
||||
|
||||
class IMC2Channel(object):
|
||||
"""
|
||||
Stores information about channels available on the network.
|
||||
"""
|
||||
def __init__(self, packet):
|
||||
self.localname = packet.optional_data.get('localname', None)
|
||||
self.name = packet.optional_data.get('channel', None)
|
||||
self.level = packet.optional_data.get('level', None)
|
||||
self.owner = packet.optional_data.get('owner', None)
|
||||
self.policy = packet.optional_data.get('policy', None)
|
||||
self.last_updated = time()
|
||||
|
||||
|
||||
class IMC2ChanList(object):
|
||||
"""
|
||||
Keeps track of other MUDs connected to the IMC network.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Chan list is stored in a dict, key being the IMC Mud name.
|
||||
self.chan_list = {}
|
||||
|
||||
def get_channel_list(self):
|
||||
"""
|
||||
Returns a sorted list of cached channels.
|
||||
"""
|
||||
channels = self.chan_list.items()
|
||||
channels.sort()
|
||||
return [value for key, value in channels]
|
||||
|
||||
def update_channel_from_packet(self, packet):
|
||||
"""
|
||||
This grabs relevant info from the packet and stuffs it in the
|
||||
channel list for later retrieval.
|
||||
"""
|
||||
channel = IMC2Channel(packet)
|
||||
self.chan_list[channel.name] = channel
|
||||
|
||||
def remove_channel_from_packet(self, packet):
|
||||
"""
|
||||
Removes a channel from the Channel list when given a packet.
|
||||
"""
|
||||
channel = IMC2Channel(packet)
|
||||
try:
|
||||
del self.chan_list[channel.name]
|
||||
except KeyError:
|
||||
# No matching entry, no big deal.
|
||||
pass
|
||||
|
|
|
|||
423
src/comms/irc.py
423
src/comms/irc.py
|
|
@ -1,205 +1,218 @@
|
|||
"""
|
||||
This connects to an IRC network/channel and launches an 'bot' onto it.
|
||||
The bot then pipes what is being said between the IRC channel and one or
|
||||
more Evennia channels.
|
||||
"""
|
||||
from twisted.application import internet
|
||||
from twisted.words.protocols import irc
|
||||
from twisted.internet import protocol
|
||||
from django.conf import settings
|
||||
from src.comms.models import ExternalChannelConnection, ChannelDB
|
||||
from src.utils import logger, utils
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
INFOCHANNEL = ChannelDB.objects.channel_search(settings.CHANNEL_MUDINFO[0])
|
||||
IRC_CHANNELS = []
|
||||
|
||||
def msg_info(message):
|
||||
"""
|
||||
Send info to default info channel
|
||||
"""
|
||||
message = '[%s][IRC]: %s' % (INFOCHANNEL[0].key, message)
|
||||
try:
|
||||
INFOCHANNEL[0].msg(message)
|
||||
except AttributeError:
|
||||
logger.log_infomsg("MUDinfo (irc): %s" % message)
|
||||
|
||||
class IRC_Bot(irc.IRCClient):
|
||||
"""
|
||||
This defines an IRC bot that connects to an IRC channel
|
||||
and relays data to and from an evennia game.
|
||||
"""
|
||||
|
||||
def _get_nickname(self):
|
||||
"required for correct nickname setting"
|
||||
return self.factory.nickname
|
||||
nickname = property(_get_nickname)
|
||||
|
||||
def signedOn(self):
|
||||
# This is the first point the protocol is instantiated.
|
||||
# add this protocol instance to the global list so we
|
||||
# can access it later to send data.
|
||||
global IRC_CHANNELS
|
||||
self.join(self.factory.channel)
|
||||
|
||||
IRC_CHANNELS.append(self)
|
||||
#msg_info("Client connecting to %s.'" % (self.factory.channel))
|
||||
|
||||
def joined(self, channel):
|
||||
msg = _("joined %s.") % self.factory.pretty_key
|
||||
msg_info(msg)
|
||||
logger.log_infomsg(msg)
|
||||
|
||||
def get_mesg_info(self, user, irc_channel, msg):
|
||||
"""
|
||||
Get basic information about a message posted in IRC.
|
||||
"""
|
||||
#find irc->evennia channel mappings
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
|
||||
if not conns:
|
||||
return
|
||||
#format message:
|
||||
user = user.split("!")[0]
|
||||
if user:
|
||||
user.strip()
|
||||
else:
|
||||
user = _("Unknown")
|
||||
msg = msg.strip()
|
||||
sender_strings = ["%s@%s" % (user, irc_channel)]
|
||||
return conns, msg, sender_strings
|
||||
|
||||
def privmsg(self, user, irc_channel, msg):
|
||||
"Someone has written something in irc channel. Echo it to the evennia channel"
|
||||
conns, msg, sender_strings = self.get_mesg_info(user, irc_channel, msg)
|
||||
#logger.log_infomsg("<IRC: " + msg)
|
||||
for conn in conns:
|
||||
if conn.channel:
|
||||
conn.to_channel(msg, sender_strings=sender_strings)
|
||||
|
||||
def action(self, user, irc_channel, msg):
|
||||
"Someone has performed an action, e.g. using /me <pose>"
|
||||
#find irc->evennia channel mappings
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
|
||||
if not conns:
|
||||
return
|
||||
conns, msg, sender_strings = self.get_mesg_info(user, irc_channel, msg)
|
||||
# Transform this into a pose.
|
||||
msg = ':' + msg
|
||||
#logger.log_infomsg("<IRC: " + msg)
|
||||
for conn in conns:
|
||||
if conn.channel:
|
||||
conn.to_channel(msg, sender_strings=sender_strings)
|
||||
|
||||
def msg_irc(self, msg, senders=None):
|
||||
"""
|
||||
Called by evennia when sending something to mapped IRC channel.
|
||||
|
||||
Note that this cannot simply be called msg() since that's the
|
||||
name of of the twisted irc hook as well, this leads to some
|
||||
initialization messages to be sent without checks, causing loops.
|
||||
"""
|
||||
self.msg(utils.to_str(self.factory.channel), utils.to_str(msg))
|
||||
|
||||
class IRCbotFactory(protocol.ClientFactory):
|
||||
protocol = IRC_Bot
|
||||
def __init__(self, key, channel, network, port, nickname, evennia_channel):
|
||||
self.key = key
|
||||
self.pretty_key = "%s:%s%s ('%s')" % (network, port, channel, nickname)
|
||||
self.network = network
|
||||
self.port = port
|
||||
self.channel = channel
|
||||
self.nickname = nickname
|
||||
self.evennia_channel = evennia_channel
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
from twisted.internet.error import ConnectionDone
|
||||
if type(reason.type) == type(ConnectionDone):
|
||||
msg_info(_("Connection closed."))
|
||||
else:
|
||||
msg_info(_("Lost connection %(key)s. Reason: '%(reason)s'. Reconnecting.") % {"key":self.pretty_key, "reason":reason})
|
||||
connector.connect()
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
msg = _("Could not connect %(key)s Reason: '%(reason)s'") % {"key":self.pretty_key, "reason":reason}
|
||||
msg_info(msg)
|
||||
logger.log_errmsg(msg)
|
||||
|
||||
def build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick):
|
||||
"Build an id hash for the connection"
|
||||
if hasattr(channel, 'key'):
|
||||
channel = channel.key
|
||||
return "irc_%s:%s%s(%s)<>%s" % (irc_network, irc_port, irc_channel, irc_bot_nick, channel)
|
||||
|
||||
def build_service_key(key):
|
||||
return "IRCbot:%s" % key
|
||||
|
||||
def create_connection(channel, irc_network, irc_port, irc_channel, irc_bot_nick):
|
||||
"""
|
||||
This will create a new IRC<->channel connection.
|
||||
"""
|
||||
if not type(channel) == ChannelDB:
|
||||
new_channel = ChannelDB.objects.filter(db_key=channel)
|
||||
if not new_channel:
|
||||
logger.log_errmsg(_("Cannot attach IRC<->Evennia: Evennia Channel '%s' not found") % channel)
|
||||
return False
|
||||
channel = new_channel[0]
|
||||
key = build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick)
|
||||
|
||||
old_conns = ExternalChannelConnection.objects.filter(db_external_key=key)
|
||||
if old_conns:
|
||||
return False
|
||||
config = "%s|%s|%s|%s" % (irc_network, irc_port, irc_channel, irc_bot_nick)
|
||||
# how the channel will be able to contact this protocol
|
||||
send_code = "from src.comms.irc import IRC_CHANNELS\n"
|
||||
send_code += "matched_ircs = [irc for irc in IRC_CHANNELS if irc.factory.key == '%s']\n" % key
|
||||
send_code += "[irc.msg_irc(message, senders=[self]) for irc in matched_ircs]\n"
|
||||
conn = ExternalChannelConnection(db_channel=channel, db_external_key=key, db_external_send_code=send_code,
|
||||
db_external_config=config)
|
||||
conn.save()
|
||||
|
||||
# connect
|
||||
connect_to_irc(conn)
|
||||
return True
|
||||
|
||||
def delete_connection(channel, irc_network, irc_port, irc_channel, irc_bot_nick):
|
||||
"Destroy a connection"
|
||||
if hasattr(channel, 'key'):
|
||||
channel = channel.key
|
||||
|
||||
key = build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick)
|
||||
service_key = build_service_key(key)
|
||||
try:
|
||||
conn = ExternalChannelConnection.objects.get(db_external_key=key)
|
||||
except Exception:
|
||||
return False
|
||||
conn.delete()
|
||||
|
||||
try:
|
||||
service = SESSIONS.server.services.getServiceNamed(service_key)
|
||||
except Exception:
|
||||
return True
|
||||
if service.running:
|
||||
SESSIONS.server.services.removeService(service)
|
||||
return True
|
||||
|
||||
def connect_to_irc(connection):
|
||||
"Create the bot instance and connect to the IRC network and channel."
|
||||
# get config
|
||||
key = utils.to_str(connection.external_key)
|
||||
service_key = build_service_key(key)
|
||||
irc_network, irc_port, irc_channel, irc_bot_nick = [utils.to_str(conf) for conf in connection.external_config.split('|')]
|
||||
# connect
|
||||
bot = internet.TCPClient(irc_network, int(irc_port), IRCbotFactory(key, irc_channel, irc_network, irc_port, irc_bot_nick,
|
||||
connection.channel.key))
|
||||
bot.setName(service_key)
|
||||
SESSIONS.server.services.addService(bot)
|
||||
|
||||
def connect_all():
|
||||
"""
|
||||
Activate all irc bots.
|
||||
"""
|
||||
for connection in ExternalChannelConnection.objects.filter(db_external_key__startswith='irc_'):
|
||||
connect_to_irc(connection)
|
||||
|
||||
|
||||
"""
|
||||
This connects to an IRC network/channel and launches an 'bot' onto it.
|
||||
The bot then pipes what is being said between the IRC channel and one or
|
||||
more Evennia channels.
|
||||
"""
|
||||
from twisted.application import internet
|
||||
from twisted.words.protocols import irc
|
||||
from twisted.internet import protocol
|
||||
from django.conf import settings
|
||||
from src.comms.models import ExternalChannelConnection, ChannelDB
|
||||
from src.utils import logger, utils
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
INFOCHANNEL = ChannelDB.objects.channel_search(settings.CHANNEL_MUDINFO[0])
|
||||
IRC_CHANNELS = []
|
||||
|
||||
|
||||
def msg_info(message):
|
||||
"""
|
||||
Send info to default info channel
|
||||
"""
|
||||
message = '[%s][IRC]: %s' % (INFOCHANNEL[0].key, message)
|
||||
try:
|
||||
INFOCHANNEL[0].msg(message)
|
||||
except AttributeError:
|
||||
logger.log_infomsg("MUDinfo (irc): %s" % message)
|
||||
|
||||
|
||||
class IRC_Bot(irc.IRCClient):
|
||||
"""
|
||||
This defines an IRC bot that connects to an IRC channel
|
||||
and relays data to and from an evennia game.
|
||||
"""
|
||||
|
||||
def _get_nickname(self):
|
||||
"required for correct nickname setting"
|
||||
return self.factory.nickname
|
||||
nickname = property(_get_nickname)
|
||||
|
||||
def signedOn(self):
|
||||
# This is the first point the protocol is instantiated.
|
||||
# add this protocol instance to the global list so we
|
||||
# can access it later to send data.
|
||||
global IRC_CHANNELS
|
||||
self.join(self.factory.channel)
|
||||
|
||||
IRC_CHANNELS.append(self)
|
||||
#msg_info("Client connecting to %s.'" % (self.factory.channel))
|
||||
|
||||
def joined(self, channel):
|
||||
msg = _("joined %s.") % self.factory.pretty_key
|
||||
msg_info(msg)
|
||||
logger.log_infomsg(msg)
|
||||
|
||||
def get_mesg_info(self, user, irc_channel, msg):
|
||||
"""
|
||||
Get basic information about a message posted in IRC.
|
||||
"""
|
||||
#find irc->evennia channel mappings
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
|
||||
if not conns:
|
||||
return
|
||||
#format message:
|
||||
user = user.split("!")[0]
|
||||
if user:
|
||||
user.strip()
|
||||
else:
|
||||
user = _("Unknown")
|
||||
msg = msg.strip()
|
||||
sender_strings = ["%s@%s" % (user, irc_channel)]
|
||||
return conns, msg, sender_strings
|
||||
|
||||
def privmsg(self, user, irc_channel, msg):
|
||||
"Someone has written something in irc channel. Echo it to the evennia channel"
|
||||
conns, msg, sender_strings = self.get_mesg_info(user, irc_channel, msg)
|
||||
#logger.log_infomsg("<IRC: " + msg)
|
||||
for conn in conns:
|
||||
if conn.channel:
|
||||
conn.to_channel(msg, sender_strings=sender_strings)
|
||||
|
||||
def action(self, user, irc_channel, msg):
|
||||
"Someone has performed an action, e.g. using /me <pose>"
|
||||
#find irc->evennia channel mappings
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
|
||||
if not conns:
|
||||
return
|
||||
conns, msg, sender_strings = self.get_mesg_info(user, irc_channel, msg)
|
||||
# Transform this into a pose.
|
||||
msg = ':' + msg
|
||||
#logger.log_infomsg("<IRC: " + msg)
|
||||
for conn in conns:
|
||||
if conn.channel:
|
||||
conn.to_channel(msg, sender_strings=sender_strings)
|
||||
|
||||
def msg_irc(self, msg, senders=None):
|
||||
"""
|
||||
Called by evennia when sending something to mapped IRC channel.
|
||||
|
||||
Note that this cannot simply be called msg() since that's the
|
||||
name of of the twisted irc hook as well, this leads to some
|
||||
initialization messages to be sent without checks, causing loops.
|
||||
"""
|
||||
self.msg(utils.to_str(self.factory.channel), utils.to_str(msg))
|
||||
|
||||
|
||||
class IRCbotFactory(protocol.ClientFactory):
|
||||
protocol = IRC_Bot
|
||||
|
||||
def __init__(self, key, channel, network, port, nickname, evennia_channel):
|
||||
self.key = key
|
||||
self.pretty_key = "%s:%s%s ('%s')" % (network, port, channel, nickname)
|
||||
self.network = network
|
||||
self.port = port
|
||||
self.channel = channel
|
||||
self.nickname = nickname
|
||||
self.evennia_channel = evennia_channel
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
from twisted.internet.error import ConnectionDone
|
||||
if type(reason.type) == type(ConnectionDone):
|
||||
msg_info(_("Connection closed."))
|
||||
else:
|
||||
msg_info(_("Lost connection %(key)s. Reason: '%(reason)s'. Reconnecting.") % {"key":self.pretty_key, "reason":reason})
|
||||
connector.connect()
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
msg = _("Could not connect %(key)s Reason: '%(reason)s'") % {"key":self.pretty_key, "reason":reason}
|
||||
msg_info(msg)
|
||||
logger.log_errmsg(msg)
|
||||
|
||||
|
||||
def build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick):
|
||||
"Build an id hash for the connection"
|
||||
if hasattr(channel, 'key'):
|
||||
channel = channel.key
|
||||
return "irc_%s:%s%s(%s)<>%s" % (irc_network, irc_port,
|
||||
irc_channel, irc_bot_nick, channel)
|
||||
|
||||
|
||||
def build_service_key(key):
|
||||
return "IRCbot:%s" % key
|
||||
|
||||
|
||||
def create_connection(channel, irc_network, irc_port,
|
||||
irc_channel, irc_bot_nick):
|
||||
"""
|
||||
This will create a new IRC<->channel connection.
|
||||
"""
|
||||
if not type(channel) == ChannelDB:
|
||||
new_channel = ChannelDB.objects.filter(db_key=channel)
|
||||
if not new_channel:
|
||||
logger.log_errmsg(_("Cannot attach IRC<->Evennia: Evennia Channel '%s' not found") % channel)
|
||||
return False
|
||||
channel = new_channel[0]
|
||||
key = build_connection_key(channel, irc_network, irc_port,
|
||||
irc_channel, irc_bot_nick)
|
||||
|
||||
old_conns = ExternalChannelConnection.objects.filter(db_external_key=key)
|
||||
if old_conns:
|
||||
return False
|
||||
config = "%s|%s|%s|%s" % (irc_network, irc_port, irc_channel, irc_bot_nick)
|
||||
# how the channel will be able to contact this protocol
|
||||
send_code = "from src.comms.irc import IRC_CHANNELS\n"
|
||||
send_code += "matched_ircs = [irc for irc in IRC_CHANNELS if irc.factory.key == '%s']\n" % key
|
||||
send_code += "[irc.msg_irc(message, senders=[self]) for irc in matched_ircs]\n"
|
||||
conn = ExternalChannelConnection(db_channel=channel,
|
||||
db_external_key=key,
|
||||
db_external_send_code=send_code,
|
||||
db_external_config=config)
|
||||
conn.save()
|
||||
|
||||
# connect
|
||||
connect_to_irc(conn)
|
||||
return True
|
||||
|
||||
def delete_connection(channel, irc_network, irc_port, irc_channel, irc_bot_nick):
|
||||
"Destroy a connection"
|
||||
if hasattr(channel, 'key'):
|
||||
channel = channel.key
|
||||
|
||||
key = build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick)
|
||||
service_key = build_service_key(key)
|
||||
try:
|
||||
conn = ExternalChannelConnection.objects.get(db_external_key=key)
|
||||
except Exception:
|
||||
return False
|
||||
conn.delete()
|
||||
|
||||
try:
|
||||
service = SESSIONS.server.services.getServiceNamed(service_key)
|
||||
except Exception:
|
||||
return True
|
||||
if service.running:
|
||||
SESSIONS.server.services.removeService(service)
|
||||
return True
|
||||
|
||||
def connect_to_irc(connection):
|
||||
"Create the bot instance and connect to the IRC network and channel."
|
||||
# get config
|
||||
key = utils.to_str(connection.external_key)
|
||||
service_key = build_service_key(key)
|
||||
irc_network, irc_port, irc_channel, irc_bot_nick = [utils.to_str(conf) for conf in connection.external_config.split('|')]
|
||||
# connect
|
||||
bot = internet.TCPClient(irc_network, int(irc_port), IRCbotFactory(key, irc_channel, irc_network, irc_port, irc_bot_nick,
|
||||
connection.channel.key))
|
||||
bot.setName(service_key)
|
||||
SESSIONS.server.services.addService(bot)
|
||||
|
||||
def connect_all():
|
||||
"""
|
||||
Activate all irc bots.
|
||||
"""
|
||||
for connection in ExternalChannelConnection.objects.filter(db_external_key__startswith='irc_'):
|
||||
connect_to_irc(connection)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ _User = None
|
|||
|
||||
# error class
|
||||
|
||||
|
||||
class CommError(Exception):
|
||||
"Raise by comm system, to allow feedback to player when caught."
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# helper functions
|
||||
#
|
||||
|
|
@ -43,6 +45,7 @@ def dbref(dbref, reqhash=True):
|
|||
return None
|
||||
return dbref
|
||||
|
||||
|
||||
def identify_object(inp):
|
||||
"identify if an object is a player or an object; return its database model"
|
||||
# load global stores
|
||||
|
|
@ -61,18 +64,25 @@ def identify_object(inp):
|
|||
return inp, None
|
||||
# try to identify the type
|
||||
try:
|
||||
obj = _GA(inp, "dbobj") # this works for all typeclassed entities
|
||||
obj = _GA(inp, "dbobj") # this works for all typeclassed entities
|
||||
except AttributeError:
|
||||
obj = inp
|
||||
typ = type(obj)
|
||||
if typ == _PlayerDB: return obj, "player"
|
||||
elif typ == _ObjectDB: return obj, "object"
|
||||
elif typ == _ChannelDB: return obj, "channel"
|
||||
elif dbref(obj): return dbref(obj), "dbref"
|
||||
elif typ == basestring: return obj, "string"
|
||||
elif typ == _ExternalConnection: return obj, "external"
|
||||
if typ == _PlayerDB:
|
||||
return obj, "player"
|
||||
elif typ == _ObjectDB:
|
||||
return obj, "object"
|
||||
elif typ == _ChannelDB:
|
||||
return obj, "channel"
|
||||
elif dbref(obj):
|
||||
return dbref(obj), "dbref"
|
||||
elif typ == basestring:
|
||||
return obj, "string"
|
||||
elif typ == _ExternalConnection:
|
||||
return obj, "external"
|
||||
return obj, None # Something else
|
||||
|
||||
|
||||
def to_object(inp, objtype='player'):
|
||||
"""
|
||||
Locates the object related to the given
|
||||
|
|
@ -85,28 +95,39 @@ def to_object(inp, objtype='player'):
|
|||
if typ == objtype:
|
||||
return obj
|
||||
if objtype == 'player':
|
||||
if typ == 'object': return obj.player
|
||||
if typ == 'string': return _PlayerDB.objects.get(user_username__iexact=obj)
|
||||
if typ == 'dbref': return _PlayerDB.objects.get(id=obj)
|
||||
if typ == 'object':
|
||||
return obj.player
|
||||
if typ == 'string':
|
||||
return _PlayerDB.objects.get(user_username__iexact=obj)
|
||||
if typ == 'dbref':
|
||||
return _PlayerDB.objects.get(id=obj)
|
||||
print objtype, inp, obj, typ, type(inp)
|
||||
raise CommError()
|
||||
elif objtype == 'object':
|
||||
if typ == 'player': return obj.obj
|
||||
if typ == 'string': return _ObjectDB.objects.get(db_key__iexact=obj)
|
||||
if typ == 'dbref': return _ObjectDB.objects.get(id=obj)
|
||||
if typ == 'player':
|
||||
return obj.obj
|
||||
if typ == 'string':
|
||||
return _ObjectDB.objects.get(db_key__iexact=obj)
|
||||
if typ == 'dbref':
|
||||
return _ObjectDB.objects.get(id=obj)
|
||||
print objtype, inp, obj, typ, type(inp)
|
||||
raise CommError()
|
||||
elif objtype == 'channel':
|
||||
if typ == 'string': return _ChannelDB.objects.get(db_key__iexact=obj)
|
||||
if typ == 'dbref': return _ChannelDB.objects.get(id=obj)
|
||||
if typ == 'string':
|
||||
return _ChannelDB.objects.get(db_key__iexact=obj)
|
||||
if typ == 'dbref':
|
||||
return _ChannelDB.objects.get(id=obj)
|
||||
print objtype, inp, obj, typ, type(inp)
|
||||
raise CommError()
|
||||
elif objtype == 'external':
|
||||
if typ == 'string': return _ExternalConnection.objects.get(db_key=inp)
|
||||
if typ == 'dbref': return _ExternalConnection.objects.get(id=obj)
|
||||
if typ == 'string':
|
||||
return _ExternalConnection.objects.get(db_key=inp)
|
||||
if typ == 'dbref':
|
||||
return _ExternalConnection.objects.get(id=obj)
|
||||
print objtype, inp, obj, typ, type(inp)
|
||||
raise CommError()
|
||||
|
||||
|
||||
#
|
||||
# Msg manager
|
||||
#
|
||||
|
|
@ -146,17 +167,21 @@ class MsgManager(models.Manager):
|
|||
|
||||
def get_messages_by_sender(self, obj, exclude_channel_messages=False):
|
||||
"""
|
||||
Get all messages sent by one entity - this could be either a player or an object
|
||||
Get all messages sent by one entity - this could be either a
|
||||
player or an object
|
||||
|
||||
only_non_channel: only return messages -not- aimed at a channel (e.g. private tells)
|
||||
only_non_channel: only return messages -not- aimed at a channel
|
||||
(e.g. private tells)
|
||||
"""
|
||||
obj, typ = identify_object(obj)
|
||||
if exclude_channel_messages:
|
||||
# explicitly exclude channel recipients
|
||||
if typ == 'player':
|
||||
return list(self.filter(db_sender_players=obj, db_receivers_channels__isnull=True).exclude(db_hide_from_players=obj))
|
||||
return list(self.filter(db_sender_players=obj,
|
||||
db_receivers_channels__isnull=True).exclude(db_hide_from_players=obj))
|
||||
elif typ == 'object':
|
||||
return list(self.filter(db_sender_objects=obj, db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj))
|
||||
return list(self.filter(db_sender_objects=obj,
|
||||
db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj))
|
||||
else:
|
||||
raise CommError
|
||||
else:
|
||||
|
|
@ -208,9 +233,10 @@ class MsgManager(models.Manager):
|
|||
if msg:
|
||||
return msg[0]
|
||||
|
||||
# We use Q objects to gradually build up the query - this way we only need to do one
|
||||
# database lookup at the end rather than gradually refining with multiple filter:s.
|
||||
# Django Note: Q objects can be combined with & and | (=AND,OR). ~ negates the queryset
|
||||
# We use Q objects to gradually build up the query - this way we only
|
||||
# need to do one database lookup at the end rather than gradually
|
||||
# refining with multiple filter:s. Django Note: Q objects can be
|
||||
# combined with & and | (=AND,OR). ~ negates the queryset
|
||||
|
||||
# filter by sender
|
||||
sender, styp = identify_object(sender)
|
||||
|
|
@ -238,6 +264,7 @@ class MsgManager(models.Manager):
|
|||
# execute the query
|
||||
return list(self.filter(sender_restrict & receiver_restrict & fulltext_restrict))
|
||||
|
||||
|
||||
#
|
||||
# Channel manager
|
||||
#
|
||||
|
|
@ -350,9 +377,12 @@ class ChannelManager(models.Manager):
|
|||
channels = self.filter(db_key__iexact=ostring)
|
||||
if not channels:
|
||||
# still no match. Search by alias.
|
||||
channels = [channel for channel in self.all() if ostring.lower() in [a.lower for a in channel.aliases.all()]]
|
||||
channels = [channel for channel in self.all()
|
||||
if ostring.lower() in [a.lower
|
||||
for a in channel.aliases.all()]]
|
||||
return channels
|
||||
|
||||
|
||||
#
|
||||
# PlayerChannelConnection manager
|
||||
#
|
||||
|
|
@ -419,6 +449,7 @@ class PlayerChannelConnectionManager(models.Manager):
|
|||
for conn in conns:
|
||||
conn.delete()
|
||||
|
||||
|
||||
class ExternalChannelConnectionManager(models.Manager):
|
||||
"""
|
||||
This ExternalChannelConnectionManager implements methods for searching
|
||||
|
|
|
|||
|
|
@ -30,12 +30,14 @@ from src.locks.lockhandler import LockHandler
|
|||
from src.utils import logger
|
||||
from src.utils.utils import is_iter, to_str, crop, make_iter
|
||||
|
||||
__all__ = ("Msg", "TempMsg", "ChannelDB", "PlayerChannelConnection", "ExternalChannelConnection")
|
||||
__all__ = ("Msg", "TempMsg", "ChannelDB",
|
||||
"PlayerChannelConnection", "ExternalChannelConnection")
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
_DA = object.__delattr__
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Msg
|
||||
|
|
@ -66,9 +68,9 @@ class Msg(SharedMemoryModel):
|
|||
# These databse fields are all set using their corresponding properties,
|
||||
# named same as the field, but withtout the db_* prefix.
|
||||
|
||||
# Sender is either a player, an object or an external sender, like an IRC channel
|
||||
# normally there is only one, but if co-modification of a message is allowed, there
|
||||
# may be more than one "author"
|
||||
# Sender is either a player, an object or an external sender, like
|
||||
# an IRC channel; normally there is only one, but if co-modification of
|
||||
# a message is allowed, there may be more than one "author"
|
||||
db_sender_players = models.ManyToManyField("players.PlayerDB", related_name='sender_player_set', null=True, verbose_name='sender(player)', db_index=True)
|
||||
db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set', null=True, verbose_name='sender(object)', db_index=True)
|
||||
db_sender_external = models.CharField('external sender', max_length=255, null=True, db_index=True,
|
||||
|
|
@ -80,8 +82,8 @@ class Msg(SharedMemoryModel):
|
|||
db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set', null=True, help_text="object receivers")
|
||||
db_receivers_channels = models.ManyToManyField("ChannelDB", related_name='channel_set', null=True, help_text="channel recievers")
|
||||
|
||||
# header could be used for meta-info about the message if your system needs it, or as a separate
|
||||
# store for the mail subject line maybe.
|
||||
# header could be used for meta-info about the message if your system needs
|
||||
# it, or as a separate store for the mail subject line maybe.
|
||||
db_header = models.TextField('header', null=True, blank=True)
|
||||
# the message body itself
|
||||
db_message = models.TextField('messsage')
|
||||
|
|
@ -124,6 +126,7 @@ class Msg(SharedMemoryModel):
|
|||
list(self.db_sender_players.all()) +
|
||||
list(self.db_sender_objects.all()) +
|
||||
self.extra_senders]
|
||||
|
||||
#@sender.setter
|
||||
def __senders_set(self, value):
|
||||
"Setter. Allows for self.sender = value"
|
||||
|
|
@ -143,6 +146,7 @@ class Msg(SharedMemoryModel):
|
|||
else:
|
||||
raise ValueError(obj)
|
||||
self.save()
|
||||
|
||||
#@sender.deleter
|
||||
def __senders_del(self):
|
||||
"Deleter. Clears all senders"
|
||||
|
|
@ -173,12 +177,19 @@ class Msg(SharedMemoryModel):
|
|||
# receivers property
|
||||
#@property
|
||||
def __receivers_get(self):
|
||||
"Getter. Allows for value = self.receivers. Returns three lists of receivers: players, objects and channels."
|
||||
"""
|
||||
Getter. Allows for value = self.receivers.
|
||||
Returns three lists of receivers: players, objects and channels.
|
||||
"""
|
||||
return [hasattr(o, "typeclass") and o.typeclass or o for o in
|
||||
list(self.db_receivers_players.all()) + list(self.db_receivers_objects.all())]
|
||||
|
||||
#@receivers.setter
|
||||
def __receivers_set(self, value):
|
||||
"Setter. Allows for self.receivers = value. This appends a new receiver to the message."
|
||||
"""
|
||||
Setter. Allows for self.receivers = value.
|
||||
This appends a new receiver to the message.
|
||||
"""
|
||||
for val in (v for v in make_iter(value) if v):
|
||||
obj, typ = identify_object(val)
|
||||
if typ == 'player':
|
||||
|
|
@ -190,6 +201,7 @@ class Msg(SharedMemoryModel):
|
|||
else:
|
||||
raise ValueError
|
||||
self.save()
|
||||
|
||||
#@receivers.deleter
|
||||
def __receivers_del(self):
|
||||
"Deleter. Clears all receivers"
|
||||
|
|
@ -215,11 +227,15 @@ class Msg(SharedMemoryModel):
|
|||
def __channels_get(self):
|
||||
"Getter. Allows for value = self.channels. Returns a list of channels."
|
||||
return self.db_receivers_channels.all()
|
||||
|
||||
#@channels.setter
|
||||
def __channels_set(self, value):
|
||||
"Setter. Allows for self.channels = value. Requires a channel to be added."
|
||||
"""
|
||||
Setter. Allows for self.channels = value.
|
||||
Requires a channel to be added."""
|
||||
for val in (v.dbobj for v in make_iter(value) if v):
|
||||
self.db_receivers_channels.add(val)
|
||||
|
||||
#@channels.deleter
|
||||
def __channels_del(self):
|
||||
"Deleter. Allows for del self.channels"
|
||||
|
|
@ -228,8 +244,12 @@ class Msg(SharedMemoryModel):
|
|||
channels = property(__channels_get, __channels_set, __channels_del)
|
||||
|
||||
def __hide_from_get(self):
|
||||
"Getter. Allows for value = self.hide_from. Returns 3 lists of players, objects and channels"
|
||||
"""
|
||||
Getter. Allows for value = self.hide_from.
|
||||
Returns 3 lists of players, objects and channels
|
||||
"""
|
||||
return self.db_hide_from_players.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all()
|
||||
|
||||
#@hide_from_sender.setter
|
||||
def __hide_from_set(self, value):
|
||||
"Setter. Allows for self.hide_from = value. Will append to hiders"
|
||||
|
|
@ -243,6 +263,7 @@ class Msg(SharedMemoryModel):
|
|||
else:
|
||||
raise ValueError
|
||||
self.save()
|
||||
|
||||
#@hide_from_sender.deleter
|
||||
def __hide_from_del(self):
|
||||
"Deleter. Allows for del self.hide_from_senders"
|
||||
|
|
@ -275,7 +296,6 @@ class TempMsg(object):
|
|||
temporary messages that will not be stored.
|
||||
It mimics the "real" Msg object, but don't require
|
||||
sender to be given.
|
||||
|
||||
"""
|
||||
def __init__(self, senders=None, receivers=None, channels=None, message="", header="", type="", lockstring="", hide_from=None):
|
||||
self.senders = senders and make_iter(senders) or []
|
||||
|
|
@ -301,7 +321,7 @@ class TempMsg(object):
|
|||
try:
|
||||
self.senders.remove(o)
|
||||
except ValueError:
|
||||
pass # nothing to remove
|
||||
pass # nothing to remove
|
||||
|
||||
def remove_receiver(self, obj):
|
||||
"Remove a sender or a list of senders"
|
||||
|
|
@ -309,11 +329,13 @@ class TempMsg(object):
|
|||
try:
|
||||
self.senders.remove(o)
|
||||
except ValueError:
|
||||
pass # nothing to remove
|
||||
pass # nothing to remove
|
||||
|
||||
def access(self, accessing_obj, access_type='read', default=False):
|
||||
"checks lock access"
|
||||
return self.locks.check(accessing_obj, access_type=access_type, default=default)
|
||||
return self.locks.check(accessing_obj,
|
||||
access_type=access_type, default=default)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
|
|
@ -347,7 +369,6 @@ class ChannelDB(TypedObject):
|
|||
_SA(self, "aliases", AliasHandler(self, category_prefix="comm_"))
|
||||
_SA(self, "attributes", AttributeHandler(self))
|
||||
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Channel"
|
||||
|
|
@ -415,6 +436,7 @@ class ChannelDB(TypedObject):
|
|||
"""
|
||||
return self.locks.check(accessing_obj, access_type=access_type, default=default)
|
||||
|
||||
|
||||
class PlayerChannelConnection(SharedMemoryModel):
|
||||
"""
|
||||
This connects a player object to a particular comm channel.
|
||||
|
|
@ -435,11 +457,13 @@ class PlayerChannelConnection(SharedMemoryModel):
|
|||
def player_get(self):
|
||||
"Getter. Allows for value = self.player"
|
||||
return self.db_player
|
||||
|
||||
#@player.setter
|
||||
def player_set(self, value):
|
||||
"Setter. Allows for self.player = value"
|
||||
self.db_player = value
|
||||
self.save()
|
||||
|
||||
#@player.deleter
|
||||
def player_del(self):
|
||||
"Deleter. Allows for del self.player. Deletes connection."
|
||||
|
|
@ -451,11 +475,13 @@ class PlayerChannelConnection(SharedMemoryModel):
|
|||
def channel_get(self):
|
||||
"Getter. Allows for value = self.channel"
|
||||
return self.db_channel.typeclass
|
||||
|
||||
#@channel.setter
|
||||
def channel_set(self, value):
|
||||
"Setter. Allows for self.channel = value"
|
||||
self.db_channel = value.dbobj
|
||||
self.save()
|
||||
|
||||
#@channel.deleter
|
||||
def channel_del(self):
|
||||
"Deleter. Allows for del self.channel. Deletes connection."
|
||||
|
|
@ -507,10 +533,12 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
"Getter. Allows for value = self.channel"
|
||||
return self.db_channel
|
||||
#@channel.setter
|
||||
|
||||
def channel_set(self, value):
|
||||
"Setter. Allows for self.channel = value"
|
||||
self.db_channel = value
|
||||
self.save()
|
||||
|
||||
#@channel.deleter
|
||||
def channel_del(self):
|
||||
"Deleter. Allows for del self.channel. Deletes connection."
|
||||
|
|
@ -522,11 +550,13 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
def external_key_get(self):
|
||||
"Getter. Allows for value = self.external_key"
|
||||
return self.db_external_key
|
||||
|
||||
#@external_key.setter
|
||||
def external_key_set(self, value):
|
||||
"Setter. Allows for self.external_key = value"
|
||||
self.db_external_key = value
|
||||
self.save()
|
||||
|
||||
#@external_key.deleter
|
||||
def external_key_del(self):
|
||||
"Deleter. Allows for del self.external_key. Deletes connection."
|
||||
|
|
@ -538,11 +568,13 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
def external_send_code_get(self):
|
||||
"Getter. Allows for value = self.external_send_code"
|
||||
return self.db_external_send_code
|
||||
|
||||
#@external_send_code.setter
|
||||
def external_send_code_set(self, value):
|
||||
"Setter. Allows for self.external_send_code = value"
|
||||
self.db_external_send_code = value
|
||||
self.save()
|
||||
|
||||
#@external_send_code.deleter
|
||||
def external_send_code_del(self):
|
||||
"Deleter. Allows for del self.external_send_code. Deletes connection."
|
||||
|
|
@ -555,11 +587,13 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
def external_config_get(self):
|
||||
"Getter. Allows for value = self.external_config"
|
||||
return self.db_external_config
|
||||
|
||||
#@external_config.setter
|
||||
def external_config_set(self, value):
|
||||
"Setter. Allows for self.external_config = value"
|
||||
self.db_external_config = value
|
||||
self.save()
|
||||
|
||||
#@external_config.deleter
|
||||
def external_config_del(self):
|
||||
"Deleter. Allows for del self.external_config. Deletes connection."
|
||||
|
|
@ -572,11 +606,13 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
def is_enabled_get(self):
|
||||
"Getter. Allows for value = self.is_enabled"
|
||||
return self.db_is_enabled
|
||||
|
||||
#@is_enabled.setter
|
||||
def is_enabled_set(self, value):
|
||||
"Setter. Allows for self.is_enabled = value"
|
||||
self.db_is_enabled = value
|
||||
self.save()
|
||||
|
||||
#@is_enabled.deleter
|
||||
def is_enabled_del(self):
|
||||
"Deleter. Allows for del self.is_enabled. Deletes connection."
|
||||
|
|
@ -589,8 +625,8 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
|
||||
def to_channel(self, message, *args, **kwargs):
|
||||
"Send external -> channel"
|
||||
if 'from_obj' in kwargs and kwargs.pop('from_obj'):
|
||||
from_obj = self.external_key
|
||||
#if 'from_obj' in kwargs and kwargs.pop('from_obj'):
|
||||
# from_obj = self.external_key
|
||||
self.channel.msg(message, senders=[self], *args, **kwargs)
|
||||
|
||||
def to_external(self, message, senders=None, from_channel=None):
|
||||
|
|
@ -599,12 +635,13 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
# make sure we are not echoing back our own message to ourselves
|
||||
# (this would result in a nasty infinite loop)
|
||||
#print senders
|
||||
if self in make_iter(senders):#.external_key:
|
||||
if self in make_iter(senders): #.external_key:
|
||||
return
|
||||
|
||||
try:
|
||||
# we execute the code snippet that should make it possible for the
|
||||
# connection to contact the protocol correctly (as set by the protocol).
|
||||
# connection to contact the protocol correctly (as set by the
|
||||
# protocol).
|
||||
# Note that the code block has access to the variables here, such
|
||||
# as message, from_obj and from_channel.
|
||||
exec(to_str(self.external_send_code))
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ RETAG = re.compile(r'<[^>]*?>')
|
|||
# holds rss readers they can be shut down at will.
|
||||
RSS_READERS = {}
|
||||
|
||||
|
||||
def msg_info(message):
|
||||
"""
|
||||
Send info to default info channel
|
||||
|
|
@ -37,6 +38,7 @@ if RSS_ENABLED:
|
|||
except ImportError:
|
||||
raise ImportError("RSS requires python-feedparser to be installed. Install or set RSS_ENABLED=False.")
|
||||
|
||||
|
||||
class RSSReader(object):
|
||||
"""
|
||||
Reader script used to connect to each individual RSS feed
|
||||
|
|
@ -50,7 +52,7 @@ class RSSReader(object):
|
|||
self.key = key
|
||||
self.url = url
|
||||
self.interval = interval
|
||||
self.entries = {} # stored feeds
|
||||
self.entries = {} # stored feeds
|
||||
self.task = None
|
||||
# first we do is to load the feed so we don't resend
|
||||
# old entries whenever the reader starts.
|
||||
|
|
@ -63,7 +65,8 @@ class RSSReader(object):
|
|||
feed = feedparser.parse(self.url)
|
||||
new = []
|
||||
for entry in (e for e in feed['entries'] if e['id'] not in self.entries):
|
||||
txt = "[RSS] %s: %s" % (RETAG.sub("", entry['title']), entry['link'].replace('\n','').encode('utf-8'))
|
||||
txt = "[RSS] %s: %s" % (RETAG.sub("", entry['title']),
|
||||
entry['link'].replace('\n','').encode('utf-8'))
|
||||
self.entries[entry['id']] = txt
|
||||
new.append(txt)
|
||||
return new
|
||||
|
|
@ -92,12 +95,14 @@ class RSSReader(object):
|
|||
self.task.start(self.interval, now=False)
|
||||
RSS_READERS[self.key] = self
|
||||
|
||||
|
||||
def build_connection_key(channel, url):
|
||||
"This is used to id the connection"
|
||||
if hasattr(channel, 'key'):
|
||||
channel = channel.key
|
||||
return "rss_%s>%s" % (url, channel)
|
||||
|
||||
|
||||
def create_connection(channel, url, interval):
|
||||
"""
|
||||
This will create a new RSS->channel connection
|
||||
|
|
@ -113,13 +118,17 @@ def create_connection(channel, url, interval):
|
|||
if old_conns:
|
||||
return False
|
||||
config = "%s|%i" % (url, interval)
|
||||
# There is no sendback from evennia to the rss, so we need not define any sendback code.
|
||||
conn = ExternalChannelConnection(db_channel=channel, db_external_key=key, db_external_config=config)
|
||||
# There is no sendback from evennia to the rss, so we need not define
|
||||
# any sendback code.
|
||||
conn = ExternalChannelConnection(db_channel=channel,
|
||||
db_external_key=key,
|
||||
db_external_config=config)
|
||||
conn.save()
|
||||
|
||||
connect_to_rss(conn)
|
||||
return True
|
||||
|
||||
|
||||
def delete_connection(channel, url):
|
||||
"""
|
||||
Delete rss connection between channel and url
|
||||
|
|
@ -135,6 +144,7 @@ def delete_connection(channel, url):
|
|||
reader.task.stop()
|
||||
return True
|
||||
|
||||
|
||||
def connect_to_rss(connection):
|
||||
"""
|
||||
Create the parser instance and connect to RSS feed and channel
|
||||
|
|
@ -145,6 +155,7 @@ def connect_to_rss(connection):
|
|||
# Create reader (this starts the running task and stores a reference in RSS_TASKS)
|
||||
RSSReader(key, url, int(interval))
|
||||
|
||||
|
||||
def connect_all():
|
||||
"""
|
||||
Activate all rss feed parsers
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"""
|
||||
Makes it easier to import by grouping all relevant things already at this level.
|
||||
Makes it easier to import by grouping all relevant things already at this level.
|
||||
|
||||
You can henceforth import most things directly from src.help
|
||||
Also, the initiated object manager is available as src.help.manager.
|
||||
|
||||
"""
|
||||
|
||||
from src.help.models import *
|
||||
from src.help.models import *
|
||||
|
||||
manager = HelpEntry.objects
|
||||
|
|
|
|||
|
|
@ -1,36 +1,37 @@
|
|||
"""
|
||||
This defines how to edit help entries in Admin.
|
||||
"""
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from src.help.models import HelpEntry
|
||||
|
||||
|
||||
|
||||
class HelpEntryForm(forms.ModelForm):
|
||||
"Defines how to display the help entry"
|
||||
class Meta:
|
||||
model = HelpEntry
|
||||
db_help_category = forms.CharField(label="Help category", initial='General',
|
||||
help_text="organizes help entries in lists")
|
||||
db_lock_storage = forms.CharField(label="Locks", initial='view:all()',required=False,
|
||||
widget=forms.TextInput(attrs={'size':'40'}),)
|
||||
|
||||
class HelpEntryAdmin(admin.ModelAdmin):
|
||||
"Sets up the admin manaager for help entries"
|
||||
|
||||
list_display = ('id', 'db_key', 'db_help_category', 'db_lock_storage')
|
||||
list_display_links = ('id', 'db_key')
|
||||
search_fields = ['^db_key', 'db_entrytext']
|
||||
ordering = ['db_help_category', 'db_key']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
|
||||
form = HelpEntryForm
|
||||
fieldsets = (
|
||||
(None, {'fields':(('db_key', 'db_help_category'), 'db_entrytext', 'db_lock_storage'),
|
||||
'description':"Sets a Help entry. Set lock to <i>view:all()</I> unless you want to restrict it."}),)
|
||||
|
||||
|
||||
admin.site.register(HelpEntry, HelpEntryAdmin)
|
||||
"""
|
||||
This defines how to edit help entries in Admin.
|
||||
"""
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from src.help.models import HelpEntry
|
||||
|
||||
|
||||
|
||||
class HelpEntryForm(forms.ModelForm):
|
||||
"Defines how to display the help entry"
|
||||
class Meta:
|
||||
model = HelpEntry
|
||||
db_help_category = forms.CharField(label="Help category", initial='General',
|
||||
help_text="organizes help entries in lists")
|
||||
db_lock_storage = forms.CharField(label="Locks", initial='view:all()',required=False,
|
||||
widget=forms.TextInput(attrs={'size':'40'}),)
|
||||
|
||||
class HelpEntryAdmin(admin.ModelAdmin):
|
||||
"Sets up the admin manaager for help entries"
|
||||
|
||||
list_display = ('id', 'db_key', 'db_help_category', 'db_lock_storage')
|
||||
list_display_links = ('id', 'db_key')
|
||||
search_fields = ['^db_key', 'db_entrytext']
|
||||
ordering = ['db_help_category', 'db_key']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
|
||||
form = HelpEntryForm
|
||||
fieldsets = (
|
||||
(None, {'fields':(('db_key', 'db_help_category'),
|
||||
'db_entrytext', 'db_lock_storage'),
|
||||
'description':"Sets a Help entry. Set lock to <i>view:all()</I> unless you want to restrict it."}),)
|
||||
|
||||
|
||||
admin.site.register(HelpEntry, HelpEntryAdmin)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from django.db import models
|
|||
from src.utils import logger, utils
|
||||
__all__ = ("HelpEntryManager",)
|
||||
|
||||
|
||||
class HelpEntryManager(models.Manager):
|
||||
"""
|
||||
This HelpEntryManager implements methods for searching
|
||||
|
|
@ -48,7 +49,7 @@ class HelpEntryManager(models.Manager):
|
|||
Do a fuzzy match, preferably within the category of the
|
||||
current topic.
|
||||
"""
|
||||
return self.filter(db_key__icontains=topicstring).exclude(db_key__iexact=topicstring)
|
||||
return self.filter(db_key__icontains=topicstr).exclude(db_key__iexact=topicstr)
|
||||
|
||||
def find_topics_with_category(self, help_category):
|
||||
"""
|
||||
|
|
@ -92,6 +93,7 @@ class HelpEntryManager(models.Manager):
|
|||
"""
|
||||
ostring = ostring.strip().lower()
|
||||
if help_category:
|
||||
return self.filter(db_key__iexact=ostring, db_help_category__iexact=help_category)
|
||||
return self.filter(db_key__iexact=ostring,
|
||||
db_help_category__iexact=help_category)
|
||||
else:
|
||||
return self.filter(db_key__iexact=ostring)
|
||||
|
|
|
|||
|
|
@ -12,12 +12,11 @@ game world, policy info, rules and similar.
|
|||
from django.db import models
|
||||
from src.utils.idmapper.models import SharedMemoryModel
|
||||
from src.help.manager import HelpEntryManager
|
||||
from src.utils import ansi
|
||||
from src.typeclasses.models import Tag, TagHandler
|
||||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils.utils import is_iter
|
||||
__all__ = ("HelpEntry",)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# HelpEntry
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -86,6 +86,7 @@ from src.utils import utils
|
|||
|
||||
_PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
|
||||
|
||||
|
||||
def _to_player(accessing_obj):
|
||||
"Helper function. Makes sure an accessing object is a player object"
|
||||
if utils.inherits_from(accessing_obj, "src.objects.objects.Object"):
|
||||
|
|
@ -99,14 +100,21 @@ def _to_player(accessing_obj):
|
|||
def true(*args, **kwargs):
|
||||
"Always returns True."
|
||||
return True
|
||||
|
||||
|
||||
def all(*args, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
def false(*args, **kwargs):
|
||||
"Always returns False"
|
||||
return False
|
||||
|
||||
|
||||
def none(*args, **kwargs):
|
||||
return False
|
||||
|
||||
|
||||
def self(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Check if accessing_obj is the same as accessed_obj
|
||||
|
|
@ -172,7 +180,8 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
else:
|
||||
return hpos_target <= hpos_player
|
||||
elif not is_quell and perm in perms_player:
|
||||
# if we get here, check player perms first, otherwise continue as normal
|
||||
# if we get here, check player perms first, otherwise
|
||||
# continue as normal
|
||||
return True
|
||||
|
||||
if perm in perms_object:
|
||||
|
|
@ -185,6 +194,7 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
if hperm in perms_object and hpos_target < hpos)
|
||||
return False
|
||||
|
||||
|
||||
def perm_above(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Only allow objects with a permission *higher* in the permission
|
||||
|
|
@ -193,7 +203,8 @@ def perm_above(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
this function has no meaning and returns False.
|
||||
"""
|
||||
kwargs["_greater_than"] = True
|
||||
return perm(accessing_obj,accessed_obj, *args, **kwargs)
|
||||
return perm(accessing_obj, accessed_obj, *args, **kwargs)
|
||||
|
||||
|
||||
def pperm(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -209,6 +220,7 @@ def pperm(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
"""
|
||||
return perm(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
|
||||
|
||||
|
||||
def pperm_above(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Only allow Player objects with a permission *higher* in the permission
|
||||
|
|
@ -218,6 +230,7 @@ def pperm_above(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
"""
|
||||
return perm_above(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
|
||||
|
||||
|
||||
def dbref(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -238,16 +251,19 @@ def dbref(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
return dbref == accessing_obj.dbid
|
||||
return False
|
||||
|
||||
|
||||
def pdbref(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Same as dbref, but making sure accessing_obj is a player.
|
||||
"""
|
||||
return dbref(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
|
||||
|
||||
|
||||
def id(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"Alias to dbref"
|
||||
return dbref(accessing_obj, accessed_obj, *args, **kwargs)
|
||||
|
||||
|
||||
def pid(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"Alias to dbref, for Players"
|
||||
return dbref(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
|
||||
|
|
@ -262,6 +278,7 @@ CF_MAPPING = {'eq': lambda val1, val2: val1 == val2 or int(val1) == int(val2),
|
|||
'ne': lambda val1, val2: int(val1) != int(val2),
|
||||
'default': lambda val1, val2: False}
|
||||
|
||||
|
||||
def attr(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -297,22 +314,26 @@ def attr(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
try:
|
||||
return CF_MAPPING.get(typ, 'default')(val1, val2)
|
||||
except Exception:
|
||||
# this might happen if we try to compare two things that cannot be compared
|
||||
# this might happen if we try to compare two things
|
||||
# that cannot be compared
|
||||
return False
|
||||
|
||||
# first, look for normal properties on the object trying to gain access
|
||||
if hasattr(accessing_obj, attrname):
|
||||
if value:
|
||||
return valcompare(str(getattr(accessing_obj, attrname)), value, compare)
|
||||
return bool(getattr(accessing_obj, attrname)) # will return Fail on False value etc
|
||||
# will return Fail on False value etc
|
||||
return bool(getattr(accessing_obj, attrname))
|
||||
# check attributes, if they exist
|
||||
if (hasattr(accessing_obj, 'attributes') and accessing_obj.attributes.has(attrname)):
|
||||
if value:
|
||||
return (hasattr(accessing_obj, 'attributes')
|
||||
and valcompare(accessing_obj.attributes.get(attrname), value, compare))
|
||||
return bool(accessing_obj.attributes.get(attrname)) # fails on False/None values
|
||||
# fails on False/None values
|
||||
return bool(accessing_obj.attributes.get(attrname))
|
||||
return False
|
||||
|
||||
|
||||
def objattr(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -328,6 +349,7 @@ def objattr(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
if hasattr(accessing_obj, "obj"):
|
||||
return attr(accessing_obj.obj, accessed_obj, *args, **kwargs)
|
||||
|
||||
|
||||
def locattr(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -350,6 +372,7 @@ def attr_eq(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
"""
|
||||
return attr(accessing_obj, accessed_obj, *args, **kwargs)
|
||||
|
||||
|
||||
def attr_gt(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -357,7 +380,9 @@ def attr_gt(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
|
||||
Only true if access_obj's attribute > the value given.
|
||||
"""
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare':'gt'})
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare': 'gt'})
|
||||
|
||||
|
||||
def attr_ge(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -365,7 +390,9 @@ def attr_ge(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
|
||||
Only true if access_obj's attribute >= the value given.
|
||||
"""
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare':'ge'})
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare': 'ge'})
|
||||
|
||||
|
||||
def attr_lt(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -373,7 +400,9 @@ def attr_lt(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
|
||||
Only true if access_obj's attribute < the value given.
|
||||
"""
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare':'lt'})
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare': 'lt'})
|
||||
|
||||
|
||||
def attr_le(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -381,7 +410,9 @@ def attr_le(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
|
||||
Only true if access_obj's attribute <= the value given.
|
||||
"""
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare':'le'})
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare': 'le'})
|
||||
|
||||
|
||||
def attr_ne(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -389,18 +420,22 @@ def attr_ne(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
|
||||
Only true if access_obj's attribute != the value given.
|
||||
"""
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare':'ne'})
|
||||
return attr(accessing_obj, accessed_obj, *args, **{'compare': 'ne'})
|
||||
|
||||
|
||||
def holds(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
holds() # checks if accessed_obj or accessed_obj.obj is held by accessing_obj
|
||||
holds(key/dbref) # checks if accessing_obj holds an object with given key/dbref
|
||||
holds(attrname, value) # checks if accessing_obj holds an object with the given attrname and value
|
||||
holds() checks if accessed_obj or accessed_obj.obj
|
||||
is held by accessing_obj
|
||||
holds(key/dbref) checks if accessing_obj holds an object
|
||||
with given key/dbref
|
||||
holds(attrname, value) checks if accessing_obj holds an
|
||||
object with the given attrname and value
|
||||
|
||||
This is passed if accessed_obj is carried by accessing_obj (that is,
|
||||
accessed_obj.location == accessing_obj), or if accessing_obj itself holds an
|
||||
object matching the given key.
|
||||
accessed_obj.location == accessing_obj), or if accessing_obj itself holds
|
||||
an object matching the given key.
|
||||
"""
|
||||
try:
|
||||
# commands and scripts don't have contents, so we are usually looking
|
||||
|
|
@ -412,6 +447,7 @@ def holds(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
contents = accessing_obj.obj.contents
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def check_holds(objid):
|
||||
# helper function. Compares both dbrefs and keys/aliases.
|
||||
objid = str(objid)
|
||||
|
|
@ -449,9 +485,11 @@ def superuser(*args, **kwargs):
|
|||
"""
|
||||
return False
|
||||
|
||||
|
||||
def serversetting(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Only returns true if the Evennia settings exists, alternatively has a certain value.
|
||||
Only returns true if the Evennia settings exists, alternatively has
|
||||
a certain value.
|
||||
|
||||
Usage:
|
||||
serversetting(IRC_ENABLED)
|
||||
|
|
|
|||
|
|
@ -67,14 +67,14 @@ Here, the lock-function perm() will be called with the string
|
|||
'Builders' (accessing_obj and accessed_obj are added automatically,
|
||||
you only need to add the args/kwargs, if any).
|
||||
|
||||
If we wanted to make sure the accessing object was BOTH a Builders and a GoodGuy, we
|
||||
could use AND:
|
||||
If we wanted to make sure the accessing object was BOTH a Builders and a
|
||||
GoodGuy, we could use AND:
|
||||
|
||||
'edit:perm(Builders) AND perm(GoodGuy)'
|
||||
|
||||
To allow EITHER Builders and GoodGuys, we replace AND with OR. perm() is just one example,
|
||||
the lock function can do anything and compare any properties of the calling object to
|
||||
decide if the lock is passed or not.
|
||||
To allow EITHER Builders and GoodGuys, we replace AND with OR. perm() is just
|
||||
one example, the lock function can do anything and compare any properties of
|
||||
the calling object to decide if the lock is passed or not.
|
||||
|
||||
'lift:attrib(very_strong) AND NOT attrib(bad_back)'
|
||||
|
||||
|
|
@ -89,7 +89,8 @@ object would do something like this:
|
|||
if not target_obj.lockhandler.has_perm(caller, 'edit'):
|
||||
caller.msg("Sorry, you cannot edit that.")
|
||||
|
||||
All objects also has a shortcut called 'access' that is recommended to use instead:
|
||||
All objects also has a shortcut called 'access' that is recommended to
|
||||
use instead:
|
||||
|
||||
if not target_obj.access(caller, 'edit'):
|
||||
caller.msg("Sorry, you cannot edit that.")
|
||||
|
|
@ -104,13 +105,15 @@ to any other identifier you can use.
|
|||
|
||||
"""
|
||||
|
||||
import re, inspect
|
||||
import re
|
||||
import inspect
|
||||
from django.conf import settings
|
||||
from src.utils import logger, utils
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
__all__ = ("LockHandler", "LockException")
|
||||
|
||||
|
||||
#
|
||||
# Exception class
|
||||
#
|
||||
|
|
@ -119,6 +122,7 @@ class LockException(Exception):
|
|||
"raised during an error in a lock."
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# Cached lock functions
|
||||
#
|
||||
|
|
@ -186,15 +190,16 @@ class LockHandler(object):
|
|||
"""
|
||||
Helper function. This is normally only called when the
|
||||
lockstring is cached and does preliminary checking. locks are
|
||||
stored as a string 'atype:[NOT] lock()[[ AND|OR [NOT] lock()[...]];atype...
|
||||
stored as a string
|
||||
'atype:[NOT] lock()[[ AND|OR [NOT] lock()[...]];atype...
|
||||
|
||||
"""
|
||||
locks = {}
|
||||
if not storage_lockstring:
|
||||
return locks
|
||||
duplicates = 0
|
||||
elist = [] # errors
|
||||
wlist = [] # warnings
|
||||
elist = [] # errors
|
||||
wlist = [] # warnings
|
||||
for raw_lockstring in storage_lockstring.split(';'):
|
||||
lock_funcs = []
|
||||
try:
|
||||
|
|
@ -234,7 +239,8 @@ class LockHandler(object):
|
|||
{"access_type":access_type, "source":locks[access_type][2], "goal":raw_lockstring}))
|
||||
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
||||
if wlist and self.log_obj:
|
||||
# a warning text was set, it's not an error, so only report if log_obj is available.
|
||||
# a warning text was set, it's not an error, so only report
|
||||
# if log_obj is available.
|
||||
self._log_error("\n".join(wlist))
|
||||
if elist:
|
||||
# an error text was set, raise exception.
|
||||
|
|
@ -252,10 +258,12 @@ class LockHandler(object):
|
|||
|
||||
def cache_lock_bypass(self, obj):
|
||||
"""
|
||||
We cache superuser bypass checks here for efficiency. This needs to be re-run when a player is assigned to a character.
|
||||
We need to grant access to superusers. We need to check both directly on the object (players), through obj.player and using the
|
||||
get_player method (this sits on serversessions, in some rare cases where a check is done
|
||||
before the login process has yet been fully finalized)
|
||||
We cache superuser bypass checks here for efficiency. This needs to
|
||||
be re-run when a player is assigned to a character.
|
||||
We need to grant access to superusers. We need to check both directly
|
||||
on the object (players), through obj.player and using the get_player()
|
||||
method (this sits on serversessions, in some rare cases where a
|
||||
check is done before the login process has yet been fully finalized)
|
||||
"""
|
||||
self.lock_bypass = hasattr(obj, "is_superuser") and obj.is_superuser
|
||||
|
||||
|
|
@ -308,7 +316,7 @@ class LockHandler(object):
|
|||
def get(self, access_type=None):
|
||||
"get the full lockstring or the lockstring of a particular access type."
|
||||
if access_type:
|
||||
return self.locks.get(access_type, ["","",""])[2]
|
||||
return self.locks.get(access_type, ["", "", ""])[2]
|
||||
return str(self)
|
||||
|
||||
def delete(self, access_type):
|
||||
|
|
@ -342,7 +350,7 @@ class LockHandler(object):
|
|||
access_type - the type of access wanted
|
||||
default - if no suitable lock type is found, use this
|
||||
no_superuser_bypass - don't use this unless you really, really need to,
|
||||
it makes supersusers susceptible to the lock check.
|
||||
it makes supersusers susceptible to the lock check.
|
||||
|
||||
A lock is executed in the follwoing way:
|
||||
|
||||
|
|
@ -403,9 +411,11 @@ class LockHandler(object):
|
|||
locks = self._parse_lockstring(lockstring)
|
||||
for access_type in locks:
|
||||
evalstring, func_tup, raw_string = locks[access_type]
|
||||
true_false = tuple(tup[0](accessing_obj, self.obj, *tup[1], **tup[2]) for tup in func_tup)
|
||||
true_false = tuple(tup[0](accessing_obj, self.obj, *tup[1],**tup[2])
|
||||
for tup in func_tup)
|
||||
return eval(evalstring % true_false)
|
||||
|
||||
|
||||
def _test():
|
||||
# testing
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ except ImportError:
|
|||
from django.test import TestCase
|
||||
|
||||
from django.conf import settings
|
||||
from src.locks import lockhandler, lockfuncs
|
||||
from src.locks import lockfuncs
|
||||
from src.utils import create
|
||||
|
||||
#------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,140 +1,138 @@
|
|||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from src.typeclasses.models import Attribute, Tag
|
||||
from src.objects.models import ObjectDB
|
||||
|
||||
class AttributeInline(admin.TabularInline):
|
||||
# This class is currently not used, because PickleField objects are not editable.
|
||||
# It's here for us to ponder making a way that allows them to be edited.
|
||||
model = Attribute
|
||||
fields = ('db_key', 'db_value')
|
||||
extra = 0
|
||||
|
||||
class TagInline(admin.TabularInline):
|
||||
model = ObjectDB.db_tags.through
|
||||
raw_id_fields = ('tag',)
|
||||
extra = 0
|
||||
|
||||
class TagAdmin(admin.ModelAdmin):
|
||||
fields = ('db_key', 'db_category', 'db_data')
|
||||
|
||||
class ObjectCreateForm(forms.ModelForm):
|
||||
"This form details the look of the fields"
|
||||
class Meta:
|
||||
model = ObjectDB
|
||||
db_key = forms.CharField(label="Name/Key",
|
||||
widget=forms.TextInput(attrs={'size':'78'}),
|
||||
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",)
|
||||
db_typeclass_path = forms.CharField(label="Typeclass",
|
||||
initial=settings.BASE_OBJECT_TYPECLASS,
|
||||
widget=forms.TextInput(attrs={'size':'78'}),
|
||||
help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
|
||||
#db_permissions = forms.CharField(label="Permissions",
|
||||
# initial=settings.PERMISSION_PLAYER_DEFAULT,
|
||||
# required=False,
|
||||
# widget=forms.TextInput(attrs={'size':'78'}),
|
||||
# help_text="a comma-separated list of text strings checked by certain locks. They are mainly of use for Character objects. Character permissions overload permissions defined on a controlling Player. Most objects normally don't have any permissions defined.")
|
||||
db_cmdset_storage = forms.CharField(label="CmdSet",
|
||||
initial="",
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'size':'78'}),
|
||||
help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
|
||||
raw_id_fields = ('db_destination', 'db_location', 'db_home')
|
||||
|
||||
|
||||
|
||||
class ObjectEditForm(ObjectCreateForm):
|
||||
"Form used for editing. Extends the create one with more fields"
|
||||
|
||||
db_lock_storage = forms.CharField(label="Locks",
|
||||
required=False,
|
||||
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
|
||||
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
|
||||
|
||||
|
||||
class ObjectDBAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ('id', 'db_key', 'db_player', 'db_typeclass_path')
|
||||
list_display_links = ('id', 'db_key')
|
||||
ordering = ['db_player', 'db_typeclass_path', 'id']
|
||||
search_fields = ['^db_key', 'db_typeclass_path']
|
||||
raw_id_fields = ('db_destination', 'db_location', 'db_home')
|
||||
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
list_filter = ('db_typeclass_path',)
|
||||
#list_filter = ('db_permissions', 'db_typeclass_path')
|
||||
|
||||
# editing fields setup
|
||||
|
||||
form = ObjectEditForm
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': (('db_key','db_typeclass_path'), ('db_lock_storage', ),
|
||||
('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
|
||||
)}),
|
||||
)
|
||||
#fieldsets = (
|
||||
# (None, {
|
||||
# 'fields': (('db_key','db_typeclass_path'), ('db_permissions', 'db_lock_storage'),
|
||||
# ('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
|
||||
# )}),
|
||||
# )
|
||||
|
||||
#deactivated temporarily, they cause empty objects to be created in admin
|
||||
inlines = [TagInline]
|
||||
|
||||
|
||||
# Custom modification to give two different forms wether adding or not.
|
||||
|
||||
add_form = ObjectCreateForm
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'fields': (('db_key','db_typeclass_path'),
|
||||
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
|
||||
)}),
|
||||
)
|
||||
#add_fieldsets = (
|
||||
# (None, {
|
||||
# 'fields': (('db_key','db_typeclass_path'), 'db_permissions',
|
||||
# ('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
|
||||
# )}),
|
||||
# )
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
if not obj:
|
||||
return self.add_fieldsets
|
||||
return super(ObjectDBAdmin, self).get_fieldsets(request, obj)
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Use special form during creation
|
||||
"""
|
||||
defaults = {}
|
||||
if obj is None:
|
||||
defaults.update({
|
||||
'form': self.add_form,
|
||||
'fields': admin.util.flatten_fieldsets(self.add_fieldsets),
|
||||
})
|
||||
defaults.update(kwargs)
|
||||
return super(ObjectDBAdmin, self).get_form(request, obj, **defaults)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.save()
|
||||
if not change:
|
||||
# adding a new object
|
||||
obj = obj.typeclass
|
||||
obj.basetype_setup()
|
||||
obj.basetype_posthook_setup()
|
||||
obj.at_object_creation()
|
||||
obj.at_init()
|
||||
|
||||
|
||||
admin.site.register(ObjectDB, ObjectDBAdmin)
|
||||
admin.site.register(Tag, TagAdmin)
|
||||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from src.typeclasses.models import Attribute, Tag
|
||||
from src.objects.models import ObjectDB
|
||||
|
||||
|
||||
class AttributeInline(admin.TabularInline):
|
||||
# This class is currently not used, because PickleField objects are
|
||||
# not editable. It's here for us to ponder making a way that allows
|
||||
# them to be edited.
|
||||
model = Attribute
|
||||
fields = ('db_key', 'db_value')
|
||||
extra = 0
|
||||
|
||||
|
||||
class TagInline(admin.TabularInline):
|
||||
model = ObjectDB.db_tags.through
|
||||
raw_id_fields = ('tag',)
|
||||
extra = 0
|
||||
|
||||
|
||||
class TagAdmin(admin.ModelAdmin):
|
||||
fields = ('db_key', 'db_category', 'db_data')
|
||||
|
||||
|
||||
class ObjectCreateForm(forms.ModelForm):
|
||||
"This form details the look of the fields"
|
||||
class Meta:
|
||||
model = ObjectDB
|
||||
db_key = forms.CharField(label="Name/Key",
|
||||
widget=forms.TextInput(attrs={'size': '78'}),
|
||||
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",)
|
||||
db_typeclass_path = forms.CharField(label="Typeclass",
|
||||
initial=settings.BASE_OBJECT_TYPECLASS,
|
||||
widget=forms.TextInput(attrs={'size': '78'}),
|
||||
help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
|
||||
db_cmdset_storage = forms.CharField(label="CmdSet",
|
||||
initial="",
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'size': '78'}),
|
||||
help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
|
||||
raw_id_fields = ('db_destination', 'db_location', 'db_home')
|
||||
|
||||
|
||||
class ObjectEditForm(ObjectCreateForm):
|
||||
"Form used for editing. Extends the create one with more fields"
|
||||
|
||||
db_lock_storage = forms.CharField(label="Locks",
|
||||
required=False,
|
||||
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
|
||||
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
|
||||
|
||||
|
||||
class ObjectDBAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ('id', 'db_key', 'db_player', 'db_typeclass_path')
|
||||
list_display_links = ('id', 'db_key')
|
||||
ordering = ['db_player', 'db_typeclass_path', 'id']
|
||||
search_fields = ['^db_key', 'db_typeclass_path']
|
||||
raw_id_fields = ('db_destination', 'db_location', 'db_home')
|
||||
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
list_filter = ('db_typeclass_path',)
|
||||
#list_filter = ('db_permissions', 'db_typeclass_path')
|
||||
|
||||
# editing fields setup
|
||||
|
||||
form = ObjectEditForm
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': (('db_key','db_typeclass_path'), ('db_lock_storage', ),
|
||||
('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
|
||||
)}),
|
||||
)
|
||||
#fieldsets = (
|
||||
# (None, {
|
||||
# 'fields': (('db_key','db_typeclass_path'), ('db_permissions', 'db_lock_storage'),
|
||||
# ('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
|
||||
# )}),
|
||||
# )
|
||||
|
||||
#deactivated temporarily, they cause empty objects to be created in admin
|
||||
inlines = [TagInline]
|
||||
|
||||
# Custom modification to give two different forms wether adding or not.
|
||||
add_form = ObjectCreateForm
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'fields': (('db_key','db_typeclass_path'),
|
||||
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
|
||||
)}),
|
||||
)
|
||||
|
||||
#add_fieldsets = (
|
||||
# (None, {
|
||||
# 'fields': (('db_key','db_typeclass_path'), 'db_permissions',
|
||||
# ('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
|
||||
# )}),
|
||||
# )
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
if not obj:
|
||||
return self.add_fieldsets
|
||||
return super(ObjectDBAdmin, self).get_fieldsets(request, obj)
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Use special form during creation
|
||||
"""
|
||||
defaults = {}
|
||||
if obj is None:
|
||||
defaults.update({
|
||||
'form': self.add_form,
|
||||
'fields': admin.util.flatten_fieldsets(self.add_fieldsets),
|
||||
})
|
||||
defaults.update(kwargs)
|
||||
return super(ObjectDBAdmin, self).get_form(request, obj, **defaults)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.save()
|
||||
if not change:
|
||||
# adding a new object
|
||||
obj = obj.typeclass
|
||||
obj.basetype_setup()
|
||||
obj.basetype_posthook_setup()
|
||||
obj.at_object_creation()
|
||||
obj.at_init()
|
||||
|
||||
|
||||
admin.site.register(ObjectDB, ObjectDBAdmin)
|
||||
admin.site.register(Tag, TagAdmin)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ _ATTR = None
|
|||
|
||||
_AT_MULTIMATCH_INPUT = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1))
|
||||
|
||||
|
||||
class ObjectManager(TypedObjectManager):
|
||||
"""
|
||||
This ObjectManager implementes methods for searching
|
||||
|
|
@ -43,7 +44,8 @@ class ObjectManager(TypedObjectManager):
|
|||
get_objs_with_db_property_match
|
||||
get_objs_with_key_or_alias
|
||||
get_contents
|
||||
object_search (interface to many of the above methods, equivalent to ev.search_object)
|
||||
object_search (interface to many of the above methods,
|
||||
equivalent to ev.search_object)
|
||||
copy_object
|
||||
|
||||
"""
|
||||
|
|
@ -93,8 +95,8 @@ class ObjectManager(TypedObjectManager):
|
|||
@returns_typeclass_list
|
||||
def get_objs_with_attr(self, attribute_name, candidates=None):
|
||||
"""
|
||||
Returns all objects having the given attribute_name defined at all. Location
|
||||
should be a valid location object.
|
||||
Returns all objects having the given attribute_name defined at all.
|
||||
Location should be a valid location object.
|
||||
"""
|
||||
cand_restriction = candidates != None and Q(objattribute__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
|
||||
return list(self.filter(cand_restriction & Q(objattribute__db_key=attribute_name)))
|
||||
|
|
@ -178,10 +180,11 @@ class ObjectManager(TypedObjectManager):
|
|||
return self.filter(db_location=location).exclude(exclude_restriction)
|
||||
|
||||
@returns_typeclass_list
|
||||
def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None, typeclasses=None):
|
||||
def get_objs_with_key_or_alias(self, ostring, exact=True,
|
||||
candidates=None, typeclasses=None):
|
||||
"""
|
||||
Returns objects based on key or alias match. Will also do fuzzy matching based on
|
||||
the utils.string_partial_matching function.
|
||||
Returns objects based on key or alias match. Will also do fuzzy
|
||||
matching based on the utils.string_partial_matching function.
|
||||
candidates - list of candidate objects to restrict on
|
||||
typeclasses - list of typeclass path strings to restrict on
|
||||
"""
|
||||
|
|
@ -191,7 +194,8 @@ class ObjectManager(TypedObjectManager):
|
|||
else:
|
||||
return []
|
||||
if is_iter(candidates) and not len(candidates):
|
||||
# if candidates is an empty iterable there can be no matches. Exit early.
|
||||
# if candidates is an empty iterable there can be no matches
|
||||
# Exit early.
|
||||
return []
|
||||
|
||||
# build query objects
|
||||
|
|
@ -231,33 +235,42 @@ class ObjectManager(TypedObjectManager):
|
|||
candidates=None,
|
||||
exact=True):
|
||||
"""
|
||||
Search as an object globally or in a list of candidates and return results. The result is always an Object.
|
||||
Always returns a list.
|
||||
Search as an object globally or in a list of candidates and return
|
||||
results. The result is always an Object. Always returns a list.
|
||||
|
||||
Arguments:
|
||||
searchdata: (str or obj) The entity to match for. This is usually a key string but may also be an object itself.
|
||||
By default (if not attribute_name is set), this will search object.key and object.aliases in order. Can also
|
||||
be on the form #dbref, which will, if exact=True be matched against primary key.
|
||||
attribute_name: (str): Use this named ObjectAttribute to match searchdata against, instead
|
||||
of the defaults. If this is the name of a database field (with or without the db_ prefix), that
|
||||
will be matched too.
|
||||
typeclass (str or TypeClass): restrict matches to objects having this typeclass. This will help
|
||||
speed up global searches.
|
||||
candidates (list obj ObjectDBs): If supplied, search will only be performed among the candidates
|
||||
in this list. A common list of candidates is the contents of the current location searched.
|
||||
exact (bool): Match names/aliases exactly or partially. Partial matching matches the
|
||||
beginning of words in the names/aliases, using a matching routine to separate
|
||||
multiple matches in names with multiple components (so "bi sw" will match
|
||||
"Big sword"). Since this is more expensive than exact matching, it is
|
||||
recommended to be used together with the objlist keyword to limit the number
|
||||
of possibilities. This value has no meaning if searching for attributes/properties.
|
||||
searchdata: (str or obj) The entity to match for. This is usually a
|
||||
key string but may also be an object itself. By default (if
|
||||
not attribute_name is set), this will search object.key and
|
||||
object.aliases in order. Can also be on the form #dbref,
|
||||
which will, if exact=True be matched against primary key.
|
||||
attribute_name: (str): Use this named ObjectAttribute to match
|
||||
searchdata against, instead of the defaults. If this is
|
||||
the name of a database field (with or without the db_ prefix),
|
||||
that will be matched too.
|
||||
typeclass (str or TypeClass): restrict matches to objects having this
|
||||
typeclass. This will help speed up global searches.
|
||||
candidates (list obj ObjectDBs): If supplied, search will only be
|
||||
performed among the candidates in this list. A common list
|
||||
of candidates is the contents of the current location
|
||||
searched.
|
||||
exact (bool): Match names/aliases exactly or partially. Partial
|
||||
matching matches the beginning of words in the names/aliases,
|
||||
using a matching routine to separate multiple matches in
|
||||
names with multiple components (so "bi sw" will match
|
||||
"Big sword"). Since this is more expensive than exact
|
||||
matching, it is recommended to be used together with the
|
||||
objlist keyword to limit the number of possibilities. This
|
||||
value has no meaning if searching for attributes/properties.
|
||||
|
||||
Returns:
|
||||
A list of matching objects (or a list with one unique match)
|
||||
|
||||
"""
|
||||
def _searcher(searchdata, candidates, typeclass, exact=False):
|
||||
"Helper method for searching objects. typeclass is only used for global searching (no candidates)"
|
||||
"""
|
||||
Helper method for searching objects. typeclass is only used
|
||||
for global searching (no candidates)
|
||||
"""
|
||||
if attribute_name:
|
||||
# attribute/property search (always exact).
|
||||
matches = self.get_objs_with_db_property_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
|
||||
|
|
@ -285,10 +298,11 @@ class ObjectManager(TypedObjectManager):
|
|||
# Convenience check to make sure candidates are really dbobjs
|
||||
candidates = [cand.dbobj for cand in make_iter(candidates) if cand]
|
||||
if typeclass:
|
||||
candidates = [cand for cand in candidates if _GA(cand, "db_typeclass_path") in typeclass]
|
||||
candidates = [cand for cand in candidates
|
||||
if _GA(cand, "db_typeclass_path") in typeclass]
|
||||
|
||||
dbref = not attribute_name and exact and self.dbref(searchdata)
|
||||
if dbref != None:
|
||||
if dbref is not None:
|
||||
# Easiest case - dbref matching (always exact)
|
||||
dbref_match = self.dbref_search(dbref)
|
||||
if dbref_match:
|
||||
|
|
@ -299,17 +313,19 @@ class ObjectManager(TypedObjectManager):
|
|||
|
||||
# Search through all possibilities.
|
||||
match_number = None
|
||||
# always run first check exact - we don't want partial matches if on the form of 1-keyword etc.
|
||||
# always run first check exact - we don't want partial matches
|
||||
# if on the form of 1-keyword etc.
|
||||
matches = _searcher(searchdata, candidates, typeclass, exact=True)
|
||||
if not matches:
|
||||
# no matches found - check if we are dealing with N-keyword query - if so, strip it.
|
||||
# no matches found - check if we are dealing with N-keyword
|
||||
# query - if so, strip it.
|
||||
match_number, searchdata = _AT_MULTIMATCH_INPUT(searchdata)
|
||||
# run search again, with the exactness set by call
|
||||
if match_number != None or not exact:
|
||||
if match_number is not None or not exact:
|
||||
matches = _searcher(searchdata, candidates, typeclass, exact=exact)
|
||||
|
||||
# deal with result
|
||||
if len(matches) > 1 and match_number != None:
|
||||
if len(matches) > 1 and match_number is not None:
|
||||
# multiple matches, but a number was given to separate them
|
||||
try:
|
||||
matches = [matches[match_number]]
|
||||
|
|
@ -324,11 +340,12 @@ class ObjectManager(TypedObjectManager):
|
|||
|
||||
def copy_object(self, original_object, new_key=None,
|
||||
new_location=None, new_home=None,
|
||||
new_permissions=None, new_locks=None, new_aliases=None, new_destination=None):
|
||||
new_permissions=None, new_locks=None,
|
||||
new_aliases=None, new_destination=None):
|
||||
"""
|
||||
Create and return a new object as a copy of the original object. All will
|
||||
be identical to the original except for the arguments given specifically
|
||||
to this method.
|
||||
Create and return a new object as a copy of the original object. All
|
||||
will be identical to the original except for the arguments given
|
||||
specifically to this method.
|
||||
|
||||
original_object (obj) - the object to make a copy from
|
||||
new_key (str) - name the copy differently from the original.
|
||||
|
|
@ -358,9 +375,14 @@ class ObjectManager(TypedObjectManager):
|
|||
# create new object
|
||||
from src.utils import create
|
||||
from src.scripts.models import ScriptDB
|
||||
new_object = create.create_object(typeclass_path, key=new_key, location=new_location,
|
||||
home=new_home, permissions=new_permissions,
|
||||
locks=new_locks, aliases=new_aliases, destination=new_destination)
|
||||
new_object = create.create_object(typeclass_path,
|
||||
key=new_key,
|
||||
location=new_location,
|
||||
home=new_home,
|
||||
permissions=new_permissions,
|
||||
locks=new_locks,
|
||||
aliases=new_aliases,
|
||||
destination=new_destination)
|
||||
if not new_object:
|
||||
return None
|
||||
|
||||
|
|
@ -381,7 +403,6 @@ class ObjectManager(TypedObjectManager):
|
|||
|
||||
return new_object
|
||||
|
||||
|
||||
def clear_all_sessids(self):
|
||||
"""
|
||||
Clear the db_sessid field of all objects having also the db_player field
|
||||
|
|
|
|||
|
|
@ -18,17 +18,15 @@ import traceback
|
|||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
from src.typeclasses.models import TypedObject, TagHandler, NickHandler, AliasHandler, AttributeHandler
|
||||
from src.server.caches import get_prop_cache, set_prop_cache
|
||||
|
||||
from src.typeclasses.typeclass import TypeClass
|
||||
from src.typeclasses.models import (TypedObject, TagHandler, NickHandler,
|
||||
AliasHandler, AttributeHandler)
|
||||
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, inherits_from
|
||||
from src.utils.utils import make_iter, to_str, to_unicode, variable_from_module
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
|
@ -46,6 +44,7 @@ _ME = _("me")
|
|||
_SELF = _("self")
|
||||
_HERE = _("here")
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# ObjectDB
|
||||
|
|
@ -98,9 +97,10 @@ class ObjectDB(TypedObject):
|
|||
# db_key (also 'name' works), db_typeclass_path, db_date_created,
|
||||
# db_permissions
|
||||
#
|
||||
# These databse fields (including the inherited ones) should normally be set
|
||||
# using their corresponding wrapper properties, named same as the field, but without
|
||||
# the db_* prefix (e.g. the db_key field is set with self.key instead). The wrappers
|
||||
# These databse fields (including the inherited ones) should normally be
|
||||
# managed by their corresponding wrapper properties, named same as the
|
||||
# field, but without the db_* prefix (e.g. the db_key field is set with
|
||||
# self.key instead). The wrappers are created at the metaclass level and
|
||||
# will automatically save and cache the data more efficiently.
|
||||
|
||||
# If this is a character object, the player is connected here.
|
||||
|
|
@ -112,7 +112,7 @@ class ObjectDB(TypedObject):
|
|||
# The location in the game world. Since this one is likely
|
||||
# to change often, we set this with the 'location' property
|
||||
# to transparently handle Typeclassing.
|
||||
db_location = models.ForeignKey('self', related_name="locations_set",db_index=True,
|
||||
db_location = models.ForeignKey('self', related_name="locations_set", db_index=True,
|
||||
blank=True, null=True, verbose_name='game location')
|
||||
# a safety location, this usually don't change much.
|
||||
db_home = models.ForeignKey('self', related_name="homes_set",
|
||||
|
|
@ -158,13 +158,19 @@ class ObjectDB(TypedObject):
|
|||
# seems very sensitive to caching, so leaving it be for now. /Griatch
|
||||
#@property
|
||||
def __cmdset_storage_get(self):
|
||||
"Getter. Allows for value = self.name. Returns a list of cmdset_storage."
|
||||
"""
|
||||
Getter. Allows for value = self.name.
|
||||
Returns a list of cmdset_storage.
|
||||
"""
|
||||
storage = _GA(self, "db_cmdset_storage")
|
||||
# we need to check so storage is not None
|
||||
return [path.strip() for path in storage.split(',')] if storage else []
|
||||
return [path.strip() for path in storage.split(',')] if storage else []
|
||||
#@cmdset_storage.setter
|
||||
def __cmdset_storage_set(self, value):
|
||||
"Setter. Allows for self.name = value. Stores as a comma-separated string."
|
||||
"""
|
||||
Setter. Allows for self.name = value.
|
||||
Stores as a comma-separated string.
|
||||
"""
|
||||
_SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value)))
|
||||
_GA(self, "save")()
|
||||
#@cmdset_storage.deleter
|
||||
|
|
@ -236,14 +242,13 @@ class ObjectDB(TypedObject):
|
|||
if exi.destination]
|
||||
exits = property(__exits_get)
|
||||
|
||||
|
||||
#
|
||||
# Main Search method
|
||||
#
|
||||
|
||||
def search(self, searchdata,
|
||||
global_search=False,
|
||||
use_nicks=True, # should this default to off?
|
||||
use_nicks=True, # should this default to off?
|
||||
typeclass=None,
|
||||
location=None,
|
||||
attribute_name=None,
|
||||
|
|
@ -259,33 +264,46 @@ class ObjectDB(TypedObject):
|
|||
|
||||
Inputs:
|
||||
|
||||
searchdata (str or obj): Primary search criterion. Will be matched against object.key (with object.aliases second)
|
||||
unless the keyword attribute_name specifies otherwise. Special strings:
|
||||
#<num> - search by unique dbref. This is always a global search.
|
||||
searchdata (str or obj): Primary search criterion. Will be matched
|
||||
against object.key (with object.aliases second) unless
|
||||
the keyword attribute_name specifies otherwise.
|
||||
Special strings:
|
||||
#<num> - search by unique dbref. This is always
|
||||
a global search.
|
||||
me,self - self-reference to this object
|
||||
<num>-<string> - can be used to differentiate between multiple same-named matches
|
||||
global_search (bool): Search all objects globally. This is overruled by "location" keyword.
|
||||
use_nicks (bool): Use nickname-replace (nicktype "object") on the search string
|
||||
typeclass (str or Typeclass, or list of either): Limit search only to Objects with this typeclass. May
|
||||
be a list of typeclasses for a broader search.
|
||||
location (Object): Specify a location to search, if different from the self's given location
|
||||
plus its contents. This can also be a list of locations.
|
||||
attribute_name (str): Define which property to search. If set, no key+alias search will be performed. This can be used to
|
||||
search database fields (db_ will be automatically appended), and if that fails, it will try to
|
||||
return objects having Attributes with this name and value equal to searchdata. A special
|
||||
use is to search for "key" here if you want to do a key-search without including aliases.
|
||||
quiet (bool) - don't display default error messages - return multiple matches as a list and
|
||||
no matches as None. If not set (default), will echo error messages and return None.
|
||||
exact (bool) - if unset (default) - prefers to match to beginning of string rather than not matching
|
||||
at all. If set, requires exact mathing of entire string.
|
||||
<num>-<string> - can be used to differentiate
|
||||
between multiple same-named matches
|
||||
global_search (bool): Search all objects globally. This is overruled
|
||||
by "location" keyword.
|
||||
use_nicks (bool): Use nickname-replace (nicktype "object") on the
|
||||
search string
|
||||
typeclass (str or Typeclass, or list of either): Limit search only
|
||||
to Objects with this typeclass. May be a list of typeclasses
|
||||
for a broader search.
|
||||
location (Object): Specify a location to search, if different from the
|
||||
self's given location plus its contents. This can also
|
||||
be a list of locations.
|
||||
attribute_name (str): Define which property to search. If set, no
|
||||
key+alias search will be performed. This can be used to
|
||||
search database fields (db_ will be automatically
|
||||
appended), and if that fails, it will try to return
|
||||
objects having Attributes with this name and value
|
||||
equal to searchdata. A special use is to search for
|
||||
"key" here if you want to do a key-search without
|
||||
including aliases.
|
||||
quiet (bool) - don't display default error messages - return multiple
|
||||
matches as a list and no matches as None. If not
|
||||
set (default), will echo error messages and return None.
|
||||
exact (bool) - if unset (default) - prefers to match to beginning of
|
||||
string rather than not matching at all. If set, requires
|
||||
exact mathing of entire string.
|
||||
|
||||
Returns:
|
||||
|
||||
quiet=False (default):
|
||||
no match or multimatch:
|
||||
auto-echoes errors to self.msg, then returns None
|
||||
(results are handled by modules set by settings.SEARCH_AT_RESULT
|
||||
and settings.SEARCH_AT_MULTIMATCH_INPUT)
|
||||
auto-echoes errors to self.msg, then returns None
|
||||
(results are handled by settings.SEARCH_AT_RESULT
|
||||
and settings.SEARCH_AT_MULTIMATCH_INPUT)
|
||||
match:
|
||||
a unique object match
|
||||
quiet=True:
|
||||
|
|
@ -315,8 +333,10 @@ class ObjectDB(TypedObject):
|
|||
break
|
||||
|
||||
candidates=None
|
||||
if global_search or (is_string and searchdata.startswith("#") and len(searchdata) > 1 and searchdata[1:].isdigit()):
|
||||
# only allow exact matching if searching the entire database or unique #dbrefs
|
||||
if(global_search or (is_string and searchdata.startswith("#") and
|
||||
len(searchdata) > 1 and searchdata[1:].isdigit())):
|
||||
# only allow exact matching if searching the entire database
|
||||
# or unique #dbrefs
|
||||
exact = True
|
||||
elif location:
|
||||
# location(s) were given
|
||||
|
|
@ -324,13 +344,15 @@ class ObjectDB(TypedObject):
|
|||
for obj in make_iter(location):
|
||||
candidates.extend([o.dbobj for o in obj.contents])
|
||||
else:
|
||||
# local search. Candidates are self.contents, self.location and self.location.contents
|
||||
# local search. Candidates are self.contents, self.location
|
||||
# and self.location.contents
|
||||
location = self.location
|
||||
candidates = self.contents
|
||||
if location:
|
||||
candidates = candidates + [location] + location.contents
|
||||
else:
|
||||
candidates.append(self) # normally we are included in location.contents
|
||||
# normally we are included in location.contents
|
||||
candidates.append(self)
|
||||
# db manager expects database objects
|
||||
candidates = [obj.dbobj for obj in candidates]
|
||||
|
||||
|
|
@ -360,23 +382,24 @@ class ObjectDB(TypedObject):
|
|||
|
||||
def execute_cmd(self, raw_string, sessid=None):
|
||||
"""
|
||||
Do something as this object. This method is a copy of the execute_cmd method on the
|
||||
session. This is never called normally, it's only used when wanting specifically to
|
||||
let an object be the caller of a command. It makes use of nicks of eventual connected
|
||||
players as well.
|
||||
Do something as this object. This method is a copy of the execute_
|
||||
cmd method on the session. This is never called normally, it's only
|
||||
used when wanting specifically to let an object be the caller of a
|
||||
command. It makes use of nicks of eventual connected players as well.
|
||||
|
||||
Argument:
|
||||
raw_string (string) - raw command input
|
||||
sessid (int) - optional session id to return results to
|
||||
|
||||
Returns Deferred - this is an asynchronous Twisted object that will
|
||||
not fire until the command has actually finished executing. To overload
|
||||
this one needs to attach callback functions to it, with addCallback(function).
|
||||
This function will be called with an eventual return value from the command
|
||||
execution.
|
||||
not fire until the command has actually finished executing. To
|
||||
overload this one needs to attach callback functions to it, with
|
||||
addCallback(function). This function will be called with an
|
||||
eventual return value from the command execution.
|
||||
|
||||
This return is not used at all by Evennia by default, but might be useful
|
||||
for coders intending to implement some sort of nested command structure.
|
||||
This return is not used at all by Evennia by default, but might
|
||||
be useful for coders intending to implement some sort of nested
|
||||
command structure.
|
||||
"""
|
||||
# nick replacement - we require full-word matching.
|
||||
|
||||
|
|
@ -384,7 +407,8 @@ class ObjectDB(TypedObject):
|
|||
raw_string = to_unicode(raw_string)
|
||||
|
||||
raw_list = raw_string.split(None)
|
||||
raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]]
|
||||
raw_list = [" ".join(raw_list[:i + 1]) for i in range(len(raw_list))
|
||||
if raw_list[:i + 1]]
|
||||
# fetch the nick data efficiently
|
||||
nicks = self.db_attributes.filter(db_category__in=("nick_inputline", "nick_channel"))
|
||||
if self.has_player:
|
||||
|
|
@ -416,7 +440,6 @@ class ObjectDB(TypedObject):
|
|||
|
||||
if "data" in kwargs:
|
||||
# deprecation warning
|
||||
from src.utils import logger
|
||||
logger.log_depmsg("ObjectDB.msg(): 'data'-dict keyword is deprecated. Use **kwargs instead.")
|
||||
data = kwargs.pop("data")
|
||||
if isinstance(data, dict):
|
||||
|
|
@ -437,7 +460,8 @@ class ObjectDB(TypedObject):
|
|||
"""
|
||||
Emits something to all objects inside an object.
|
||||
|
||||
exclude is a list of objects not to send to. See self.msg() for more info.
|
||||
exclude is a list of objects not to send to. See self.msg() for
|
||||
more info.
|
||||
"""
|
||||
contents = _GA(self, "contents")
|
||||
if exclude:
|
||||
|
|
@ -454,22 +478,27 @@ class ObjectDB(TypedObject):
|
|||
Moves this object to a new location. Note that if <destination> is an
|
||||
exit object (i.e. it has "destination"!=None), the move_to will
|
||||
happen to this destination and -not- into the exit object itself, unless
|
||||
use_destination=False. Note that no lock checks are done by this function,
|
||||
such things are assumed to have been handled before calling move_to.
|
||||
use_destination=False. Note that no lock checks are done by this
|
||||
function, such things are assumed to have been handled before calling
|
||||
move_to.
|
||||
|
||||
destination: (Object) Reference to the object to move to. This
|
||||
can also be an exit object, in which case the destination
|
||||
property is used as destination.
|
||||
quiet: (bool) If true, don't emit left/arrived messages.
|
||||
emit_to_obj: (Object) object to receive error messages
|
||||
use_destination (bool): Default is for objects to use the "destination" property
|
||||
of destinations as the target to move to. Turning off this
|
||||
keyword allows objects to move "inside" exit objects.
|
||||
to_none - allow destination to be None. Note that no hooks are run when moving
|
||||
to a None location. If you want to run hooks, run them manually.
|
||||
use_destination (bool): Default is for objects to use the "destination"
|
||||
property of destinations as the target to move to.
|
||||
Turning off this keyword allows objects to move
|
||||
"inside" exit objects.
|
||||
to_none - allow destination to be None. Note that no hooks are run when
|
||||
moving to a None location. If you want to run hooks,
|
||||
run them manually (and make sure they can manage None
|
||||
locations).
|
||||
|
||||
Returns True/False depending on if there were problems with the move. This method
|
||||
may also return various error messages to the emit_to_obj.
|
||||
Returns True/False depending on if there were problems with the move.
|
||||
This method may also return various error messages to the
|
||||
emit_to_obj.
|
||||
"""
|
||||
def logerr(string=""):
|
||||
trc = traceback.format_exc()
|
||||
|
|
@ -633,11 +662,13 @@ class ObjectDB(TypedObject):
|
|||
|
||||
def copy(self, new_key=None):
|
||||
"""
|
||||
Makes an identical copy of this object. If you want to customize the copy by
|
||||
changing some settings, use ObjectDB.object.copy_object() directly.
|
||||
Makes an identical copy of this object. If you want to customize the
|
||||
copy by changing some settings, use ObjectDB.object.copy_object()
|
||||
directly.
|
||||
|
||||
new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named
|
||||
<old_key>_copy by default.
|
||||
new_key (string) - new key/name of copied object. If new_key is not
|
||||
specified, the copy will be named <old_key>_copy
|
||||
by default.
|
||||
Returns: Object (copy of this one)
|
||||
"""
|
||||
def find_clone_key():
|
||||
|
|
@ -650,7 +681,8 @@ class ObjectDB(TypedObject):
|
|||
key = _GA(self, "key")
|
||||
num = 1
|
||||
for obj in (obj for obj in self.location.contents
|
||||
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
|
||||
if obj.key.startswith(key) and
|
||||
obj.key.lstrip(key).isdigit()):
|
||||
num += 1
|
||||
return "%s%03i" % (key, num)
|
||||
new_key = new_key or find_clone_key()
|
||||
|
|
@ -705,7 +737,7 @@ class ObjectDB(TypedObject):
|
|||
_GA(self, "clear_exits")()
|
||||
# Clear out any non-exit objects located within the object
|
||||
_GA(self, "clear_contents")()
|
||||
old_loc = _GA(self, "location")
|
||||
#old_loc = _GA(self, "location")
|
||||
# Perform the deletion of the object
|
||||
super(ObjectDB, self).delete()
|
||||
# clear object's old location's content cache of this object
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ _GA = object.__getattribute__
|
|||
_SA = object.__setattr__
|
||||
_DA = object.__delattr__
|
||||
|
||||
|
||||
#
|
||||
# Base class to inherit from.
|
||||
#
|
||||
|
|
@ -55,41 +56,53 @@ class Object(TypeClass):
|
|||
|
||||
key (string) - name of object
|
||||
name (string) - same as key
|
||||
aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings.
|
||||
aliases (list of strings) - aliases to the object. Will be saved to
|
||||
database as AliasDB entries but returned as strings.
|
||||
dbref (int, read-only) - unique #id-number. Also "id" can be used.
|
||||
dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class
|
||||
typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch.
|
||||
dbobj (Object, read-only) - link to database model. dbobj.typeclass
|
||||
points back to this class
|
||||
typeclass (Object, read-only) - this links back to this class as an
|
||||
identified only. Use self.swap_typeclass() to switch.
|
||||
date_created (string) - time stamp of object creation
|
||||
permissions (list of strings) - list of permission strings
|
||||
|
||||
player (Player) - controlling player (if any, only set together with sessid below)
|
||||
sessid (int, read-only) - session id (if any, only set together with player above)
|
||||
player (Player) - controlling player (if any, only set together with
|
||||
sessid below)
|
||||
sessid (int, read-only) - session id (if any, only set together with
|
||||
player above)
|
||||
location (Object) - current location. Is None if this is a room
|
||||
home (Object) - safety start-location
|
||||
sessions (list of Sessions, read-only) - returns all sessions connected to this object
|
||||
sessions (list of Sessions, read-only) - returns all sessions
|
||||
connected to this object
|
||||
has_player (bool, read-only)- will only return *connected* players
|
||||
contents (list of Objects, read-only) - returns all objects inside this object (including exits)
|
||||
exits (list of Objects, read-only) - returns all exits from this object, if any
|
||||
contents (list of Objects, read-only) - returns all objects inside
|
||||
this object (including exits)
|
||||
exits (list of Objects, read-only) - returns all exits from this
|
||||
object, if any
|
||||
destination (Object) - only set if this object is an exit.
|
||||
is_superuser (bool, read-only) - True/False if this user is a superuser
|
||||
|
||||
* Handlers available
|
||||
|
||||
locks - lock-handler: use locks.add() to add new lock strings
|
||||
db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr
|
||||
ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data
|
||||
db - attribute-handler: store/retrieve database attributes on this
|
||||
self.db.myattr=val, val=self.db.myattr
|
||||
ndb - non-persistent attribute handler: same as db but does not
|
||||
create a database entry when storing data
|
||||
scripts - script-handler. Add new scripts to object with scripts.add()
|
||||
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
|
||||
nicks - nick-handler. New nicks with nicks.add().
|
||||
|
||||
* Helper methods (see src.objects.objects.py for full headers)
|
||||
|
||||
search(ostring, global_search=False, global_dbref=False, attribute_name=None,
|
||||
use_nicks=True, location=None, ignore_errors=False, player=False)
|
||||
search(ostring, global_search=False, global_dbref=False,
|
||||
attribute_name=None, use_nicks=True, location=None,
|
||||
ignore_errors=False, player=False)
|
||||
execute_cmd(raw_string)
|
||||
msg(message, **kwargs)
|
||||
msg_contents(message, exclude=None, from_obj=None, **kwargs)
|
||||
move_to(destination, quiet=False, emit_to_obj=None, use_destination=True, to_none=False)
|
||||
move_to(destination, quiet=False, emit_to_obj=None,
|
||||
use_destination=True, to_none=False)
|
||||
copy(new_key=None)
|
||||
delete()
|
||||
is_typeclass(typeclass, exact=False)
|
||||
|
|
@ -99,42 +112,73 @@ class Object(TypeClass):
|
|||
|
||||
* Hook methods
|
||||
|
||||
basetype_setup() - only called once, used for behind-the-scenes setup. Normally not modified.
|
||||
basetype_posthook_setup() - customization in basetype, after the object has been created; Normally not modified.
|
||||
basetype_setup() - only called once, used for behind-the-scenes
|
||||
setup. Normally not modified.
|
||||
basetype_posthook_setup() - customization in basetype, after the
|
||||
object has been created; Normally not modified.
|
||||
|
||||
at_object_creation() - only called once, when object is first created. Object customizations go here.
|
||||
at_object_delete() - called just before deleting an object. If returning False, deletion is aborted. Note that all objects
|
||||
inside a deleted object are automatically moved to their <home>, they don't need to be removed here.
|
||||
at_object_creation() - only called once, when object is first created.
|
||||
Object customizations go here.
|
||||
at_object_delete() - called just before deleting an object. If
|
||||
returning False, deletion is aborted. Note that
|
||||
all objects inside a deleted object are
|
||||
automatically moved to their <home>, they don't
|
||||
need to be removed here.
|
||||
|
||||
at_init() - called whenever typeclass is cached from memory, at least once every server restart/reload
|
||||
at_cmdset_get() - this is called just before the command handler requests a cmdset from this objecth
|
||||
at_pre_puppet(player)- (player-controlled objects only) called just before puppeting
|
||||
at_post_puppet() - (player-controlled objects only) called just after completing connection player<->object
|
||||
at_pre_unpuppet() - (player-controlled objects only) called just before un-puppeting
|
||||
at_post_unpuppet(player) - (player-controlled objects only) called just after disconnecting player<->object link
|
||||
at_init() called whenever typeclass is cached from
|
||||
memory, at least once every server restart/reload
|
||||
at_cmdset_get() - this is called just before the command
|
||||
handler requests a cmdset from this objecth
|
||||
at_pre_puppet(player)- (player-controlled objects only) called just
|
||||
before puppeting
|
||||
at_post_puppet() - (player-controlled objects only) called just
|
||||
after completing connection player<->object
|
||||
at_pre_unpuppet() - (player-controlled objects only) called just
|
||||
before un-puppeting
|
||||
at_post_unpuppet(player) (player-controlled objects only) called
|
||||
just after disconnecting player<->object link
|
||||
at_server_reload() - called before server is reloaded
|
||||
at_server_shutdown() - called just before server is fully shut down
|
||||
|
||||
at_before_move(destination) - called just before moving object to the destination. If returns False, move is cancelled.
|
||||
announce_move_from(destination) - called in old location, just before move, if obj.move_to() has quiet=False
|
||||
announce_move_to(source_location) - called in new location, just after move, if obj.move_to() has quiet=False
|
||||
at_after_move(source_location) - always called after a move has been successfully performed.
|
||||
at_object_leave(obj, target_location) - called when an object leaves this object in any fashion
|
||||
at_object_receive(obj, source_location) - called when this object receives another object
|
||||
at_before_move(destination) called just before moving
|
||||
object to the destination. If returns
|
||||
False, move is cancelled.
|
||||
announce_move_from(destination) - called in old location, just before
|
||||
move, if obj.move_to() has
|
||||
quiet=False
|
||||
announce_move_to(source_location) - called in new location,
|
||||
just after move, if obj.move_to()
|
||||
has quiet=False
|
||||
at_after_move(source_location) - always called after a move
|
||||
has been successfully performed.
|
||||
at_object_leave(obj, target_location) - called when an object leaves
|
||||
this object in any fashion
|
||||
at_object_receive(obj, source_location) - called when this object
|
||||
receives another object
|
||||
|
||||
at_before_traverse(traversing_object) - (exit-objects only) called just before an object traverses this object
|
||||
at_after_traverse(traversing_object, source_location) - (exit-objects only) called just after a traversal has happened.
|
||||
at_failed_traverse(traversing_object) - (exit-objects only) called if traversal fails and property err_traverse is not defined.
|
||||
at_before_traverse(traversing_object) - (exit-objects only) called
|
||||
just before an object
|
||||
traverses this object
|
||||
at_after_traverse(traversing_object, source_location) - (exit-objects
|
||||
only) called just after a traversal has happened.
|
||||
at_failed_traverse(traversing_object) - (exit-objects only) called
|
||||
if traversal fails and property err_traverse is not defined.
|
||||
|
||||
at_msg_receive(self, msg, from_obj=None, data=None) - called when a message (via self.msg()) is sent to this obj.
|
||||
If returns false, aborts send.
|
||||
at_msg_send(self, msg, to_obj=None, data=None) - called when this objects sends a message to someone via self.msg().
|
||||
at_msg_receive(self, msg, from_obj=None, data=None) - called when a
|
||||
message (via self.msg()) is sent to this obj.
|
||||
If returns false, aborts send.
|
||||
at_msg_send(self, msg, to_obj=None, data=None) - called when this
|
||||
objects sends a message to someone via self.msg().
|
||||
|
||||
return_appearance(looker) - describes this object. Used by "look" command by default
|
||||
at_desc(looker=None) - called by 'look' whenever the appearance is requested.
|
||||
at_get(getter) - called after object has been picked up. Does not stop pickup.
|
||||
return_appearance(looker) - describes this object. Used by "look"
|
||||
command by default
|
||||
at_desc(looker=None) - called by 'look' whenever the appearance
|
||||
is requested.
|
||||
at_get(getter) - called after object has been picked up.
|
||||
Does not stop pickup.
|
||||
at_drop(dropper) - called when this object has been dropped.
|
||||
at_say(speaker, message) - by default, called if an object inside this object speaks
|
||||
at_say(speaker, message) - by default, called if an object inside
|
||||
this object speaks
|
||||
|
||||
"""
|
||||
super(Object, self).__init__(dbobj)
|
||||
|
|
@ -159,30 +203,41 @@ class Object(TypeClass):
|
|||
|
||||
Inputs:
|
||||
|
||||
ostring (str): Primary search criterion. Will be matched against object.key (with object.aliases second)
|
||||
unless the keyword attribute_name specifies otherwise. Special strings:
|
||||
#<num> - search by unique dbref. This is always a global search.
|
||||
ostring (str): Primary search criterion. Will be matched against
|
||||
object.key (with object.aliases second)
|
||||
unless the keyword attribute_name specifies otherwise.
|
||||
Special strings:
|
||||
#<num> - search by unique dbref. This is always a
|
||||
global search.
|
||||
me,self - self-reference to this object
|
||||
<num>-<string> - can be used to differentiate between multiple same-named matches
|
||||
global_search (bool): Search all objects globally. This is overruled by "location" keyword.
|
||||
use_nicks (bool): Use nickname-replace (nicktype "object") on the search string
|
||||
typeclass (str or Typeclass): Limit search only to Objects with this typeclass. May be a list of typeclasses
|
||||
for a broader search.
|
||||
location (Object): Specify a location to search, if different from the self's given location
|
||||
<num>-<string> - can be used to differentiate between
|
||||
multiple same-named matches
|
||||
global_search (bool): Search all objects globally. This is overruled
|
||||
by "location" keyword.
|
||||
use_nicks (bool): Use nickname-replace (nicktype "object") on the
|
||||
search string
|
||||
typeclass (str or Typeclass): Limit search only to Objects with this
|
||||
typeclass. May be a list of typeclasses for a
|
||||
broader search.
|
||||
location (Object): Specify a location to search, if different from the
|
||||
self's given location
|
||||
plus its contents. This can also be a list of locations.
|
||||
attribute_name (str): Use this named Attribute to match ostring against, instead of object.key.
|
||||
quiet (bool) - don't display default error messages - return multiple matches as a list and
|
||||
no matches as None. If not set (default), will echo error messages and return None.
|
||||
exact (bool) - if unset (default) - prefers to match to beginning of string rather than not matching
|
||||
at all. If set, requires exact mathing of entire string.
|
||||
attribute_name (str): Use this named Attribute to match ostring against,
|
||||
instead of object.key.
|
||||
quiet (bool) - don't display default error messages - return multiple
|
||||
matches as a list and no matches as None. If not
|
||||
set (default), will echo error messages and return None.
|
||||
exact (bool) - if unset (default) - prefers to match to beginning of
|
||||
string rather than not matching at all. If set,
|
||||
requires exact mathing of entire string.
|
||||
|
||||
Returns:
|
||||
|
||||
quiet=False (default):
|
||||
no match or multimatch:
|
||||
auto-echoes errors to self.msg, then returns None
|
||||
(results are handled by modules set by settings.SEARCH_AT_RESULT
|
||||
and settings.SEARCH_AT_MULTIMATCH_INPUT)
|
||||
(results are handled by settings.SEARCH_AT_RESULT
|
||||
and settings.SEARCH_AT_MULTIMATCH_INPUT)
|
||||
match:
|
||||
a unique object match
|
||||
quiet=True:
|
||||
|
|
@ -209,16 +264,18 @@ class Object(TypeClass):
|
|||
|
||||
Argument:
|
||||
raw_string (string) - raw command input
|
||||
sessid (int) - id of session executing the command. This sets the sessid property on the command.
|
||||
sessid (int) - id of session executing the command. This sets the
|
||||
sessid property on the command.
|
||||
|
||||
Returns Deferred - this is an asynchronous Twisted object that will
|
||||
not fire until the command has actually finished executing. To overload
|
||||
this one needs to attach callback functions to it, with addCallback(function).
|
||||
This function will be called with an eventual return value from the command
|
||||
execution.
|
||||
not fire until the command has actually finished executing. To
|
||||
overload this one needs to attach callback functions to it, with
|
||||
addCallback(function). This function will be called with an
|
||||
eventual return value from the command execution.
|
||||
|
||||
This return is not used at all by Evennia by default, but might be useful
|
||||
for coders intending to implement some sort of nested command structure.
|
||||
This return is not used at all by Evennia by default, but might be
|
||||
useful for coders intending to implement some sort of nested
|
||||
command structure.
|
||||
"""
|
||||
return self.dbobj.execute_cmd(raw_string, sessid=sessid)
|
||||
|
||||
|
|
@ -233,48 +290,59 @@ class Object(TypeClass):
|
|||
sessid: optional session target. If sessid=0, the session will
|
||||
default to self.sessid or from_obj.sessid.
|
||||
"""
|
||||
self.dbobj.msg(text=text, **kwargs)#message, from_obj=from_obj, data=data, sessid=0)
|
||||
|
||||
self.dbobj.msg(text=text, **kwargs)
|
||||
|
||||
def msg_contents(self, text=None, exclude=None, from_obj=None, **kwargs):
|
||||
"""
|
||||
Emits something to all objects inside an object.
|
||||
|
||||
exclude is a list of objects not to send to. See self.msg() for more info.
|
||||
exclude is a list of objects not to send to. See self.msg() for
|
||||
more info.
|
||||
"""
|
||||
self.dbobj.msg_contents(text, exclude=exclude, from_obj=from_obj, **kwargs)
|
||||
self.dbobj.msg_contents(text, exclude=exclude,
|
||||
from_obj=from_obj, **kwargs)
|
||||
|
||||
def move_to(self, destination, quiet=False,
|
||||
emit_to_obj=None, use_destination=True, to_none=False):
|
||||
"""
|
||||
Moves this object to a new location. Note that if <destination> is an
|
||||
exit object (i.e. it has "destination"!=None), the move_to will
|
||||
happen to this destination and -not- into the exit object itself, unless
|
||||
use_destination=False. Note that no lock checks are done by this function,
|
||||
such things are assumed to have been handled before calling move_to.
|
||||
happen to this destination and -not- into the exit object itself,
|
||||
unless use_destination=False. Note that no lock checks are done by
|
||||
this function, such things are assumed to have been handled before
|
||||
calling move_to.
|
||||
|
||||
destination: (Object) Reference to the object to move to. This
|
||||
can also be an exit object, in which case the destination
|
||||
property is used as destination.
|
||||
quiet: (bool) If true, don't emit left/arrived messages.
|
||||
emit_to_obj: (Object) object to receive error messages
|
||||
use_destination (bool): Default is for objects to use the "destination" property
|
||||
of destinations as the target to move to. Turning off this
|
||||
keyword allows objects to move "inside" exit objects.
|
||||
to_none - allow destination to be None. Note that no hooks are run when moving
|
||||
to a None location. If you want to run hooks, run them manually.
|
||||
Returns True/False depending on if there were problems with the move. This method
|
||||
may also return various error messages to the emit_to_obj.
|
||||
use_destination (bool): Default is for objects to use the "destination"
|
||||
property of destinations as the target to move to.
|
||||
Turning off this keyword allows objects to move
|
||||
"inside" exit objects.
|
||||
to_none - allow destination to be None. Note that no hooks are run
|
||||
when moving to a None location. If you want to run hooks, run
|
||||
them manually (and make sure the hooks can handle a None
|
||||
location).
|
||||
Returns True/False depending on if there were problems with the move.
|
||||
This method may also return various error messages to the
|
||||
emit_to_obj.
|
||||
|
||||
"""
|
||||
return self.dbobj.move_to(destination, quiet=quiet,
|
||||
emit_to_obj=emit_to_obj, use_destination=use_destination)
|
||||
emit_to_obj=emit_to_obj,
|
||||
use_destination=use_destination)
|
||||
|
||||
def copy(self, new_key=None):
|
||||
"""
|
||||
Makes an identical copy of this object. If you want to customize the copy by
|
||||
changing some settings, use ObjectDB.object.copy_object() directly.
|
||||
Makes an identical copy of this object. If you want to customize the
|
||||
copy by changing some settings, use ObjectDB.object.copy_object()
|
||||
directly.
|
||||
|
||||
new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named
|
||||
new_key (string) - new key/name of copied object. If new_key is not
|
||||
specified, the copy will be named
|
||||
<old_key>_copy by default.
|
||||
Returns: Object (copy of this one)
|
||||
"""
|
||||
|
|
@ -293,7 +361,6 @@ class Object(TypeClass):
|
|||
"""
|
||||
return self.dbobj.delete()
|
||||
|
||||
|
||||
# methods inherited from the typeclass system
|
||||
|
||||
def is_typeclass(self, typeclass, exact=False):
|
||||
|
|
@ -347,18 +414,20 @@ class Object(TypeClass):
|
|||
|
||||
|
||||
"""
|
||||
return self.dbobj.swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default)
|
||||
return self.dbobj.swap_typeclass(new_typeclass,
|
||||
clean_attributes=clean_attributes, no_default=no_default)
|
||||
|
||||
def access(self, accessing_obj, access_type='read', default=False):
|
||||
"""
|
||||
Determines if another object has permission to access this object in whatever way.
|
||||
Determines if another object has permission to access this object in
|
||||
whatever way.
|
||||
|
||||
accessing_obj (Object)- object trying to access this one
|
||||
access_type (string) - type of access sought
|
||||
default (bool) - what to return if no lock of access_type was found
|
||||
|
||||
This function will call at_access_success or at_access_failure depending on the
|
||||
outcome of the access check.
|
||||
This function will call at_access_success or at_access_failure
|
||||
depending on the outcome of the access check.
|
||||
|
||||
"""
|
||||
if self.dbobj.access(accessing_obj, access_type=access_type, default=default):
|
||||
|
|
@ -373,12 +442,12 @@ class Object(TypeClass):
|
|||
This explicitly checks the given string against this object's
|
||||
'permissions' property without involving any locks.
|
||||
|
||||
permstring (string) - permission string that need to match a permission on the object.
|
||||
permstring (string) - permission string that need to match a
|
||||
permission on the object.
|
||||
(example: 'Builders')
|
||||
"""
|
||||
return self.dbobj.check_permstring(permstring)
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Checks for equality against an id string or another object or user.
|
||||
|
|
@ -387,24 +456,23 @@ class Object(TypeClass):
|
|||
parent doesn't work.
|
||||
"""
|
||||
try:
|
||||
return _GA(_GA(self, "dbobj"),"dbid") == _GA(_GA(other,"dbobj"),"dbid")
|
||||
return _GA(_GA(self, "dbobj"), "dbid") == _GA(_GA(other, "dbobj"), "dbid")
|
||||
except AttributeError:
|
||||
# compare players instead
|
||||
try:
|
||||
return _GA(_GA(_GA(self, "dbobj"),"player"),"uid") == _GA(_GA(other, "player"),"uid")
|
||||
return _GA(_GA(_GA(self, "dbobj"), "player"), "uid") == _GA(_GA(other, "player"), "uid")
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
## hooks called by the game engine
|
||||
|
||||
|
||||
def basetype_setup(self):
|
||||
"""
|
||||
This sets up the default properties of an Object,
|
||||
just before the more general at_object_creation.
|
||||
|
||||
You normally don't need to change this unless you change some fundamental
|
||||
things like names of permission groups.
|
||||
You normally don't need to change this unless you change some
|
||||
fundamental things like names of permission groups.
|
||||
"""
|
||||
# the default security setup fallback for a generic
|
||||
# object. Overload in child for a custom setup. Also creation
|
||||
|
|
@ -412,22 +480,25 @@ class Object(TypeClass):
|
|||
# controller, for example)
|
||||
|
||||
dbref = self.dbobj.dbref
|
||||
self.locks.add(";".join(["control:perm(Immortals)", # edit locks/permissions, delete
|
||||
"examine:perm(Builders)", # examine properties
|
||||
"view:all()", # look at object (visibility)
|
||||
"edit:perm(Wizards)", # edit properties/attributes
|
||||
"delete:perm(Wizards)", # delete object
|
||||
"get:all()", # pick up object
|
||||
"call:true()", # allow to call commands on this object
|
||||
"tell:perm(Wizards)", # allow emits to this object
|
||||
"puppet:pid(%s) or perm(Immortals) or pperm(Immortals)" % dbref])) # restricts puppeting of this object
|
||||
self.locks.add(";".join([
|
||||
"control:perm(Immortals)", # edit locks/permissions, delete
|
||||
"examine:perm(Builders)", # examine properties
|
||||
"view:all()", # look at object (visibility)
|
||||
"edit:perm(Wizards)", # edit properties/attributes
|
||||
"delete:perm(Wizards)", # delete object
|
||||
"get:all()", # pick up object
|
||||
"call:true()", # allow to call commands on this object
|
||||
"tell:perm(Wizards)", # allow emits to this object
|
||||
# restricts puppeting of this object
|
||||
"puppet:pid(%s) or perm(Immortals) or pperm(Immortals)" % dbref]))
|
||||
|
||||
def basetype_posthook_setup(self):
|
||||
"""
|
||||
Called once, after basetype_setup and at_object_creation. This should generally not be overloaded unless
|
||||
you are redefining how a room/exit/object works. It allows for basetype-like setup
|
||||
after the object is created. An example of this is EXITs, who need to know keys, aliases, locks
|
||||
etc to set up their exit-cmdsets.
|
||||
Called once, after basetype_setup and at_object_creation. This should
|
||||
generally not be overloaded unless you are redefining how a
|
||||
room/exit/object works. It allows for basetype-like setup after the
|
||||
object is created. An example of this is EXITs, who need to know keys,
|
||||
aliases, locks etc to set up their exit-cmdsets.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -437,7 +508,6 @@ class Object(TypeClass):
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
def at_object_delete(self):
|
||||
"""
|
||||
Called just before the database object is
|
||||
|
|
@ -502,34 +572,34 @@ class Object(TypeClass):
|
|||
|
||||
def at_server_reload(self):
|
||||
"""
|
||||
This hook is called whenever the server is shutting down for restart/reboot.
|
||||
If you want to, for example, save non-persistent properties across a restart,
|
||||
this is the place to do it.
|
||||
This hook is called whenever the server is shutting down for
|
||||
restart/reboot. If you want to, for example, save non-persistent
|
||||
properties across a restart, this is the place to do it.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_server_shutdown(self):
|
||||
"""
|
||||
This hook is called whenever the server is shutting down fully (i.e. not for
|
||||
a restart).
|
||||
This hook is called whenever the server is shutting down fully
|
||||
(i.e. not for a restart).
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_access_success(self, accessing_obj, access_type):
|
||||
"""
|
||||
This hook is called whenever accessing_obj succeed a lock check of type access_type
|
||||
on this object, for whatever reason. The return value of this hook is not used,
|
||||
the lock will still pass regardless of what this hook does (use lockstring/funcs to tweak
|
||||
the lock result).
|
||||
This hook is called whenever accessing_obj succeed a lock check of
|
||||
type access_type on this object, for whatever reason. The return value
|
||||
of this hook is not used, the lock will still pass regardless of what
|
||||
this hook does (use lockstring/funcs to tweak the lock result).
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_access_failure(self, accessing_obj, access_type):
|
||||
"""
|
||||
This hook is called whenever accessing_obj fails a lock check of type access_type
|
||||
on this object, for whatever reason. The return value of this hook is not used, the
|
||||
lock will still fail regardless of what this hook does (use lockstring/funcs to tweak the
|
||||
lock result).
|
||||
This hook is called whenever accessing_obj fails a lock check of type
|
||||
access_type on this object, for whatever reason. The return value of
|
||||
this hook is not used, the lock will still fail regardless of what
|
||||
this hook does (use lockstring/funcs to tweak the lock result).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -588,7 +658,6 @@ class Object(TypeClass):
|
|||
string = "%s arrives to %s from %s."
|
||||
self.location.msg_contents(string % (name, loc_name, src_name), exclude=self)
|
||||
|
||||
|
||||
def at_after_move(self, source_location):
|
||||
"""
|
||||
Called after move has completed, regardless of quiet mode or not.
|
||||
|
|
@ -598,7 +667,6 @@ class Object(TypeClass):
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
def at_object_leave(self, moved_obj, target_location):
|
||||
"""
|
||||
Called just before an object leaves from inside this object
|
||||
|
|
@ -630,9 +698,9 @@ class Object(TypeClass):
|
|||
"""
|
||||
This hook is responsible for handling the actual traversal, normally
|
||||
by calling traversing_object.move_to(target_location). It is normally
|
||||
only implemented by Exit objects. If it returns False (usually because move_to
|
||||
returned False), at_after_traverse below should not be called and
|
||||
instead at_failed_traverse should be called.
|
||||
only implemented by Exit objects. If it returns False (usually because
|
||||
move_to returned False), at_after_traverse below should not be called
|
||||
and instead at_failed_traverse should be called.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -687,7 +755,6 @@ class Object(TypeClass):
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
# hooks called by the default cmdset.
|
||||
|
||||
def return_appearance(self, pobject):
|
||||
|
|
@ -698,7 +765,8 @@ class Object(TypeClass):
|
|||
if not pobject:
|
||||
return
|
||||
# get and identify all objects
|
||||
visible = (con for con in self.contents if con != pobject and con.access(pobject, "view"))
|
||||
visible = (con for con in self.contents if con != pobject and
|
||||
con.access(pobject, "view"))
|
||||
exits, users, things = [], [], []
|
||||
for con in visible:
|
||||
key = con.key
|
||||
|
|
@ -744,6 +812,7 @@ class Object(TypeClass):
|
|||
dropper - the object which just dropped this object.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_say(self, speaker, message):
|
||||
"""
|
||||
Called on this object if an object inside this object speaks.
|
||||
|
|
@ -797,12 +866,13 @@ class Character(Object):
|
|||
|
||||
def at_pre_puppet(self, player, sessid=None):
|
||||
"""
|
||||
This recovers the character again after having been "stoved away" at the unpuppet
|
||||
This recovers the character again after having been "stoved away"
|
||||
at the unpuppet
|
||||
"""
|
||||
if self.db.prelogout_location:
|
||||
# try to recover
|
||||
self.location = self.db.prelogout_location
|
||||
if self.location == None:
|
||||
if self.location is None:
|
||||
# make sure location is never None (home should always exist)
|
||||
self.location = self.home
|
||||
if self.location:
|
||||
|
|
@ -824,8 +894,9 @@ class Character(Object):
|
|||
|
||||
def at_post_unpuppet(self, player, sessid=None):
|
||||
"""
|
||||
We stove away the character when the player goes ooc/logs off, otherwise the character object will
|
||||
remain in the room also after the player logged off ("headless", so to say).
|
||||
We stove away the character when the player goes ooc/logs off,
|
||||
otherwise the character object will remain in the room also after the
|
||||
player logged off ("headless", so to say).
|
||||
"""
|
||||
if self.location: # have to check, in case of multiple connections closing
|
||||
self.location.msg_contents("%s has left the game." % self.name, exclude=[self])
|
||||
|
|
@ -852,6 +923,7 @@ class Room(Object):
|
|||
"puppet:false()"])) # would be weird to puppet a room ...
|
||||
self.location = None
|
||||
|
||||
|
||||
#
|
||||
# Base Exit object
|
||||
#
|
||||
|
|
@ -905,7 +977,8 @@ class Exit(Object):
|
|||
# No shorthand error message. Call hook.
|
||||
self.obj.at_failed_traverse(self.caller)
|
||||
|
||||
# create an exit command. We give the properties here, to always trigger metaclass preparations
|
||||
# create an exit command. We give the properties here,
|
||||
# to always trigger metaclass preparations
|
||||
cmd = ExitCommand(key=exidbobj.db_key.strip().lower(),
|
||||
aliases=exidbobj.aliases.all(),
|
||||
locks=str(exidbobj.locks),
|
||||
|
|
@ -944,8 +1017,9 @@ class Exit(Object):
|
|||
|
||||
def at_cmdset_get(self):
|
||||
"""
|
||||
Called when the cmdset is requested from this object, just before the cmdset is
|
||||
actually extracted. If no Exit-cmdset is cached, create it now.
|
||||
Called when the cmdset is requested from this object, just before the
|
||||
cmdset is actually extracted. If no Exit-cmdset is cached, create
|
||||
it now.
|
||||
"""
|
||||
|
||||
if self.ndb.exit_reset or not self.cmdset.has_cmdset("_exitset", must_be_default=True):
|
||||
|
|
@ -984,8 +1058,9 @@ class Exit(Object):
|
|||
def at_failed_traverse(self, traversing_object):
|
||||
"""
|
||||
This is called if an object fails to traverse this object for some
|
||||
reason. It will not be called if the attribute "err_traverse" is defined,
|
||||
that attribute will then be echoed back instead as a convenient shortcut.
|
||||
reason. It will not be called if the attribute "err_traverse" is
|
||||
defined, that attribute will then be echoed back instead as a
|
||||
convenient shortcut.
|
||||
|
||||
(See also hooks at_before_traverse and at_after_traverse).
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ Runs as part of the Evennia's test suite with 'manage.py test"
|
|||
Please add new tests to this module as needed.
|
||||
|
||||
Guidelines:
|
||||
A 'test case' is testing a specific component and is defined as a class inheriting from unittest.TestCase.
|
||||
The test case class can have a method setUp() that creates and sets up the testing environment.
|
||||
All methods inside the test case class whose names start with 'test' are used as test methods by the runner.
|
||||
Inside the test methods, special member methods assert*() are used to test the behaviour.
|
||||
A 'test case' is testing a specific component and is defined as a class
|
||||
inheriting from unittest.TestCase. The test case class can have a method
|
||||
setUp() that creates and sets up the testing environment.
|
||||
All methods inside the test case class whose names start with 'test' are
|
||||
used as test methods by the runner. Inside the test methods, special member
|
||||
methods assert*() are used to test the behaviour.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
@ -24,12 +26,10 @@ try:
|
|||
except ImportError:
|
||||
import unittest
|
||||
|
||||
from django.conf import settings
|
||||
from src.objects import models, objects
|
||||
from src.utils import create
|
||||
from src.commands.default import tests as commandtests
|
||||
from src.locks import tests as locktests
|
||||
|
||||
|
||||
class TestObjAttrs(TestCase):
|
||||
"""
|
||||
Test aspects of ObjAttributes
|
||||
|
|
@ -49,6 +49,7 @@ class TestObjAttrs(TestCase):
|
|||
# self.assertEqual(self.obj2 ,self.obj1.db.testattr)
|
||||
# self.assertEqual(self.obj2.location, self.obj1.db.testattr.location)
|
||||
|
||||
|
||||
def suite():
|
||||
"""
|
||||
This function is called automatically by the django test runner.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
"""
|
||||
Makes it easier to import by grouping all relevant things already at this level.
|
||||
Makes it easier to import by grouping all relevant things already at this
|
||||
level.
|
||||
|
||||
You can henceforth import most things directly from src.player
|
||||
Also, the initiated object manager is available as src.players.manager.
|
||||
|
||||
"""
|
||||
|
||||
from src.players.player import *
|
||||
from src.players.player import *
|
||||
from src.players.models import PlayerDB
|
||||
|
||||
manager = PlayerDB.objects
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@
|
|||
#
|
||||
|
||||
from django import forms
|
||||
from django.db import models
|
||||
#from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.admin import widgets
|
||||
#from django.contrib.admin import widgets
|
||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||
from django.contrib.auth.models import User
|
||||
#from django.contrib.auth.models import User
|
||||
from src.players.models import PlayerDB
|
||||
from src.typeclasses.models import Attribute
|
||||
from src.utils import logger, create
|
||||
#from src.typeclasses.models import Attribute
|
||||
from src.utils import create
|
||||
|
||||
|
||||
# handle the custom User editor
|
||||
|
|
@ -109,6 +109,7 @@ class PlayerForm(forms.ModelForm):
|
|||
required=False,
|
||||
help_text="python path to player cmdset class (set in settings.CMDSET_PLAYER by default)")
|
||||
|
||||
|
||||
class PlayerInline(admin.StackedInline):
|
||||
"Inline creation of Player"
|
||||
model = PlayerDB
|
||||
|
|
@ -127,6 +128,7 @@ class PlayerInline(admin.StackedInline):
|
|||
extra = 1
|
||||
max_num = 1
|
||||
|
||||
|
||||
class PlayerDBAdmin(BaseUserAdmin):
|
||||
"This is the main creation screen for Users/players"
|
||||
|
||||
|
|
@ -136,17 +138,17 @@ class PlayerDBAdmin(BaseUserAdmin):
|
|||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password', 'email')}),
|
||||
('Website profile', {'fields': ('first_name', 'last_name'),
|
||||
'description':"<i>These are not used in the default system.</i>"}),
|
||||
'description': "<i>These are not used in the default system.</i>"}),
|
||||
('Website dates', {'fields': ('last_login', 'date_joined'),
|
||||
'description':'<i>Relevant only to the website.</i>'}),
|
||||
('Website Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions','groups'),
|
||||
'description': '<i>Relevant only to the website.</i>'}),
|
||||
('Website Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser',
|
||||
'user_permissions', 'groups'),
|
||||
'description': "<i>These are permissions/permission groups for accessing the admin site. They are unrelated to in-game access rights.</i>"}),
|
||||
('Game Options', {'fields': ('db_typeclass_path', 'db_cmdset_storage', 'db_lock_storage'),
|
||||
'description': '<i>These are attributes that are more relevant to gameplay.</i>'}))
|
||||
#('Game Options', {'fields': ('db_typeclass_path', 'db_cmdset_storage', 'db_permissions', 'db_lock_storage'),
|
||||
# 'description': '<i>These are attributes that are more relevant to gameplay.</i>'}))
|
||||
|
||||
|
||||
add_fieldsets = (
|
||||
(None,
|
||||
{'fields': ('username', 'password1', 'password2', 'email'),
|
||||
|
|
@ -162,7 +164,7 @@ class PlayerDBAdmin(BaseUserAdmin):
|
|||
#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("","","",
|
||||
create.create_player("", "", "",
|
||||
user=userobj,
|
||||
typeclass=typeclass,
|
||||
player_dbobj=userobj)
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ The managers for the custom Player object and permissions.
|
|||
|
||||
import datetime
|
||||
from django.contrib.auth.models import UserManager
|
||||
from functools import update_wrapper
|
||||
#from functools import update_wrapper
|
||||
from src.typeclasses.managers import returns_typeclass_list, returns_typeclass, TypedObjectManager
|
||||
from src.utils import logger
|
||||
#from src.utils import logger
|
||||
__all__ = ("PlayerManager",)
|
||||
|
||||
|
||||
#
|
||||
# Player Manager
|
||||
#
|
||||
|
|
@ -118,8 +119,8 @@ class PlayerManager(TypedObjectManager, UserManager):
|
|||
|
||||
def swap_character(self, player, new_character, delete_old_character=False):
|
||||
"""
|
||||
This disconnects a player from the current character (if any) and connects
|
||||
to a new character object.
|
||||
This disconnects a player from the current character (if any) and
|
||||
connects to a new character object.
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -23,15 +23,16 @@ 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, TagHandler, NickHandler,
|
||||
AliasHandler, AttributeHandler)
|
||||
from src.commands.cmdsethandler import CmdSetHandler
|
||||
from src.commands import cmdhandler
|
||||
from src.utils import utils
|
||||
from src.utils import utils, logger
|
||||
from src.utils.utils import to_str, make_iter
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
__all__ = ("PlayerDB",)
|
||||
__all__ = ("PlayerDB",)
|
||||
|
||||
_ME = _("me")
|
||||
_SELF = _("self")
|
||||
|
|
@ -47,14 +48,12 @@ _DA = object.__delattr__
|
|||
_TYPECLASS = None
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# PlayerDB
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
|
||||
class PlayerDB(TypedObject, AbstractUser):
|
||||
"""
|
||||
This is a special model using Django's 'profile' functionality
|
||||
|
|
@ -90,7 +89,9 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
|
||||
# store a connected flag here too, not just in sessionhandler.
|
||||
# This makes it easier to track from various out-of-process locations
|
||||
db_is_connected = models.BooleanField(default=False, verbose_name="is_connected", help_text="If player is connected to game or not")
|
||||
db_is_connected = models.BooleanField(default=False,
|
||||
verbose_name="is_connected",
|
||||
help_text="If player is connected to game or not")
|
||||
# database storage of persistant cmdsets.
|
||||
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True,
|
||||
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.")
|
||||
|
|
@ -118,24 +119,36 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
_SA(self, "nicks", NickHandler(self))
|
||||
|
||||
# alias to the objs property
|
||||
def __characters_get(self): return self.objs
|
||||
def __characters_set(self, value): self.objs = value
|
||||
def __characters_del(self): raise Exception("Cannot delete name")
|
||||
def __characters_get(self):
|
||||
return self.objs
|
||||
|
||||
def __characters_set(self, value):
|
||||
self.objs = value
|
||||
|
||||
def __characters_del(self):
|
||||
raise Exception("Cannot delete name")
|
||||
characters = property(__characters_get, __characters_set, __characters_del)
|
||||
|
||||
# cmdset_storage property
|
||||
# This seems very sensitive to caching, so leaving it be for now /Griatch
|
||||
#@property
|
||||
def cmdset_storage_get(self):
|
||||
"Getter. Allows for value = self.name. Returns a list of cmdset_storage."
|
||||
"""
|
||||
Getter. Allows for value = self.name. Returns a list of cmdset_storage.
|
||||
"""
|
||||
storage = _GA(self, "db_cmdset_storage")
|
||||
# we need to check so storage is not None
|
||||
return [path.strip() for path in storage.split(',')] if storage else []
|
||||
return [path.strip() for path in storage.split(',')] if storage else []
|
||||
|
||||
#@cmdset_storage.setter
|
||||
def cmdset_storage_set(self, value):
|
||||
"Setter. Allows for self.name = value. Stores as a comma-separated string."
|
||||
"""
|
||||
Setter. Allows for self.name = value. Stores as a comma-separated
|
||||
string.
|
||||
"""
|
||||
_SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value)))
|
||||
_GA(self, "save")()
|
||||
|
||||
#@cmdset_storage.deleter
|
||||
def cmdset_storage_del(self):
|
||||
"Deleter. Allows for del self.name"
|
||||
|
|
@ -161,10 +174,12 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
#@property
|
||||
def __username_get(self):
|
||||
return _GA(self, "username")
|
||||
|
||||
def __username_set(self, value):
|
||||
_SA(self, "username", value)
|
||||
|
||||
def __username_del(self):
|
||||
_DA(self, "username", value)
|
||||
_DA(self, "username")
|
||||
# aliases
|
||||
name = property(__username_get, __username_set, __username_del)
|
||||
key = property(__username_get, __username_set, __username_del)
|
||||
|
|
@ -173,8 +188,10 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
def __uid_get(self):
|
||||
"Getter. Retrieves the user id"
|
||||
return self.id
|
||||
|
||||
def __uid_set(self, value):
|
||||
raise Exception("User id cannot be set!")
|
||||
|
||||
def __uid_del(self):
|
||||
raise Exception("User id cannot be deleted!")
|
||||
uid = property(__uid_get, __uid_set, __uid_del)
|
||||
|
|
@ -197,21 +214,21 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
def msg(self, text=None, from_obj=None, sessid=None, **kwargs):
|
||||
"""
|
||||
Evennia -> User
|
||||
This is the main route for sending data back to the user from the server.
|
||||
This is the main route for sending data back to the user from the
|
||||
server.
|
||||
|
||||
outgoing_string (string) - text data to send
|
||||
from_obj (Object/Player) - source object of message to send. Its at_msg_send
|
||||
hook will be called.
|
||||
sessid - the session id of the session to send to. If not given, return to
|
||||
all sessions connected to this player. This is usually only
|
||||
from_obj (Object/Player) - source object of message to send. Its
|
||||
at_msg_send() hook will be called.
|
||||
sessid - the session id of the session to send to. If not given, return
|
||||
to all sessions connected to this player. This is usually only
|
||||
relevant when using msg() directly from a player-command (from
|
||||
a command on a Character, the character automatically stores and
|
||||
handles the sessid).
|
||||
a command on a Character, the character automatically stores
|
||||
and handles the sessid).
|
||||
kwargs (dict) - All other keywords are parsed as extra data.
|
||||
"""
|
||||
if "data" in kwargs:
|
||||
# deprecation warning
|
||||
from src.utils import logger
|
||||
logger.log_depmsg("PlayerDB:msg() 'data'-dict keyword is deprecated. Use **kwargs instead.")
|
||||
data = kwargs.pop("data")
|
||||
if isinstance(data, dict):
|
||||
|
|
@ -253,16 +270,17 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
if not _SESSIONS:
|
||||
from src.server.sessionhandler import SESSIONS as _SESSIONS
|
||||
return _SESSIONS.sessions_from_player(self)
|
||||
sessions = property(get_all_sessions) # alias shortcut
|
||||
|
||||
sessions = property(get_all_sessions) # alias shortcut
|
||||
|
||||
def disconnect_session_from_player(self, sessid):
|
||||
"""
|
||||
Access method for disconnecting a given session from the player
|
||||
(connection happens automatically in the sessionhandler)
|
||||
"""
|
||||
# this should only be one value, loop just to make sure to clean everything
|
||||
sessions = (session for session in self.get_all_sessions() if session.sessid == sessid)
|
||||
# this should only be one value, loop just to make sure to
|
||||
# clean everything
|
||||
sessions = (session for session in self.get_all_sessions()
|
||||
if session.sessid == sessid)
|
||||
for session in sessions:
|
||||
# this will also trigger unpuppeting
|
||||
session.sessionhandler.disconnect(session)
|
||||
|
|
@ -292,8 +310,9 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
# we don't allow to puppet an object already controlled by an active
|
||||
# player. To kick a player, call unpuppet_object on them explicitly.
|
||||
return
|
||||
# if we get to this point the character is ready to puppet or it was left
|
||||
# with a lingering player/sessid reference from an unclean server kill or similar
|
||||
# if we get to this point the character is ready to puppet or it
|
||||
# was left with a lingering player/sessid reference from an unclean
|
||||
# server kill or similar
|
||||
|
||||
if normal_mode:
|
||||
_GA(obj.typeclass, "at_pre_puppet")(self.typeclass, sessid=sessid)
|
||||
|
|
@ -341,8 +360,9 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
|
||||
def get_puppet(self, sessid, return_dbobj=False):
|
||||
"""
|
||||
Get an object puppeted by this session through this player. This is the main
|
||||
method for retrieving the puppeted object from the player's end.
|
||||
Get an object puppeted by this session through this player. This is
|
||||
the main method for retrieving the puppeted object from the
|
||||
player's end.
|
||||
|
||||
sessid - return character connected to this sessid,
|
||||
character - return character if connected to this player, else None.
|
||||
|
|
@ -359,7 +379,8 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
"""
|
||||
Get all currently puppeted objects as a list
|
||||
"""
|
||||
puppets = [session.puppet for session in self.get_all_sessions() if session.puppet]
|
||||
puppets = [session.puppet for session in self.get_all_sessions()
|
||||
if session.puppet]
|
||||
if return_dbobj:
|
||||
return puppets
|
||||
return [puppet.typeclass for puppet in puppets]
|
||||
|
|
@ -379,30 +400,25 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
|
||||
# utility methods
|
||||
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the player permanently.
|
||||
Makes sure to delete user also when deleting player - the two may never exist separately.
|
||||
"""
|
||||
for session in self.get_all_sessions():
|
||||
# unpuppeting all objects and disconnecting the user, if any
|
||||
# sessions remain (should usually be handled from the deleting command)
|
||||
# sessions remain (should usually be handled from the
|
||||
# deleting command)
|
||||
self.unpuppet_object(session.sessid)
|
||||
session.sessionhandler.disconnect(session, reason=_("Player being deleted."))
|
||||
|
||||
#try:
|
||||
# if _GA(self, "user"):
|
||||
# _GA(_GA(self, "user"), "delete")()
|
||||
#except AssertionError:
|
||||
# pass
|
||||
super(PlayerDB, self).delete(*args, **kwargs)
|
||||
|
||||
def execute_cmd(self, raw_string, sessid=None):
|
||||
"""
|
||||
Do something as this player. This method is never called normally,
|
||||
but only when the player object itself is supposed to execute the command. It
|
||||
does not take nicks on eventual puppets into account.
|
||||
but only when the player object itself is supposed to execute the
|
||||
command. It does not take nicks on eventual puppets into account.
|
||||
|
||||
raw_string - raw command input coming from the command line.
|
||||
"""
|
||||
# nick replacement - we require full-word matching.
|
||||
|
|
@ -410,8 +426,9 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
raw_string = utils.to_unicode(raw_string)
|
||||
|
||||
raw_list = raw_string.split(None)
|
||||
raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]]
|
||||
# get the nick replacement data directly from the database to be able to use db_category__in
|
||||
raw_list = [" ".join(raw_list[:i + 1]) for i in range(len(raw_list)) if raw_list[:i + 1]]
|
||||
# get the nick replacement data directly from the database to be
|
||||
# able to use db_category__in
|
||||
nicks = self.db_attributes.filter(db_category__in=("nick_inputline", "nick_channel"))
|
||||
for nick in nicks:
|
||||
if nick.db_key in raw_list:
|
||||
|
|
@ -419,22 +436,30 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
break
|
||||
if not sessid and _MULTISESSION_MODE in (0, 1):
|
||||
# in this case, we should either have only one sessid, or the sessid
|
||||
# should not matter (since the return goes to all of them we can just
|
||||
# use the first one as the source)
|
||||
# should not matter (since the return goes to all of them we can
|
||||
# just use the first one as the source)
|
||||
sessid = self.get_all_sessions()[0].sessid
|
||||
return cmdhandler.cmdhandler(self.typeclass, raw_string, callertype="player", sessid=sessid)
|
||||
return cmdhandler.cmdhandler(self.typeclass, raw_string,
|
||||
callertype="player", sessid=sessid)
|
||||
|
||||
def search(self, ostring, return_character=False, **kwargs):
|
||||
def search(self, ostring, return_puppet=False,
|
||||
return_character=False, **kwargs):
|
||||
"""
|
||||
This is similar to the ObjectDB search method but will search for Players only. Errors
|
||||
will be echoed, and None returned if no Player is found.
|
||||
This is similar to the ObjectDB search method but will search for
|
||||
Players only. Errors will be echoed, and None returned if no Player
|
||||
is found.
|
||||
|
||||
return_character - will try to return the character the player controls instead of
|
||||
the Player object itself. If no Character exists (since Player is
|
||||
OOC), None will be returned.
|
||||
Extra keywords are ignored, but are allowed in call in order to make API more consistent
|
||||
with objects.models.TypedObject.search.
|
||||
return_character - will try to return the character the player controls
|
||||
instead of the Player object itself. If no
|
||||
Character exists (since Player is OOC), None will
|
||||
be returned.
|
||||
Extra keywords are ignored, but are allowed in call in order to make
|
||||
API more consistent with objects.models.
|
||||
TypedObject.search.
|
||||
"""
|
||||
if return_character:
|
||||
logger.log_depmsg("Player.search's 'return_character' keyword is deprecated. Use the return_puppet keyword instead.")
|
||||
#return_puppet = return_character
|
||||
# handle me, self
|
||||
if ostring in (_ME, _SELF, '*' + _ME, '*' + _SELF):
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -41,23 +41,29 @@ class Player(TypeClass):
|
|||
|
||||
key (string) - name of player
|
||||
name (string)- wrapper for user.username
|
||||
aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings.
|
||||
aliases (list of strings) - aliases to the object. Will be saved to
|
||||
database as AliasDB entries but returned as strings.
|
||||
dbref (int, read-only) - unique #id-number. Also "id" can be used.
|
||||
dbobj (Player, read-only) - link to database model. dbobj.typeclass points back to this class
|
||||
typeclass (Player, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch.
|
||||
dbobj (Player, read-only) - link to database model. dbobj.typeclass
|
||||
points back to this class
|
||||
typeclass (Player, read-only) - this links back to this class as an
|
||||
identified only. Use self.swap_typeclass() to switch.
|
||||
date_created (string) - time stamp of object creation
|
||||
permissions (list of strings) - list of permission strings
|
||||
|
||||
user (User, read-only) - django User authorization object
|
||||
obj (Object) - game object controlled by player. 'character' can also be used.
|
||||
obj (Object) - game object controlled by player. 'character' can also
|
||||
be used.
|
||||
sessions (list of Sessions) - sessions connected to this player
|
||||
is_superuser (bool, read-only) - if the connected user is a superuser
|
||||
|
||||
* Handlers
|
||||
|
||||
locks - lock-handler: use locks.add() to add new lock strings
|
||||
db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr
|
||||
ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data
|
||||
db - attribute-handler: store/retrieve database attributes on this
|
||||
self.db.myattr=val, val=self.db.myattr
|
||||
ndb - non-persistent attribute handler: same as db but does not
|
||||
create a database entry when storing data
|
||||
scripts - script-handler. Add new scripts to object with scripts.add()
|
||||
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
|
||||
nicks - nick-handler. New nicks with nicks.add().
|
||||
|
|
@ -67,7 +73,9 @@ class Player(TypeClass):
|
|||
msg(outgoing_string, from_obj=None, **kwargs)
|
||||
swap_character(new_character, delete_old_character=False)
|
||||
execute_cmd(raw_string)
|
||||
search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False)
|
||||
search(ostring, global_search=False, attribute_name=None,
|
||||
use_nicks=False, location=None,
|
||||
ignore_errors=False, player=False)
|
||||
is_typeclass(typeclass, exact=False)
|
||||
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
|
||||
access(accessing_obj, access_type='read', default=False)
|
||||
|
|
@ -99,15 +107,16 @@ class Player(TypeClass):
|
|||
def msg(self, text=None, from_obj=None, sessid=None, **kwargs):
|
||||
"""
|
||||
Evennia -> User
|
||||
This is the main route for sending data back to the user from the server.
|
||||
This is the main route for sending data back to the user from
|
||||
the server.
|
||||
|
||||
text (string) - text data to send
|
||||
from_obj (Object/Player) - source object of message to send
|
||||
sessid - the session id of the session to send to. If not given, return to
|
||||
all sessions connected to this player. This is usually only
|
||||
relevant when using msg() directly from a player-command (from
|
||||
a command on a Character, the character automatically stores and
|
||||
handles the sessid).
|
||||
sessid - the session id of the session to send to. If not given,
|
||||
return to all sessions connected to this player. This is usually only
|
||||
relevant when using msg() directly from a player-command (from
|
||||
a command on a Character, the character automatically stores and
|
||||
handles the sessid).
|
||||
kwargs - extra data to send through protocol
|
||||
"""
|
||||
self.dbobj.msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs)
|
||||
|
|
@ -131,16 +140,18 @@ class Player(TypeClass):
|
|||
|
||||
Argument:
|
||||
raw_string (string) - raw command input
|
||||
sessid (int) - id of session executing the command. This sets the sessid property on the command
|
||||
sessid (int) - id of session executing the command. This sets the
|
||||
sessid property on the command
|
||||
|
||||
Returns Deferred - this is an asynchronous Twisted object that will
|
||||
not fire until the command has actually finished executing. To overload
|
||||
this one needs to attach callback functions to it, with addCallback(function).
|
||||
This function will be called with an eventual return value from the command
|
||||
execution.
|
||||
not fire until the command has actually finished executing. To
|
||||
overload this one needs to attach callback functions to it, with
|
||||
addCallback(function). This function will be called with an
|
||||
eventual return value from the command execution.
|
||||
|
||||
This return is not used at all by Evennia by default, but might be useful
|
||||
for coders intending to implement some sort of nested command structure.
|
||||
This return is not used at all by Evennia by default, but might
|
||||
be useful for coders intending to implement some sort of nested
|
||||
command structure.
|
||||
"""
|
||||
return self.dbobj.execute_cmd(raw_string, sessid=sessid)
|
||||
|
||||
|
|
@ -204,11 +215,13 @@ class Player(TypeClass):
|
|||
|
||||
|
||||
"""
|
||||
self.dbobj.swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default)
|
||||
self.dbobj.swap_typeclass(new_typeclass,
|
||||
clean_attributes=clean_attributes, no_default=no_default)
|
||||
|
||||
def access(self, accessing_obj, access_type='read', default=False):
|
||||
"""
|
||||
Determines if another object has permission to access this object in whatever way.
|
||||
Determines if another object has permission to access this object
|
||||
in whatever way.
|
||||
|
||||
accessing_obj (Object)- object trying to access this one
|
||||
access_type (string) - type of access sought
|
||||
|
|
@ -221,8 +234,8 @@ class Player(TypeClass):
|
|||
This explicitly checks the given string against this object's
|
||||
'permissions' property without involving any locks.
|
||||
|
||||
permstring (string) - permission string that need to match a permission on the object.
|
||||
(example: 'Builders')
|
||||
permstring (string) - permission string that need to match a permission
|
||||
on the object. (example: 'Builders')
|
||||
"""
|
||||
return self.dbobj.check_permstring(permstring)
|
||||
|
||||
|
|
@ -307,7 +320,8 @@ class Player(TypeClass):
|
|||
except Exception:
|
||||
logger.log_trace()
|
||||
now = datetime.datetime.now()
|
||||
now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month, now.day, now.hour, now.minute)
|
||||
now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month,
|
||||
now.day, now.hour, now.minute)
|
||||
if _CONNECT_CHANNEL:
|
||||
_CONNECT_CHANNEL.tempmsg("[%s, %s]: %s" % (_CONNECT_CHANNEL.key, now, message))
|
||||
else:
|
||||
|
|
@ -361,15 +375,15 @@ class Player(TypeClass):
|
|||
|
||||
def at_server_reload(self):
|
||||
"""
|
||||
This hook is called whenever the server is shutting down for restart/reboot.
|
||||
If you want to, for example, save non-persistent properties across a restart,
|
||||
this is the place to do it.
|
||||
This hook is called whenever the server is shutting down for
|
||||
restart/reboot. If you want to, for example, save non-persistent
|
||||
properties across a restart, this is the place to do it.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_server_shutdown(self):
|
||||
"""
|
||||
This hook is called whenever the server is shutting down fully (i.e. not for
|
||||
a restart).
|
||||
This hook is called whenever the server is shutting down fully
|
||||
(i.e. not for a restart).
|
||||
"""
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
"""
|
||||
Makes it easier to import by grouping all relevant things already at this level.
|
||||
Makes it easier to import by grouping all relevant things already at this
|
||||
level.
|
||||
|
||||
You can henceforth import most things directly from src.scripts
|
||||
Also, the initiated object manager is available as src.scripts.manager.
|
||||
|
||||
"""
|
||||
|
||||
from src.scripts.scripts import *
|
||||
from src.scripts.scripts import *
|
||||
from src.scripts.models import ScriptDB
|
||||
|
||||
manager = ScriptDB.objects
|
||||
|
|
|
|||
|
|
@ -7,14 +7,17 @@ from src.typeclasses.models import Attribute
|
|||
from src.scripts.models import ScriptDB
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
class AttributeInline(admin.TabularInline):
|
||||
model = Attribute
|
||||
fields = ('db_key', 'db_value')
|
||||
max_num = 1
|
||||
|
||||
|
||||
class ScriptDBAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ('id', 'db_key', 'db_typeclass_path', 'db_obj', 'db_interval', 'db_repeats', 'db_persistent')
|
||||
list_display = ('id', 'db_key', 'db_typeclass_path',
|
||||
'db_obj', 'db_interval', 'db_repeats', 'db_persistent')
|
||||
list_display_links = ('id', 'db_key')
|
||||
ordering = ['db_obj', 'db_typeclass_path']
|
||||
search_fields = ['^db_key', 'db_typeclass_path']
|
||||
|
|
@ -25,7 +28,9 @@ class ScriptDBAdmin(admin.ModelAdmin):
|
|||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields':(('db_key', 'db_typeclass_path'), 'db_interval', 'db_repeats', 'db_start_delay', 'db_persistent', 'db_obj')}),
|
||||
'fields': (('db_key', 'db_typeclass_path'), 'db_interval',
|
||||
'db_repeats', 'db_start_delay', 'db_persistent',
|
||||
'db_obj')}),
|
||||
)
|
||||
#inlines = [AttributeInline]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ __all__ = ("ScriptManager",)
|
|||
|
||||
VALIDATE_ITERATION = 0
|
||||
|
||||
|
||||
class ScriptManager(TypedObjectManager):
|
||||
"""
|
||||
This Scriptmanager implements methods for searching
|
||||
|
|
@ -82,8 +83,8 @@ class ScriptManager(TypedObjectManager):
|
|||
def remove_non_persistent(self, obj=None):
|
||||
"""
|
||||
This cleans up the script database of all non-persistent
|
||||
scripts, or only those on obj. It is called every time the server restarts
|
||||
and
|
||||
scripts, or only those on obj. It is called every time the server
|
||||
restarts.
|
||||
"""
|
||||
if obj:
|
||||
to_stop = self.filter(db_obj=obj, db_persistent=False, db_is_active=True)
|
||||
|
|
@ -211,10 +212,11 @@ class ScriptManager(TypedObjectManager):
|
|||
Make an identical copy of the original_script
|
||||
"""
|
||||
typeclass = original_script.typeclass_path
|
||||
new_key = new_key if new_key!=None else original_script.key
|
||||
new_obj = new_obj if new_obj!=None else original_script.obj
|
||||
new_locks = new_locks if new_locks!=None else original_script.db_lock_storage
|
||||
new_key = new_key if new_key is not None else original_script.key
|
||||
new_obj = new_obj if new_obj is not None else original_script.obj
|
||||
new_locks = new_locks if new_locks is not None else original_script.db_lock_storage
|
||||
|
||||
from src.utils import create
|
||||
new_script = create.create_script(typeclass, key=new_key, obj=new_obj, locks=new_locks, autostart=True)
|
||||
new_script = create.create_script(typeclass, key=new_key, obj=new_obj,
|
||||
locks=new_locks, autostart=True)
|
||||
return new_script
|
||||
|
|
|
|||
|
|
@ -26,10 +26,9 @@ Common examples of uses of Scripts:
|
|||
"""
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_init, pre_delete
|
||||
|
||||
from src.typeclasses.models import Attribute, TypedObject, TagHandler, AttributeHandler#, AliasHandler, NickHandler
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from src.typeclasses.models import (TypedObject, TagHandler,
|
||||
AttributeHandler)
|
||||
from src.scripts.manager import ScriptManager
|
||||
|
||||
__all__ = ("ScriptDB",)
|
||||
|
|
|
|||
|
|
@ -38,10 +38,13 @@ class ScriptHandler(object):
|
|||
interval = script.interval
|
||||
if script.repeats:
|
||||
repeats = script.repeats
|
||||
try: next_repeat = script.time_until_next_repeat()
|
||||
except: next_repeat = "?"
|
||||
try:
|
||||
next_repeat = script.time_until_next_repeat()
|
||||
except:
|
||||
next_repeat = "?"
|
||||
string += _("\n '%(key)s' (%(next_repeat)s/%(interval)s, %(repeats)s repeats): %(desc)s") % \
|
||||
{"key":script.key, "next_repeat":next_repeat, "interval":interval,"repeats":repeats,"desc":script.desc}
|
||||
{"key": script.key, "next_repeat": next_repeat,
|
||||
"interval": interval, "repeats": repeats, "desc": script.desc}
|
||||
return string.strip()
|
||||
|
||||
def add(self, scriptclass, key=None, autostart=True):
|
||||
|
|
@ -51,10 +54,12 @@ class ScriptHandler(object):
|
|||
scriptclass - either a class object
|
||||
inheriting from Script, an instantiated script object
|
||||
or a python path to such a class object.
|
||||
key - optional identifier for the script (often set in script definition)
|
||||
key - optional identifier for the script (often set in script
|
||||
definition)
|
||||
autostart - start the script upon adding it
|
||||
"""
|
||||
script = create.create_script(scriptclass, key=key, obj=self.obj, autostart=autostart)
|
||||
script = create.create_script(scriptclass, key=key, obj=self.obj,
|
||||
autostart=autostart)
|
||||
if not script:
|
||||
logger.log_errmsg("Script %s could not be created and/or started." % scriptclass)
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -5,30 +5,31 @@ scripts are inheriting from.
|
|||
It also defines a few common scripts.
|
||||
"""
|
||||
|
||||
from sys import getsizeof
|
||||
from time import time
|
||||
from collections import defaultdict
|
||||
from twisted.internet.defer import maybeDeferred
|
||||
from twisted.internet.task import LoopingCall
|
||||
from django.conf import settings
|
||||
from src.server import caches
|
||||
from django.utils.translation import ugettext as _
|
||||
from src.typeclasses.typeclass import TypeClass
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.comms import channelhandler
|
||||
from src.utils import logger, is_pypy
|
||||
from django.utils.translation import ugettext as _
|
||||
from src.utils import logger
|
||||
|
||||
__all__ = ["Script", "DoNothing", "CheckSessions", "ValidateScripts", "ValidateChannelHandler"]
|
||||
__all__ = ["Script", "DoNothing", "CheckSessions",
|
||||
"ValidateScripts", "ValidateChannelHandler"]
|
||||
|
||||
_SESSIONS = None
|
||||
_ATTRIBUTE_CACHE_MAXSIZE = settings.ATTRIBUTE_CACHE_MAXSIZE # attr-cache size in MB.
|
||||
# attr-cache size in MB
|
||||
_ATTRIBUTE_CACHE_MAXSIZE = settings.ATTRIBUTE_CACHE_MAXSIZE
|
||||
|
||||
|
||||
#
|
||||
# Base script, inherit from Script below instead.
|
||||
#
|
||||
class ScriptClass(TypeClass):
|
||||
"""
|
||||
Base class for scripts. Don't inherit from this, inherit from Script instead.
|
||||
Base class for scripts. Don't inherit from this, inherit
|
||||
from the class 'Script' instead.
|
||||
"""
|
||||
# private methods
|
||||
|
||||
|
|
@ -53,7 +54,8 @@ class ScriptClass(TypeClass):
|
|||
else:
|
||||
# starting script anew.
|
||||
#print "_start_task: self.interval:", self.key, self.dbobj.interval
|
||||
self.ndb.twisted_task.start(self.dbobj.interval, now=start_now and not self.start_delay)
|
||||
self.ndb.twisted_task.start(self.dbobj.interval,
|
||||
now=start_now and not self.start_delay)
|
||||
self.ndb.time_last_called = int(time())
|
||||
|
||||
def _stop_task(self):
|
||||
|
|
@ -64,16 +66,19 @@ class ScriptClass(TypeClass):
|
|||
self.ndb.twisted_task.stop()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
||||
def _step_err_callback(self, e):
|
||||
"callback for runner errors"
|
||||
cname = self.__class__.__name__
|
||||
estring = _("Script %(key)s(#%(dbid)i) of type '%(cname)s': at_repeat() error '%(err)s'.") % \
|
||||
{"key":self.key, "dbid":self.dbid, "cname":cname, "err":e.getErrorMessage()}
|
||||
{"key": self.key, "dbid": self.dbid, "cname": cname,
|
||||
"err": e.getErrorMessage()}
|
||||
try:
|
||||
self.dbobj.db_obj.msg(estring)
|
||||
except Exception:
|
||||
pass
|
||||
logger.log_errmsg(estring)
|
||||
|
||||
def _step_succ_callback(self):
|
||||
"step task runner. No try..except needed due to defer wrap."
|
||||
if not self.is_valid():
|
||||
|
|
@ -82,7 +87,7 @@ class ScriptClass(TypeClass):
|
|||
self.at_repeat()
|
||||
repeats = self.dbobj.db_repeats
|
||||
if repeats <= 0:
|
||||
pass # infinite repeat
|
||||
pass # infinite repeat
|
||||
elif repeats == 1:
|
||||
self.stop()
|
||||
return
|
||||
|
|
@ -92,14 +97,14 @@ class ScriptClass(TypeClass):
|
|||
self.save()
|
||||
|
||||
if self.ndb._paused_time:
|
||||
# this means we were running an unpaused script, for the time remaining
|
||||
# after the pause. Now we start a normal-running timer again.
|
||||
#print "switching to normal run:", self.key
|
||||
# this means we were running an unpaused script, for the
|
||||
# time remaining after the pause. Now we start a normal-running
|
||||
# timer again.
|
||||
# print "switching to normal run:", self.key
|
||||
del self.ndb._paused_time
|
||||
self._stop_task()
|
||||
self._start_task(start_now=False)
|
||||
|
||||
|
||||
def _step_task(self):
|
||||
"step task"
|
||||
try:
|
||||
|
|
@ -109,7 +114,6 @@ class ScriptClass(TypeClass):
|
|||
except Exception:
|
||||
logger.log_trace()
|
||||
|
||||
|
||||
# Public methods
|
||||
|
||||
def time_until_next_repeat(self):
|
||||
|
|
@ -136,7 +140,8 @@ class ScriptClass(TypeClass):
|
|||
force_restart - if True, will always restart the script, regardless
|
||||
of if it has started before.
|
||||
|
||||
returns 0 or 1 to indicated the script has been started or not. Used in counting.
|
||||
returns 0 or 1 to indicated the script has been started or not.
|
||||
Used in counting.
|
||||
"""
|
||||
#print "Script %s (%s) start (active:%s, force:%s) ..." % (self.key, id(self.dbobj),
|
||||
# self.is_active, force_restart)
|
||||
|
|
@ -205,7 +210,7 @@ class ScriptClass(TypeClass):
|
|||
"""
|
||||
#print "pausing", self.key, self.time_until_next_repeat()
|
||||
dt = self.time_until_next_repeat()
|
||||
if dt == None:
|
||||
if dt is None:
|
||||
return
|
||||
self.db._paused_time = dt
|
||||
self._stop_task()
|
||||
|
|
@ -216,7 +221,7 @@ class ScriptClass(TypeClass):
|
|||
"""
|
||||
#print "unpausing", self.key, self.db._paused_time
|
||||
dt = self.db._paused_time
|
||||
if dt == None:
|
||||
if dt is None:
|
||||
return False
|
||||
try:
|
||||
self.dbobj.is_active = True
|
||||
|
|
@ -234,18 +239,23 @@ class ScriptClass(TypeClass):
|
|||
def at_script_creation(self):
|
||||
"placeholder"
|
||||
pass
|
||||
|
||||
def is_valid(self):
|
||||
"placeholder"
|
||||
pass
|
||||
|
||||
def at_start(self):
|
||||
"placeholder."
|
||||
pass
|
||||
|
||||
def at_stop(self):
|
||||
"placeholder"
|
||||
pass
|
||||
|
||||
def at_repeat(self):
|
||||
"placeholder"
|
||||
pass
|
||||
|
||||
def at_init(self):
|
||||
"called when typeclass re-caches. Usually not used for scripts."
|
||||
pass
|
||||
|
|
@ -263,8 +273,9 @@ class Script(ScriptClass):
|
|||
|
||||
def __init__(self, dbobj):
|
||||
"""
|
||||
This is the base TypeClass for all Scripts. Scripts describe events, timers and states in game,
|
||||
they can have a time component or describe a state that changes under certain conditions.
|
||||
This is the base TypeClass for all Scripts. Scripts describe events,
|
||||
timers and states in game, they can have a time component or describe
|
||||
a state that changes under certain conditions.
|
||||
|
||||
Script API:
|
||||
|
||||
|
|
@ -272,57 +283,73 @@ class Script(ScriptClass):
|
|||
|
||||
key (string) - name of object
|
||||
name (string)- same as key
|
||||
aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings.
|
||||
aliases (list of strings) - aliases to the object. Will be saved to
|
||||
database as AliasDB entries but returned as strings.
|
||||
dbref (int, read-only) - unique #id-number. Also "id" can be used.
|
||||
dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class
|
||||
typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch.
|
||||
dbobj (Object, read-only) - link to database model. dbobj.typeclass
|
||||
points back to this class
|
||||
typeclass (Object, read-only) - this links back to this class as an
|
||||
identified only. Use self.swap_typeclass() to switch.
|
||||
date_created (string) - time stamp of object creation
|
||||
permissions (list of strings) - list of permission strings
|
||||
|
||||
desc (string) - optional description of script, shown in listings
|
||||
obj (Object) - optional object that this script is connected to and acts on (set automatically by obj.scripts.add())
|
||||
interval (int) - how often script should run, in seconds. <=0 turns off ticker
|
||||
start_delay (bool) - if the script should start repeating right away or wait self.interval seconds
|
||||
repeats (int) - how many times the script should repeat before stopping. <=0 means infinite repeats
|
||||
obj (Object) - optional object that this script is connected to
|
||||
and acts on (set automatically
|
||||
by obj.scripts.add())
|
||||
interval (int) - how often script should run, in seconds.
|
||||
<=0 turns off ticker
|
||||
start_delay (bool) - if the script should start repeating right
|
||||
away or wait self.interval seconds
|
||||
repeats (int) - how many times the script should repeat before
|
||||
stopping. <=0 means infinite repeats
|
||||
persistent (bool) - if script should survive a server shutdown or not
|
||||
is_active (bool) - if script is currently running
|
||||
|
||||
* Handlers
|
||||
|
||||
locks - lock-handler: use locks.add() to add new lock strings
|
||||
db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr
|
||||
ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data
|
||||
db - attribute-handler: store/retrieve database attributes on this
|
||||
self.db.myattr=val, val=self.db.myattr
|
||||
ndb - non-persistent attribute handler: same as db but does not
|
||||
create a database entry when storing data
|
||||
|
||||
* Helper methods
|
||||
|
||||
start() - start script (this usually happens automatically at creation and obj.script.add() etc)
|
||||
start() - start script (this usually happens automatically at creation
|
||||
and obj.script.add() etc)
|
||||
stop() - stop script, and delete it
|
||||
pause() - put the script on hold, until unpause() is called. If script is persistent, the pause state will survive a shutdown.
|
||||
unpause() - restart a previously paused script. The script will continue as if it was never paused.
|
||||
time_until_next_repeat() - if a timed script (interval>0), returns time until next tick
|
||||
pause() - put the script on hold, until unpause() is called. If script
|
||||
is persistent, the pause state will survive a shutdown.
|
||||
unpause() - restart a previously paused script. The script will
|
||||
continue as if it was never paused.
|
||||
time_until_next_repeat() - if a timed script (interval>0), returns
|
||||
time until next tick
|
||||
|
||||
* Hook methods
|
||||
|
||||
at_script_creation() - called only once, when an object of this
|
||||
class is first created.
|
||||
is_valid() - is called to check if the script is valid to be running
|
||||
at the current time. If is_valid() returns False, the running
|
||||
script is stopped and removed from the game. You can use this
|
||||
to check state changes (i.e. an script tracking some combat
|
||||
stats at regular intervals is only valid to run while there is
|
||||
actual combat going on).
|
||||
at_start() - Called every time the script is started, which for persistent
|
||||
scripts is at least once every server start. Note that this is
|
||||
unaffected by self.delay_start, which only delays the first call
|
||||
to at_repeat().
|
||||
at_repeat() - Called every self.interval seconds. It will be called immediately
|
||||
upon launch unless self.delay_start is True, which will delay
|
||||
the first call of this method by self.interval seconds. If
|
||||
self.interval<=0, this method will never be called.
|
||||
at_stop() - Called as the script object is stopped and is about to be removed from
|
||||
the game, e.g. because is_valid() returned False.
|
||||
at_server_reload() - Called when server reloads. Can be used to save temporary
|
||||
variables you want should survive a reload.
|
||||
at the current time. If is_valid() returns False, the
|
||||
running script is stopped and removed from the game. You
|
||||
can use this to check state changes (i.e. an script
|
||||
tracking some combat stats at regular intervals is only
|
||||
valid to run while there is actual combat going on).
|
||||
at_start() - Called every time the script is started, which for
|
||||
persistent scripts is at least once every server start.
|
||||
Note that this is unaffected by self.delay_start, which
|
||||
only delays the first call to at_repeat().
|
||||
at_repeat() - Called every self.interval seconds. It will be called
|
||||
immediately upon launch unless self.delay_start is True,
|
||||
which will delay the first call of this method by
|
||||
self.interval seconds. If self.interval<=0, this method
|
||||
will never be called.
|
||||
at_stop() - Called as the script object is stopped and is about to
|
||||
be removed from the game, e.g. because is_valid()
|
||||
returned False or self.stop() was called manually.
|
||||
at_server_reload() - Called when server reloads. Can be used to save
|
||||
temporary variables you want should survive a reload.
|
||||
at_server_shutdown() - called at a full server shutdown.
|
||||
|
||||
|
||||
|
|
@ -335,7 +362,7 @@ class Script(ScriptClass):
|
|||
"""
|
||||
self.key = "<unnamed>"
|
||||
self.desc = ""
|
||||
self.interval = 0 # infinite
|
||||
self.interval = 0 # infinite
|
||||
self.start_delay = False
|
||||
self.repeats = 0 # infinite
|
||||
self.persistent = False
|
||||
|
|
@ -372,29 +399,29 @@ class Script(ScriptClass):
|
|||
|
||||
def at_server_reload(self):
|
||||
"""
|
||||
This hook is called whenever the server is shutting down for restart/reboot.
|
||||
If you want to, for example, save non-persistent properties across a restart,
|
||||
this is the place to do it.
|
||||
This hook is called whenever the server is shutting down for
|
||||
restart/reboot. If you want to, for example, save non-persistent
|
||||
properties across a restart, this is the place to do it.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_server_shutdown(self):
|
||||
"""
|
||||
This hook is called whenever the server is shutting down fully (i.e. not for
|
||||
a restart).
|
||||
This hook is called whenever the server is shutting down fully
|
||||
(i.e. not for a restart).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# Some useful default Script types used by Evennia.
|
||||
|
||||
class DoNothing(Script):
|
||||
"An script that does nothing. Used as default fallback."
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_do_nothing"
|
||||
self.desc = _("This is an empty placeholder script.")
|
||||
"Setup the script"
|
||||
self.key = "sys_do_nothing"
|
||||
self.desc = _("This is an empty placeholder script.")
|
||||
|
||||
|
||||
class Store(Script):
|
||||
"Simple storage script"
|
||||
|
|
@ -403,6 +430,7 @@ class Store(Script):
|
|||
self.key = "sys_storage"
|
||||
self.desc = _("This is a generic storage container.")
|
||||
|
||||
|
||||
class CheckSessions(Script):
|
||||
"Check sessions regularly."
|
||||
def at_script_creation(self):
|
||||
|
|
@ -421,13 +449,14 @@ class CheckSessions(Script):
|
|||
#print "ValidateSessions run"
|
||||
_SESSIONS.validate_sessions()
|
||||
|
||||
|
||||
class ValidateScripts(Script):
|
||||
"Check script validation regularly"
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_scripts_validate"
|
||||
self.desc = _("Validates all scripts regularly.")
|
||||
self.interval = 3600 # validate every hour.
|
||||
self.interval = 3600 # validate every hour.
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
|
|
@ -435,13 +464,14 @@ class ValidateScripts(Script):
|
|||
#print "ValidateScripts run."
|
||||
ScriptDB.objects.validate()
|
||||
|
||||
|
||||
class ValidateChannelHandler(Script):
|
||||
"Update the channelhandler to make sure it's in sync."
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_channels_validate"
|
||||
self.desc = _("Updates the channel handler")
|
||||
self.interval = 3700 # validate a little later than ValidateScripts
|
||||
self.interval = 3700 # validate a little later than ValidateScripts
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from django.contrib import admin
|
||||
from src.server.models import ServerConfig
|
||||
|
||||
class ServerConfigAdmin(admin.ModelAdmin):
|
||||
"Custom admin for server configs"
|
||||
list_display = ('db_key', 'db_value')
|
||||
list_display_links = ('db_key',)
|
||||
ordering = ['db_key', 'db_value']
|
||||
search_fields = ['db_key']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
admin.site.register(ServerConfig, ServerConfigAdmin)
|
||||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from django.contrib import admin
|
||||
from src.server.models import ServerConfig
|
||||
|
||||
|
||||
class ServerConfigAdmin(admin.ModelAdmin):
|
||||
"Custom admin for server configs"
|
||||
list_display = ('db_key', 'db_value')
|
||||
list_display_links = ('db_key',)
|
||||
ordering = ['db_key', 'db_value']
|
||||
search_fields = ['db_key']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
admin.site.register(ServerConfig, ServerConfigAdmin)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
"""
|
||||
Contains the protocols, commands, and client factory needed for the Server and Portal
|
||||
to communicate with each other, letting Portal work as a proxy. Both sides use this
|
||||
same protocol.
|
||||
Contains the protocols, commands, and client factory needed for the Server
|
||||
and Portal to communicate with each other, letting Portal work as a proxy.
|
||||
Both sides use this same protocol.
|
||||
|
||||
The separation works like this:
|
||||
|
||||
Portal - (AMP client) handles protocols. It contains a list of connected sessions in a
|
||||
dictionary for identifying the respective player connected. If it looses the AMP connection
|
||||
it will automatically try to reconnect.
|
||||
Portal - (AMP client) handles protocols. It contains a list of connected
|
||||
sessions in a dictionary for identifying the respective player
|
||||
connected. If it looses the AMP connection it will automatically
|
||||
try to reconnect.
|
||||
|
||||
Server - (AMP server) Handles all mud operations. The server holds its own list
|
||||
of sessions tied to player objects. This is synced against the portal at startup
|
||||
and when a session connects/disconnects
|
||||
of sessions tied to player objects. This is synced against the portal
|
||||
at startup and when a session connects/disconnects
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -29,16 +30,17 @@ from src.utils.utils import to_str, variable_from_module
|
|||
|
||||
# communication bits
|
||||
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server sessigon sync
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server sessigon sync
|
||||
|
||||
MAXLEN = 65535 # max allowed data length in AMP protocol
|
||||
|
||||
MAXLEN = 65535 # max allowed data length in AMP protocol
|
||||
|
||||
def get_restart_mode(restart_file):
|
||||
"""
|
||||
|
|
@ -49,6 +51,7 @@ def get_restart_mode(restart_file):
|
|||
return flag == "True"
|
||||
return False
|
||||
|
||||
|
||||
class AmpServerFactory(protocol.ServerFactory):
|
||||
"""
|
||||
This factory creates the Server as a new AMPProtocol instance for accepting
|
||||
|
|
@ -71,9 +74,11 @@ class AmpServerFactory(protocol.ServerFactory):
|
|||
self.server.amp_protocol.factory = self
|
||||
return self.server.amp_protocol
|
||||
|
||||
|
||||
class AmpClientFactory(protocol.ReconnectingClientFactory):
|
||||
"""
|
||||
This factory creates an instance of the Portal, an AMPProtocol instances to use to connect
|
||||
This factory creates an instance of the Portal, an AMPProtocol
|
||||
instances to use to connect
|
||||
"""
|
||||
# Initial reconnect delay in seconds.
|
||||
initialDelay = 1
|
||||
|
|
@ -139,6 +144,7 @@ class MsgPortal2Server(amp.Command):
|
|||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
|
||||
class MsgServer2Portal(amp.Command):
|
||||
"""
|
||||
Message server -> portal
|
||||
|
|
@ -151,6 +157,7 @@ class MsgServer2Portal(amp.Command):
|
|||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
|
||||
class ServerAdmin(amp.Command):
|
||||
"""
|
||||
Portal -> Server
|
||||
|
|
@ -165,6 +172,7 @@ class ServerAdmin(amp.Command):
|
|||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
|
||||
class PortalAdmin(amp.Command):
|
||||
"""
|
||||
Server -> Portal
|
||||
|
|
@ -178,6 +186,7 @@ class PortalAdmin(amp.Command):
|
|||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
|
||||
class FunctionCall(amp.Command):
|
||||
"""
|
||||
Bidirectional
|
||||
|
|
@ -202,6 +211,7 @@ loads = lambda data: pickle.loads(to_str(data))
|
|||
|
||||
MSGBUFFER = defaultdict(list)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Core AMP protocol for communication Server <-> Portal
|
||||
#------------------------------------------------------------
|
||||
|
|
@ -241,7 +251,8 @@ class AMPProtocol(amp.AMP):
|
|||
def errback(self, e, info):
|
||||
"error handler, to avoid dropping connections on server tracebacks."
|
||||
f = e.trap(Exception)
|
||||
print "AMP Error for %(info)s: %(e)s" % {'info': info, 'e': e.getErrorMessage()}
|
||||
print "AMP Error for %(info)s: %(e)s" % {'info': info,
|
||||
'e': e.getErrorMessage()}
|
||||
|
||||
def send_split_msg(self, sessid, msg, data, command):
|
||||
"""
|
||||
|
|
@ -258,14 +269,16 @@ class AMPProtocol(amp.AMP):
|
|||
datastr = dumps(data)
|
||||
nmsg, ndata = len(msg), len(datastr)
|
||||
if nmsg > MAXLEN or ndata > MAXLEN:
|
||||
msglist = [msg[i:i+MAXLEN] for i in range(0, len(msg), MAXLEN)]
|
||||
datalist = [datastr[i:i+MAXLEN] for i in range(0, len(datastr), MAXLEN)]
|
||||
msglist = [msg[i:i + MAXLEN] for i in range(0, len(msg), MAXLEN)]
|
||||
datalist = [datastr[i:i + MAXLEN]
|
||||
for i in range(0, len(datastr), MAXLEN)]
|
||||
nmsglist, ndatalist = len(msglist), len(datalist)
|
||||
if ndatalist < nmsglist:
|
||||
datalist.extend("" for i in range(nmsglist-ndatalist))
|
||||
datalist.extend("" for i in range(nmsglist - ndatalist))
|
||||
if nmsglist < ndatalist:
|
||||
msglist.extend("" for i in range(ndatalist-nmsglist))
|
||||
# we have split the msg/data into right-size chunks. Now we send it in sequence
|
||||
msglist.extend("" for i in range(ndatalist - nmsglist))
|
||||
# we have split the msg/data into right-size chunks. Now we
|
||||
# send it in sequence
|
||||
return [self.callRemote(command,
|
||||
sessid=sessid,
|
||||
msg=to_str(msg),
|
||||
|
|
@ -295,8 +308,8 @@ class AMPProtocol(amp.AMP):
|
|||
return {}
|
||||
else:
|
||||
# we have all parts. Put it all together in the right order.
|
||||
msg = "".join(t[1] for t in sorted(MSGBUFFER[sessid], key=lambda o:o[0]))
|
||||
data = "".join(t[2] for t in sorted(MSGBUFFER[sessid], key=lambda o:o[0]))
|
||||
msg = "".join(t[1] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
|
||||
data = "".join(t[2] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
|
||||
del MSGBUFFER[sessid]
|
||||
# call session hook with the data
|
||||
self.factory.server.sessions.data_in(sessid, text=msg, **loads(data))
|
||||
|
|
@ -311,13 +324,14 @@ class AMPProtocol(amp.AMP):
|
|||
try:
|
||||
return self.callRemote(MsgPortal2Server,
|
||||
sessid=sessid,
|
||||
msg=to_str(msg) if msg!=None else "",
|
||||
msg=to_str(msg) if msg is not None else "",
|
||||
ipart=0,
|
||||
nparts=1,
|
||||
data=dumps(data)).addErrback(self.errback, "MsgPortal2Server")
|
||||
except amp.TooLong:
|
||||
# the msg (or data) was too long for AMP to send. We need to send in blocks.
|
||||
return self.send_split_msg(sessid, msg, kwargs, MsgPortal2Server)
|
||||
# the msg (or data) was too long for AMP to send.
|
||||
# We need to send in blocks.
|
||||
return self.send_split_msg(sessid, msg, data, MsgPortal2Server)
|
||||
|
||||
# Server -> Portal message
|
||||
|
||||
|
|
@ -335,8 +349,8 @@ class AMPProtocol(amp.AMP):
|
|||
return {}
|
||||
else:
|
||||
# we have all parts. Put it all together in the right order.
|
||||
msg = "".join(t[1] for t in sorted(MSGBUFFER[sessid], key=lambda o:o[0]))
|
||||
data = "".join(t[2] for t in sorted(MSGBUFFER[sessid], key=lambda o:o[0]))
|
||||
msg = "".join(t[1] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
|
||||
data = "".join(t[2] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
|
||||
del MSGBUFFER[sessid]
|
||||
# call session hook with the data
|
||||
self.factory.portal.sessions.data_out(sessid, text=msg, **loads(data))
|
||||
|
|
@ -351,14 +365,14 @@ class AMPProtocol(amp.AMP):
|
|||
try:
|
||||
return self.callRemote(MsgServer2Portal,
|
||||
sessid=sessid,
|
||||
msg=to_str(msg) if msg!=None else "",
|
||||
msg=to_str(msg) if msg is not None else "",
|
||||
ipart=0,
|
||||
nparts=1,
|
||||
data=dumps(data)).addErrback(self.errback, "MsgServer2Portal")
|
||||
except amp.TooLong:
|
||||
# the msg (or data) was too long for AMP to send. We need to send in blocks.
|
||||
return self.send_split_msg(sessid, msg, kwargs, MsgServer2Portal)
|
||||
|
||||
# the msg (or data) was too long for AMP to send.
|
||||
# We need to send in blocks.
|
||||
return self.send_split_msg(sessid, msg, data, MsgServer2Portal)
|
||||
|
||||
# Server administration from the Portal side
|
||||
def amp_server_admin(self, sessid, operation, data):
|
||||
|
|
@ -372,17 +386,18 @@ class AMPProtocol(amp.AMP):
|
|||
|
||||
#print "serveradmin (server side):", sessid, ord(operation), data
|
||||
|
||||
if operation == PCONN: #portal_session_connect
|
||||
if operation == PCONN: # portal_session_connect
|
||||
# create a new session and sync it
|
||||
server_sessionhandler.portal_connect(data)
|
||||
|
||||
elif operation == PDISCONN: #'portal_session_disconnect'
|
||||
elif operation == PDISCONN: # portal_session_disconnect
|
||||
# session closed from portal side
|
||||
self.factory.server.sessions.portal_disconnect(sessid)
|
||||
|
||||
elif operation == PSYNC: #'portal_session_sync'
|
||||
# force a resync of sessions when portal reconnects to server (e.g. after a server reboot)
|
||||
# the data kwarg contains a dict {sessid: {arg1:val1,...}} representing the attributes
|
||||
elif operation == PSYNC: # portal_session_sync
|
||||
# force a resync of sessions when portal reconnects to server
|
||||
# (e.g. after a server reboot) the data kwarg contains a dict
|
||||
# {sessid: {arg1:val1,...}} representing the attributes
|
||||
# to sync for each session.
|
||||
server_sessionhandler.portal_session_sync(data)
|
||||
else:
|
||||
|
|
@ -414,23 +429,23 @@ class AMPProtocol(amp.AMP):
|
|||
portal_sessionhandler = self.factory.portal.sessions
|
||||
|
||||
#print "portaladmin (portal side):", sessid, ord(operation), data
|
||||
if operation == SLOGIN: # 'server_session_login'
|
||||
if operation == SLOGIN: # server_session_login
|
||||
# a session has authenticated; sync it.
|
||||
portal_sessionhandler.server_logged_in(sessid, data)
|
||||
|
||||
elif operation == SDISCONN: #'server_session_disconnect'
|
||||
elif operation == SDISCONN: # server_session_disconnect
|
||||
# the server is ordering to disconnect the session
|
||||
portal_sessionhandler.server_disconnect(sessid, reason=data)
|
||||
|
||||
elif operation == SDISCONNALL: #'server_session_disconnect_all'
|
||||
elif operation == SDISCONNALL: # server_session_disconnect_all
|
||||
# server orders all sessions to disconnect
|
||||
portal_sessionhandler.server_disconnect_all(reason=data)
|
||||
|
||||
elif operation == SSHUTD: #server_shutdown'
|
||||
elif operation == SSHUTD: # server_shutdown
|
||||
# the server orders the portal to shut down
|
||||
self.factory.portal.shutdown(restart=False)
|
||||
|
||||
elif operation == SSYNC: #'server_session_sync'
|
||||
elif operation == SSYNC: # server_session_sync
|
||||
# server wants to save session data to the portal, maybe because
|
||||
# it's about to shut down.
|
||||
portal_sessionhandler.server_session_sync(data)
|
||||
|
|
@ -465,25 +480,28 @@ class AMPProtocol(amp.AMP):
|
|||
result = variable_from_module(module, function)(*args, **kwargs)
|
||||
|
||||
if isinstance(result, Deferred):
|
||||
# if result is a deferred, attach handler to properly wrap the return value
|
||||
result.addCallback(lambda r: {"result":dumps(r)})
|
||||
# if result is a deferred, attach handler to properly
|
||||
# wrap the return value
|
||||
result.addCallback(lambda r: {"result": dumps(r)})
|
||||
return result
|
||||
else:
|
||||
return {'result':dumps(result)}
|
||||
return {'result': dumps(result)}
|
||||
FunctionCall.responder(amp_function_call)
|
||||
|
||||
|
||||
def call_remote_FunctionCall(self, modulepath, functionname, *args, **kwargs):
|
||||
"""
|
||||
Access method called by either process. This will call an arbitrary function
|
||||
on the other process (On Portal if calling from Server and vice versa).
|
||||
Access method called by either process. This will call an arbitrary
|
||||
function on the other process (On Portal if calling from Server and
|
||||
vice versa).
|
||||
|
||||
Inputs:
|
||||
modulepath (str) - python path to module holding function to call
|
||||
functionname (str) - name of function in given module
|
||||
*args, **kwargs will be used as arguments/keyword args for the remote function call
|
||||
*args, **kwargs will be used as arguments/keyword args for the
|
||||
remote function call
|
||||
Returns:
|
||||
A deferred that fires with the return value of the remote function call
|
||||
A deferred that fires with the return value of the remote
|
||||
function call
|
||||
"""
|
||||
return self.callRemote(FunctionCall,
|
||||
module=modulepath,
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ Central caching module.
|
|||
"""
|
||||
|
||||
from sys import getsizeof
|
||||
import os, threading
|
||||
import os
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
|
||||
from django.core.cache import get_cache
|
||||
from src.server.models import ServerConfig
|
||||
from src.utils.utils import uses_database, to_str, get_evennia_pids
|
||||
|
||||
|
|
@ -35,6 +35,7 @@ if uses_database("mysql") and ServerConfig.objects.get_mysql_db_version() < '5.6
|
|||
else:
|
||||
_DATESTRING = "%Y:%m:%d-%H:%M:%S:%f"
|
||||
|
||||
|
||||
def hashid(obj, suffix=""):
|
||||
"""
|
||||
Returns a per-class unique hash that combines the object's
|
||||
|
|
@ -59,11 +60,15 @@ def hashid(obj, suffix=""):
|
|||
# rely on memory adressing in this case.
|
||||
date, idnum = "InMemory", id(obj)
|
||||
if not idnum or not date:
|
||||
# this will happen if setting properties on an object which is not yet saved
|
||||
# this will happen if setting properties on an object which
|
||||
# is not yet saved
|
||||
return None
|
||||
# we have to remove the class-name's space, for eventual use
|
||||
# of memcached
|
||||
hid = "%s-%s-#%s" % (_GA(obj, "__class__"), date, idnum)
|
||||
hid = hid.replace(" ", "") # we have to remove the class-name's space, for memcached's sake
|
||||
# we cache the object part of the hashid to avoid too many object lookups
|
||||
hid = hid.replace(" ", "")
|
||||
# we cache the object part of the hashid to avoid too many
|
||||
# object lookups
|
||||
_SA(obj, "_hashid", hid)
|
||||
# build the complete hashid
|
||||
hid = "%s%s" % (hid, suffix)
|
||||
|
|
@ -84,8 +89,9 @@ def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwarg
|
|||
"""
|
||||
Called at the beginning of the field save operation. The save method
|
||||
must be called with the update_fields keyword in order to be most efficient.
|
||||
This method should NOT save; rather it is the save() that triggers this function.
|
||||
Its main purpose is to allow to plug-in a save handler and oob handlers.
|
||||
This method should NOT save; rather it is the save() that triggers this
|
||||
function. Its main purpose is to allow to plug-in a save handler and oob
|
||||
handlers.
|
||||
"""
|
||||
if raw:
|
||||
return
|
||||
|
|
@ -102,12 +108,14 @@ def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwarg
|
|||
if callable(handler):
|
||||
handler()
|
||||
|
||||
|
||||
def field_post_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
|
||||
"""
|
||||
Called at the beginning of the field save operation. The save method
|
||||
must be called with the update_fields keyword in order to be most efficient.
|
||||
This method should NOT save; rather it is the save() that triggers this function.
|
||||
Its main purpose is to allow to plug-in a save handler and oob handlers.
|
||||
This method should NOT save; rather it is the save() that triggers this
|
||||
function. Its main purpose is to allow to plug-in a save handler and oob
|
||||
handlers.
|
||||
"""
|
||||
if raw:
|
||||
return
|
||||
|
|
@ -127,70 +135,6 @@ def field_post_save(sender, instance=None, update_fields=None, raw=False, **kwar
|
|||
if trackerhandler:
|
||||
trackerhandler.update(fieldname, _GA(instance, fieldname))
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Attr cache - caching the attribute objects related to a given object to
|
||||
# avoid lookups more than necessary (this makes Attributes en par in speed
|
||||
# to any property).
|
||||
#------------------------------------------------------------
|
||||
|
||||
## connected to m2m_changed signal in respective model class
|
||||
#def post_attr_update(sender, **kwargs):
|
||||
# "Called when the many2many relation changes (NOT when updating the value of an Attribute!)"
|
||||
# obj = kwargs['instance']
|
||||
# model = kwargs['model']
|
||||
# action = kwargs['action']
|
||||
# if kwargs['reverse']:
|
||||
# # the reverse relation changed (the Attribute itself was acted on)
|
||||
# pass
|
||||
# else:
|
||||
# # forward relation changed (the Object holding the Attribute m2m field)
|
||||
# if not kwargs["pk_set"]:
|
||||
# return
|
||||
# if action == "post_add":
|
||||
# # cache all added objects
|
||||
# for attr_id in kwargs["pk_set"]:
|
||||
# attr_obj = model.objects.get(pk=attr_id)
|
||||
# set_attr_cache(obj, _GA(attr_obj, "db_key"), attr_obj)
|
||||
# elif action == "post_remove":
|
||||
# # obj.db_attributes.remove(attr) was called
|
||||
# for attr_id in kwargs["pk_set"]:
|
||||
# attr_obj = model.objects.get(pk=attr_id)
|
||||
# del_attr_cache(obj, _GA(attr_obj, "db_key"))
|
||||
# attr_obj.delete()
|
||||
# elif action == "post_clear":
|
||||
# # obj.db_attributes.clear() was called
|
||||
# clear_obj_attr_cache(obj)
|
||||
#
|
||||
#
|
||||
## attr cache - this is only left as deprecated cache
|
||||
#
|
||||
#def get_attr_cache(obj, attrname):
|
||||
# "Called by getting attribute"
|
||||
# hid = hashid(obj, "-%s" % attrname)
|
||||
# return _ATTR_CACHE.get(hid, None)
|
||||
#
|
||||
#def set_attr_cache(obj, attrname, attrobj):
|
||||
# "Set the attr cache manually; this can be used to update"
|
||||
# global _ATTR_CACHE
|
||||
# hid = hashid(obj, "-%s" % attrname)
|
||||
# _ATTR_CACHE[hid] = attrobj
|
||||
#
|
||||
#def del_attr_cache(obj, attrname):
|
||||
# "Del attribute cache"
|
||||
# global _ATTR_CACHE
|
||||
# hid = hashid(obj, "-%s" % attrname)
|
||||
# if hid in _ATTR_CACHE:
|
||||
# del _ATTR_CACHE[hid]
|
||||
#
|
||||
#def flush_attr_cache():
|
||||
# "Clear attribute cache"
|
||||
# global _ATTR_CACHE
|
||||
# _ATTR_CACHE = {}
|
||||
#
|
||||
#def clear_obj_attr_cache(obj):
|
||||
# global _ATTR_CACHE
|
||||
# hid = hashid(obj)
|
||||
# _ATTR_CACHE = {key:value for key, value in _ATTR_CACHE if not key.startswith(hid)}
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Property cache - this is a generic cache for properties stored on models.
|
||||
|
|
@ -203,12 +147,14 @@ def get_prop_cache(obj, propname):
|
|||
hid = hashid(obj, "-%s" % propname)
|
||||
return _PROP_CACHE[hid].get(propname, None) if hid else None
|
||||
|
||||
|
||||
def set_prop_cache(obj, propname, propvalue):
|
||||
"Set property cache"
|
||||
hid = hashid(obj, "-%s" % propname)
|
||||
if hid:
|
||||
_PROP_CACHE[hid][propname] = propvalue
|
||||
|
||||
|
||||
def del_prop_cache(obj, propname):
|
||||
"Delete element from property cache"
|
||||
hid = hashid(obj, "-%s" % propname)
|
||||
|
|
@ -216,11 +162,12 @@ def del_prop_cache(obj, propname):
|
|||
if propname in _PROP_CACHE[hid]:
|
||||
del _PROP_CACHE[hid][propname]
|
||||
|
||||
|
||||
def flush_prop_cache():
|
||||
"Clear property cache"
|
||||
global _PROP_CACHE
|
||||
_PROP_CACHE = defaultdict(dict)
|
||||
#_PROP_CACHE.clear()
|
||||
|
||||
|
||||
def get_cache_sizes():
|
||||
"""
|
||||
|
|
@ -229,8 +176,8 @@ def get_cache_sizes():
|
|||
global _ATTR_CACHE, _PROP_CACHE
|
||||
attr_n = len(_ATTR_CACHE)
|
||||
attr_mb = sum(getsizeof(obj) for obj in _ATTR_CACHE) / 1024.0
|
||||
field_n = 0 #sum(len(dic) for dic in _FIELD_CACHE.values())
|
||||
field_mb = 0 # sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _FIELD_CACHE.values()) / 1024.0
|
||||
field_n = 0 # sum(len(dic) for dic in _FIELD_CACHE.values())
|
||||
field_mb = 0 # sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _FIELD_CACHE.values()) / 1024.0
|
||||
prop_n = sum(len(dic) for dic in _PROP_CACHE.values())
|
||||
prop_mb = sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _PROP_CACHE.values()) / 1024.0
|
||||
return (attr_n, attr_mb), (field_n, field_mb), (prop_n, prop_mb)
|
||||
|
|
|
|||
|
|
@ -7,16 +7,13 @@ Everything starts at handle_setup()
|
|||
"""
|
||||
|
||||
import django
|
||||
from django.core import management
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from src.server.models import ServerConfig
|
||||
from src.help.models import HelpEntry
|
||||
from src.utils import create
|
||||
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
def create_config_values():
|
||||
"""
|
||||
Creates the initial config values.
|
||||
|
|
@ -24,6 +21,7 @@ def create_config_values():
|
|||
ServerConfig.objects.conf("site_name", settings.SERVERNAME)
|
||||
ServerConfig.objects.conf("idle_timeout", settings.IDLE_TIMEOUT)
|
||||
|
||||
|
||||
def get_god_player():
|
||||
"""
|
||||
Creates the god user.
|
||||
|
|
@ -32,13 +30,15 @@ def get_god_player():
|
|||
try:
|
||||
god_player = PlayerDB.objects.get(id=1)
|
||||
except PlayerDB.DoesNotExist:
|
||||
txt = "\n\nNo superuser exists yet. The superuser is the 'owner' account on the"
|
||||
txt += "\nEvennia server. Create a new superuser using the command"
|
||||
txt = "\n\nNo superuser exists yet. The superuser is the 'owner'"
|
||||
txt += "\account on the Evennia server. Create a new superuser using"
|
||||
txt += "\nthe command"
|
||||
txt += "\n\n python manage.py createsuperuser"
|
||||
txt += "\n\nFollow the prompts, then restart the server."
|
||||
raise Exception(txt)
|
||||
return god_player
|
||||
|
||||
|
||||
def create_objects():
|
||||
"""
|
||||
Creates the #1 player and Limbo room.
|
||||
|
|
@ -54,18 +54,23 @@ def create_objects():
|
|||
# mud-specific settings for the PlayerDB object.
|
||||
player_typeclass = settings.BASE_PLAYER_TYPECLASS
|
||||
|
||||
# run all creation hooks on god_player (we must do so manually since the manage.py command does not)
|
||||
# run all creation hooks on god_player (we must do so manually
|
||||
# since the manage.py command does not)
|
||||
god_player.typeclass_path = player_typeclass
|
||||
god_player.basetype_setup()
|
||||
god_player.at_player_creation()
|
||||
god_player.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all()")
|
||||
god_player.permissions.add("Immortals") # this is necessary for quelling to work correctly.
|
||||
# this is necessary for quelling to work correctly.
|
||||
god_player.permissions.add("Immortals")
|
||||
|
||||
# Limbo is the default "nowhere" starting room
|
||||
|
||||
# Create the in-game god-character for player #1 and set it to exist in Limbo.
|
||||
# Create the in-game god-character for player #1 and set
|
||||
# it to exist in Limbo.
|
||||
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
god_character = create.create_object(character_typeclass, key=god_player.username, nohome=True)
|
||||
god_character = create.create_object(character_typeclass,
|
||||
key=god_player.username, nohome=True)
|
||||
print "god_character:", character_typeclass, god_character, god_character.cmdset.all()
|
||||
|
||||
god_character.id = 1
|
||||
god_character.db.desc = _('This is User #1.')
|
||||
|
|
@ -81,10 +86,13 @@ def create_objects():
|
|||
limbo_obj = create.create_object(room_typeclass, _('Limbo'), nohome=True)
|
||||
limbo_obj.id = 2
|
||||
string = " ".join([
|
||||
"Welcome to your new {wEvennia{n-based game. From here you are ready to begin development.",
|
||||
"Visit http://evennia.com if you should need help or would like to participate in community discussions.",
|
||||
"If you are logged in as User #1 you can create a demo/tutorial area with '@batchcommand contrib.tutorial_world.build'.",
|
||||
"Log out and create a new non-admin account at the login screen to play the tutorial properly."])
|
||||
"Welcome to your new {wEvennia{n-based game. From here you are ready",
|
||||
"to begin development. Visit http://evennia.com if you should need",
|
||||
"help or would like to participate in community discussions. If you",
|
||||
"are logged in as User #1 you can create a demo/tutorial area with",
|
||||
"'@batchcommand contrib.tutorial_world.build'. Log out and create",
|
||||
"a new non-admin account at the login screen to play the tutorial",
|
||||
"properly."])
|
||||
string = _(string)
|
||||
limbo_obj.db.desc = string
|
||||
limbo_obj.save()
|
||||
|
|
@ -96,6 +104,7 @@ def create_objects():
|
|||
if not god_character.home:
|
||||
god_character.home = limbo_obj
|
||||
|
||||
|
||||
def create_channels():
|
||||
"""
|
||||
Creates some sensible default channels.
|
||||
|
|
@ -112,20 +121,21 @@ def create_channels():
|
|||
key3, aliases, desc, locks = settings.CHANNEL_CONNECTINFO
|
||||
cchan = create.create_channel(key3, aliases, desc, locks=locks)
|
||||
|
||||
|
||||
# TODO: postgresql-psycopg2 has a strange error when trying to connect the user
|
||||
# to the default channels. It works fine from inside the game, but not from
|
||||
# the initial startup. We are temporarily bypassing the problem with the following
|
||||
# fix. See Evennia Issue 151.
|
||||
if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASE_ENGINE == "postgresql_psycopg2")
|
||||
# TODO: postgresql-psycopg2 has a strange error when trying to
|
||||
# connect the user to the default channels. It works fine from inside
|
||||
# the game, but not from the initial startup. We are temporarily bypassing
|
||||
# the problem with the following fix. See Evennia Issue 151.
|
||||
if ((".".join(str(i) for i in django.VERSION) < "1.2"
|
||||
and settings.DATABASE_ENGINE == "postgresql_psycopg2")
|
||||
or (hasattr(settings, 'DATABASES')
|
||||
and settings.DATABASES.get("default", {}).get('ENGINE', None)
|
||||
== 'django.db.backends.postgresql_psycopg2')):
|
||||
warning = """
|
||||
PostgreSQL-psycopg2 compatability fix:
|
||||
The in-game channels %s, %s and %s were created,
|
||||
but the superuser was not yet connected to them. Please use in-game commands to
|
||||
connect Player #1 to those channels when first logging in.
|
||||
but the superuser was not yet connected to them. Please use in
|
||||
game commands to onnect Player #1 to those channels when first
|
||||
logging in.
|
||||
""" % (key1, key2, key3)
|
||||
print warning
|
||||
return
|
||||
|
|
@ -137,6 +147,7 @@ def create_channels():
|
|||
PlayerChannelConnection.objects.create_connection(goduser, ichan)
|
||||
PlayerChannelConnection.objects.create_connection(goduser, cchan)
|
||||
|
||||
|
||||
def create_system_scripts():
|
||||
"""
|
||||
Setup the system repeat scripts. They are automatically started
|
||||
|
|
@ -152,11 +163,10 @@ def create_system_scripts():
|
|||
script2 = create.create_script(scripts.ValidateScripts)
|
||||
# update the channel handler to make sure it's in sync
|
||||
script3 = create.create_script(scripts.ValidateChannelHandler)
|
||||
# clear the attribute cache regularly
|
||||
#script4 = create.create_script(scripts.ClearAttributeCache)
|
||||
if not script1 or not script2 or not script3:# or not script4:
|
||||
if not script1 or not script2 or not script3:
|
||||
print " Error creating system scripts."
|
||||
|
||||
|
||||
def start_game_time():
|
||||
"""
|
||||
This starts a persistent script that keeps track of the
|
||||
|
|
@ -168,6 +178,7 @@ def start_game_time():
|
|||
from src.utils import gametime
|
||||
gametime.init_gametime()
|
||||
|
||||
|
||||
def create_admin_media_links():
|
||||
"""
|
||||
This traverses to src/web/media and tries to create a symbolic
|
||||
|
|
@ -179,7 +190,8 @@ def create_admin_media_links():
|
|||
since the django install may be at different locations depending
|
||||
on system.
|
||||
"""
|
||||
import django, os
|
||||
import django
|
||||
import os
|
||||
|
||||
if django.get_version() < 1.4:
|
||||
dpath = os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
|
||||
|
|
@ -202,6 +214,7 @@ def create_admin_media_links():
|
|||
else:
|
||||
print " Admin-media files should be copied manually to ADMIN_MEDIA_ROOT."
|
||||
|
||||
|
||||
def at_initial_setup():
|
||||
"""
|
||||
Custom hook for users to overload some or all parts of the initial
|
||||
|
|
@ -220,6 +233,7 @@ def at_initial_setup():
|
|||
if mod.__dict__.get("at_initial_setup", None):
|
||||
mod.at_initial_setup()
|
||||
|
||||
|
||||
def reset_server():
|
||||
"""
|
||||
We end the initialization by resetting the server. This
|
||||
|
|
@ -231,6 +245,7 @@ def reset_server():
|
|||
print " Initial setup complete. Restarting Server once."
|
||||
SESSIONS.server.shutdown(mode='reset')
|
||||
|
||||
|
||||
def handle_setup(last_step):
|
||||
"""
|
||||
Main logic for the module. It allows for restarting
|
||||
|
|
@ -242,7 +257,7 @@ def handle_setup(last_step):
|
|||
# this means we don't need to handle setup since
|
||||
# it already ran sucessfully once.
|
||||
return
|
||||
elif last_step == None:
|
||||
elif last_step is None:
|
||||
# config doesn't exist yet. First start of server
|
||||
last_step = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ Custom manager for ServerConfig objects.
|
|||
"""
|
||||
from django.db import models
|
||||
|
||||
|
||||
class ServerConfigManager(models.Manager):
|
||||
"""
|
||||
This ServerConfigManager implements methods for searching
|
||||
|
|
@ -24,16 +25,16 @@ class ServerConfigManager(models.Manager):
|
|||
"""
|
||||
if not key:
|
||||
return self.all()
|
||||
elif delete == True:
|
||||
elif delete is True:
|
||||
for conf in self.filter(db_key=key):
|
||||
conf.delete()
|
||||
elif value != None:
|
||||
elif value is not None:
|
||||
conf = self.filter(db_key=key)
|
||||
if conf:
|
||||
conf = conf[0]
|
||||
else:
|
||||
conf = self.model(db_key=key)
|
||||
conf.value = value # this will pickle
|
||||
conf.value = value # this will pickle
|
||||
else:
|
||||
conf = self.filter(db_key=key)
|
||||
if not conf:
|
||||
|
|
@ -42,7 +43,8 @@ class ServerConfigManager(models.Manager):
|
|||
|
||||
def get_mysql_db_version(self):
|
||||
"""
|
||||
This is a helper method for getting the version string of a mysql database.
|
||||
This is a helper method for getting the version string
|
||||
of a mysql database.
|
||||
"""
|
||||
from django.db import connection
|
||||
conn = connection.cursor()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from src.utils.idmapper.models import SharedMemoryModel
|
|||
from src.utils import logger, utils
|
||||
from src.server.manager import ServerConfigManager
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# ServerConfig
|
||||
|
|
@ -61,11 +62,13 @@ class ServerConfig(SharedMemoryModel):
|
|||
def __key_get(self):
|
||||
"Getter. Allows for value = self.key"
|
||||
return self.db_key
|
||||
|
||||
#@key.setter
|
||||
def __key_set(self, value):
|
||||
"Setter. Allows for self.key = value"
|
||||
self.db_key = value
|
||||
self.save()
|
||||
|
||||
#@key.deleter
|
||||
def __key_del(self):
|
||||
"Deleter. Allows for del self.key. Deletes entry."
|
||||
|
|
@ -77,6 +80,7 @@ class ServerConfig(SharedMemoryModel):
|
|||
def __value_get(self):
|
||||
"Getter. Allows for value = self.value"
|
||||
return pickle.loads(str(self.db_value))
|
||||
|
||||
#@value.setter
|
||||
def __value_set(self, value):
|
||||
"Setter. Allows for self.value = value"
|
||||
|
|
@ -86,6 +90,7 @@ class ServerConfig(SharedMemoryModel):
|
|||
return
|
||||
self.db_value = pickle.dumps(value)
|
||||
self.save()
|
||||
|
||||
#@value.deleter
|
||||
def __value_del(self):
|
||||
"Deleter. Allows for del self.value. Deletes entry."
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ from django.conf import settings
|
|||
from src.utils.utils import to_str
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
_NA = lambda o: (None, "N/A") # not implemented
|
||||
_NA = lambda o: (None, "N/A") # not implemented
|
||||
|
||||
# mapper for which properties may be requested/sent to the client and how to do so.
|
||||
# Each entry should define a function that returns two values - the name of the
|
||||
# propertye being returned (a string) and the value. If tracking database fields,
|
||||
# make sure to enter the full database field name (e.g. db_key rather than just key)
|
||||
# since the db_ prefix is used by trackers to know which tracking mechanism to activate.
|
||||
# mapper for which properties may be requested/sent to the client and how
|
||||
# to do so. Each entry should define a function that returns two values - the
|
||||
# name of the property being returned (a string) and the value. If tracking
|
||||
# database fields, make sure to enter the full database field name (e.g.
|
||||
# db_key rather than just key) since the db_ prefix is used by trackers
|
||||
# to know which tracking mechanism to activate.
|
||||
|
||||
OOB_SENDABLE = {
|
||||
## General
|
||||
|
|
@ -97,13 +98,16 @@ class TrackerBase(object):
|
|||
"""
|
||||
def __init__(self, oobhandler, *args, **kwargs):
|
||||
self.oobhandler = oobhandler
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
"Called by tracked objects"
|
||||
pass
|
||||
|
||||
def at_remove(self, *args, **kwargs):
|
||||
"Called when tracker is removed"
|
||||
pass
|
||||
|
||||
|
||||
class OOBFieldTracker(TrackerBase):
|
||||
"""
|
||||
Tracker that passively sends data to a stored sessid whenever
|
||||
|
|
@ -127,7 +131,9 @@ class OOBFieldTracker(TrackerBase):
|
|||
new_value = new_value.key
|
||||
except AttributeError:
|
||||
new_value = to_str(new_value, force_string=True)
|
||||
self.oobhandler.msg(self.sessid, "report", self.fieldname, new_value, *args, **kwargs)
|
||||
self.oobhandler.msg(self.sessid, "report", self.fieldname,
|
||||
new_value, *args, **kwargs)
|
||||
|
||||
|
||||
class OOBAttributeTracker(TrackerBase):
|
||||
"""
|
||||
|
|
@ -136,13 +142,13 @@ class OOBAttributeTracker(TrackerBase):
|
|||
we instead store the name of the attribute to return.
|
||||
"""
|
||||
def __init__(self, oobhandler, fieldname, sessid, attrname, *args, **kwargs):
|
||||
"""
|
||||
attrname - name of attribute to track
|
||||
sessid - sessid of session to report to
|
||||
"""
|
||||
self.oobhandler = oobhandler
|
||||
self.attrname = attrname
|
||||
self.sessid = sessid
|
||||
"""
|
||||
attrname - name of attribute to track
|
||||
sessid - sessid of session to report to
|
||||
"""
|
||||
self.oobhandler = oobhandler
|
||||
self.attrname = attrname
|
||||
self.sessid = sessid
|
||||
|
||||
def update(self, new_value, *args, **kwargs):
|
||||
"Called by cache when attribute's db_value field updates"
|
||||
|
|
@ -152,6 +158,7 @@ class OOBAttributeTracker(TrackerBase):
|
|||
new_value = to_str(new_value, force_string=True)
|
||||
self.oobhandler.msg(self.sessid, "report", self.attrname, new_value, *args, **kwargs)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# OOB commands
|
||||
# This defines which internal server commands the OOB handler
|
||||
|
|
@ -173,31 +180,51 @@ def oob_error(oobhandler, session, errmsg, *args, **kwargs):
|
|||
occurs already at the execution stage (such as the oob function
|
||||
not being recognized or having the wrong args etc).
|
||||
"""
|
||||
session.msg(oob=("send", {"ERROR":errmsg}))
|
||||
session.msg(oob=("send", {"ERROR": errmsg}))
|
||||
|
||||
|
||||
def LIST(oobhandler, session, mode, *args, **kwargs):
|
||||
"""
|
||||
List available properties. Mode is the type of information
|
||||
desired:
|
||||
"COMMANDS" Request an array of commands supported by the server.
|
||||
"LISTS" Request an array of lists supported by the server.
|
||||
"CONFIGURABLE_VARIABLES" Request an array of variables the client can configure.
|
||||
"REPORTABLE_VARIABLES" Request an array of variables the server will report.
|
||||
"REPORTED_VARIABLES" Request an array of variables currently being reported.
|
||||
"SENDABLE_VARIABLES" Request an array of variables the server will send.
|
||||
"COMMANDS" Request an array of commands supported
|
||||
by the server.
|
||||
"LISTS" Request an array of lists supported
|
||||
by the server.
|
||||
"CONFIGURABLE_VARIABLES" Request an array of variables the client
|
||||
can configure.
|
||||
"REPORTABLE_VARIABLES" Request an array of variables the server
|
||||
will report.
|
||||
"REPORTED_VARIABLES" Request an array of variables currently
|
||||
being reported.
|
||||
"SENDABLE_VARIABLES" Request an array of variables the server
|
||||
will send.
|
||||
"""
|
||||
mode = mode.upper()
|
||||
# the first return argument is treated by the msdp protocol as the name of the msdp array to return
|
||||
# the first return argument is treated by the msdp protocol as the
|
||||
# name of the msdp array to return
|
||||
if mode == "COMMANDS":
|
||||
session.msg(oob=("list", ("COMMANDS", "LIST", "REPORT", "UNREPORT", "SEND"))) # RESET
|
||||
session.msg(oob=("list", ("COMMANDS",
|
||||
"LIST",
|
||||
"REPORT",
|
||||
"UNREPORT",
|
||||
# "RESET",
|
||||
"SEND")))
|
||||
elif mode == "LISTS":
|
||||
session.msg(oob=("list", ("LISTS", "REPORTABLE_VARIABLES","REPORTED_VARIABLES", "SENDABLE_VARIABLES"))) #CONFIGURABLE_VARIABLES
|
||||
session.msg(oob=("list", ("LISTS",
|
||||
"REPORTABLE_VARIABLES",
|
||||
"REPORTED_VARIABLES",
|
||||
# "CONFIGURABLE_VARIABLES",
|
||||
"SENDABLE_VARIABLES")))
|
||||
elif mode == "REPORTABLE_VARIABLES":
|
||||
session.msg(oob=("list", ("REPORTABLE_VARIABLES",) + tuple(key for key in OOB_REPORTABLE.keys())))
|
||||
session.msg(oob=("list", ("REPORTABLE_VARIABLES",) +
|
||||
tuple(key for key in OOB_REPORTABLE.keys())))
|
||||
elif mode == "REPORTED_VARIABLES":
|
||||
session.msg(oob=("list", ("REPORTED_VARIABLES",) + tuple(oobhandler.get_all_tracked(session))))
|
||||
session.msg(oob=("list", ("REPORTED_VARIABLES",) +
|
||||
tuple(oobhandler.get_all_tracked(session))))
|
||||
elif mode == "SENDABLE_VARIABLES":
|
||||
session.msg(oob=("list", ("SENDABLE_VARIABLES",) + tuple(key for key in OOB_REPORTABLE.keys())))
|
||||
session.msg(oob=("list", ("SENDABLE_VARIABLES",) +
|
||||
tuple(key for key in OOB_REPORTABLE.keys())))
|
||||
#elif mode == "CONFIGURABLE_VARIABLES":
|
||||
# pass
|
||||
else:
|
||||
|
|
@ -221,6 +248,7 @@ def send(oobhandler, session, *args, **kwargs):
|
|||
# return result
|
||||
session.msg(oob=("send", ret))
|
||||
|
||||
|
||||
def report(oobhandler, session, *args, **kwargs):
|
||||
"""
|
||||
This creates a tracker instance to track the data given in *args.
|
||||
|
|
@ -232,9 +260,12 @@ def report(oobhandler, session, *args, **kwargs):
|
|||
key, val = OOB_REPORTABLE.get(name, _NA)(obj)
|
||||
if key:
|
||||
if key.startswith("db_"):
|
||||
oobhandler.track_field(obj, session.sessid, key, OOBFieldTracker)
|
||||
else: # assume attribute
|
||||
oobhandler.track_attribute(obj, session.sessid, key, OOBAttributeTracker)
|
||||
oobhandler.track_field(obj, session.sessid,
|
||||
key, OOBFieldTracker)
|
||||
else: # assume attribute
|
||||
oobhandler.track_attribute(obj, session.sessid,
|
||||
key, OOBAttributeTracker)
|
||||
|
||||
|
||||
def unreport(oobhandler, session, vartype="prop", *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -248,6 +279,6 @@ def unreport(oobhandler, session, vartype="prop", *args, **kwargs):
|
|||
if key:
|
||||
if key.startswith("db_"):
|
||||
oobhandler.untrack_field(obj, session.sessid, key)
|
||||
else: # assume attribute
|
||||
else: # assume attribute
|
||||
oobhandler.untrack_attribute(obj, session.sessid, key)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@ The OOBHandler is called directly by out-of-band protocols. It supplies three
|
|||
pieces of functionality:
|
||||
|
||||
function execution - the oob protocol can execute a function directly on
|
||||
the server. Only functions specified in settings.OOB_PLUGIN_MODULE.OOB_FUNCS
|
||||
are valid for this use.
|
||||
repeat func execution - the oob protocol can request a given function be executed repeatedly
|
||||
at a regular interval.
|
||||
tracking - the oob protocol can request Evennia to track changes to fields on
|
||||
objects, as well as changes in Attributes. This is done by dynamically adding
|
||||
tracker-objects on entities. The behaviour of those objects can be customized
|
||||
via settings.OOB_PLUGIN_MODULE
|
||||
the server. Only functions specified in
|
||||
settings.OOB_PLUGIN_MODULE.OOB_FUNCS are valid
|
||||
for this use.
|
||||
repeat func execution - the oob protocol can request a given function be
|
||||
executed repeatedly at a regular interval.
|
||||
tracking - the oob protocol can request Evennia to track changes to
|
||||
fields on objects, as well as changes in Attributes. This is
|
||||
done by dynamically adding tracker-objects on entities. The
|
||||
behaviour of those objects can be customized via
|
||||
settings.OOB_PLUGIN_MODULE
|
||||
|
||||
oob functions have the following call signature:
|
||||
function(caller, *args, **kwargs)
|
||||
|
|
@ -30,7 +32,7 @@ from src.scripts.scripts import Script
|
|||
from src.utils.create import create_script
|
||||
from src.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj, unpack_dbobj
|
||||
from src.utils import logger
|
||||
from src.utils.utils import all_from_module, to_str, is_iter, make_iter
|
||||
from src.utils.utils import all_from_module
|
||||
|
||||
_SA = object.__setattr__
|
||||
_GA = object.__getattribute__
|
||||
|
|
@ -50,14 +52,18 @@ class TrackerHandler(object):
|
|||
"""
|
||||
def __init__(self, obj):
|
||||
"""
|
||||
This is initiated and stored on the object as a property _trackerhandler.
|
||||
This is initiated and stored on the object as a
|
||||
property _trackerhandler.
|
||||
"""
|
||||
try: obj = obj.dbobj
|
||||
except AttributeError: pass
|
||||
try:
|
||||
obj = obj.dbobj
|
||||
except AttributeError:
|
||||
pass
|
||||
self.obj = obj
|
||||
self.ntrackers = 0
|
||||
# initiate store only with valid on-object fieldnames
|
||||
self.tracktargets = dict((key, {}) for key in _GA(_GA(self.obj, "_meta"), "get_all_field_names")())
|
||||
self.tracktargets = dict((key, {})
|
||||
for key in _GA(_GA(self.obj, "_meta"), "get_all_field_names")())
|
||||
|
||||
def add(self, fieldname, tracker):
|
||||
"""
|
||||
|
|
@ -95,19 +101,23 @@ class TrackerHandler(object):
|
|||
except Exception:
|
||||
logger.log_trace()
|
||||
|
||||
|
||||
class TrackerBase(object):
|
||||
"""
|
||||
Base class for OOB Tracker objects.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
"Called by tracked objects"
|
||||
pass
|
||||
|
||||
def at_remove(self, *args, **kwargs):
|
||||
"Called when tracker is removed"
|
||||
pass
|
||||
|
||||
|
||||
class _RepeaterScript(Script):
|
||||
"""
|
||||
Repeating and subscription-enabled script for triggering OOB
|
||||
|
|
@ -117,7 +127,7 @@ class _RepeaterScript(Script):
|
|||
"Called when script is initialized"
|
||||
self.key = "oob_func"
|
||||
self.desc = "OOB functionality script"
|
||||
self.persistent = False #oob scripts should always be non-persistent
|
||||
self.persistent = False # oob scripts should always be non-persistent
|
||||
self.ndb.subscriptions = {}
|
||||
|
||||
def at_repeat(self):
|
||||
|
|
@ -142,11 +152,12 @@ class _RepeaterScript(Script):
|
|||
"""
|
||||
self.ndb.subscriptions.pop(store_key, None)
|
||||
|
||||
|
||||
class _RepeaterPool(object):
|
||||
"""
|
||||
This maintains a pool of _RepeaterScript scripts, ordered one per interval. It
|
||||
will automatically cull itself once a given interval's script has no more
|
||||
subscriptions.
|
||||
This maintains a pool of _RepeaterScript scripts, ordered one per
|
||||
interval. It will automatically cull itself once a given interval's
|
||||
script has no more subscriptions.
|
||||
|
||||
This is used and accessed from oobhandler.repeat/unrepeat
|
||||
"""
|
||||
|
|
@ -160,9 +171,11 @@ class _RepeaterPool(object):
|
|||
"""
|
||||
if interval not in self.scripts:
|
||||
# if no existing interval exists, create new script to fill the gap
|
||||
new_tracker = create_script(_RepeaterScript, key="oob_repeater_%is" % interval, interval=interval)
|
||||
new_tracker = create_script(_RepeaterScript,
|
||||
key="oob_repeater_%is" % interval, interval=interval)
|
||||
self.scripts[interval] = new_tracker
|
||||
self.scripts[interval].subscribe(store_key, sessid, func_key, interval, *args, **kwargs)
|
||||
self.scripts[interval].subscribe(store_key, sessid, func_key,
|
||||
interval, *args, **kwargs)
|
||||
|
||||
def remove(self, store_key, interval):
|
||||
"""
|
||||
|
|
@ -176,8 +189,8 @@ class _RepeaterPool(object):
|
|||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop all scripts in pool. This is done at server reload since restoring the pool
|
||||
will automatically re-populate the pool.
|
||||
Stop all scripts in pool. This is done at server reload since
|
||||
restoring the pool will automatically re-populate the pool.
|
||||
"""
|
||||
for script in self.scripts.values():
|
||||
script.stop()
|
||||
|
|
@ -188,8 +201,8 @@ class _RepeaterPool(object):
|
|||
class OOBHandler(object):
|
||||
"""
|
||||
The OOBHandler maintains all dynamic on-object oob hooks. It will store the
|
||||
creation instructions and and re-apply them at a server reload (but not after
|
||||
a server shutdown)
|
||||
creation instructions and and re-apply them at a server reload (but
|
||||
not after a server shutdown)
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
|
|
@ -207,10 +220,12 @@ class OOBHandler(object):
|
|||
"""
|
||||
if self.oob_tracker_storage:
|
||||
#print "saved tracker_storage:", self.oob_tracker_storage
|
||||
ServerConfig.objects.conf(key="oob_tracker_storage", value=dbserialize(self.oob_tracker_storage))
|
||||
ServerConfig.objects.conf(key="oob_tracker_storage",
|
||||
value=dbserialize(self.oob_tracker_storage))
|
||||
if self.oob_repeat_storage:
|
||||
#print "saved repeat_storage:", self.oob_repeat_storage
|
||||
ServerConfig.objects.conf(key="oob_repeat_storage", value=dbserialize(self.oob_repeat_storage))
|
||||
ServerConfig.objects.conf(key="oob_repeat_storage",
|
||||
value=dbserialize(self.oob_repeat_storage))
|
||||
self.oob_tracker_pool.stop()
|
||||
|
||||
def restore(self):
|
||||
|
|
@ -242,12 +257,14 @@ class OOBHandler(object):
|
|||
Create an OOB obj of class _oob_MAPPING[tracker_key] on obj. args,
|
||||
kwargs will be used to initialize the OOB hook before adding
|
||||
it to obj.
|
||||
If property_key is not given, but the OOB has a class property property_name, this
|
||||
will be used as the property name when assigning the OOB to
|
||||
obj, otherwise tracker_key is used as the property name.
|
||||
If property_key is not given, but the OOB has a class property
|
||||
property_name, this will be used as the property name when assigning
|
||||
the OOB to obj, otherwise tracker_key is used as the property name.
|
||||
"""
|
||||
try: obj = obj.dbobj
|
||||
except AttributeError: pass
|
||||
try:
|
||||
obj = obj.dbobj
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if not "_trackerhandler" in _GA(obj, "__dict__"):
|
||||
# assign trackerhandler to object
|
||||
|
|
@ -266,8 +283,10 @@ class OOBHandler(object):
|
|||
Remove the OOB from obj. If oob implements an
|
||||
at_delete hook, this will be called with args, kwargs
|
||||
"""
|
||||
try: obj = obj.dbobj
|
||||
except AttributeError: pass
|
||||
try:
|
||||
obj = obj.dbobj
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
# call at_delete hook
|
||||
|
|
@ -278,7 +297,7 @@ class OOBHandler(object):
|
|||
store_key = (pack_dbobj(obj), sessid, fieldname)
|
||||
self.oob_tracker_storage.pop(store_key, None)
|
||||
|
||||
def get_all_tracked(session):
|
||||
def get_all_tracked(self, session):
|
||||
"""
|
||||
Get the names of all variables this session is tracking.
|
||||
"""
|
||||
|
|
@ -304,12 +323,14 @@ class OOBHandler(object):
|
|||
def track_attribute(self, obj, sessid, attr_name, trackerclass):
|
||||
"""
|
||||
Shortcut wrapper method for specifically tracking the changes of an
|
||||
Attribute on an object. Will create a tracker on the Attribute Object and
|
||||
name in a way the Attribute expects.
|
||||
Attribute on an object. Will create a tracker on the Attribute
|
||||
Object and name in a way the Attribute expects.
|
||||
"""
|
||||
# get the attribute object if we can
|
||||
try: obj = obj.dbobj
|
||||
except AttributeError: pass
|
||||
try:
|
||||
obj = obj.dbobj
|
||||
except AttributeError:
|
||||
pass
|
||||
attrobj = _GA(obj, "attributes").get(attr_name, return_obj=True)
|
||||
if attrobj:
|
||||
self.track(attrobj, sessid, "db_value", trackerclass, attr_name)
|
||||
|
|
@ -318,8 +339,10 @@ class OOBHandler(object):
|
|||
"""
|
||||
Shortcut for deactivating tracking for a given attribute.
|
||||
"""
|
||||
try: obj = obj.dbobj
|
||||
except AttributeError: pass
|
||||
try:
|
||||
obj = obj.dbobj
|
||||
except AttributeError:
|
||||
pass
|
||||
attrobj = _GA(obj, "attributes").get(attr_name, return_obj=True)
|
||||
if attrobj:
|
||||
self.untrack(attrobj, sessid, attr_name, trackerclass)
|
||||
|
|
@ -335,7 +358,7 @@ class OOBHandler(object):
|
|||
try:
|
||||
obj = obj.dbobj
|
||||
except AttributeError:
|
||||
passj
|
||||
pass
|
||||
store_obj = pack_dbobj(obj)
|
||||
store_key = (store_obj, sessid, func_key, interval)
|
||||
# prepare to store
|
||||
|
|
@ -363,7 +386,6 @@ class OOBHandler(object):
|
|||
|
||||
# access method - called from session.msg()
|
||||
|
||||
|
||||
def execute_cmd(self, session, func_key, *args, **kwargs):
|
||||
"""
|
||||
Retrieve oobfunc from OOB_FUNCS and execute it immediately
|
||||
|
|
@ -371,7 +393,7 @@ class OOBHandler(object):
|
|||
"""
|
||||
try:
|
||||
#print "OOB execute_cmd:", session, func_key, args, kwargs, _OOB_FUNCS.keys()
|
||||
oobfunc = _OOB_FUNCS[func_key] # raise traceback if not found
|
||||
oobfunc = _OOB_FUNCS[func_key] # raise traceback if not found
|
||||
oobfunc(self, session, *args, **kwargs)
|
||||
except KeyError,e:
|
||||
errmsg = "OOB Error: function '%s' not recognized: %s" % (func_key, e)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -20,12 +20,14 @@ import zlib
|
|||
MCCP = chr(86)
|
||||
FLUSH = zlib.Z_SYNC_FLUSH
|
||||
|
||||
|
||||
def mccp_compress(protocol, data):
|
||||
"Handles zlib compression, if applicable"
|
||||
if hasattr(protocol, 'zlib'):
|
||||
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
|
||||
return data
|
||||
|
||||
|
||||
class Mccp(object):
|
||||
"""
|
||||
Implements the MCCP protocol. Add this to a
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ etc.
|
|||
|
||||
"""
|
||||
import re
|
||||
from django.conf import settings
|
||||
from src.utils.utils import make_iter, mod_import, to_str
|
||||
from src.utils import logger
|
||||
from src.utils.utils import to_str
|
||||
|
||||
# MSDP-relevant telnet cmd/opt-codes
|
||||
MSDP = chr(69)
|
||||
|
|
@ -29,11 +27,18 @@ SE = chr(240)
|
|||
force_str = lambda inp: to_str(inp, force_string=True)
|
||||
|
||||
# pre-compiled regexes
|
||||
regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE)) # return 2-tuple
|
||||
regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)) # return 2-tuple (may be nested)
|
||||
# returns 2-tuple
|
||||
regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_ARRAY_OPEN,
|
||||
MSDP_ARRAY_CLOSE))
|
||||
# returns 2-tuple (may be nested)
|
||||
regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_TABLE_OPEN,
|
||||
MSDP_TABLE_CLOSE))
|
||||
regex_var = re.compile(MSDP_VAR)
|
||||
regex_val = re.compile(MSDP_VAL)
|
||||
|
||||
|
||||
# Msdp object handler
|
||||
|
||||
class Msdp(object):
|
||||
|
|
@ -90,7 +95,7 @@ class Msdp(object):
|
|||
else:
|
||||
string += MSDP_VAR + force_str(key) + MSDP_VAL + force_str(val)
|
||||
string += MSDP_TABLE_CLOSE
|
||||
return stringk
|
||||
return string
|
||||
|
||||
def make_array(name, *args):
|
||||
"build a array. Arrays may not nest tables by definition."
|
||||
|
|
@ -169,7 +174,7 @@ class Msdp(object):
|
|||
tables[key] = {}
|
||||
for varval in regex_var.split(table):
|
||||
parts = regex_val.split(varval)
|
||||
tables[key].expand({parts[0] : tuple(parts[1:]) if len(parts)>1 else ("",)})
|
||||
tables[key].expand({parts[0]: tuple(parts[1:]) if len(parts) > 1 else ("",)})
|
||||
for key, array in regex_array.findall(data):
|
||||
arrays[key] = []
|
||||
for val in regex_val.split(array):
|
||||
|
|
@ -178,16 +183,18 @@ class Msdp(object):
|
|||
for varval in regex_var.split(regex_array.sub("", regex_table.sub("", data))):
|
||||
# get remaining varvals after cleaning away tables/arrays
|
||||
parts = regex_val.split(varval)
|
||||
variables[parts[0].upper()] = tuple(parts[1:]) if len(parts)>1 else ("", )
|
||||
variables[parts[0].upper()] = tuple(parts[1:]) if len(parts) > 1 else ("", )
|
||||
|
||||
#print "MSDP: table, array, variables:", tables, arrays, variables
|
||||
|
||||
# all variables sent through msdp to Evennia are considered commands with arguments.
|
||||
# there are three forms of commands possible through msdp:
|
||||
# all variables sent through msdp to Evennia are considered commands
|
||||
# with arguments. There are three forms of commands possible
|
||||
# through msdp:
|
||||
#
|
||||
# VARNAME VAR -> varname(var)
|
||||
# ARRAYNAME VAR VAL VAR VAL VAR VAL ENDARRAY -> arrayname(val,val,val)
|
||||
# TABLENAME TABLE VARNAME VAL VARNAME VAL ENDTABLE -> tablename(varname=val, varname=val)
|
||||
# TABLENAME TABLE VARNAME VAL VARNAME VAL ENDTABLE ->
|
||||
# tablename(varname=val, varname=val)
|
||||
#
|
||||
|
||||
# default MSDP functions
|
||||
|
|
@ -232,82 +239,4 @@ class Msdp(object):
|
|||
Send oob data to Evennia
|
||||
"""
|
||||
#print "msdp data_in:", funcname, args, kwargs
|
||||
self.protocol.data_in(text=None, oob=(funcname, args, kwargs))
|
||||
|
||||
# # MSDP Commands
|
||||
# # Some given MSDP (varname, value) pairs can also be treated as command + argument.
|
||||
# # Generic msdp command map. The argument will be sent to the given command.
|
||||
# # See http://tintin.sourceforge.net/msdp/ for definitions of each command.
|
||||
# # These are client->server commands.
|
||||
# def msdp_cmd_list(self, arg):
|
||||
# """
|
||||
# The List command allows for retrieving various info about the server/client
|
||||
# """
|
||||
# if arg == 'COMMANDS':
|
||||
# return self.evennia_to_msdp(arg, MSDP_COMMANDS)
|
||||
# elif arg == 'LISTS':
|
||||
# return self.evennia_to_msdp(arg, ("COMMANDS", "LISTS", "CONFIGURABLE_VARIABLES",
|
||||
# "REPORTED_VARIABLES", "SENDABLE_VARIABLES"))
|
||||
# elif arg == 'CONFIGURABLE_VARIABLES':
|
||||
# return self.evennia_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
|
||||
# elif arg == 'REPORTABLE_VARIABLES':
|
||||
# return self.evennia_to_msdp(arg, MSDP_REPORTABLE.keys())
|
||||
# elif arg == 'REPORTED_VARIABLES':
|
||||
# # the dynamically set items to report
|
||||
# return self.evennia_to_msdp(arg, self.msdp_reported.keys())
|
||||
# elif arg == 'SENDABLE_VARIABLES':
|
||||
# return self.evennia_to_msdp(arg, MSDP_SENDABLE.keys())
|
||||
# else:
|
||||
# return self.evennia_to_msdp("LIST", arg)
|
||||
|
||||
# # default msdp commands
|
||||
|
||||
# def msdp_cmd_report(self, *arg):
|
||||
# """
|
||||
# The report command instructs the server to start reporting a
|
||||
# reportable variable to the client.
|
||||
# """
|
||||
# try:
|
||||
# return MSDP_REPORTABLE[arg](report=True)
|
||||
# except Exception:
|
||||
# logger.log_trace()
|
||||
|
||||
# def msdp_cmd_unreport(self, arg):
|
||||
# """
|
||||
# Unreport a previously reported variable
|
||||
# """
|
||||
# try:
|
||||
# MSDP_REPORTABLE[arg](report=False)
|
||||
# except Exception:
|
||||
# self.logger.log_trace()
|
||||
|
||||
# def msdp_cmd_reset(self, arg):
|
||||
# """
|
||||
# The reset command resets a variable to its initial state.
|
||||
# """
|
||||
# try:
|
||||
# MSDP_REPORTABLE[arg](reset=True)
|
||||
# except Exception:
|
||||
# logger.log_trace()
|
||||
|
||||
# def msdp_cmd_send(self, *args):
|
||||
# """
|
||||
# Request the server to send a particular variable
|
||||
# to the client.
|
||||
|
||||
# arg - this is a list of variables the client wants.
|
||||
# """
|
||||
# ret = []
|
||||
# for var in make_iter(arg)
|
||||
|
||||
|
||||
|
||||
|
||||
# for var in make_iter(arg):
|
||||
# try:
|
||||
# ret.append(MSDP_REPORTABLE[var.upper()])# (send=True))
|
||||
# except Exception:
|
||||
# ret.append("ERROR")#logger.log_trace()
|
||||
# return ret
|
||||
|
||||
|
||||
self.protocol.data_in(text=None, oob=(funcname, args, kwargs))
|
||||
|
|
@ -83,7 +83,7 @@ class Portal(object):
|
|||
|
||||
# create a store of services
|
||||
self.services = service.IServiceCollection(application)
|
||||
self.amp_protocol = None # set by amp factory
|
||||
self.amp_protocol = None # set by amp factory
|
||||
self.sessions = PORTAL_SESSIONS
|
||||
self.sessions.portal = self
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ class Portal(object):
|
|||
be restarted or is shutting down. Valid modes are True/False and None.
|
||||
If mode is None, no change will be done to the flag file.
|
||||
"""
|
||||
if mode == None:
|
||||
if mode is None:
|
||||
return
|
||||
f = open(PORTAL_RESTART, 'w')
|
||||
print "writing mode=%(mode)s to %(portal_restart)s" % {'mode': mode, 'portal_restart': PORTAL_RESTART}
|
||||
|
|
@ -211,17 +211,20 @@ if SSL_ENABLED:
|
|||
factory = protocol.ServerFactory()
|
||||
factory.sessionhandler = PORTAL_SESSIONS
|
||||
factory.protocol = ssl.SSLProtocol
|
||||
ssl_service = internet.SSLServer(port, factory, ssl.getSSLContext(), interface=interface)
|
||||
ssl_service = internet.SSLServer(port,
|
||||
factory,
|
||||
ssl.getSSLContext(),
|
||||
interface=interface)
|
||||
ssl_service.setName('EvenniaSSL%s' % pstring)
|
||||
PORTAL.services.addService(ssl_service)
|
||||
|
||||
print " ssl%s: %s" % (ifacestr, port)
|
||||
|
||||
|
||||
|
||||
if SSH_ENABLED:
|
||||
|
||||
# Start SSH game connections. Will create a keypair in evennia/game if necessary.
|
||||
# Start SSH game connections. Will create a keypair in
|
||||
# evennia/game if necessary.
|
||||
|
||||
from src.server.portal import ssh
|
||||
|
||||
|
|
@ -234,9 +237,9 @@ if SSH_ENABLED:
|
|||
ifacestr = "-%s" % interface
|
||||
for port in SSH_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = ssh.makeFactory({'protocolFactory':ssh.SshProtocol,
|
||||
'protocolArgs':(),
|
||||
'sessions':PORTAL_SESSIONS})
|
||||
factory = ssh.makeFactory({'protocolFactory': ssh.SshProtocol,
|
||||
'protocolArgs': (),
|
||||
'sessions': PORTAL_SESSIONS})
|
||||
ssh_service = internet.TCPServer(port, factory, interface=interface)
|
||||
ssh_service.setName('EvenniaSSH%s' % pstring)
|
||||
PORTAL.services.addService(ssh_service)
|
||||
|
|
@ -247,8 +250,6 @@ if WEBSERVER_ENABLED:
|
|||
|
||||
# Start a reverse proxy to relay data to the Server-side webserver
|
||||
|
||||
from twisted.web import proxy
|
||||
|
||||
for interface in WEBSERVER_INTERFACES:
|
||||
if ":" in interface:
|
||||
print " iPv6 interfaces not yet supported"
|
||||
|
|
@ -269,7 +270,9 @@ if WEBSERVER_ENABLED:
|
|||
webclientstr = "/client"
|
||||
|
||||
web_root = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||
proxy_service = internet.TCPServer(proxyport, web_root, interface=interface)
|
||||
proxy_service = internet.TCPServer(proxyport,
|
||||
web_root,
|
||||
interface=interface)
|
||||
proxy_service.setName('EvenniaWebProxy%s' % pstring)
|
||||
PORTAL.services.addService(proxy_service)
|
||||
print " webproxy%s%s:%s (<-> %s)" % (webclientstr, ifacestr, proxyport, serverport)
|
||||
|
|
@ -278,7 +281,7 @@ for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
|
|||
# external plugin services to start
|
||||
plugin_module.start_plugin_services(PORTAL)
|
||||
|
||||
print '-' * 50 # end of terminal output
|
||||
print '-' * 50 # end of terminal output
|
||||
|
||||
if os.name == 'nt':
|
||||
# Windows only: Set PID file manually
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Sessionhandler for portal sessions
|
|||
import time
|
||||
from src.server.sessionhandler import SessionHandler, PCONN, PDISCONN
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Portal-SessionHandler class
|
||||
#------------------------------------------------------------
|
||||
|
|
@ -39,8 +40,8 @@ class PortalSessionHandler(SessionHandler):
|
|||
|
||||
def connect(self, session):
|
||||
"""
|
||||
Called by protocol at first connect. This adds a not-yet authenticated session
|
||||
using an ever-increasing counter for sessid.
|
||||
Called by protocol at first connect. This adds a not-yet
|
||||
authenticated session using an ever-increasing counter for sessid.
|
||||
"""
|
||||
self.latest_sessid += 1
|
||||
sessid = self.latest_sessid
|
||||
|
|
@ -48,13 +49,15 @@ class PortalSessionHandler(SessionHandler):
|
|||
sessdata = session.get_sync_data()
|
||||
self.sessions[sessid] = session
|
||||
# sync with server-side
|
||||
if self.portal.amp_protocol: # this is a timing issue
|
||||
if self.portal.amp_protocol: # this is a timing issue
|
||||
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
|
||||
operation=PCONN,
|
||||
data=sessdata)
|
||||
|
||||
def disconnect(self, session):
|
||||
"""
|
||||
Called from portal side when the connection is closed from the portal side.
|
||||
Called from portal side when the connection is closed
|
||||
from the portal side.
|
||||
"""
|
||||
sessid = session.sessid
|
||||
if sessid in self.sessions:
|
||||
|
|
@ -86,18 +89,22 @@ class PortalSessionHandler(SessionHandler):
|
|||
self.sessions = {}
|
||||
|
||||
def server_logged_in(self, sessid, data):
|
||||
"The server tells us that the session has been authenticated. Updated it."
|
||||
"""
|
||||
The server tells us that the session has been
|
||||
authenticated. Updated it.
|
||||
"""
|
||||
sess = self.get_session(sessid)
|
||||
sess.load_sync_data(data)
|
||||
|
||||
def server_session_sync(self, serversessions):
|
||||
"""
|
||||
Server wants to save data to the portal, maybe because it's about to shut down.
|
||||
We don't overwrite any sessions here, just update them in-place and remove
|
||||
any that are out of sync (which should normally not be the case)
|
||||
Server wants to save data to the portal, maybe because it's about
|
||||
to shut down. We don't overwrite any sessions here, just update
|
||||
them in-place and remove any that are out of sync (which should
|
||||
normally not be the case)
|
||||
|
||||
serversessions - dictionary {sessid:{property:value},...} describing the properties
|
||||
to sync on all sessions
|
||||
serversessions - dictionary {sessid:{property:value},...} describing
|
||||
the properties to sync on all sessions
|
||||
"""
|
||||
to_save = [sessid for sessid in serversessions if sessid in self.sessions]
|
||||
to_delete = [sessid for sessid in self.sessions if sessid not in to_save]
|
||||
|
|
@ -131,6 +138,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid,
|
||||
msg=text,
|
||||
data=kwargs)
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
Send message to all connection sessions
|
||||
|
|
@ -138,7 +146,6 @@ class PortalSessionHandler(SessionHandler):
|
|||
for session in self.sessions.values():
|
||||
session.data_out(message)
|
||||
|
||||
|
||||
def data_out(self, sessid, text=None, **kwargs):
|
||||
"""
|
||||
Called by server for having the portal relay messages and data
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from twisted.python import components
|
|||
from django.conf import settings
|
||||
from src.server import session
|
||||
from src.players.models import PlayerDB
|
||||
from src.utils import ansi, utils, logger
|
||||
from src.utils import ansi, utils
|
||||
|
||||
ENCODINGS = settings.ENCODINGS
|
||||
|
||||
|
|
@ -35,6 +35,7 @@ CTRL_D = '\x04'
|
|||
CTRL_BACKSLASH = '\x1c'
|
||||
CTRL_L = '\x0c'
|
||||
|
||||
|
||||
class SshProtocol(Manhole, session.Session):
|
||||
"""
|
||||
Each player connecting over ssh gets this protocol assigned to
|
||||
|
|
@ -47,7 +48,8 @@ class SshProtocol(Manhole, session.Session):
|
|||
login automatically.
|
||||
"""
|
||||
self.authenticated_player = starttuple[0]
|
||||
self.cfactory = starttuple[1] # obs may not be called self.factory, it gets overwritten!
|
||||
# obs must not be called self.factory, that gets overwritten!
|
||||
self.cfactory = starttuple[1]
|
||||
|
||||
def terminalSize(self, width, height):
|
||||
"""
|
||||
|
|
@ -95,7 +97,6 @@ class SshProtocol(Manhole, session.Session):
|
|||
self.terminal.write("KeyboardInterrupt")
|
||||
self.terminal.nextLine()
|
||||
|
||||
|
||||
def handle_EOF(self):
|
||||
"""
|
||||
Handles EOF generally used to exit.
|
||||
|
|
@ -105,7 +106,6 @@ class SshProtocol(Manhole, session.Session):
|
|||
else:
|
||||
self.handle_QUIT()
|
||||
|
||||
|
||||
def handle_FF(self):
|
||||
"""
|
||||
Handle a 'form feed' byte - generally used to request a screen
|
||||
|
|
@ -114,14 +114,12 @@ class SshProtocol(Manhole, session.Session):
|
|||
self.terminal.eraseDisplay()
|
||||
self.terminal.cursorHome()
|
||||
|
||||
|
||||
def handle_QUIT(self):
|
||||
"""
|
||||
Quit, end, and lose the connection.
|
||||
"""
|
||||
self.terminal.loseConnection()
|
||||
|
||||
|
||||
def connectionLost(self, reason=None):
|
||||
"""
|
||||
This is executed when the connection is lost for
|
||||
|
|
@ -140,9 +138,7 @@ class SshProtocol(Manhole, session.Session):
|
|||
"""
|
||||
return self.terminal.transport.getPeer()
|
||||
|
||||
|
||||
def lineReceived(self, string):
|
||||
|
||||
"""
|
||||
Communication Player -> Evennia. Any line return indicates a
|
||||
command for the purpose of the MUD. So we take the user input
|
||||
|
|
@ -159,10 +155,10 @@ class SshProtocol(Manhole, session.Session):
|
|||
|
||||
"""
|
||||
for line in string.split('\n'):
|
||||
self.terminal.write(line) #this is the telnet-specific method for sending
|
||||
#this is the telnet-specific method for sending
|
||||
self.terminal.write(line)
|
||||
self.terminal.nextLine()
|
||||
|
||||
|
||||
# session-general method hooks
|
||||
|
||||
def disconnect(self, reason="Connection closed. Goodbye for now."):
|
||||
|
|
@ -175,7 +171,8 @@ class SshProtocol(Manhole, session.Session):
|
|||
|
||||
def data_out(self, text=None, **kwargs):
|
||||
"""
|
||||
Data Evennia -> Player access hook. 'data' argument is a dict parsed for string settings.
|
||||
Data Evennia -> Player access hook. 'data' argument is a dict
|
||||
parsed for string settings.
|
||||
|
||||
ssh flags:
|
||||
raw=True - leave all ansi markup and tokens unparsed
|
||||
|
|
@ -190,9 +187,9 @@ class SshProtocol(Manhole, session.Session):
|
|||
raw = kwargs.get("raw", False)
|
||||
nomarkup = kwargs.get("nomarkup", False)
|
||||
if raw:
|
||||
self.lineSend(string)
|
||||
self.lineSend(text)
|
||||
else:
|
||||
self.lineSend(ansi.parse_ansi(string.strip("{r") + "{r", strip_ansi=nomarkup))
|
||||
self.lineSend(ansi.parse_ansi(text.strip("{r") + "{r", strip_ansi=nomarkup))
|
||||
|
||||
|
||||
class ExtraInfoAuthServer(SSHUserAuthServer):
|
||||
|
|
@ -209,6 +206,7 @@ class ExtraInfoAuthServer(SSHUserAuthServer):
|
|||
return self.portal.login(c, None, IConchUser).addErrback(
|
||||
self._ebPassword)
|
||||
|
||||
|
||||
class PlayerDBPasswordChecker(object):
|
||||
"""
|
||||
Checks the django db for the correct credentials for
|
||||
|
|
@ -232,6 +230,7 @@ class PlayerDBPasswordChecker(object):
|
|||
res = (player, self.factory)
|
||||
return defer.succeed(res)
|
||||
|
||||
|
||||
class PassAvatarIdTerminalRealm(TerminalRealm):
|
||||
"""
|
||||
Returns an avatar that passes the avatarId through to the
|
||||
|
|
@ -244,7 +243,7 @@ class PassAvatarIdTerminalRealm(TerminalRealm):
|
|||
sess = self.sessionFactory(comp)
|
||||
|
||||
sess.transportFactory = self.transportFactory
|
||||
sess.chainedProtocolFactory = lambda : self.chainedProtocolFactory(avatarId)
|
||||
sess.chainedProtocolFactory = lambda: self.chainedProtocolFactory(avatarId)
|
||||
|
||||
comp.setComponent(iconch.IConchUser, user)
|
||||
comp.setComponent(iconch.ISession, sess)
|
||||
|
|
@ -252,7 +251,6 @@ class PassAvatarIdTerminalRealm(TerminalRealm):
|
|||
return user
|
||||
|
||||
|
||||
|
||||
class TerminalSessionTransport_getPeer:
|
||||
"""
|
||||
Taken from twisted's TerminalSessionTransport which doesn't
|
||||
|
|
@ -345,4 +343,4 @@ def makeFactory(configdict):
|
|||
|
||||
factory.portal.registerChecker(PlayerDBPasswordChecker(factory))
|
||||
|
||||
return factory
|
||||
return factory
|
||||
|
|
@ -3,7 +3,8 @@ This is a simple context factory for auto-creating
|
|||
SSL keys and certificates.
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
import os
|
||||
import sys
|
||||
from twisted.internet import ssl as twisted_ssl
|
||||
try:
|
||||
import OpenSSL
|
||||
|
|
@ -13,6 +14,7 @@ except ImportError:
|
|||
|
||||
from src.server.portal.telnet import TelnetProtocol
|
||||
|
||||
|
||||
class SSLProtocol(TelnetProtocol):
|
||||
"""
|
||||
Communication is the same as telnet, except data transfer
|
||||
|
|
@ -20,6 +22,7 @@ class SSLProtocol(TelnetProtocol):
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
def verify_SSL_key_and_cert(keyfile, certfile):
|
||||
"""
|
||||
This function looks for RSA key and certificate in the current
|
||||
|
|
@ -41,24 +44,27 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
rsaKey = Key(RSA.generate(KEY_LENGTH))
|
||||
keyString = rsaKey.toString(type="OPENSSH")
|
||||
file(keyfile, 'w+b').write(keyString)
|
||||
except Exception,e:
|
||||
except Exception, e:
|
||||
print "rsaKey error: %(e)s\n WARNING: Evennia could not auto-generate SSL private key." % {'e': e}
|
||||
print "If this error persists, create game/%(keyfile)s yourself using third-party tools." % {'keyfile': keyfile}
|
||||
sys.exit(5)
|
||||
|
||||
# try to create the certificate
|
||||
CERT_EXPIRE = 365 * 20 # twenty years validity
|
||||
CERT_EXPIRE = 365 * 20 # twenty years validity
|
||||
# default:
|
||||
#openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
|
||||
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
|
||||
#print "exestring:", exestring
|
||||
try:
|
||||
err = subprocess.call(exestring)#, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
#, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
subprocess.call(exestring)
|
||||
except OSError, e:
|
||||
string = "\n".join([
|
||||
" %s\n" % e,
|
||||
" Evennia's SSL context factory could not automatically create an SSL certificate game/%(cert)s." % {'cert': certfile},
|
||||
" A private key 'ssl.key' was already created. Please create %(cert)s manually using the commands valid" % {'cert': certfile},
|
||||
" Evennia's SSL context factory could not automatically",
|
||||
" create an SSL certificate game/%(cert)s." % {'cert': certfile},
|
||||
" A private key 'ssl.key' was already created. Please",
|
||||
" create %(cert)s manually using the commands valid" % {'cert': certfile},
|
||||
" for your operating system.",
|
||||
" Example (linux, using the openssl program): ",
|
||||
" %s" % exestring])
|
||||
|
|
@ -66,6 +72,7 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
sys.exit(5)
|
||||
print "done."
|
||||
|
||||
|
||||
def getSSLContext():
|
||||
"""
|
||||
Returns an SSL context (key and certificate). This function
|
||||
|
|
|
|||
|
|
@ -9,15 +9,14 @@ sessions etc.
|
|||
|
||||
import re
|
||||
from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
from src.server.session import Session
|
||||
from src.server.portal import ttype, mssp, msdp
|
||||
from src.server.portal.mccp import Mccp, mccp_compress, MCCP
|
||||
from src.utils import utils, ansi, logger
|
||||
from src.utils.utils import make_iter, is_iter
|
||||
|
||||
_RE_N = re.compile(r"\{n$")
|
||||
|
||||
|
||||
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||
"""
|
||||
Each player connecting over telnet (ie using most traditional mud
|
||||
|
|
@ -127,7 +126,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
|
||||
def _write(self, data):
|
||||
"hook overloading the one used in plain telnet"
|
||||
#print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in data))
|
||||
# print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in data))
|
||||
data = data.replace('\n', '\r\n').replace('\r\r\n', '\r\n')
|
||||
#data = data.replace('\n', '\r\n')
|
||||
super(TelnetProtocol, self)._write(mccp_compress(self, data))
|
||||
|
|
@ -147,7 +146,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
"""
|
||||
self.data_in(text=string)
|
||||
|
||||
|
||||
# Session hooks
|
||||
|
||||
def disconnect(self, reason=None):
|
||||
|
|
@ -172,11 +170,13 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
through the telnet connection.
|
||||
|
||||
valid telnet kwargs:
|
||||
raw=True - pass string through without any ansi processing (i.e. include Evennia
|
||||
ansi markers but do not convert them into ansi tokens)
|
||||
raw=True - pass string through without any ansi
|
||||
processing (i.e. include Evennia ansi markers but do
|
||||
not convert them into ansi tokens)
|
||||
nomarkup=True - strip all ansi markup
|
||||
|
||||
The telnet ttype negotiation flags, if any, are used if no kwargs are given.
|
||||
The telnet ttype negotiation flags, if any, are used if no kwargs
|
||||
are given.
|
||||
"""
|
||||
try:
|
||||
text = utils.to_str(text if text else "", encoding=self.encoding)
|
||||
|
|
@ -200,6 +200,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
# no processing whatsoever
|
||||
self.sendLine(text)
|
||||
else:
|
||||
# we need to make sure to kill the color at the end in order to match the webclient output.
|
||||
#print "telnet data out:", self.protocol_flags, id(self.protocol_flags), id(self)
|
||||
# we need to make sure to kill the color at the end in order
|
||||
# to match the webclient output.
|
||||
# print "telnet data out:", self.protocol_flags, id(self.protocol_flags), id(self)
|
||||
self.sendLine(ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=ttype.get('256 COLORS')))
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ under the 'TTYPE' key.
|
|||
"""
|
||||
|
||||
# telnet option codes
|
||||
TTYPE = chr(24)
|
||||
TTYPE = chr(24)
|
||||
IS = chr(0)
|
||||
SEND = chr(1)
|
||||
|
||||
# terminal capabilities and their codes
|
||||
MTTS = [(128,'PROXY'),
|
||||
MTTS = [(128, 'PROXY'),
|
||||
(64, 'SCREEN READER'),
|
||||
(32, 'OSC COLOR PALETTE'),
|
||||
(16, 'MOUSE TRACKING'),
|
||||
|
|
@ -27,8 +27,8 @@ MTTS = [(128,'PROXY'),
|
|||
(1, 'ANSI')]
|
||||
# some clients sends erroneous strings instead
|
||||
# of capability numbers. We try to convert back.
|
||||
MTTS_invert = {"PROXY":128,
|
||||
"SCREEN COLOR PALETTE":64,
|
||||
MTTS_invert = {"PROXY": 128,
|
||||
"SCREEN COLOR PALETTE": 64,
|
||||
"OSC COLOR PALETTE": 32,
|
||||
"MOUSE TRACKING": 16,
|
||||
"256 COLORS": 8,
|
||||
|
|
@ -36,6 +36,7 @@ MTTS_invert = {"PROXY":128,
|
|||
"VT100": 2,
|
||||
"ANSI": 1}
|
||||
|
||||
|
||||
class Ttype(object):
|
||||
"""
|
||||
Handles ttype negotiations. Called and initiated by the
|
||||
|
|
@ -51,7 +52,7 @@ class Ttype(object):
|
|||
"""
|
||||
self.ttype_step = 0
|
||||
self.protocol = protocol
|
||||
self.protocol.protocol_flags['TTYPE'] = {"init_done":False}
|
||||
self.protocol.protocol_flags['TTYPE'] = {"init_done": False}
|
||||
# setup protocol to handle ttype initialization and negotiation
|
||||
self.protocol.negotiationMap[TTYPE] = self.do_ttype
|
||||
# ask if client will ttype, connect callback if it does.
|
||||
|
|
@ -61,7 +62,7 @@ class Ttype(object):
|
|||
"""
|
||||
Callback if ttype is not supported by client.
|
||||
"""
|
||||
self.protocol.protocol_flags['TTYPE'] = {"init_done":True}
|
||||
self.protocol.protocol_flags['TTYPE'] = {"init_done": True}
|
||||
|
||||
def do_ttype(self, option):
|
||||
"""
|
||||
|
|
@ -95,9 +96,9 @@ class Ttype(object):
|
|||
try:
|
||||
option = int(option.strip('MTTS '))
|
||||
except ValueError:
|
||||
# it seems some clients don't send MTTS according to protocol
|
||||
# specification, but instead just sends the data as plain
|
||||
# strings. We try to convert back.
|
||||
# it seems some clients don't send MTTS according to
|
||||
# protocol specification, but instead just sends
|
||||
# the data as plain strings. We try to convert back.
|
||||
option = MTTS_invert.get(option.strip('MTTS ').upper())
|
||||
if not option:
|
||||
# no conversion possible. Give up.
|
||||
|
|
|
|||
|
|
@ -20,19 +20,19 @@ import time
|
|||
from hashlib import md5
|
||||
|
||||
from twisted.web import server, resource
|
||||
from twisted.internet import defer, reactor
|
||||
|
||||
from django.utils import simplejson
|
||||
from django.utils.functional import Promise
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.conf import settings
|
||||
from src.utils import utils, logger, ansi
|
||||
from src.utils import utils, logger
|
||||
from src.utils.text2html import parse_html
|
||||
from src.server import session
|
||||
|
||||
SERVERNAME = settings.SERVERNAME
|
||||
ENCODINGS = settings.ENCODINGS
|
||||
|
||||
|
||||
# defining a simple json encoder for returning
|
||||
# django data to the client. Might need to
|
||||
# extend this if one wants to send more
|
||||
|
|
@ -43,6 +43,8 @@ class LazyEncoder(simplejson.JSONEncoder):
|
|||
if isinstance(obj, Promise):
|
||||
return force_unicode(obj)
|
||||
return super(LazyEncoder, self).default(obj)
|
||||
|
||||
|
||||
def jsonify(obj):
|
||||
return utils.to_str(simplejson.dumps(obj, ensure_ascii=False, cls=LazyEncoder))
|
||||
|
||||
|
|
@ -84,23 +86,23 @@ class WebClient(resource.Resource):
|
|||
request = self.requests.get(suid)
|
||||
if request:
|
||||
# we have a request waiting. Return immediately.
|
||||
request.write(jsonify({'msg':string, 'data':data}))
|
||||
request.write(jsonify({'msg': string, 'data': data}))
|
||||
request.finish()
|
||||
del self.requests[suid]
|
||||
else:
|
||||
# no waiting request. Store data in buffer
|
||||
dataentries = self.databuffer.get(suid, [])
|
||||
dataentries.append(jsonify({'msg':string, 'data':data}))
|
||||
dataentries.append(jsonify({'msg': string, 'data': data}))
|
||||
self.databuffer[suid] = dataentries
|
||||
|
||||
def client_disconnect(self, suid):
|
||||
"""
|
||||
Disconnect session with given suid.
|
||||
"""
|
||||
if self.requests.has_key(suid):
|
||||
if suid in self.requests:
|
||||
self.requests[suid].finish()
|
||||
del self.requests[suid]
|
||||
if self.databuffer.has_key(suid):
|
||||
if suid in self.databuffer:
|
||||
del self.databuffer[suid]
|
||||
|
||||
def mode_init(self, request):
|
||||
|
|
@ -108,7 +110,8 @@ class WebClient(resource.Resource):
|
|||
This is called by render_POST when the client
|
||||
requests an init mode operation (at startup)
|
||||
"""
|
||||
#csess = request.getSession() # obs, this is a cookie, not an evennia session!
|
||||
#csess = request.getSession() # obs, this is a cookie, not
|
||||
# an evennia session!
|
||||
#csees.expireCallbacks.append(lambda : )
|
||||
suid = request.args.get('suid', ['0'])[0]
|
||||
|
||||
|
|
@ -124,7 +127,7 @@ class WebClient(resource.Resource):
|
|||
sess.init_session("webclient", remote_addr, self.sessionhandler)
|
||||
sess.suid = suid
|
||||
sess.sessionhandler.connect(sess)
|
||||
return jsonify({'msg':host_string, 'suid':suid})
|
||||
return jsonify({'msg': host_string, 'suid': suid})
|
||||
|
||||
def mode_input(self, request):
|
||||
"""
|
||||
|
|
@ -158,8 +161,8 @@ class WebClient(resource.Resource):
|
|||
if dataentries:
|
||||
return dataentries.pop(0)
|
||||
request.notifyFinish().addErrback(self._responseFailed, suid, request)
|
||||
if self.requests.has_key(suid):
|
||||
self.requests[suid].finish() # Clear any stale request.
|
||||
if suid in self.requests:
|
||||
self.requests[suid].finish() # Clear any stale request.
|
||||
self.requests[suid] = request
|
||||
return server.NOT_DONE_YET
|
||||
|
||||
|
|
@ -206,6 +209,7 @@ class WebClient(resource.Resource):
|
|||
# this should not happen if client sends valid data.
|
||||
return ''
|
||||
|
||||
|
||||
#
|
||||
# A session type handling communication over the
|
||||
# web client interface.
|
||||
|
|
@ -241,7 +245,8 @@ class WebClientSession(session.Session):
|
|||
if raw:
|
||||
self.client.lineSend(self.suid, text)
|
||||
else:
|
||||
self.client.lineSend(self.suid, parse_html(text, strip_ansi=nomarkup))
|
||||
self.client.lineSend(self.suid,
|
||||
parse_html(text, strip_ansi=nomarkup))
|
||||
return
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
|
|
|||
|
|
@ -32,12 +32,11 @@ from src.server.sessionhandler import SESSIONS
|
|||
|
||||
# setting up server-side field cache
|
||||
|
||||
from django.db.models.signals import pre_save, post_save
|
||||
from django.db.models.signals import post_save
|
||||
from src.server.caches import field_pre_save
|
||||
#pre_save.connect(field_pre_save, dispatch_uid="fieldcache")
|
||||
post_save.connect(field_pre_save, dispatch_uid="fieldcache")
|
||||
|
||||
from src.typeclasses.models import TypedObject
|
||||
#from src.server.caches import post_attr_update
|
||||
#from django.db.models.signals import m2m_changed
|
||||
|
||||
|
|
@ -104,7 +103,7 @@ class Evennia(object):
|
|||
|
||||
# create a store of services
|
||||
self.services = service.IServiceCollection(application)
|
||||
self.amp_protocol = None # set by amp factory
|
||||
self.amp_protocol = None # set by amp factory
|
||||
self.sessions = SESSIONS
|
||||
self.sessions.server = self
|
||||
|
||||
|
|
@ -121,7 +120,8 @@ class Evennia(object):
|
|||
|
||||
# set a callback if the server is killed abruptly,
|
||||
# by Ctrl-C, reboot etc.
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _reactor_stopping=True)
|
||||
reactor.addSystemEventTrigger('before', 'shutdown',
|
||||
self.shutdown, _reactor_stopping=True)
|
||||
|
||||
self.game_running = True
|
||||
|
||||
|
|
@ -146,38 +146,48 @@ class Evennia(object):
|
|||
|
||||
def update_defaults(self):
|
||||
"""
|
||||
We make sure to store the most important object defaults here, so we can catch if they
|
||||
change and update them on-objects automatically. This allows for changing default cmdset locations
|
||||
and default typeclasses in the settings file and have them auto-update all already existing
|
||||
objects.
|
||||
We make sure to store the most important object defaults here, so
|
||||
we can catch if they change and update them on-objects automatically.
|
||||
This allows for changing default cmdset locations and default
|
||||
typeclasses in the settings file and have them auto-update all
|
||||
already existing objects.
|
||||
"""
|
||||
# setting names
|
||||
settings_names = ("CMDSET_CHARACTER", "CMDSET_PLAYER", "BASE_PLAYER_TYPECLASS", "BASE_OBJECT_TYPECLASS",
|
||||
"BASE_CHARACTER_TYPECLASS", "BASE_ROOM_TYPECLASS", "BASE_EXIT_TYPECLASS", "BASE_SCRIPT_TYPECLASS")
|
||||
settings_names = ("CMDSET_CHARACTER", "CMDSET_PLAYER",
|
||||
"BASE_PLAYER_TYPECLASS", "BASE_OBJECT_TYPECLASS",
|
||||
"BASE_CHARACTER_TYPECLASS", "BASE_ROOM_TYPECLASS",
|
||||
"BASE_EXIT_TYPECLASS", "BASE_SCRIPT_TYPECLASS")
|
||||
# get previous and current settings so they can be compared
|
||||
settings_compare = zip([ServerConfig.objects.conf(name) for name in settings_names],
|
||||
[settings.__getattr__(name) for name in settings_names])
|
||||
mismatches = [i for i, tup in enumerate(settings_compare) if tup[0] and tup[1] and tup[0] != tup[1]]
|
||||
if len(mismatches): # can't use any() since mismatches may be [0] which reads as False for any()
|
||||
# we have a changed default. Import relevant objects and run the update
|
||||
if len(mismatches): # can't use any() since mismatches may be [0] which reads as False for any()
|
||||
# we have a changed default. Import relevant objects and
|
||||
# run the update
|
||||
from src.objects.models import ObjectDB
|
||||
#from src.players.models import PlayerDB
|
||||
for i, prev, curr in ((i, tup[0], tup[1]) for i, tup in enumerate(settings_compare) if i in mismatches):
|
||||
# update the database
|
||||
print " %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..." % (settings_names[i], prev, curr)
|
||||
if i == 0: [obj.__setattr__("cmdset_storage", curr) for obj in ObjectDB.objects.filter(db_cmdset_storage__exact=prev)]
|
||||
if i == 1: [ply.__setattr__("cmdset_storage", curr) for ply in PlayerDB.objects.filter(db_cmdset_storage__exact=prev)]
|
||||
if i == 2: [ply.__setattr__("typeclass_path", curr) for ply in PlayerDB.objects.filter(db_typeclass_path__exact=prev)]
|
||||
if i in (3,4,5,6): [obj.__setattr__("typeclass_path",curr)
|
||||
for obj in ObjectDB.objects.filter(db_typeclass_path__exact=prev)]
|
||||
if i == 7: [scr.__setattr__("typeclass_path", curr) for scr in ScriptDB.objects.filter(db_typeclass_path__exact=prev)]
|
||||
if i == 0:
|
||||
[obj.__setattr__("cmdset_storage", curr) for obj in ObjectDB.objects.filter(db_cmdset_storage__exact=prev)]
|
||||
if i == 1:
|
||||
[ply.__setattr__("cmdset_storage", curr) for ply in PlayerDB.objects.filter(db_cmdset_storage__exact=prev)]
|
||||
if i == 2:
|
||||
[ply.__setattr__("typeclass_path", curr) for ply in PlayerDB.objects.filter(db_typeclass_path__exact=prev)]
|
||||
if i in (3, 4, 5, 6):
|
||||
[obj.__setattr__("typeclass_path", curr) for obj in ObjectDB.objects.filter(db_typeclass_path__exact=prev)]
|
||||
if i == 7:
|
||||
[scr.__setattr__("typeclass_path", curr) for scr in ScriptDB.objects.filter(db_typeclass_path__exact=prev)]
|
||||
# store the new default and clean caches
|
||||
ServerConfig.objects.conf(settings_names[i], curr)
|
||||
ObjectDB.flush_instance_cache()
|
||||
PlayerDB.flush_instance_cache()
|
||||
ScriptDB.flush_instance_cache()
|
||||
# if this is the first start we might not have a "previous" setup saved. Store it now.
|
||||
[ServerConfig.objects.conf(settings_names[i], tup[1]) for i, tup in enumerate(settings_compare) if not tup[0]]
|
||||
# if this is the first start we might not have a "previous"
|
||||
# setup saved. Store it now.
|
||||
[ServerConfig.objects.conf(settings_names[i], tup[1])
|
||||
for i, tup in enumerate(settings_compare) if not tup[0]]
|
||||
|
||||
def run_initial_setup(self):
|
||||
"""
|
||||
|
|
@ -191,7 +201,7 @@ class Evennia(object):
|
|||
# i.e. this is an empty DB that needs populating.
|
||||
print ' Server started for the first time. Setting defaults.'
|
||||
initial_setup.handle_setup(0)
|
||||
print '-'*50
|
||||
print '-' * 50
|
||||
elif int(last_initial_setup_step) >= 0:
|
||||
# a positive value means the setup crashed on one of its
|
||||
# modules and setup will resume from this step, retrying
|
||||
|
|
@ -200,7 +210,7 @@ class Evennia(object):
|
|||
print ' Resuming initial setup from step %(last)s.' % \
|
||||
{'last': last_initial_setup_step}
|
||||
initial_setup.handle_setup(int(last_initial_setup_step))
|
||||
print '-'*50
|
||||
print '-' * 50
|
||||
|
||||
def run_init_hooks(self):
|
||||
"""
|
||||
|
|
@ -244,7 +254,7 @@ class Evennia(object):
|
|||
Either way, the active restart setting (Restart=True/False) is
|
||||
returned so the server knows which more it's in.
|
||||
"""
|
||||
if mode == None:
|
||||
if mode is None:
|
||||
with open(SERVER_RESTART, 'r') as f:
|
||||
# mode is either shutdown, reset or reload
|
||||
mode = f.read()
|
||||
|
|
@ -259,16 +269,20 @@ class Evennia(object):
|
|||
Shuts down the server from inside it.
|
||||
|
||||
mode - sets the server restart mode.
|
||||
'reload' - server restarts, no "persistent" scripts are stopped, at_reload hooks called.
|
||||
'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called.
|
||||
'reload' - server restarts, no "persistent" scripts
|
||||
are stopped, at_reload hooks called.
|
||||
'reset' - server restarts, non-persistent scripts stopped,
|
||||
at_shutdown hooks called.
|
||||
'shutdown' - like reset, but server will not auto-restart.
|
||||
None - keep currently set flag from flag file.
|
||||
_reactor_stopping - this is set if server is stopped by a kill command OR this method was already called
|
||||
once - in both cases the reactor is dead/stopping already.
|
||||
_reactor_stopping - this is set if server is stopped by a kill
|
||||
command OR this method was already called
|
||||
once - in both cases the reactor is
|
||||
dead/stopping already.
|
||||
"""
|
||||
if _reactor_stopping and hasattr(self, "shutdown_complete"):
|
||||
# this means we have already passed through this method once; we don't need
|
||||
# to run the shutdown procedure again.
|
||||
# this means we have already passed through this method
|
||||
# once; we don't need to run the shutdown procedure again.
|
||||
defer.returnValue(None)
|
||||
|
||||
mode = self.set_restart_mode(mode)
|
||||
|
|
@ -280,9 +294,12 @@ class Evennia(object):
|
|||
|
||||
if mode == 'reload':
|
||||
# call restart hooks
|
||||
yield [(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
|
||||
yield [(p.typeclass, p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()]
|
||||
yield [(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
|
||||
yield [(o.typeclass, o.at_server_reload())
|
||||
for o in ObjectDB.get_all_cached_instances()]
|
||||
yield [(p.typeclass, p.at_server_reload())
|
||||
for p in PlayerDB.get_all_cached_instances()]
|
||||
yield [(s.typeclass, s.pause(), s.at_server_reload())
|
||||
for s in ScriptDB.get_all_cached_instances()]
|
||||
yield self.sessions.all_sessions_portal_sync()
|
||||
ServerConfig.objects.conf("server_restart_mode", "reload")
|
||||
|
||||
|
|
@ -294,14 +311,20 @@ class Evennia(object):
|
|||
|
||||
else:
|
||||
if mode == 'reset':
|
||||
# don't unset the is_connected flag on reset, otherwise same as shutdown
|
||||
yield [(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
|
||||
else: # shutdown
|
||||
yield [_SA(p, "is_connected", False) for p in PlayerDB.get_all_cached_instances()]
|
||||
yield [(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
|
||||
# don't unset the is_connected flag on reset, otherwise
|
||||
# same as shutdown
|
||||
yield [(o.typeclass, o.at_server_shutdown())
|
||||
for o in ObjectDB.get_all_cached_instances()]
|
||||
else: # shutdown
|
||||
yield [_SA(p, "is_connected", False)
|
||||
for p in PlayerDB.get_all_cached_instances()]
|
||||
yield [(o.typeclass, o.at_server_shutdown())
|
||||
for o in ObjectDB.get_all_cached_instances()]
|
||||
|
||||
yield [(p.typeclass, p.unpuppet_all(), p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
|
||||
yield [(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
|
||||
yield [(p.typeclass, p.unpuppet_all(), p.at_server_shutdown())
|
||||
for p in PlayerDB.get_all_cached_instances()]
|
||||
yield [(s.typeclass, s.at_server_shutdown())
|
||||
for s in ScriptDB.get_all_cached_instances()]
|
||||
yield ObjectDB.objects.clear_all_sessids()
|
||||
ServerConfig.objects.conf("server_restart_mode", "reset")
|
||||
|
||||
|
|
@ -310,12 +333,14 @@ class Evennia(object):
|
|||
|
||||
if SERVER_STARTSTOP_MODULE:
|
||||
SERVER_STARTSTOP_MODULE.at_server_stop()
|
||||
# if _reactor_stopping is true, reactor does not need to be stopped again.
|
||||
# if _reactor_stopping is true, reactor does not need to
|
||||
# be stopped again.
|
||||
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
|
||||
# for Windows we need to remove pid files manually
|
||||
os.remove(SERVER_PIDFILE)
|
||||
if not _reactor_stopping:
|
||||
# this will also send a reactor.stop signal, so we set a flag to avoid loops.
|
||||
# this will also send a reactor.stop signal, so we set a
|
||||
# flag to avoid loops.
|
||||
self.shutdown_complete = True
|
||||
reactor.callLater(0, reactor.stop)
|
||||
|
||||
|
|
@ -415,7 +440,7 @@ for plugin_module in SERVER_SERVICES_PLUGIN_MODULES:
|
|||
# external plugin protocols
|
||||
plugin_module.start_plugin_services(EVENNIA)
|
||||
|
||||
print '-' * 50 # end of terminal output
|
||||
print '-' * 50 # end of terminal output
|
||||
|
||||
# clear server startup mode
|
||||
ServerConfig.objects.conf("server_starting_mode", delete=True)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ are stored on the Portal side)
|
|||
import time
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from src.scripts.models import ScriptDB
|
||||
#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_str
|
||||
|
|
@ -53,7 +53,8 @@ class ServerSession(Session):
|
|||
self.cmdset = cmdsethandler.CmdSetHandler(self)
|
||||
|
||||
def __cmdset_storage_get(self):
|
||||
return [path.strip() for path in self.cmdset_storage_string.split(',')]
|
||||
return [path.strip() for path in self.cmdset_storage_string.split(',')]
|
||||
|
||||
def __cmdset_storage_set(self, value):
|
||||
self.cmdset_storage_string = ",".join(str(val).strip() for val in make_iter(value))
|
||||
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set)
|
||||
|
|
@ -61,8 +62,8 @@ class ServerSession(Session):
|
|||
def at_sync(self):
|
||||
"""
|
||||
This is called whenever a session has been resynced with the portal.
|
||||
At this point all relevant attributes have already been set and self.player
|
||||
been assigned (if applicable).
|
||||
At this point all relevant attributes have already been set and
|
||||
self.player been assigned (if applicable).
|
||||
|
||||
Since this is often called after a server restart we need to set up
|
||||
the session as it was.
|
||||
|
|
@ -78,7 +79,8 @@ class ServerSession(Session):
|
|||
self.cmdset.update(init_mode=True)
|
||||
|
||||
if self.puid:
|
||||
# reconnect puppet (puid is only set if we are coming back from a server reload)
|
||||
# reconnect puppet (puid is only set if we are coming
|
||||
# back from a server reload)
|
||||
obj = _ObjectDB.objects.get(id=self.puid)
|
||||
self.player.puppet_object(self.sessid, obj, normal_mode=False)
|
||||
|
||||
|
|
@ -139,7 +141,8 @@ class ServerSession(Session):
|
|||
|
||||
def get_puppet_or_player(self):
|
||||
"""
|
||||
Returns session if not logged in; puppet if one exists, otherwise return the player.
|
||||
Returns session if not logged in; puppet if one exists,
|
||||
otherwise return the player.
|
||||
"""
|
||||
if self.logged_in:
|
||||
return self.puppet if self.puppet else self.player
|
||||
|
|
@ -192,7 +195,8 @@ class ServerSession(Session):
|
|||
# merge, give prio to the lowest level (puppet)
|
||||
nicks = list(puppet.db_attributes.filter(db_category__in=("nick_inputline", "nick_channel"))) + list(nicks)
|
||||
raw_list = text.split(None)
|
||||
raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]]
|
||||
raw_list = [" ".join(raw_list[:i + 1])
|
||||
for i in range(len(raw_list)) if raw_list[:i + 1]]
|
||||
for nick in nicks:
|
||||
if nick.db_key in raw_list:
|
||||
text = text.replace(nick.db_key, nick.db_strvalue, 1)
|
||||
|
|
@ -209,7 +213,7 @@ class ServerSession(Session):
|
|||
if funcname:
|
||||
_OOB_HANDLER.execute_cmd(self, funcname, *args, **kwargs)
|
||||
|
||||
execute_cmd = data_in # alias
|
||||
execute_cmd = data_in # alias
|
||||
|
||||
def data_out(self, text=None, **kwargs):
|
||||
"""
|
||||
|
|
@ -255,7 +259,6 @@ class ServerSession(Session):
|
|||
"alias for at_data_out"
|
||||
self.data_out(text=text, **kwargs)
|
||||
|
||||
|
||||
# Dummy API hooks for use during non-loggedin operation
|
||||
|
||||
def at_cmdset_get(self):
|
||||
|
|
@ -282,6 +285,7 @@ class ServerSession(Session):
|
|||
def all(self):
|
||||
return [val for val in self.__dict__.keys()
|
||||
if not val.startswith['_']]
|
||||
|
||||
def __getattribute__(self, key):
|
||||
# return None if no matching attribute was found.
|
||||
try:
|
||||
|
|
@ -290,12 +294,14 @@ class ServerSession(Session):
|
|||
return None
|
||||
self._ndb_holder = NdbHolder()
|
||||
return self._ndb_holder
|
||||
|
||||
#@ndb.setter
|
||||
def ndb_set(self, value):
|
||||
"Stop accidentally replacing the db object"
|
||||
string = "Cannot assign directly to ndb object! "
|
||||
string = "Use ndb.attr=value instead."
|
||||
raise Exception(string)
|
||||
|
||||
#@ndb.deleter
|
||||
def ndb_del(self):
|
||||
"Stop accidental deletion."
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ on Portal and Server side) should inherit from this class.
|
|||
|
||||
import time
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Server Session
|
||||
#------------------------------------------------------------
|
||||
|
|
@ -18,23 +19,24 @@ class Session(object):
|
|||
Each connection will see two session instances created:
|
||||
|
||||
1) A Portal session. This is customized for the respective connection
|
||||
protocols that Evennia supports, like Telnet, SSH etc. The Portal session
|
||||
must call init_session() as part of its initialization. The respective
|
||||
hook methods should be connected to the methods unique for the respective
|
||||
protocol so that there is a unified interface to Evennia.
|
||||
2) A Server session. This is the same for all connected players, regardless
|
||||
of how they connect.
|
||||
protocols that Evennia supports, like Telnet, SSH etc. The Portal
|
||||
session must call init_session() as part of its initialization. The
|
||||
respective hook methods should be connected to the methods unique
|
||||
for the respective protocol so that there is a unified interface
|
||||
to Evennia.
|
||||
2) A Server session. This is the same for all connected players,
|
||||
regardless of how they connect.
|
||||
|
||||
The Portal and Server have their own respective sessionhandlers. These are synced
|
||||
whenever new connections happen or the Server restarts etc, which means much of the
|
||||
same information must be stored in both places e.g. the portal can re-sync with the
|
||||
server when the server reboots.
|
||||
The Portal and Server have their own respective sessionhandlers. These
|
||||
are synced whenever new connections happen or the Server restarts etc,
|
||||
which means much of the same information must be stored in both places
|
||||
e.g. the portal can re-sync with the server when the server reboots.
|
||||
|
||||
"""
|
||||
|
||||
# names of attributes that should be affected by syncing.
|
||||
_attrs_to_sync = ('protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
|
||||
'logged_in', 'puid', 'encoding',
|
||||
_attrs_to_sync = ('protocol_key', 'address', 'suid', 'sessid', 'uid',
|
||||
'uname', 'logged_in', 'puid', 'encoding',
|
||||
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
|
||||
'protocol_flags', 'server_data', "cmdset_storage_string")
|
||||
|
||||
|
|
@ -55,7 +57,7 @@ class Session(object):
|
|||
self.suid = None
|
||||
|
||||
# unique id for this session
|
||||
self.sessid = 0 # no sessid yet
|
||||
self.sessid = 0 # no sessid yet
|
||||
# database id for the user connected to this session
|
||||
self.uid = None
|
||||
# user name, for easier tracking of sessions
|
||||
|
|
@ -84,7 +86,8 @@ class Session(object):
|
|||
"""
|
||||
Return all data relevant to sync the session
|
||||
"""
|
||||
return dict((key, value) for key, value in self.__dict__.items() if key in self._attrs_to_sync)
|
||||
return dict((key, value) for key, value in self.__dict__.items()
|
||||
if key in self._attrs_to_sync)
|
||||
|
||||
def load_sync_data(self, sessdata):
|
||||
"""
|
||||
|
|
@ -124,4 +127,3 @@ class Session(object):
|
|||
hook for protocols to send incoming data to the engine.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ There are two similar but separate stores of sessions:
|
|||
import time
|
||||
from django.conf import settings
|
||||
from src.commands.cmdhandler import CMD_LOGINSTART
|
||||
from src.utils.utils import variable_from_module, to_str
|
||||
from src.utils.utils import variable_from_module
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
|
|
@ -29,14 +29,14 @@ _ScriptDB = None
|
|||
|
||||
|
||||
# AMP signals
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server session sync
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server session sync
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
|
@ -45,6 +45,7 @@ SERVERNAME = settings.SERVERNAME
|
|||
MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||
|
||||
|
||||
def delayed_import():
|
||||
"Helper method for delayed import of all needed entities"
|
||||
global _ServerSession, _PlayerDB, _ServerConfig, _ScriptDB
|
||||
|
|
@ -61,6 +62,7 @@ def delayed_import():
|
|||
# including once to avoid warnings in Python syntax checkers
|
||||
_ServerSession, _PlayerDB, _ServerConfig, _ScriptDB
|
||||
|
||||
|
||||
#-----------------------------------------------------------
|
||||
# SessionHandler base class
|
||||
#------------------------------------------------------------
|
||||
|
|
@ -110,7 +112,8 @@ class SessionHandler(object):
|
|||
(cmdname, (args,), {kwargs})
|
||||
((cmdname, (arg1,arg2)), cmdname, (cmdname, (arg1,)))
|
||||
outputs an ordered structure on the form
|
||||
((cmdname, (args,), {kwargs}), ...), where the two last parts of each tuple may be empty
|
||||
((cmdname, (args,), {kwargs}), ...), where the two last
|
||||
parts of each tuple may be empty
|
||||
"""
|
||||
def _parse(oobstruct):
|
||||
slen = len(oobstruct)
|
||||
|
|
@ -119,8 +122,9 @@ class SessionHandler(object):
|
|||
elif not hasattr(oobstruct, "__iter__"):
|
||||
# a singular command name, without arguments or kwargs
|
||||
return (oobstruct.lower(), (), {})
|
||||
# regardless of number of args/kwargs, the first element must be the function name.
|
||||
# we will not catch this error if not, but allow it to propagate.
|
||||
# regardless of number of args/kwargs, the first element must be
|
||||
# the function name. We will not catch this error if not, but
|
||||
# allow it to propagate.
|
||||
if slen == 1:
|
||||
return (oobstruct[0].lower(), (), {})
|
||||
elif slen == 2:
|
||||
|
|
@ -135,7 +139,9 @@ class SessionHandler(object):
|
|||
return (oobstruct[0].lower(), tuple(oobstruct[1]), dict(oobstruct[2]))
|
||||
|
||||
if hasattr(oobstruct, "__iter__"):
|
||||
# differentiate between (cmdname, cmdname), (cmdname, args, kwargs) and ((cmdname,args,kwargs), (cmdname,args,kwargs), ...)
|
||||
# differentiate between (cmdname, cmdname),
|
||||
# (cmdname, args, kwargs) and ((cmdname,args,kwargs),
|
||||
# (cmdname,args,kwargs), ...)
|
||||
|
||||
if oobstruct and isinstance(oobstruct[0], basestring):
|
||||
return (tuple(_parse(oobstruct)),)
|
||||
|
|
@ -146,6 +152,7 @@ class SessionHandler(object):
|
|||
return (tuple(out),)
|
||||
return (_parse(oobstruct),)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Server-SessionHandler class
|
||||
#------------------------------------------------------------
|
||||
|
|
@ -171,7 +178,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
"""
|
||||
self.sessions = {}
|
||||
self.server = None
|
||||
self.server_data = {"servername":SERVERNAME}
|
||||
self.server_data = {"servername": SERVERNAME}
|
||||
|
||||
def portal_connect(self, portalsession):
|
||||
"""
|
||||
|
|
@ -189,7 +196,8 @@ class ServerSessionHandler(SessionHandler):
|
|||
sess.sessionhandler = self
|
||||
sess.load_sync_data(portalsession)
|
||||
if sess.logged_in and sess.uid:
|
||||
# this can happen in the case of auto-authenticating protocols like SSH
|
||||
# this can happen in the case of auto-authenticating
|
||||
# protocols like SSH
|
||||
sess.player = _PlayerDB.objects.get_player_from_uid(sess.uid)
|
||||
sess.at_sync()
|
||||
# validate all script
|
||||
|
|
@ -216,17 +224,19 @@ class ServerSessionHandler(SessionHandler):
|
|||
|
||||
def portal_session_sync(self, portalsessions):
|
||||
"""
|
||||
Syncing all session ids of the portal with the ones of the server. This is instantiated
|
||||
by the portal when reconnecting.
|
||||
Syncing all session ids of the portal with the ones of the
|
||||
server. This is instantiated by the portal when reconnecting.
|
||||
|
||||
portalsessions is a dictionary {sessid: {property:value},...} defining
|
||||
each session and the properties in it which should be synced.
|
||||
each session and the properties in it which should
|
||||
be synced.
|
||||
"""
|
||||
delayed_import()
|
||||
global _ServerSession, _PlayerDB, _ServerConfig, _ScriptDB
|
||||
|
||||
for sess in self.sessions.values():
|
||||
# we delete the old session to make sure to catch eventual lingering references.
|
||||
# we delete the old session to make sure to catch eventual
|
||||
# lingering references.
|
||||
del sess
|
||||
|
||||
for sessid, sessdict in portalsessions.items():
|
||||
|
|
@ -238,7 +248,8 @@ class ServerSessionHandler(SessionHandler):
|
|||
self.sessions[sessid] = sess
|
||||
sess.at_sync()
|
||||
|
||||
# after sync is complete we force-validate all scripts (this also starts them)
|
||||
# after sync is complete we force-validate all scripts
|
||||
# (this also starts them)
|
||||
init_mode = _ServerConfig.objects.conf("server_restart_mode", default=None)
|
||||
_ScriptDB.objects.validate(init_mode=init_mode)
|
||||
_ServerConfig.objects.conf("server_restart_mode", delete=True)
|
||||
|
|
@ -333,7 +344,6 @@ class ServerSessionHandler(SessionHandler):
|
|||
operation=SSYNC,
|
||||
data=sessdata)
|
||||
|
||||
|
||||
def disconnect_all_sessions(self, reason=_("You have been disconnected.")):
|
||||
"""
|
||||
Cleanly disconnect all of the connected sessions.
|
||||
|
|
@ -346,7 +356,8 @@ class ServerSessionHandler(SessionHandler):
|
|||
operation=SDISCONNALL,
|
||||
data=reason)
|
||||
|
||||
def disconnect_duplicate_sessions(self, curr_session, reason = _("Logged in from elsewhere. Disconnecting.") ):
|
||||
def disconnect_duplicate_sessions(self, curr_session,
|
||||
reason=_("Logged in from elsewhere. Disconnecting.")):
|
||||
"""
|
||||
Disconnects any existing sessions with the same user.
|
||||
"""
|
||||
|
|
@ -364,7 +375,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
and see if any are dead.
|
||||
"""
|
||||
tcurr = time.time()
|
||||
reason= _("Idle timeout exceeded, disconnecting.")
|
||||
reason = _("Idle timeout exceeded, disconnecting.")
|
||||
for session in (session for session in self.sessions.values()
|
||||
if session.logged_in and IDLE_TIMEOUT > 0
|
||||
and (tcurr - session.cmd_last) > IDLE_TIMEOUT):
|
||||
|
|
@ -407,7 +418,6 @@ class ServerSessionHandler(SessionHandler):
|
|||
return self.sessions.get(sessid)
|
||||
return None
|
||||
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
Send message to all connected sessions
|
||||
|
|
@ -422,6 +432,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
self.server.amp_protocol.call_remote_MsgServer2Portal(sessid=session.sessid,
|
||||
msg=text,
|
||||
data=kwargs)
|
||||
|
||||
def data_in(self, sessid, text="", **kwargs):
|
||||
"""
|
||||
Data Portal -> Server
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import urlparse
|
|||
from urllib import quote as urlquote
|
||||
from twisted.web import resource, http
|
||||
from twisted.internet import reactor
|
||||
from twisted.application import service, internet
|
||||
from twisted.application import internet
|
||||
from twisted.web.proxy import ReverseProxyResource
|
||||
from twisted.web.server import NOT_DONE_YET
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ from django.core.handlers.wsgi import WSGIHandler
|
|||
|
||||
from settings import UPSTREAM_IPS
|
||||
|
||||
|
||||
#
|
||||
# X-Forwarded-For Handler
|
||||
#
|
||||
|
|
@ -40,13 +41,14 @@ class HTTPChannelWithXForwardedFor(http.HTTPChannel):
|
|||
proxy_chain = req.getHeader('X-FORWARDED-FOR')
|
||||
if proxy_chain and client_ip in UPSTREAM_IPS:
|
||||
forwarded = proxy_chain.split(', ', 1)[CLIENT]
|
||||
self.transport.client = (forwarded, port)
|
||||
self.transport.client = (forwarded, port)
|
||||
|
||||
|
||||
# Monkey-patch Twisted to handle X-Forwarded-For.
|
||||
|
||||
http.HTTPFactory.protocol = HTTPChannelWithXForwardedFor
|
||||
|
||||
|
||||
class EvenniaReverseProxyResource(ReverseProxyResource):
|
||||
def getChild(self, path, request):
|
||||
"""
|
||||
|
|
@ -58,7 +60,6 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
|
|||
self.host, self.port, self.path + '/' + urlquote(path, safe=""),
|
||||
self.reactor)
|
||||
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Render a request by forwarding it to the proxied server.
|
||||
|
|
@ -77,6 +78,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
|
|||
self.reactor.connectTCP(self.host, self.port, clientFactory)
|
||||
return NOT_DONE_YET
|
||||
|
||||
|
||||
#
|
||||
# Website server resource
|
||||
#
|
||||
|
|
@ -92,7 +94,7 @@ class DjangoWebRoot(resource.Resource):
|
|||
Setup the django+twisted resource
|
||||
"""
|
||||
resource.Resource.__init__(self)
|
||||
self.wsgi_resource = WSGIResource(reactor, pool , WSGIHandler())
|
||||
self.wsgi_resource = WSGIResource(reactor, pool, WSGIHandler())
|
||||
|
||||
def getChild(self, path, request):
|
||||
"""
|
||||
|
|
@ -102,6 +104,8 @@ class DjangoWebRoot(resource.Resource):
|
|||
path0 = request.prepath.pop(0)
|
||||
request.postpath.insert(0, path0)
|
||||
return self.wsgi_resource
|
||||
|
||||
|
||||
#
|
||||
# Threaded Webserver
|
||||
#
|
||||
|
|
@ -114,14 +118,16 @@ class WSGIWebServer(internet.TCPServer):
|
|||
|
||||
call with WSGIWebServer(threadpool, port, wsgi_resource)
|
||||
"""
|
||||
def __init__(self, pool, *args, **kwargs ):
|
||||
def __init__(self, pool, *args, **kwargs):
|
||||
"This just stores the threadpool"
|
||||
self.pool = pool
|
||||
internet.TCPServer.__init__(self, *args, **kwargs)
|
||||
|
||||
def startService(self):
|
||||
"Start the pool after the service"
|
||||
internet.TCPServer.startService(self)
|
||||
self.pool.start()
|
||||
|
||||
def stopService(self):
|
||||
"Safely stop the pool after service stop."
|
||||
internet.TCPServer.stopService(self)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ TELNET_INTERFACES = ['0.0.0.0']
|
|||
# full use of OOB, you need to prepare functions to handle the data
|
||||
# server-side (see OOB_FUNC_MODULE). TELNET_ENABLED is required for this
|
||||
# to work.
|
||||
TELNET_OOB_ENABLED = False # OBS - currently not fully implemented - do not use!
|
||||
TELNET_OOB_ENABLED = False
|
||||
# Start the evennia django+twisted webserver so you can
|
||||
# browse the evennia website and the admin interface
|
||||
# (Obs - further web configuration can be found below
|
||||
|
|
@ -85,8 +85,9 @@ LOG_DIR = os.path.join(GAME_DIR, 'logs')
|
|||
SERVER_LOG_FILE = os.path.join(LOG_DIR, 'server.log')
|
||||
PORTAL_LOG_FILE = os.path.join(LOG_DIR, 'portal.log')
|
||||
HTTP_LOG_FILE = os.path.join(LOG_DIR, 'http_requests.log')
|
||||
# Rotate log files when server and/or portal stops. This will keep log file sizes down.
|
||||
# Turn off to get ever growing log files and never loose log info.
|
||||
# Rotate log files when server and/or portal stops. This will keep log
|
||||
# file sizes down. Turn off to get ever growing log files and never
|
||||
# loose log info.
|
||||
CYCLE_LOGFILES = True
|
||||
# Local time zone for this installation. All choices can be found here:
|
||||
# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
||||
|
|
@ -114,20 +115,22 @@ ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"]
|
|||
# The game server opens an AMP port so that the portal can
|
||||
# communicate with it. This is an internal functionality of Evennia, usually
|
||||
# operating between two processes on the same machine. You usually don't need to
|
||||
# change this unless you cannot use the default AMP port/host for whatever reason.
|
||||
# change this unless you cannot use the default AMP port/host for
|
||||
# whatever reason.
|
||||
AMP_HOST = 'localhost'
|
||||
AMP_PORT = 5000
|
||||
AMP_INTERFACE = '127.0.0.1'
|
||||
# Caching speeds up all forms of database access, often considerably. There
|
||||
# are (currently) only two settings, "local" or None, the latter of which turns
|
||||
# off all caching completely. Local caching stores data in the process. It's very
|
||||
# fast but will go out of sync if more than one process writes to the database (such
|
||||
# as when using procpool or an extensice web precense).
|
||||
# off all caching completely. Local caching stores data in the process. It's
|
||||
# very fast but will go out of sync if more than one process writes to the
|
||||
# database (such as when using procpool or an extensice web precense).
|
||||
GAME_CACHE_TYPE = "local"
|
||||
# Attributes on objects are cached aggressively for speed. If the number of
|
||||
# objects is large (and their attributes are often accessed) this can use up a lot of
|
||||
# memory. So every now and then Evennia checks the size of this cache and resets
|
||||
# it if it's too big. This variable sets the maximum size (in MB).
|
||||
# objects is large (and their attributes are often accessed) this can use up
|
||||
# a lot of memory. So every now and then Evennia checks the size of this
|
||||
# cache and resets it if it's too big. This variable sets the maximum
|
||||
# size (in MB).
|
||||
ATTRIBUTE_CACHE_MAXSIZE = 100
|
||||
|
||||
######################################################################
|
||||
|
|
@ -146,13 +149,13 @@ ATTRIBUTE_CACHE_MAXSIZE = 100
|
|||
# HOST - empty string is localhost (unused in sqlite3)
|
||||
# PORT - empty string defaults to localhost (unused in sqlite3)
|
||||
DATABASES = {
|
||||
'default':{
|
||||
'ENGINE':'django.db.backends.sqlite3',
|
||||
'NAME':os.path.join(GAME_DIR, 'evennia.db3'),
|
||||
'USER':'',
|
||||
'PASSWORD':'',
|
||||
'HOST':'',
|
||||
'PORT':''
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(GAME_DIR, 'evennia.db3'),
|
||||
'USER': '',
|
||||
'PASSWORD': '',
|
||||
'HOST': '',
|
||||
'PORT': ''
|
||||
}}
|
||||
|
||||
######################################################################
|
||||
|
|
@ -185,12 +188,14 @@ AT_INITIAL_SETUP_HOOK_MODULE = ""
|
|||
# at_server_stop() methods. These methods will be called every time
|
||||
# the server starts, reloads and resets/stops respectively.
|
||||
AT_SERVER_STARTSTOP_MODULE = ""
|
||||
# List of one or more module paths to modules containing a function start_plugin_services(application). This module
|
||||
# will be called with the main Evennia Server application when the Server is initiated.
|
||||
# List of one or more module paths to modules containing a function start_
|
||||
# plugin_services(application). This module will be called with the main
|
||||
# Evennia Server application when the Server is initiated.
|
||||
# It will be called last in the startup sequence.
|
||||
SERVER_SERVICES_PLUGIN_MODULES = []
|
||||
# List of one or more module paths to modules containing a function start_plugin_services(application). This module
|
||||
# will be called with the main Evennia Portal application when the Portal is initiated.
|
||||
# List of one or more module paths to modules containing a function
|
||||
# start_plugin_services(application). This module will be called with the
|
||||
# main Evennia Portal application when the Portal is initiated.
|
||||
# It will be called last in the startup sequence.
|
||||
PORTAL_SERVICES_PLUGIN_MODULES = []
|
||||
# Module holding MSSP meta data. This is used by MUD-crawlers to determine
|
||||
|
|
@ -208,9 +213,9 @@ OOB_PLUGIN_MODULE = "src.server.oob_msdp"
|
|||
# Default command sets
|
||||
######################################################################
|
||||
# Note that with the exception of the unloggedin set (which is not
|
||||
# stored anywhere in the databse), changing these paths will only affect NEW created
|
||||
# characters/objects, not those already in play. So if you plan to change
|
||||
# this, it's recommended you do it before having created a lot of objects
|
||||
# stored anywhere in the databse), changing these paths will only affect
|
||||
# NEW created characters/objects, not those already in play. So if you plan to
|
||||
# change this, it's recommended you do it before having created a lot of objects
|
||||
# (or simply reset the database after the change for simplicity). Remember
|
||||
# that you should never edit things in src/. Instead copy out the examples
|
||||
# in game/gamesrc/commands/examples up one level and re-point these settings
|
||||
|
|
@ -236,8 +241,12 @@ SERVER_SESSION_CLASS = "src.server.serversession.ServerSession"
|
|||
# Base paths for typeclassed object classes. These paths must be
|
||||
# defined relative evennia's root directory. They will be searched in
|
||||
# order to find relative typeclass paths.
|
||||
OBJECT_TYPECLASS_PATHS = ["game.gamesrc.objects", "game.gamesrc.objects.examples", "contrib"]
|
||||
SCRIPT_TYPECLASS_PATHS = ["game.gamesrc.scripts", "game.gamesrc.scripts.examples", "contrib"]
|
||||
OBJECT_TYPECLASS_PATHS = ["game.gamesrc.objects",
|
||||
"game.gamesrc.objects.examples",
|
||||
"contrib"]
|
||||
SCRIPT_TYPECLASS_PATHS = ["game.gamesrc.scripts",
|
||||
"game.gamesrc.scripts.examples",
|
||||
"contrib"]
|
||||
PLAYER_TYPECLASS_PATHS = ["game.gamesrc.objects", "contrib"]
|
||||
COMM_TYPECLASS_PATHS = ["game.gamesrc.objects", "contrib"]
|
||||
|
||||
|
|
@ -304,20 +313,31 @@ TIME_MONTH_PER_YEAR = 12
|
|||
# Default Player setup and access
|
||||
######################################################################
|
||||
|
||||
# Different Multisession modes allow a player (=account) to connect to the game simultaneously
|
||||
# with multiple clients (=sessions). In modes 0,1 there is only one character created to the same
|
||||
# name as the account at first login. In modes 1,2 no default character will be created and
|
||||
# the MAX_NR_CHARACTERS value (below) defines how many characters are allowed.
|
||||
# 0 - single session, one player, one character, when a new session is connected, the old one is disconnected
|
||||
# 1 - multiple sessions, one player, one character, each session getting the same data
|
||||
# 2 - multiple sessions, one player, many characters, each session getting data from different characters
|
||||
# Different Multisession modes allow a player (=account) to connect to the
|
||||
# game simultaneously with multiple clients (=sessions). In modes 0,1 there is
|
||||
# only one character created to the same name as the account at first login.
|
||||
# In modes 1,2 no default character will be created and the MAX_NR_CHARACTERS
|
||||
# value (below) defines how many characters are allowed.
|
||||
# 0 - single session, one player, one character, when a new session is
|
||||
# connected, the old one is disconnected
|
||||
# 1 - multiple sessions, one player, one character, each session getting
|
||||
# the same data
|
||||
# 2 - multiple sessions, one player, many characters, each session getting
|
||||
# data from different characters
|
||||
MULTISESSION_MODE = 0
|
||||
# The maximum number of characters allowed for MULTISESSION_MODE 2. This is checked
|
||||
# by the default ooc char-creation command. Forced to 1 for MULTISESSION_MODE 0 and 1.
|
||||
# The maximum number of characters allowed for MULTISESSION_MODE 2. This is
|
||||
# checked
|
||||
# by the default ooc char-creation command. Forced to 1 for
|
||||
# MULTISESSION_MODE 0 and 1.
|
||||
MAX_NR_CHARACTERS = 1
|
||||
# The access hiearchy, in climbing order. A higher permission in the
|
||||
# hierarchy includes access of all levels below it. Used by the perm()/pperm() lock functions.
|
||||
PERMISSION_HIERARCHY = ("Players","PlayerHelpers","Builders", "Wizards", "Immortals")
|
||||
# hierarchy includes access of all levels below it. Used by the perm()/pperm()
|
||||
# lock functions.
|
||||
PERMISSION_HIERARCHY = ("Players",
|
||||
"PlayerHelpers",
|
||||
"Builders",
|
||||
"Wizards",
|
||||
"Immortals")
|
||||
# The default permission given to all new players
|
||||
PERMISSION_PLAYER_DEFAULT = "Players"
|
||||
|
||||
|
|
@ -334,10 +354,10 @@ CHANNEL_PUBLIC = ("Public", ('ooc',), 'Public discussion',
|
|||
"control:perm(Wizards);listen:all();send:all()")
|
||||
# General info about the server
|
||||
CHANNEL_MUDINFO = ("MUDinfo", '', 'Informative messages',
|
||||
"control:perm(Immortals);listen:perm(Immortals);send:false()")
|
||||
"control:perm(Immortals);listen:perm(Immortals);send:false()")
|
||||
# Channel showing when new people connecting
|
||||
CHANNEL_CONNECTINFO = ("MUDconnections", '', 'Connection log',
|
||||
"control:perm(Immortals);listen:perm(Wizards);send:false()")
|
||||
"control:perm(Immortals);listen:perm(Wizards);send:false()")
|
||||
|
||||
######################################################################
|
||||
# External Channel connections
|
||||
|
|
@ -461,7 +481,7 @@ TEMPLATE_LOADERS = (
|
|||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware', # 1.4?
|
||||
'django.contrib.messages.middleware.MessageMiddleware', # 1.4?
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.middleware.doc.XViewMiddleware',
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -13,6 +13,7 @@ from src.utils.dbserialize import to_pickle
|
|||
__all__ = ("AttributeManager", "TypedObjectManager")
|
||||
_GA = object.__getattribute__
|
||||
|
||||
|
||||
# Managers
|
||||
|
||||
def _attr_pickled(method):
|
||||
|
|
@ -29,24 +30,30 @@ def _attr_pickled(method):
|
|||
return method(self, *args, **kwargs)
|
||||
return update_wrapper(wrapper, method)
|
||||
|
||||
|
||||
class AttributeManager(models.Manager):
|
||||
"Manager for handling Attributes."
|
||||
@_attr_pickled
|
||||
def get(self, *args, **kwargs):
|
||||
return super(AttributeManager, self).get(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
def filter(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).filter(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
def exclude(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).exclude(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
def values(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).values(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
def values_list(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).values_list(*args, **kwargs)
|
||||
@_attr_pickled
|
||||
|
||||
def exists(self,*args, **kwargs):
|
||||
return super(AttributeManager, self).exists(*args, **kwargs)
|
||||
|
||||
|
|
@ -113,11 +120,11 @@ class TagManager(models.Manager):
|
|||
search_key (string) - the tag identifier
|
||||
category (string) - the tag category
|
||||
|
||||
Returns a single Tag (or None) if both key and category is given, otherwise
|
||||
it will return a list.
|
||||
Returns a single Tag (or None) if both key and category is given,
|
||||
otherwise it will return a list.
|
||||
"""
|
||||
key_cands = Q(db_key__iexact=key.lower().strip()) if key!=None else Q()
|
||||
cat_cands = Q(db_category__iexact=category.lower().strip()) if category!=None else Q()
|
||||
key_cands = Q(db_key__iexact=key.lower().strip()) if key is not None else Q()
|
||||
cat_cands = Q(db_category__iexact=category.lower().strip()) if category is not None else Q()
|
||||
tags = self.filter(key_cands & cat_cands)
|
||||
if key and category:
|
||||
return tags[0] if tags else None
|
||||
|
|
@ -132,8 +139,8 @@ class TagManager(models.Manager):
|
|||
key (string) - the tag identifier
|
||||
category (string) - the tag category
|
||||
"""
|
||||
key_cands = Q(db_tags__db_key__iexact=key.lower().strip()) if search_key!=None else Q()
|
||||
cat_cands = Q(db_tags__db_category__iexact=category.lower().strip()) if category!=None else Q()
|
||||
key_cands = Q(db_tags__db_key__iexact=key.lower().strip()) if key is not None else Q()
|
||||
cat_cands = Q(db_tags__db_category__iexact=category.lower().strip()) if category is not None else Q()
|
||||
return objclass.objects.filter(key_cands & cat_cands)
|
||||
|
||||
def create_tag(self, key=None, category=None, data=None):
|
||||
|
|
@ -144,19 +151,21 @@ class TagManager(models.Manager):
|
|||
data on a tag (it is not part of what makes the tag unique).
|
||||
|
||||
"""
|
||||
data = str(data) if data!=None else None
|
||||
data = str(data) if data is not None else None
|
||||
|
||||
tag = self.get_tag(key=key, category=category)
|
||||
if tag and data != None:
|
||||
if tag and data is not None:
|
||||
tag.db_data = data
|
||||
tag.save()
|
||||
elif not tag:
|
||||
tag = self.create(db_key=key.lower().strip() if key!=None else None,
|
||||
db_category=category.lower().strip() if category and key!=None else None,
|
||||
db_data=str(data) if data!=None else None)
|
||||
tag = self.create(db_key=key.lower().strip() if key is not None else None,
|
||||
db_category=category.lower().strip()
|
||||
if category and key is not None else None,
|
||||
db_data=str(data) if data is not None else None)
|
||||
tag.save()
|
||||
return make_iter(tag)[0]
|
||||
|
||||
|
||||
#
|
||||
# helper functions for the TypedObjectManager.
|
||||
#
|
||||
|
|
@ -171,9 +180,11 @@ def returns_typeclass_list(method):
|
|||
"decorator. Returns a list."
|
||||
self.__doc__ = method.__doc__
|
||||
matches = make_iter(method(self, *args, **kwargs))
|
||||
return [(hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj for dbobj in make_iter(matches)]
|
||||
return [(hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj
|
||||
for dbobj in make_iter(matches)]
|
||||
return update_wrapper(func, method)
|
||||
|
||||
|
||||
def returns_typeclass(method):
|
||||
"""
|
||||
Decorator: Will always return a single typeclassed result or None.
|
||||
|
|
@ -184,7 +195,7 @@ def returns_typeclass(method):
|
|||
matches = method(self, *args, **kwargs)
|
||||
dbobj = matches and make_iter(matches)[0] or None
|
||||
if dbobj:
|
||||
return (hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj
|
||||
return (hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj
|
||||
return None
|
||||
return update_wrapper(func, method)
|
||||
|
||||
|
|
@ -240,9 +251,9 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
|||
given boundaries.
|
||||
"""
|
||||
retval = super(TypedObjectManager, self).all()
|
||||
if min_dbref != None:
|
||||
if min_dbref is not None:
|
||||
retval = retval.filter(id__gte=self.dbref(min_dbref, reqhash=False))
|
||||
if max_dbref != None:
|
||||
if max_dbref is not None:
|
||||
retval = retval.filter(id__lte=self.dbref(max_dbref, reqhash=False))
|
||||
return retval
|
||||
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ class Attribute(SharedMemoryModel):
|
|||
# self.cached_value = value
|
||||
# self.no_cache = False
|
||||
#return self.cached_value
|
||||
|
||||
#@value.setter
|
||||
def __value_set(self, new_value):
|
||||
"""
|
||||
|
|
@ -168,13 +169,13 @@ class Attribute(SharedMemoryModel):
|
|||
# self._track_db_value_change.update(self.cached_value)
|
||||
#except AttributeError:
|
||||
# pass
|
||||
|
||||
#@value.deleter
|
||||
def __value_del(self):
|
||||
"Deleter. Allows for del attr.value. This removes the entire attribute."
|
||||
self.delete()
|
||||
value = property(__value_get, __value_set, __value_del)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
# Attribute methods
|
||||
|
|
@ -202,6 +203,7 @@ class Attribute(SharedMemoryModel):
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# Handlers making use of the Attribute model
|
||||
#
|
||||
|
|
@ -226,19 +228,21 @@ class AttributeHandler(object):
|
|||
|
||||
def has(self, key, category=None):
|
||||
"""
|
||||
Checks if the given Attribute (or list of Attributes) exists on the object.
|
||||
Checks if the given Attribute (or list of Attributes) exists on
|
||||
the object.
|
||||
|
||||
If an iterable is given, returns list of booleans.
|
||||
"""
|
||||
if self._cache == None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
self._recache()
|
||||
catkey = to_str(category, force_string=True).lower()
|
||||
searchkeys = ["%s_%s" % (k.lower(), catkey) for k in make_iter(key)]
|
||||
ret = [self._cache[skey] for skey in searchkeys if skey in self._cache]
|
||||
return ret[0] if len(ret) == 1 else ret
|
||||
|
||||
def get(self, key=None, category=None, default=None, return_obj=False, strattr=False,
|
||||
raise_exception=False, accessing_obj=None, default_access=True):
|
||||
def get(self, key=None, category=None, default=None, return_obj=False,
|
||||
strattr=False, raise_exception=False, accessing_obj=None,
|
||||
default_access=True):
|
||||
"""
|
||||
Returns the value of the given Attribute or list of Attributes.
|
||||
strattr will cause the string-only value field instead of the normal
|
||||
|
|
@ -253,7 +257,7 @@ class AttributeHandler(object):
|
|||
checked before displaying each looked-after Attribute. If no
|
||||
accessing_obj is given, no check will be done.
|
||||
"""
|
||||
if self._cache == None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
self._recache()
|
||||
ret = []
|
||||
catkey = to_str(category, force_string=True).lower()
|
||||
|
|
@ -280,32 +284,36 @@ class AttributeHandler(object):
|
|||
ret = ret if return_obj else [attr.value if attr else None for attr in ret]
|
||||
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):
|
||||
def add(self, key, value, category=None, lockstring="",
|
||||
strattr=False, accessing_obj=None, default_access=True):
|
||||
"""
|
||||
Add attribute to object, with optional lockstring.
|
||||
|
||||
If strattr is set, the db_strvalue field will be used (no pickling). Use the get() method
|
||||
with the strattr keyword to get it back.
|
||||
If strattr is set, the db_strvalue field will be used (no pickling).
|
||||
Use the get() method with the strattr keyword to get it back.
|
||||
|
||||
If accessing_obj is given, self.obj's 'attrcreate' lock access
|
||||
will be checked against it. If no accessing_obj is given, no check will be done.
|
||||
will be checked against it. If no accessing_obj is given, no check
|
||||
will be done.
|
||||
"""
|
||||
if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access):
|
||||
if accessing_obj and not self.obj.access(accessing_obj,
|
||||
self._attrcreate, default=default_access):
|
||||
# check create access
|
||||
return
|
||||
if self._cache == None:
|
||||
if self._cache is None:
|
||||
self._recache()
|
||||
cachekey = "%s_%s" % (key.lower(), to_str(category, force_string=True).lower())
|
||||
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)
|
||||
attr_obj.save() # important
|
||||
attr_obj.save() # important
|
||||
_GA(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
|
||||
# we shouldn't need to fear stale objects, the field signalling
|
||||
# should catch all cases
|
||||
if strattr:
|
||||
# store as a simple string
|
||||
attr_obj.strvalue = value
|
||||
|
|
@ -315,18 +323,21 @@ class AttributeHandler(object):
|
|||
attr_obj.value = value
|
||||
attr_obj.strvalue = None
|
||||
|
||||
def remove(self, key, raise_exception=False, category=None, accessing_obj=None, default_access=True):
|
||||
def remove(self, key, raise_exception=False, category=None,
|
||||
accessing_obj=None, default_access=True):
|
||||
"""Remove attribute or a list of attributes from object.
|
||||
|
||||
If accessing_obj is given, will check against the 'attredit' lock. If not given, this check is skipped.
|
||||
If accessing_obj is given, will check against the 'attredit' lock.
|
||||
If not given, this check is skipped.
|
||||
"""
|
||||
if self._cache == None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
self._recache()
|
||||
catkey = to_str(category, force_string=True).lower()
|
||||
for keystr in ("%s_%s" % (k.lower(), catkey) for k in make_iter(key)):
|
||||
attr_obj = self._cache.get(keystr)
|
||||
if attr_obj:
|
||||
if accessing_obj and not attr_obj.access(accessing_obj, self._attredit, default=default_access):
|
||||
if accessing_obj and not attr_obj.access(accessing_obj,
|
||||
self._attredit, default=default_access):
|
||||
continue
|
||||
attr_obj.delete()
|
||||
elif not attr_obj and raise_exception:
|
||||
|
|
@ -353,7 +364,7 @@ class AttributeHandler(object):
|
|||
each attribute before returning them. If not given, this
|
||||
check is skipped.
|
||||
"""
|
||||
if self._cache == None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
self._recache()
|
||||
catkey = "_%s" % to_str(category, force_string=True).lower()
|
||||
return [attr for key, attr in self._cache.items() if key.endswith(catkey)]
|
||||
|
|
@ -367,6 +378,7 @@ class AttributeHandler(object):
|
|||
#else:
|
||||
# return list(all_attrs)
|
||||
|
||||
|
||||
class NickHandler(AttributeHandler):
|
||||
"""
|
||||
Handles the addition and removal of Nicks
|
||||
|
|
@ -401,27 +413,33 @@ class NickHandler(AttributeHandler):
|
|||
return super(NickHandler, self).all(category=category)
|
||||
return _GA(self.obj, self._m2m_fieldname).filter(db_category__startswith="nick_")
|
||||
|
||||
|
||||
class NAttributeHandler(object):
|
||||
"""
|
||||
This stand-alone handler manages non-database saved properties by storing them
|
||||
as properties on obj.ndb. It has the same methods as AttributeHandler, but they
|
||||
are much simplified.
|
||||
This stand-alone handler manages non-database saved properties by storing
|
||||
them as properties on obj.ndb. It has the same methods as AttributeHandler,
|
||||
but they are much simplified.
|
||||
"""
|
||||
def __init__(self, obj):
|
||||
"initialized on the object"
|
||||
self.ndb = _GA(obj, "ndb")
|
||||
|
||||
def has(self, key):
|
||||
"Check if object has this attribute or not"
|
||||
return _GA(self.ndb, key) # ndb returns None if not found
|
||||
return _GA(self.ndb, key) # ndb returns None if not found
|
||||
|
||||
def get(self, key):
|
||||
"Returns named key value"
|
||||
return _GA(self.ndb, key)
|
||||
|
||||
def add(self, key, value):
|
||||
"Add new key and value"
|
||||
_SA(self.ndb, key, value)
|
||||
|
||||
def remove(self, key):
|
||||
"Remove key from storage"
|
||||
_DA(self.ndb, key)
|
||||
|
||||
def all(self):
|
||||
"List all keys stored"
|
||||
if callable(self.ndb.all):
|
||||
|
|
@ -429,6 +447,7 @@ class NAttributeHandler(object):
|
|||
else:
|
||||
return [val for val in self.ndb.__dict__.keys() if not val.startswith('_')]
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Tags
|
||||
|
|
@ -456,18 +475,25 @@ class Tag(models.Model):
|
|||
this uses the 'aliases' tag category, which is also checked by the
|
||||
default search functions of Evennia to allow quick searches by alias.
|
||||
"""
|
||||
db_key = models.CharField('key', max_length=255, null=True, help_text="tag identifier", db_index=True)
|
||||
db_category = models.CharField('category', max_length=64, null=True, help_text="tag category", db_index=True)
|
||||
db_data = models.TextField('data', null=True, blank=True, help_text="optional data field with extra information. This is not searched for.")
|
||||
db_key = models.CharField('key', max_length=255, null=True,
|
||||
help_text="tag identifier", db_index=True)
|
||||
db_category = models.CharField('category', max_length=64, null=True,
|
||||
help_text="tag category", db_index=True)
|
||||
db_data = models.TextField('data', null=True, blank=True,
|
||||
help_text="optional data field with extra information. This is not searched for.")
|
||||
|
||||
objects = managers.TagManager()
|
||||
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Tag"
|
||||
unique_together =(('db_key', 'db_category'),)
|
||||
unique_together = (('db_key', 'db_category'),)
|
||||
index_together = (('db_key', 'db_category'),)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s" % self.db_key
|
||||
|
||||
def __str__(self):
|
||||
return str(self.db_key)
|
||||
|
||||
|
|
@ -489,7 +515,8 @@ class TagHandler(object):
|
|||
using the category <category_prefix><tag_category>
|
||||
"""
|
||||
self.obj = obj
|
||||
self.prefix = "%s%s" % (category_prefix.strip().lower() if category_prefix else "", self._base_category)
|
||||
self.prefix = "%s%s" % (category_prefix.strip().lower()
|
||||
if category_prefix else "", self._base_category)
|
||||
self._cache = None
|
||||
|
||||
def _recache(self):
|
||||
|
|
@ -499,38 +526,46 @@ class TagHandler(object):
|
|||
def add(self, tag, category=None, data=None):
|
||||
"Add a new tag to the handler. Tag is a string or a list of strings."
|
||||
for tagstr in make_iter(tag):
|
||||
tagstr = tagstr.strip().lower() if tagstr!=None else None
|
||||
category = "%s%s" % (self.prefix, category.strip().lower() if category!=None else "")
|
||||
data = str(data) if data!=None else None
|
||||
# this will only create tag if no matches existed beforehand (it will overload
|
||||
# data on an existing tag since that is not considered part of making the tag unique)
|
||||
tagstr = tagstr.strip().lower() if tagstr is not None else None
|
||||
category = "%s%s" % (self.prefix, category.strip().lower() if category is not None else "")
|
||||
data = str(data) if data is not None else None
|
||||
# this will only create tag if no matches existed beforehand (it
|
||||
# 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)
|
||||
_GA(self.obj, self._m2m_fieldname).add(tagobj)
|
||||
if self._cache == None:
|
||||
if self._cache is None:
|
||||
self._recache()
|
||||
self._cache[tagstr] = True
|
||||
|
||||
def get(self, key, category="", return_obj=False):
|
||||
"Get the data field for the given tag or list of tags. If return_obj=True, return the matching Tag objects instead."
|
||||
if self._cache == None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
"""
|
||||
Get the data field for the given tag or list of tags. If
|
||||
return_obj=True, return the matching Tag objects instead.
|
||||
"""
|
||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
self._recache()
|
||||
ret = []
|
||||
category = "%s%s" % (self.prefix, category.strip().lower() if category!=None else "")
|
||||
ret = [val for val in (self._cache.get(keystr.strip().lower()) for keystr in make_iter(key)) if val]
|
||||
category = "%s%s" % (self.prefix, category.strip().lower()
|
||||
if category is not None else "")
|
||||
ret = [val for val in (self._cache.get(keystr.strip().lower())
|
||||
for keystr in make_iter(key)) if val]
|
||||
ret = ret if return_obj else [to_str(tag.db_data) for tag in ret if tag]
|
||||
return ret[0] if len(ret)==1 else ret
|
||||
return ret[0] if len(ret) == 1 else ret
|
||||
|
||||
def remove(self, tag, category=None):
|
||||
"Remove a tag from the handler"
|
||||
if self._cache == None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
self._recache()
|
||||
for tag in make_iter(tag):
|
||||
if not (tag or tag.strip()): # we don't allow empty tags
|
||||
if not (tag or tag.strip()): # we don't allow empty tags
|
||||
continue
|
||||
tagstr = tag.strip().lower() if tag!=None else None
|
||||
category = "%s%s" % (self.prefix, category.strip().lower() if category!=None else "")
|
||||
#TODO This does not delete the tag object itself. Maybe it should do that when no
|
||||
# objects reference the tag anymore?
|
||||
tagstr = tag.strip().lower() if tag is not None else None
|
||||
category = "%s%s" % (self.prefix, category.strip().lower()
|
||||
if category is not None else "")
|
||||
|
||||
# This does not delete the tag object itself. Maybe it should do
|
||||
# that when no objects reference the tag anymore (how to check)?
|
||||
tagobj = self.obj.db_tags.filter(db_key=tagstr, db_category=category)
|
||||
if tagobj:
|
||||
_GA(self.obj, self._m2m_fieldname).remove(tagobj[0])
|
||||
|
|
@ -543,19 +578,22 @@ class TagHandler(object):
|
|||
|
||||
def all(self):
|
||||
"Get all tags in this handler"
|
||||
if self._cache == None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
self._recache()
|
||||
return self._cache.keys()
|
||||
#return [to_str(p[0]) for p in _GA(self.obj, self._m2m_fieldname).filter(db_category__startswith=self.prefix).values_list("db_key") if p[0]]
|
||||
|
||||
def __str__(self):
|
||||
return ",".join(self.all())
|
||||
|
||||
def __unicode(self):
|
||||
return u",".join(self.all())
|
||||
|
||||
|
||||
class AliasHandler(TagHandler):
|
||||
_base_category = "alias"
|
||||
|
||||
|
||||
class PermissionHandler(TagHandler):
|
||||
_base_category = "permission"
|
||||
|
||||
|
|
@ -591,19 +629,23 @@ class TypedObject(SharedMemoryModel):
|
|||
# TypedObject Database Model setup
|
||||
#
|
||||
#
|
||||
# These databse fields are all accessed and set using their corresponding properties,
|
||||
# named same as the field, but without the db_* prefix (no separate save() call is needed)
|
||||
# These databse fields are all accessed and set using their corresponding
|
||||
# properties, named same as the field, but without the db_* prefix
|
||||
# (no separate save() call is needed)
|
||||
|
||||
# Main identifier of the object, for searching. Is accessed with self.key or self.name
|
||||
# Main identifier of the object, for searching. Is accessed with self.key
|
||||
# or self.name
|
||||
db_key = models.CharField('key', max_length=255, db_index=True)
|
||||
# This is the python path to the type class this object is tied to the type class is what defines what kind of Object this is)
|
||||
# This is the python path to the type class this object is tied to the
|
||||
# typeclass is what defines what kind of Object this is)
|
||||
db_typeclass_path = models.CharField('typeclass', max_length=255, null=True,
|
||||
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.")
|
||||
# Creation date. This is not changed once the object is created.
|
||||
db_date_created = models.DateTimeField('creation date', editable=False, auto_now_add=True)
|
||||
# Permissions (access these through the 'permissions' property)
|
||||
#db_permissions = models.CharField('permissions', max_length=255, blank=True,
|
||||
# help_text="a comma-separated list of text strings checked by in-game locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. Character objects use 'Players' by default. Most other objects don't have any permissions.")
|
||||
# help_text="a comma-separated list of text strings checked by
|
||||
# in-game locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. Character objects use 'Players' by default. Most other objects don't have any permissions.")
|
||||
# Lock storage
|
||||
db_lock_storage = models.TextField('locks', blank=True,
|
||||
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.")
|
||||
|
|
@ -647,9 +689,14 @@ class TypedObject(SharedMemoryModel):
|
|||
# is the object in question).
|
||||
|
||||
# name property (alias to self.key)
|
||||
def __name_get(self): return self.key
|
||||
def __name_set(self, value): self.key = value
|
||||
def __name_del(self): raise Exception("Cannot delete name")
|
||||
def __name_get(self):
|
||||
return self.key
|
||||
|
||||
def __name_set(self, value):
|
||||
self.key = value
|
||||
|
||||
def __name_del(self):
|
||||
raise Exception("Cannot delete name")
|
||||
name = property(__name_get, __name_set, __name_del)
|
||||
|
||||
#
|
||||
|
|
@ -711,8 +758,10 @@ class TypedObject(SharedMemoryModel):
|
|||
dbid = _GA(self, "id")
|
||||
set_prop_cache(self, "_dbid", dbid)
|
||||
return dbid
|
||||
|
||||
def __dbid_set(self, value):
|
||||
raise Exception("dbid cannot be set!")
|
||||
|
||||
def __dbid_del(self):
|
||||
raise Exception("dbid cannot be deleted!")
|
||||
dbid = property(__dbid_get, __dbid_set, __dbid_del)
|
||||
|
|
@ -723,13 +772,14 @@ class TypedObject(SharedMemoryModel):
|
|||
Returns the object's dbref on the form #NN.
|
||||
"""
|
||||
return "#%s" % _GA(self, "_TypedObject__dbid_get")()
|
||||
|
||||
def __dbref_set(self):
|
||||
raise Exception("dbref cannot be set!")
|
||||
|
||||
def __dbref_del(self):
|
||||
raise Exception("dbref cannot be deleted!")
|
||||
dbref = property(__dbref_get, __dbref_set, __dbref_del)
|
||||
|
||||
|
||||
# typeclass property
|
||||
#@property
|
||||
def __typeclass_get(self):
|
||||
|
|
@ -760,9 +810,10 @@ class TypedObject(SharedMemoryModel):
|
|||
else:
|
||||
# handle loading/importing of typeclasses, searching all paths.
|
||||
# (self._typeclass_paths is a shortcut to settings.TYPECLASS_*_PATHS
|
||||
# where '*' is either OBJECT, SCRIPT or PLAYER depending on the typed
|
||||
# entities).
|
||||
typeclass_paths = [path] + ["%s.%s" % (prefix, path) for prefix in _GA(self, '_typeclass_paths')]
|
||||
# where '*' is either OBJECT, SCRIPT or PLAYER depending on the
|
||||
# typed entities).
|
||||
typeclass_paths = [path] + ["%s.%s" % (prefix, path)
|
||||
for prefix in _GA(self, '_typeclass_paths')]
|
||||
|
||||
for tpath in typeclass_paths:
|
||||
|
||||
|
|
@ -787,8 +838,9 @@ class TypedObject(SharedMemoryModel):
|
|||
errstring += " to specify the actual typeclass name inside the module too."
|
||||
else:
|
||||
errstring += "\n%s" % typeclass # this will hold a growing error message.
|
||||
# If we reach this point we couldn't import any typeclasses. Return default. It's up to the calling
|
||||
# method to use e.g. self.is_typeclass() to detect that the result is not the one asked for.
|
||||
# If we reach this point we couldn't import any typeclasses. Return
|
||||
# default. It's up to the calling method to use e.g. self.is_typeclass()
|
||||
# to detect that the result is not the one asked for.
|
||||
_GA(self, "_display_errmsg")(errstring)
|
||||
_SA(self, "typeclass_lasterrmsg", errstring)
|
||||
return _GA(self, "_get_default_typeclass")(cache=False, silent=False, save=False)
|
||||
|
|
@ -818,12 +870,13 @@ class TypedObject(SharedMemoryModel):
|
|||
return None
|
||||
try:
|
||||
modpath, class_name = path.rsplit('.', 1)
|
||||
module = __import__(modpath, fromlist=["none"])
|
||||
module = __import__(modpath, fromlist=["none"])
|
||||
return module.__dict__[class_name]
|
||||
except ImportError:
|
||||
trc = sys.exc_traceback
|
||||
if not trc.tb_next:
|
||||
# we separate between not finding the module, and finding a buggy one.
|
||||
# we separate between not finding the module, and finding
|
||||
# a buggy one.
|
||||
errstring = "Typeclass not found trying path '%s'." % path
|
||||
else:
|
||||
# a bug in the module is reported normally.
|
||||
|
|
@ -866,7 +919,8 @@ class TypedObject(SharedMemoryModel):
|
|||
|
||||
if not callable(typeclass):
|
||||
# if typeclass still doesn't exist at this point, we're in trouble.
|
||||
# fall back to hardcoded core class which is wrong for e.g. scripts/players etc.
|
||||
# fall back to hardcoded core class which is wrong for e.g.
|
||||
# scripts/players etc.
|
||||
failpath = defpath
|
||||
defpath = "src.objects.objects.Object"
|
||||
typeclass = _GA(self, "_path_import")(defpath)
|
||||
|
|
@ -876,7 +930,8 @@ class TypedObject(SharedMemoryModel):
|
|||
errstring += "\n Using Evennia's default class '%s'." % defpath
|
||||
_GA(self, "_display_errmsg")(errstring)
|
||||
if not callable(typeclass):
|
||||
# if this is still giving an error, Evennia is wrongly configured or buggy
|
||||
# if this is still giving an error, Evennia is wrongly
|
||||
# configured or buggy
|
||||
raise Exception("CRITICAL ERROR: The final fallback typeclass %s cannot load!!" % defpath)
|
||||
typeclass = typeclass(self)
|
||||
if save:
|
||||
|
|
@ -911,15 +966,17 @@ class TypedObject(SharedMemoryModel):
|
|||
typeclass = _GA(typeclass, "path")
|
||||
except AttributeError:
|
||||
pass
|
||||
typeclasses = [typeclass] + ["%s.%s" % (path, typeclass) for path in _GA(self, "_typeclass_paths")]
|
||||
typeclasses = [typeclass] + ["%s.%s" % (path, typeclass)
|
||||
for path in _GA(self, "_typeclass_paths")]
|
||||
if exact:
|
||||
current_path = _GA(self.typeclass, "path") #"_GA(self, "_cached_db_typeclass_path")
|
||||
return typeclass and any((current_path == typec for typec in typeclasses))
|
||||
else:
|
||||
# check parent chain
|
||||
return any((cls for cls in self.typeclass.__class__.mro()
|
||||
if any(("%s.%s" % (_GA(cls,"__module__"), _GA(cls,"__name__")) == typec for typec in typeclasses))))
|
||||
|
||||
if any(("%s.%s" % (_GA(cls, "__module__"),
|
||||
_GA(cls, "__name__")) == typec
|
||||
for typec in typeclasses))))
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -927,7 +984,6 @@ class TypedObject(SharedMemoryModel):
|
|||
"""
|
||||
super(TypedObject, self).delete(*args, **kwargs)
|
||||
|
||||
|
||||
#
|
||||
# Object manipulation methods
|
||||
#
|
||||
|
|
@ -1020,13 +1076,15 @@ class TypedObject(SharedMemoryModel):
|
|||
|
||||
def check_permstring(self, permstring):
|
||||
"""
|
||||
This explicitly checks if we hold particular permission without involving
|
||||
any locks.
|
||||
This explicitly checks if we hold particular permission without
|
||||
involving any locks.
|
||||
"""
|
||||
if hasattr(self, "player"):
|
||||
if self.player and self.player.is_superuser: return True
|
||||
if self.player and self.player.is_superuser:
|
||||
return True
|
||||
else:
|
||||
if self.is_superuser: return True
|
||||
if self.is_superuser:
|
||||
return True
|
||||
|
||||
if not permstring:
|
||||
return False
|
||||
|
|
@ -1048,9 +1106,9 @@ class TypedObject(SharedMemoryModel):
|
|||
|
||||
def flush_from_cache(self):
|
||||
"""
|
||||
Flush this object instance from cache, forcing an object reload. Note that this
|
||||
will kill all temporary attributes on this object since it will be recreated
|
||||
as a new Typeclass instance.
|
||||
Flush this object instance from cache, forcing an object reload.
|
||||
Note that this will kill all temporary attributes on this object
|
||||
since it will be recreated as a new Typeclass instance.
|
||||
"""
|
||||
self.__class__.flush_cached_instance(self)
|
||||
|
||||
|
|
@ -1068,8 +1126,8 @@ class TypedObject(SharedMemoryModel):
|
|||
and
|
||||
del obj.db.attrname
|
||||
and
|
||||
all_attr = obj.db.all (unless there is no attribute named 'all', in which
|
||||
case that will be returned instead).
|
||||
all_attr = obj.db.all() (unless there is an attribute
|
||||
named 'all', in which case that will be returned instead).
|
||||
"""
|
||||
try:
|
||||
return self._db_holder
|
||||
|
|
@ -1079,6 +1137,7 @@ class TypedObject(SharedMemoryModel):
|
|||
def __init__(self, obj):
|
||||
_SA(self, 'obj', obj)
|
||||
_SA(self, "attrhandler", _GA(_GA(self, "obj"), "attributes"))
|
||||
|
||||
def __getattribute__(self, attrname):
|
||||
if attrname == 'all':
|
||||
# we allow to overload our default .all
|
||||
|
|
@ -1087,21 +1146,26 @@ class TypedObject(SharedMemoryModel):
|
|||
return attr
|
||||
return _GA(self, 'all')
|
||||
return _GA(self, "attrhandler").get(attrname)
|
||||
|
||||
def __setattr__(self, attrname, value):
|
||||
_GA(self, "attrhandler").add(attrname, value)
|
||||
|
||||
def __delattr__(self, attrname):
|
||||
_GA(self, "attrhandler").remove(attrname)
|
||||
|
||||
def get_all(self):
|
||||
return _GA(self, "attrhandler").all()
|
||||
all = property(get_all)
|
||||
self._db_holder = DbHolder(self)
|
||||
return self._db_holder
|
||||
|
||||
#@db.setter
|
||||
def __db_set(self, value):
|
||||
"Stop accidentally replacing the db object"
|
||||
string = "Cannot assign directly to db object! "
|
||||
string += "Use db.attr=value instead."
|
||||
raise Exception(string)
|
||||
|
||||
#@db.deleter
|
||||
def __db_del(self):
|
||||
"Stop accidental deletion."
|
||||
|
|
@ -1129,24 +1193,28 @@ class TypedObject(SharedMemoryModel):
|
|||
return [val for val in self.__dict__.keys()
|
||||
if not val.startswith('_')]
|
||||
all = property(get_all)
|
||||
|
||||
def __getattribute__(self, key):
|
||||
# return None if no matching attribute was found.
|
||||
try:
|
||||
return _GA(self, key)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
# hook the oob handler here
|
||||
#call_ndb_hooks(self, key, value)
|
||||
_SA(self, key, value)
|
||||
self._ndb_holder = NdbHolder()
|
||||
return self._ndb_holder
|
||||
|
||||
#@ndb.setter
|
||||
def __ndb_set(self, value):
|
||||
"Stop accidentally replacing the db object"
|
||||
string = "Cannot assign directly to ndb object! "
|
||||
string = "Use ndb.attr=value instead."
|
||||
raise Exception(string)
|
||||
|
||||
#@ndb.deleter
|
||||
def __ndb_del(self):
|
||||
"Stop accidental deletion."
|
||||
|
|
@ -1239,12 +1307,12 @@ class TypedObject(SharedMemoryModel):
|
|||
value to None using this method. Use set_attribute.
|
||||
"""
|
||||
logger.log_depmsg("obj.attr() is deprecated. Use handlers obj.db or obj.attributes.")
|
||||
if attribute_name == None:
|
||||
if attribute_name is None:
|
||||
# act as a list method
|
||||
return _GA(self, "attributes").all()
|
||||
elif delete == True:
|
||||
elif delete is True:
|
||||
_GA(self, "attributes").remove(attribute_name)
|
||||
elif value == None:
|
||||
elif value is None:
|
||||
# act as a getter.
|
||||
return _GA(self, "attributes").get(attribute_name)
|
||||
else:
|
||||
|
|
@ -1272,12 +1340,12 @@ class TypedObject(SharedMemoryModel):
|
|||
|
||||
"""
|
||||
logger.log_depmsg("obj.secure_attr() is deprecated. Use obj.attributes methods, giving accessing_obj keyword.")
|
||||
if attribute_name == None:
|
||||
if attribute_name is None:
|
||||
return _GA(self, "attributes").all(accessing_obj=accessing_object, default_access=default_access_read)
|
||||
elif delete == True:
|
||||
elif delete is True:
|
||||
# act as deleter
|
||||
_GA(self, "attributes").remove(attribute_name, accessing_obj=accessing_object, default_access=default_access_edit)
|
||||
elif value == None:
|
||||
elif value is None:
|
||||
# act as getter
|
||||
return _GA(self, "attributes").get(attribute_name, accessing_obj=accessing_object, default_access=default_access_read)
|
||||
else:
|
||||
|
|
@ -1296,17 +1364,17 @@ class TypedObject(SharedMemoryModel):
|
|||
a method call. Will return None if trying to access a non-existing property.
|
||||
"""
|
||||
logger.log_depmsg("obj.nattr() is deprecated. Use obj.nattributes instead.")
|
||||
if attribute_name == None:
|
||||
if attribute_name is None:
|
||||
# act as a list method
|
||||
if callable(self.ndb.all):
|
||||
return self.ndb.all()
|
||||
else:
|
||||
return [val for val in self.ndb.__dict__.keys()
|
||||
if not val.startswith['_']]
|
||||
elif delete == True:
|
||||
elif delete is True:
|
||||
if hasattr(self.ndb, attribute_name):
|
||||
_DA(_GA(self, "ndb"), attribute_name)
|
||||
elif value == None:
|
||||
elif value is None:
|
||||
# act as a getter.
|
||||
if hasattr(self.ndb, attribute_name):
|
||||
_GA(_GA(self, "ndb"), attribute_name)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ PROTECTED = ('id', 'dbobj', 'db', 'ndb', 'objects', 'typeclass', 'db_player',
|
|||
'attr', 'save', 'delete', 'db_model_name','attribute_class',
|
||||
'typeclass_paths')
|
||||
|
||||
|
||||
# If this is true, all non-protected property assignments
|
||||
# are directly stored to a database attribute
|
||||
|
||||
|
|
@ -49,6 +50,7 @@ class MetaTypeClass(type):
|
|||
def __str__(cls):
|
||||
return "%s" % cls.__name__
|
||||
|
||||
|
||||
class TypeClass(object):
|
||||
"""
|
||||
This class implements a 'typeclass' object. This is connected
|
||||
|
|
@ -70,8 +72,10 @@ class TypeClass(object):
|
|||
def __init__(self, dbobj):
|
||||
"""
|
||||
Initialize the object class. There are two ways to call this class.
|
||||
o = object_class(dbobj) : this is used to initialize dbobj with the class name
|
||||
o = dbobj.object_class(dbobj) : this is used when dbobj.object_class is already set.
|
||||
o = object_class(dbobj) : this is used to initialize dbobj with the
|
||||
class name
|
||||
o = dbobj.object_class(dbobj) : this is used when dbobj.object_class
|
||||
is already set.
|
||||
|
||||
"""
|
||||
# typecheck of dbobj - we can't allow it to be added here
|
||||
|
|
@ -145,11 +149,10 @@ class TypeClass(object):
|
|||
except AttributeError:
|
||||
return id(self) == id(other)
|
||||
|
||||
|
||||
def __delattr__(self, propname):
|
||||
"""
|
||||
Transparently deletes data from the typeclass or dbobj by first searching on the typeclass,
|
||||
secondly on the dbobj.db.
|
||||
Transparently deletes data from the typeclass or dbobj by first
|
||||
searching on the typeclass, secondly on the dbobj.db.
|
||||
Will not allow deletion of properties stored directly on dbobj.
|
||||
"""
|
||||
if propname in PROTECTED:
|
||||
|
|
@ -166,7 +169,7 @@ class TypeClass(object):
|
|||
dbobj = _GA(self, 'dbobj')
|
||||
except AttributeError:
|
||||
log_trace("This is probably due to an unsafe reload.")
|
||||
return # ignore delete
|
||||
return # ignore delete
|
||||
try:
|
||||
dbobj.del_attribute(propname, raise_exception=True)
|
||||
except AttributeError:
|
||||
|
|
@ -178,5 +181,6 @@ class TypeClass(object):
|
|||
def __str__(self):
|
||||
"represent the object"
|
||||
return self.key
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s" % self.key
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ ANSI_SPACE = " "
|
|||
# Escapes
|
||||
ANSI_ESCAPES = ("{{", "%%", "\\\\")
|
||||
|
||||
|
||||
class ANSIParser(object):
|
||||
"""
|
||||
A class that parses ansi markup
|
||||
|
|
@ -75,10 +76,11 @@ class ANSIParser(object):
|
|||
# MUX-style mappings %cr %cn etc
|
||||
|
||||
self.mux_ansi_map = [
|
||||
# commented out by default; they (especially blink) are potentially annoying
|
||||
(r'%r', ANSI_RETURN),
|
||||
(r'%t', ANSI_TAB),
|
||||
(r'%b', ANSI_SPACE),
|
||||
# commented out by default; they (especially blink) are
|
||||
# potentially annoying
|
||||
(r'%r', ANSI_RETURN),
|
||||
(r'%t', ANSI_TAB),
|
||||
(r'%b', ANSI_SPACE),
|
||||
#(r'%cf', ANSI_BLINK),
|
||||
#(r'%ci', ANSI_INVERSE),
|
||||
(r'%cr', ANSI_RED),
|
||||
|
|
@ -118,18 +120,18 @@ class ANSIParser(object):
|
|||
(r'{M', normal + ANSI_MAGENTA),
|
||||
(r'{c', hilite + ANSI_CYAN),
|
||||
(r'{C', normal + ANSI_CYAN),
|
||||
(r'{w', hilite + ANSI_WHITE), # pure white
|
||||
(r'{W', normal + ANSI_WHITE), #light grey
|
||||
(r'{x', hilite + ANSI_BLACK), #dark grey
|
||||
(r'{X', normal + ANSI_BLACK), #pure black
|
||||
(r'{n', normal) #reset
|
||||
(r'{w', hilite + ANSI_WHITE), # pure white
|
||||
(r'{W', normal + ANSI_WHITE), # light grey
|
||||
(r'{x', hilite + ANSI_BLACK), # dark grey
|
||||
(r'{X', normal + ANSI_BLACK), # pure black
|
||||
(r'{n', normal) # reset
|
||||
]
|
||||
|
||||
# xterm256 {123, %c134,
|
||||
|
||||
self.xterm256_map = [
|
||||
(r'%c([0-5]{3})', self.parse_rgb), # %c123 - foreground colour
|
||||
(r'%c(b[0-5]{3})', self.parse_rgb), # %cb123 - background colour
|
||||
(r'%c(b[0-5]{3})', self.parse_rgb), # %cb123 - background colour
|
||||
(r'{([0-5]{3})', self.parse_rgb), # {123 - foreground colour
|
||||
(r'{(b[0-5]{3})', self.parse_rgb) # {b123 - background colour
|
||||
]
|
||||
|
|
@ -145,7 +147,8 @@ class ANSIParser(object):
|
|||
# prepare matching ansi codes overall
|
||||
self.ansi_regex = re.compile("\033\[[0-9;]+m")
|
||||
|
||||
# escapes - these double-chars will be replaced with a single instance of each
|
||||
# escapes - these double-chars will be replaced with a single
|
||||
# instance of each
|
||||
self.ansi_escapes = re.compile(r"(%s)" % "|".join(ANSI_ESCAPES), re.DOTALL)
|
||||
|
||||
def parse_rgb(self, rgbmatch):
|
||||
|
|
@ -174,37 +177,61 @@ class ANSIParser(object):
|
|||
#print "ANSI convert:", red, green, blue
|
||||
# xterm256 not supported, convert the rgb value to ansi instead
|
||||
if red == green and red == blue and red < 2:
|
||||
if background: return ANSI_BACK_BLACK
|
||||
elif red >= 1: return ANSI_HILITE + ANSI_BLACK
|
||||
else: return ANSI_NORMAL + ANSI_BLACK
|
||||
if background:
|
||||
return ANSI_BACK_BLACK
|
||||
elif red >= 1:
|
||||
return ANSI_HILITE + ANSI_BLACK
|
||||
else:
|
||||
return ANSI_NORMAL + ANSI_BLACK
|
||||
elif red == green and red == blue:
|
||||
if background: return ANSI_BACK_WHITE
|
||||
elif red >= 4: return ANSI_HILITE + ANSI_WHITE
|
||||
else: return ANSI_NORMAL + ANSI_WHITE
|
||||
if background:
|
||||
return ANSI_BACK_WHITE
|
||||
elif red >= 4:
|
||||
return ANSI_HILITE + ANSI_WHITE
|
||||
else:
|
||||
return ANSI_NORMAL + ANSI_WHITE
|
||||
elif red > green and red > blue:
|
||||
if background: return ANSI_BACK_RED
|
||||
elif red >= 3: return ANSI_HILITE + ANSI_RED
|
||||
else: return ANSI_NORMAL + ANSI_RED
|
||||
if background:
|
||||
return ANSI_BACK_RED
|
||||
elif red >= 3:
|
||||
return ANSI_HILITE + ANSI_RED
|
||||
else:
|
||||
return ANSI_NORMAL + ANSI_RED
|
||||
elif red == green and red > blue:
|
||||
if background: return ANSI_BACK_YELLOW
|
||||
elif red >= 3: return ANSI_HILITE + ANSI_YELLOW
|
||||
else: return ANSI_NORMAL + ANSI_YELLOW
|
||||
if background:
|
||||
return ANSI_BACK_YELLOW
|
||||
elif red >= 3:
|
||||
return ANSI_HILITE + ANSI_YELLOW
|
||||
else:
|
||||
return ANSI_NORMAL + ANSI_YELLOW
|
||||
elif red == blue and red > green:
|
||||
if background: return ANSI_BACK_MAGENTA
|
||||
elif red >= 3: return ANSI_HILITE + ANSI_MAGENTA
|
||||
else: return ANSI_NORMAL + ANSI_MAGENTA
|
||||
if background:
|
||||
return ANSI_BACK_MAGENTA
|
||||
elif red >= 3:
|
||||
return ANSI_HILITE + ANSI_MAGENTA
|
||||
else:
|
||||
return ANSI_NORMAL + ANSI_MAGENTA
|
||||
elif green > blue:
|
||||
if background: return ANSI_BACK_GREEN
|
||||
elif green >= 3: return ANSI_HILITE + ANSI_GREEN
|
||||
else: return ANSI_NORMAL + ANSI_GREEN
|
||||
if background:
|
||||
return ANSI_BACK_GREEN
|
||||
elif green >= 3:
|
||||
return ANSI_HILITE + ANSI_GREEN
|
||||
else:
|
||||
return ANSI_NORMAL + ANSI_GREEN
|
||||
elif green == blue:
|
||||
if background: return ANSI_BACK_CYAN
|
||||
elif green >= 3: return ANSI_HILITE + ANSI_CYAN
|
||||
else: return ANSI_NORMAL + ANSI_CYAN
|
||||
if background:
|
||||
return ANSI_BACK_CYAN
|
||||
elif green >= 3:
|
||||
return ANSI_HILITE + ANSI_CYAN
|
||||
else:
|
||||
return ANSI_NORMAL + ANSI_CYAN
|
||||
else: # mostly blue
|
||||
if background: return ANSI_BACK_BLUE
|
||||
elif blue >= 3: return ANSI_HILITE + ANSI_BLUE
|
||||
else: return ANSI_NORMAL + ANSI_BLUE
|
||||
if background:
|
||||
return ANSI_BACK_BLUE
|
||||
elif blue >= 3:
|
||||
return ANSI_HILITE + ANSI_BLUE
|
||||
else:
|
||||
return ANSI_NORMAL + ANSI_BLUE
|
||||
|
||||
def parse_ansi(self, string, strip_ansi=False, xterm256=False):
|
||||
"""
|
||||
|
|
@ -227,13 +254,14 @@ class ANSIParser(object):
|
|||
part = sub[0].sub(sub[1], part)
|
||||
string += "%s%s" % (part, sep[0].strip())
|
||||
if strip_ansi:
|
||||
# remove all ansi codes (including those manually inserted in string)
|
||||
# remove all ansi codes (including those manually
|
||||
# inserted in string)
|
||||
string = self.ansi_regex.sub("", string)
|
||||
return string
|
||||
|
||||
|
||||
ANSI_PARSER = ANSIParser()
|
||||
|
||||
|
||||
#
|
||||
# Access function
|
||||
#
|
||||
|
|
@ -245,8 +273,9 @@ def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER, xterm256=False):
|
|||
"""
|
||||
return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256)
|
||||
|
||||
|
||||
def raw(string):
|
||||
"""
|
||||
Escapes a string into a form which won't be colorized by the ansi parser.
|
||||
"""
|
||||
return string.replace('{','{{').replace('%','%%')
|
||||
return string.replace('{', '{{').replace('%', '%%')
|
||||
|
|
|
|||
|
|
@ -167,16 +167,18 @@ script = create.create_script()
|
|||
|
||||
import re
|
||||
import codecs
|
||||
import traceback, sys
|
||||
from traceback import format_exc
|
||||
import traceback
|
||||
import sys
|
||||
#from traceback import format_exc
|
||||
from django.conf import settings
|
||||
from src.utils import logger
|
||||
from src.utils import utils
|
||||
from game import settings as settings_module
|
||||
#from game import settings as settings_module
|
||||
|
||||
ENCODINGS = settings.ENCODINGS
|
||||
CODE_INFO_HEADER = re.compile(r"\(.*?\)")
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Helper function
|
||||
#------------------------------------------------------------
|
||||
|
|
@ -216,7 +218,7 @@ def read_batchfile(pythonpath, file_ending='.py'):
|
|||
continue
|
||||
|
||||
load_errors = []
|
||||
err =None
|
||||
err = None
|
||||
# We have successfully found and opened the file. Now actually
|
||||
# try to decode it using the given protocol.
|
||||
try:
|
||||
|
|
@ -246,6 +248,7 @@ def read_batchfile(pythonpath, file_ending='.py'):
|
|||
else:
|
||||
return lines
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Batch-command processor
|
||||
|
|
@ -261,9 +264,9 @@ class BatchCommandProcessor(object):
|
|||
"""
|
||||
This parses the lines of a batchfile according to the following
|
||||
rules:
|
||||
1) # at the beginning of a line marks the end of the command before it.
|
||||
It is also a comment and any number of # can exist on subsequent
|
||||
lines (but not inside comments).
|
||||
1) # at the beginning of a line marks the end of the command before
|
||||
it. It is also a comment and any number of # can exist on
|
||||
subsequent lines (but not inside comments).
|
||||
2) #INSERT at the beginning of a line imports another
|
||||
batch-cmd file file and pastes it into the batch file as if
|
||||
it was written there.
|
||||
|
|
@ -323,10 +326,10 @@ class BatchCommandProcessor(object):
|
|||
curr_cmd = ""
|
||||
filename = line.lstrip("#INSERT").strip()
|
||||
insert_commands = self.parse_file(filename)
|
||||
if insert_commands == None:
|
||||
if insert_commands is None:
|
||||
insert_commands = ["{rINSERT ERROR: %s{n" % filename]
|
||||
commands.extend(insert_commands)
|
||||
else: #comment
|
||||
else: #comment
|
||||
if curr_cmd:
|
||||
commands.append(curr_cmd.strip())
|
||||
curr_cmd = ""
|
||||
|
|
@ -341,6 +344,7 @@ class BatchCommandProcessor(object):
|
|||
commands = [c.strip('\r\n') for c in commands]
|
||||
return commands
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Batch-code processor
|
||||
|
|
@ -350,11 +354,14 @@ class BatchCommandProcessor(object):
|
|||
def tb_filename(tb):
|
||||
"Helper to get filename from traceback"
|
||||
return tb.tb_frame.f_code.co_filename
|
||||
|
||||
|
||||
def tb_iter(tb):
|
||||
while tb is not None:
|
||||
yield tb
|
||||
tb = tb.tb_next
|
||||
|
||||
|
||||
class BatchCodeProcessor(object):
|
||||
"""
|
||||
This implements a batch-code processor
|
||||
|
|
@ -368,7 +375,8 @@ class BatchCodeProcessor(object):
|
|||
|
||||
1) Lines starting with #HEADER starts a header block (ends other blocks)
|
||||
2) Lines starting with #CODE begins a code block (ends other blocks)
|
||||
3) #CODE headers may be of the following form: #CODE (info) objname, objname2, ...
|
||||
3) #CODE headers may be of the following form:
|
||||
#CODE (info) objname, objname2, ...
|
||||
4) Lines starting with #INSERT are on form #INSERT filename.
|
||||
3) All lines outside blocks are stripped.
|
||||
4) All excess whitespace beginning/ending a block is stripped.
|
||||
|
|
@ -378,8 +386,8 @@ class BatchCodeProcessor(object):
|
|||
# helper function
|
||||
def parse_line(line):
|
||||
"""
|
||||
Identifies the line type: block command, comment, empty or normal code.
|
||||
|
||||
Identifies the line type:
|
||||
block command, comment, empty or normal code.
|
||||
"""
|
||||
parseline = line.strip()
|
||||
|
||||
|
|
@ -432,7 +440,7 @@ class BatchCodeProcessor(object):
|
|||
# are not checking for cyclic imports!
|
||||
in_header = False
|
||||
in_code = False
|
||||
inserted_codes = self.parse_file(line) or [{'objs':"", 'info':line, 'code':""}]
|
||||
inserted_codes = self.parse_file(line) or [{'objs': "", 'info': line, 'code': ""}]
|
||||
for codedict in inserted_codes:
|
||||
codedict["inserted"] = True
|
||||
codes.extend(inserted_codes)
|
||||
|
|
@ -444,7 +452,7 @@ class BatchCodeProcessor(object):
|
|||
in_code = True
|
||||
# the line is a list of object variable names
|
||||
# (or an empty list) at this point.
|
||||
codedict = {'objs':line, 'info':info, 'code':""}
|
||||
codedict = {'objs': line, 'info': info, 'code': ""}
|
||||
codes.append(codedict)
|
||||
elif mode == 'comment' and in_header:
|
||||
continue
|
||||
|
|
@ -485,7 +493,7 @@ class BatchCodeProcessor(object):
|
|||
"""
|
||||
# define the execution environment
|
||||
environ = "settings_module.configure()"
|
||||
environdict = {"settings_module":settings}
|
||||
environdict = {"settings_module": settings}
|
||||
if extra_environ:
|
||||
for key, value in extra_environ.items():
|
||||
environdict[key] = value
|
||||
|
|
|
|||
|
|
@ -43,10 +43,12 @@ _channelhandler = None
|
|||
|
||||
|
||||
# limit symbol import from API
|
||||
__all__ = ("create_object", "create_script", "create_help_entry", "create_message", "create_channel", "create_player")
|
||||
__all__ = ("create_object", "create_script", "create_help_entry",
|
||||
"create_message", "create_channel", "create_player")
|
||||
|
||||
_GA = object.__getattribute__
|
||||
|
||||
|
||||
#
|
||||
# Game Object creation
|
||||
#
|
||||
|
|
@ -105,7 +107,8 @@ def create_object(typeclass, key=None, location=None,
|
|||
new_object = new_db_object.typeclass
|
||||
|
||||
if not _GA(new_object, "is_typeclass")(typeclass, exact=True):
|
||||
# this will fail if we gave a typeclass as input and it still gave us a default
|
||||
# this will fail if we gave a typeclass as input and it still
|
||||
# gave us a default
|
||||
SharedMemoryModel.delete(new_db_object)
|
||||
if report_to:
|
||||
_GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_object.key, typeclass,
|
||||
|
|
@ -122,14 +125,14 @@ def create_object(typeclass, key=None, location=None,
|
|||
# call the hook method. This is where all at_creation
|
||||
# customization happens as the typeclass stores custom
|
||||
# things on its database object.
|
||||
new_object.basetype_setup() # setup the basics of Exits, Characters etc.
|
||||
new_object.basetype_setup() # setup the basics of Exits, Characters etc.
|
||||
new_object.at_object_creation()
|
||||
|
||||
# custom-given perms/locks overwrite hooks
|
||||
if permissions:
|
||||
new_object.permissions.add(permissions)
|
||||
if locks:
|
||||
new_object.locks.add(locks)
|
||||
new_object.locks.add(locks)
|
||||
if aliases:
|
||||
new_object.aliases.add(aliases)
|
||||
|
||||
|
|
@ -137,7 +140,7 @@ def create_object(typeclass, key=None, location=None,
|
|||
if home:
|
||||
new_object.home = home
|
||||
else:
|
||||
new_object.home = settings.CHARACTER_DEFAULT_HOME if not nohome else None
|
||||
new_object.home = settings.CHARACTER_DEFAULT_HOME if not nohome else None
|
||||
|
||||
if location:
|
||||
new_object.move_to(location, quiet=True)
|
||||
|
|
@ -154,6 +157,7 @@ def create_object(typeclass, key=None, location=None,
|
|||
#alias for create_object
|
||||
object = create_object
|
||||
|
||||
|
||||
#
|
||||
# Script creation
|
||||
#
|
||||
|
|
@ -218,7 +222,8 @@ def create_script(typeclass, key=None, obj=None, locks=None,
|
|||
new_script = new_db_script.typeclass
|
||||
|
||||
if not _GA(new_db_script, "is_typeclass")(typeclass, exact=True):
|
||||
# this will fail if we gave a typeclass as input and it still gave us a default
|
||||
# this will fail if we gave a typeclass as input and it still
|
||||
# gave us a default
|
||||
SharedMemoryModel.delete(new_db_script)
|
||||
if report_to:
|
||||
_GA(report_to, "msg")("Error creating %s (%s): %s" % (new_db_script.key, typeclass,
|
||||
|
|
@ -243,13 +248,13 @@ def create_script(typeclass, key=None, obj=None, locks=None,
|
|||
new_script.key = key
|
||||
if locks:
|
||||
new_script.locks.add(locks)
|
||||
if interval != None:
|
||||
if interval is not None:
|
||||
new_script.interval = interval
|
||||
if start_delay != None:
|
||||
if start_delay is not None:
|
||||
new_script.start_delay = start_delay
|
||||
if repeats != None:
|
||||
if repeats is not None:
|
||||
new_script.repeats = repeats
|
||||
if persistent != None:
|
||||
if persistent is not None:
|
||||
new_script.persistent = persistent
|
||||
|
||||
# a new created script should usually be started.
|
||||
|
|
@ -261,6 +266,7 @@ def create_script(typeclass, key=None, obj=None, locks=None,
|
|||
#alias
|
||||
script = create_script
|
||||
|
||||
|
||||
#
|
||||
# Help entry creation
|
||||
#
|
||||
|
|
@ -296,6 +302,7 @@ def create_help_entry(key, entrytext, category="General", locks=None):
|
|||
# alias
|
||||
help_entry = create_help_entry
|
||||
|
||||
|
||||
#
|
||||
# Comm system methods
|
||||
#
|
||||
|
|
@ -343,6 +350,7 @@ def create_message(senderobj, message, channels=None,
|
|||
return new_message
|
||||
message = create_message
|
||||
|
||||
|
||||
def create_channel(key, aliases=None, desc=None,
|
||||
locks=None, keep_log=True,
|
||||
typeclass=None):
|
||||
|
|
@ -389,6 +397,7 @@ def create_channel(key, aliases=None, desc=None,
|
|||
|
||||
channel = create_channel
|
||||
|
||||
|
||||
#
|
||||
# Player creation methods
|
||||
#
|
||||
|
|
@ -456,7 +465,8 @@ def create_player(key, email, password,
|
|||
new_player = new_db_player.typeclass
|
||||
|
||||
if not _GA(new_db_player, "is_typeclass")(typeclass, exact=True):
|
||||
# this will fail if we gave a typeclass as input and it still gave us a default
|
||||
# this will fail if we gave a typeclass as input
|
||||
# and it still gave us a default
|
||||
SharedMemoryModel.delete(new_db_player)
|
||||
if report_to:
|
||||
_GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_player.key, typeclass,
|
||||
|
|
@ -465,7 +475,7 @@ def create_player(key, email, password,
|
|||
else:
|
||||
raise Exception(_GA(new_db_player, "typeclass_last_errmsg"))
|
||||
|
||||
new_player.basetype_setup() # setup the basic locks and cmdset
|
||||
new_player.basetype_setup() # setup the basic locks and cmdset
|
||||
# call hook method (may override default permissions)
|
||||
new_player.at_player_creation()
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,12 @@ if uses_database("mysql") and ServerConfig.objects.get_mysql_db_version() < '5.6
|
|||
else:
|
||||
_DATESTRING = "%Y:%m:%d-%H:%M:%S:%f"
|
||||
|
||||
|
||||
def _TO_DATESTRING(obj):
|
||||
"this will only be called with valid database objects. Returns datestring on correct form."
|
||||
"""
|
||||
this will only be called with valid database objects. Returns datestring
|
||||
on correct form.
|
||||
"""
|
||||
try:
|
||||
return _GA(obj, "db_date_created").strftime(_DATESTRING)
|
||||
except AttributeError:
|
||||
|
|
@ -74,6 +78,7 @@ def _init_globals():
|
|||
# SaverList, SaverDict, SaverSet - Attribute-specific helper classes and functions
|
||||
#
|
||||
|
||||
|
||||
def _save(method):
|
||||
"method decorator that saves data to Attribute"
|
||||
def save_wrapper(self, *args, **kwargs):
|
||||
|
|
@ -83,6 +88,7 @@ def _save(method):
|
|||
return ret
|
||||
return update_wrapper(save_wrapper, method)
|
||||
|
||||
|
||||
class _SaverMutable(object):
|
||||
"""
|
||||
Parent class for properly handling of nested mutables in
|
||||
|
|
@ -95,6 +101,7 @@ class _SaverMutable(object):
|
|||
self._parent = kwargs.pop("parent", None)
|
||||
self._db_obj = kwargs.pop("db_obj", None)
|
||||
self._data = None
|
||||
|
||||
def _save_tree(self):
|
||||
"recursively traverse back up the tree, save when we reach the root"
|
||||
if self._parent:
|
||||
|
|
@ -103,6 +110,7 @@ class _SaverMutable(object):
|
|||
self._db_obj.value = self
|
||||
else:
|
||||
logger.log_errmsg("_SaverMutable %s has no root Attribute to save to." % self)
|
||||
|
||||
def _convert_mutables(self, data):
|
||||
"converts mutables to Saver* variants and assigns .parent property"
|
||||
def process_tree(item, parent):
|
||||
|
|
@ -127,19 +135,25 @@ class _SaverMutable(object):
|
|||
|
||||
def __repr__(self):
|
||||
return self._data.__repr__()
|
||||
|
||||
def __len__(self):
|
||||
return self._data.__len__()
|
||||
|
||||
def __iter__(self):
|
||||
return self._data.__iter__()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._data.__getitem__(key)
|
||||
|
||||
@_save
|
||||
def __setitem__(self, key, value):
|
||||
self._data.__setitem__(key, self._convert_mutables(value))
|
||||
|
||||
@_save
|
||||
def __delitem__(self, key):
|
||||
self._data.__delitem__(key)
|
||||
|
||||
|
||||
class _SaverList(_SaverMutable, MutableSequence):
|
||||
"""
|
||||
A list that saves itself to an Attribute when updated.
|
||||
|
|
@ -147,14 +161,17 @@ class _SaverList(_SaverMutable, MutableSequence):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super(_SaverList, self).__init__(*args, **kwargs)
|
||||
self._data = list(*args)
|
||||
|
||||
@_save
|
||||
def __add__(self, otherlist):
|
||||
self._data = self._data.__add__(otherlist)
|
||||
return self._data
|
||||
|
||||
@_save
|
||||
def insert(self, index, value):
|
||||
self._data.insert(index, self._convert_mutables(value))
|
||||
|
||||
|
||||
class _SaverDict(_SaverMutable, MutableMapping):
|
||||
"""
|
||||
A dict that stores changes to an Attribute when updated
|
||||
|
|
@ -163,6 +180,7 @@ class _SaverDict(_SaverMutable, MutableMapping):
|
|||
super(_SaverDict, self).__init__(*args, **kwargs)
|
||||
self._data = dict(*args)
|
||||
|
||||
|
||||
class _SaverSet(_SaverMutable, MutableSet):
|
||||
"""
|
||||
A set that saves to an Attribute when updated
|
||||
|
|
@ -170,11 +188,14 @@ class _SaverSet(_SaverMutable, MutableSet):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super(_SaverSet, self).__init__(*args, **kwargs)
|
||||
self._data = set(*args)
|
||||
|
||||
def __contains__(self, value):
|
||||
return self._data.__contains__(value)
|
||||
|
||||
@_save
|
||||
def add(self, value):
|
||||
self._data.add(self._convert_mutables(value))
|
||||
|
||||
@_save
|
||||
def discard(self, value):
|
||||
self._data.discard(value)
|
||||
|
|
@ -187,14 +208,18 @@ class _SaverSet(_SaverMutable, MutableSet):
|
|||
def pack_dbobj(item):
|
||||
"""
|
||||
Check and convert django database objects to an internal representation.
|
||||
This either returns the original input item or a tuple ("__packed_dbobj__", key, creation_time, id)
|
||||
This either returns the original input item or a tuple
|
||||
("__packed_dbobj__", key, creation_time, id)
|
||||
"""
|
||||
_init_globals()
|
||||
obj = hasattr(item, 'dbobj') and item.dbobj or item
|
||||
obj = hasattr(item, 'dbobj') and item.dbobj or item
|
||||
natural_key = _FROM_MODEL_MAP[hasattr(obj, "id") and hasattr(obj, "db_date_created") and
|
||||
hasattr(obj, '__class__') and obj.__class__.__name__.lower()]
|
||||
# build the internal representation as a tuple ("__packed_dbobj__", key, creation_time, id)
|
||||
return natural_key and ('__packed_dbobj__', natural_key, _TO_DATESTRING(obj), _GA(obj, "id")) or item
|
||||
# build the internal representation as a tuple
|
||||
# ("__packed_dbobj__", key, creation_time, id)
|
||||
return natural_key and ('__packed_dbobj__', natural_key,
|
||||
_TO_DATESTRING(obj), _GA(obj, "id")) or item
|
||||
|
||||
|
||||
def unpack_dbobj(item):
|
||||
"""
|
||||
|
|
@ -208,21 +233,26 @@ def unpack_dbobj(item):
|
|||
obj = item[3] and _TO_TYPECLASS(_TO_MODEL_MAP[item[1]].objects.get(id=item[3]))
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
# even if we got back a match, check the sanity of the date (some databases may 're-use' the id)
|
||||
try: dbobj = obj.dbobj
|
||||
except AttributeError: dbobj = obj
|
||||
# even if we got back a match, check the sanity of the date (some
|
||||
# databases may 're-use' the id)
|
||||
try:
|
||||
dbobj = obj.dbobj
|
||||
except AttributeError:
|
||||
dbobj = obj
|
||||
return _TO_DATESTRING(dbobj) == item[2] and obj or None
|
||||
|
||||
|
||||
#
|
||||
# Access methods
|
||||
#
|
||||
|
||||
def to_pickle(data):
|
||||
"""
|
||||
This prepares data on arbitrary form to be pickled. It handles any nested structure
|
||||
and returns data on a form that is safe to pickle (including having converted any
|
||||
database models to their internal representation). We also convert any Saver*-type
|
||||
objects back to their normal representations, they are not pickle-safe.
|
||||
This prepares data on arbitrary form to be pickled. It handles any nested
|
||||
structure and returns data on a form that is safe to pickle (including
|
||||
having converted any database models to their internal representation).
|
||||
We also convert any Saver*-type objects back to their normal
|
||||
representations, they are not pickle-safe.
|
||||
"""
|
||||
def process_item(item):
|
||||
"Recursive processor and identification of data"
|
||||
|
|
@ -246,20 +276,22 @@ def to_pickle(data):
|
|||
return pack_dbobj(item)
|
||||
return process_item(data)
|
||||
|
||||
|
||||
@transaction.autocommit
|
||||
def from_pickle(data, db_obj=None):
|
||||
"""
|
||||
This should be fed a just de-pickled data object. It will be converted back
|
||||
to a form that may contain database objects again. Note that if a database
|
||||
object was removed (or changed in-place) in the database, None will be returned.
|
||||
object was removed (or changed in-place) in the database, None will be
|
||||
returned.
|
||||
|
||||
db_obj - this is the model instance (normally an Attribute) that _Saver*-type
|
||||
iterables (_SaverList etc) will save to when they update. It must have a 'value'
|
||||
property that saves assigned data to the database. Skip if not serializing onto
|
||||
a given object.
|
||||
db_obj - this is the model instance (normally an Attribute) that
|
||||
_Saver*-type iterables (_SaverList etc) will save to when they
|
||||
update. It must have a 'value' property that saves assigned data
|
||||
to the database. Skip if not serializing onto a given object.
|
||||
|
||||
If db_obj is given, this function will convert lists, dicts and sets to their
|
||||
_SaverList, _SaverDict and _SaverSet counterparts.
|
||||
If db_obj is given, this function will convert lists, dicts and sets
|
||||
to their _SaverList, _SaverDict and _SaverSet counterparts.
|
||||
|
||||
"""
|
||||
def process_item(item):
|
||||
|
|
@ -278,7 +310,8 @@ def from_pickle(data, db_obj=None):
|
|||
return set(process_item(val) for val in item)
|
||||
elif hasattr(item, '__iter__'):
|
||||
try:
|
||||
# we try to conserve the iterable class if it accepts an iterator
|
||||
# we try to conserve the iterable class if
|
||||
# it accepts an iterator
|
||||
return item.__class__(process_item(val) for val in item)
|
||||
except (AttributeError, TypeError):
|
||||
return [process_item(val) for val in item]
|
||||
|
|
@ -300,7 +333,8 @@ def from_pickle(data, db_obj=None):
|
|||
return dat
|
||||
elif dtype == dict:
|
||||
dat = _SaverDict(parent=parent)
|
||||
dat._data.update(dict((key, process_tree(val, dat)) for key, val in item.items()))
|
||||
dat._data.update(dict((key, process_tree(val, dat))
|
||||
for key, val in item.items()))
|
||||
return dat
|
||||
elif dtype == set:
|
||||
dat = _SaverSet(parent=parent)
|
||||
|
|
@ -308,7 +342,8 @@ def from_pickle(data, db_obj=None):
|
|||
return dat
|
||||
elif hasattr(item, '__iter__'):
|
||||
try:
|
||||
# we try to conserve the iterable class if it accepts an iterator
|
||||
# we try to conserve the iterable class if it
|
||||
# accepts an iterator
|
||||
return item.__class__(process_tree(val, parent) for val in item)
|
||||
except (AttributeError, TypeError):
|
||||
dat = _SaverList(parent=parent)
|
||||
|
|
@ -326,7 +361,8 @@ def from_pickle(data, db_obj=None):
|
|||
return dat
|
||||
elif dtype == dict:
|
||||
dat = _SaverDict(db_obj=db_obj)
|
||||
dat._data.update((key, process_tree(val, parent=dat)) for key, val in data.items())
|
||||
dat._data.update((key, process_tree(val, parent=dat))
|
||||
for key, val in data.items())
|
||||
return dat
|
||||
elif dtype == set:
|
||||
dat = _SaverSet(db_obj=db_obj)
|
||||
|
|
@ -334,17 +370,22 @@ def from_pickle(data, db_obj=None):
|
|||
return dat
|
||||
return process_item(data)
|
||||
|
||||
|
||||
def do_pickle(data):
|
||||
"Perform pickle to string"
|
||||
return to_str(dumps(data, protocol=PICKLE_PROTOCOL))
|
||||
|
||||
|
||||
def do_unpickle(data):
|
||||
"Retrieve pickle from pickled string"
|
||||
return loads(to_str(data))
|
||||
|
||||
|
||||
def dbserialize(data):
|
||||
"Serialize to pickled form in one step"
|
||||
return do_pickle(to_pickle(data))
|
||||
|
||||
|
||||
def dbunserialize(data, db_obj=None):
|
||||
"Un-serialize in one step. See from_pickle for help db_obj."
|
||||
return do_unpickle(from_pickle(data, db_obj=db_obj))
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ TIMEFACTOR = settings.TIME_FACTOR
|
|||
# Common real-life time measures, in seconds.
|
||||
# You should not change these.
|
||||
|
||||
REAL_TICK = max(1.0, settings.TIME_TICK) #Smallest time unit (min 1s)
|
||||
REAL_MIN = 60.0 # seconds per minute in real world
|
||||
REAL_TICK = max(1.0, settings.TIME_TICK) # Smallest time unit (min 1s)
|
||||
REAL_MIN = 60.0 # seconds per minute in real world
|
||||
|
||||
# Game-time units, in real-life seconds. These are supplied as
|
||||
# a convenient measure for determining the current in-game time,
|
||||
|
|
@ -40,6 +40,7 @@ WEEK = DAY * settings.TIME_DAY_PER_WEEK
|
|||
MONTH = WEEK * settings.TIME_WEEK_PER_MONTH
|
||||
YEAR = MONTH * settings.TIME_MONTH_PER_YEAR
|
||||
|
||||
|
||||
class GameTime(Script):
|
||||
"""
|
||||
This sets up an script that keeps track of the
|
||||
|
|
@ -51,12 +52,12 @@ class GameTime(Script):
|
|||
"""
|
||||
self.key = "sys_game_time"
|
||||
self.desc = "Keeps track of the game time"
|
||||
self.interval = REAL_MIN # update every minute
|
||||
self.interval = REAL_MIN # update every minute
|
||||
self.persistent = True
|
||||
self.start_delay = True
|
||||
self.attributes.add("game_time", 0.0) #IC time
|
||||
self.attributes.add("run_time", 0.0) #OOC time
|
||||
self.attributes.add("up_time", 0.0) #OOC time
|
||||
self.attributes.add("game_time", 0.0) # IC time
|
||||
self.attributes.add("run_time", 0.0) # OOC time
|
||||
self.attributes.add("up_time", 0.0) # OOC time
|
||||
|
||||
def at_repeat(self):
|
||||
"""
|
||||
|
|
@ -77,6 +78,7 @@ class GameTime(Script):
|
|||
"""
|
||||
self.attributes.add("up_time", 0.0)
|
||||
|
||||
|
||||
# Access routines
|
||||
|
||||
def gametime_format(seconds):
|
||||
|
|
@ -94,27 +96,29 @@ def gametime_format(seconds):
|
|||
# do this or we cancel the already counted
|
||||
# timefactor in the timer script...
|
||||
sec = int(seconds * TIMEFACTOR)
|
||||
years, sec = sec/YEAR, sec % YEAR
|
||||
months, sec = sec/MONTH, sec % MONTH
|
||||
weeks, sec = sec/WEEK, sec % WEEK
|
||||
days, sec = sec/DAY, sec % DAY
|
||||
hours, sec = sec/HOUR, sec % HOUR
|
||||
minutes, sec = sec/MIN, sec % MIN
|
||||
years, sec = sec / YEAR, sec % YEAR
|
||||
months, sec = sec / MONTH, sec % MONTH
|
||||
weeks, sec = sec / WEEK, sec % WEEK
|
||||
days, sec = sec / DAY, sec % DAY
|
||||
hours, sec = sec / HOUR, sec % HOUR
|
||||
minutes, sec = sec / MIN, sec % MIN
|
||||
return (years, months, weeks, days, hours, minutes, sec)
|
||||
|
||||
|
||||
def realtime_format(seconds):
|
||||
"""
|
||||
As gametime format, but with real time units
|
||||
"""
|
||||
sec = int(seconds)
|
||||
years, sec = sec/29030400, sec % 29030400
|
||||
months, sec = sec/2419200, sec % 2419200
|
||||
weeks, sec = sec/604800, sec % 604800
|
||||
days, sec = sec/86400, sec % 86400
|
||||
hours, sec = sec/3600, sec % 3600
|
||||
minutes, sec = sec/60, sec % 60
|
||||
years, sec = sec / 29030400, sec % 29030400
|
||||
months, sec = sec / 2419200, sec % 2419200
|
||||
weeks, sec = sec / 604800, sec % 604800
|
||||
days, sec = sec / 86400, sec % 86400
|
||||
hours, sec = sec / 3600, sec % 3600
|
||||
minutes, sec = sec / 60, sec % 60
|
||||
return (years, months, weeks, days, hours, minutes, sec)
|
||||
|
||||
|
||||
def gametime(format=False):
|
||||
"""
|
||||
Find the current in-game time (in seconds) since the start of the mud.
|
||||
|
|
@ -136,6 +140,7 @@ def gametime(format=False):
|
|||
return gametime_format(game_time)
|
||||
return game_time
|
||||
|
||||
|
||||
def runtime(format=False):
|
||||
"""
|
||||
Get the total actual time the server has been running (minus downtimes)
|
||||
|
|
@ -151,6 +156,7 @@ def runtime(format=False):
|
|||
return realtime_format(run_time)
|
||||
return run_time
|
||||
|
||||
|
||||
def uptime(format=False):
|
||||
"""
|
||||
Get the actual time the server has been running since last downtime.
|
||||
|
|
@ -170,31 +176,33 @@ def uptime(format=False):
|
|||
def gametime_to_realtime(secs=0, mins=0, hrs=0, days=0,
|
||||
weeks=0, months=0, yrs=0):
|
||||
"""
|
||||
This method helps to figure out the real-world time it will take until a in-game time
|
||||
has passed. E.g. if an event should take place a month later in-game, you will be able
|
||||
to find the number of real-world seconds this corresponds to (hint: Interval events deal
|
||||
with real life seconds).
|
||||
This method helps to figure out the real-world time it will take until an
|
||||
in-game time has passed. E.g. if an event should take place a month later
|
||||
in-game, you will be able to find the number of real-world seconds this
|
||||
corresponds to (hint: Interval events deal with real life seconds).
|
||||
|
||||
Example:
|
||||
gametime_to_realtime(days=2) -> number of seconds in real life from now after which
|
||||
2 in-game days will have passed.
|
||||
gametime_to_realtime(days=2) -> number of seconds in real life from
|
||||
now after which 2 in-game days will have passed.
|
||||
"""
|
||||
real_time = secs/TIMEFACTOR + mins*MIN + hrs*HOUR + \
|
||||
days*DAY + weeks*WEEK + months*MONTH + yrs*YEAR
|
||||
real_time = secs / TIMEFACTOR + mins * MIN + hrs * HOUR + \
|
||||
days * DAY + weeks * WEEK + months * MONTH + yrs * YEAR
|
||||
return real_time
|
||||
|
||||
|
||||
def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0,
|
||||
weeks=0, months=0, yrs=0):
|
||||
"""
|
||||
This method calculates how large an in-game time a real-world time interval would
|
||||
correspond to. This is usually a lot less interesting than the other way around.
|
||||
This method calculates how large an in-game time a real-world time
|
||||
interval would correspond to. This is usually a lot less interesting
|
||||
than the other way around.
|
||||
|
||||
Example:
|
||||
realtime_to_gametime(days=2) -> number of game-world seconds
|
||||
corresponding to 2 real days.
|
||||
"""
|
||||
game_time = TIMEFACTOR * (secs + mins*60 + hrs*3600 + days*86400 + \
|
||||
weeks*604800 + months*2419200 + yrs*29030400)
|
||||
game_time = TIMEFACTOR * (secs + mins * 60 + hrs * 3600 + days * 86400 +
|
||||
weeks * 604800 + months * 2419200 + yrs * 29030400)
|
||||
return game_time
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ a higher layer module.
|
|||
from traceback import format_exc
|
||||
from twisted.python import log
|
||||
|
||||
|
||||
def log_trace(errmsg=None):
|
||||
"""
|
||||
Log a traceback to the log. This should be called
|
||||
|
|
@ -27,7 +28,8 @@ def log_trace(errmsg=None):
|
|||
for line in errmsg.splitlines():
|
||||
log.msg('[EE] %s' % line)
|
||||
except Exception:
|
||||
log.msg('[EE] %s' % errmsg )
|
||||
log.msg('[EE] %s' % errmsg)
|
||||
|
||||
|
||||
def log_errmsg(errmsg):
|
||||
"""
|
||||
|
|
@ -43,6 +45,7 @@ def log_errmsg(errmsg):
|
|||
log.msg('[EE] %s' % line)
|
||||
#log.err('ERROR: %s' % (errormsg,))
|
||||
|
||||
|
||||
def log_warnmsg(warnmsg):
|
||||
"""
|
||||
Prints/logs any warnings that aren't critical but should be noted.
|
||||
|
|
@ -57,6 +60,7 @@ def log_warnmsg(warnmsg):
|
|||
log.msg('[WW] %s' % line)
|
||||
#log.msg('WARNING: %s' % (warnmsg,))
|
||||
|
||||
|
||||
def log_infomsg(infomsg):
|
||||
"""
|
||||
Prints any generic debugging/informative info that should appear in the log.
|
||||
|
|
@ -70,6 +74,7 @@ def log_infomsg(infomsg):
|
|||
for line in infomsg.splitlines():
|
||||
log.msg('[..] %s' % line)
|
||||
|
||||
|
||||
def log_depmsg(depmsg):
|
||||
"""
|
||||
Prints a deprecation message
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ Example: To reach the search method 'get_object_with_player'
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
# limit symbol import from API
|
||||
__all__ = ("search_object", "search_player", "search_script", "search_message", "search_channel", "search_help_entry")
|
||||
__all__ = ("search_object", "search_player", "search_script",
|
||||
"search_message", "search_channel", "search_help_entry")
|
||||
|
||||
|
||||
# import objects this way to avoid circular import problems
|
||||
|
|
@ -54,25 +55,29 @@ HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_c
|
|||
# candidates=None,
|
||||
# exact=True):
|
||||
#
|
||||
# Search globally or in a list of candidates and return results. The result is always an Object.
|
||||
# Always returns a list.
|
||||
# Search globally or in a list of candidates and return results.
|
||||
# The result is always a list of Objects (or the empty list)
|
||||
#
|
||||
# Arguments:
|
||||
# ostring: (str) The string to compare names against. By default (if not attribute_name
|
||||
# is set), this will search object.key and object.aliases in order. Can also
|
||||
# be on the form #dbref, which will, if exact=True be matched against primary key.
|
||||
# attribute_name: (str): Use this named ObjectAttribute to match ostring against, instead
|
||||
# of the defaults.
|
||||
# typeclass (str or TypeClass): restrict matches to objects having this typeclass. This will help
|
||||
# speed up global searches.
|
||||
# candidates (list obj ObjectDBs): If supplied, search will only be performed among the candidates
|
||||
# in this list. A common list of candidates is the contents of the current location searched.
|
||||
# exact (bool): Match names/aliases exactly or partially. Partial matching matches the
|
||||
# beginning of words in the names/aliases, using a matching routine to separate
|
||||
# multiple matches in names with multiple components (so "bi sw" will match
|
||||
# "Big sword"). Since this is more expensive than exact matching, it is
|
||||
# recommended to be used together with the objlist keyword to limit the number
|
||||
# of possibilities. This value has no meaning if searching for attributes/properties.
|
||||
# ostring: (str) The string to compare names against. By default (if
|
||||
# not attribute_name is set), this will search object.key
|
||||
# and object.aliases in order. Can also be on the form #dbref,
|
||||
# which will, if exact=True be matched against primary key.
|
||||
# attribute_name: (str): Use this named ObjectAttribute to match ostring
|
||||
# against, instead of the defaults.
|
||||
# typeclass (str or TypeClass): restrict matches to objects having
|
||||
# this typeclass. This will help speed up global searches.
|
||||
# candidates (list obj ObjectDBs): If supplied, search will only be
|
||||
# performed among the candidates in this list. A common list
|
||||
# of candidates is the contents of the current location.
|
||||
# exact (bool): Match names/aliases exactly or partially. Partial
|
||||
# matching matches the beginning of words in the names/aliases,
|
||||
# using a matching routine to separate multiple matches in
|
||||
# names with multiple components (so "bi sw" will match
|
||||
# "Big sword"). Since this is more expensive than exact
|
||||
# matching, it is recommended to be used together with
|
||||
# the objlist keyword to limit the number of possibilities.
|
||||
# This keyword has no meaning if attribute_name is set.
|
||||
#
|
||||
# Returns:
|
||||
# A list of matching objects (or a list with one unique match)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
"""
|
||||
Test runner for Evennia test suite. Run with "game/manage.py test".
|
||||
Test runner for Evennia test suite. Run with "game/manage.py test".
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.test.simple import DjangoTestSuiteRunner
|
||||
|
||||
|
||||
class EvenniaTestSuiteRunner(DjangoTestSuiteRunner):
|
||||
"""
|
||||
This test runner only runs tests on the apps specified in src/ and game/ to
|
||||
|
|
@ -13,11 +14,11 @@ class EvenniaTestSuiteRunner(DjangoTestSuiteRunner):
|
|||
"""
|
||||
def build_suite(self, test_labels, extra_tests=None, **kwargs):
|
||||
"""
|
||||
Build a test suite for Evennia. test_labels is a list of apps to test.
|
||||
Build a test suite for Evennia. test_labels is a list of apps to test.
|
||||
If not given, a subset of settings.INSTALLED_APPS will be used.
|
||||
"""
|
||||
if not test_labels:
|
||||
test_labels = [applabel.rsplit('.', 1)[1] for applabel in settings.INSTALLED_APPS
|
||||
test_labels = [applabel.rsplit('.', 1)[1] for applabel in settings.INSTALLED_APPS
|
||||
if (applabel.startswith('src.') or applabel.startswith('game.'))]
|
||||
return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import re
|
|||
import cgi
|
||||
from ansi import *
|
||||
|
||||
|
||||
class TextToHTMLparser(object):
|
||||
"""
|
||||
This class describes a parser for converting from ansi to html.
|
||||
|
|
@ -36,10 +37,10 @@ class TextToHTMLparser(object):
|
|||
('purple', ANSI_MAGENTA),
|
||||
('cyan', hilite + ANSI_CYAN),
|
||||
('teal', ANSI_CYAN),
|
||||
('white', hilite + ANSI_WHITE), # pure white
|
||||
('gray', ANSI_WHITE), #light grey
|
||||
('dimgray', hilite + ANSI_BLACK), #dark grey
|
||||
('black', ANSI_BLACK), #pure black
|
||||
('white', hilite + ANSI_WHITE), # pure white
|
||||
('gray', ANSI_WHITE), # light grey
|
||||
('dimgray', hilite + ANSI_BLACK), # dark grey
|
||||
('black', ANSI_BLACK), # pure black
|
||||
]
|
||||
colorback = [
|
||||
('bgred', hilite + ANSI_BACK_RED),
|
||||
|
|
@ -61,8 +62,8 @@ class TextToHTMLparser(object):
|
|||
]
|
||||
|
||||
# make sure to escape [
|
||||
colorcodes = [(c, code.replace("[",r"\[")) for c, code in colorcodes]
|
||||
colorback = [(c, code.replace("[",r"\[")) for c, code in colorback]
|
||||
colorcodes = [(c, code.replace("[", r"\[")) for c, code in colorcodes]
|
||||
colorback = [(c, code.replace("[", r"\[")) for c, code in colorback]
|
||||
# create stop markers
|
||||
fgstop = [("", c.replace("[", r"\[")) for c in (normal, hilite, underline)]
|
||||
bgstop = [("", c.replace("[", r"\[")) for c in (normal,)]
|
||||
|
|
@ -74,7 +75,7 @@ class TextToHTMLparser(object):
|
|||
re_bgs = [(cname, re.compile("(?:%s)(.*?)(?=%s)" % (code, bgstop))) for cname, code in colorback]
|
||||
re_normal = re.compile(normal.replace("[", r"\["))
|
||||
re_hilite = re.compile("(?:%s)(.*)(?=%s)" % (hilite.replace("[", r"\["), fgstop))
|
||||
re_uline = re.compile("(?:%s)(.*?)(?=%s)" % (ANSI_UNDERLINE.replace("[",r"\["), fgstop))
|
||||
re_uline = re.compile("(?:%s)(.*?)(?=%s)" % (ANSI_UNDERLINE.replace("[", r"\["), fgstop))
|
||||
re_string = re.compile(r'(?P<htmlchars>[<&>])|(?P<space>^[ \t]+)|(?P<lineend>\r\n|\r|\n)', re.S|re.M|re.I)
|
||||
|
||||
def re_color(self, text):
|
||||
|
|
@ -126,9 +127,9 @@ class TextToHTMLparser(object):
|
|||
if c['lineend']:
|
||||
return '<br>'
|
||||
elif c['space'] == '\t':
|
||||
return ' '*self.tabstop
|
||||
return ' ' * self.tabstop
|
||||
elif c['space']:
|
||||
t = m.group().replace('\t', ' '*self.tabstop)
|
||||
t = m.group().replace('\t', ' ' * self.tabstop)
|
||||
t = t.replace(' ', ' ')
|
||||
return t
|
||||
|
||||
|
|
@ -155,6 +156,7 @@ class TextToHTMLparser(object):
|
|||
|
||||
HTML_PARSER = TextToHTMLparser()
|
||||
|
||||
|
||||
#
|
||||
# Access function
|
||||
#
|
||||
|
|
|
|||
|
|
@ -6,12 +6,19 @@ be of use when designing your own game.
|
|||
|
||||
"""
|
||||
|
||||
import os, sys, imp, types, math, re
|
||||
import textwrap, datetime, random, traceback, inspect
|
||||
import os
|
||||
import sys
|
||||
import imp
|
||||
import types
|
||||
import math
|
||||
import re
|
||||
import textwrap
|
||||
import datetime
|
||||
import random
|
||||
import traceback
|
||||
from inspect import ismodule
|
||||
from collections import defaultdict
|
||||
from twisted.internet import threads, defer, reactor
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.conf import settings
|
||||
|
||||
try:
|
||||
|
|
@ -24,6 +31,7 @@ _GA = object.__getattribute__
|
|||
_SA = object.__setattr__
|
||||
_DA = object.__delattr__
|
||||
|
||||
|
||||
def is_iter(iterable):
|
||||
"""
|
||||
Checks if an object behaves iterably. However,
|
||||
|
|
@ -39,10 +47,12 @@ def is_iter(iterable):
|
|||
except AttributeError:
|
||||
return False
|
||||
|
||||
|
||||
def make_iter(obj):
|
||||
"Makes sure that the object is always iterable."
|
||||
return not hasattr(obj, '__iter__') and [obj] or obj
|
||||
|
||||
|
||||
def fill(text, width=78, indent=0):
|
||||
"""
|
||||
Safely wrap text to a certain number of characters.
|
||||
|
|
@ -69,7 +79,8 @@ def crop(text, width=78, suffix="[...]"):
|
|||
return text
|
||||
else:
|
||||
lsuffix = len(suffix)
|
||||
return "%s%s" % (text[:width-lsuffix], suffix)
|
||||
return "%s%s" % (text[:width - lsuffix], suffix)
|
||||
|
||||
|
||||
def dedent(text):
|
||||
"""
|
||||
|
|
@ -83,6 +94,7 @@ def dedent(text):
|
|||
return ""
|
||||
return textwrap.dedent(text)
|
||||
|
||||
|
||||
def list_to_string(inlist, endsep="and", addquote=False):
|
||||
"""
|
||||
This pretty-formats a list as string output, adding
|
||||
|
|
@ -108,6 +120,7 @@ def list_to_string(inlist, endsep="and", addquote=False):
|
|||
return str(inlist[0])
|
||||
return ", ".join(str(v) for v in inlist[:-1]) + " %s %s" % (endsep, inlist[-1])
|
||||
|
||||
|
||||
def wildcard_to_regexp(instring):
|
||||
"""
|
||||
Converts a player-supplied string that may have wildcards in it to regular
|
||||
|
|
@ -123,7 +136,7 @@ def wildcard_to_regexp(instring):
|
|||
regexp_string += "^"
|
||||
|
||||
# Replace any occurances of * or ? with the appropriate groups.
|
||||
regexp_string += instring.replace("*","(.*)").replace("?", "(.{1})")
|
||||
regexp_string += instring.replace("*", "(.*)").replace("?", "(.{1})")
|
||||
|
||||
# If there's an asterisk at the end of the string, we can't impose the
|
||||
# end of string ($) limiter.
|
||||
|
|
@ -132,6 +145,7 @@ def wildcard_to_regexp(instring):
|
|||
|
||||
return regexp_string
|
||||
|
||||
|
||||
def time_format(seconds, style=0):
|
||||
"""
|
||||
Function to return a 'prettified' version of a value in seconds.
|
||||
|
|
@ -146,11 +160,11 @@ def time_format(seconds, style=0):
|
|||
# We'll just use integer math, no need for decimal precision.
|
||||
seconds = int(seconds)
|
||||
|
||||
days = seconds / 86400
|
||||
days = seconds / 86400
|
||||
seconds -= days * 86400
|
||||
hours = seconds / 3600
|
||||
hours = seconds / 3600
|
||||
seconds -= hours * 3600
|
||||
minutes = seconds / 60
|
||||
minutes = seconds / 60
|
||||
seconds -= minutes * 60
|
||||
|
||||
if style is 0:
|
||||
|
|
@ -225,6 +239,7 @@ def time_format(seconds, style=0):
|
|||
|
||||
return retval
|
||||
|
||||
|
||||
def datetime_format(dtobj):
|
||||
"""
|
||||
Takes a datetime object instance (e.g. from django's DateTimeField)
|
||||
|
|
@ -250,6 +265,7 @@ def datetime_format(dtobj):
|
|||
timestring = "%02i:%02i:%02i" % (hour, minute, second)
|
||||
return timestring
|
||||
|
||||
|
||||
def host_os_is(osname):
|
||||
"""
|
||||
Check to see if the host OS matches the query.
|
||||
|
|
@ -258,6 +274,7 @@ def host_os_is(osname):
|
|||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_evennia_version():
|
||||
"""
|
||||
Check for the evennia version info.
|
||||
|
|
@ -268,6 +285,7 @@ def get_evennia_version():
|
|||
except IOError:
|
||||
return "Unknown version"
|
||||
|
||||
|
||||
def pypath_to_realpath(python_path, file_ending='.py'):
|
||||
"""
|
||||
Converts a path on dot python form (e.g. 'src.objects.models') to
|
||||
|
|
@ -284,11 +302,13 @@ def pypath_to_realpath(python_path, file_ending='.py'):
|
|||
return "%s%s" % (path, file_ending)
|
||||
return path
|
||||
|
||||
|
||||
def dbref(dbref, reqhash=True):
|
||||
"""
|
||||
Converts/checks if input is a valid dbref.
|
||||
If reqhash is set, only input strings on the form '#N', where N is an integer
|
||||
is accepted. Otherwise strings '#N', 'N' and integers N are all accepted.
|
||||
If reqhash is set, only input strings on the form '#N', where N is an
|
||||
integer is accepted. Otherwise strings '#N', 'N' and integers N are all
|
||||
accepted.
|
||||
Output is the integer part.
|
||||
"""
|
||||
if reqhash:
|
||||
|
|
@ -301,6 +321,7 @@ def dbref(dbref, reqhash=True):
|
|||
return int(dbref) if dbref.isdigit() else None
|
||||
return dbref if isinstance(dbref, int) else None
|
||||
|
||||
|
||||
def to_unicode(obj, encoding='utf-8', force_string=False):
|
||||
"""
|
||||
This decodes a suitable object to the unicode format. Note that
|
||||
|
|
@ -335,6 +356,7 @@ def to_unicode(obj, encoding='utf-8', force_string=False):
|
|||
raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding))
|
||||
return obj
|
||||
|
||||
|
||||
def to_str(obj, encoding='utf-8', force_string=False):
|
||||
"""
|
||||
This encodes a unicode string back to byte-representation,
|
||||
|
|
@ -366,6 +388,7 @@ def to_str(obj, encoding='utf-8', force_string=False):
|
|||
raise Exception("Error: Unicode could not encode unicode string '%s'(%s) to a bytestring. " % (obj, encoding))
|
||||
return obj
|
||||
|
||||
|
||||
def validate_email_address(emailaddress):
|
||||
"""
|
||||
Checks if an email address is syntactically correct.
|
||||
|
|
@ -382,18 +405,18 @@ def validate_email_address(emailaddress):
|
|||
|
||||
# Email address must be more than 7 characters in total.
|
||||
if len(emailaddress) < 7:
|
||||
return False # Address too short.
|
||||
return False # Address too short.
|
||||
|
||||
# Split up email address into parts.
|
||||
try:
|
||||
localpart, domainname = emailaddress.rsplit('@', 1)
|
||||
host, toplevel = domainname.rsplit('.', 1)
|
||||
except ValueError:
|
||||
return False # Address does not have enough parts.
|
||||
return False # Address does not have enough parts.
|
||||
|
||||
# Check for Country code or Generic Domain.
|
||||
if len(toplevel) != 2 and toplevel not in domains:
|
||||
return False # Not a domain name.
|
||||
return False # Not a domain name.
|
||||
|
||||
for i in '-_.%+.':
|
||||
localpart = localpart.replace(i, "")
|
||||
|
|
@ -401,9 +424,9 @@ def validate_email_address(emailaddress):
|
|||
host = host.replace(i, "")
|
||||
|
||||
if localpart.isalnum() and host.isalnum():
|
||||
return True # Email address is fine.
|
||||
return True # Email address is fine.
|
||||
else:
|
||||
return False # Email address has funny characters.
|
||||
return False # Email address has funny characters.
|
||||
|
||||
|
||||
def inherits_from(obj, parent):
|
||||
|
|
@ -447,6 +470,7 @@ def server_services():
|
|||
del SESSIONS
|
||||
return server
|
||||
|
||||
|
||||
def uses_database(name="sqlite3"):
|
||||
"""
|
||||
Checks if the game is currently using a given database. This is a
|
||||
|
|
@ -460,13 +484,15 @@ def uses_database(name="sqlite3"):
|
|||
engine = settings.DATABASE_ENGINE
|
||||
return engine == "django.db.backends.%s" % name
|
||||
|
||||
|
||||
def delay(delay=2, retval=None, callback=None):
|
||||
"""
|
||||
Delay the return of a value.
|
||||
Inputs:
|
||||
delay (int) - the delay in seconds
|
||||
retval (any) - this will be returned by this function after a delay
|
||||
callback (func(retval)) - if given, this will be called with retval after delay seconds
|
||||
callback (func(retval)) - if given, this will be called with retval
|
||||
after delay seconds
|
||||
Returns:
|
||||
deferred that will fire with to_return after delay seconds
|
||||
"""
|
||||
|
|
@ -499,14 +525,15 @@ def clean_object_caches(obj):
|
|||
pass
|
||||
|
||||
# on-object property cache
|
||||
[_DA(obj, cname) for cname in obj.__dict__.keys() if cname.startswith("_cached_db_")]
|
||||
[_DA(obj, cname) for cname in obj.__dict__.keys()
|
||||
if cname.startswith("_cached_db_")]
|
||||
try:
|
||||
hashid = _GA(obj, "hashid")
|
||||
hasid = obj.hashid
|
||||
_TYPECLASSMODELS._ATTRIBUTE_CACHE[hashid] = {}
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
_PPOOL = None
|
||||
_PCMD = None
|
||||
_PROC_ERR = "A process has ended with a probable error condition: process ended by signal 9."
|
||||
|
|
@ -574,7 +601,6 @@ def run_async(to_execute, *args, **kwargs):
|
|||
deferred.addCallback(callback, **callback_kwargs)
|
||||
deferred.addErrback(errback, **errback_kwargs)
|
||||
|
||||
#
|
||||
|
||||
def check_evennia_dependencies():
|
||||
"""
|
||||
|
|
@ -631,7 +657,7 @@ def check_evennia_dependencies():
|
|||
if settings.IRC_ENABLED:
|
||||
try:
|
||||
import twisted.words
|
||||
twisted.words # set to avoid debug info about not-used import
|
||||
twisted.words # set to avoid debug info about not-used import
|
||||
except ImportError:
|
||||
errstring += "\n ERROR: IRC is enabled, but twisted.words is not installed. Please install it."
|
||||
errstring += "\n Linux Debian/Ubuntu users should install package 'python-twisted-words', others"
|
||||
|
|
@ -642,6 +668,7 @@ def check_evennia_dependencies():
|
|||
print "%s\n %s\n%s" % ("-"*78, errstring, '-'*78)
|
||||
return no_error
|
||||
|
||||
|
||||
def has_parent(basepath, obj):
|
||||
"Checks if basepath is somewhere in objs parent tree."
|
||||
try:
|
||||
|
|
@ -652,17 +679,19 @@ def has_parent(basepath, obj):
|
|||
# instance. Not sure if one should defend against this.
|
||||
return False
|
||||
|
||||
|
||||
def mod_import(module):
|
||||
"""
|
||||
A generic Python module loader.
|
||||
|
||||
Args:
|
||||
module - this can be either a Python path (dot-notation like src.objects.models),
|
||||
an absolute path (e.g. /home/eve/evennia/src/objects.models.py)
|
||||
module - this can be either a Python path (dot-notation like
|
||||
src.objects.models), an absolute path
|
||||
(e.g. /home/eve/evennia/src/objects.models.py)
|
||||
or an already import module object (e.g. models)
|
||||
Returns:
|
||||
an imported module. If the input argument was already a model, this is returned as-is,
|
||||
otherwise the path is parsed and imported.
|
||||
an imported module. If the input argument was already a model,
|
||||
this is returned as-is, otherwise the path is parsed and imported.
|
||||
Error:
|
||||
returns None. The error is also logged.
|
||||
"""
|
||||
|
|
@ -691,7 +720,7 @@ def mod_import(module):
|
|||
if not module:
|
||||
return None
|
||||
|
||||
if type(module) == types.ModuleType:
|
||||
if isinstance(module, types.ModuleType):
|
||||
# if this is already a module, we are done
|
||||
mod = module
|
||||
else:
|
||||
|
|
@ -699,8 +728,9 @@ def mod_import(module):
|
|||
try:
|
||||
mod = __import__(module, fromlist=["None"])
|
||||
except ImportError, ex:
|
||||
# check just where the ImportError happened (it could have been an erroneous
|
||||
# import inside the module as well). This is the trivial way to do it ...
|
||||
# check just where the ImportError happened (it could have been
|
||||
# an erroneous import inside the module as well). This is the
|
||||
# trivial way to do it ...
|
||||
if str(ex) != "Import by filename is not supported.":
|
||||
#log_trace("ImportError inside module '%s': '%s'" % (module, str(ex)))
|
||||
raise
|
||||
|
|
@ -726,29 +756,36 @@ def mod_import(module):
|
|||
result[0].close()
|
||||
return mod
|
||||
|
||||
|
||||
def all_from_module(module):
|
||||
"""
|
||||
Return all global-level variables from a module as a dict
|
||||
"""
|
||||
mod = mod_import(module)
|
||||
return dict((key, val) for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val)))
|
||||
return dict((key, val) for key, val in mod.__dict__.items()
|
||||
if not (key.startswith("_") or ismodule(val)))
|
||||
|
||||
|
||||
def variable_from_module(module, variable=None, default=None):
|
||||
"""
|
||||
Retrieve a variable or list of variables from a module. The variable(s) must be defined
|
||||
globally in the module. If no variable is given (or a list entry is None), a random variable
|
||||
is extracted from the module.
|
||||
Retrieve a variable or list of variables from a module. The variable(s)
|
||||
must be defined globally in the module. If no variable is given (or a
|
||||
list entry is None), a random variable is extracted from the module.
|
||||
|
||||
If module cannot be imported or given variable not found, default
|
||||
is returned.
|
||||
|
||||
Args:
|
||||
module (string or module)- python path, absolute path or a module
|
||||
variable (string or iterable) - single variable name or iterable of variable names to extract
|
||||
default (string) - default value to use if a variable fails to be extracted.
|
||||
variable (string or iterable) - single variable name or iterable of
|
||||
variable names to extract
|
||||
default (string) - default value to use if a variable fails
|
||||
to be extracted.
|
||||
Returns:
|
||||
a single value or a list of values depending on the type of 'variable' argument. Errors in lists
|
||||
are replaced by the 'default' argument."""
|
||||
a single value or a list of values depending on the type of
|
||||
'variable' argument. Errors in lists are replaced by the
|
||||
'default' argument.
|
||||
"""
|
||||
|
||||
if not module:
|
||||
return default
|
||||
|
|
@ -761,12 +798,14 @@ def variable_from_module(module, variable=None, default=None):
|
|||
result.append(mod.__dict__.get(var, default))
|
||||
else:
|
||||
# random selection
|
||||
mvars = [val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val))]
|
||||
mvars = [val for key, val in mod.__dict__.items()
|
||||
if not (key.startswith("_") or ismodule(val))]
|
||||
result.append((mvars and random.choice(mvars)) or default)
|
||||
if len(result) == 1:
|
||||
return result[0]
|
||||
return result
|
||||
|
||||
|
||||
def string_from_module(module, variable=None, default=None):
|
||||
"""
|
||||
This is a wrapper for variable_from_module that requires return
|
||||
|
|
@ -779,6 +818,7 @@ def string_from_module(module, variable=None, default=None):
|
|||
return [(isinstance(v, basestring) and v or default) for v in val]
|
||||
return default
|
||||
|
||||
|
||||
def init_new_player(player):
|
||||
"""
|
||||
Helper method to call all hooks, set flags etc on a newly created
|
||||
|
|
@ -790,41 +830,50 @@ def init_new_player(player):
|
|||
# player.character.db.FIRST_LOGIN = True
|
||||
player.db.FIRST_LOGIN = True
|
||||
|
||||
|
||||
def string_similarity(string1, string2):
|
||||
"""
|
||||
This implements a "cosine-similarity" algorithm as described for example in
|
||||
Proceedings of the 22nd International Conference on Computation Linguistics
|
||||
(Coling 2008), pages 593-600, Manchester, August 2008
|
||||
The measure-vectors used is simply a "bag of words" type histogram (but for letters).
|
||||
Proceedings of the 22nd International Conference on Computation
|
||||
Linguistics (Coling 2008), pages 593-600, Manchester, August 2008
|
||||
The measure-vectors used is simply a "bag of words" type histogram
|
||||
(but for letters).
|
||||
|
||||
The function returns a value 0...1 rating how similar the two strings are. The strings can
|
||||
contain multiple words.
|
||||
The function returns a value 0...1 rating how similar the two strings
|
||||
are. The strings can contain multiple words.
|
||||
"""
|
||||
vocabulary = set(list(string1 + string2))
|
||||
vec1 = [string1.count(v) for v in vocabulary]
|
||||
vec2 = [string2.count(v) for v in vocabulary]
|
||||
try:
|
||||
return float(sum(vec1[i]*vec2[i] for i in range(len(vocabulary)))) / \
|
||||
return float(sum(vec1[i] * vec2[i] for i in range(len(vocabulary)))) / \
|
||||
(math.sqrt(sum(v1**2 for v1 in vec1)) * math.sqrt(sum(v2**2 for v2 in vec2)))
|
||||
except ZeroDivisionError:
|
||||
# can happen if empty-string cmdnames appear for some reason. This is a no-match.
|
||||
# can happen if empty-string cmdnames appear for some reason.
|
||||
# This is a no-match.
|
||||
return 0
|
||||
|
||||
|
||||
def string_suggestions(string, vocabulary, cutoff=0.6, maxnum=3):
|
||||
"""
|
||||
Given a string and a vocabulary, return a match or a list of suggestsion based on
|
||||
string similarity.
|
||||
Given a string and a vocabulary, return a match or a list of suggestsion
|
||||
based on string similarity.
|
||||
|
||||
Args:
|
||||
string (str)- a string to search for
|
||||
vocabulary (iterable) - a list of available strings
|
||||
cutoff (int, 0-1) - limit the similarity matches (higher, the more exact is required)
|
||||
cutoff (int, 0-1) - limit the similarity matches (higher, the more
|
||||
exact is required)
|
||||
maxnum (int) - maximum number of suggestions to return
|
||||
Returns:
|
||||
list of suggestions from vocabulary (could be empty if there are no matches)
|
||||
list of suggestions from vocabulary (could be empty if there are
|
||||
no matches)
|
||||
"""
|
||||
return [tup[1] for tup in sorted([(string_similarity(string, sugg), sugg) for sugg in vocabulary],
|
||||
key=lambda tup: tup[0], reverse=True) if tup[0] >= cutoff][:maxnum]
|
||||
return [tup[1] for tup in sorted([(string_similarity(string, sugg), sugg)
|
||||
for sugg in vocabulary],
|
||||
key=lambda tup: tup[0], reverse=True)
|
||||
if tup[0] >= cutoff][:maxnum]
|
||||
|
||||
|
||||
def string_partial_matching(alternatives, inp, ret_index=True):
|
||||
"""
|
||||
|
|
@ -837,7 +886,8 @@ def string_partial_matching(alternatives, inp, ret_index=True):
|
|||
Input:
|
||||
alternatives (list of str) - list of possible strings to match
|
||||
inp (str) - search criterion
|
||||
ret_index (bool) - return list of indices (from alternatives array) or strings
|
||||
ret_index (bool) - return list of indices (from alternatives
|
||||
array) or strings
|
||||
Returns:
|
||||
list of matching indices or strings, or an empty list
|
||||
|
||||
|
|
@ -855,7 +905,8 @@ def string_partial_matching(alternatives, inp, ret_index=True):
|
|||
# loop over parts, making sure only to visit each part once
|
||||
# (this will invalidate input in the wrong word order)
|
||||
submatch = [last_index + alt_num for alt_num, alt_word
|
||||
in enumerate(alt_words[last_index:]) if alt_word.startswith(inp_word)]
|
||||
in enumerate(alt_words[last_index:])
|
||||
if alt_word.startswith(inp_word)]
|
||||
if submatch:
|
||||
last_index = min(submatch) + 1
|
||||
score += 1
|
||||
|
|
@ -871,6 +922,7 @@ def string_partial_matching(alternatives, inp, ret_index=True):
|
|||
return matches[max(matches)]
|
||||
return []
|
||||
|
||||
|
||||
def format_table(table, extra_space=1):
|
||||
"""
|
||||
Note: src.utils.prettytable is more powerful than this, but this
|
||||
|
|
@ -909,6 +961,7 @@ def format_table(table, extra_space=1):
|
|||
for icol, col in enumerate(table)])
|
||||
return ftable
|
||||
|
||||
|
||||
def get_evennia_pids():
|
||||
"""
|
||||
Get the currently valids PIDs (Process IDs) of the Portal and Server
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue