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:
Griatch 2013-11-14 19:31:17 +01:00
parent 30b7d2a405
commit 1ae17bcbe4
154 changed files with 5613 additions and 4054 deletions

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -96,7 +96,7 @@ in-game.
from ev import Command, Script, CmdSet
TRADE_TIMEOUT = 60 # timeout for B to accept trade
TRADE_TIMEOUT = 60 # timeout for B to accept trade
class TradeTimeout(Script):
@ -111,15 +111,18 @@ class TradeTimeout(Script):
self.start_delay = True
self.repeats = 1
self.persistent = False
def at_repeat(self):
"called once"
if self.ndb.tradeevent:
self.obj.ndb.tradeevent.finish(force=True)
self.obj.msg("Trade request timed out.")
def is_valid(self):
"Only valid if the trade has not yet started"
return self.obj.ndb.tradeevent and not self.obj.ndb.tradeevent.trade_started
class TradeHandler(object):
"""
Objects of this class handles the ongoing trade, notably storing the current
@ -131,7 +134,8 @@ class TradeHandler(object):
a trade with part B. The trade will not start until part B repeats
this command (B will then call the self.join() command)
We also store the back-reference from the respective party to this object.
We also store the back-reference from the respective party to
this object.
"""
# parties
self.partA = partA
@ -145,7 +149,7 @@ class TradeHandler(object):
self.partB_offers = []
self.partA_accepted = False
self.partB_accepted = False
# start a timer
def msg(self, party, string):
"""
Relay a message to the other party. This allows
@ -159,6 +163,7 @@ class TradeHandler(object):
else:
# no match, relay to oneself
self.party.msg(string)
def get_other(self, party):
"Returns the other party of the trade"
if self.partA == party:
@ -171,13 +176,14 @@ class TradeHandler(object):
"""
This is used once B decides to join the trade
"""
print "join:", self.partB, partB, self.partB == partB, type(self.partB),type(partB)
print "join:", self.partB, partB, self.partB == partB, type(self.partB), type(partB)
if self.partB == partB:
self.partB.ndb.tradehandler = self
self.partB.cmdset.add(CmdsetTrade())
self.trade_started = True
return True
return False
def unjoin(self, partB):
"""
This is used if B decides not to join the trade
@ -203,6 +209,7 @@ class TradeHandler(object):
self.partB_offers = list(args)
else:
raise ValueError
def list(self):
"""
Returns two lists of objects on offer, separated by partA/B.
@ -244,7 +251,8 @@ class TradeHandler(object):
self.partB_accepted = True
else:
raise ValueError
return self.finish() # try to close the deal
return self.finish() # try to close the deal
def decline(self, party):
"""
Remove an previously accepted status (changing ones mind)
@ -264,6 +272,7 @@ class TradeHandler(object):
return False
else:
raise ValueError
def finish(self, force=False):
"""
Conclude trade - move all offers and clean up
@ -282,11 +291,13 @@ class TradeHandler(object):
self.partB.cmdset.delete("cmdset_trade")
self.partA_offers = None
self.partB_offers = None
del self.partA.ndb.tradehandler # this will kill it also from partB
# this will kill it also from partB
del self.partA.ndb.tradehandler
if self.partB.ndb.tradehandler:
del self.partB.ndb.tradehandler
return True
# trading commands (will go into CmdsetTrade, initialized by the
# CmdTrade command further down).
@ -320,6 +331,7 @@ class CmdTradeBase(Command):
else:
self.str_other = '%s says, "' % self.caller.key + self.emote + '"\n [%s]'
# trade help
class CmdTradeHelp(CmdTradeBase):
@ -342,24 +354,30 @@ class CmdTradeHelp(CmdTradeBase):
Trading commands
{woffer <objects> [:emote]{n
offer one or more objects for trade. The emote can be used for RP/arguments.
A new offer will require both parties to re-accept it again.
offer one or more objects for trade. The emote can be used for
RP/arguments. A new offer will require both parties to re-accept
it again.
{waccept [:emote]{n
accept the currently standing offer from both sides. Also 'agree' works.
Once both have accepted, the deal is finished and goods will change hands.
accept the currently standing offer from both sides. Also 'agree'
works. Once both have accepted, the deal is finished and goods
will change hands.
{wdecline [:emote]{n
change your mind and remove a previous accept (until other has also accepted)
change your mind and remove a previous accept (until other
has also accepted)
{wstatus{n
show the current offers on each side of the deal. Also 'offers' and 'deal' works.
show the current offers on each side of the deal. Also 'offers'
and 'deal' works.
{wevaluate <nr> or <offer>{n
examine any offer in the deal. List them with the 'status' command.
{wend trade{n
end the negotiations prematurely. No trade will take place.
You can also use {wemote{n, {wsay{n etc to discuss without making a decision or offer.
You can also use {wemote{n, {wsay{n etc to discuss
without making a decision or offer.
"""
self.caller.msg(string)
# offer
class CmdOffer(CmdTradeBase):
@ -406,6 +424,7 @@ class CmdOffer(CmdTradeBase):
caller.msg(self.str_caller % ("You offer %s" % objnames))
self.msg_other(caller, self.str_other % ("They offer %s" % objnames))
# accept
class CmdAccept(CmdTradeBase):
@ -441,6 +460,7 @@ class CmdAccept(CmdTradeBase):
caller.msg(self.str_caller % "You {Gaccept{n the offer. %s must now also accept." % self.other.key)
self.msg_other(caller, self.str_other % "%s {Gaccepts{n the offer. You must now also accept." % caller.key)
# decline
class CmdDecline(CmdTradeBase):
@ -521,6 +541,7 @@ class CmdEvaluate(CmdTradeBase):
# show the description
caller.msg(offer.db.desc)
# status
class CmdStatus(CmdTradeBase):
@ -571,6 +592,7 @@ class CmdStatus(CmdTradeBase):
string += "\n Use 'offer', 'eval' and 'accept'/'decline' to trade. See also 'trade help'."
caller.msg(string)
# finish
class CmdFinish(CmdTradeBase):
@ -596,6 +618,7 @@ class CmdFinish(CmdTradeBase):
caller.msg(self.str_caller % "You {raborted{n trade. No deal was made.")
self.msg_other(caller, self.str_other % "%s {raborted{n trade. No deal was made." % caller.key)
# custom Trading cmdset
class CmdsetTrade(CmdSet):
@ -616,8 +639,8 @@ class CmdsetTrade(CmdSet):
self.add(CmdFinish())
# access command - once both have given this, this will create the trading cmdset
# to start trade.
# access command - once both have given this, this will create the
# trading cmdset to start trade.
class CmdTrade(Command):
"""
@ -662,9 +685,9 @@ class CmdTrade(Command):
else:
theiremote = '%s says, "%s"\n ' % (self.caller.key, emote)
# for the sake of this command, the caller is always partA; this might not
# match the actual name in tradehandler (in the case of using this command
# to accept/decline a trade invitation).
# for the sake of this command, the caller is always partA; this
# might not match the actual name in tradehandler (in the case of
# using this command to accept/decline a trade invitation).
partA = self.caller
accept = 'accept' in self.args
decline = 'decline' in self.args

View file

@ -122,9 +122,11 @@ class CmdOOCLook(default_cmds.CmdLook):
else:
# not ooc mode - leave back to normal look
self.caller = self.character # we have to put this back for normal look to work.
# we have to put this back for normal look to work.
self.caller = self.character
super(CmdOOCLook, self).func()
class CmdOOCCharacterCreate(Command):
"""
creates a character
@ -179,9 +181,9 @@ class CmdOOCCharacterCreate(Command):
else:
avail_chars = [new_character.id]
self.caller.db._character_dbrefs = avail_chars
self.caller.msg("{gThe Character {c%s{g was successfully created!" % charname)
class OOCCmdSetCharGen(default_cmds.OOCCmdSet):
"""
Extends the default OOC cmdset.

View file

@ -34,6 +34,7 @@ import re
from random import randint
from ev import default_cmds
def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False):
"""
This is a standard dice roller.
@ -41,18 +42,22 @@ def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=F
Input:
dicenum - number of dice to roll (the result to be added)
dicetype - number of sides of the dice to be rolled
modifier - tuple (operator, value), where operator is a character string with one of +,-,/ or *. The
entire result of the dice rolls will be modified by this value.
conditional - tuple (conditional, value), where conditional is a character string with one of ==,<,>,>=,<= or !=.
modifier - tuple (operator, value), where operator is a character string
with one of +,-,/ or *. The entire result of the dice rolls will
be modified by this value.
conditional - tuple (conditional, value), where conditional is a character
string with one of ==,<,>,>=,<= or !=.
return_tuple - return result as a tuple containing all relevant info
return_tuple - (default False) - return a tuple with all individual roll results
return_tuple - (default False) - return a tuple with all individual roll
results
All input numbers are converted to integers.
Returns:
normally returns the result
if return_tuple=True, returns a tuple (result, outcome, diff, rolls)
In this tuple, outcome and diff will be None if conditional is not set. rolls is itself
a tuple holding all the individual rolls in the case of multiple die-rolls.
In this tuple, outcome and diff will be None if conditional is
not set. rolls is itself a tuple holding all the individual
rolls in the case of multiple die-rolls.
Raises:
TypeError if non-supported modifiers or conditionals are given.
@ -62,7 +67,7 @@ def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=F
dicetype = int(dicetype)
# roll all dice, remembering each roll
rolls= tuple([randint(1, dicetype) for roll in range(dicenum)])
rolls = tuple([randint(1, dicetype) for roll in range(dicenum)])
result = sum(rolls)
if modifier:
@ -70,7 +75,7 @@ def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=F
mod, modvalue = modifier
if not mod in ('+', '-', '*', '/'):
raise TypeError("Non-supported dice modifier: %s" % mod)
modvalue = int(modvalue) # for safety
modvalue = int(modvalue) # for safety
result = eval("%s %s %s" % (result, mod, modvalue))
outcome, diff = None, None
if conditional:
@ -78,19 +83,19 @@ def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=F
cond, condvalue = conditional
if not cond in ('>', '<', '>=', '<=', '!=', '=='):
raise TypeError("Non-supported dice result conditional: %s" % conditional)
condvalue = int(condvalue) # for safety
outcome = eval("%s %s %s" % (result, cond, condvalue)) # gives True/False
condvalue = int(condvalue) # for safety
outcome = eval("%s %s %s" % (result, cond, condvalue)) # True/False
diff = abs(result - condvalue)
if return_tuple:
return (result, outcome, diff, rolls)
else:
return result
RE_PARTS = re.compile(r"(d|\+|-|/|\*|<|>|<=|>=|!=|==)")
RE_MOD = re.compile(r"(\+|-|/|\*)")
RE_COND = re.compile(r"(<|>|<=|>=|!=|==)")
class CmdDice(default_cmds.MuxCommand):
"""
roll dice
@ -106,14 +111,16 @@ class CmdDice(default_cmds.MuxCommand):
dice 3d6 + 4
dice 1d100 - 2 < 50
This will roll the given number of dice with given sides and modifiers. So e.g. 2d6 + 3
means to 'roll a 6-sided die 2 times and add the result, then add 3 to the total'.
This will roll the given number of dice with given sides and modifiers.
So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,
then add 3 to the total'.
Accepted modifiers are +, -, * and /.
A success condition is given as normal Python conditionals (<,>,<=,>=,==,!=).
So e.g. 2d6 + 3 > 10 means that the roll will succeed only if the final result is above 8.
If a success condition is given, the outcome (pass/fail) will be echoed along with how
much it succeeded/failed with. The hidden/secret switches will hide all or parts of the roll
from everyone but the person rolling.
A success condition is given as normal Python conditionals
(<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed
only if the final result is above 8. If a success condition is given, the
outcome (pass/fail) will be echoed along with how much it succeeded/failed
with. The hidden/secret switches will hide all or parts of the roll from
everyone but the person rolling.
"""
key = "dice"
@ -145,9 +152,9 @@ class CmdDice(default_cmds.MuxCommand):
pass
elif lparts == 5:
# either e.g. 1d6 + 3 or something like 1d6 > 3
if parts[3] in ('+','-','*','/'):
if parts[3] in ('+', '-', '*', '/'):
modifier = (parts[3], parts[4])
else: #assume it is a conditional
else: # assume it is a conditional
conditional = (parts[3], parts[4])
elif lparts == 7:
# the whole sequence, e.g. 1d6 + 3 > 5
@ -159,7 +166,11 @@ class CmdDice(default_cmds.MuxCommand):
return
# do the roll
try:
result, outcome, diff, rolls = roll_dice(ndice, nsides, modifier=modifier, conditional=conditional, return_tuple=True)
result, outcome, diff, rolls = roll_dice(ndice,
nsides,
modifier=modifier,
conditional=conditional,
return_tuple=True)
except ValueError:
self.caller.msg("You need to enter valid integer numbers, modifiers and operators. {w%s{n was not understood." % self.args)
return
@ -168,7 +179,7 @@ class CmdDice(default_cmds.MuxCommand):
rolls = ", ".join(str(roll) for roll in rolls[:-1]) + " and " + str(rolls[-1])
else:
rolls = rolls[0]
if outcome == None:
if outcome is None:
outcomestring = ""
elif outcome:
outcomestring = " This is a {gsuccess{n (by %s)." % diff

View file

@ -46,7 +46,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")
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
CONNECTION_SCREEN = ""
@ -57,6 +58,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.
@ -68,7 +70,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):
"""
@ -104,7 +106,7 @@ class CmdUnconnectedConnect(MuxCommand):
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0]==player.name for tup in bans)
if bans and (any(tup[0] == player.name for tup in bans)
or
any(tup[2].match(session.address[0]) for tup in bans if tup[2])):
# this is a banned IP or name!
@ -160,7 +162,7 @@ class CmdUnconnectedCreate(MuxCommand):
else:
playername, email, password = self.arglist
playername = playername.replace('"', '') # remove "
playername = playername.replace('"', '') # remove "
playername = playername.replace("'", "")
self.playerinfo = (playername, email, password)
@ -214,17 +216,20 @@ its and @/./+/-/_ only.") # this echoes the restrictions made by django's auth m
permissions = settings.PERMISSION_PLAYER_DEFAULT
try:
new_character = create.create_player(playername, email, password,
new_character = create.create_player(playername,
email,
password,
permissions=permissions,
character_typeclass=typeclass,
character_location=default_home,
character_home=default_home)
except Exception, e:
except Exception:
session.msg("There was an error creating the default Character/Player:\n%s\n If this problem persists, contact an admin.")
return
new_player = new_character.player
# This needs to be called so the engine knows this player is logging in for the first time.
# 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)
@ -236,11 +241,11 @@ its and @/./+/-/_ only.") # this echoes the restrictions made by django's auth m
string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_errmsg(string)
# allow only the character itself and the player to puppet this character (and Immortals).
# allow only the character itself and the player to puppet
# this character (and Immortals).
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
(new_character.id, new_player.id))
# set a default description
new_character.db.desc = "This is a Player."
@ -250,12 +255,14 @@ its and @/./+/-/_ only.") # this echoes the restrictions made by django's auth m
session.msg(string % (playername, email, email))
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
@ -272,6 +279,7 @@ class CmdUnconnectedQuit(MuxCommand):
session.msg("Good bye! Disconnecting ...")
session.session_disconnect()
class CmdUnconnectedLook(MuxCommand):
"""
This is an unconnected version of the look command for simplicity.
@ -287,6 +295,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,
@ -328,6 +337,7 @@ You can use the {wlook{n command if you want to see the connect screen again.
"""
self.caller.msg(string)
# command set for the mux-like login
class UnloggedinCmdSet(CmdSet):

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -15,6 +15,7 @@ from ev import utils
from ev import default_cmds
from src.utils import prettytable
#------------------------------------------------------------
# Evlang-related commands
#
@ -86,13 +87,17 @@ class CmdCode(default_cmds.MuxCommand):
if not self.rhs:
if codetype:
scripts = [(name, tup[1], utils.crop(tup[0])) for name, tup in evlang_scripts.items() if name==codetype]
scripts.extend([(name, "--", "--") for name in evlang_locks if name not in evlang_scripts if name==codetype])
scripts = [(name, tup[1], utils.crop(tup[0]))
for name, tup in evlang_scripts.items() if name==codetype]
scripts.extend([(name, "--", "--") for name in evlang_locks
if name not in evlang_scripts if name==codetype])
else:
# no type specified. List all scripts/slots on object
print evlang_scripts
scripts = [(name, tup[1], utils.crop(tup[0])) for name, tup in evlang_scripts.items()]
scripts.extend([(name, "--", "--") for name in evlang_locks if name not in evlang_scripts])
scripts = [(name, tup[1], utils.crop(tup[0]))
for name, tup in evlang_scripts.items()]
scripts.extend([(name, "--", "--") for name in evlang_locks
if name not in evlang_scripts])
scripts = sorted(scripts, key=lambda p: p[0])
table = prettytable.PrettyTable(["{wtype", "{wcreator", "{wcode"])
@ -114,7 +119,7 @@ class CmdCode(default_cmds.MuxCommand):
# we have code access to this type.
oldcode = None
if codetype in evlang_scripts:
oldcode = str(evlang_scripts[codetype][0])
oldcode = str(evlang_scripts[codetype][0])
# this updates the database right away too
obj.ndb.evlang.add(codetype, codestring, scripter=caller)
if oldcode:

View file

@ -98,19 +98,25 @@ _LOGGER = None
# specifically forbidden symbols
_EV_UNALLOWED_SYMBOLS = ["attr", "attributes", "delete"]
try: _EV_UNALLOWED_SYMBOLS.expand(settings.EVLANG_UNALLOWED_SYMBOLS)
except AttributeError: pass
try:
_EV_UNALLOWED_SYMBOLS.expand(settings.EVLANG_UNALLOWED_SYMBOLS)
except AttributeError:
pass
# safe methods (including self in args) to make available on
# the evl object
_EV_SAFE_METHODS = {}
try: _EV_SAFE_METHODS.update(settings.EVLANG_SAFE_METHODS)
except AttributeError: pass
try:
_EV_SAFE_METHODS.update(settings.EVLANG_SAFE_METHODS)
except AttributeError:
pass
# symbols to make available directly in code
_EV_SAFE_CONTEXT = {"testvar": "This is a safe var!"}
try: _EV_SAFE_CONTEXT.update(settings.EVLANG_SAFE_CONTEXT)
except AttributeError: pass
try:
_EV_SAFE_CONTEXT.update(settings.EVLANG_SAFE_CONTEXT)
except AttributeError:
pass
#------------------------------------------------------------
@ -146,8 +152,9 @@ class Evl(object):
"""
# must do it this way since __dict__ is restricted
members = [mtup for mtup in inspect.getmembers(Evl, predicate=inspect.ismethod)
if not mtup[0].startswith("_")]
string = "\n".join(["{w%s{n\n %s" % (mtup[0], mtup[1].func_doc.strip()) for mtup in members])
if not mtup[0].startswith("_")]
string = "\n".join(["{w%s{n\n %s" % (mtup[0], mtup[1].func_doc.strip())
for mtup in members])
return string
def msg(self, string, obj=None):
@ -200,18 +207,24 @@ class Evl(object):
errobj = kwargs["errobj"]
del kwargs["errobj"]
# set up some callbacks for delayed execution
def errback(f, errobj):
"error callback"
if errobj:
try: f = f.getErrorMessage()
except: pass
try:
f = f.getErrorMessage()
except:
pass
errobj.msg("EVLANG delay error: " + str(f))
def runfunc(func, *args, **kwargs):
"threaded callback"
threads.deferToThread(func, *args, **kwargs).addErrback(errback, errobj)
# get things going
if seconds <= 120:
task.deferLater(reactor, seconds, runfunc, function, *args, **kwargs).addErrback(errback, errobj)
else:
raise EvlangError("delay() can only delay for a maximum of 120 seconds (got %ss)." % seconds )
raise EvlangError("delay() can only delay for a maximum of 120 seconds (got %ss)." % seconds)
return True
def attr(self, obj, attrname=None, value=None, delete=False):
@ -244,6 +257,7 @@ class EvlangError(Exception):
"Error for evlang handler"
pass
class Evlang(object):
"""
This is a handler for launching limited execution Python scripts.
@ -265,17 +279,23 @@ class Evlang(object):
assumed to be granted.
"""
def __init__(self, obj=None, scripts=None, storage_attr="evlang_scripts", safe_context=None, safe_timeout=2):
def __init__(self, obj=None, scripts=None, storage_attr="evlang_scripts",
safe_context=None, safe_timeout=2):
"""
Setup of the Evlang handler.
Input:
obj - a reference to the object this handler is defined on. If not set, handler will operate stand-alone.
scripts = dictionary {scriptname, (codestring, callerobj), ...} where callerobj can be None.
evlang_storage_attr - if obj is given, will look for a dictionary {scriptname, (codestring, callerobj)...}
stored in this given attribute name on that object.
safe_funcs - dictionary of {funcname:funcobj, ...} to make available for the execution environment
safe_timeout - the time we let a script run. If it exceeds this time, it will be blocked from running again.
obj - a reference to the object this handler is defined on. If not
set, handler will operate stand-alone.
scripts = dictionary {scriptname, (codestring, callerobj), ...}
where callerobj can be Noneevlang_storage_attr - if obj
is given, will look for a dictionary
{scriptname, (codestring, callerobj)...}
stored in this given attribute name on that object.
safe_funcs - dictionary of {funcname:funcobj, ...} to make available
for the execution environment
safe_timeout - the time we let a script run. If it exceeds this
time, it will be blocked from running again.
"""
self.obj = obj
@ -286,7 +306,7 @@ class Evlang(object):
self.evlang_scripts.update(scripts)
if self.obj:
self.evlang_scripts.update(obj.attributes.get(storage_attr))
self.safe_context = _EV_SAFE_CONTEXT # set by default + settings
self.safe_context = _EV_SAFE_CONTEXT # set by default + settings
if safe_context:
self.safe_context.update(safe_context)
self.timedout_codestrings = []
@ -322,12 +342,16 @@ class Evlang(object):
_LOGGER.log_errmsg("EVLANG time exceeded: caller: %s, scripter: %s, code: %s" % (caller, scripter, codestring))
if not self.msg(err, scripter, caller):
raise EvlangError(err)
def errback(f):
"We need an empty errback, to catch the traceback of defer.cancel()"
pass
return task.deferLater(reactor, timeout, alarm, codestring).addErrback(errback)
def stop_timer(self, _, deferred):
"Callback for stopping a previously started timer. Cancels the given deferred."
"""Callback for stopping a previously started timer.
Cancels the given deferred.
"""
deferred.cancel()
@inlineCallbacks
@ -337,7 +361,8 @@ class Evlang(object):
codestring - the actual code to execute.
scripter - the creator of the script. Preferentially sees error messages
caller - the object triggering the script - sees error messages if no scripter is given
caller - the object triggering the script - sees error messages if
no scripter is given
"""
# catching previously detected long-running code
@ -391,7 +416,6 @@ class Evlang(object):
# execute code
self.run(codestring, caller, scripter)
def add(self, scriptname, codestring, scripter=None):
"""
Add a new script to the handler. This will also save the
@ -401,7 +425,8 @@ class Evlang(object):
self.evlang_scripts[scriptname] = (codestring, scripter)
if self.obj:
# save to database
self.obj.attributes.add(self.evlang_storage_attr, self.evlang_scripts)
self.obj.attributes.add(self.evlang_storage_attr,
self.evlang_scripts)
def delete(self, scriptname):
"""
@ -411,7 +436,8 @@ class Evlang(object):
del self.evlang_scripts[scriptname]
if self.obj:
# update change to database
self.obj.attributes.add(self.evlang_storage_attr, self.evlang_scripts)
self.obj.attributes.add(self.evlang_storage_attr,
self.evlang_scripts)
#----------------------------------------------------------------------
@ -436,8 +462,6 @@ class Evlang(object):
# to create an infinite loop.
#----------------------------------------------------------------------
#----------------------------------------------------------------------
# Module globals.
#----------------------------------------------------------------------
@ -555,25 +579,31 @@ UNALLOWED_BUILTINS = set([
# in with new unsafe things
SAFE_BUILTINS = set([
'False', 'None', 'True', 'abs', 'all', 'any', 'apply', 'basestring',
'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod',
'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr',
'classmethod',
'cmp', 'coerce', 'complex', 'dict', 'divmod', 'enumerate', 'filter',
'float', 'format', 'frozenset', 'hash', 'hex', 'id', 'int',
'isinstance', 'issubclass', 'iter', 'len', 'list', 'long', 'map', 'max', 'min',
'next', 'object', 'oct', 'ord', 'pow', 'print', 'property', 'range', 'reduce',
'repr', 'reversed', 'round', 'set', 'slice', 'sorted', 'staticmethod', 'str',
'sum', 'tuple', 'unichr', 'unicode', 'xrange', 'zip' ])
'isinstance', 'issubclass', 'iter', 'len', 'list', 'long', 'map',
'max', 'min',
'next', 'object', 'oct', 'ord', 'pow', 'print', 'property', 'range',
'reduce',
'repr', 'reversed', 'round', 'set', 'slice', 'sorted', 'staticmethod',
'str',
'sum', 'tuple', 'unichr', 'unicode', 'xrange', 'zip'])
for ast_name in UNALLOWED_AST_NODES:
assert(is_valid_ast_node(ast_name))
for name in UNALLOWED_BUILTINS:
assert(is_valid_builtin(name))
def _cross_match_whitelist():
"check the whitelist's completeness"
available = ALL_BUILTINS - UNALLOWED_BUILTINS
diff = available.difference(SAFE_BUILTINS)
assert not diff, diff # check so everything not disallowed is in safe
assert not diff, diff # check so everything not disallowed is in safe
diff = SAFE_BUILTINS.difference(available)
assert not diff, diff # check so everything everything in safe is in not-disallowed
assert not diff, diff # check so everything in safe is in not-disallowed
_cross_match_whitelist()
def is_unallowed_ast_node(kind):
@ -595,6 +625,7 @@ UNALLOWED_ATTR = [
'f_exc_type', 'f_exc_value', 'f_globals', 'f_locals']
UNALLOWED_ATTR.extend(_EV_UNALLOWED_SYMBOLS)
def is_unallowed_attr(name):
return (name[:2] == '__' and name[-2:] == '__') or \
(name in UNALLOWED_ATTR)
@ -614,19 +645,26 @@ class LimitedExecError(object):
"""
def __init__(self, errmsg, lineno):
self.errmsg, self.lineno = errmsg, lineno
def __str__(self):
return "line %d : %s" % (self.lineno, self.errmsg)
class LimitedExecASTNodeError(LimitedExecError):
"Expression/statement in AST evaluates to a restricted AST node type."
pass
class LimitedExecBuiltinError(LimitedExecError):
"Expression/statement in tried to access a restricted builtin."
pass
class LimitedExecAttrError(LimitedExecError):
"Expression/statement in tried to access a restricted attribute."
pass
class LimitedExecVisitor(object):
"""
Data-driven visitor which walks the AST for some code and makes
@ -672,7 +710,8 @@ class LimitedExecVisitor(object):
def visit(self, node, *args):
"Recursively validate node and all of its children."
fn = getattr(self, 'visit' + classname(node))
if DEBUG: self.trace(node)
if DEBUG:
self.trace(node)
fn(node, *args)
for child in node.getChildNodes():
self.visit(child, *args)
@ -682,10 +721,10 @@ class LimitedExecVisitor(object):
name = node.getChildren()[0]
lineno = get_node_lineno(node)
if is_unallowed_builtin(name):
self.errors.append(LimitedExecBuiltinError( \
self.errors.append(LimitedExecBuiltinError(
"access to builtin '%s' is denied" % name, lineno))
elif is_unallowed_attr(name):
self.errors.append(LimitedExecAttrError( \
self.errors.append(LimitedExecAttrError(
"access to attribute '%s' is denied" % name, lineno))
def visitGetattr(self, node, *args):
@ -696,10 +735,10 @@ class LimitedExecVisitor(object):
except Exception:
name = ""
lineno = get_node_lineno(node)
if attrname == 'attr' and name =='evl':
if attrname == 'attr' and name == 'evl':
pass
elif is_unallowed_attr(attrname):
self.errors.append(LimitedExecAttrError( \
self.errors.append(LimitedExecAttrError(
"access to attribute '%s' is denied" % attrname, lineno))
def visitAssName(self, node, *args):
@ -710,8 +749,8 @@ class LimitedExecVisitor(object):
def visitPower(self, node, *args):
"Make sure power-of operations don't get too big"
if node.left.value > 1000000 or node.right.value > 10:
lineno = get_node_lineno(node)
self.errors.append(LimitedExecAttrError( \
lineno = get_node_lineno(node)
self.errors.append(LimitedExecAttrError(
"power law solution too big - restricted", lineno))
def ok(self, node, *args):
@ -721,7 +760,7 @@ class LimitedExecVisitor(object):
def fail(self, node, *args):
"Default callback for unallowed AST nodes."
lineno = get_node_lineno(node)
self.errors.append(LimitedExecASTNodeError( \
self.errors.append(LimitedExecASTNodeError(
"execution of '%s' statements is denied" % classname(node),
lineno))
@ -732,6 +771,7 @@ class LimitedExecVisitor(object):
if attr[:2] != '__':
print ' ' * 4, "%-15.15s" % attr, getattr(node, attr)
#----------------------------------------------------------------------
# Safe 'eval' replacement.
#----------------------------------------------------------------------
@ -740,6 +780,7 @@ class LimitedExecException(Exception):
"Base class for all safe-eval related errors."
pass
class LimitedExecCodeException(LimitedExecException):
"""
Exception class for reporting all errors which occured while
@ -754,6 +795,7 @@ class LimitedExecCodeException(LimitedExecException):
def __str__(self):
return '\n'.join([str(err) for err in self.errors])
class LimitedExecContextException(LimitedExecException):
"""
Exception class for reporting unallowed objects found in the dict
@ -769,6 +811,7 @@ class LimitedExecContextException(LimitedExecException):
def __str__(self):
return '\n'.join([str(err) for err in self.errors])
class LimitedExecTimeoutException(LimitedExecException):
"""
Exception class for reporting that code evaluation execeeded
@ -798,6 +841,7 @@ def validate_context(context):
raise LimitedExecContextException(ctx_errkeys, ctx_errors)
return True
def validate_code(codestring):
"validate a code string"
# prepare the code tree for checking
@ -809,6 +853,7 @@ def validate_code(codestring):
raise LimitedExecCodeException(codestring, checker.errors)
return True
def limited_exec(code, context = {}, timeout_secs=2, retobj=None, procpool_async=None):
"""
Validate source code and make sure it contains no unauthorized
@ -824,8 +869,8 @@ def limited_exec(code, context = {}, timeout_secs=2, retobj=None, procpool_async
retobj - only used if procpool_async is also given. Defines an Object
(which must define a msg() method), for receiving returns from
the execution.
procpool_async - a run_async function alternative to the one in src.utils.utils.
this must accept the keywords
procpool_async - a run_async function alternative to the one in
src.utils.utils. This must accept the keywords
proc_timeout (will be set to timeout_secs
at_return - a callback
at_err - an errback
@ -842,7 +887,10 @@ def limited_exec(code, context = {}, timeout_secs=2, retobj=None, procpool_async
if retobj:
callback = lambda r: retobj.msg(r)
errback = lambda e: retobj.msg(e)
procpool_async(code, *context, proc_timeout=timeout_secs, at_return=callback, at_err=errback)
procpool_async(code, *context,
proc_timeout=timeout_secs,
at_return=callback,
at_err=errback)
else:
procpool_async(code, *context, proc_timeout=timeout_secs)
else:
@ -864,41 +912,41 @@ class TestLimitedExec(unittest.TestCase):
def test_getattr(self):
# attempt to get arround direct attr access
self.assertRaises(LimitedExecException, \
self.assertRaises(LimitedExecException,
limited_exec, "getattr(int, '__abs__')")
def test_func_globals(self):
# attempt to access global enviroment where fun was defined
self.assertRaises(LimitedExecException, \
self.assertRaises(LimitedExecException,
limited_exec, "def x(): pass; print x.func_globals")
def test_lowlevel(self):
# lowlevel tricks to access 'object'
self.assertRaises(LimitedExecException, \
self.assertRaises(LimitedExecException,
limited_exec, "().__class__.mro()[1].__subclasses__()")
def test_timeout_ok(self):
# attempt to exectute 'slow' code which finishes within timelimit
def test(): time.sleep(2)
env = {'test':test}
limited_exec("test()", env, timeout_secs = 5)
env = {'test': test}
limited_exec("test()", env, timeout_secs=5)
def test_timeout_exceed(self):
# attempt to exectute code which never teminates
self.assertRaises(LimitedExecException, \
self.assertRaises(LimitedExecException,
limited_exec, "while 1: pass")
def test_invalid_context(self):
# can't pass an enviroment with modules or builtins
env = {'f' : __builtins__.open, 'g' : time}
self.assertRaises(LimitedExecException, \
env = {'f': __builtins__.open, 'g': time}
self.assertRaises(LimitedExecException,
limited_exec, "print 1", env)
def test_callback(self):
# modify local variable via callback
self.value = 0
def test(): self.value = 1
env = {'test':test}
env = {'test': test}
limited_exec("test()", env)
self.assertEqual(self.value, 1)

View file

@ -53,13 +53,16 @@ if applicable. An extended @desc command is used to set details.
CmdExtendedLook - look command supporting room details
CmdExtendedDesc - @desc command allowing to add seasonal descs and details,
as well as listing them
CmdGameTime - A simple "time" command, displaying the current time and season.
CmdGameTime - A simple "time" command, displaying the current
time and season.
Installation/testing:
1) Add CmdExtendedLook, CmdExtendedDesc and CmdGameTime to the default cmdset (see wiki how to do this).
2) @dig a room of type contrib.extended_room.ExtendedRoom (or make it the default room type)
1) Add CmdExtendedLook, CmdExtendedDesc and CmdGameTime to the default cmdset
(see wiki how to do this).
2) @dig a room of type contrib.extended_room.ExtendedRoom (or make it the
default room type)
3) Use @desc and @detail to customize the room, then play around!
"""
@ -90,16 +93,18 @@ REGEXMAP = {"morning": (RE_MORNING, RE_AFTERNOON, RE_EVENING, RE_NIGHT),
# beginning of the year (so month 1 is equivalent to January), and that
# one CAN divive the game's year into four seasons in the first place ...
MONTHS_PER_YEAR = settings.TIME_MONTH_PER_YEAR
SEASONAL_BOUNDARIES = (3/12.0, 6/12.0, 9/12.0)
SEASONAL_BOUNDARIES = (3 / 12.0, 6 / 12.0, 9 / 12.0)
HOURS_PER_DAY = settings.TIME_HOUR_PER_DAY
DAY_BOUNDARIES = (0, 6/24.0, 12/24.0, 18/24.0)
DAY_BOUNDARIES = (0, 6 / 24.0, 12 / 24.0, 18 / 24.0)
# implements the Extended Room
class ExtendedRoom(Room):
"""
This room implements a more advanced look functionality depending on time. It also
allows for "details", together with a slightly modified look command.
This room implements a more advanced look functionality depending on
time. It also allows for "details", together with a slightly modified
look command.
"""
def at_object_creation(self):
"Called when room is first created only."
@ -107,10 +112,12 @@ class ExtendedRoom(Room):
self.db.summer_desc = ""
self.db.autumn_desc = ""
self.db.winter_desc = ""
# the general desc is used as a fallback if a given seasonal one is not set
# the general desc is used as a fallback if a seasonal one is not set
self.db.general_desc = ""
self.db.raw_desc = "" # will be set dynamically. Can contain raw timeslot codes
self.db.desc = "" # this will be set dynamically at first look. Parsed for timeslot codes
# will be set dynamically. Can contain raw timeslot codes
self.db.raw_desc = ""
# this will be set dynamically at first look. Parsed for timeslot codes
self.db.desc = ""
# these will be filled later
self.ndb.last_season = None
self.ndb.last_timeslot = None
@ -122,28 +129,37 @@ class ExtendedRoom(Room):
Calculate the current time and season ids
"""
# get the current time as parts of year and parts of day
time = gametime.gametime(format=True) # returns a tuple (years,months,weeks,days,hours,minutes,sec)
# returns a tuple (years,months,weeks,days,hours,minutes,sec)
time = gametime.gametime(format=True)
month, hour = time[1], time[4]
season = float(month) / MONTHS_PER_YEAR
timeslot = float(hour) / HOURS_PER_DAY
# figure out which slots these represent
if SEASONAL_BOUNDARIES[0] <= season < SEASONAL_BOUNDARIES[1]: curr_season = "spring"
elif SEASONAL_BOUNDARIES[1] <= season < SEASONAL_BOUNDARIES[2]: curr_season = "summer"
elif SEASONAL_BOUNDARIES[2] <= season < 1.0 + SEASONAL_BOUNDARIES[0]: curr_season = "autumn"
else: curr_season = "winter"
if SEASONAL_BOUNDARIES[0] <= season < SEASONAL_BOUNDARIES[1]:
curr_season = "spring"
elif SEASONAL_BOUNDARIES[1] <= season < SEASONAL_BOUNDARIES[2]:
curr_season = "summer"
elif SEASONAL_BOUNDARIES[2] <= season < 1.0 + SEASONAL_BOUNDARIES[0]:
curr_season = "autumn"
else:
curr_season = "winter"
if DAY_BOUNDARIES[0] <= timeslot < DAY_BOUNDARIES[1]: curr_timeslot = "night"
elif DAY_BOUNDARIES[1] <= timeslot < DAY_BOUNDARIES[2]: curr_timeslot = "morning"
elif DAY_BOUNDARIES[2] <= timeslot < DAY_BOUNDARIES[3]: curr_timeslot = "afternoon"
else: curr_timeslot = "evening"
if DAY_BOUNDARIES[0] <= timeslot < DAY_BOUNDARIES[1]:
curr_timeslot = "night"
elif DAY_BOUNDARIES[1] <= timeslot < DAY_BOUNDARIES[2]:
curr_timeslot = "morning"
elif DAY_BOUNDARIES[2] <= timeslot < DAY_BOUNDARIES[3]:
curr_timeslot = "afternoon"
else:
curr_timeslot = "evening"
return curr_season, curr_timeslot
def replace_timeslots(self, raw_desc, curr_time):
"""
Filter so that only time markers <timeslot>...</timeslot> of the correct timeslot
remains in the description.
Filter so that only time markers <timeslot>...</timeslot> of the
correct timeslot remains in the description.
"""
if raw_desc:
regextuple = REGEXMAP[curr_time]
@ -158,8 +174,9 @@ class ExtendedRoom(Room):
This will attempt to match a "detail" to look for in the room. A detail
is a way to offer more things to look at in a room without having to
add new objects. For this to work, we require a custom look command that
allows for "look <detail>" - the look command should defer to this method
on the current location (if it exists) before giving up on finding the target.
allows for "look <detail>" - the look command should defer to this
method on the current location (if it exists) before giving up on
finding the target.
Details are not season-sensitive, but are parsed for timeslot markers.
"""
@ -188,10 +205,14 @@ class ExtendedRoom(Room):
if curr_season != last_season:
# season changed. Load new desc, or a fallback.
if curr_season == 'spring': new_raw_desc = self.db.spring_desc
elif curr_season == 'summer': new_raw_desc = self.db.summer_desc
elif curr_season == 'autumn': new_raw_desc = self.db.autumn_desc
else: new_raw_desc = self.db.winter_desc
if curr_season == 'spring':
new_raw_desc = self.db.spring_desc
elif curr_season == 'summer':
new_raw_desc = self.db.summer_desc
elif curr_season == 'autumn':
new_raw_desc = self.db.autumn_desc
else:
new_raw_desc = self.db.winter_desc
if new_raw_desc:
raw_desc = new_raw_desc
else:
@ -207,14 +228,16 @@ class ExtendedRoom(Room):
update = True
if update:
# if anything changed we have to re-parse the raw_desc for time markers
# if anything changed we have to re-parse
# the raw_desc for time markers
# and re-save the description again.
self.db.desc = self.replace_timeslots(self.db.raw_desc, curr_timeslot)
# run the normal return_appearance method, now that desc is updated.
return super(ExtendedRoom, self).return_appearance(looker)
# Custom Look command supporting Room details. Add this to the Default cmdset to use.
# Custom Look command supporting Room details. Add this to
# the Default cmdset to use.
class CmdExtendedLook(default_cmds.CmdLook):
"""
@ -237,7 +260,8 @@ class CmdExtendedLook(default_cmds.CmdLook):
if args:
looking_at_obj = caller.search(args, use_nicks=True, ignore_errors=True)
if not looking_at_obj:
# no object found. Check if there is a matching detail at location.
# no object found. Check if there is a matching
# detail at location.
location = caller.location
if location and hasattr(location, "return_detail") and callable(location.return_detail):
detail = location.return_detail(args)
@ -269,7 +293,8 @@ class CmdExtendedLook(default_cmds.CmdLook):
looking_at_obj.at_desc(looker=caller)
# Custom build commands for setting seasonal descriptions and detailing extended rooms.
# Custom build commands for setting seasonal descriptions
# and detailing extended rooms.
class CmdExtendedDesc(default_cmds.CmdDesc):
"""
@ -358,7 +383,10 @@ class CmdExtendedDesc(default_cmds.CmdDesc):
string += " {wgeneral:{n %s" % location.db.general_desc
caller.msg(string)
return
if self.switches and self.switches[0] in ("spring", "summer", "autumn", "winter"):
if self.switches and self.switches[0] in ("spring",
"summer",
"autumn",
"winter"):
# a seasonal switch was given
if self.rhs:
caller.msg("Seasonal descs only works with rooms, not objects.")
@ -367,10 +395,14 @@ class CmdExtendedDesc(default_cmds.CmdDesc):
if not location:
caller.msg("No location was found!")
return
if switch == 'spring': location.db.spring_desc = self.args
elif switch == 'summer': location.db.summer_desc = self.args
elif switch == 'autumn': location.db.autumn_desc = self.args
elif switch == 'winter': location.db.winter_desc = self.args
if switch == 'spring':
location.db.spring_desc = self.args
elif switch == 'summer':
location.db.summer_desc = self.args
elif switch == 'autumn':
location.db.autumn_desc = self.args
elif switch == 'winter':
location.db.winter_desc = self.args
# clear flag to force an update
self.reset_times(location)
caller.msg("Seasonal description was set on %s." % location.key)
@ -384,9 +416,10 @@ class CmdExtendedDesc(default_cmds.CmdDesc):
else:
text = self.args
obj = location
obj.db.desc = self.rhs # this is set as a compatability fallback
obj.db.desc = self.rhs # a compatability fallback
if utils.inherits_from(obj, ExtendedRoom):
# this is an extendedroom, we need to reset times and set general_desc
# this is an extendedroom, we need to reset
# times and set general_desc
obj.db.general_desc = text
self.reset_times(obj)
caller.msg("General description was set on %s." % obj.key)

View file

@ -26,6 +26,7 @@ CMD_NOINPUT = syscmdkeys.CMD_NOINPUT
RE_GROUP = re.compile(r"\".*?\"|\'.*?\'|\S*")
class CmdEditorBase(Command):
"""
Base parent for editor commands
@ -44,7 +45,8 @@ class CmdEditorBase(Command):
:cmd [li] [w] [txt]
Where all arguments are optional.
li - line number (int), starting from 1. This could also be a range given as <l>:<l>
li - line number (int), starting from 1. This could also
be a range given as <l>:<l>
w - word(s) (string), could be encased in quotes.
txt - extra text (string), could be encased in quotes
"""
@ -63,7 +65,8 @@ class CmdEditorBase(Command):
arglist = [part for part in RE_GROUP.findall(self.args) if part]
temp = []
for arg in arglist:
# we want to clean the quotes, but only one type, in case we are nesting.
# we want to clean the quotes, but only one type,
# in case we are nesting.
if arg.startswith('"'):
arg.strip('"')
elif arg.startswith("'"):
@ -71,7 +74,6 @@ class CmdEditorBase(Command):
temp.append(arg)
arglist = temp
# A dumb split, without grouping quotes
words = self.args.split()
@ -106,8 +108,8 @@ class CmdEditorBase(Command):
else:
lstr = "lines %i-%i" % (lstart + 1, lend)
# arg1 and arg2 is whatever arguments. Line numbers or -ranges are never included here.
# arg1 and arg2 is whatever arguments. Line numbers or -ranges are
# never included here.
args = " ".join(arglist)
arg1, arg2 = "", ""
if len(arglist) > 1:
@ -141,6 +143,7 @@ class CmdLineInput(CmdEditorBase):
"""
key = CMD_NOMATCH
aliases = [CMD_NOINPUT]
def func(self):
"Adds the line without any formatting changes."
# add a line of text
@ -150,9 +153,11 @@ class CmdLineInput(CmdEditorBase):
buf = self.editor.buffer + "\n%s" % self.args
self.editor.update_buffer(buf)
if self.editor.echo_mode:
cline = len(self.editor.buffer.split('\n')) # need to do it here or we will be off one line
# need to do it here or we will be off one line
cline = len(self.editor.buffer.split('\n'))
self.caller.msg("{b%02i|{n %s" % (cline, self.args))
class CmdEditorGroup(CmdEditorBase):
"""
Commands for the editor
@ -165,8 +170,9 @@ class CmdEditorGroup(CmdEditorBase):
def func(self):
"""
This command handles all the in-editor :-style commands. Since each command
is small and very limited, this makes for a more efficient presentation.
This command handles all the in-editor :-style commands. Since
each command is small and very limited, this makes for a more
efficient presentation.
"""
caller = self.caller
editor = self.editor
@ -187,7 +193,9 @@ class CmdEditorGroup(CmdEditorBase):
# Echo buffer without the line numbers and syntax parsing
if self.linerange:
buf = linebuffer[lstart:lend]
string = editor.display_buffer(buf=buf, offset=lstart, linenums=False)
string = editor.display_buffer(buf=buf,
offset=lstart,
linenums=False)
else:
string = editor.display_buffer(linenums=False)
self.caller.msg(string, raw=True)
@ -242,7 +250,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange:
lstart = 0
lend = self.cline + 1
string = "Removed %s for lines %i-%i." % (self.arg1, lstart + 1 , lend + 1)
string = "Removed %s for lines %i-%i." % (self.arg1, lstart + 1, lend + 1)
else:
string = "Removed %s for %s." % (self.arg1, self.lstr)
sarea = "\n".join(linebuffer[lstart:lend])
@ -279,7 +287,7 @@ class CmdEditorGroup(CmdEditorBase):
if not new_lines:
string = "You need to enter a new line and where to insert it."
else:
buf = linebuffer[:lstart] + new_lines + linebuffer[lstart:]
buf = linebuffer[:lstart] + new_lines + linebuffer[lstart:]
editor.update_buffer(buf)
string = "Inserted %i new line(s) at %s." % (len(new_lines), self.lstr)
elif cmd == ":r":
@ -308,7 +316,8 @@ class CmdEditorGroup(CmdEditorBase):
editor.update_buffer(buf)
string = "Appended text to end of %s." % self.lstr
elif cmd == ":s":
# :s <li> <w> <txt> - search and replace words in entire buffer or on certain lines
# :s <li> <w> <txt> - search and replace words
# in entire buffer or on certain lines
if not self.arg1 or not self.arg2:
string = "You must give a search word and something to replace it with."
else:
@ -376,6 +385,7 @@ class EditorCmdSet(CmdSet):
key = "editorcmdset"
mergetype = "Replace"
class LineEditor(object):
"""
This defines a line editor object. It creates all relevant commands
@ -391,17 +401,21 @@ class LineEditor(object):
"""
caller - who is using the editor
loadfunc - this will be called as func(*loadfunc_args) when the editor is first started, e.g. for pre-loading text into it.
loadfunc - this will be called as func(*loadfunc_args) when the
editor is first started, e.g. for pre-loading text into it.
loadfunc_args - optional tuple of arguments to supply to loadfunc.
savefunc - this will be called as func(*savefunc_args) when the save-command is given and is
used to actually determine where/how result is saved. It should return True if save was successful and
also handle any feedback to the user.
savefunc - this will be called as func(*savefunc_args) when the
save-command is given and is used to actually determine
where/how result is saved. It should return True if save
was successful and also handle any feedback to the user.
savefunc_args - optional tuple of arguments to supply to savefunc.
quitfunc - this will optionally e called as func(*quitfunc_args) when the editor is exited. If defined, it should
handle all wanted feedback to the user.
quitfunc - this will optionally e called as func(*quitfunc_args) when
the editor is exited. If defined, it should handle all
wanted feedback to the user.
quitfunc_args - optional tuple of arguments to supply to quitfunc.
key = an optional key for naming this session (such as which attribute is being edited)
key = an optional key for naming this session (such as which attribute
is being edited)
"""
self.key = key
self.caller = caller
@ -420,13 +434,12 @@ class LineEditor(object):
# If no save function is defined, save an error-reporting function
err = "{rNo save function defined. Buffer cannot be saved.{n"
caller.msg(err)
savefunc = lambda: self.caller.msg(err)
savefunc = lambda: self.caller.msg(err)
self.savefunc = savefunc
self.savefunc_args = savefunc_args or ()
self.quitfunc = quitfunc
self.quitfunc_args = quitfunc_args or ()
# Create the commands we need
cmd1 = CmdLineInput()
cmd1.editor = self
@ -494,8 +507,9 @@ class LineEditor(object):
if self.unsaved:
try:
if self.savefunc(*self.savefunc_args):
# Save codes should return a true value to indicate save worked.
# The saving function is responsible for any status messages.
# Save codes should return a true value to indicate
# save worked. The saving function is responsible for
# any status messages.
self.unsaved = False
return ""
except Exception, e:
@ -555,7 +569,7 @@ class LineEditor(object):
"""
Shows the help entry for the editor.
"""
string = self.sep*78 + """
string = self.sep * 78 + """
<txt> - any non-command is appended to the end of the buffer.
: <l> - view buffer or only line <l>
:: <l> - view buffer without line numbers or other parsing
@ -578,7 +592,7 @@ class LineEditor(object):
:y <l> - yank (copy) line <l> to the copy buffer
:x <l> - cut line <l> and store it in the copy buffer
:p <l> - put (paste) previously copied line directly after <l>
:i <l> <txt> - insert new text <txt> at line <l>. Old line will be shifted down
:i <l> <txt> - insert new text <txt> at line <l>. Old line will move down
:r <l> <txt> - replace line <l> with text <txt>
:I <l> <txt> - insert text at the beginning of line <l>
:A <l> <txt> - append text after the end of line <l>
@ -627,7 +641,8 @@ class CmdEditor(Command):
if not self.args or not '/' in self.args:
self.caller.msg("Usage: @editor <obj>/<attrname>")
return
self.objname, self.attrname = [part.strip() for part in self.args.split("/", 1)]
self.objname, self.attrname = [part.strip()
for part in self.args.split("/", 1)]
self.obj = self.caller.search(self.objname)
if not self.obj:
return
@ -636,20 +651,28 @@ class CmdEditor(Command):
def load_attr():
"inital loading of buffer data from given attribute."
target = self.obj.attributes.get(self.attrname)
if target != None and not isinstance(target, basestring):
if target is not None and not isinstance(target, basestring):
typ = type(target).__name__
self.caller.msg("{RWARNING! Saving this buffer will overwrite the current attribute (of type %s) with a string!{n" % typ)
return target and str(target) or ""
def save_attr():
"Save line buffer to given attribute name. This should return True if successful and also report its status."
"""
Save line buffer to given attribute name. This should
return True if successful and also report its status.
"""
self.obj.attributes.add(self.attrname, self.editor.buffer)
self.caller.msg("Saved.")
return True
def quit_hook():
"Example quit hook. Since it's given, it's responsible for giving feedback messages."
self.caller.msg("Exited Editor.")
editor_key = "%s/%s" % (self.objname, self.attrname)
# start editor, it will handle things from here.
self.editor = LineEditor(self.caller, loadfunc=load_attr, savefunc=save_attr, quitfunc=quit_hook, key=editor_key)
self.editor = LineEditor(self.caller,
loadfunc=load_attr,
savefunc=save_attr,
quitfunc=quit_hook,
key=editor_key)

View file

@ -46,8 +46,9 @@ CMD_NOMATCH = syscmdkeys.CMD_NOMATCH
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
# Commands run on the unloggedin screen. Note that this is not using settings.UNLOGGEDIN_CMDSET but
# the menu system, which is why some are named for the numbers in the menu.
# Commands run on the unloggedin screen. Note that this is not using
# settings.UNLOGGEDIN_CMDSET but the menu system, which is why some are
# named for the numbers in the menu.
#
# Also note that the menu system will automatically assign all
# commands used in its structure a property "menutree" holding a reference
@ -63,10 +64,12 @@ class CmdBackToStart(Command):
"""
key = CMD_NOINPUT
locks = "cmd:all()"
def func(self):
"Execute the command"
self.menutree.goto("START")
class CmdUsernameSelect(Command):
"""
Handles the entering of a username and
@ -74,6 +77,7 @@ class CmdUsernameSelect(Command):
"""
key = CMD_NOMATCH
locks = "cmd:all()"
def func(self):
"Execute the command"
player = managers.players.get_player_from_name(self.args)
@ -81,9 +85,11 @@ class CmdUsernameSelect(Command):
self.caller.msg("{rThis account name couldn't be found. Did you create it? If you did, make sure you spelled it right (case doesn't matter).{n")
self.menutree.goto("node1a")
else:
self.menutree.player = player # store the player so next step can find it
# store the player so next step can find it
self.menutree.player = player
self.menutree.goto("node1b")
# Menu entry 1b - Entering a Password
class CmdPasswordSelectBack(Command):
@ -92,10 +98,12 @@ class CmdPasswordSelectBack(Command):
"""
key = CMD_NOINPUT
locks = "cmd:all()"
def func(self):
"Execute the command"
self.menutree.goto("node1a")
class CmdPasswordSelect(Command):
"""
Handles the entering of a password and logs into the game.
@ -117,9 +125,10 @@ class CmdPasswordSelect(Command):
# before going on, check eventual bans
bans = managers.serverconfigs.conf("server_bans")
if bans and (any(tup[0]==player.name for tup in bans)
if bans and (any(tup[0] == player.name for tup in bans)
or
any(tup[2].match(player.sessions[0].address[0]) for tup in bans if tup[2])):
any(tup[2].match(player.sessions[0].address[0])
for tup in bans if tup[2])):
# this is a banned IP or name!
string = "{rYou have been banned and cannot continue from here."
string += "\nIf you feel this ban is in error, please email an admin.{x"
@ -142,6 +151,7 @@ class CmdPasswordSelect(Command):
# we have no character yet; use player's look, if it exists
player.execute_cmd("look")
# Menu entry 2a - Creating a Username
class CmdUsernameCreate(Command):
@ -169,16 +179,19 @@ its and @/./+/-/_ only.{n") # this echoes the restrictions made by django's auth
self.menutree.playername = playername
self.menutree.goto("node2b")
# Menu entry 2b - Creating a Password
class CmdPasswordCreateBack(Command):
"Step back from the password creation"
key = CMD_NOINPUT
locks = "cmd:all()"
def func(self):
"Execute the command"
self.menutree.goto("node2a")
class CmdPasswordCreate(Command):
"Handle the creation of a password. This also creates the actual Player/User object."
key = CMD_NOMATCH
@ -195,12 +208,14 @@ class CmdPasswordCreate(Command):
if len(password) < 3:
# too short password
string = "{rYour password must be at least 3 characters or longer."
string += "\n\rFor best security, make it at least 8 characters long, "
string += "avoid making it a real word and mix numbers into it.{n"
string += "\n\rFor best security, make it at least 8 characters "
string += "long, avoid making it a real word and mix numbers "
string += "into it.{n"
self.caller.msg(string)
self.menutree.goto("node2b")
return
# everything's ok. Create the new player account. Don't create a Character here.
# everything's ok. Create the new player account. Don't create
# a Character here.
try:
permissions = settings.PERMISSION_PLAYER_DEFAULT
typeclass = settings.BASE_PLAYER_TYPECLASS
@ -227,8 +242,9 @@ class CmdPasswordCreate(Command):
self.caller.msg(string % (playername))
self.menutree.goto("START")
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."
self.caller.msg(string % (traceback.format_exc()))
logger.log_errmsg(traceback.format_exc())
@ -259,6 +275,8 @@ LOGIN_SCREEN_HELP = \
(return to go back)""" % settings.SERVERNAME
# Menu entry 4
class CmdUnloggedinQuit(Command):
@ -285,7 +303,7 @@ START = MenuNode("START", text=utils.string_from_module(CONNECTION_SCREEN_MODULE
linktexts=["Log in with an existing account",
"Create a new account",
"Help",
"Quit",],
"Quit"],
selectcmds=[None, None, None, CmdUnloggedinQuit])
node1a = MenuNode("node1a", text="Please enter your account name (empty to abort).",
@ -325,13 +343,17 @@ class UnloggedInCmdSet(CmdSet):
"Cmdset for the unloggedin state"
key = "UnloggedinState"
priority = 0
def at_cmdset_creation(self):
"Called when cmdset is first created"
self.add(CmdUnloggedinLook())
class CmdUnloggedinLook(Command):
"""
An unloggedin version of the look command. This is called by the server when the player
first connects. It sets up the menu before handing off to the menu's own look command..
An unloggedin version of the look command. This is called by the server
when the player first connects. It sets up the menu before handing off
to the menu's own look command..
"""
key = CMD_LOGINSTART
aliases = ["look", "l"]
@ -339,5 +361,7 @@ class CmdUnloggedinLook(Command):
def func(self):
"Execute the menu"
menu = MenuTree(self.caller, nodes=(START, node1a, node1b, node2a, node2b, node3), exec_end=None)
menu = MenuTree(self.caller, nodes=(START, node1a, node1b,
node2a, node2b, node3),
exec_end=None)
menu.start()

View file

@ -65,6 +65,7 @@ class CmdMenuNode(Command):
else:
self.caller.msg("{rThis option is not available.{n")
class CmdMenuLook(default_cmds.CmdLook):
"""
ooc look
@ -92,6 +93,7 @@ class CmdMenuLook(default_cmds.CmdLook):
# otherwise we use normal look
super(CmdMenuLook, self).func()
class CmdMenuHelp(default_cmds.CmdHelp):
"""
help
@ -118,6 +120,7 @@ class CmdMenuHelp(default_cmds.CmdHelp):
# otherwise we use normal help
super(CmdMenuHelp, self).func()
class MenuCmdSet(CmdSet):
"""
Cmdset for the menu. Will replace all other commands.
@ -129,10 +132,12 @@ class MenuCmdSet(CmdSet):
key = "menucmdset"
priority = 1
mergetype = "Replace"
def at_cmdset_creation(self):
"populate cmdset"
pass
#
# Menu Node system
#
@ -153,7 +158,8 @@ class MenuTree(object):
'START' and 'END' respectively.
"""
def __init__(self, caller, nodes=None, startnode="START", endnode="END", exec_end="look"):
def __init__(self, caller, nodes=None,
startnode="START", endnode="END", exec_end="look"):
"""
We specify startnode/endnode so that the system knows where to
enter and where to exit the menu tree. If nodes is given, it
@ -194,7 +200,7 @@ class MenuTree(object):
# if we was given the END node key, we clean up immediately.
self.caller.cmdset.delete("menucmdset")
del self.caller.db._menu_data
if self.exec_end != None:
if self.exec_end is not None:
self.caller.execute_cmd(self.exec_end)
return
# not exiting, look for a valid code.
@ -205,13 +211,14 @@ class MenuTree(object):
# node. self.caller is available at this point.
try:
exec(node.code)
except Exception, e:
except Exception:
self.caller.msg("{rCode could not be executed for node %s. Continuing anyway.{n" % key)
# clean old menu cmdset and replace with the new one
self.caller.cmdset.delete("menucmdset")
self.caller.cmdset.add(node.cmdset)
# set the menu flag data for the default commands
self.caller.db._menu_data = {"help":node.helptext, "look":str(node.text)}
self.caller.db._menu_data = {"help": node.helptext,
"look": str(node.text)}
# display the node
self.caller.msg(node.text)
else:
@ -226,27 +233,39 @@ class MenuNode(object):
"""
def __init__(self, key, text="", links=None, linktexts=None,
keywords=None, cols=1, helptext=None, selectcmds=None, code="", nodefaultcmds=False, separator=""):
keywords=None, cols=1, helptext=None,
selectcmds=None, code="", nodefaultcmds=False, separator=""):
"""
key - the unique identifier of this node.
text - is the text that will be displayed at top when viewing this node.
links - a list of keys for unique menunodes this is connected to. The actual keys will not be
printed - keywords will be used (or a number)
linktexts - an optional list of texts to describe the links. Must match link list if defined. Entries can be None
to not generate any extra text for a particular link.
keywords - an optional list of unique keys for choosing links. Must match links list. If not given, index numbers
will be used. Also individual list entries can be None and will be replaed by indices.
If CMD_NOMATCH or CMD_NOENTRY, no text will be generated to indicate the option exists.
text - is the text that will be displayed at top when viewing this
node.
links - a list of keys for unique menunodes this is connected to.
The actual keys will not printed - keywords will be used
(or a number)
linktexts - an optional list of texts to describe the links. Must
match link list if defined. Entries can be None to not
generate any extra text for a particular link.
keywords - an optional list of unique keys for choosing links. Must
match links list. If not given, index numbers will be used.
Also individual list entries can be None and will be replaed
by indices. If CMD_NOMATCH or CMD_NOENTRY, no text will be
generated to indicate the option exists.
cols - how many columns to use for displaying options.
helptext - if defined, this is shown when using the help command instead of the normal help index.
selectcmds- a list of custom cmdclasses for handling each option. Must match links list, but some entries
may be set to None to use default menu cmds. The given command's key will be used for the menu
list entry unless it's CMD_NOMATCH or CMD_NOENTRY, in which case no text will be generated. These
commands have access to self.menutree and so can be used to select nodes.
code - functional code. This will be executed just before this node is loaded (i.e.
as soon after it's been selected from another node). self.caller is available
to call from this code block, as well as ev.
nodefaultcmds - if true, don't offer the default help and look commands in the node
helptext - if defined, this is shown when using the help command
instead of the normal help index.
selectcmds- a list of custom cmdclasses for handling each option.
Must match links list, but some entries may be set to None
to use default menu cmds. The given command's key will be
used for the menu list entry unless it's CMD_NOMATCH or
CMD_NOENTRY, in which case no text will be generated. These
commands have access to self.menutree and so can be used to
select nodes.
code - functional code. This will be executed just before this
node is loaded (i.e. as soon after it's been selected from
another node). self.caller is available to call from this
code block, as well as ev.
nodefaultcmds - if true, don't offer the default help and look commands
in the node
separator - this string will be put on the line between menu nodes5B.
"""
self.key = key
@ -311,13 +330,14 @@ class MenuNode(object):
break
ftable = utils.format_table(cols)
for row in ftable:
string +="\n" + "".join(row)
string += "\n" + "".join(row)
# store text
self.text = self.separator + "\n" + string.rstrip()
def init(self, menutree):
"""
Called by menu tree. Initializes the commands needed by the menutree structure.
Called by menu tree. Initializes the commands needed by
the menutree structure.
"""
# Create the relevant cmdset
self.cmdset = MenuCmdSet()
@ -362,7 +382,8 @@ def prompt_yesno(caller, question="", yescode="", nocode="", default="N"):
cmdyes = CmdMenuNode()
cmdyes.key = "yes"
cmdyes.aliases = ["y"]
# this will be executed in the context of the yes command (so self.caller will be available)
# this will be executed in the context of the yes command (so
# self.caller will be available)
cmdyes.code = yescode + "\nself.caller.cmdset.delete('menucmdset')\ndel self.caller.db._menu_data"
cmdno = CmdMenuNode()
@ -387,8 +408,8 @@ def prompt_yesno(caller, question="", yescode="", nocode="", default="N"):
yesnocmdset.add(defaultcmd)
# assinging menu data flags to caller.
caller.db._menu_data = {"help":"Please select Yes or No.",
"look":"Please select Yes or No."}
caller.db._menu_data = {"help": "Please select Yes or No.",
"look": "Please select Yes or No."}
# assign cmdset and ask question
caller.cmdset.add(yesnocmdset)
if default == "Y":
@ -398,6 +419,7 @@ def prompt_yesno(caller, question="", yescode="", nocode="", default="N"):
prompt = "%s %s: " % (question, prompt)
caller.msg(prompt)
#
# Menu command test
#
@ -419,6 +441,7 @@ class CmdMenuTest(Command):
key = "menu"
locks = "cmd:all()"
help_category = "Menu"
def func(self):
"Testing the menu system"

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -36,6 +36,7 @@ from src.utils.utils import clean_object_caches, to_str
from src.utils import logger
from src import PROC_MODIFIED_OBJS
#
# Multiprocess command for communication Server<->Client, relaying
# data for remote Python execution
@ -64,6 +65,7 @@ class ExecuteCode(amp.Command):
response = [('response', amp.String()),
('recached', amp.String())]
#
# Multiprocess AMP client-side factory, for executing remote Python code
#
@ -118,8 +120,7 @@ class PythonProcPoolChild(AMPChild):
return ""
_return = Ret()
available_vars = {'_return':_return}
available_vars = {'_return': _return}
if environment:
# load environment
try:
@ -141,7 +142,8 @@ class PythonProcPoolChild(AMPChild):
# get the list of affected objects to recache
objs = list(set(PROC_MODIFIED_OBJS))
# we need to include the locations too, to update their content caches
objs = objs + list(set([o.location for o in objs if hasattr(o, "location") and o.location]))
objs = objs + list(set([o.location for o in objs
if hasattr(o, "location") and o.location]))
#print "objs:", objs
#print "to_pickle", to_pickle(objs, emptypickle=False, do_pickle=False)
if objs not in (None, [], ()):
@ -156,13 +158,15 @@ class PythonProcPoolChild(AMPChild):
#
# Procpool run_async - Server-side access function for executing code in another process
# Procpool run_async - Server-side access function for executing
# code in another process
#
_PPOOL = None
_SESSIONS = None
_PROC_ERR = "A process has ended with a probable error condition: process ended by signal 9."
def run_async(to_execute, *args, **kwargs):
"""
Runs a function or executes a code snippet asynchronously.
@ -227,9 +231,9 @@ def run_async(to_execute, *args, **kwargs):
Use this function with restrain and only for features/commands
that you know has no influence on the cause-and-effect order of your
game (commands given after the async function might be executed before
it has finished). Accessing the same property from different threads/processes
can lead to unpredicted behaviour if you are not careful (this is called a
"race condition").
it has finished). Accessing the same property from different
threads/processes can lead to unpredicted behaviour if you are not
careful (this is called a "race condition").
Also note that some databases, notably sqlite3, don't support access from
multiple threads simultaneously, so if you do heavy database access from
@ -243,7 +247,7 @@ def run_async(to_execute, *args, **kwargs):
# get the procpool name, if set in kwargs
procpool_name = kwargs.get("procpool_name", "PythonProcPool")
if _PPOOL == None:
if _PPOOL is None:
# Try to load process Pool
from src.server.sessionhandler import SESSIONS as _SESSIONS
try:
@ -260,8 +264,10 @@ def run_async(to_execute, *args, **kwargs):
reca = ret["recached"] and from_pickle(do_unpickle(ret["recached"]))
# recache all indicated objects
[clean_object_caches(obj) for obj in reca]
if f: return f(rval, *args, **kwargs)
else: return rval
if f:
return f(rval, *args, **kwargs)
else:
return rval
return func
def convert_err(f):
def func(err, *args, **kwargs):
@ -287,18 +293,22 @@ def run_async(to_execute, *args, **kwargs):
# process pool is running
if isinstance(to_execute, basestring):
# run source code in process pool
cmdargs = {"_timeout":use_timeout}
cmdargs = {"_timeout": use_timeout}
cmdargs["source"] = to_str(to_execute)
if kwargs: cmdargs["environment"] = do_pickle(to_pickle(kwargs))
else: cmdargs["environment"] = ""
if kwargs:
cmdargs["environment"] = do_pickle(to_pickle(kwargs))
else:
cmdargs["environment"] = ""
# defer to process pool
deferred = _PPOOL.doWork(ExecuteCode, **cmdargs)
elif callable(to_execute):
# execute callable in process
callname = to_execute.__name__
cmdargs = {"_timeout":use_timeout}
cmdargs = {"_timeout": use_timeout}
cmdargs["source"] = "_return(%s(*args,**kwargs))" % callname
cmdargs["environment"] = do_pickle(to_pickle({callname:to_execute, "args":args, "kwargs":kwargs}))
cmdargs["environment"] = do_pickle(to_pickle({callname: to_execute,
"args": args,
"kwargs": kwargs}))
deferred = _PPOOL.doWork(ExecuteCode, **cmdargs)
else:
raise RuntimeError("'%s' could not be handled by the process pool" % to_execute)

View file

@ -62,6 +62,7 @@ PROCPOOL_DIRECTORY = None
# don't need to change normally
SERVICE_NAME = "PythonProcPool"
# plugin hook
def start_plugin_services(server):
@ -87,8 +88,8 @@ def start_plugin_services(server):
os.path.join(os.pardir, "contrib", "procpools", "ampoule"),
os.path.join(os.pardir, "ev"),
"settings")
aenv = {"DJANGO_SETTINGS_MODULE":"settings",
"DATABASE_NAME":settings.DATABASES.get("default", {}).get("NAME") or settings.DATABASE_NAME}
aenv = {"DJANGO_SETTINGS_MODULE": "settings",
"DATABASE_NAME": settings.DATABASES.get("default", {}).get("NAME") or settings.DATABASE_NAME}
if PROCPOOL_DEBUG:
_BOOTSTRAP = _BOOTSTRAP % "log.startLogging(sys.stderr)"
else:

View file

@ -54,8 +54,9 @@ class CmdTalk(default_cmds.MuxCommand):
self.caller.msg("(You walk up and talk to %s.)" % self.obj.key)
# conversation is a dictionary of keys, each pointing to a dictionary defining
# the keyword arguments to the MenuNode constructor.
# conversation is a dictionary of keys, each pointing to
# a dictionary defining the keyword arguments to the MenuNode
# constructor.
conversation = obj.db.conversation
if not conversation:
self.caller.msg("%s says: 'Sorry, I don't have time to talk right now.'" % (self.obj.key))
@ -67,9 +68,11 @@ class CmdTalk(default_cmds.MuxCommand):
menu.add(menusystem.MenuNode(key, **kwargs))
menu.start()
class TalkingCmdSet(CmdSet):
"Stores the talk command."
key = "talkingcmdset"
def at_cmdset_creation(self):
"populates the cmdset"
self.add(CmdTalk())
@ -79,33 +82,34 @@ class TalkingCmdSet(CmdSet):
# (This could be in a separate module too)
#
CONV = {"START":{"text": "Hello there, how can I help you?",
"links":["info1", "info2"],
"linktexts":["Hey, do you know what this 'Evennia' thing is all about?",
"What's your name, little NPC?"],
"keywords":None,
"code":None},
"info1":{"text": "Oh, Evennia is where you are right now! Don't you feel the power?",
"links":["info3", "info2", "END"],
"linktexts":["Sure, *I* do, not sure how you do though. You are just an NPC.",
CONV = {"START": {"text": "Hello there, how can I help you?",
"links": ["info1", "info2"],
"linktexts": ["Hey, do you know what this 'Evennia' thing is all about?",
"What's your name, little NPC?"],
"keywords": None,
"code": None},
"info1": {"text": "Oh, Evennia is where you are right now! Don't you feel the power?",
"links": ["info3", "info2", "END"],
"linktexts":["Sure, *I* do, not sure how you do though. You are just an NPC.",
"Sure I do. What's yer name, NPC?",
"Ok, bye for now then."],
"keywords":None,
"code":None},
"info2":{"text":"My name is not really important ... I'm just an NPC after all.",
"links":["info3", "info1"],
"linktexts":["I didn't really want to know it anyhow.",
"keywords": None,
"code": None},
"info2": {"text": "My name is not really important ... I'm just an NPC after all.",
"links": ["info3", "info1"],
"linktexts": ["I didn't really want to know it anyhow.",
"Okay then, so what's this 'Evennia' thing about?"],
"keywords":None,
"code":None},
"info3":{"text":"Well ... I'm sort of busy so, have to go. NPC business. Important stuff. You wouldn't understand.",
"links":["END", "info2"],
"linktexts":["Oookay ... I won't keep you. Bye.",
"Wait, why don't you tell me your name first?"],
"keywords":None,
"code":None},
"keywords": None,
"code": None},
"info3": {"text": "Well ... I'm sort of busy so, have to go. NPC business. Important stuff. You wouldn't understand.",
"links": ["END", "info2"],
"linktexts": ["Oookay ... I won't keep you. Bye.",
"Wait, why don't you tell me your name first?"],
"keywords": None,
"code": None},
}
class TalkingNPC(Object):
"""
This implements a simple Object using the talk command and using the
@ -118,4 +122,4 @@ class TalkingNPC(Object):
self.db.conversation = CONV
self.db.desc = "This is a talkative NPC."
# assign the talk command to npc
self.cmdset.add_default(TalkingCmdSet, permanent=True)
self.cmdset.add_default(TalkingCmdSet, permanent=True)

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -14,6 +14,7 @@ from contrib.tutorial_world import scripts as tut_scripts
BASE_CHARACTER_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
#------------------------------------------------------------
#
# Mob - mobile object
@ -52,15 +53,18 @@ class Mob(tut_objects.TutorialObject):
def update_irregular(self):
"Called at irregular intervals. Moves the mob."
if self.roam_mode:
exits = [ex for ex in self.location.exits if ex.access(self, "traverse")]
exits = [ex for ex in self.location.exits
if ex.access(self, "traverse")]
if exits:
# Try to make it so the mob doesn't backtrack.
new_exits = [ex for ex in exits if ex.destination != self.db.last_location]
new_exits = [ex for ex in exits
if ex.destination != self.db.last_location]
if new_exits:
exits = new_exits
self.db.last_location = self.location
# execute_cmd() allows the mob to respect exit and exit-command locks,
# but may pose a problem if there is more than one exit with the same name.
# execute_cmd() allows the mob to respect exit and
# exit-command locks, but may pose a problem if there is more
# than one exit with the same name.
# - see Enemy example for another way to move
self.execute_cmd("%s" % exits[random.randint(0, len(exits) - 1)].key)
@ -100,7 +104,8 @@ class AttackTimer(Script):
"Called every self.interval seconds."
if self.obj.db.inactive:
return
#print "attack timer: at_repeat", self.dbobj.id, self.ndb.twisted_task, id(self.ndb.twisted_task)
#print "attack timer: at_repeat", self.dbobj.id, self.ndb.twisted_task,
# id(self.ndb.twisted_task)
if self.obj.db.roam_mode:
self.obj.roam()
#return
@ -119,10 +124,12 @@ class AttackTimer(Script):
if (time.time() - self.obj.db.dead_at) > self.obj.db.dead_timer:
self.obj.reset()
class Enemy(Mob):
"""
This is a ghostly enemy with health (hit points). Their chance to hit, damage etc is
determined by the weapon they are wielding, same as characters.
This is a ghostly enemy with health (hit points). Their chance to hit,
damage etc is determined by the weapon they are wielding, same as
characters.
An enemy can be in four modes:
roam (inherited from Mob) - where it just moves around randomly
@ -133,12 +140,16 @@ class Enemy(Mob):
Upon creation, the following attributes describe the enemy's actions
desc - description
full_health - integer number > 0
defeat_location - unique name or #dbref to the location the player is taken when defeated. If not given, will remain in room.
defeat_text - text to show player when they are defeated (just before being whisped away to defeat_location)
defeat_text_room - text to show other players in room when a player is defeated
defeat_location - unique name or #dbref to the location the player is
taken when defeated. If not given, will remain in room.
defeat_text - text to show player when they are defeated (just before
being whisped away to defeat_location)
defeat_text_room - text to show other players in room when a player
is defeated
win_text - text to show player when defeating the enemy
win_text_room - text to show room when a player defeates the enemy
respawn_text - text to echo to room when the mob is reset/respawn in that room.
respawn_text - text to echo to room when the mob is reset/respawn in
that room.
"""
def at_object_creation(self):
@ -157,7 +168,8 @@ class Enemy(Mob):
self.db.health = 20
self.db.dead_at = time.time()
self.db.dead_timer = 100 # how long to stay dead
self.db.inactive = True # this is used during creation to make sure the mob doesn't move away
# this is used during creation to make sure the mob doesn't move away
self.db.inactive = True
# store the last player to hit
self.db.last_attacker = None
# where to take defeated enemies
@ -185,10 +197,12 @@ class Enemy(Mob):
elif random.random() < 0.2:
# no players to attack, move about randomly.
exits = [ex.destination for ex in self.location.exits if ex.access(self, "traverse")]
exits = [ex.destination for ex in self.location.exits
if ex.access(self, "traverse")]
if exits:
# Try to make it so the mob doesn't backtrack.
new_exits = [ex for ex in exits if ex.destination != self.db.last_location]
new_exits = [ex for ex in exits
if ex.destination != self.db.last_location]
if new_exits:
exits = new_exits
self.db.last_location = self.location
@ -224,7 +238,8 @@ class Enemy(Mob):
# analyze result.
if target.db.health <= 0:
# we reduced enemy to 0 health. Whisp them off to the prison room.
# we reduced enemy to 0 health. Whisp them off to
# the prison room.
tloc = search_object(self.db.defeat_location)
tstring = self.db.defeat_text
if not tstring:
@ -235,7 +250,8 @@ class Enemy(Mob):
if tloc:
if not ostring:
ostring = "\n%s envelops the fallen ... and then their body is suddenly gone!" % self.key
# silently move the player to defeat location (we need to call hook manually)
# silently move the player to defeat location
# (we need to call hook manually)
target.location = tloc[0]
tloc[0].at_object_receive(target, self.location)
elif not ostring:
@ -246,7 +262,8 @@ class Enemy(Mob):
self.roam_mode = False
self.pursue_mode = True
else:
# no players found, this could mean they have fled. Switch to pursue mode.
# no players found, this could mean they have fled.
# Switch to pursue mode.
self.battle_mode = False
self.roam_mode = False
self.pursue_mode = True
@ -259,20 +276,24 @@ class Enemy(Mob):
last_attacker = self.db.last_attacker
players = [obj for obj in self.location.contents if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
if players:
# we found players in the room. Maybe we caught up with some, or some walked in on us
# before we had time to pursue them. Switch to battle mode.
# we found players in the room. Maybe we caught up with some,
# or some walked in on us before we had time to pursue them.
# Switch to battle mode.
self.battle_mode = True
self.roam_mode = False
self.pursue_mode = False
else:
# find all possible destinations.
destinations = [ex.destination for ex in self.location.exits if ex.access(self, "traverse")]
# find all players in the possible destinations. OBS-we cannot just use the player's
# current position to move the Enemy; this might have changed when the move is performed,
# causing the enemy to teleport out of bounds.
destinations = [ex.destination for ex in self.location.exits
if ex.access(self, "traverse")]
# find all players in the possible destinations. OBS-we cannot
# just use the player's current position to move the Enemy; this
# might have changed when the move is performed, causing the enemy
# to teleport out of bounds.
players = {}
for dest in destinations:
for obj in [o for o in dest.contents if utils.inherits_from(o, BASE_CHARACTER_TYPECLASS)]:
for obj in [o for o in dest.contents
if utils.inherits_from(o, BASE_CHARACTER_TYPECLASS)]:
players[obj] = dest
if players:
# we found targets. Move to intercept.
@ -335,7 +356,8 @@ class Enemy(Mob):
string += "You fear it's only a matter of time before it materializes somewhere again."
self.location.msg_contents(string, exclude=[attacker])
# put enemy in dead mode and hide it from view. AttackTimer will bring it back later.
# put mob in dead mode and hide it from view.
# AttackTimer will bring it back later.
self.db.dead_at = time.time()
self.db.roam_mode = False
self.db.pursue_mode = False
@ -347,7 +369,9 @@ class Enemy(Mob):
return False
def reset(self):
"If the mob was 'dead', respawn it to its home position and reset all modes and damage."
"""
If the mob was 'dead', respawn it to its home position and reset
all modes and damage."""
if self.db.dead_mode:
self.db.health = self.db.full_health
self.db.roam_mode = True
@ -358,4 +382,4 @@ class Enemy(Mob):
string = self.db.respawn_text
if not string:
string = "%s fades into existence from out of thin air. It's looking pissed." % self.key
self.location.msg_contents(string)
self.location.msg_contents(string)

View file

@ -19,9 +19,10 @@ WeaponRack
"""
import time, random
import time
import random
from ev import utils, create_object
from ev import create_object
from ev import Object, Exit, Command, CmdSet, Script
#------------------------------------------------------------
@ -91,12 +92,14 @@ class CmdRead(Command):
string = "There is nothing to read on %s." % obj.key
self.caller.msg(string)
class CmdSetReadable(CmdSet):
"CmdSet for readables"
def at_cmdset_creation(self):
"called when object is created."
self.add(CmdRead())
class Readable(TutorialObject):
"""
This object defines some attributes and defines a read method on itself.
@ -147,6 +150,7 @@ class CmdClimb(Command):
self.caller.msg(ostring)
self.caller.db.last_climbed = self.obj
class CmdSetClimbable(CmdSet):
"Climbing cmdset"
def at_cmdset_creation(self):
@ -182,6 +186,7 @@ OBELISK_DESCS = ["You can briefly make out the image of {ba woman with a blue bi
"You think you can see the outline of {ba flaming shield{n in the stone.",
"The surface for a moment seems to portray {ba woman fighting a beast{n."]
class Obelisk(TutorialObject):
"""
This object changes its description randomly.
@ -196,7 +201,7 @@ class Obelisk(TutorialObject):
def return_appearance(self, caller):
"Overload the default version of this hook."
clueindex = random.randint(0, len(OBELISK_DESCS)-1)
clueindex = random.randint(0, len(OBELISK_DESCS) - 1)
# set this description
string = "The surface of the obelisk seem to waver, shift and writhe under your gaze, with "
string += "different scenes and structures appearing whenever you look at it. "
@ -206,6 +211,7 @@ class Obelisk(TutorialObject):
# call the parent function as normal (this will use db.desc we just set)
return super(Obelisk, self).return_appearance(caller)
#------------------------------------------------------------
#
# LightSource
@ -237,6 +243,7 @@ class StateLightSourceOn(Script):
self.db.script_started = time.time()
def at_repeat(self):
"Called at self.interval seconds"
# this is only called when torch has burnt out
self.obj.db.burntime = -1
self.obj.reset()
@ -262,13 +269,14 @@ class StateLightSourceOn(Script):
"This script is only valid as long as the lightsource burns."
return self.obj.db.is_active
class CmdLightSourceOn(Command):
"""
Switches on the lightsource.
"""
key = "on"
aliases = ["switch on", "turn on", "light"]
locks = "cmd:holds()" # only allow if command.obj is carried by caller.
locks = "cmd:holds()" # only allow if command.obj is carried by caller.
help_category = "TutorialWorld"
def func(self):
@ -283,18 +291,19 @@ class CmdLightSourceOn(Command):
self.obj.scripts.add(StateLightSourceOn)
self.caller.msg("{gYou light {C%s.{n" % self.obj.key)
self.caller.location.msg_contents("%s lights %s!" % (self.caller, self.obj.key), exclude=[self.caller])
# we run script validation on the room to make light/dark states tick.
# run script validation on the room to make light/dark states tick.
self.caller.location.scripts.validate()
# look around
self.caller.execute_cmd("look")
class CmdLightSourceOff(Command):
"""
Switch off the lightsource.
"""
key = "off"
aliases = ["switch off", "turn off", "dowse"]
locks = "cmd:holds()" # only allow if command.obj is carried by caller.
locks = "cmd:holds()" # only allow if command.obj is carried by caller.
help_category = "TutorialWorld"
def func(self):
@ -311,38 +320,39 @@ class CmdLightSourceOff(Command):
self.caller.location.msg_contents("%s dowses %s." % (self.caller, self.obj.key), exclude=[self.caller])
self.caller.location.scripts.validate()
self.caller.execute_cmd("look")
# we run script validation on the room to make light/dark states tick.
class CmdSetLightSource(CmdSet):
"CmdSet for the lightsource commands"
key = "lightsource_cmdset"
def at_cmdset_creation(self):
"called at cmdset creation"
self.add(CmdLightSourceOn())
self.add(CmdLightSourceOff())
class LightSource(TutorialObject):
"""
This implements a light source object.
When burned out, lightsource will be moved to its home - which by default is the
location it was first created at.
When burned out, lightsource will be moved to its home - which by
default is the location it was first created at.
"""
def at_object_creation(self):
"Called when object is first created."
super(LightSource, self).at_object_creation()
self.db.tutorial_info = "This object can be turned on off and has a timed script controlling it."
self.db.is_active = False
self.db.burntime = 60*3 # 3 minutes
self.db.burntime = 60 * 3 # 3 minutes
self.db.desc = "A splinter of wood with remnants of resin on it, enough for burning."
# add commands
self.cmdset.add_default(CmdSetLightSource, permanent=True)
def reset(self):
"""
Can be called by tutorial world runner, or by the script when the lightsource
has burned out.
Can be called by tutorial world runner, or by the script when
the lightsource has burned out.
"""
if self.db.burntime <= 0:
# light burned out. Since the lightsources's "location" should be
@ -360,10 +370,11 @@ class LightSource(TutorialObject):
# maybe it was dropped, try validating at current location.
try:
self.location.scripts.validate()
except AttributeError,e:
except AttributeError:
pass
self.delete()
#------------------------------------------------------------
#
# Crumbling wall - unique exit
@ -473,7 +484,7 @@ class CmdShiftRoot(Command):
root_pos["green"] += 1
self.caller.msg("The green weedy root falls down.")
elif direction == "down":
root_pos[color] = min(1, root_pos[color] +1)
root_pos[color] = min(1, root_pos[color] + 1)
self.caller.msg("You shove the root adorned with small yellow flowers downwards.")
if root_pos[color] != 0 and root_pos[color] == root_pos["green"]:
root_pos["green"] -= 1
@ -502,13 +513,15 @@ class CmdShiftRoot(Command):
self.caller.db.crumbling_wall_found_button = True
self.caller.msg("Holding aside the root you think you notice something behind it ...")
class CmdPressButton(Command):
"""
Presses a button.
"""
key = "press"
aliases = ["press button", "button", "push", "push button"]
locks = "cmd:attr(crumbling_wall_found_button) and not locattr(is_dark)" # only accessible if the button was found and there is light.
# only accessible if the button was found and there is light.
locks = "cmd:attr(crumbling_wall_found_button) and not locattr(is_dark)"
help_category = "TutorialWorld"
def func(self):
@ -534,14 +547,17 @@ class CmdPressButton(Command):
self.obj.destination = eloc
self.caller.msg(string)
class CmdSetCrumblingWall(CmdSet):
"Group the commands for crumblingWall"
key = "crumblingwall_cmdset"
def at_cmdset_creation(self):
"called when object is first created."
self.add(CmdShiftRoot())
self.add(CmdPressButton())
class CrumblingWall(TutorialObject, Exit):
"""
The CrumblingWall can be examined in various
@ -559,24 +575,28 @@ class CrumblingWall(TutorialObject, Exit):
"called when the object is first created."
super(CrumblingWall, self).at_object_creation()
self.aliases.add(["secret passage", "passage", "crack", "opening", "secret door"])
# this is assigned first when pushing button, so assign this at creation time!
self.aliases.add(["secret passage", "passage",
"crack", "opening", "secret door"])
# this is assigned first when pushing button, so assign
# this at creation time!
self.db.destination = 2
# locks on the object directly transfer to the exit "command"
self.locks.add("cmd:not locattr(is_dark)")
self.db.tutorial_info = "This is an Exit with a conditional traverse-lock. Try to shift the roots around."
# the lock is important for this exit; we only allow passage if we "found exit".
# the lock is important for this exit; we only allow passage
# if we "found exit".
self.locks.add("traverse:attr(crumbling_wall_found_exit)")
# set cmdset
self.cmdset.add(CmdSetCrumblingWall, permanent=True)
# starting root positions. H1/H2 are the horizontally hanging roots, V1/V2 the
# vertically hanging ones. Each can have three positions: (-1, 0, 1) where
# 0 means the middle position. yellow/green are horizontal roots and red/blue vertical.
# all may have value 0, but never any other identical value.
self.db.root_pos = {"yellow":0, "green":0, "red":0, "blue":0}
# starting root positions. H1/H2 are the horizontally hanging roots,
# V1/V2 the vertically hanging ones. Each can have three positions:
# (-1, 0, 1) where 0 means the middle position. yellow/green are
# horizontal roots and red/blue vertical, all may have value 0, but n
# ever any other identical value.
self.db.root_pos = {"yellow": 0, "green": 0, "red": 0, "blue": 0}
def _translate_position(self, root, ipos):
"Translates the position into words"
@ -598,7 +618,10 @@ class CrumblingWall(TutorialObject, Exit):
return string
def return_appearance(self, caller):
"This is called when someone looks at the wall. We need to echo the current root positions."
"""
This is called when someone looks at the wall. We need to echo the
current root positions.
"""
if caller.db.crumbling_wall_found_button:
string = "Having moved all the roots aside, you find that the center of the wall, "
string += "previously hidden by the vegetation, hid a curious square depression. It was maybe once "
@ -615,7 +638,10 @@ class CrumblingWall(TutorialObject, Exit):
return super(CrumblingWall, self).return_appearance(caller)
def at_after_traverse(self, traverser, source_location):
"This is called after we traversed this exit. Cleans up and resets the puzzle."
"""
This is called after we traversed this exit. Cleans up and resets
the puzzle.
"""
del traverser.db.crumbling_wall_found_button
del traverser.db.crumbling_wall_found_exit
self.reset()
@ -625,11 +651,14 @@ class CrumblingWall(TutorialObject, Exit):
traverser.msg("No matter how you try, you cannot force yourself through %s." % self.key)
def reset(self):
"Called by tutorial world runner, or whenever someone successfully traversed the Exit."
"""
Called by tutorial world runner, or whenever someone successfully
traversed the Exit.
"""
self.location.msg_contents("The secret door closes abruptly, roots falling back into place.")
for obj in self.location.contents:
# clear eventual puzzle-solved attribues on everyone that didn't get out in time. They
# have to try again.
# clear eventual puzzle-solved attribues on everyone that didn't
# get out in time. They have to try again.
del obj.db.crumbling_wall_found_exit
# Reset the roots with some random starting positions for the roots:
@ -641,6 +670,7 @@ class CrumblingWall(TutorialObject, Exit):
self.db.root_pos = start_pos[random.randint(0, 4)]
self.destination = None
#------------------------------------------------------------
#
# Weapon - object type
@ -667,15 +697,17 @@ class CmdAttack(Command):
stab - (thrust) makes a lot of damage but is harder to hit with.
slash - is easier to land, but does not make as much damage.
parry - forgoes your attack but will make you harder to hit on next enemy attack.
parry - forgoes your attack but will make you harder to hit on next
enemy attack.
"""
# this is an example of implementing many commands as a single command class,
# using the given command alias to separate between them.
# this is an example of implementing many commands as a single
# command class, using the given command alias to separate between them.
key = "attack"
aliases = ["hit","kill", "fight", "thrust", "pierce", "stab", "slash", "chop", "parry", "defend"]
aliases = ["hit","kill", "fight", "thrust", "pierce", "stab",
"slash", "chop", "parry", "defend"]
locks = "cmd:all()"
help_category = "TutorialWorld"
@ -684,7 +716,6 @@ class CmdAttack(Command):
cmdstring = self.cmdstring
if cmdstring in ("attack", "fight"):
string = "How do you want to fight? Choose one of 'stab', 'slash' or 'defend'."
self.caller.msg(string)
@ -709,15 +740,15 @@ class CmdAttack(Command):
tstring = ""
ostring = ""
if cmdstring in ("thrust", "pierce", "stab"):
hit = float(self.obj.db.hit) * 0.7 # modified due to stab
damage = self.obj.db.damage * 2 # modified due to stab
hit = float(self.obj.db.hit) * 0.7 # modified due to stab
damage = self.obj.db.damage * 2 # modified due to stab
string = "You stab with %s. " % self.obj.key
tstring = "%s stabs at you with %s. " % (self.caller.key, self.obj.key)
ostring = "%s stabs at %s with %s. " % (self.caller.key, target.key, self.obj.key)
self.caller.db.combat_parry_mode = False
elif cmdstring in ("slash", "chop"):
hit = float(self.obj.db.hit) # un modified due to slash
damage = self.obj.db.damage # un modified due to slash
hit = float(self.obj.db.hit) # un modified due to slash
damage = self.obj.db.damage # un modified due to slash
string = "You slash with %s. " % self.obj.key
tstring = "%s slash at you with %s. " % (self.caller.key, self.obj.key)
ostring = "%s slash at %s with %s. " % (self.caller.key, target.key, self.obj.key)
@ -753,12 +784,14 @@ class CmdAttack(Command):
target.msg(tstring + "{gThey miss you.{n")
self.caller.location.msg_contents(ostring + "They miss.", exclude=[target, self.caller])
class CmdSetWeapon(CmdSet):
"Holds the attack command."
def at_cmdset_creation(self):
"called at first object creation."
self.add(CmdAttack())
class Weapon(TutorialObject):
"""
This defines a bladed weapon.
@ -766,7 +799,8 @@ class Weapon(TutorialObject):
Important attributes (set at creation):
hit - chance to hit (0-1)
parry - chance to parry (0-1)
damage - base damage given (modified by hit success and type of attack) (0-10)
damage - base damage given (modified by hit success and
type of attack) (0-10)
"""
def at_object_creation(self):
@ -779,13 +813,17 @@ class Weapon(TutorialObject):
self.cmdset.add_default(CmdSetWeapon, permanent=True)
def reset(self):
"When reset, the weapon is simply deleted, unless it has a place to return to."
"""
When reset, the weapon is simply deleted, unless it has a place
to return to.
"""
if self.location.has_player and self.home == self.location:
self.location.msg_contents("%s suddenly and magically fades into nothingness, as if it was never there ..." % self.key)
self.delete()
else:
self.location = self.home
#------------------------------------------------------------
#
# Weapon rack - spawns weapons
@ -833,18 +871,23 @@ class CmdSetWeaponRack(CmdSet):
"group the rack cmd"
key = "weaponrack_cmdset"
mergemode = "Replace"
def at_cmdset_creation(self):
"Called at first creation of cmdset"
self.add(CmdGetWeapon())
class WeaponRack(TutorialObject):
"""
This will spawn a new weapon for the player unless the player already has one from this rack.
This will spawn a new weapon for the player unless the player already has
one from this rack.
attribute to set at creation:
min_dmg - the minimum damage of objects from this rack
max_dmg - the maximum damage of objects from this rack
magic - if weapons should be magical (have the magic flag set)
get_text - the echo text to return when getting the weapon. Give '%s' to include the name of the weapon.
get_text - the echo text to return when getting the weapon. Give '%s'
to include the name of the weapon.
"""
def at_object_creation(self):
"called at creation"

View file

@ -10,6 +10,7 @@ from ev import utils, create_object, search_object
from contrib.tutorial_world import scripts as tut_scripts
from contrib.tutorial_world.objects import LightSource, TutorialObject
#------------------------------------------------------------
#
# Tutorial room - parent room class
@ -45,7 +46,7 @@ class CmdTutorial(Command):
caller = self.caller
if not self.args:
target = self.obj # this is the room object the command is defined on
target = self.obj # this is the room the command is defined on
else:
target = caller.search(self.args.strip())
if not target:
@ -56,13 +57,16 @@ class CmdTutorial(Command):
else:
caller.msg("{RSorry, there is no tutorial help available here.{n")
class TutorialRoomCmdSet(CmdSet):
"Implements the simple tutorial cmdset"
key = "tutorial_cmdset"
def at_cmdset_creation(self):
"add the tutorial cmd"
self.add(CmdTutorial())
class TutorialRoom(Room):
"""
This is the base room type for all rooms in the tutorial world.
@ -78,7 +82,6 @@ class TutorialRoom(Room):
pass
#------------------------------------------------------------
#
# Weather room - scripted room
@ -89,7 +92,6 @@ class TutorialRoom(Room):
#
#------------------------------------------------------------
class WeatherRoom(TutorialRoom):
"""
This should probably better be called a rainy room...
@ -107,6 +109,7 @@ class WeatherRoom(TutorialRoom):
self.scripts.add(tut_scripts.IrregularEvent)
self.db.tutorial_info = \
"This room has a Script running that has it echo a weather-related message at irregular intervals."
def update_irregular(self):
"create a tuple of possible texts to return."
strings = (
@ -122,21 +125,23 @@ class WeatherRoom(TutorialRoom):
"You hear the distant howl of what sounds like some sort of dog or wolf.",
"Large clouds rush across the sky, throwing their load of rain over the world.")
# get a random value so we can select one of the strings above. Send this to the room.
# get a random value so we can select one of the strings above.
# Send this to the room.
irand = random.randint(0, 15)
if irand > 10:
return # don't return anything, to add more randomness
return # don't return anything, to add more randomness
self.msg_contents("{w%s{n" % strings[irand])
#-----------------------------------------------------------------------------------
#------------------------------------------------------------------------------
#
# Dark Room - a scripted room
#
# This room limits the movemenets of its denizens unless they carry a and active
# LightSource object (LightSource is defined in tutorialworld.objects.LightSource)
# LightSource object (LightSource is defined in
# tutorialworld.objects.LightSource)
#
#-----------------------------------------------------------------------------------
#------------------------------------------------------------------------------
class CmdLookDark(Command):
"""
@ -169,13 +174,15 @@ class CmdLookDark(Command):
caller.msg(messages[irand])
else:
# check so we don't already carry a lightsource.
carried_lights = [obj for obj in caller.contents if utils.inherits_from(obj, LightSource)]
carried_lights = [obj for obj in caller.contents
if utils.inherits_from(obj, LightSource)]
if carried_lights:
string = "You don't want to stumble around in blindness anymore. You already found what you need. Let's get light already!"
caller.msg(string)
return
#if we are lucky, we find the light source.
lightsources = [obj for obj in self.obj.contents if utils.inherits_from(obj, LightSource)]
lightsources = [obj for obj in self.obj.contents
if utils.inherits_from(obj, LightSource)]
if lightsources:
lightsource = lightsources[0]
else:
@ -186,6 +193,7 @@ class CmdLookDark(Command):
string += "\nYou pick it up, holding it firmly. Now you just need to {wlight{n it using the flint and steel you carry with you."
caller.msg(string)
class CmdDarkHelp(Command):
"""
Help command for the dark state.
@ -193,27 +201,34 @@ class CmdDarkHelp(Command):
key = "help"
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Implements the help command."
string = "Can't help you until you find some light! Try feeling around for something to burn."
string += " You cannot give up even if you don't find anything right away."
self.caller.msg(string)
# the nomatch system command will give a suitable error when we cannot find the normal commands.
# the nomatch system command will give a suitable error when we cannot find
# the normal commands.
from src.commands.default.syscommands import CMD_NOMATCH
from src.commands.default.general import CmdSay
class CmdDarkNoMatch(Command):
"This is called when there is no match"
key = CMD_NOMATCH
locks = "cmd:all()"
def func(self):
"Implements the command."
self.caller.msg("Until you find some light, there's not much you can do. Try feeling around.")
class DarkCmdSet(CmdSet):
"Groups the commands."
key = "darkroom_cmdset"
mergetype = "Replace" # completely remove all other commands
mergetype = "Replace" # completely remove all other commands
def at_cmdset_creation(self):
"populates the cmdset."
self.add(CmdTutorial())
@ -221,6 +236,7 @@ class DarkCmdSet(CmdSet):
self.add(CmdDarkHelp())
self.add(CmdDarkNoMatch())
self.add(CmdSay)
#
# Darkness room two-state system
#
@ -238,6 +254,7 @@ class DarkState(Script):
self.key = "tutorial_darkness_state"
self.desc = "A dark room"
self.persistent = True
def at_start(self):
"called when the script is first starting up."
for char in [char for char in self.obj.contents if char.has_player]:
@ -246,9 +263,11 @@ class DarkState(Script):
else:
char.cmdset.add(DarkCmdSet)
char.msg("The room is pitch dark! You are likely to be eaten by a Grue.")
def is_valid(self):
"is valid only as long as noone in the room has lit the lantern."
return not self.obj.is_lit()
def at_stop(self):
"Someone turned on a light. This state dies. Switch to LightState."
for char in [char for char in self.obj.contents if char.has_player]:
@ -256,23 +275,31 @@ class DarkState(Script):
self.obj.db.is_dark = False
self.obj.scripts.add(LightState)
class LightState(Script):
"""
This is the counterpart to the Darkness state. It is active when the lantern is on.
This is the counterpart to the Darkness state. It is active when the
lantern is on.
"""
def at_script_creation(self):
"Called when script is first created."
self.key = "tutorial_light_state"
self.desc = "A room lit up"
self.persistent = True
def is_valid(self):
"This state is only valid as long as there is an active light source in the room."
"""
This state is only valid as long as there is an active light
source in the room.
"""
return self.obj.is_lit()
def at_stop(self):
"Light disappears. This state dies. Return to DarknessState."
self.obj.db.is_dark = True
self.obj.scripts.add(DarkState)
class DarkRoom(TutorialRoom):
"""
A dark room. This tries to start the DarkState script on all
@ -287,20 +314,24 @@ class DarkRoom(TutorialRoom):
"""
return any([any([True for obj in char.contents
if utils.inherits_from(obj, LightSource) and obj.db.is_active])
for char in self.contents if char.has_player])
for char in self.contents if char.has_player])
def at_object_creation(self):
"Called when object is first created."
super(DarkRoom, self).at_object_creation()
self.db.tutorial_info = "This is a room with custom command sets on itself."
# this variable is set by the scripts. It makes for an easy flag to look for
# by other game elements (such as the crumbling wall in the tutorial)
# this variable is set by the scripts. It makes for an easy flag to
# look for by other game elements (such as the crumbling wall in
# the tutorial)
self.db.is_dark = True
# the room starts dark.
self.scripts.add(DarkState)
def at_object_receive(self, character, source_location):
"Called when an object enters the room. We crank the wheels to make sure scripts are synced."
"""
Called when an object enters the room. We crank the wheels to make
sure scripts are synced.
"""
if character.has_player:
if not self.is_lit() and not character.is_superuser:
character.cmdset.add(DarkCmdSet)
@ -313,10 +344,14 @@ class DarkRoom(TutorialRoom):
self.scripts.validate()
def at_object_leave(self, character, target_location):
"In case people leave with the light, we make sure to update the states accordingly."
character.cmdset.delete(DarkCmdSet) # in case we are teleported away
"""
In case people leave with the light, we make sure to update the
states accordingly.
"""
character.cmdset.delete(DarkCmdSet) # in case we are teleported away
self.scripts.validate()
#------------------------------------------------------------
#
# Teleport room - puzzle room
@ -347,23 +382,27 @@ class TeleportRoom(TutorialRoom):
super(TeleportRoom, self).at_object_creation()
# what character.db.puzzle_clue must be set to, to avoid teleportation.
self.db.puzzle_value = 1
# the target of the success teleportation. Can be a dbref or a unique room name.
# target of successful teleportation. Can be a dbref or a
# unique room name.
self.db.success_teleport_to = "treasure room"
# the target of the failure teleportation.
self.db.failure_teleport_to = "dark cell"
def at_object_receive(self, character, source_location):
"This hook is called by the engine whenever the player is moved into this room."
"""
This hook is called by the engine whenever the player is moved into
this room.
"""
if not character.has_player:
# only act on player characters.
return
#print character.db.puzzle_clue, self.db.puzzle_value
if character.db.puzzle_clue != self.db.puzzle_value:
# we didn't pass the puzzle. See if we can teleport.
teleport_to = self.db.failure_teleport_to # this is a room name
teleport_to = self.db.failure_teleport_to # this is a room name
else:
# passed the puzzle
teleport_to = self.db.success_teleport_to # this is a room name
teleport_to = self.db.success_teleport_to # this is a room name
results = search_object(teleport_to)
if not results or len(results) > 1:
@ -376,7 +415,7 @@ class TeleportRoom(TutorialRoom):
return
# teleport
character.execute_cmd("look")
character.location = results[0] # stealth move
character.location = results[0] # stealth move
character.location.at_object_receive(character, self)
#------------------------------------------------------------
@ -412,7 +451,8 @@ class CmdEast(Command):
bridge_step = min(5, caller.db.tutorial_bridge_position + 1)
if bridge_step > 4:
# we have reached the far east end of the bridge. Move to the east room.
# we have reached the far east end of the bridge.
# Move to the east room.
eexit = search_object(self.obj.db.east_exit)
if eexit:
caller.move_to(eexit[0])
@ -423,6 +463,7 @@ class CmdEast(Command):
caller.location.msg_contents("%s steps eastwards across the bridge." % caller.name, exclude=caller)
caller.execute_cmd("look")
# go back across the bridge
class CmdWest(Command):
"""
@ -440,7 +481,8 @@ class CmdWest(Command):
bridge_step = max(-1, caller.db.tutorial_bridge_position - 1)
if bridge_step < 0:
# we have reached the far west end of the bridge. Move to the west room.
# we have reached the far west end of the bridge.#
# Move to the west room.
wexit = search_object(self.obj.db.west_exit)
if wexit:
caller.move_to(wexit[0])
@ -451,6 +493,7 @@ class CmdWest(Command):
caller.location.msg_contents("%s steps westwartswards across the bridge." % caller.name, exclude=caller)
caller.execute_cmd("look")
class CmdLookBridge(Command):
"""
looks around at the bridge.
@ -486,7 +529,8 @@ class CmdLookBridge(Command):
self.caller.msg(message)
# there is a chance that we fall if we are on the western or central part of the bridge.
# there is a chance that we fall if we are on the western or central
# part of the bridge.
if bridge_position < 3 and random.random() < 0.05 and not self.caller.is_superuser:
# we fall on 5% of the times.
fexit = search_object(self.obj.db.fall_exit)
@ -504,6 +548,7 @@ class CmdLookBridge(Command):
self.caller.location = fexit[0] # stealth move, without any other hook calls.
self.obj.msg_contents("A plank gives way under %s's feet and they fall from the bridge!" % self.caller.key)
# custom help command
class CmdBridgeHelp(Command):
"""
@ -521,33 +566,38 @@ class CmdBridgeHelp(Command):
string += "or try to get back to the mainland {wwest{n)."
self.caller.msg(string)
class BridgeCmdSet(CmdSet):
"This groups the bridge commands. We will store it on the room."
key = "Bridge commands"
priority = 1 # this gives it precedence over the normal look/help commands.
def at_cmdset_creation(self):
"Called at first cmdset creation"
self.add(CmdTutorial())
self.add(CmdEast())
self.add(CmdWest())
self.add(CmdLookBridge())
self.add(CmdBridgeHelp())
class BridgeRoom(TutorialRoom):
"""
The bridge room implements an unsafe bridge. It also enters the player into a
state where they get new commands so as to try to cross the bridge.
The bridge room implements an unsafe bridge. It also enters the player into
a state where they get new commands so as to try to cross the bridge.
We want this to result in the player getting a special set of
commands related to crossing the bridge. The result is that it will take several
steps to cross it, despite it being represented by only a single room.
commands related to crossing the bridge. The result is that it will
take several steps to cross it, despite it being represented by only a
single room.
We divide the bridge into steps:
self.db.west_exit - - | - - self.db.east_exit
0 1 2 3 4
The position is handled by a variable stored on the player when entering and giving
special move commands will increase/decrease the counter until the bridge is crossed.
The position is handled by a variable stored on the player when entering
and giving special move commands will increase/decrease the counter
until the bridge is crossed.
"""
def at_object_creation(self):
@ -617,8 +667,8 @@ class BridgeRoom(TutorialRoom):
#
# Intro Room - unique room
#
# This room marks the start of the tutorial. It sets up properties on the player char
# that is needed for the tutorial.
# This room marks the start of the tutorial. It sets up properties on
# the player char that is needed for the tutorial.
#
#------------------------------------------------------------
@ -652,6 +702,7 @@ class IntroRoom(TutorialRoom):
string += "-"*78
character.msg("{r%s{n" % string)
#------------------------------------------------------------
#
# Outro room - unique room
@ -683,5 +734,6 @@ class OutroRoom(TutorialRoom):
del character.db.puzzle_clue
del character.db.combat_parry_mode
del character.db.tutorial_bridge_position
for tut_obj in [obj for obj in character.contents if utils.inherits_from(obj, TutorialObject)]:
for tut_obj in [obj for obj in character.contents
if utils.inherits_from(obj, TutorialObject)]:
tut_obj.reset()

View file

@ -5,6 +5,7 @@ This defines some generally useful scripts for the tutorial world.
import random
from ev import Script
#------------------------------------------------------------
#
# IrregularEvent - script firing at random intervals
@ -28,8 +29,9 @@ class IrregularEvent(Script):
self.key = "update_irregular"
self.desc = "Updates at irregular intervals"
self.interval = random.randint(30, 70) # interval to call.
self.start_delay = True # wait at least self.interval seconds before calling at_repeat the first time
self.interval = random.randint(30, 70) # interval to call.
self.start_delay = True # wait at least self.interval seconds before
# calling at_repeat the first time
self.persistent = True
# this attribute determines how likely it is the
@ -47,11 +49,13 @@ class IrregularEvent(Script):
except Exception:
pass
class FastIrregularEvent(IrregularEvent):
"A faster updating irregular event"
def at_script_creation(self):
"Called at initial script creation"
super(FastIrregularEvent, self).at_script_creation()
self.interval = 5 # every 5 seconds, 1/5 chance of firing
self.interval = 5 # every 5 seconds, 1/5 chance of firing
#------------------------------------------------------------
@ -64,11 +68,13 @@ class FastIrregularEvent(IrregularEvent):
# #
# # This sets up a reset system -- it resets the entire tutorial_world domain
# # and all objects inheriting from it back to an initial state, MORPG style. This is useful in order for
# # different players to explore it without finding things missing.
# # and all objects inheriting from it back to an initial state, MORPG style.
# This is useful in order for different players to explore it without finding
# # things missing.
# #
# # Note that this will of course allow a single player to end up with multiple versions of objects if
# # they just wait around between resets; In a real game environment this would have to be resolved e.g.
# # Note that this will of course allow a single player to end up with
# # multiple versions of objects if they just wait around between resets;
# # In a real game environment this would have to be resolved e.g.
# # with custom versions of the 'get' command not accepting doublets.
# #
@ -77,7 +83,8 @@ class FastIrregularEvent(IrregularEvent):
# UPDATE_INTERVAL = 60 * 10 # Measured in seconds
# #This is a list of script parent objects that subscribe to the reset functionality.
# #This is a list of script parent objects that subscribe to the reset
# functionality.
# RESET_SUBSCRIBERS = ["examples.tutorial_world.p_weapon_rack",
# "examples.tutorial_world.p_mob"]
@ -104,7 +111,4 @@ class FastIrregularEvent(IrregularEvent):
# try:
# obj.scriptlink.reset()
# except:
# logger.log_errmsg(traceback.print_exc())
# logger.log_errmsg(traceback.print_exc())

83
ev.py
View file

@ -2,53 +2,63 @@
Central API for the Evennia MUD/MUX/MU* creation system.
This is basically a set of shortcuts for accessing things in src/ with less boiler plate.
Import this from your code, use it with @py from in-game or explore it interactively from
a python shell.
This is basically a set of shortcuts for accessing things in src/ with less
boiler plate. Import this from your code, use it with @py from in-game or
explore it interactively from a python shell.
Notes:
0) Use ev.help(), ev.managers.help(), ev.default_cmds.help() and syscmdkeys.help() to
view the API structure and explore which variables/methods are available.
0) Use ev.help(), ev.managers.help(), ev.default_cmds.help() and
syscmdkeys.help() to view the API structure and explore which
variables/methods are available.
1) You should import things explicitly from the root of this module - you can not use
dot-notation to import deeper. Hence, to access a default command, you can do
1) You should import things explicitly from the root of this module - you
can not use ot-notation to import deeper. Hence, to access a default c
ommand, you can do
import ev
ev.default_cmds.CmdLook
or
from ev import default_cmds
default_cmds.CmdLook
But trying to import CmdLook directly with "from ev.default_cmds import CmdLook" will
not work since default_cmds is a property on the "ev" module, not a module of its own.
But trying to import CmdLook directly with
from ev.default_cmds import CmdLook
will not work since default_cmds is a property on the "ev" module,
not a module of its own.
2) "managers" is a container object that contains shortcuts to initiated versions of Evennia's django
database managers (e.g. managers.objects is an alias for ObjectDB.objects). These allow
for exploring the database in various ways. To use in code, do 'from ev import managers', then
access the managers on the managers object. Please note that the evennia-specific methods in
managers return typeclasses (or lists of typeclasses), whereas the default django ones (filter etc)
return database objects. You can convert between the two easily via dbobj.typeclass and
typeclass.dbobj, but it's worth to remember this difference.
2) "managers" is a container object that contains shortcuts to initiated
versions of Evennia's django database managers (e.g. managers.objects
is an alias for ObjectDB.objects). These allow for exploring the database
in various ways. To use in code, do 'from ev import managers', then access
the managers on the managers object. Please note that the evennia-specific
methods inmanagers return typeclasses (or lists of typeclasses), whereas
the default django ones (filter etc) return database objects. You can
convert between the two easily via dbobj.typeclass and typeclass.dbobj,
but it's worth to remember this difference.
3) "syscmdkeys" is a container object holding the names of system commands. Import with
'from ev import syscmdkeys', then access the variables on the syscmdkeys object.
3) "syscmdkeys" is a container object holding the names of system commands.
Import with 'from ev import syscmdkeys', then access the variables on
the syscmdkeys object.
4) You -have- to use the create_* functions (shortcuts to src.utils.create) to create new
Typeclassed game entities (Objects, Scripts or Players). Just initializing e.g. the Player class will
-not- set up Typeclasses correctly and will lead to errors. Other types of database objects
can be created normally, but there are conveniant create_* functions for those too, making
some more error checking.
4) You -have- to use the create_* functions (shortcuts to src.utils.create)
to create new ypeclassed game entities (Objects, Scripts, Channels or
Players). Just initializing e.g. the Player class will -not- set up
Typeclasses correctly and will lead to errors. Other types of database
objects can be created normally, but there are conveniant create_*
functions for those too, making some more error checking.
5) "settings" links to Evennia's game/settings file. "settings_full" shows all of django's available
settings. Note that this is for viewing only - you cannot *change* settings from here in a meaningful
way but have to update game/settings.py and restart the server.
5) "settings" links to Evennia's game/settings file. "settings_full" shows
all of django's available settings. Note that this is for viewing only -
you cannot *change* settings from here in a meaningful way but have to
update game/settings.py and restart the server.
6) The API accesses all relevant and most-neeeded functions/classes from src/ but might not
always include all helper-functions referenced from each such entity. To get to those, access
the modules in src/ directly. You can always do this anyway, if you do not want to go through
this API.
6) The API accesses all relevant and most-neeeded functions/classes from
src/ but might not always include all helper-functions referenced from
each such entity. To get to those, access the modules in src/ directly.
You can always do this anyway, if you do not want to go through this API.
"""
import sys, os
import sys
import os
######################################################################
# set Evennia version in __version__ property
@ -147,6 +157,7 @@ from src.utils import utils
from src.utils import gametime
from src.utils import ansi
######################################################################
# API containers and helper functions
######################################################################
@ -166,6 +177,7 @@ def help(header=False):
names = [var for var in ev.__dict__ if not var.startswith('_')]
return ", ".join(names)
class _EvContainer(object):
"""
Parent for other containers
@ -175,7 +187,8 @@ class _EvContainer(object):
"Returns list of contents"
names = [name for name in self.__class__.__dict__ if not name.startswith('_')]
names += [name for name in self.__dict__ if not name.startswith('_')]
return self.__doc__ + "-"*60 + "\n" + ", ".join(names)
return self.__doc__ + "-" * 60 + "\n" + ", ".join(names)
class DBmanagers(_EvContainer):
"""
@ -213,6 +226,7 @@ class DBmanagers(_EvContainer):
managers = DBmanagers()
del DBmanagers
class DefaultCmds(_EvContainer):
"""
This container holds direct shortcuts to all default commands in Evennia.
@ -235,7 +249,9 @@ class DefaultCmds(_EvContainer):
cmdlist = utils.variable_from_module(module, module.__all__)
self.__dict__.update(dict([(c.__name__, c) for c in cmdlist]))
from src.commands.default import admin, batchprocess, building, comms, general, player, help, system, unloggedin
from src.commands.default import (admin, batchprocess,
building, comms, general,
player, help, system, unloggedin)
add_cmds(admin)
add_cmds(building)
add_cmds(batchprocess)
@ -249,6 +265,7 @@ class DefaultCmds(_EvContainer):
default_cmds = DefaultCmds()
del DefaultCmds
class SystemCmds(_EvContainer):
"""
Creating commands with keys set to these constants will make

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -10,7 +10,8 @@ menu. Run the script with the -h flag to see usage information.
"""
import os
import sys, signal
import sys
import signal
from optparse import OptionParser
from subprocess import Popen
@ -132,7 +133,7 @@ from django.db import DatabaseError
from src.players.models import PlayerDB
try:
superuser = PlayerDB.objects.get(id=1)
except DatabaseError,e:
except DatabaseError, e:
print """
Your database does not seem to be set up correctly.
(error was '%s')
@ -186,7 +187,7 @@ if os.name == 'nt':
os.path.join(os.path.dirname(twistd.__file__),
os.pardir, os.pardir, os.pardir, os.pardir,
'scripts', 'twistd.py'))
bat_file = open('twistd.bat','w')
bat_file = open('twistd.bat', 'w')
bat_file.write("@\"%s\" \"%s\" %%*" % (sys.executable, twistd_path))
bat_file.close()
print """
@ -221,6 +222,7 @@ def get_pid(pidfile):
pid = f.read()
return pid
def del_pid(pidfile):
"""
The pidfile should normally be removed after a process has finished, but
@ -229,6 +231,7 @@ def del_pid(pidfile):
if os.path.exists(pidfile):
os.remove(pidfile)
def kill(pidfile, signal=SIG, succmsg="", errmsg="", restart_file=SERVER_RESTART, restart="reload"):
"""
Send a kill signal to a process based on PID. A customized success/error
@ -255,6 +258,7 @@ def kill(pidfile, signal=SIG, succmsg="", errmsg="", restart_file=SERVER_RESTART
return
print "Evennia:", errmsg
def run_menu():
"""
This launches an interactive menu.
@ -285,7 +289,7 @@ def run_menu():
errmsg = "The %s does not seem to be running."
if inp < 5:
if inp == 1:
pass # default operation
pass # default operation
elif inp == 2:
cmdstr.extend(['--iserver'])
elif inp == 3:
@ -344,8 +348,9 @@ def handle_args(options, mode, service):
if inter:
cmdstr.append('--iportal')
cmdstr.append('--noserver')
else: # all
# for convenience we don't start logging of portal, only of server with this command.
else: # all
# for convenience we don't start logging of
# portal, only of server with this command.
if inter:
cmdstr.extend(['--iserver'])
return cmdstr
@ -429,7 +434,9 @@ def main():
parser = OptionParser(usage="%prog [-i] [menu|start|reload|stop [server|portal|all]]",
description="""This is the main Evennia launcher. It handles the Portal and Server, the two services making up Evennia. Default is to operate on both services. Interactive mode sets the service to log to stdout, in the foreground. Note that when launching 'all' services with the \"--interactive\" flag, both services will be started, but only Server will actually be started in interactive mode, simply because this is the most commonly useful setup. To activate interactive mode also for Portal, use the menu or launch the two services explicitly as two separate calls to this program.""")
parser.add_option('-i', '--interactive', action='store_true', dest='interactive', default=False, help="Start given processes in interactive mode.")
parser.add_option('-i', '--interactive', action='store_true',
dest='interactive', default=False,
help="Start given processes in interactive mode.")
options, args = parser.parse_args()

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -32,6 +32,7 @@ from ev import default_cmds
#from contrib import misc_commands
#from contrib import chargen
class ExampleCmdSet(CmdSet):
"""
Implements an empty, example cmdset.
@ -44,7 +45,8 @@ class ExampleCmdSet(CmdSet):
This is the only method defined in a cmdset, called during
its creation. It should populate the set with command instances.
As and example we just add the empty base Command object. It prints some info.
As and example we just add the empty base Command object.
It prints some info.
"""
self.add(Command())
@ -75,6 +77,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
#self.add(lineeditor.CmdEditor())
#self.add(misc_commands.CmdQuell())
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
"""
This is an example of how to overload the command set of the
@ -99,6 +102,7 @@ class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
# any commands you add below will overload the default ones.
#
class PlayerCmdSet(default_cmds.PlayerCmdSet):
"""
This is set is available to the player when they have no

View file

@ -16,6 +16,7 @@ from ev import Command, CmdSet
# Commands defined on the red button
#------------------------------------------------------------
class CmdNudge(Command):
"""
Try to nudge the button's lid
@ -27,7 +28,7 @@ class CmdNudge(Command):
push the lid of the button away.
"""
key = "nudge lid" # two-word command name!
key = "nudge lid" # two-word command name!
aliases = ["nudge"]
locks = "cmd:all()"
@ -44,6 +45,7 @@ class CmdNudge(Command):
self.caller.msg("You manage to get a nail under the lid.")
self.caller.execute_cmd("open lid")
class CmdPush(Command):
"""
Push the red button
@ -82,7 +84,6 @@ class CmdPush(Command):
self.caller.msg(string)
class CmdSmashGlass(Command):
"""
smash glass
@ -118,7 +119,8 @@ class CmdSmashGlass(Command):
string += " you should just try to open the lid instead?"
self.caller.msg(string)
self.caller.location.msg_contents("%s tries to smash the glass of the button." %
(self.caller.name), exclude=self.caller)
(self.caller.name), exclude=self.caller)
class CmdOpenLid(Command):
"""
@ -144,12 +146,13 @@ class CmdOpenLid(Command):
string += "the lid will soon close again."
self.caller.msg(string)
self.caller.location.msg_contents("%s opens the lid of the button." %
(self.caller.name), exclude=self.caller)
(self.caller.name), exclude=self.caller)
# add the relevant cmdsets to button
self.obj.cmdset.add(LidClosedCmdSet)
# call object method
self.obj.open_lid()
class CmdCloseLid(Command):
"""
close the lid
@ -174,6 +177,7 @@ class CmdCloseLid(Command):
self.caller.location.msg_contents("%s closes the button's lid." %
(self.caller.name), exclude=self.caller)
class CmdBlindLook(Command):
"""
Looking around in darkness
@ -209,7 +213,8 @@ class CmdBlindLook(Command):
string += "Until it wears off, all you can do is feel around blindly."
self.caller.msg(string)
self.caller.location.msg_contents("%s stumbles around, blinded." %
(self.caller.name), exclude=self.caller)
(self.caller.name), exclude=self.caller)
class CmdBlindHelp(Command):
"""
@ -232,7 +237,6 @@ class CmdBlindHelp(Command):
# Command sets for the red button
#---------------------------------------------------------------
# We next tuck these commands into their respective command sets.
# (note that we are overdoing the cdmset separation a bit here
# to show how it works).
@ -247,12 +251,13 @@ class DefaultCmdSet(CmdSet):
using obj.cmdset.add_default().
"""
key = "RedButtonDefault"
mergetype = "Union" # this is default, we don't really need to put it here.
mergetype = "Union" # this is default, we don't really need to put it here.
def at_cmdset_creation(self):
"Init the cmdset"
self.add(CmdPush())
class LidClosedCmdSet(CmdSet):
"""
A simple cmdset tied to the redbutton object.
@ -274,6 +279,7 @@ class LidClosedCmdSet(CmdSet):
self.add(CmdSmashGlass())
self.add(CmdOpenLid())
class LidOpenCmdSet(CmdSet):
"""
This is the opposite of the Closed cmdset.
@ -288,6 +294,7 @@ class LidOpenCmdSet(CmdSet):
"setup the cmdset (just one command)"
self.add(CmdCloseLid())
class BlindCmdSet(CmdSet):
"""
This is the cmdset added to the *player* when
@ -300,8 +307,8 @@ class BlindCmdSet(CmdSet):
# we want to stop the player from walking around
# in this blinded state, so we hide all exits too.
# (channel commands will still work).
no_exits = True # keep player in the same room
no_objs = True # don't allow object commands
no_exits = True # keep player in the same room
no_objs = True # don't allow object commands
def at_cmdset_creation(self):
"Setup the blind cmdset"

View file

@ -12,6 +12,7 @@ from ev import Command as BaseCommand
from ev import default_cmds
from ev import utils
class Command(BaseCommand):
"""
Inherit from this if you want to create your own
@ -34,7 +35,6 @@ class Command(BaseCommand):
# arg_regex = r"\s.*?|$" # optional regex detailing how the part after
# the cmdname must look to match this command.
# (we don't implement hook method access() here, you don't need to
# modify that unless you want to change how the lock system works
# (in that case see src.commands.command.Command))
@ -104,8 +104,8 @@ class MuxCommand(default_cmds.MuxCommand):
cmdhandler at this point, and stored in self.cmdname. The rest is stored
in self.args.
The MuxCommand parser breaks self.args into its constituents and stores them in the
following variables:
The MuxCommand parser breaks self.args into its constituents and stores them
in the following variables:
self.switches = optional list of /switches (without the /)
self.raw = This is the raw argument input, including switches
self.args = This is re-defined to be everything *except* the switches

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -20,5 +20,6 @@ does what you expect it to.
"""
def at_initial_setup():
pass

View file

@ -26,6 +26,7 @@ at_server_cold_stop()
"""
def at_server_start():
"""
This is called every time the server starts up, regardless of
@ -33,6 +34,7 @@ def at_server_start():
"""
pass
def at_server_stop():
"""
This is called just before a server is shut down, regardless
@ -40,18 +42,21 @@ def at_server_stop():
"""
pass
def at_server_reload_start():
"""
This is called only when server starts back up after a reload.
"""
pass
def at_server_reload_stop():
"""
This is called only time the server stops before a reload.
"""
pass
def at_server_cold_start():
"""
This is called only when the server starts "cold", i.e. after a
@ -59,6 +64,7 @@ def at_server_cold_start():
"""
pass
def at_server_cold_stop():
"""
This is called only when the server goes down due to a shutdown or reset.

View file

@ -22,6 +22,7 @@ See many more examples of lock functions in src.locks.lockfuncs.
"""
def myfalse(accessing_obj, accessed_obj, *args, **kwargs):
"""
called in lockstring with myfalse().

View file

@ -18,10 +18,11 @@
"""
def testoob(character, *args, **kwargs):
"Simple test function"
print "Called testoob: %s" % val
return "testoob did stuff to the input string '%s'!" % val
print "Called testoob: %s" % args
return "testoob did stuff to the input string '%s'!" % args
# MSDP_MAP is a standard suggestions for making it easy to create generic guis.
@ -62,7 +63,7 @@ MSDP_REPORTABLE = {
# Combat
"OPPONENT_HEALTH": "opponent_health",
"OPPONENT_HEALTH_MAX":"opponent_health_max",
"OPPONENT_HEALTH_MAX": "opponent_health_max",
"OPPONENT_LEVEL": "opponent_level",
"OPPONENT_NAME": "opponent_name",

View file

@ -14,6 +14,7 @@ the Server startup process.
"""
def start_plugin_services(server):
"""
This hook is called by Evennia, last in the Server startup process.

View file

@ -14,6 +14,7 @@ the Portal startup process.
"""
def start_plugin_services(portal):
"""
This hook is called by Evennia, last in the Portal startup process.

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -1,41 +1,42 @@
"""
Template for Characters
Copy this module up one level and name it as you like, then
use it as a template to create your own Character class.
To make new logins default to creating characters
of your new type, change settings.BASE_CHARACTER_TYPECLASS to point to
your new class, e.g.
settings.BASE_CHARACTER_TYPECLASS = "game.gamesrc.objects.mychar.MyChar"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Character as DefaultCharacter
class Character(DefaultCharacter):
"""
The Character is like any normal Object (see example/object.py for
a list of properties and methods), except it actually implements
some of its hook methods to do some work:
at_basetype_setup - always assigns the default_cmdset to this object type
(important!)sets locks so character cannot be picked up
and its commands only be called by itself, not anyone else.
(to change things, use at_object_creation() instead)
at_after_move - launches the "look" command
at_post_puppet(player) - when Player disconnects from the Character, we
store the current location, so the "unconnected" character
object does not need to stay on grid but can be given a
None-location while offline.
at_pre_puppet - just before Player re-connects, retrieves the character's old
location and puts it back on the grid with a "charname has
connected" message echoed to the room
"""
pass
"""
Template for Characters
Copy this module up one level and name it as you like, then
use it as a template to create your own Character class.
To make new logins default to creating characters
of your new type, change settings.BASE_CHARACTER_TYPECLASS to point to
your new class, e.g.
settings.BASE_CHARACTER_TYPECLASS = "game.gamesrc.objects.mychar.MyChar"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Character as DefaultCharacter
class Character(DefaultCharacter):
"""
The Character is like any normal Object (see example/object.py for
a list of properties and methods), except it actually implements
some of its hook methods to do some work:
at_basetype_setup - always assigns the default_cmdset to this object type
(important!)sets locks so character cannot be picked up
and its commands only be called by itself, not anyone else.
(to change things, use at_object_creation() instead)
at_after_move - launches the "look" command
at_post_puppet(player) - when Player disconnects from the Character, we
store the current location, so the "unconnected" character
object does not need to stay on grid but can be given a
None-location while offline.
at_pre_puppet - just before Player re-connects, retrieves the character's
old location and puts it back on the grid with a "charname
has connected" message echoed to the room
"""
pass

View file

@ -1,43 +1,44 @@
"""
Template module for Exits
Copy this module up one level and name it as you like, then
use it as a template to create your own Exits.
To make the default commands (such as @dig/@open) default to creating exits
of your new type, change settings.BASE_EXIT_TYPECLASS to point to
your new class, e.g.
settings.BASE_EXIT_TYPECLASS = "game.gamesrc.objects.myexit.MyExit"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Exit as DefaultExit
class Exit(DefaultExit):
"""
Exits are connectors between rooms. Exits are normal Objects except
they defines the 'destination' property. It also does work in the
following methods:
basetype_setup() - sets default exit locks (to change, use at_object_creation instead)
at_cmdset_get() - this auto-creates and caches a command and a command set on itself
with the same name as the Exit object. This
allows users to use the exit by only giving its
name alone on the command line.
at_failed_traverse() - gives a default error message ("You cannot
go there") if exit traversal fails and an
attribute err_traverse is not defined.
Relevant hooks to overload (compared to other types of Objects):
at_before_traverse(traveller) - called just before traversing
at_after_traverse(traveller, source_loc) - called just after traversing
at_failed_traverse(traveller) - called if traversal failed for some reason. Will
not be called if the attribute 'err_traverse' is
defined, in which case that will simply be echoed.
"""
pass
"""
Template module for Exits
Copy this module up one level and name it as you like, then
use it as a template to create your own Exits.
To make the default commands (such as @dig/@open) default to creating exits
of your new type, change settings.BASE_EXIT_TYPECLASS to point to
your new class, e.g.
settings.BASE_EXIT_TYPECLASS = "game.gamesrc.objects.myexit.MyExit"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Exit as DefaultExit
class Exit(DefaultExit):
"""
Exits are connectors between rooms. Exits are normal Objects except
they defines the 'destination' property. It also does work in the
following methods:
basetype_setup() - sets default exit locks (to change, use at_object_creation instead)
at_cmdset_get() - this auto-creates and caches a command and a command set on itself
with the same name as the Exit object. This
allows users to use the exit by only giving its
name alone on the command line.
at_failed_traverse() - gives a default error message ("You cannot
go there") if exit traversal fails and an
attribute err_traverse is not defined.
Relevant hooks to overload (compared to other types of Objects):
at_before_traverse(traveller) - called just before traversing
at_after_traverse(traveller, source_loc) - called just after traversing
at_failed_traverse(traveller) - called if traversal failed for some reason. Will
not be called if the attribute 'err_traverse' is
defined, in which case that will simply be echoed.
"""
pass

View file

@ -1,127 +1,167 @@
"""
Template for Objects
Copy this module up one level and name it as you like, then
use it as a template to create your own Objects.
To make the default commands default to creating objects of your new
type (and also change the "fallback" object used when typeclass
creation fails), change settings.BASE_OBJECT_TYPECLASS to point to
your new class, e.g.
settings.BASE_OBJECT_TYPECLASS = "game.gamesrc.objects.myobj.MyObj"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Object as DefaultObject
class Object(DefaultObject):
"""
This is the root typeclass object, implementing an in-game Evennia
game object, such as having a location, being able to be
manipulated or looked at, etc. If you create a new typeclass, it
must always inherit from this object (or any of the other objects
in this file, since they all actually inherit from BaseObject, as
seen in src.object.objects).
The BaseObject class implements several hooks tying into the game
engine. By re-implementing these hooks you can control the
system. You should never need to re-implement special Python
methods, such as __init__ and especially never __getattribute__ and
__setattr__ since these are used heavily by the typeclass system
of Evennia and messing with them might well break things for you.
* Base properties defined/available on all Objects
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.
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.
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)
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
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
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
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, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False)
execute_cmd(raw_string)
msg(text=None, **kwargs)
msg_contents(message, exclude=None, from_obj=None, **kwargs)
move_to(destination, quiet=False, emit_to_obj=None, use_destination=True)
copy(new_key=None)
delete()
is_typeclass(typeclass, exact=False)
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
access(accessing_obj, access_type='read', default=False)
check_permstring(permstring)
* Hooks (these are class methods, so their arguments should also start with self):
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_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 object
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_access_success(accessing_obj, access_type) - called if an lock access check succeeded on this object
at_access_failure(accessing_obj, access_type) - called if an lock access check failed on this 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_msg_receive(self, msg, from_obj=None, **kwargs) - 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, **kwargs) - 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.
at_drop(dropper) - called when this object has been dropped.
at_say(speaker, message) - by default, called if an object inside this object speaks
"""
pass
"""
Template for Objects
Copy this module up one level and name it as you like, then
use it as a template to create your own Objects.
To make the default commands default to creating objects of your new
type (and also change the "fallback" object used when typeclass
creation fails), change settings.BASE_OBJECT_TYPECLASS to point to
your new class, e.g.
settings.BASE_OBJECT_TYPECLASS = "game.gamesrc.objects.myobj.MyObj"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Object as DefaultObject
class Object(DefaultObject):
"""
This is the root typeclass object, implementing an in-game Evennia
game object, such as having a location, being able to be
manipulated or looked at, etc. If you create a new typeclass, it
must always inherit from this object (or any of the other objects
in this file, since they all actually inherit from BaseObject, as
seen in src.object.objects).
The BaseObject class implements several hooks tying into the game
engine. By re-implementing these hooks you can control the
system. You should never need to re-implement special Python
methods, such as __init__ and especially never __getattribute__ and
__setattr__ since these are used heavily by the typeclass system
of Evennia and messing with them might well break things for you.
* Base properties defined/available on all Objects
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.
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.
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)
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
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
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
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, attribute_name=None,
use_nicks=False, location=None, ignore_errors=False, player=False)
execute_cmd(raw_string)
msg(text=None, **kwargs)
msg_contents(message, exclude=None, from_obj=None, **kwargs)
move_to(destination, quiet=False, emit_to_obj=None, use_destination=True)
copy(new_key=None)
delete()
is_typeclass(typeclass, exact=False)
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
access(accessing_obj, access_type='read', default=False)
check_permstring(permstring)
* Hooks (these are class methods, so args should start with self):
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_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 object
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_access_success(accessing_obj, access_type) - called if an lock access
check succeeded on this object
at_access_failure(accessing_obj, access_type) - called if an lock access
check failed on this 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_msg_receive(self, msg, from_obj=None, **kwargs) - 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, **kwargs) - 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.
at_drop(dropper) - called when this object has been dropped.
at_say(speaker, message) - by default, called if an object inside this
object speaks
"""
pass

View file

@ -1,90 +1,91 @@
"""
Template module for Players
Copy this module up one level and name it as you like, then
use it as a template to create your own Player class.
To make the default account login default to using a Player
of your new type, change settings.BASE_PLAYER_TYPECLASS to point to
your new class, e.g.
settings.BASE_PLAYER_TYPECLASS = "game.gamesrc.objects.myplayer.MyPlayer"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Player as DefaultPlayer
class Player(DefaultPlayer):
"""
This class describes the actual OOC player (i.e. the user connecting
to the MUD). It does NOT have visual appearance in the game world (that
is handled by the character which is connected to this). Comm channels
are attended/joined using this object.
It can be useful e.g. for storing configuration options for your game, but
should generally not hold any character-related info (that's best handled
on the character level).
Can be set using BASE_PLAYER_TYPECLASS.
* available properties
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.
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.
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.
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
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
msg(text=None, **kwargs)
swap_character(new_character, delete_old_character=False)
execute_cmd(raw_string, sessid=None)
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)
check_permstring(permstring)
* Hook methods (when re-implementation, remember methods need to have self as first arg)
basetype_setup()
at_player_creation()
- note that the following hooks are also found on Objects and are
usually handled on the character level:
at_init()
at_cmdset_get()
at_first_login()
at_post_login(sessid=None)
at_disconnect()
at_message_receive()
at_message_send()
at_server_reload()
at_server_shutdown()
"""
pass
"""
Template module for Players
Copy this module up one level and name it as you like, then
use it as a template to create your own Player class.
To make the default account login default to using a Player
of your new type, change settings.BASE_PLAYER_TYPECLASS to point to
your new class, e.g.
settings.BASE_PLAYER_TYPECLASS = "game.gamesrc.objects.myplayer.MyPlayer"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Player as DefaultPlayer
class Player(DefaultPlayer):
"""
This class describes the actual OOC player (i.e. the user connecting
to the MUD). It does NOT have visual appearance in the game world (that
is handled by the character which is connected to this). Comm channels
are attended/joined using this object.
It can be useful e.g. for storing configuration options for your game, but
should generally not hold any character-related info (that's best handled
on the character level).
Can be set using BASE_PLAYER_TYPECLASS.
* available properties
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.
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.
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.
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
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
msg(text=None, **kwargs)
swap_character(new_character, delete_old_character=False)
execute_cmd(raw_string, sessid=None)
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)
check_permstring(permstring)
* Hook methods (when re-implementation, remember methods need to have self as first arg)
basetype_setup()
at_player_creation()
- note that the following hooks are also found on Objects and are
usually handled on the character level:
at_init()
at_cmdset_get()
at_first_login()
at_post_login(sessid=None)
at_disconnect()
at_message_receive()
at_message_send()
at_server_reload()
at_server_shutdown()
"""
pass

View file

@ -19,6 +19,7 @@ from game.gamesrc.commands.examples import cmdset_red_button as cmdsetexamples
# Definition of the object itself
#
class RedButton(Object):
"""
This class describes an evil red button. It will use the script

View file

@ -1,32 +1,33 @@
"""
Template module for Rooms
Copy this module up one level and name it as you like, then
use it as a template to create your own Objects.
To make the default commands (such as @dig) default to creating rooms
of your new type, change settings.BASE_ROOM_TYPECLASS to point to
your new class, e.g.
settings.BASE_ROOM_TYPECLASS = "game.gamesrc.objects.myroom.MyRoom"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Room as DefaultRoom
class Room(DefaultRoom):
"""
Rooms are like any Object, except their location is None
(which is default). They also use basetype_setup() to
add locks so they cannot be puppeted or picked up.
(to change that, use at_object_creation instead)
See examples/object.py for a list of
properties and methods available on all Objects.
"""
pass
"""
Template module for Rooms
Copy this module up one level and name it as you like, then
use it as a template to create your own Objects.
To make the default commands (such as @dig) default to creating rooms
of your new type, change settings.BASE_ROOM_TYPECLASS to point to
your new class, e.g.
settings.BASE_ROOM_TYPECLASS = "game.gamesrc.objects.myroom.MyRoom"
Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.
"""
from ev import Room as DefaultRoom
class Room(DefaultRoom):
"""
Rooms are like any Object, except their location is None
(which is default). They also use basetype_setup() to
add locks so they cannot be puppeted or picked up.
(to change that, use at_object_creation instead)
See examples/object.py for a list of
properties and methods available on all Objects.
"""
pass

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -22,9 +22,9 @@ class BodyFunctions(Script):
def at_script_creation(self):
self.key = "bodyfunction"
self.desc = "Adds various timed events to a character."
self.interval = 20 # seconds
self.interval = 20 # seconds
#self.repeats = 5 # repeat only a certain number of times
self.start_delay = True # wait self.interval until first call
self.start_delay = True # wait self.interval until first call
#self.persistent = True
def at_repeat(self):

View file

@ -112,10 +112,10 @@ class BlindedState(Script):
"""
self.key = "temporary_blinder"
self.desc = "Temporarily blinds the player for a little while."
self.interval = 20 # seconds
self.start_delay = True # we don't want it to stop until after 20s.
self.repeats = 1 # this will go away after interval seconds.
self.persistent = False # we will ditch this if server goes down
self.interval = 20 # seconds
self.start_delay = True # we don't want it to stop until after 20s.
self.repeats = 1 # this will go away after interval seconds.
self.persistent = False # we will ditch this if server goes down
def at_start(self):
"""
@ -139,8 +139,8 @@ class BlindedState(Script):
self.obj.location.msg_contents("%s seems to be recovering their eyesight."
% self.obj.name,
exclude=self.obj)
self.obj.cmdset.delete() # this will clear the latest added cmdset,
# (which is the blinded one).
self.obj.cmdset.delete() # this will clear the latest added cmdset,
# (which is the blinded one).
#
@ -169,11 +169,11 @@ class CloseLidEvent(Script):
"""
self.key = "lid_closer"
self.desc = "Closes lid on a red buttons"
self.interval = 20 # seconds
self.start_delay = True # we want to pospone the launch.
self.repeats = 1 # we only close the lid once
self.persistent = True # even if the server crashes in those 20 seconds,
# the lid will still close once the game restarts.
self.interval = 20 # seconds
self.start_delay = True # we want to pospone the launch.
self.repeats = 1 # we only close the lid once
self.persistent = True # even if the server crashes in those 20 seconds,
# the lid will still close once the game restarts.
def is_valid(self):
"""
@ -207,9 +207,9 @@ class BlinkButtonEvent(Script):
"""
self.key = "blink_button"
self.desc = "Blinks red buttons"
self.interval = 35 #seconds
self.start_delay = False #blink right away
self.persistent = True #keep blinking also after server reboot
self.interval = 35 #seconds
self.start_delay = False #blink right away
self.persistent = True #keep blinking also after server reboot
def is_valid(self):
"""
@ -239,10 +239,10 @@ class DeactivateButtonEvent(Script):
"""
self.key = "deactivate_button"
self.desc = "Deactivate red button temporarily"
self.interval = 21 #seconds
self.start_delay = True # wait with the first repeat for self.interval seconds.
self.interval = 21 #seconds
self.start_delay = True # wait with the first repeat for self.interval seconds.
self.persistent = True
self.repeats = 1 # only do this once
self.repeats = 1 # only do this once
def at_start(self):
"""

View file

@ -20,44 +20,59 @@ dropped connections etc.
"""
from ev import Script
from ev import Script as BaseScript
class ExampleScript(BaseScript):
"""
A script type is customized by redefining some or all of its hook methods and variables.
A script type is customized by redefining some or all of its hook
methods and variables.
* available properties
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
from the paused timer (but at_start() will be called).
time_until_next_repeat() - if a timed script (interval>0), returns time
until next tick
* Hook methods (should also include self as the first argument):
@ -71,16 +86,17 @@ class ExampleScript(BaseScript):
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.
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_server_shutdown() - called at a full server shutdown.
"""

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -17,20 +17,22 @@
# automatically be made available for each block. Observe
# that changes to these variables made in one block is not
# preserved between blocks!)
# #CODE (infotext) [objname, objname, ...] - This designates a code block that will be executed like a
# stand-alone piece of code together with any #HEADER
# defined.
# infotext is a describing text about what goes in in this block. It will be
# shown by the batchprocessing command.
# <objname>s mark the (variable-)names of objects created in the code,
# and which may be auto-deleted by the processor if desired (such as when
# debugging the script). E.g., if the code contains the command
# myobj = create.create_object(...), you could put 'myobj' in the #CODE header
# regardless of what the created object is actually called in-game.
# #INSERT filename - this includes another code batch file. The named file will be loaded and
# run at this point. Note that code from the inserted file will NOT share #HEADERs
# with the importing file, but will only use the headers in the importing file.
# make sure to not create a cyclic import here!
# #CODE (infotext) [objname, objname, ...] - This designates a code block that
# will be executed like a stand-alone piece of code together with
# any #HEADER defined.
# infotext is a describing text about what goes in in this block.
# It will be shown by the batchprocessing command.
# <objname>s mark the (variable-)names of objects created in
# the code, and which may be auto-deleted by the processor if
# desired (such as when debugging the script). E.g., if the code
# contains the command myobj = create.create_object(...), you could
# put 'myobj' in the #CODE header regardless of what the created
# object is actually called in-game.
# #INSERT filename - this includes another code batch file. The named file will
# be loaded and run at this point. Note that code from the inserted
# file will NOT share #HEADERs with the importing file, but will
# only use the headers in the importing file. Make sure to not
# create a cyclic import here!
# The following variable is automatically made available for the script:

View file

@ -31,8 +31,10 @@ if not os.path.exists('settings.py'):
# basic stuff.
# make random secret_key.
import random, string
secret_key = list((string.letters + string.digits + string.punctuation).replace("\\","").replace("'",'"'))
import random
import string
secret_key = list((string.letters +
string.digits + string.punctuation).replace("\\", "").replace("'", '"'))
random.shuffle(secret_key)
secret_key = "".join(secret_key[:40])

View file

@ -82,6 +82,7 @@ if os.name == 'nt':
# Functions
def set_restart_mode(restart_file, flag="reload"):
"""
This sets a flag file for the restart mode.
@ -89,6 +90,7 @@ def set_restart_mode(restart_file, flag="reload"):
with open(restart_file, 'w') as f:
f.write(str(flag))
def get_restart_mode(restart_file):
"""
Parse the server/portal restart status
@ -98,6 +100,7 @@ def get_restart_mode(restart_file):
return f.read()
return "shutdown"
def get_pid(pidfile):
"""
Get the PID (Process ID) by trying to access
@ -109,6 +112,7 @@ def get_pid(pidfile):
pid = f.read()
return pid
def cycle_logfile(logfile):
"""
Rotate the old log files to <filename>.old
@ -126,13 +130,13 @@ def cycle_logfile(logfile):
SERVER = None
PORTAL = None
def start_services(server_argv, portal_argv):
"""
This calls a threaded loop that launces the Portal and Server
and then restarts them when they finish.
"""
global SERVER, PORTAL
processes = Queue.Queue()
def server_waiter(queue):
@ -141,7 +145,8 @@ def start_services(server_argv, portal_argv):
except Exception, e:
print "Server process error: %(e)s" % {'e': e}
return
queue.put(("server_stopped", rc)) # this signals the controller that the program finished
# this signals the controller that the program finished
queue.put(("server_stopped", rc))
def portal_waiter(queue):
try:
@ -149,7 +154,8 @@ def start_services(server_argv, portal_argv):
except Exception, e:
print "Portal process error: %(e)s" % {'e': e}
return
queue.put(("portal_stopped", rc)) # this signals the controller that the program finished
# this signals the controller that the program finished
queue.put(("portal_stopped", rc))
if portal_argv:
try:
@ -157,7 +163,8 @@ def start_services(server_argv, portal_argv):
# start portal as interactive, reloadable thread
PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
else:
# normal operation: start portal as a daemon; we don't care to monitor it for restart
# normal operation: start portal as a daemon;
# we don't care to monitor it for restart
PORTAL = Popen(portal_argv)
except IOError, e:
print "Portal IOError: %s\nA possible explanation for this is that 'twistd' is not found." % e
@ -178,13 +185,15 @@ def start_services(server_argv, portal_argv):
message, rc = processes.get()
# restart only if process stopped cleanly
if message == "server_stopped" and int(rc) == 0 and get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset"):
if (message == "server_stopped" and int(rc) == 0 and
get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset")):
print "Evennia Server stopped. Restarting ..."
SERVER = thread.start_new_thread(server_waiter, (processes, ))
continue
# normally the portal is not reloaded since it's run as a daemon.
if message == "portal_stopped" and int(rc) == 0 and get_restart_mode(PORTAL_RESTART) == "True":
if (message == "portal_stopped" and int(rc) == 0 and
get_restart_mode(PORTAL_RESTART) == "True"):
print "Evennia Portal stopped in interactive mode. Restarting ..."
PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
continue
@ -194,11 +203,12 @@ def start_services(server_argv, portal_argv):
def main():
"""
This handles the command line input of the runner (it's most often called by evennia.py)
This handles the command line input of the runner
(it's most often called by evennia.py)
"""
parser = OptionParser(usage="%prog [options] start",
description="This runner should normally *not* be called directly - it is called automatically from the evennia.py main program. It manages the Evennia game server and portal processes an hosts a threaded loop to restart the Server whenever it is stopped (this constitues Evennia's reload mechanism).")
description="This runner should normally *not* be called directly - it is called automatically from the evennia.py main program. It manages the Evennia game server and portal processes an hosts a threaded loop to restart the Server whenever it is stopped (this constitues Evennia's reload mechanism).")
parser.add_option('-s', '--noserver', action='store_true',
dest='noserver', default=False,
help='Do not start Server process')
@ -267,7 +277,6 @@ def main():
server_argv.extend(sprof_argv)
print "\nRunning Evennia Server under cProfile."
# Portal
pid = get_pid(PORTAL_PIDFILE)
@ -292,7 +301,6 @@ def main():
portal_argv.extend(pprof_argv)
print "\nRunning Evennia Portal under cProfile."
# Windows fixes (Windows don't support pidfiles natively)
if os.name == 'nt':
if server_argv:

View file

@ -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 = []

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = {}

View file

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

View file

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

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more