Reworked the build script and made the default tutorial_room more clever, using details and custom cmdsets.

This commit is contained in:
Griatch 2015-02-21 20:08:57 +01:00
parent f0770da672
commit c63ae1742f
11 changed files with 767 additions and 449 deletions

View file

@ -88,7 +88,9 @@ except (IOError, CalledProcessError):
def init():
"""
This is called only after Evennia has fully initialized all its models.
This is called by the launcher only after Evennia has fully
initialized all its models. It sets up the API in a safe
environment where all models are available already.
"""
def imp(path, variable=True):
"Helper function"
@ -97,14 +99,14 @@ def init():
mod, fromlist = path.rsplit('.', 1)
return __import__(mod, fromlist=[fromlist])
global DefaultPlayer, DefaultObject, DefaultGuest, DefaultCharacter, \
DefaultRoom, DefaultExit, DefaultChannel, DefaultScript
global DefaultPlayer, DefaultObject, DefaultGuest, DefaultCharacter
global DefaultRoom, DefaultExit, DefaultChannel, DefaultScript
global ObjectDB, PlayerDB, ScriptDB, ChannelDB, Msg
global Command, CmdSet, default_cmds, syscmdkeys
global search_object, search_script, search_player, search_channel, search_help
global create_object, create_script, create_player, create_channel, create_message
global lockfuncs, tickerhandler, logger, utils, gametime, ansi, spawn, managers
global contrib
global lockfuncs, logger, utils, gametime, ansi, spawn, managers
global contrib, TICKER_HANDLER, OOB_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER
from players.players import DefaultPlayer
from players.players import DefaultGuest

View file

@ -146,6 +146,7 @@ def get_and_merge_cmdsets(caller, session, player, obj,
yield [lobj.cmdset.current for lobj in local_objlist
if (lobj.cmdset.current and
lobj.locks.check(caller, 'call', no_superuser_bypass=True))]
print "local_obj_cmdsets:", [c.key for c in local_obj_cmdsets]
for cset in local_obj_cmdsets:
#This is necessary for object sets, or we won't be able to
# separate the command sets from each other in a busy room.
@ -209,6 +210,7 @@ def get_and_merge_cmdsets(caller, session, player, obj,
if cmdsets:
# faster to do tuple on list than to build tuple directly
mergehash = tuple([id(cmdset) for cmdset in cmdsets])
print "mergehash:", mergehash, mergehash in _CMDSET_MERGE_CACHE, [(c.key, c.priority) for c in cmdsets]
if mergehash in _CMDSET_MERGE_CACHE:
# cached merge exist; use that
cmdset = _CMDSET_MERGE_CACHE[mergehash]

View file

@ -694,11 +694,11 @@ class CmdStateCC(MuxCommand):
class CmdStateJJ(MuxCommand):
"""
j <command number>
jj <command number>
Jump to specific command number
"""
key = "j"
key = "jj"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"

View file

@ -54,65 +54,33 @@ class CmdLook(MuxCommand):
locks = "cmd:all()"
arg_regex = r"\s|$"
# we split up the functionality of Look a little
# since this is a command which is very common to
# overload; this makes it easy to overload different
# sections of it without overloading all.
def at_found_target(self, target):
"""
Called when a target object has been found to look at.
Args:
target (Object): object found to look at (args have
been parsed and searched for already at this point)
The default implementation calls the return_appearance hook on
the observed target object to have it describe itself (and
possibly its contents) as well as format the description into
a suitable form.
"""
caller = self.caller
if not hasattr(target, 'return_appearance'):
# this is likely due to us having a player instead
target = target.character
if not target.access(caller, "view"):
# no permission to view this object - act as if
# it was not found at all.
caller.msg("Could not find '%s'." % self.args)
return
# get object's appearance
self.caller.msg(target.return_appearance(caller))
# the object's at_desc() method.
target.at_desc(looker=caller)
def at_not_found_target(self):
"""
Called when no target object was found to look at.
"""
if not self.args:
# this means we tried to look at location but failed. It
# usually means we are OOC.
return self.caller.msg("You have no location to look at!")
# otherwise we just return quietly.
return
def func(self):
"""
Handle the looking.
"""
caller = self.caller
if self.args:
target = caller.search(self.args, use_nicks=True)
if target:
return self.at_found_target(target)
else:
return self.at_not_found_target()
args = self.args
if args:
# Use search to handle duplicate/nonexistant results.
looking_at_obj = caller.search(args, use_nicks=True)
if not looking_at_obj:
return
else:
target = caller.location
if target:
return self.at_found_target(target)
else:
return self.at_not_found_target()
looking_at_obj = caller.location
if not looking_at_obj:
caller.msg("You have no location to look at!")
return
if not hasattr(looking_at_obj, 'return_appearance'):
# this is likely due to us having a player instead
looking_at_obj = looking_at_obj.character
if not looking_at_obj.access(caller, "view"):
caller.msg("Could not find '%s'." % args)
return
# get object's appearance
caller.msg(looking_at_obj.return_appearance(caller))
# the object's at_desc() method.
looking_at_obj.at_desc(looker=caller)
class CmdNick(MuxCommand):

File diff suppressed because it is too large Load diff

View file

@ -9,13 +9,55 @@ import random
from evennia import TICKER_HANDLER
from evennia import search_object
from evennia import Command, CmdSet
from evennia.contrib.tutorial_world import objects as tut_objects
class CmdMobOnOff(Command):
"""
Activates/deactivates Mob
Usage:
mobon <mob>
moboff <mob>
This turns the mob from active (alive) mode
to inactive (dead) mode. It is used during
building to activate the mob once it's
prepared.
"""
key = "mobon"
aliases = "moboff"
locks = "cmd:superuser()"
def func(self):
"""
Uses the mob's set_alive/set_dead methods
to turn on/off the mob."
"""
if not self.args:
self.caller.msg("Usage: mobon|moboff <mob>")
return
mob = self.caller.search(self.args)
if not mob:
return
if self.cmdname == "mobon":
mob.set_alive()
else:
mob.set_dead()
class MobCmdSet(CmdSet):
"""
Holds the admin command controlling the mob
"""
def at_cmdset_creation(self):
self.add(CmdMobOnOff())
class Mob(tut_objects.TutorialObject):
"""
This is a state-machine AI mobile. It has several states which
are controlled from setting various Attributes:
This is a state-machine AI mobile. It has several states which are
controlled from setting various Attributes. All default to True:
patrolling: if set, the mob will move randomly
from room to room, but preferring to not return
@ -30,18 +72,23 @@ class Mob(tut_objects.TutorialObject):
to flee from it, so it can enter combat. If unset,
it will return to patrolling/idling if fled from.
immortal: If set, the mob cannot take any damage.
It also has several states,
is_patrolling - set when the mob is patrolling.
is_attacking - set when the mob is in combat
is_hunting - set when the mob is pursuing an enemy.
is_immortal - is currently immortal
is_dead: if set, the Mob is set to immortal, non-patrolling
and non-aggressive mode. Its description is
turned into that of a corpse-description.
Other important properties:
home - the home location should set to someplace inside
the patrolling area. The mob will use this if it should
happen to roam into a room with no exits.
irregular_echoes: list of strings the mob generates at irregular intervals.
desc_alive: the physical description while alive
desc_dead: the physical descripion while dead
send_defeated_to: unique key/alias for location to send defeated enemies to
defeat_msg: message to echo to defeated opponent
defeat_msg_room: message to echo to room. Accepts %s as the name of the defeated.
hit_msg: message to echo when this mob is hit. Accepts %s for the mob's key.
weapon_ineffective_msg: message to echo for useless attacks
death_msg: message to echo to room when this mob dies.
patrolling_pace: how many seconds per tick, when patrolling
aggressive_pace: -"- attacking
hunting_pace: -"- hunting
death_pace: -"- returning to life when dead
field 'home' - the home location should set to someplace inside
the patrolling area. The mob will use this if it should
happen to roam into a room with no exits.
"""
def __init__(self):
@ -60,10 +107,11 @@ class Mob(tut_objects.TutorialObject):
Called the first time the object is created.
We set up the base properties and flags here.
"""
self.cmdsets.add(MobCmdSet, permanent=True)
# Main AI flags. We start in dead mode so we don't have to
# chase the mob around when building.
self.db.patrolling = False
self.db.aggressive = False
self.db.patrolling = True
self.db.aggressive = True
self.db.immortal = True
# db-store if it is dead or not
self.db.is_dead = True
@ -99,10 +147,11 @@ class Mob(tut_objects.TutorialObject):
# text to echo to the defeated foe.
self.db.defeat_msg = "You fall to the ground."
self.db.defeat_msg_room = "%s falls to the ground."
self.db.weapon_ineffective_text = "Your weapon just passes through your enemy, causing almost no effect!"
self.db.weapon_ineffective_msg = "Your weapon just passes through your enemy, causing almost no effect!"
self.db.death_msg = "After the last hit %s evaporates." % self.key
self.db.hit_msg = "%s wails, shudders and writhes." % self.key
self.db.irregular_msgs = ["the enemy looks about.", "the enemy changes stance."]
self.db.tutorial_info = "This is an object with simple state AI, using a ticker to move."
@ -127,7 +176,7 @@ class Mob(tut_objects.TutorialObject):
previous ticker subscription so that we can
easily find and stop it before setting a
new one. The tickerhandler is persistent so
we need to remmeber this across reloads.
we need to remember this across reloads.
"""
idstring = "tutorial_mob" # this doesn't change
@ -155,7 +204,7 @@ class Mob(tut_objects.TutorialObject):
if obj.has_player and not obj.is_superuser]
return targets[0] if targets else None
def set_alive(self):
def set_alive(self, *args, **kwargs):
"""
Set the mob to "alive" mode. This effectively
resurrects it from the dead state.
@ -232,7 +281,7 @@ class Mob(tut_objects.TutorialObject):
self.ndb.is_hunting = False
self.ndb.is_attacking = True
def do_patrol(self):
def do_patrol(self, *args, **kwargs):
"""
Called repeatedly during patrolling mode. In this mode, the
mob scans its surroundings and randomly chooses a viable exit.
@ -240,6 +289,8 @@ class Mob(tut_objects.TutorialObject):
order to block the mob from moving outside its area while
allowing player-controlled characters to move normally.
"""
if random.random() < 0.01:
self.location.msg_contents(random.choice(self.db.irregular_msgs))
if self.db.aggressive:
# first check if there are any targets in the room.
target = self._find_target(self.location)
@ -263,12 +314,14 @@ class Mob(tut_objects.TutorialObject):
# no exits! teleport to home to get away.
self.move_to(self.home)
def do_hunting(self):
def do_hunting(self, *args, **kwargs):
"""
Called regularly when in hunting mode. In hunting mode the mob
scans adjacent rooms for enemies and moves towards them to
attack if possible.
"""
if random.random() < 0.01:
self.location.msg_contents(random.choice(self.db.irregular_msgs))
if self.db.aggressive:
# first check if there are any targets in the room.
target = self._find_target(self.location)
@ -293,12 +346,14 @@ class Mob(tut_objects.TutorialObject):
# no exits! teleport to home to get away.
self.move_to(self.home)
def do_attacking(self):
def do_attacking(self, *args, **kwargs):
"""
Called regularly when in attacking mode. In attacking mode
the mob will bring its weapons to bear on any targets
in the room.
"""
if random.random() < 0.01:
self.location.msg_contents(random.choice(self.db.irregular_msgs))
# first make sure we have a target
target = self._find_target(self.location)
if not target:

View file

@ -19,11 +19,9 @@ WeaponRack
"""
import time
import random
from evennia import create_object
from evennia import DefaultObject, DefaultExit, Command, CmdSet, DefaultScript
from evennia import DefaultObject, DefaultExit, Command, CmdSet
from evennia import utils
from evennia.utils.spawner import spawn
@ -171,8 +169,8 @@ class CmdClimb(Command):
if not ostring:
ostring = "You climb %s. Having looked around, you climb down again." % self.obj.name
self.caller.msg(ostring)
# store this object to remember what we last climbed
self.caller.db.last_climbed = self.obj
# set a tag on the caller to remember that we climbed.
self.caller.tags.add("tutorial_climbed_tree")
class CmdSetClimbable(CmdSet):
@ -611,8 +609,6 @@ class CrumblingWall(TutorialObject, DefaultExit):
# if its location is lit and only traverse it once the Attribute
# exit_open is set to True.
self.locks.add("cmd:locattr(is_lit);traverse:objattr(exit_open)")
self.db.tutorial_info = "This is an Exit with a conditional traverse-lock. Try to shift the roots around."
# set cmdset
self.cmdset.add(CmdSetCrumblingWall, permanent=True)
@ -671,7 +667,8 @@ class CrumblingWall(TutorialObject, DefaultExit):
# puzzle not solved yet.
string = "The wall is old and covered with roots that here and there have permeated the stone. " \
"The roots (or whatever they are - some of them are covered in small non-descript flowers) " \
"crisscross the wall, making it hard to clearly see its stony surface.\n"
"crisscross the wall, making it hard to clearly see its stony surface. Maybe you could " \
"try to {wshift{n or {wmove{n them.\n"
# display the root positions to help with the puzzle
for key, pos in self.db.root_pos.items():
string += "\n" + self._translate_position(key, pos)
@ -851,7 +848,7 @@ class Weapon(TutorialObject):
super(Weapon, self).at_object_creation()
self.db.hit = 0.4 # hit chance
self.db.parry = 0.8 # parry chance
self.db.damage = 8.0
self.db.damage = 1.0
self.db.magic = False
self.cmdset.add_default(CmdSetWeapon, permanent=True)
@ -1007,6 +1004,13 @@ class WeaponRack(TutorialObject):
on it. This will also set a property on the character
to make sure they can't get more than one at a time.
Attributes to set on this object:
available_weapons: list of prototype-keys from
WEAPON_PROTOTYPES, the weapons available in this rack.
no_more_weapons_msg - error message to return to players
who already got one weapon from the rack and tries to
grab another one.
"""
def at_object_creation(self):
"""
@ -1016,21 +1020,27 @@ class WeaponRack(TutorialObject):
self.db.rack_id = "weaponrack_1"
# these are prototype names from the prototype
# dictionary above.
self.db.get_weapon_msg = "You pull %s from the rack."
self.db.no_more_weapons_msg = "%s has no more to offer you." % self.key
self.db.available_weapons = ["knife", "rusty_dagger",
"sword", "club"]
def produce_weapon(self, caller):
"""
This will produce a new weapon from the rack,
assuming the caller hasn't already gotten one.
assuming the caller hasn't already gotten one. When
doing so, the caller will get Tagged with the id
of this rack, to make sure they cannot keep
pulling weapons from it indefinitely.
"""
if caller.attributes.get(self.db.rack_id):
rack_id = self.db.rack_id
if caller.tags.get(rack_id):
caller.msg("%s has no more to offer you." % self.key)
else:
prototype = random.choice(self.db.available_weapons)
# use the spawner to create a new Weapon from the
# spawner dictionary
# spawner dictionary, tag the caller
wpn = spawn(WEAPON_PROTOTYPES[prototype], prototype_parents=WEAPON_PROTOTYPES)
caller.attributes.add(self.db.rack_id, True)
caller.tags.add(rack_id)
wpn.location = caller
caller.msg("You grab %s - %s." % (wpn.key, wpn.db.desc))
caller.msg(self.db.weapon_msg % wpn.key)

View file

@ -16,6 +16,11 @@ from evennia import utils, create_object, search_object
from evennia import syscmdkeys, default_cmds
from evennia.contrib.tutorial_world.objects import LightSource, TutorialObject
# the system error-handling module is defined in the settings. We load the
# given setting here using utils.object_from_module. This way we can use
# it regardless of if we change settings later.
from django.conf import settings
_SEARCH_AT_RESULT = utils.object_from_module(settings.SEARCH_AT_RESULT)
#------------------------------------------------------------
#
@ -68,15 +73,139 @@ class CmdTutorial(Command):
caller.msg("{RSorry, there is no tutorial help available here.{n")
# for the @detail command we inherit from MuxCommand, since
# we want to make use of MuxCommand's pre-parsing of '=' in the
# argument.
class CmdTutorialSetDetail(default_cmds.MuxCommand):
"""
sets a detail on a room
Usage:
@detail <key> = <description>
@detail <key>;<alias>;... = description
Example:
@detail walls = The walls are covered in ...
@detail castle;ruin;tower = The distant ruin ...
This sets a "detail" on the object this command is defined on
(TutorialRoom for this tutorial). This detail can be accessed with
the TutorialRoomLook command sitting on TutorialRoom objects (details
are set as a simple dictionary on the room). This is a Builder command.
We custom parse the key for the ;-separator in order to create
multiple aliases to the detail all at once.
"""
key = "@detail"
locks = "cmd:perm(Builders)"
help_category = "TutorialWorld"
def func(self):
"""
All this does is to check if the object has
the set_detail method and uses it.
"""
if not self.args or not self.rhs:
self.caller.msg("Usage: @detail key = description")
return
if not hasattr(self.obj, "set_detail"):
self.caller.msg("Details cannot be set on %s." % self.obj)
return
for key in self.args.split(";"):
# loop over all aliases, if any (if not, this will just be
# the one key to loop over)
self.obj.set_detail(key, self.rhs)
self.caller.msg("Detail set: '%s': '%s'" % (self.lhs, self.rhs))
class CmdTutorialLook(default_cmds.CmdLook):
"""
looks at the room and on details
Usage:
look <obj>
look <room detail>
look *<player>
Observes your location, details at your location or objects
in your vicinity.
Tutorial: This is a child of the default Look command, that also
allows us to look at "details" in the room. These details are
things to examine and offers some extra description without
actually having to be actual database objects. It uses the
return_detail() hook on TutorialRooms for this.
"""
# we don't need to specify key/locks etc, this is already
# set by the parent.
help_category = "TutorialWorld"
def func(self):
"""
Handle the looking. This is a copy of the default look
code except for adding in the details.
"""
caller = self.caller
args = self.args
print "tutorial look"
if args:
# we use quiet=True to turn off automatic error reporting.
# This tells search that we want to handle error messages
# ourself. This also means the search function will always
# return a list (with 0, 1 or more elements) rather than
# result/None.
looking_at_obj = caller.search(args, use_nicks=True, quiet=True)
if len(looking_at_obj) != 1:
# no target found or more than one target found (multimatch)
# look for a detail that may match
detail = self.obj.return_detail(args)
print "look detail:", detail, self.obj, self.obj.db.details
if detail:
self.caller.msg(detail)
return
else:
# no detail found, delegate our result to the normal
# error message handler.
_SEARCH_AT_RESULT(caller, args, looking_at_obj)
return
else:
# we found a match, extract it from the list and carry on
# normally with the look handling.
looking_at_obj = looking_at_obj[0]
else:
looking_at_obj = caller.location
if not looking_at_obj:
caller.msg("You have no location to look at!")
return
if not hasattr(looking_at_obj, 'return_appearance'):
# this is likely due to us having a player instead
looking_at_obj = looking_at_obj.character
if not looking_at_obj.access(caller, "view"):
caller.msg("Could not find '%s'." % args)
return
# get object's appearance
caller.msg(looking_at_obj.return_appearance(caller))
# the object's at_desc() method.
looking_at_obj.at_desc(looker=caller)
class TutorialRoomCmdSet(CmdSet):
"""
Implements the simple tutorial cmdset
Implements the simple tutorial cmdset. This will overload the look
command in the default CharacterCmdSet since it has a higher
priority (ChracterCmdSet has prio 0)
"""
key = "tutorial_cmdset"
priority = 1
def at_cmdset_creation(self):
"add the tutorial cmd"
"add the tutorial-room commands"
self.add(CmdTutorial())
self.add(CmdTutorialSetDetail())
self.add(CmdTutorialLook())
class TutorialRoom(DefaultRoom):
@ -95,6 +224,11 @@ class TutorialRoom(DefaultRoom):
the room about it by trying to call a hook on them. The Mob object
uses this to cheaply get notified of enemies without having
to constantly scan for them.
Args:
new_arrival (Object): the object that just entered this room.
source_location (Object): the previous location of new_arrival.
"""
if new_arrival.has_player and not new_arrival.is_superuser:
# this is a character
@ -102,6 +236,36 @@ class TutorialRoom(DefaultRoom):
if hasattr(obj, "at_new_arrival"):
obj.at_new_arrival(new_arrival)
def return_detail(self, detailkey):
"""
This looks for an Attribute "obj_details" and possibly
returns the value of it.
Args:
detailkey (str): The detail being looked at
"""
details = self.db.details
if details:
return details.get(detailkey, None)
def set_detail(self, detailkey, description):
"""
This sets a new detail, using an Attribute "details".
Args:
detailkey (str): The detail identifier to add (for
aliases you need to add multiple keys to the
same description).
description (str): The text to return when looking
at the given detailkey.
"""
if self.db.details:
self.db.details[detailkey] = description
else:
self.db.details = {detailkey: description}
def reset(self):
"Can be called by the tutorial runner."
pass
@ -155,11 +319,13 @@ class WeatherRoom(TutorialRoom):
self.db.tutorial_info = \
"This room has a Script running that has it echo a weather-related message at irregular intervals."
def update_weather(self):
def update_weather(self, *args, **kwargs):
"""
Called by the tickerhandler at regular intervals. Even so, we
only update 20% of the time, picking a random weather message
when we do.
when we do. The tickerhandler requires that this hook accepts
any arguments and keyword arguments (hence the *args, **kwargs
even though we don't actually use them in this example)
"""
if random.random() < 0.2:
# only update 20 % of the time
@ -168,7 +334,7 @@ class WeatherRoom(TutorialRoom):
#------------------------------------------------------------------------------
#
# Dark Room - a scripted room
# Dark Room - a room with states
#
# This room limits the movemenets of its denizens unless they carry an active
# LightSource object (LightSource is defined in
@ -395,7 +561,10 @@ class TeleportRoom(TutorialRoom):
Important attributes (set at creation):
puzzle_key - which attr to look for on character
puzzle_value - what char.db.puzzle_key must be set to
teleport_to - where to teleport to in case of failure to match
success_teleport_to - where to teleport in case if success
success_teleport_msg - message to echo while teleporting to success
failure_teleport_to - where to teleport to in case of failure
failure_teleport_msg - message to echo while teleporting to failure
"""
def at_object_creation(self):
@ -405,8 +574,10 @@ class TeleportRoom(TutorialRoom):
self.db.puzzle_value = 1
# target of successful teleportation. Can be a dbref or a
# unique room name.
self.db.success_teleport_msg = "You are successful!"
self.db.success_teleport_to = "treasure room"
# the target of the failure teleportation.
self.db.failure_teleport_msg = "You fail!"
self.db.failure_teleport_to = "dark cell"
def at_object_receive(self, character, source_location):
@ -417,14 +588,10 @@ class TeleportRoom(TutorialRoom):
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
else:
# passed the puzzle
teleport_to = self.db.success_teleport_to # this is a room name
# determine if the puzzle is a success or not
is_success = character.db.puzzle_clue == self.db.puzzle_value
teleport_to = self.db.success_teleport_to if is_success else self.db.failure_teleport_to
# note that this returns a list
results = search_object(teleport_to)
if not results or len(results) > 1:
# we cannot move anywhere since no valid target was found.
@ -434,8 +601,11 @@ class TeleportRoom(TutorialRoom):
# superusers don't get teleported
character.msg("Superuser block: You would have been teleported to %s." % results[0])
return
# the teleporter room's desc should give the 'teleporting message'.
character.execute_cmd("look")
# perform the teleport
if is_success:
character.msg(self.db.success_teleport_msg)
else:
character.msg(self.db.failure_teleport_msg)
# teleport quietly to the new place
character.move_to(results[0], quiet=True)
@ -468,7 +638,7 @@ class CmdEast(Command):
when exiting east.
- west_exit: a unique name or dbref to the room to go to
when exiting west.
The room must also have the following property:
The room must also have the following Attributes
- tutorial_bridge_posistion: the current position on
on the bridge, 0 - 4.
@ -687,13 +857,8 @@ class BridgeRoom(WeatherRoom):
self.db.fall_exit = "cliffledge"
# add the cmdset on the room.
self.cmdset.add_default(BridgeCmdSet)
# information for those using the tutorial command
self.db.tutorial_info = \
"The bridge seems large but is actually only a " \
"single room that assigns custom west/east commands " \
"and a counter to determine how far across you are."
def update_weather(self):
def update_weather(self, *args, **kwargs):
"""
This is called at irregular intervals and makes the passage
over the bridge a little more interesting.
@ -801,7 +966,7 @@ class OutroRoom(TutorialRoom):
"""
Called when the room is first created.
"""
super(IntroRoom, self).at_object_creation()
super(OutroRoom, self).at_object_creation()
self.db_tutorial_info = "The last room of the tutorial. " \
"This cleans up all temporary Attributes " \
"the tutorial may have assigned to the "\

View file

@ -422,6 +422,27 @@ def attr_ne(accessing_obj, accessed_obj, *args, **kwargs):
"""
return attr(accessing_obj, accessed_obj, *args, **{'compare': 'ne'})
def tag(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
tag(tagkey)
tag(tagkey, category)
Only true if accessing_obj has the specified tag and optional
category
"""
return accessing_obj.tags.get(*args)
def objtag(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
objtag(tagkey)
objtag(tagkey, category)
Only true if accessed_obj has the specified tag and optional
category.
"""
return accessed_obj.tags.get(*args)
def inside(accessing_obj, accessed_obj, *args, **kwargs):
"""

View file

@ -268,7 +268,8 @@ class TickerHandler(object):
the same time interval
hook_key (str, optional): The name of the hook method
on `obj` to call every `interval` seconds. Defaults to
`at_tick()`.
`at_tick(*args, **kwargs`. All hook methods must
always accept *args, **kwargs.
args, kwargs (optional): These will be passed into the
method given by `hook_key` every time it is called.

View file

@ -16,7 +16,6 @@ import textwrap
import datetime
import random
import traceback
from subprocess import check_output
from importlib import import_module
from inspect import ismodule, trace
from collections import defaultdict
@ -967,7 +966,8 @@ def class_from_module(path, defaultpaths=None):
err += "."
raise ImportError(err)
return cls
# alias
object_from_module = class_from_module
def init_new_player(player):
"""