PEP8 cleanup of the entire codebase. Unchanged are many cases of too-long lines, partly because of the rewrite they would require but also because splitting many lines up would make the code harder to read. Also the third-party libraries (idmapper, prettytable etc) were not cleaned.
This commit is contained in:
parent
30b7d2a405
commit
1ae17bcbe4
154 changed files with 5613 additions and 4054 deletions
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue