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

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