Fixed the ending of the tutorial and a bug in the spawner.

This commit is contained in:
Griatch 2015-02-22 17:45:48 +01:00
parent d1cd9da6bf
commit 3db3d83d4c
7 changed files with 467 additions and 456 deletions

View file

@ -171,8 +171,8 @@ def get_and_merge_cmdsets(caller, session, player, obj,
# Gather all cmdsets stored on objects in the room and
# also in the caller's inventory and the location itself
local_objlist = yield (location.contents_get(exclude=obj) +
obj.contents +
[location])
obj.contents + [location])
local_objlist = [o for o in local_objlist if not o._is_deleted]
for lobj in local_objlist:
try:
# call hook in case we need to do dynamic changing to cmdset
@ -205,6 +205,8 @@ def get_and_merge_cmdsets(caller, session, player, obj,
yield obj.at_cmdset_get()
except Exception:
logger.log_trace()
_msg_err(caller, _ERROR_CMDSETS)
raise ErrorReported
try:
returnValue(obj.cmdset.current)
except AttributeError:

View file

@ -38,14 +38,14 @@
# | +--------+ +--------+ +--------+ +---+----+
# | \ |
# ++---------+ \ +--------+ +--------+ +---+----+
# |intro | \ |cell | |trap/ |temple |
# o--+ 01 | \| 08 +----+ fall | | 13 |
# | | | | /| 15 | | |
# |intro | \ |cell | | | |temple |
# o--+ 01 | \| 08 +----+ trap | | 13 |
# | | | | /| | | |
# +----+-----+ +--------+ / +--+-+-+-+ +---+----+
# | / | | | |
# +----+-----+ +--------+/ +--+-+-+---------+----+
# |outro | |tomb | |antechamber |
# o--+ 17 +----------+ 16 | | 14 |
# o--+ 16 +----------+ 15 | | 14 |
# | | | | | |
# +----------+ +--------+ +---------------------+
#
@ -826,8 +826,11 @@ archway
#
@set obelisk/get_err_msg = It's way too heavy for anyone to move.
#
# (the obelisk describes itself, so we need no do it here)
# Set the puzzle clues on the obelisk. The order should correspond
# to the ids later checked by the antechamber puzzle.
#
@set obelisk/puzzle_descs = ("You can briefly make out the image of {ba woman with a blue bird{n.", "You for a moment see the visage of {ba woman on a horse{n.", "For the briefest moment you make out an engraving of {ba regal woman wearing a crown{n.", "You think you can see the outline of {ba flaming shield{n in the stone.", "The surface for a moment seems to portray {ba sharp-faced woman with white hair{n.")
# Create the mobile. This is its start location.
@create/drop Ghostly apparition;ghost;apparition;fog : tutorial_world.mob.Mob
#
@ -874,6 +877,7 @@ archway
#
@set ghost/hit_msg =
The ghostly apparition howls and writhes, shifts and shivers.
#
@set ghost/death_msg =
After the last strike, the ghostly apparition seems to collapse
inwards. It fades and becomes one with the mist. Its howls rise to a
@ -1028,7 +1032,7 @@ stairs down
#
@dig Blue bird tomb
: tutorial_world.rooms.TeleportRoom
= Tomb with stone bird;bird;blue;stone
= Blue bird tomb;bird;blue;stone
#
@desc Blue bird tomb =
The entrance to this tomb is decorated with a very lifelike blue bird.
@ -1037,9 +1041,9 @@ Blue bird tomb
#
@set here/puzzle_value = 0
#
@set here/failure_teleport_to = tut#15
@set here/failure_teleport_to = tut#08
#
@set here/success_teleport_to = tut#16
@set here/success_teleport_to = tut#15
#
@set here/failure_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
@ -1059,7 +1063,7 @@ Blue bird tomb
The air is damp. Where are you?
#
@set here/success_teleport_to =
@set here/success_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
make out a coffin in front of you in the gloom.
@ -1072,7 +1076,7 @@ Blue bird tomb
#
@dig Tomb of woman on horse
: tutorial_world.rooms.TeleportRoom
= Tomb with statue of riding woman;horse;riding;
= Tomb of woman on horse;horse;riding;
#
@desc Tomb of woman on horse =
The entrance to this tomb depicts a scene of a strong
@ -1084,9 +1088,9 @@ Tomb of woman on horse
#
@set here/puzzle_value = 1
#
@set here/failure_teleport_to = tut#15
@set here/failure_teleport_to = tut#08
#
@set here/success_teleport_to = tut#16
@set here/success_teleport_to = tut#15
#
@set here/failure_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
@ -1106,7 +1110,7 @@ Tomb of woman on horse
The air is damp. Where are you?
#
@set here/success_teleport_to =
@set here/success_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
make out a coffin in front of you in the gloom.
@ -1119,7 +1123,7 @@ Tomb of woman on horse
#
@dig Tomb of the crowned queen
: tutorial_world.rooms.TeleportRoom
= Tomb with statue of a crowned queen;crown;queen
= Tomb of the crowned queen;crown;queen
#
@desc Tomb of the crowned queen =
The entrance to this tomb shows a beautiful mural of a queen ruling
@ -1130,9 +1134,9 @@ Tomb of the crowned queen
#
@set here/puzzle_value = 2
#
@set here/failure_teleport_to = tut#15
@set here/failure_teleport_to = tut#08
#
@set here/success_teleport_to = tut#16
@set here/success_teleport_to = tut#15
#
@set here/failure_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
@ -1152,7 +1156,7 @@ Tomb of the crowned queen
The air is damp. Where are you?
#
@set here/success_teleport_to =
@set here/success_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
make out a coffin in front of you in the gloom.
@ -1165,9 +1169,7 @@ Tomb of the crowned queen
#
@dig Tomb of the shield
: tutorial_world.rooms.TeleportRoom
= Tomb with shield of arms;shield
#
Tomb of the shield
= Tomb of the shield;shield
#
@desc Tomb of the shield =
This tomb shows a warrior woman fighting shadowy creatures from the
@ -1175,11 +1177,13 @@ top of a hill. Her sword lies broken on the ground before her but she
fights on with her battered shield - the scene depicts her just as she
rams the shield into an enemy in wild desperation.
#
Tomb of the shield
#
@set here/puzzle_value = 3
#
@set here/failure_teleport_to = tut#15
@set here/failure_teleport_to = tut#08
#
@set here/success_teleport_to = tut#16
@set here/success_teleport_to = tut#15
#
@set here/failure_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
@ -1199,7 +1203,7 @@ rams the shield into an enemy in wild desperation.
The air is damp. Where are you?
#
@set here/success_teleport_to =
@set here/success_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
make out a coffin in front of you in the gloom.
@ -1212,7 +1216,7 @@ rams the shield into an enemy in wild desperation.
#
@dig Tomb of the hero
: tutorial_world.rooms.TeleportRoom
= Tomb depicting a heroine fighting a monster;knight;hero;monster;beast
= Tomb of the hero;knight;hero;monster;beast
#
@desc Tomb of the hero =
The entrance to this tomb shows a mural of an aging woman in a
@ -1225,9 +1229,9 @@ Tomb of the hero
#
@set here/puzzle_value = 4
#
@set here/failure_teleport_to = tut#15
@set here/failure_teleport_to = tut#08
#
@set here/success_teleport_to = tut#16
@set here/success_teleport_to = tut#15
#
@set here/failure_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
@ -1247,7 +1251,7 @@ Tomb of the hero
The air is damp. Where are you?
#
@set here/success_teleport_to =
@set here/success_teleport_msg =
The tomb is dark. You fumble your way through it. You think you can
make out a coffin in front of you in the gloom.
@ -1269,14 +1273,14 @@ Tomb of the hero
#
#------------------------------------------------------------
#
@dig/teleport Ancient tomb;tut#16
@dig/teleport Ancient tomb;tut#15
: tutorial_world.rooms.TutorialRoom
= ,back to antechamber;antechamber;back
#
@desc
Apart from the ornate sarcophagus, the tomb is bare from extra decorations.
This is the resting place of a warrior with little patience for
glamour and trinkets.
glamour and trinkets. You have reached the end of your quest.
#
@set here/tutorial_info =
Congratulations, you have reached the end of this little tutorial
@ -1330,7 +1334,7 @@ Tomb of the hero
#
#------------------------------------------------------------
#
@dig End of tutorial;end;tut#17
@dig End of tutorial;end;tut#16
: tutorial_world.rooms.OutroRoom
= Exit tutorial;exit;end
#
@ -1341,7 +1345,7 @@ Tomb of the hero
@lock Exit tutorial = view:holds(rack_sarcophagus) ; traverse:holds(rack_sarcophagus)
#
# to tutorial outro
@tel tut#17
@tel tut#16
#
# we want to clear the weapon-rack ids on the character when exiting.
@set here/wracklist = ["rack_barrel", "rack_sarcophagus"]

View file

@ -52,7 +52,6 @@ class TutorialObject(DefaultObject):
"Called when the object is first created."
super(TutorialObject, self).at_object_creation()
self.db.tutorial_info = "No tutorial info is available for this object."
#self.db.last_reset = time.time()
def reset(self):
"Resets the object, whatever that may mean."
@ -171,7 +170,7 @@ class CmdClimb(Command):
ostring = "You climb %s. Having looked around, you climb down again." % self.obj.name
self.caller.msg(ostring)
# set a tag on the caller to remember that we climbed.
self.caller.tags.add("tutorial_climbed_tree")
self.caller.tags.add("tutorial_climbed_tree", category="tutorial_world")
class CmdSetClimbable(CmdSet):
@ -205,21 +204,22 @@ class Climbable(TutorialObject):
#
#------------------------------------------------------------
OBELISK_DESCS = ("You can briefly make out the image of {ba woman with a blue bird{n.",
"You for a moment see the visage of {ba woman on a horse{n.",
"For the briefest moment you make out an engraving of {ba regal woman wearing a crown{n.",
"You think you can see the outline of {ba flaming shield{n in the stone.",
"The surface for a moment seems to portray {ba woman fighting a beast{n.")
class Obelisk(TutorialObject):
"""
This object changes its description randomly.
This object changes its description randomly, and which is shown
determines which order "clue id" is stored on the Character for
future puzzles.
Important Attribute:
puzzle_descs (list): list of descriptions. One of these is
"""
def at_object_creation(self):
"Called when object is created."
super(Obelisk, self).at_object_creation()
self.db.tutorial_info = "This object changes its desc randomly, and makes sure to remember which one you saw."
self.db.puzzle_descs = ["You see a normal stone slab"]
# make sure this can never be picked up
self.locks.add("get:false()")
@ -229,11 +229,12 @@ class Obelisk(TutorialObject):
of the object. We overload it with our own version.
"""
# randomly get the index for one of the descriptions
clueindex = random.randint(0, len(OBELISK_DESCS) - 1)
descs = self.db.puzzle_descs
clueindex = random.randint(0, len(descs) - 1)
# set this description, with the random extra
string = "The surface of the obelisk seem to waver, shift and writhe under your gaze, with " \
"different scenes and structures appearing whenever you look at it. "
self.db.desc = string + OBELISK_DESCS[clueindex]
self.db.desc = string + descs[clueindex]
# remember that this was the clue we got. The Puzzle room will
# look for this later to determine if you should be teleported
# or not.
@ -1051,13 +1052,13 @@ class WeaponRack(TutorialObject):
pulling weapons from it indefinitely.
"""
rack_id = self.db.rack_id
if caller.tags.get(rack_id):
if caller.tags.get(rack_id, category="tutorial_world"):
caller.msg(self.db.no_more_weapons_msg)
else:
prototype = random.choice(self.db.available_weapons)
# use the spawner to create a new Weapon from the
# spawner dictionary, tag the caller
wpn = spawn(WEAPON_PROTOTYPES[prototype], prototype_parents=WEAPON_PROTOTYPES)[0]
caller.tags.add(rack_id)
caller.tags.add(rack_id, category="tutorial_world")
wpn.location = caller
caller.msg(self.db.get_weapon_msg % wpn.key)

View file

@ -265,10 +265,6 @@ class TutorialRoom(DefaultRoom):
else:
self.db.details = {detailkey.lower(): description}
def reset(self):
"Can be called by the tutorial runner."
pass
#------------------------------------------------------------
#
@ -331,6 +327,347 @@ class WeatherRoom(TutorialRoom):
self.msg_contents("{w%s{n" % random.choice(WEATHER_STRINGS))
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" \
#-----------------------------------------------------------
#
# Intro Room - unique room
#
# This room marks the start of the tutorial. It sets up properties on
# the player char that is needed for the tutorial.
#
#------------------------------------------------------------
class IntroRoom(TutorialRoom):
"""
Intro room
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 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 + SUPERUSER_WARNING + "-"*78
character.msg("{r%s{n" % string.format(name=character.key, quell="{w@quell{r"))
#------------------------------------------------------------
#
# Bridge - unique room
#
# Defines a special west-eastward "bridge"-room, a large room it takes
# several steps to cross. It is complete with custom commands and a
# chance of falling off the bridge. This room has no regular exits,
# instead the exiting are handled by custom commands set on the player
# upon first entering the room.
#
# Since one can enter the bridge room from both ends, it is
# divided into five steps:
# westroom <- 0 1 2 3 4 -> eastroom
#
#------------------------------------------------------------
class CmdEast(Command):
"""
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 Attributes
- tutorial_bridge_posistion: the current position on
on the bridge, 0 - 4.
"""
key = "east"
aliases = ["e"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"move one step eastwards"
caller = self.caller
bridge_step = min(5, caller.db.tutorial_bridge_position + 1)
if bridge_step > 4:
# we have reached the far east end of the bridge.
# Move to the east room.
eexit = search_object(self.obj.db.east_exit)
if eexit:
caller.move_to(eexit[0])
else:
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")
# go back across the bridge
class CmdWest(Command):
"""
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"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"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.
# Move to the west room.
wexit = search_object(self.obj.db.west_exit)
if wexit:
caller.move_to(wexit[0])
else:
caller.msg("No west 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 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"]
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.contents_get(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 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[0], 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 while on the bridge.
"""
key = "help"
aliases = ["h"]
locks = "cmd:all()"
help_category = "Tutorial world"
def func(self):
"Implements the command."
string = "You are trying hard not to fall off the bridge ..."
string += "\n\nWhat you can do is trying to cross the bridge {weast{n "
string += "or try to get back to the mainland {wwest{n)."
self.caller.msg(string)
class BridgeCmdSet(CmdSet):
"This groups the bridge commands. We will store it on the room."
key = "Bridge commands"
priority = 1 # this gives it precedence over the normal look/help commands.
def at_cmdset_creation(self):
"Called at first cmdset creation"
self.add(CmdTutorial())
self.add(CmdEast())
self.add(CmdWest())
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(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.
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 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()
# 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.
self.db.west_exit = "cliff"
self.db.east_exit = "gate"
self.db.fall_exit = "cliffledge"
# add the cmdset on the room.
self.cmdset.add_default(BridgeCmdSet)
def update_weather(self, *args, **kwargs):
"""
This is called at irregular intervals and makes the passage
over the bridge a little more interesting.
"""
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):
"""
This hook is called by the engine whenever the player is moved
into this room.
"""
if character.has_player:
# we only run this if the entered object is indeed a player object.
# check so our east/west exits are correctly defined.
wexit = search_object(self.db.west_exit)
eexit = search_object(self.db.east_exit)
fexit = search_object(self.db.fall_exit)
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):
"""
This is triggered when the player leaves the bridge room.
"""
if character.has_player:
# clean up the position attribute
del character.db.tutorial_bridge_position
#------------------------------------------------------------------------------
#
# Dark Room - a room with states
@ -341,6 +678,7 @@ class WeatherRoom(TutorialRoom):
#
#------------------------------------------------------------------------------
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!",
@ -357,6 +695,7 @@ ALREADY_LIGHTSOURCE = "You don't want to stumble around in blindness anymore. Yo
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
@ -542,9 +881,10 @@ class DarkRoom(TutorialRoom):
"""
self.check_light_state()
#------------------------------------------------------------
#
# Teleport room - puzzle room
# Teleport room - puzzles solution
#
# This is a sort of puzzle room that requires a certain
# attribute on the entering character to be the same as
@ -557,6 +897,7 @@ class DarkRoom(TutorialRoom):
#
#------------------------------------------------------------
class TeleportRoom(TutorialRoom):
"""
Teleporter - puzzle room.
@ -592,7 +933,7 @@ class TeleportRoom(TutorialRoom):
# only act on player characters.
return
# determine if the puzzle is a success or not
is_success = character.db.puzzle_clue == self.db.puzzle_value
is_success = str(character.db.puzzle_clue) == str(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)
@ -600,7 +941,7 @@ class TeleportRoom(TutorialRoom):
# we cannot move anywhere since no valid target was found.
print "no valid teleport target for %s was found." % teleport_to
return
if character.player.is_superuser:
if character.is_superuser:
# superusers don't get teleported
character.msg("Superuser block: You would have been teleported to %s." % results[0])
return
@ -610,347 +951,12 @@ class TeleportRoom(TutorialRoom):
else:
character.msg(self.db.failure_teleport_msg)
# teleport quietly to the new place
character.move_to(results[0], quiet=True)
character.move_to(results[0], quiet=True, move_hooks=False)
#------------------------------------------------------------
#
# Bridge - unique room
#
# Defines a special west-eastward "bridge"-room, a large room it takes
# several steps to cross. It is complete with custom commands and a
# chance of falling off the bridge. This room has no regular exits,
# instead the exiting are handled by custom commands set on the player
# upon first entering the room.
#
# Since one can enter the bridge room from both ends, it is
# divided into five steps:
# westroom <- 0 1 2 3 4 -> eastroom
#
#------------------------------------------------------------
class CmdEast(Command):
"""
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 Attributes
- tutorial_bridge_posistion: the current position on
on the bridge, 0 - 4.
"""
key = "east"
aliases = ["e"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"move one step eastwards"
caller = self.caller
bridge_step = min(5, caller.db.tutorial_bridge_position + 1)
if bridge_step > 4:
# we have reached the far east end of the bridge.
# Move to the east room.
eexit = search_object(self.obj.db.east_exit)
if eexit:
caller.move_to(eexit[0])
else:
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")
# go back across the bridge
class CmdWest(Command):
"""
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"]
locks = "cmd:all()"
help_category = "TutorialWorld"
def func(self):
"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.
# Move to the west room.
wexit = search_object(self.obj.db.west_exit)
if wexit:
caller.move_to(wexit[0])
else:
caller.msg("No west 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 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"]
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.contents_get(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 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[0], 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 while on the bridge.
"""
key = "help"
aliases = ["h"]
locks = "cmd:all()"
help_category = "Tutorial world"
def func(self):
"Implements the command."
string = "You are trying hard not to fall off the bridge ..."
string += "\n\nWhat you can do is trying to cross the bridge {weast{n "
string += "or try to get back to the mainland {wwest{n)."
self.caller.msg(string)
class BridgeCmdSet(CmdSet):
"This groups the bridge commands. We will store it on the room."
key = "Bridge commands"
priority = 1 # this gives it precedence over the normal look/help commands.
def at_cmdset_creation(self):
"Called at first cmdset creation"
self.add(CmdTutorial())
self.add(CmdEast())
self.add(CmdWest())
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(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.
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 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()
# 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.
self.db.west_exit = "cliff"
self.db.east_exit = "gate"
self.db.fall_exit = "cliffledge"
# add the cmdset on the room.
self.cmdset.add_default(BridgeCmdSet)
def update_weather(self, *args, **kwargs):
"""
This is called at irregular intervals and makes the passage
over the bridge a little more interesting.
"""
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):
"""
This hook is called by the engine whenever the player is moved
into this room.
"""
if character.has_player:
# we only run this if the entered object is indeed a player object.
# check so our east/west exits are correctly defined.
wexit = search_object(self.db.west_exit)
eexit = search_object(self.db.east_exit)
fexit = search_object(self.db.fall_exit)
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):
"""
This is triggered when the player leaves the bridge room.
"""
if character.has_player:
# 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" \
#-----------------------------------------------------------
#
# Intro Room - unique room
#
# This room marks the start of the tutorial. It sets up properties on
# the player char that is needed for the tutorial.
#
#------------------------------------------------------------
class IntroRoom(TutorialRoom):
"""
Intro room
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 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 + SUPERUSER_WARNING + "-"*78
character.msg("{r%s{n" % string.format(name=character.key, quell="{w@quell{r"))
#------------------------------------------------------------
#
# Outro room - unique room
# Outro room - unique exit room
#
# Cleans up the character from all tutorial-related properties.
#
@ -989,6 +995,4 @@ class OutroRoom(TutorialRoom):
del character.db.puzzle_clue
del character.db.combat_parry_mode
del character.db.tutorial_bridge_position
for tut_obj in [obj for obj in character.contents
if utils.inherits_from(obj, TutorialObject)]:
tut_obj.reset()
character.tags.clear(category="tutorial_world")

View file

@ -557,7 +557,7 @@ class DefaultObject(ObjectDB):
obj.msg(message, from_obj=from_obj, **kwargs)
def move_to(self, destination, quiet=False,
emit_to_obj=None, use_destination=True, to_none=False):
emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True):
"""
Moves this object to a new location.
@ -568,23 +568,26 @@ class DefaultObject(ObjectDB):
function, such things are assumed to have been handled before calling
move_to.
destination: (Object) Reference to the object to move to. This
can also be an exit object, in which case the destination
property is used as destination.
quiet: (bool) If true, don't emit left/arrived messages.
emit_to_obj: (Object) object to receive error messages
use_destination (bool): Default is for objects to use the "destination"
property of destinations as the target to move to.
Turning off this keyword allows objects to move
"inside" exit objects.
to_none - allow destination to be None. Note that no hooks are run when
moving to a None location. If you want to run hooks,
run them manually (and make sure they can manage None
locations).
Args:
destination (Object): Reference to the object to move to. This
can also be an exit object, in which case the
destination property is used as destination.
quiet (bool): If true, turn off the calling of the emit hooks
(announce_move_to/from etc)
emit_to_obj (Object): object to receive error messages
use_destination (bool): Default is for objects to use the "destination"
property of destinations as the target to move to. Turning off this
keyword allows objects to move "inside" exit objects.
to_none (bool): Allow destination to be None. Note that no hooks are run when
moving to a None location. If you want to run hooks, run them manually
(and make sure they can manage None locations).
move_hooks (bool): If False, turn off the calling of move-related hooks (at_before/after_move etc)
with quiet=True, this is as quiet a move as can be done.
Returns True/False depending on if there were problems with the move.
This method may also return various error messages to the
emit_to_obj.
Returns:
result (bool): True/False depending on if there were problems with the move.
This method may also return various error messages to the
emit_to_obj.
"""
def logerr(string=""):
trc = traceback.format_exc()
@ -609,14 +612,15 @@ class DefaultObject(ObjectDB):
destination = destination.destination
# Before the move, call eventual pre-commands.
try:
if not self.at_before_move(destination):
return
except Exception:
logerr(errtxt % "at_before_move()")
#emit_to_obj.msg(errtxt % "at_before_move()")
#logger.log_trace()
return False
if move_hooks:
try:
if not self.at_before_move(destination):
return
except Exception:
logerr(errtxt % "at_before_move()")
#emit_to_obj.msg(errtxt % "at_before_move()")
#logger.log_trace()
return False
# Save the old location
source_location = self.location
@ -630,13 +634,14 @@ class DefaultObject(ObjectDB):
source_location = default_home
# Call hook on source location
try:
source_location.at_object_leave(self, destination)
except Exception:
logerr(errtxt % "at_object_leave()")
#emit_to_obj.msg(errtxt % "at_object_leave()")
#logger.log_trace()
return False
if move_hooks:
try:
source_location.at_object_leave(self, destination)
except Exception:
logerr(errtxt % "at_object_leave()")
#emit_to_obj.msg(errtxt % "at_object_leave()")
#logger.log_trace()
return False
if not quiet:
#tell the old room we are leaving
@ -667,25 +672,27 @@ class DefaultObject(ObjectDB):
#logger.log_trace()
return False
# Perform eventual extra commands on the receiving location
# (the object has already arrived at this point)
try:
destination.at_object_receive(self, source_location)
except Exception:
logerr(errtxt % "at_object_receive()")
#emit_to_obj.msg(errtxt % "at_object_receive()")
#logger.log_trace()
return False
if move_hooks:
# Perform eventual extra commands on the receiving location
# (the object has already arrived at this point)
try:
destination.at_object_receive(self, source_location)
except Exception:
logerr(errtxt % "at_object_receive()")
#emit_to_obj.msg(errtxt % "at_object_receive()")
#logger.log_trace()
return False
# Execute eventual extra commands on this object after moving it
# (usually calling 'look')
try:
self.at_after_move(source_location)
except Exception:
logerr(errtxt % "at_after_move")
#emit_to_obj.msg(errtxt % "at_after_move()")
#logger.log_trace()
return False
if move_hooks:
try:
self.at_after_move(source_location)
except Exception:
logerr(errtxt % "at_after_move")
#emit_to_obj.msg(errtxt % "at_after_move()")
#logger.log_trace()
return False
return True
def clear_exits(self):
@ -810,12 +817,6 @@ class DefaultObject(ObjectDB):
for script in _ScriptDB.objects.get_all_scripts_on_obj(self):
script.stop()
#for script in _GA(self, "scripts").all():
# script.stop()
# if self.player:
# self.player.user.is_active = False
# self.player.user.save(
# Destroy any exits to and from this room, if any
self.clear_exits()
@ -828,7 +829,6 @@ class DefaultObject(ObjectDB):
# Perform the deletion of the object
super(ObjectDB, self).delete()
return True
# methods inherited from the typeclass system
def __eq__(self, other):
@ -892,7 +892,6 @@ class DefaultObject(ObjectDB):
if cdict.get("location"):
cdict["location"].at_object_receive(self, None)
self.at_after_move(None)
if cdict.get("attributes"):
# this should be a dict of attrname:value
keys, values = cdict["attributes"].keys(), cdict["attributes"].values()

View file

@ -446,7 +446,6 @@ class TypedObject(SharedMemoryModel):
self.aliases.clear()
if hasattr(self, "nicks"):
self.nicks.clear()
self.flush_from_cache()
# scrambling properties
self.delete = self._deleted

View file

@ -128,25 +128,27 @@ def _batch_create_object(*objparams):
creation/add handlers in the following order:
(create, permissions, locks, aliases, nattributes, attributes)
Returns:
A list of created objects
objects (list): A list of created objects
"""
# bulk create all objects in one go
dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams]
# unfortunately this doesn't work since bulk_create don't creates pks;
# unfortunately this doesn't work since bulk_create doesn't creates pks;
# the result are double objects at the next stage
#dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams]
objs = []
for iobj, obj in enumerate(dbobjs):
# call all setup hooks on each object
objparam = objparams[iobj]
# setup
obj._createdict = {"pernmissions": objparam[1],
obj._createdict = {"permissions": objparam[1],
"locks": objparam[2],
"aliases": objparam[3],
"attributes": objparam[4],
"nattributes": objparam[5]}
"nattributes": objparam[4],
"attributes": objparam[5]}
# this triggers all hooks
obj.save()
objs.append(obj)