Updated the tutorial world's rooms.

This commit is contained in:
Griatch 2015-02-15 14:04:48 +01:00
parent a77d56c7e1
commit 0029232ab0

View file

@ -2,12 +2,18 @@
Room Typeclasses for the TutorialWorld. Room Typeclasses for the TutorialWorld.
This defines special types of Rooms available in the tutorial. To keep
everything in one place we define them together with custom commands
to control them, those commands could also have been in a separate
module (e.g. if they could have been re-used elsewhere.)
""" """
import random import random
from evennia import CmdSet, DefaultScript, Command, DefaultRoom from evennia import TICKER_HANDLER
from evennia import CmdSet, Command, DefaultRoom
from evennia import utils, create_object, search_object from evennia import utils, create_object, search_object
from evennia.contrib.tutorial_world import scripts as tut_scripts from evennia import syscmdkeys, default_cmds
from evennia.contrib.tutorial_world.objects import LightSource, TutorialObject from evennia.contrib.tutorial_world.objects import LightSource, TutorialObject
@ -21,6 +27,10 @@ from evennia.contrib.tutorial_world.objects import LightSource, TutorialObject
# #
#------------------------------------------------------------ #------------------------------------------------------------
#
# Special command avaiable in all tutorial rooms
#
class CmdTutorial(Command): class CmdTutorial(Command):
""" """
Get help during the tutorial Get help during the tutorial
@ -39,7 +49,7 @@ class CmdTutorial(Command):
def func(self): def func(self):
""" """
All we do is to scan the current location for an attribute All we do is to scan the current location for an Attribute
called `tutorial_info` and display that. called `tutorial_info` and display that.
""" """
@ -59,7 +69,9 @@ class CmdTutorial(Command):
class TutorialRoomCmdSet(CmdSet): class TutorialRoomCmdSet(CmdSet):
"Implements the simple tutorial cmdset" """
Implements the simple tutorial cmdset
"""
key = "tutorial_cmdset" key = "tutorial_cmdset"
def at_cmdset_creation(self): def at_cmdset_creation(self):
@ -84,35 +96,12 @@ class TutorialRoom(DefaultRoom):
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Weather room - scripted room # Weather room - room with a ticker
#
# The weather room is called by a script at
# irregular intervals. The script is generally useful
# and so is split out into tutorialworld.scripts.
# #
#------------------------------------------------------------ #------------------------------------------------------------
class WeatherRoom(TutorialRoom): # These are rainy weather strings
""" WEATHER_STRINGS = (
This should probably better be called a rainy room...
This sets up an outdoor room typeclass. At irregular intervals,
the effects of weather will show in the room. Outdoor rooms should
inherit from this.
"""
def at_object_creation(self):
"Called when object is first created."
super(WeatherRoom, self).at_object_creation()
# we use the imported IrregularEvent script
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 = (
"The rain coming down from the iron-grey sky intensifies.", "The rain coming down from the iron-grey sky intensifies.",
"A gush of wind throws the rain right in your face. Despite your cloak you shiver.", "A gush of wind throws the rain right in your face. Despite your cloak you shiver.",
"The rainfall eases a bit and the sky momentarily brightens.", "The rainfall eases a bit and the sky momentarily brightens.",
@ -125,24 +114,71 @@ class WeatherRoom(TutorialRoom):
"You hear the distant howl of what sounds like some sort of dog or wolf.", "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.") "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. class WeatherRoom(TutorialRoom):
# Send this to the room. """
irand = random.randint(0, 15) This should probably better be called a rainy room...
if irand > 10:
return # don't return anything, to add more randomness This sets up an outdoor room typeclass. At irregular intervals,
self.msg_contents("{w%s{n" % strings[irand]) the effects of weather will show in the room. Outdoor rooms should
inherit from this.
"""
def at_object_creation(self):
"""
Called when object is first created.
We set up a ticker to update this room regularly.
Note that we could in principle also use a Script to manage
the ticking of the room, the TickerHandler is works fine for
simple things like this though.
"""
super(WeatherRoom, self).at_object_creation()
# subscribe ourselves to a ticker to repeatedly call the hook
# "update_weather" on this object. The interval is randomized
# so as to not have all weather rooms update at the same time.
interval = random.randint(50, 70)
TICKER_HANDLER.add(self, interval, idstring="tutorial", hook_key="update_weather")
# this is parsed by the 'tutorial' command on TutorialRooms.
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):
"""
Called by the tickerhandler at regular intervals. Even so, we
only update 20% of the time, picking a random weather message
when we do.
"""
if random.random() < 0.2:
# only update 20 % of the time
self.msg_contents("{w%s{n" % random.choice(WEATHER_STRINGS))
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# #
# Dark Room - a scripted room # Dark Room - a scripted room
# #
# This room limits the movemenets of its denizens unless they carry a and 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
# tutorialworld.objects.LightSource) # tutorialworld.objects.LightSource)
# #
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
DARK_MESSAGES = ("It is pitch black. You are likely to be eaten by a grue."
"It's pitch black. You fumble around but cannot find anything.",
"You don't see a thing. You feel around, managing to bump your fingers hard against something. Ouch!",
"You don't see a thing! Blindly grasping the air around you, you find nothing.",
"It's totally dark here. You almost stumble over some un-evenness in the ground.",
"You are completely blind. For a moment you think you hear someone breathing nearby ... \n ... surely you must be mistaken.",
"Blind, you think you find some sort of object on the ground, but it turns out to be just a stone.",
"Blind, you bump into a wall. The wall seems to be covered with some sort of vegetation, but its too damp to burn.",
"You can't see anything, but the air is damp. It feels like you are far underground.")
ALREADY_LIGHTSOURCE = "You don't want to stumble around in blindness anymore. You already " \
"found what you need. Let's get light already!"
FOUND_LIGHTSOURCE = "Your fingers bump against a splinter of wood in a corner. It smells of resin and seems dry enough to burn! " \
"You pick it up, holding it firmly. Now you just need to {wlight{n it using the flint and steel you carry with you."
class CmdLookDark(Command): class CmdLookDark(Command):
""" """
Look around in darkness Look around in darkness
@ -150,48 +186,35 @@ class CmdLookDark(Command):
Usage: Usage:
look look
Looks in darkness Look around in the darkness, trying
to find something.
""" """
key = "look" key = "look"
aliases = ["l", 'feel', 'feel around', 'fiddle'] aliases = ["l", 'feel', 'search', 'feel around', 'fiddle']
locks = "cmd:all()" locks = "cmd:all()"
help_category = "TutorialWorld" help_category = "TutorialWorld"
def func(self): def func(self):
"Implement the command." """
Implement the command.
This works both as a look and a search command; there is a
random chance of eventually finding a light source.
"""
caller = self.caller caller = self.caller
# we don't have light, grasp around blindly.
messages = ("It's pitch black. You fumble around but cannot find anything.", if random.random() < 0.8:
"You don't see a thing. You feel around, managing to bump your fingers hard against something. Ouch!", # we don't find anything
"You don't see a thing! Blindly grasping the air around you, you find nothing.", caller.msg(random.choice(DARK_MESSAGES))
"It's totally dark here. You almost stumble over some un-evenness in the ground.",
"You are completely blind. For a moment you think you hear someone breathing nearby ... \n ... surely you must be mistaken.",
"Blind, you think you find some sort of object on the ground, but it turns out to be just a stone.",
"Blind, you bump into a wall. The wall seems to be covered with some sort of vegetation, but its too damp to burn.",
"You can't see anything, but the air is damp. It feels like you are far underground.")
irand = random.randint(0, 10)
if irand < len(messages):
caller.msg(messages[irand])
else: else:
# check so we don't already carry a lightsource. # we could have found something!
carried_lights = [obj for obj in caller.contents if any(obj for obj in caller.contents if utils.inherits_from(obj, LightSource)):
if utils.inherits_from(obj, LightSource)] # we already carry a LightSource object.
if carried_lights: caller.msg(ALREADY_LIGHTSOURCE)
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)]
if lightsources:
lightsource = lightsources[0]
else: else:
# create the light source from scratch. # don't have a light source, create a new one.
lightsource = create_object(LightSource, key="splinter") create_object(LightSource, key="splinter", location=caller)
lightsource.location = caller caller.msg(FOUND_LIGHTSOURCE)
string = "Your fingers bump against a splinter of wood in a corner. It smells of resin and seems dry enough to burn!"
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): class CmdDarkHelp(Command):
@ -203,20 +226,23 @@ class CmdDarkHelp(Command):
help_category = "TutorialWorld" help_category = "TutorialWorld"
def func(self): def func(self):
"Implements the help command." """
string = "Can't help you until you find some light! Try feeling around for something to burn." Replace the the help command with a not-so-useful help
string += " You cannot give up even if you don't find anything right away." """
string = "Can't help you until you find some light! Try looking/feeling around for something to burn. " \
"You shouldn't give up even if you don't find anything right away."
self.caller.msg(string) self.caller.msg(string)
# the nomatch system command will give a suitable error when we cannot find
# the normal commands.
from evennia.commands.default.syscommands import CMD_NOMATCH
from evennia.commands.default.general import CmdSay
class CmdDarkNoMatch(Command): class CmdDarkNoMatch(Command):
"This is called when there is no match" """
key = CMD_NOMATCH This is a system command. Commands with special keys are used to
override special sitations in the game. The CMD_NOMATCH is used
when the given command is not found in the current command set (it
replaces Evennia's default behavior or offering command
suggestions)
"""
key = syscmdkeys.CMD_NOMATCH
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
@ -225,79 +251,25 @@ class CmdDarkNoMatch(Command):
class DarkCmdSet(CmdSet): class DarkCmdSet(CmdSet):
"Groups the commands." """
Groups the commands of the dark room together. We also import the
default say command here so that players can still talk in the
darkness.
We give the cmdset the mergetype "Replace" to make sure it
completely replaces whichever command set it is merged onto
(usually the default cmdset)
"""
key = "darkroom_cmdset" key = "darkroom_cmdset"
mergetype = "Replace" # completely remove all other commands mergetype = "Replace"
def at_cmdset_creation(self): def at_cmdset_creation(self):
"populates the cmdset." "populate the cmdset."
self.add(CmdTutorial()) self.add(CmdTutorial())
self.add(CmdLookDark()) self.add(CmdLookDark())
self.add(CmdDarkHelp()) self.add(CmdDarkHelp())
self.add(CmdDarkNoMatch()) self.add(CmdDarkNoMatch())
self.add(CmdSay) self.add(default_cmds.CmdSay)
#
# Darkness room two-state system
#
class DarkState(DefaultScript):
"""
The darkness state is a script that keeps tabs on when
a player in the room carries an active light source. It places
a new, very restrictive cmdset (DarkCmdSet) on all the players
in the room whenever there is no light in it. Upon turning on
a light, the state switches off and moves to LightState.
"""
def at_script_creation(self):
"This setups the 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]:
if char.is_superuser:
char.msg("You are Superuser, so you are not affected by the dark state.")
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]:
char.cmdset.delete(DarkCmdSet)
self.obj.db.is_dark = False
self.obj.scripts.add(LightState)
class LightState(DefaultScript):
"""
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.
"""
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): class DarkRoom(TutorialRoom):
@ -305,52 +277,86 @@ class DarkRoom(TutorialRoom):
A dark room. This tries to start the DarkState script on all A dark room. This tries to start the DarkState script on all
objects entering. The script is responsible for making sure it is objects entering. The script is responsible for making sure it is
valid (that is, that there is no light source shining in the room). valid (that is, that there is no light source shining in the room).
"""
def is_lit(self):
"""
Helper method to check if the room is lit up. It checks all
characters in room to see if they carry an active object of
type LightSource.
"""
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])
The is_lit Attribute is used to define if the room is currently lit
or not, so as to properly echo state changes.
Since this room (in the tutorial) is meant as a sort of catch-all,
we also make sure to heal characters ending up here, since they
may have been beaten up by the ghostly apparition at this point.
"""
def at_object_creation(self): def at_object_creation(self):
"Called when object is first created." """
Called when object is first created.
"""
super(DarkRoom, self).at_object_creation() super(DarkRoom, self).at_object_creation()
self.db.tutorial_info = "This is a room with custom command sets on itself." 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)
self.db.is_dark = True
# the room starts dark. # the room starts dark.
self.scripts.add(DarkState) self.db.is_lit = False
def at_object_receive(self, character, source_location): def _carries_light(self, obj):
""" """
Called when an object enters the room. We crank the wheels to make Checks if the given object carries anything that gives light.
sure scripts are synced.
Note that we do NOT look for a specific LightSource typeclass,
but for the Attribute is_giving_light - this makes it easy to
later add other types of light-giving items.
""" """
if character.has_player: return any(obj for obj in obj.contents if obj.db.is_giving_light)
if not self.is_lit() and not character.is_superuser:
character.cmdset.add(DarkCmdSet) def _heal(self, character):
if character.db.health and character.db.health <= 0: """
# heal character coming here from being defeated by mob. Heal a character.
health = character.db.health_max """
if not health: health = character.db.health_max or 20
health = 20
character.db.health = health character.db.health = health
self.scripts.validate()
def at_object_leave(self, character, target_location): def check_light_state(self):
""" """
In case people leave with the light, we make sure to update the This method checks if there are any light sources in the room.
states accordingly. If there isn't it makes sure to add the dark cmdset to all
characters in the room. It is called whenever characters enter
the room and also by the Light sources when they turn on.
""" """
character.cmdset.delete(DarkCmdSet) # in case we are teleported away if any(self._carries_light(obj) for obj in self.contents):
self.scripts.validate() # people are carrying lights
if not self.db.is_lit:
self.db.is_lit = True
for char in (obj for obj in self.contents if obj.has_player):
# this won't do anything if it is already removed
char.cmdset.delete(DarkCmdSet)
char.msg("The room is lit up.")
else:
# noone is carrying light - darken the room
for char in (obj for obj in self.contents if obj.has_player):
if self.db.is_lit:
self.db.is_lit = False
if char.is_superuser:
char.msg("You are Superuser, so you are not affected by the dark state.")
else:
# put players in darkness
char.cmdset.add(DarkCmdSet)
char.msg("The room is completely dark.")
def at_object_receive(self, obj, source_location):
"""
Called when an object enters the room.
"""
if obj.has_player:
# a puppeted object, that is, a Character
self._heal(obj)
# in case the new guy carries light with them
self.check_light_state()
def at_object_leave(self, obj, target_location):
"""
In case people leave with the light, we make sure to clear the
DarkCmdSet if necessary. This also works if they are
teleported away.
"""
obj.cmdset.delete(DarkCmdSet)
self.check_light_state()
#------------------------------------------------------------ #------------------------------------------------------------
# #
@ -413,10 +419,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
# teleport # the teleporter room's desc should give the 'teleporting message'.
character.execute_cmd("look") character.execute_cmd("look")
character.location = results[0] # stealth move # teleport quietly to the new place
character.location.at_object_receive(character, self) character.move_to(results[0], quiet=True)
#------------------------------------------------------------ #------------------------------------------------------------
# #
@ -437,7 +444,19 @@ class TeleportRoom(TutorialRoom):
class CmdEast(Command): class CmdEast(Command):
""" """
Try to cross the bridge eastwards. Go eastwards across the bridge.
Tutorial info:
This command relies on the caller having two Attributes
(assigned by the room when entering):
- east_exit: a unique name or dbref to the room to go to
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:
- tutorial_bridge_posistion: the current position on
on the bridge, 0 - 4.
""" """
key = "east" key = "east"
aliases = ["e"] aliases = ["e"]
@ -445,7 +464,7 @@ class CmdEast(Command):
help_category = "TutorialWorld" help_category = "TutorialWorld"
def func(self): def func(self):
"move forward" "move one step eastwards"
caller = self.caller caller = self.caller
bridge_step = min(5, caller.db.tutorial_bridge_position + 1) bridge_step = min(5, caller.db.tutorial_bridge_position + 1)
@ -460,6 +479,8 @@ class CmdEast(Command):
caller.msg("No east exit was found for this room. Contact an admin.") caller.msg("No east exit was found for this room. Contact an admin.")
return return
caller.db.tutorial_bridge_position = bridge_step caller.db.tutorial_bridge_position = bridge_step
# since we are really in one room, we have to notify others
# in the room when we move.
caller.location.msg_contents("%s steps eastwards across the bridge." % caller.name, exclude=caller) caller.location.msg_contents("%s steps eastwards across the bridge." % caller.name, exclude=caller)
caller.execute_cmd("look") caller.execute_cmd("look")
@ -467,7 +488,19 @@ class CmdEast(Command):
# go back across the bridge # go back across the bridge
class CmdWest(Command): class CmdWest(Command):
""" """
Go back across the bridge westwards. Go westwards across the bridge.
Tutorial info:
This command relies on the caller having two Attributes
(assigned by the room when entering):
- east_exit: a unique name or dbref to the room to go to
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:
- tutorial_bridge_posistion: the current position on
on the bridge, 0 - 4.
""" """
key = "west" key = "west"
aliases = ["w"] aliases = ["w"]
@ -475,13 +508,13 @@ class CmdWest(Command):
help_category = "TutorialWorld" help_category = "TutorialWorld"
def func(self): def func(self):
"move forward" "move one step westwards"
caller = self.caller caller = self.caller
bridge_step = max(-1, caller.db.tutorial_bridge_position - 1) bridge_step = max(-1, caller.db.tutorial_bridge_position - 1)
if bridge_step < 0: if bridge_step < 0:
# we have reached the far west end of the bridge.# # we have reached the far west end of the bridge.
# Move to the west room. # Move to the west room.
wexit = search_object(self.obj.db.west_exit) wexit = search_object(self.obj.db.west_exit)
if wexit: if wexit:
@ -490,30 +523,18 @@ class CmdWest(Command):
caller.msg("No west exit was found for this room. Contact an admin.") caller.msg("No west exit was found for this room. Contact an admin.")
return return
caller.db.tutorial_bridge_position = bridge_step caller.db.tutorial_bridge_position = bridge_step
caller.location.msg_contents("%s steps westwartswards across the bridge." % caller.name, exclude=caller) # since we are really in one room, we have to notify others
# in the room when we move.
caller.location.msg_contents("%s steps westwards across the bridge." % caller.name, exclude=caller)
caller.execute_cmd("look") caller.execute_cmd("look")
class CmdLookBridge(Command): BRIDGE_POS_MESSAGES = ("You are standing {wvery close to the the bridge's western foundation{n. If you go west you will be back on solid ground ...",
"""
looks around at the bridge.
"""
key = 'look'
aliases = ["l"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Looking around, including a chance to fall."
bridge_position = self.caller.db.tutorial_bridge_position
messages =("You are standing {wvery close to the the bridge's western foundation{n. If you go west you will be back on solid ground ...",
"The bridge slopes precariously where it extends eastwards towards the lowest point - the center point of the hang bridge.", "The bridge slopes precariously where it extends eastwards towards the lowest point - the center point of the hang bridge.",
"You are {whalfways{n out on the unstable bridge.", "You are {whalfways{n out on the unstable bridge.",
"The bridge slopes precariously where it extends westwards towards the lowest point - the center point of the hang bridge.", "The bridge slopes precariously where it extends westwards towards the lowest point - the center point of the hang bridge.",
"You are standing {wvery close to the bridge's eastern foundation{n. If you go east you will be back on solid ground ...") "You are standing {wvery close to the bridge's eastern foundation{n. If you go east you will be back on solid ground ...")
moods = ("The bridge sways in the wind.", "The hanging bridge creaks dangerously.", BRIDGE_MOODS = ("The bridge sways in the wind.", "The hanging bridge creaks dangerously.",
"You clasp the ropes firmly as the bridge sways and creaks under you.", "You clasp the ropes firmly as the bridge sways and creaks under you.",
"From the castle you hear a distant howling sound, like that of a large dog or other beast.", "From the castle you hear a distant howling sound, like that of a large dog or other beast.",
"The bridge creaks under your feet. Those planks does not seem very sturdy.", "The bridge creaks under your feet. Those planks does not seem very sturdy.",
@ -522,37 +543,60 @@ class CmdLookBridge(Command):
"A gust of wind causes the bridge to sway precariously.", "A gust of wind causes the bridge to sway precariously.",
"Under your feet a plank comes loose, tumbling down. For a moment you dangle over the abyss ...", "Under your feet a plank comes loose, tumbling down. For a moment you dangle over the abyss ...",
"The section of rope you hold onto crumble in your hands, parts of it breaking apart. You sway trying to regain balance.") "The section of rope you hold onto crumble in your hands, parts of it breaking apart. You sway trying to regain balance.")
message = "{c%s{n\n" % self.obj.key + messages[bridge_position] + "\n" + moods[random.randint(0, len(moods) - 1)]
chars = [obj for obj in self.obj.contents if obj != self.caller and obj.has_player]
if chars:
message += "\n You see: %s" % ", ".join("{c%s{n" % char.key for char in chars)
FALL_MESSAGE = "Suddenly the plank you stand on gives way under your feet! You fall!" \
"\nYou try to grab hold of an adjoining plank, but all you manage to do is to " \
"divert your fall westwards, towards the cliff face. This is going to hurt ... " \
"\n ... The world goes dark ...\n\n" \
class CmdLookBridge(Command):
"""
looks around at the bridge.
Tutorial info:
This command assumes that the room has an Attribute
"fall_exit", a unique name or dbref to the place they end upp
if they fall off the bridge.
"""
key = 'look'
aliases = ["l"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"Looking around, including a chance to fall."
caller = self.caller
bridge_position = self.caller.db.tutorial_bridge_position
# this command is defined on the room, so we get it through self.obj
location = self.obj
# randomize the look-echo
message = "{c%s{n\n%s\n%s" % (location.key,
BRIDGE_POS_MESSAGES[bridge_position],
random.choice(BRIDGE_MOODS))
chars = [obj for obj in self.obj.get_contents(exclude=caller) if obj.has_player]
if chars:
# we create the You see: message manually here
message += "\n You see: %s" % ", ".join("{c%s{n" % char.key for char in chars)
self.caller.msg(message) self.caller.msg(message)
# there is a chance that we fall if we are on the western or central # there is a chance that we fall if we are on the western or central
# part of the bridge. # part of the bridge.
if bridge_position < 3 and random.random() < 0.05 and not self.caller.is_superuser: if bridge_position < 3 and random.random() < 0.05 and not self.caller.is_superuser:
# we fall on 5% of the times. # we fall 5% of time.
fexit = search_object(self.obj.db.fall_exit) fall_exit = search_object(self.obj.db.fall_exit)
if fexit: if fall_exit:
string = "\n Suddenly the plank you stand on gives way under your feet! You fall!" self.caller.msg("{r%s{n" % FALL_MESSAGE)
string += "\n You try to grab hold of an adjoining plank, but all you manage to do is to " self.caller.move_to(fall_exit, quiet=True)
string += "divert your fall westwards, towards the cliff face. This is going to hurt ... " # inform others on the bridge
string += "\n ... The world goes dark ...\n" self.obj.msg_contents("A plank gives way under %s's feet and " \
# note that we move silently so as to not call look hooks (this is a little trick to leave "they fall from the bridge!" % self.caller.key)
# the player with the "world goes dark ..." message, giving them ample time to read it. They
# have to manually call look to find out their new location). Thus we also call the
# at_object_leave hook manually (otherwise this is done by move_to()).
self.caller.msg("{r%s{n" % string)
self.obj.at_object_leave(self.caller, fexit)
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 # custom help command
class CmdBridgeHelp(Command): class CmdBridgeHelp(Command):
""" """
Overwritten help command Overwritten help command while on the bridge.
""" """
key = "help" key = "help"
aliases = ["h"] aliases = ["h"]
@ -579,33 +623,47 @@ class BridgeCmdSet(CmdSet):
self.add(CmdLookBridge()) self.add(CmdLookBridge())
self.add(CmdBridgeHelp()) self.add(CmdBridgeHelp())
BRIDGE_WEATHER = (
"The rain intensifies, making the planks of the bridge even more slippery.",
"A gush of wind throws the rain right in your face.",
"The rainfall eases a bit and the sky momentarily brightens.",
"The bridge shakes under the thunder of a closeby thunder strike.",
"The rain pummels you with large, heavy drops. You hear the distinct howl of a large hound in the distance.",
"The wind is picking up, howling around you and causing the bridge to sway from side to side.",
"Some sort of large bird sweeps by overhead, giving off an eery screech. Soon it has disappeared in the gloom.",
"The bridge sways from side to side in the wind.",
"Below you a particularly large wave crashes into the rocks.",
"From the ruin you hear a distant, otherwordly howl. Or maybe it was just the wind.")
class BridgeRoom(TutorialRoom): class BridgeRoom(WeatherRoom):
""" """
The bridge room implements an unsafe bridge. It also enters the player into 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. 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 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 commands related to crossing the bridge. The result is that it
take several steps to cross it, despite it being represented by only a will take several steps to cross it, despite it being represented
single room. by only a single room.
We divide the bridge into steps: We divide the bridge into steps:
self.db.west_exit - - | - - self.db.east_exit self.db.west_exit - - | - - self.db.east_exit
0 1 2 3 4 0 1 2 3 4
The position is handled by a variable stored on the player when entering The position is handled by a variable stored on the character
and giving special move commands will increase/decrease the counter when entering and giving special move commands will
until the bridge is crossed. increase/decrease the counter until the bridge is crossed.
We also has self.db.fall_exit, which points to a gathering
location to end up if we happen to fall off the bridge (used by
the CmdLookBridge command).
""" """
def at_object_creation(self): def at_object_creation(self):
"Setups the room" "Setups the room"
# this will start the weather room's ticker and tell
# it to call update_weather regularly.
super(BridgeRoom, self).at_object_creation() super(BridgeRoom, self).at_object_creation()
# at irregular intervals, this will call self.update_irregular()
self.scripts.add(tut_scripts.IrregularEvent)
# this identifies the exits from the room (should be the command # this identifies the exits from the room (should be the command
# needed to leave through that exit). These are defaults, but you # needed to leave through that exit). These are defaults, but you
# could of course also change them after the room has been created. # could of course also change them after the room has been created.
@ -614,25 +672,20 @@ class BridgeRoom(TutorialRoom):
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 = \ self.db.tutorial_info = \
"""The bridge seem large but is actually only a single room that assigns custom west/east commands.""" "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_irregular(self): def update_weather(self):
""" """
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.
""" """
strings = ( if random.random() < 80:
"The rain intensifies, making the planks of the bridge even more slippery.", # send a message most of the time
"A gush of wind throws the rain right in your face.", self.msg_contents("{w%s{n" % random.choice(BRIDGE_WEATHER))
"The rainfall eases a bit and the sky momentarily brightens.",
"The bridge shakes under the thunder of a closeby thunder strike.",
"The rain pummels you with large, heavy drops. You hear the distinct howl of a large hound in the distance.",
"The wind is picking up, howling around you and causing the bridge to sway from side to side.",
"Some sort of large bird sweeps by overhead, giving off an eery screech. Soon it has disappeared in the gloom.",
"The bridge sways from side to side in the wind.")
self.msg_contents("{w%s{n" % strings[random.randint(0, 7)])
def at_object_receive(self, character, source_location): def at_object_receive(self, character, source_location):
""" """
@ -645,13 +698,16 @@ class BridgeRoom(TutorialRoom):
wexit = search_object(self.db.west_exit) wexit = search_object(self.db.west_exit)
eexit = search_object(self.db.east_exit) eexit = search_object(self.db.east_exit)
fexit = search_object(self.db.fall_exit) fexit = search_object(self.db.fall_exit)
if not wexit or not eexit or not fexit: if not (wexit and eexit and fexit):
character.msg("The bridge's exits are not properly configured. Contact an admin. Forcing west-end placement.") character.msg("The bridge's exits are not properly configured. "\
"Contact an admin. Forcing west-end placement.")
character.db.tutorial_bridge_position = 0 character.db.tutorial_bridge_position = 0
return return
if source_location == eexit[0]: if source_location == eexit[0]:
# we assume we enter from the same room we will exit to
character.db.tutorial_bridge_position = 4 character.db.tutorial_bridge_position = 4
else: else:
# if not from the east, then from the west!
character.db.tutorial_bridge_position = 0 character.db.tutorial_bridge_position = 0
def at_object_leave(self, character, target_location): def at_object_leave(self, character, target_location):
@ -662,6 +718,10 @@ class BridgeRoom(TutorialRoom):
# clean up the position attribute # clean up the position attribute
del character.db.tutorial_bridge_position del character.db.tutorial_bridge_position
SUPERUSER_WARNING = "\nWARNING: You are playing as a superuser ({name}). Use the {quell} command to\n" \
"play without superuser privileges (many functions and puzzles ignore the \n" \
"presence of a superuser, making this mode useful for exploring things behind \n" \
"the scenes later).\n" \
#----------------------------------------------------------- #-----------------------------------------------------------
# #
@ -679,28 +739,29 @@ class IntroRoom(TutorialRoom):
properties to customize: properties to customize:
char_health - integer > 0 (default 20) char_health - integer > 0 (default 20)
""" """
def at_object_creation(self):
"""
Called when the room is first created.
"""
super(IntroRoom, self).at_object_creation()
self.db_tutorial_info = "The first room of the tutorial. " \
"This assigns the health Attribute to "\
"the player."
def at_object_receive(self, character, source_location): def at_object_receive(self, character, source_location):
""" """
Assign properties on characters Assign properties on characters
""" """
# setup # setup character for the tutorial
health = self.db.char_health health = self.db.char_health or 20
if not health:
health = 20
if character.has_player: if character.has_player:
character.db.health = health character.db.health = health
character.db.health_max = health character.db.health_max = health
if character.is_superuser: if character.is_superuser:
string = "-"*78 + "\n" \ string = "-"*78 + SUPERUSER_WARNING + "-"*78
"WARNING: You are playing as a superuser ({name}). Use the {quell} command to\n" \
"play without superuser privileges (many functions and puzzles ignore the \n" \
"presence of a superuser, making this mode useful for exploring things behind \n" \
"the scenes later).\n" \
+ "-"*78
character.msg("{r%s{n" % string.format(name=character.key, quell="{w@quell{r")) character.msg("{r%s{n" % string.format(name=character.key, quell="{w@quell{r"))
@ -716,11 +777,21 @@ class OutroRoom(TutorialRoom):
""" """
Outro room. Outro room.
One can set an attribute list "wracklist" with weapon-rack ids Called when exiting the tutorial, cleans the
in order to clear all weapon rack ids from the character. character of tutorial-related attributes.
""" """
def at_object_creation(self):
"""
Called when the room is first created.
"""
super(IntroRoom, 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 "\
"character."
def at_object_receive(self, character, source_location): def at_object_receive(self, character, source_location):
""" """
Do cleanup. Do cleanup.