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(): 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): def imp(path, variable=True):
"Helper function" "Helper function"
@ -97,14 +99,14 @@ def init():
mod, fromlist = path.rsplit('.', 1) mod, fromlist = path.rsplit('.', 1)
return __import__(mod, fromlist=[fromlist]) return __import__(mod, fromlist=[fromlist])
global DefaultPlayer, DefaultObject, DefaultGuest, DefaultCharacter, \ global DefaultPlayer, DefaultObject, DefaultGuest, DefaultCharacter
DefaultRoom, DefaultExit, DefaultChannel, DefaultScript global DefaultRoom, DefaultExit, DefaultChannel, DefaultScript
global ObjectDB, PlayerDB, ScriptDB, ChannelDB, Msg global ObjectDB, PlayerDB, ScriptDB, ChannelDB, Msg
global Command, CmdSet, default_cmds, syscmdkeys global Command, CmdSet, default_cmds, syscmdkeys
global search_object, search_script, search_player, search_channel, search_help global search_object, search_script, search_player, search_channel, search_help
global create_object, create_script, create_player, create_channel, create_message global create_object, create_script, create_player, create_channel, create_message
global lockfuncs, tickerhandler, logger, utils, gametime, ansi, spawn, managers global lockfuncs, logger, utils, gametime, ansi, spawn, managers
global contrib global contrib, TICKER_HANDLER, OOB_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER
from players.players import DefaultPlayer from players.players import DefaultPlayer
from players.players import DefaultGuest 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 yield [lobj.cmdset.current for lobj in local_objlist
if (lobj.cmdset.current and if (lobj.cmdset.current and
lobj.locks.check(caller, 'call', no_superuser_bypass=True))] 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: for cset in local_obj_cmdsets:
#This is necessary for object sets, or we won't be able to #This is necessary for object sets, or we won't be able to
# separate the command sets from each other in a busy room. # 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: if cmdsets:
# faster to do tuple on list than to build tuple directly # faster to do tuple on list than to build tuple directly
mergehash = tuple([id(cmdset) for cmdset in cmdsets]) 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: if mergehash in _CMDSET_MERGE_CACHE:
# cached merge exist; use that # cached merge exist; use that
cmdset = _CMDSET_MERGE_CACHE[mergehash] cmdset = _CMDSET_MERGE_CACHE[mergehash]

View file

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

View file

@ -54,65 +54,33 @@ class CmdLook(MuxCommand):
locks = "cmd:all()" locks = "cmd:all()"
arg_regex = r"\s|$" 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): def func(self):
""" """
Handle the looking. Handle the looking.
""" """
caller = self.caller caller = self.caller
if self.args: args = self.args
target = caller.search(self.args, use_nicks=True) if args:
if target: # Use search to handle duplicate/nonexistant results.
return self.at_found_target(target) looking_at_obj = caller.search(args, use_nicks=True)
else: if not looking_at_obj:
return self.at_not_found_target() return
else: else:
target = caller.location looking_at_obj = caller.location
if target: if not looking_at_obj:
return self.at_found_target(target) caller.msg("You have no location to look at!")
else: return
return self.at_not_found_target()
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): 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 TICKER_HANDLER
from evennia import search_object from evennia import search_object
from evennia import Command, CmdSet
from evennia.contrib.tutorial_world import objects as tut_objects 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): class Mob(tut_objects.TutorialObject):
""" """
This is a state-machine AI mobile. It has several states which This is a state-machine AI mobile. It has several states which are
are controlled from setting various Attributes: controlled from setting various Attributes. All default to True:
patrolling: if set, the mob will move randomly patrolling: if set, the mob will move randomly
from room to room, but preferring to not return 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, to flee from it, so it can enter combat. If unset,
it will return to patrolling/idling if fled from. it will return to patrolling/idling if fled from.
immortal: If set, the mob cannot take any damage. immortal: If set, the mob cannot take any damage.
It also has several states, irregular_echoes: list of strings the mob generates at irregular intervals.
is_patrolling - set when the mob is patrolling. desc_alive: the physical description while alive
is_attacking - set when the mob is in combat desc_dead: the physical descripion while dead
is_hunting - set when the mob is pursuing an enemy. send_defeated_to: unique key/alias for location to send defeated enemies to
is_immortal - is currently immortal defeat_msg: message to echo to defeated opponent
is_dead: if set, the Mob is set to immortal, non-patrolling defeat_msg_room: message to echo to room. Accepts %s as the name of the defeated.
and non-aggressive mode. Its description is hit_msg: message to echo when this mob is hit. Accepts %s for the mob's key.
turned into that of a corpse-description. weapon_ineffective_msg: message to echo for useless attacks
Other important properties: death_msg: message to echo to room when this mob dies.
home - the home location should set to someplace inside patrolling_pace: how many seconds per tick, when patrolling
the patrolling area. The mob will use this if it should aggressive_pace: -"- attacking
happen to roam into a room with no exits. 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): def __init__(self):
@ -60,10 +107,11 @@ class Mob(tut_objects.TutorialObject):
Called the first time the object is created. Called the first time the object is created.
We set up the base properties and flags here. 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 # Main AI flags. We start in dead mode so we don't have to
# chase the mob around when building. # chase the mob around when building.
self.db.patrolling = False self.db.patrolling = True
self.db.aggressive = False self.db.aggressive = True
self.db.immortal = True self.db.immortal = True
# db-store if it is dead or not # db-store if it is dead or not
self.db.is_dead = True self.db.is_dead = True
@ -99,10 +147,11 @@ class Mob(tut_objects.TutorialObject):
# text to echo to the defeated foe. # text to echo to the defeated foe.
self.db.defeat_msg = "You fall to the ground." self.db.defeat_msg = "You fall to the ground."
self.db.defeat_msg_room = "%s falls 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.death_msg = "After the last hit %s evaporates." % self.key
self.db.hit_msg = "%s wails, shudders and writhes." % 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." 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 previous ticker subscription so that we can
easily find and stop it before setting a easily find and stop it before setting a
new one. The tickerhandler is persistent so 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 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] if obj.has_player and not obj.is_superuser]
return targets[0] if targets else None 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 Set the mob to "alive" mode. This effectively
resurrects it from the dead state. resurrects it from the dead state.
@ -232,7 +281,7 @@ class Mob(tut_objects.TutorialObject):
self.ndb.is_hunting = False self.ndb.is_hunting = False
self.ndb.is_attacking = True self.ndb.is_attacking = True
def do_patrol(self): def do_patrol(self, *args, **kwargs):
""" """
Called repeatedly during patrolling mode. In this mode, the Called repeatedly during patrolling mode. In this mode, the
mob scans its surroundings and randomly chooses a viable exit. 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 order to block the mob from moving outside its area while
allowing player-controlled characters to move normally. 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: if self.db.aggressive:
# first check if there are any targets in the room. # first check if there are any targets in the room.
target = self._find_target(self.location) target = self._find_target(self.location)
@ -263,12 +314,14 @@ class Mob(tut_objects.TutorialObject):
# no exits! teleport to home to get away. # no exits! teleport to home to get away.
self.move_to(self.home) 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 Called regularly when in hunting mode. In hunting mode the mob
scans adjacent rooms for enemies and moves towards them to scans adjacent rooms for enemies and moves towards them to
attack if possible. attack if possible.
""" """
if random.random() < 0.01:
self.location.msg_contents(random.choice(self.db.irregular_msgs))
if self.db.aggressive: if self.db.aggressive:
# first check if there are any targets in the room. # first check if there are any targets in the room.
target = self._find_target(self.location) target = self._find_target(self.location)
@ -293,12 +346,14 @@ class Mob(tut_objects.TutorialObject):
# no exits! teleport to home to get away. # no exits! teleport to home to get away.
self.move_to(self.home) self.move_to(self.home)
def do_attacking(self): def do_attacking(self, *args, **kwargs):
""" """
Called regularly when in attacking mode. In attacking mode Called regularly when in attacking mode. In attacking mode
the mob will bring its weapons to bear on any targets the mob will bring its weapons to bear on any targets
in the room. 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 # first make sure we have a target
target = self._find_target(self.location) target = self._find_target(self.location)
if not target: if not target:

View file

@ -19,11 +19,9 @@ WeaponRack
""" """
import time
import random import random
from evennia import create_object from evennia import DefaultObject, DefaultExit, Command, CmdSet
from evennia import DefaultObject, DefaultExit, Command, CmdSet, DefaultScript
from evennia import utils from evennia import utils
from evennia.utils.spawner import spawn from evennia.utils.spawner import spawn
@ -171,8 +169,8 @@ class CmdClimb(Command):
if not ostring: if not ostring:
ostring = "You climb %s. Having looked around, you climb down again." % self.obj.name ostring = "You climb %s. Having looked around, you climb down again." % self.obj.name
self.caller.msg(ostring) self.caller.msg(ostring)
# store this object to remember what we last climbed # set a tag on the caller to remember that we climbed.
self.caller.db.last_climbed = self.obj self.caller.tags.add("tutorial_climbed_tree")
class CmdSetClimbable(CmdSet): class CmdSetClimbable(CmdSet):
@ -611,8 +609,6 @@ class CrumblingWall(TutorialObject, DefaultExit):
# if its location is lit and only traverse it once the Attribute # if its location is lit and only traverse it once the Attribute
# exit_open is set to True. # exit_open is set to True.
self.locks.add("cmd:locattr(is_lit);traverse:objattr(exit_open)") 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 # set cmdset
self.cmdset.add(CmdSetCrumblingWall, permanent=True) self.cmdset.add(CmdSetCrumblingWall, permanent=True)
@ -671,7 +667,8 @@ class CrumblingWall(TutorialObject, DefaultExit):
# puzzle not solved yet. # puzzle not solved yet.
string = "The wall is old and covered with roots that here and there have permeated the stone. " \ 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) " \ "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 # display the root positions to help with the puzzle
for key, pos in self.db.root_pos.items(): for key, pos in self.db.root_pos.items():
string += "\n" + self._translate_position(key, pos) string += "\n" + self._translate_position(key, pos)
@ -851,7 +848,7 @@ class Weapon(TutorialObject):
super(Weapon, self).at_object_creation() super(Weapon, self).at_object_creation()
self.db.hit = 0.4 # hit chance self.db.hit = 0.4 # hit chance
self.db.parry = 0.8 # parry chance self.db.parry = 0.8 # parry chance
self.db.damage = 8.0 self.db.damage = 1.0
self.db.magic = False self.db.magic = False
self.cmdset.add_default(CmdSetWeapon, permanent=True) 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 on it. This will also set a property on the character
to make sure they can't get more than one at a time. 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): def at_object_creation(self):
""" """
@ -1016,21 +1020,27 @@ class WeaponRack(TutorialObject):
self.db.rack_id = "weaponrack_1" self.db.rack_id = "weaponrack_1"
# these are prototype names from the prototype # these are prototype names from the prototype
# dictionary above. # 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", self.db.available_weapons = ["knife", "rusty_dagger",
"sword", "club"] "sword", "club"]
def produce_weapon(self, caller): def produce_weapon(self, caller):
""" """
This will produce a new weapon from the rack, 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) caller.msg("%s has no more to offer you." % self.key)
else: else:
prototype = random.choice(self.db.available_weapons) prototype = random.choice(self.db.available_weapons)
# use the spawner to create a new Weapon from the # 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) 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 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 import syscmdkeys, default_cmds
from evennia.contrib.tutorial_world.objects import LightSource, TutorialObject 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") 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): 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" key = "tutorial_cmdset"
priority = 1
def at_cmdset_creation(self): def at_cmdset_creation(self):
"add the tutorial cmd" "add the tutorial-room commands"
self.add(CmdTutorial()) self.add(CmdTutorial())
self.add(CmdTutorialSetDetail())
self.add(CmdTutorialLook())
class TutorialRoom(DefaultRoom): 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 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 uses this to cheaply get notified of enemies without having
to constantly scan for them. 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: if new_arrival.has_player and not new_arrival.is_superuser:
# this is a character # this is a character
@ -102,6 +236,36 @@ class TutorialRoom(DefaultRoom):
if hasattr(obj, "at_new_arrival"): if hasattr(obj, "at_new_arrival"):
obj.at_new_arrival(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): def reset(self):
"Can be called by the tutorial runner." "Can be called by the tutorial runner."
pass pass
@ -155,11 +319,13 @@ class WeatherRoom(TutorialRoom):
self.db.tutorial_info = \ self.db.tutorial_info = \
"This room has a Script running that has it echo a weather-related message at irregular intervals." "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 Called by the tickerhandler at regular intervals. Even so, we
only update 20% of the time, picking a random weather message 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: if random.random() < 0.2:
# only update 20 % of the time # 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 # This room limits the movemenets of its denizens unless they carry an active
# LightSource object (LightSource is defined in # LightSource object (LightSource is defined in
@ -395,7 +561,10 @@ class TeleportRoom(TutorialRoom):
Important attributes (set at creation): Important attributes (set at creation):
puzzle_key - which attr to look for on character puzzle_key - which attr to look for on character
puzzle_value - what char.db.puzzle_key must be set to 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): def at_object_creation(self):
@ -405,8 +574,10 @@ class TeleportRoom(TutorialRoom):
self.db.puzzle_value = 1 self.db.puzzle_value = 1
# target of successful teleportation. Can be a dbref or a # target of successful teleportation. Can be a dbref or a
# unique room name. # unique room name.
self.db.success_teleport_msg = "You are successful!"
self.db.success_teleport_to = "treasure room" self.db.success_teleport_to = "treasure room"
# the target of the failure teleportation. # the target of the failure teleportation.
self.db.failure_teleport_msg = "You fail!"
self.db.failure_teleport_to = "dark cell" self.db.failure_teleport_to = "dark cell"
def at_object_receive(self, character, source_location): def at_object_receive(self, character, source_location):
@ -417,14 +588,10 @@ class TeleportRoom(TutorialRoom):
if not character.has_player: if not character.has_player:
# only act on player characters. # only act on player characters.
return return
#print character.db.puzzle_clue, self.db.puzzle_value # determine if the puzzle is a success or not
if character.db.puzzle_clue != self.db.puzzle_value: is_success = character.db.puzzle_clue == self.db.puzzle_value
# we didn't pass the puzzle. See if we can teleport. teleport_to = self.db.success_teleport_to if is_success else self.db.failure_teleport_to
teleport_to = self.db.failure_teleport_to # this is a room name # note that this returns a list
else:
# passed the puzzle
teleport_to = self.db.success_teleport_to # this is a room name
results = search_object(teleport_to) results = search_object(teleport_to)
if not results or len(results) > 1: if not results or len(results) > 1:
# we cannot move anywhere since no valid target was found. # we cannot move anywhere since no valid target was found.
@ -434,8 +601,11 @@ class TeleportRoom(TutorialRoom):
# superusers don't get teleported # superusers don't get teleported
character.msg("Superuser block: You would have been teleported to %s." % results[0]) character.msg("Superuser block: You would have been teleported to %s." % results[0])
return return
# the teleporter room's desc should give the 'teleporting message'. # perform the teleport
character.execute_cmd("look") if is_success:
character.msg(self.db.success_teleport_msg)
else:
character.msg(self.db.failure_teleport_msg)
# teleport quietly to the new place # teleport quietly to the new place
character.move_to(results[0], quiet=True) character.move_to(results[0], quiet=True)
@ -468,7 +638,7 @@ class CmdEast(Command):
when exiting east. when exiting east.
- west_exit: a unique name or dbref to the room to go to - west_exit: a unique name or dbref to the room to go to
when exiting west. 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 - tutorial_bridge_posistion: the current position on
on the bridge, 0 - 4. on the bridge, 0 - 4.
@ -687,13 +857,8 @@ class BridgeRoom(WeatherRoom):
self.db.fall_exit = "cliffledge" self.db.fall_exit = "cliffledge"
# add the cmdset on the room. # add the cmdset on the room.
self.cmdset.add_default(BridgeCmdSet) 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 This is called at irregular intervals and makes the passage
over the bridge a little more interesting. over the bridge a little more interesting.
@ -801,7 +966,7 @@ class OutroRoom(TutorialRoom):
""" """
Called when the room is first created. 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. " \ self.db_tutorial_info = "The last room of the tutorial. " \
"This cleans up all temporary Attributes " \ "This cleans up all temporary Attributes " \
"the tutorial may have assigned to the "\ "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'}) 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): def inside(accessing_obj, accessed_obj, *args, **kwargs):
""" """

View file

@ -268,7 +268,8 @@ class TickerHandler(object):
the same time interval the same time interval
hook_key (str, optional): The name of the hook method hook_key (str, optional): The name of the hook method
on `obj` to call every `interval` seconds. Defaults to 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 args, kwargs (optional): These will be passed into the
method given by `hook_key` every time it is called. method given by `hook_key` every time it is called.

View file

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