From 0029232ab0a5b5a0ef3b6664a334af3da352a0f5 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 15 Feb 2015 14:04:48 +0100 Subject: [PATCH] Updated the tutorial world's rooms. --- evennia/contrib/tutorial_world/rooms.py | 593 +++++++++++++----------- 1 file changed, 332 insertions(+), 261 deletions(-) diff --git a/evennia/contrib/tutorial_world/rooms.py b/evennia/contrib/tutorial_world/rooms.py index 905d152a93..fc8bce00c7 100644 --- a/evennia/contrib/tutorial_world/rooms.py +++ b/evennia/contrib/tutorial_world/rooms.py @@ -2,12 +2,18 @@ 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 -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.contrib.tutorial_world import scripts as tut_scripts +from evennia import syscmdkeys, default_cmds 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): """ Get help during the tutorial @@ -39,7 +49,7 @@ class CmdTutorial(Command): 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. """ @@ -59,7 +69,9 @@ class CmdTutorial(Command): class TutorialRoomCmdSet(CmdSet): - "Implements the simple tutorial cmdset" + """ + Implements the simple tutorial cmdset + """ key = "tutorial_cmdset" def at_cmdset_creation(self): @@ -84,14 +96,24 @@ class TutorialRoom(DefaultRoom): #------------------------------------------------------------ # -# Weather room - scripted room -# -# The weather room is called by a script at -# irregular intervals. The script is generally useful -# and so is split out into tutorialworld.scripts. +# Weather room - room with a ticker # #------------------------------------------------------------ +# These are rainy weather strings +WEATHER_STRINGS = ( + "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.", + "The rainfall eases a bit and the sky momentarily brightens.", + "For a moment it looks like the rain is slowing, then it begins anew with renewed force.", + "The rain pummels you with large, heavy drops. You hear the rumble of thunder in the distance.", + "The wind is picking up, howling around you, throwing water droplets in your face. It's cold.", + "Bright fingers of lightning flash over the sky, moments later followed by a deafening rumble.", + "It rains so hard you can hardly see your hand in front of you. You'll soon be drenched to the bone.", + "Lightning strikes in several thundering bolts, striking the trees in the forest to your west.", + "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.") + class WeatherRoom(TutorialRoom): """ This should probably better be called a rainy room... @@ -102,47 +124,61 @@ class WeatherRoom(TutorialRoom): """ def at_object_creation(self): - "Called when object is first created." - super(WeatherRoom, self).at_object_creation() + """ + Called when object is first created. + We set up a ticker to update this room regularly. - # we use the imported IrregularEvent script - self.scripts.add(tut_scripts.IrregularEvent) + 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_irregular(self): - "create a tuple of possible texts to return." - strings = ( - "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.", - "The rainfall eases a bit and the sky momentarily brightens.", - "For a moment it looks like the rain is slowing, then it begins anew with renewed force.", - "The rain pummels you with large, heavy drops. You hear the rumble of thunder in the distance.", - "The wind is picking up, howling around you, throwing water droplets in your face. It's cold.", - "Bright fingers of lightning flash over the sky, moments later followed by a deafening rumble.", - "It rains so hard you can hardly see your hand in front of you. You'll soon be drenched to the bone.", - "Lightning strikes in several thundering bolts, striking the trees in the forest to your west.", - "You hear the distant howl of what sounds like some sort of dog or wolf.", - "Large clouds rush across the sky, throwing their load of rain over the world.") - - # get a random value so we can select one of the strings above. - # Send this to the room. - irand = random.randint(0, 15) - if irand > 10: - return # don't return anything, to add more randomness - self.msg_contents("{w%s{n" % strings[irand]) + 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 # -# 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 # 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): """ Look around in darkness @@ -150,48 +186,35 @@ class CmdLookDark(Command): Usage: look - Looks in darkness + Look around in the darkness, trying + to find something. """ key = "look" - aliases = ["l", 'feel', 'feel around', 'fiddle'] + aliases = ["l", 'feel', 'search', 'feel around', 'fiddle'] locks = "cmd:all()" help_category = "TutorialWorld" 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 - # we don't have light, grasp around blindly. - messages = ("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.") - irand = random.randint(0, 10) - if irand < len(messages): - caller.msg(messages[irand]) + + if random.random() < 0.8: + # we don't find anything + caller.msg(random.choice(DARK_MESSAGES)) else: - # check so we don't already carry a lightsource. - carried_lights = [obj for obj in caller.contents - if utils.inherits_from(obj, LightSource)] - if carried_lights: - string = "You don't want to stumble around in blindness anymore. You already found what you need. Let's get light already!" - caller.msg(string) - return - #if we are lucky, we find the light source. - lightsources = [obj for obj in self.obj.contents - if utils.inherits_from(obj, LightSource)] - if lightsources: - lightsource = lightsources[0] + # we could have found something! + if any(obj for obj in caller.contents if utils.inherits_from(obj, LightSource)): + # we already carry a LightSource object. + caller.msg(ALREADY_LIGHTSOURCE) else: - # create the light source from scratch. - lightsource = create_object(LightSource, key="splinter") - lightsource.location = caller - 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) + # don't have a light source, create a new one. + create_object(LightSource, key="splinter", location=caller) + caller.msg(FOUND_LIGHTSOURCE) class CmdDarkHelp(Command): @@ -203,20 +226,23 @@ class CmdDarkHelp(Command): help_category = "TutorialWorld" def func(self): - "Implements the help command." - string = "Can't help you until you find some light! Try feeling around for something to burn." - string += " You cannot give up even if you don't find anything right away." + """ + Replace the the help command with a not-so-useful help + """ + 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) -# 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): - "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()" def func(self): @@ -225,79 +251,25 @@ class CmdDarkNoMatch(Command): 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" - mergetype = "Replace" # completely remove all other commands + mergetype = "Replace" def at_cmdset_creation(self): - "populates the cmdset." + "populate the cmdset." self.add(CmdTutorial()) self.add(CmdLookDark()) self.add(CmdDarkHelp()) self.add(CmdDarkNoMatch()) - self.add(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) + self.add(default_cmds.CmdSay) class DarkRoom(TutorialRoom): @@ -305,52 +277,86 @@ class DarkRoom(TutorialRoom): A dark room. This tries to start the DarkState script on all objects entering. The script is responsible for making sure it is 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): - "Called when object is first created." + """ + Called when object is first created. + """ super(DarkRoom, self).at_object_creation() self.db.tutorial_info = "This is a room with custom command sets on itself." - # this variable is set by the scripts. It makes for an easy flag to - # look for by other game elements (such as the crumbling wall in - # the tutorial) - self.db.is_dark = True # 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 - sure scripts are synced. - """ - if character.has_player: - if not self.is_lit() and not character.is_superuser: - character.cmdset.add(DarkCmdSet) - if character.db.health and character.db.health <= 0: - # heal character coming here from being defeated by mob. - health = character.db.health_max - if not health: - health = 20 - character.db.health = health - self.scripts.validate() + Checks if the given object carries anything that gives light. - def at_object_leave(self, character, target_location): + 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. """ - In case people leave with the light, we make sure to update the - states accordingly. - """ - character.cmdset.delete(DarkCmdSet) # in case we are teleported away - self.scripts.validate() + return any(obj for obj in obj.contents if obj.db.is_giving_light) + def _heal(self, character): + """ + Heal a character. + """ + health = character.db.health_max or 20 + character.db.health = health + + def check_light_state(self): + """ + This method checks if there are any light sources in the room. + 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. + """ + if any(self._carries_light(obj) for obj in self.contents): + # 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 character.msg("Superuser block: You would have been teleported to %s." % results[0]) return - # teleport + # the teleporter room's desc should give the 'teleporting message'. character.execute_cmd("look") - character.location = results[0] # stealth move - character.location.at_object_receive(character, self) + # teleport quietly to the new place + character.move_to(results[0], quiet=True) + #------------------------------------------------------------ # @@ -437,7 +444,19 @@ class TeleportRoom(TutorialRoom): 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" aliases = ["e"] @@ -445,7 +464,7 @@ class CmdEast(Command): help_category = "TutorialWorld" def func(self): - "move forward" + "move one step eastwards" caller = self.caller 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.") return 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.execute_cmd("look") @@ -467,7 +488,19 @@ class CmdEast(Command): # go back across the bridge 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" aliases = ["w"] @@ -475,13 +508,13 @@ class CmdWest(Command): help_category = "TutorialWorld" def func(self): - "move forward" + "move one step westwards" caller = self.caller bridge_step = max(-1, caller.db.tutorial_bridge_position - 1) 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. wexit = search_object(self.obj.db.west_exit) if wexit: @@ -490,13 +523,40 @@ class CmdWest(Command): caller.msg("No west exit was found for this room. Contact an admin.") return 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") +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 ...", + "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.", + "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 ...") +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.", + "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.", + "Far below you the ocean roars and throws its waves against the cliff, as if trying its best to reach you.", + "Parts of the bridge come loose behind you, falling into the chasm far below!", + "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 ...", + "The section of rope you hold onto crumble in your hands, parts of it breaking apart. You sway trying to regain balance.") + +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"] @@ -505,54 +565,38 @@ class CmdLookBridge(Command): 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)) - - 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.", - "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.", - "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.", - "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.", - "The bridge creaks under your feet. Those planks does not seem very sturdy.", - "Far below you the ocean roars and throws its waves against the cliff, as if trying its best to reach you.", - "Parts of the bridge come loose behind you, falling into the chasm far below!", - "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 ...", - "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] + 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) # there is a chance that we fall if we are on the western or central # part of the bridge. if bridge_position < 3 and random.random() < 0.05 and not self.caller.is_superuser: - # we fall on 5% of the times. - fexit = search_object(self.obj.db.fall_exit) - if fexit: - string = "\n Suddenly the plank you stand on gives way under your feet! You fall!" - string += "\n You try to grab hold of an adjoining plank, but all you manage to do is to " - string += "divert your fall westwards, towards the cliff face. This is going to hurt ... " - string += "\n ... The world goes dark ...\n" - # note that we move silently so as to not call look hooks (this is a little trick to leave - # 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) + # we fall 5% of time. + fall_exit = search_object(self.obj.db.fall_exit) + if fall_exit: + self.caller.msg("{r%s{n" % FALL_MESSAGE) + self.caller.move_to(fall_exit, quiet=True) + # inform others on the bridge + self.obj.msg_contents("A plank gives way under %s's feet and " \ + "they fall from the bridge!" % self.caller.key) # custom help command class CmdBridgeHelp(Command): """ - Overwritten help command + Overwritten help command while on the bridge. """ key = "help" aliases = ["h"] @@ -579,33 +623,47 @@ class BridgeCmdSet(CmdSet): self.add(CmdLookBridge()) 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 a state where they get new commands so as to try to cross the bridge. We want this to result in the player getting a special set of - commands related to crossing the bridge. The result is that it will - take several steps to cross it, despite it being represented by only a - single room. + commands related to crossing the bridge. The result is that it + will take several steps to cross it, despite it being represented + by only a single room. - We divide the bridge into steps: + We divide the bridge into steps: self.db.west_exit - - | - - self.db.east_exit 0 1 2 3 4 - The position is handled by a variable stored on the player when entering - and giving special move commands will increase/decrease the counter - until the bridge is crossed. + The position is handled by a variable stored on the character + when entering and giving special move commands will + 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): "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() - - # 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 # needed to leave through that exit). These are defaults, but you # could of course also change them after the room has been created. @@ -614,25 +672,20 @@ class BridgeRoom(TutorialRoom): 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 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 over the bridge a little more interesting. """ - strings = ( - "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.") - self.msg_contents("{w%s{n" % strings[random.randint(0, 7)]) + if random.random() < 80: + # send a message most of the time + self.msg_contents("{w%s{n" % random.choice(BRIDGE_WEATHER)) def at_object_receive(self, character, source_location): """ @@ -645,13 +698,16 @@ class BridgeRoom(TutorialRoom): wexit = search_object(self.db.west_exit) eexit = search_object(self.db.east_exit) fexit = search_object(self.db.fall_exit) - if not wexit or not eexit or not fexit: - character.msg("The bridge's exits are not properly configured. Contact an admin. Forcing west-end placement.") + 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.db.tutorial_bridge_position = 0 return if source_location == eexit[0]: + # we assume we enter from the same room we will exit to character.db.tutorial_bridge_position = 4 else: + # if not from the east, then from the west! character.db.tutorial_bridge_position = 0 def at_object_leave(self, character, target_location): @@ -662,6 +718,10 @@ class BridgeRoom(TutorialRoom): # clean up the position attribute 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: 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): """ Assign properties on characters """ - # setup - health = self.db.char_health - if not health: - health = 20 + # setup character for the tutorial + health = self.db.char_health or 20 if character.has_player: character.db.health = health character.db.health_max = health if character.is_superuser: - string = "-"*78 + "\n" \ - "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 + string = "-"*78 + SUPERUSER_WARNING + "-"*78 character.msg("{r%s{n" % string.format(name=character.key, quell="{w@quell{r")) @@ -716,11 +777,21 @@ class OutroRoom(TutorialRoom): """ Outro room. - One can set an attribute list "wracklist" with weapon-rack ids - in order to clear all weapon rack ids from the character. + Called when exiting the tutorial, cleans the + 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): """ Do cleanup.