Set sail for adventure!

Big change for sailing across the sea to an Escape Room
This commit is contained in:
Howard Abrams 2025-07-07 22:34:06 -07:00
parent e9b0dd7614
commit d1542ba846
8 changed files with 663 additions and 136 deletions

View file

@ -16,7 +16,7 @@ from evennia.contrib.game_systems.gendersub import GenderCharacter
from evennia.contrib.rpg.rpsystem import ContribRPCharacter, send_emote
from evennia.prototypes.spawner import spawn
from evennia.utils import delay, logger, int2str
from evennia.utils.search import search_object
from evennia.utils.search import search_object, search_objects_by_typeclass
from utils.word_list import routput, choices
from .objects import Object
@ -411,26 +411,18 @@ class Character(Object, GenderCharacter, ContribRPCharacter):
self.db.reappear_msg.split(';;'),
self.db.appear_delay or 2)
def characters_here(self):
"""
Return a list of characters in the current location.
"""
return [char for char in
self.search("", typeclass="typeclasses.characters.Character",
location=self.location, quiet=True)
if char != self]
# Hooks to the puppets:
# Hooks to the puppets and storycubes:
def puppets_here(self):
"""
Return a list of puppets in the current location.
Only used for calling hooks on the animatronic dolls.
"""
return [puppet for puppet in
self.search("", typeclass="typeclasses.puppets.Puppet",
location=self.location, quiet=True)
if puppet != self]
return [ puppet
for puppet in self.location.contents
if puppet.is_typeclass("typeclasses.puppets.Puppet")
or puppet.is_typeclass("typeclasses.puzzles.StoryCube")
]
def at_post_move(self, past_location, move_type="move", **kwargs):
super().at_post_move(past_location, move_type)

View file

@ -46,7 +46,7 @@ class LightSource(Object):
self.db.burntime = 60 * 3 # 3 minutes
# this is the default desc, it can of course be customized
# when created.
self.db.desc = "A tapered candle."
self.db.desc = "A resin-covered splinter of wood."
# add the Light command
# self.cmdset.add_default(CmdSetLight, persistent=True)

View file

@ -9,7 +9,7 @@ with a location in the game world (like Characters, Rooms, Exits).
"""
from re import split, match
from re import split, match, sub, IGNORECASE
from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPObject
from evennia.utils import delay, logger
@ -31,7 +31,6 @@ class ObjectParent:
return "\n"
# class Object(ObjectParent, DefaultObject):
class Object(ObjectParent, ContribRPObject):
"""
This is the root Object typeclass, representing all entities that
@ -252,6 +251,18 @@ class Object(ObjectParent, ContribRPObject):
"""True if this object has a method of a particular name."""
return hasattr(self, method_name) and callable(getattr(self, method_name))
def characters_here(self, puppets=False):
"""
Return a list of characters in the current location.
"""
return [ char
for char in self.location.contents
if char != self and
(char.is_typeclass("typeclasses.characters.Character")
or (char.is_typeclass("typeclasses.puppets.Puppet") and
puppets))
]
def delay_sequence(self, sequence_str, time_delay=1, *args):
"""Run a sequence of messages or commands with a delay.
@ -282,6 +293,11 @@ class Object(ObjectParent, ContribRPObject):
except ValueError:
return x
if self.db.current_sequence and self.db.current_sequence == sequence_str:
logger.info("Duplicate sequences. Ignoring.")
return
self.db.current_sequence = sequence_str
lines = [convert(line) for line in split(r" *;; *", sequence_str)]
pause = 0
for line in lines:
@ -290,13 +306,16 @@ class Object(ObjectParent, ContribRPObject):
else:
pause = pause + time_delay
m = match(r"(# *|gm +)(.*)", line)
m = match(r"(^# *|^gm +)(.*)", line)
if m:
logger.info(f"GM'd: {m.group(2)}")
delay(pause, self.location.msg_contents, routput(m.group(2), *args))
else:
cmd = routput(line, *args)
logger.info(f"Executing: {cmd}")
delay(pause, self.execute_cmd, cmd)
delay(pause, self.do_cmd, cmd)
delay(pause, self.attributes.remove, "current_sequence")
def at_post_move(self, source_location, move_type="move", **kwargs):
"""
@ -313,3 +332,121 @@ class Object(ObjectParent, ContribRPObject):
super().at_give(giver, getter)
if hasattr(getter, 'other_given') and callable(getter.other_given):
getter.other_given(giver, self)
class Listener:
"""
Mix in class with methods for being able to respond
to events from characters.
"""
def get_character_label(self, character):
cleaned = sub(r"|[A-z/]", "",
character.get_display_name(self))
return cleaned.split(' ')[-1]
def trigger_sequence(self, seq, character, *other_stuff):
"""
The actions associated with the trigger
{0} - character's real name
{1} - character's sdesc (from puppet's pov)
{2} - character's label, last word in sdesc
{3} - character object
"""
logger.info(f"Triggering {seq}")
self.delay_sequence(seq, 1, character.key,
character.get_display_name(self),
self.get_character_label(character),
character, *other_stuff)
def get_attribute_trigger(self, label, character):
"""
Return the attribute where 'key' is the label, and the
category can by either the 'character' or its display name.
"""
name = self.get_character_label(character)
return (self.attributes.get(key=label, category=character.key) or
self.attributes.get(key=label, category=name) or
self.attributes.get(key=label))
def move_triggers(self, label, character):
"""
Return a list of triggers matching 'label' and 'character'.
"""
seq = self.get_attribute_trigger(label, character)
if seq:
self.trigger_sequence(seq, character)
def other_arrive(self, character):
"""
Execute a command when a character arrives in the same location.
"""
self.move_triggers('arrive', character)
def other_leave(self, character):
"""
Execute a command when a character arrives in the same location.
"""
self.move_triggers('leave', character)
def say_triggers(self, label, character, speech):
trigs = self.get_attribute_trigger(label, character)
if trigs:
for _, trigger in enumerate(dict(trigs)):
seq = trigs[trigger]
if match(trigger, speech, IGNORECASE):
self.trigger_sequence(seq, character)
def other_say(self, speaker, speech):
self.say_triggers('say', speaker, speech)
def other_sayto(self, speaker, speech):
self.say_triggers('sayto', speaker, speech)
def other_given(self, giver, obj):
target = giver.get_display_name(self).split(' ')[-1]
self.execute_cmd(f"emote /Me says to /{target}, \"Thanks for the {obj.name}.\"")
def do_cmd(self, cmd):
"""
Like 'execute_cmd', but for objects.
"""
m = match(r"teleport +(.*?) *= *(.*)", cmd)
if m:
o = self.global_search(m.group(1))
d = self.global_search(m.group(2))
logger.info(f"Teleporting: {m.group(1)} {o} to {d}")
if o and d:
o.move_to(d, quiet=True)
return
m = match(r"tag_all +(.*) *", cmd)
if m:
tag = m.group(1)
logger.info(f"Tagging '{tag}'")
for c in self.characters_here(puppets=True):
# Two tags?
c.tags.add(tag)
c.tags.add(tag, category="mp")
return
logger.info(f"Can't do {cmd}, yet.")
def cleanup(self):
"""
Execute orders.
@set thing/objects_here = other_object
@set thing/objects_here = [one_object, two_object]
"""
def move_to_me(obj):
o = self.global_search(obj)
if o:
o.move_to(self, quiet=True)
objs = self.db.objects_here
if objs:
if isinstance(objs, str):
move_to_me(objs)
else:
for obj in objs:
move_to_me(obj)

View file

@ -10,10 +10,11 @@ from evennia.utils import logger
from commands.command import Command
from typeclasses.characters import Character
from typeclasses.objects import Listener
from utils.word_list import routput
class Puppet(Character):
class Puppet(Character, Listener):
"""
Special character that if not puppetable, stays put.
Perhaps responding or logging information to the GM.
@ -81,77 +82,6 @@ class Puppet(Character):
else:
return self.db.desc_unpuppeted if self.db.desc_unpuppeted else self.db.desc
# ----------------------------------------------------------------------
# TRIGGERS
def get_character_label(self, character):
cleaned = sub(r"|[A-z/]", "",
character.get_display_name(self))
return cleaned.split(' ')[-1]
def trigger_sequence(self, seq, character, *other_stuff):
"""
The actions associated with the trigger
{0} - character's real name
{1} - character's sdesc (from puppet's pov)
{2} - character's label, last word in sdesc
{3} - character object
"""
self.delay_sequence(seq, 1, character.key,
character.get_display_name(self),
self.get_character_label(character),
character, *other_stuff)
def get_attribute_trigger(self, label, character):
"""
Return the attribute where 'key' is the label, and the
category can by either the 'character' or its display name.
"""
name = self.get_character_label(character)
return (self.attributes.get(key=label, category=character.key) or
self.attributes.get(key=label, category=name) or
self.attributes.get(key=label))
def move_triggers(self, label, character):
"""
Return a list of triggers matching 'label' and 'character'.
"""
seq = self.get_attribute_trigger(label, character)
if seq:
self.trigger_sequence(seq, character)
def other_arrive(self, character):
"""
Execute a command when a character arrives in the same location.
"""
self.move_triggers('arrive', character)
def other_leave(self, character):
"""
Execute a command when a character arrives in the same location.
"""
self.move_triggers('leave', character)
def say_triggers(self, label, character, speech):
trigs = self.get_attribute_trigger(label, character)
if trigs:
for _, trigger in enumerate(dict(trigs)):
seq = trigs[trigger]
if match(trigger, speech, IGNORECASE):
self.trigger_sequence(seq, character)
def other_say(self, speaker, speech):
self.say_triggers('say', speaker, speech)
def other_sayto(self, speaker, speech):
self.say_triggers('sayto', speaker, speech)
def other_given(self, giver, obj):
target = giver.get_display_name(self).split(' ')[-1]
self.execute_cmd(f"emote /Me says to /{target}, \"Thanks for the {obj.name}.\"")
class CmdShrubSay(Command):
"""Erase and write on the shrub's chalkboard.

90
typeclasses/puzzles.py Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env python
from os.path import exists
from datetime import datetime
from random import randint
from re import split, match
from shutil import copyfile
from evennia import CmdSet
from evennia.utils import logger, delay
from commands.command import Command
from typeclasses.objects import Object, Listener
from typeclasses.characters import Character
from utils.word_list import routput
class StoryCube(Object, Listener):
"""
An invisible puppet to respond to situations.
@create/drop cube1:typeclasses.puppets.StoryCube
@desc cube1 = Description for Admins
@lock cube1 = view:false() # to make it invisible
"""
def return_appearance(self, caller):
return "What?"
class Changling(StoryCube):
"""
Object that changes appearance every time someone looks at it.
Important Attribute:
desc_prefix (string): The initial part of the description.
This can be an empty string.
desc_postfix (string): The final part of the description.
This can be an empty string.
descs (list): list of descriptions. One of these is
picked randomly when this object is looked at and its index
in the list is used as a key for to solve the puzzle.
"""
def at_object_creation(self):
"""Called when object is created."""
super().at_object_creation()
self.db.desc_prefix = ""
self.db.desc_postfix = ""
self.db.descs = ["You don't see anything particular."]
def return_appearance(self, caller):
"""
This hook is called by the look command to get the description
of the object. We overload it with our own version.
"""
def optional(text, prefix=False):
if text and text != "":
return ("" if prefix else " ") + \
routput(text) + \
(" " if prefix else "")
else:
return ""
# randomly get the index for one of the descriptions
descs = self.db.descs
clueindex = randint(0, len(descs) - 1)
# set this description, with the random extra
self.db.desc = optional(self.db.desc_prefix, True) + \
descs[clueindex] + optional(self.db.desc_postfix)
# Might as well remember the last view we got, as it could be
# a clue we could refer to.
caller.attributes.add(key=f"{self.key}_view_index",
value=clueindex)
# call the parent function as normal (this will use
# the new desc Attribute we just set)
# return super().return_appearance(caller)
return self.db.desc
def at_object_receive(self, obj, giver, **kwargs):
if obj.key == 'conch':
return super().at_object_receive(obj, giver)
obj.move_to(self.location, quiet=True, move_type="drop")
delay(2, giver.announce_action,
f"Soon after $you() $conj(place) a {obj.name} in the open cavity of the obelisk, it falls to the floor.")
return True

View file

@ -177,10 +177,6 @@ class DarkRoom(Room):
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 is meant as a sort of catch-all, we also make sure
to heal characters ending up here.
"""
def at_object_creation(self):
@ -249,7 +245,6 @@ class DarkRoom(Room):
"""
if moved_obj.has_account:
# a puppeted object, that is, a Character
self._heal(moved_obj)
# in case the new guy carries light with them
self.check_light_state()

View file

@ -6,6 +6,7 @@ from re import match
from evennia import CmdSet
from evennia.utils import logger, delay, int2str, search
from typeclasses.puzzles import StoryCube
from typeclasses.scripts import Script
from typeclasses.objects import Object
from commands.command import Command
@ -18,6 +19,20 @@ DEFAULT_ISLE = "Lonely Island" # The 'default' destination
ISLE_EXIT = "shore"
class HornControl(StoryCube):
"""
An invisible puppet to take care of horn's location.
@create/drop hornctl:typeclasses.puppets.StoryCube
@desc hornctl = Moves horn to room when character arrives.
@lock hornctl = view:false() # to make it invisible
@set hornctl/horn_object = mist horn
@set hornctl/arrive = "15 ;; gm The mists gather over the sea. ;; 18 ;; gm The swirling mists coalesce into a shape of a curved horn. ;;
20 ;; The curved horn, now physical, drops to the dock. ;; @teleport mist horn = here
"""
class CmdBlow(Command):
"""
Blow a horn.

View file

@ -1,4 +1,80 @@
# The Boat
# [[file:../../../projects/mud-adventure.org::*The Mist Horn][The Mist Horn:1]]
@create Mist Horn:typeclasses.sailing.CallingHorn
# The Mist Horn:1 ends here
# And a description if someone looks at:
# [[file:../../../projects/mud-adventure.org::*The Mist Horn][The Mist Horn:2]]
@desc horn = While physical, this curved horn made from the sea mist, has an amorphous quality. Wonder what would happen if you |gblow|n this horn?
# The Mist Horn:2 ends here
# The horn will come and go, and this is controlled by a StoryCube:
# [[file:../../../projects/mud-adventure.org::*The Mist Horn][The Mist Horn:3]]
@teleport mp06
#
@create/drop mp06ctl:typeclasses.puzzles.StoryCube
#
@desc mp06ctl = Moves horn to room when character arrives.
#
# To make it invisible, we lock the view:
@lock mp06ctl = view:false()
# The Mist Horn:3 ends here
# To make sure other players can use the horn, we need to have it return to its original state. For this, we tether it to the StoryCube, and come up with a lovely excuse why they cant have it forever:
# [[file:../../../projects/mud-adventure.org::*The Mist Horn][The Mist Horn:4]]
@lock mist horn = tethered:id(mp06ctl)
#
@set mist horn/tethered_msg = "The horn dissipates back into the mist."
# The Mist Horn:4 ends here
# When a character arrives at the Lazy Dock and wait, the horn will appear.
# [[file:../../../projects/mud-adventure.org::*The Mist Horn][The Mist Horn:5]]
@set mp06ctl/arrive = "25 ;; gm The mists gather over the sea. ;; 5 ;; gm The swirling mists coalesce into a shape of a curved horn. ;; 2 ;; teleport mist horn = Lazy Dock ;; gm The curved horn, now physical, drops to the dock."
# The Mist Horn:5 ends here
# And lets put the horn /in/ the StoryCube:
# [[file:../../../projects/mud-adventure.org::*The Mist Horn][The Mist Horn:6]]
@teleport/quiet mist horn = mp06ctl
# The Mist Horn:6 ends here
# And a cleanup command to remove the horn from wherever it is:
# [[file:../../../projects/mud-adventure.org::*The Mist Horn][The Mist Horn:7]]
@set mp06ctl/cleanup = "@teleport mist horn = me"
# The Mist Horn:7 ends here
# And cleanup:
# [[file:../../../projects/mud-adventure.org::*The Mist Horn][The Mist Horn:8]]
py me.search('mp06ctl').cleanup()
# The Mist Horn:8 ends here
# When the boat is docked, we should have a special /state/ to describe it:
@ -28,6 +104,15 @@
# Controlling the boat comes from a Script:
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:4]]
@script here = typeclasses.sailing.Boat
# The Boat:4 ends here
# The boat has three /states/:
# - =docked= (at *Lazy Dock*)
@ -35,78 +120,89 @@
# - =ashore= (at some island below)
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:4]]
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:5]]
@desc/docked here = This leaf, large enough to accommodate a few people comfortably, seems to be a strange, but steady watercraft. While next to the dock, you can disembark, but feel this boat could set sail at any minute.
[Waiting on other potential passengers]
# The Boat:4 ends here
|x[|WNote:|x Waiting on other potential passengers]|n
# The Boat:5 ends here
# And describe sailing on it.:
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:6]]
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:7]]
@desc/sailing here = This leaf, large enough to accommodate a few people comfortably, seems to be a strange, but steady watercraft.
Only eddies from the subtle wake, intrude on the lavender sea's tranquility, as the giant leaf floats along an invisible current. <morning>The morning sun peaks above the colossal trees and the snow-capped mountains beyond.</morning><afternoon>The afternoon sun peaks out briefly from misty clouds.</afternoon><evening>The evening sun begins to set on a watery horizon.</evening><night>The moon peaks out from behind wispy clouds.</night>
# The Boat:6 ends here
# The Boat:7 ends here
# And the description of the boat when ashore on an island:
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:7]]
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:8]]
@desc/ashore here = This leaf, large enough to accommodate a few people comfortably, seems to be a strange, but steady watercraft. Bobbing softly in the surf, the giant leaf bumps against the shore of the island, allowing you to disembark.
[Waiting on other potential passengers]
# The Boat:7 ends here
|x[|WNote:|x Waiting on other potential passengers]|n
# The Boat:8 ends here
# While on the boat, describe the dock:
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:8]]
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:9]]
desc dock = A lazy dock with a comfortable-looking chair and a forest of colossal trees behind it on a hill.
# The Boat:8 ends here
# The Boat:9 ends here
# And describe disembarking to the Lazy Dock:
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:9]]
@set dock/traverse_miss = "You easily disembark from the giant leaf and step onto the dock..."
# The Boat:9 ends here
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:10]]
@set dock/traverse_msg = "You easily disembark from the giant leaf and step onto the dock..."
# The Boat:10 ends here
# Disembark to describe the exits to the boat:
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:10]]
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:11]]
@teleport/quiet Lazy Dock
# The Boat:10 ends here
# The Boat:11 ends here
# Describe the leaf boat (as an exit):
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:11]]
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:12]]
@desc boat = A giant leaf, tipped on all sides, gently floats on the tranquil sea, subtly bumping against the dock. Doesn't seem to have an oar, or even a rudder, but... it appears to be a strange, but steady watercraft if one were to gather a few friends to venture to the lands beyond...
# The Boat:11 ends here
# The Boat:12 ends here
# And describe embarking:
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:12]]
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:13]]
@set boat/traverse_msg = "You step into the bowed cavity of the leaf. Seems surprisingly steady...for a leaf."
# The Boat:12 ends here
# The Boat:13 ends here
# The /exit/ to and from the Dock to the *Leaf Boat* comes and goes.
# Send the “exit” to te leaf boat into the void to be picked up later:
# [[file:../../../projects/mud-adventure.org::*The Boat][The Boat:14]]
@teleport/tonone boat
# The Boat:14 ends here
# Throne Island
# The boat should land on a distant island.
# We should come up with a *theme* for the entire island, from the “statue” to the puzzles and answers.
@ -122,7 +218,7 @@ desc dock = A lazy dock with a comfortable-looking chair and a forest of colossa
# [[file:../../../projects/mud-adventure.org::*Throne Island][Throne Island:2]]
@desc here = Vibrant green moss covers the island's gray rock. Wandering around the conifers you encounter a statue of an elegant woman seated on a large throne.
@desc here = Vibrant green moss covers the island's gray rock. Wandering around the conifers you encounter a |Ystatue|n of an elegant woman seated on a large throne.
$state(boat, A large leaf bobs invitingly in the surf.)
# Throne Island:2 ends here
@ -132,8 +228,6 @@ $state(boat, A large leaf bobs invitingly in the surf.)
# [[file:../../../projects/mud-adventure.org::*Throne Island][Throne Island:3]]
@teleport gr02
#
@open leaf boat;boat;embark = gr01
# Throne Island:3 ends here
@ -167,7 +261,7 @@ $state(boat, A large leaf bobs invitingly in the surf.)
# [[file:../../../projects/mud-adventure.org::*Throne Island][Throne Island:7]]
@set shore/traverse_msg = "You disembark from the giant leaf and you step off into the surf and onto the shore..."
@set shore/traverse_msg = "You disembark from the giant leaf to step off into the surf and onto the shore..."
# Throne Island:7 ends here
@ -179,22 +273,39 @@ $state(boat, A large leaf bobs invitingly in the surf.)
@desc shore = You seem to have arrived at some island covered in large conifer trees.
# Throne Island:8 ends here
# First Puzzle
# And a bit of clean up to leave the boat at the island.
# [[file:../../../projects/mud-adventure.org::*Throne Island][Throne Island:9]]
@teleport/tonone dock
# Throne Island:9 ends here
# And the boats “room state”:
# [[file:../../../projects/mud-adventure.org::*Throne Island][Throne Island:10]]
@roomstate shore
# Throne Island:10 ends here
# Details on the throne and the statues and maybe moss? What about sitting on the throne?
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:1]]
@teleport gr02
#
@describe statue = A massive statue of an elegant woman atop a throne. A shield rests against her knee as she hold an upright trident. In her other hand, she holds aloft an orb. Eons of moss and mildew adds the appearance of tears streaming down her face. Only a raised eyebrow and subtle smirk hints at an earlier impression. At the base of statue is a plaque.
@detail statue = A massive statue of an elegant woman atop a throne. A |Yshield|n rests against her knee as she hold an upright |Ytrident|n. In her other hand, she holds aloft an |Yorb|n. Eons of moss and mildew adds the appearance of tears streaming down her face. Only a raised eyebrow and subtle smirk hints at an earlier impression. At the base of statue is a |Yplaque|n.
# First Puzzle:1 ends here
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:2]]
@describe trident = Held loosely with one hand. Unclear if the sculptor intended for the middle tine to be missing, or if it broke centuries ago.
@detail trident = Held loosely with one hand. Unclear if the sculptor intended for the middle tine to be missing, or if it broke centuries ago.
# First Puzzle:2 ends here
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:3]]
@describe ball = The sphere resting on the statue's outstretched hand has an ornamental cross on top and the carved letters: |yR E G I N A|n
@detail orb;ball = The sphere resting on the statue's outstretched hand has an ornamental cross on top and the carved letters: |mR E G I N A|n
# First Puzzle:3 ends here
@ -203,17 +314,17 @@ $state(boat, A large leaf bobs invitingly in the surf.)
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:4]]
@describe shield = An ornate shield carved with a poem:
@detail shield = An ornate shield carved with a poem:
Upon the waves, as ship doeth glide,
Fearful whispers of the sea abide,
We cast our plea, with silver and song
To keep us safe as we sail along.
Upon the waves, as ship doeth glide,
Fearful whispers of the sea abide,
We cast our plea, with silver and song
To keep us safe as we sail along.
She stirs the tides with playful ease,
Her whispers ride the ocean's breeze,
Her mischief wrapped in salty foam,
A siren's call, the sea, her home.
She stirs the tides with playful ease,
Her whispers ride the ocean's breeze,
Her mischief wrapped in salty foam,
A siren's call, the sea, her home.
# First Puzzle:4 ends here
@ -222,5 +333,262 @@ A siren's call, the sea, her home.
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:5]]
@describe plaque = Glory to the Wildmother. All should |wspeak|n her |wname|n: |yA Q U E E R T H E M E L O N|n
@detail plaque = Glory to the Wildmother. All should |yspeak|n her |yname|n: |mA Q U E E R T H E M E L O N|n
# First Puzzle:5 ends here
# The plaque contains a /password/ to say aloud. We need another StoryCube to listen for the pass phrase:
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:6]]
@create/drop gr02ctl:typeclasses.puzzles.StoryCube
#
@desc gr02ctl = Opens the door when someone says Melora
#
@lock gr02ctl = view:false()
# First Puzzle:6 ends here
# The letters form a phrase to be said: =Melora the Queen= … which if said aloud, we hear:
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:7]]
@set gr02ctl/say = {r"melora": "gm Upon uttering that phrase, sparks of octarine magic pop the illusion, revealing an open archway at the base of the giant statue. ;; tag_all hidden_archway"}
# First Puzzle:7 ends here
# And we need to get rid of it when any one leaves:
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:8]]
@set gr02ctl/leave = "30 ;; @teleport/tonone arched opening"
# First Puzzle:8 ends here
# Which exit would need to be moved from the =None= space and a script to teleport it back after a minute.
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:9]]
@dig Antechamber;gr03:typeclasses.rooms_dark.DarkRoom = arched opening;opening;arch
# First Puzzle:9 ends here
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:10]]
@desc arched opening = An arch, shaped as a cresent moon, rests on two ornate pillars to frame a short opening at the base of the statue. One pillar shows carved letters: |mF I C K L E|n and the other pillar shows: |mF A I T H|n.
# First Puzzle:10 ends here
# And the description of entering:
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:11]]
@set arched opening/traverse_msg = "You slightly stoop to enter into the darkness beyond the archway... As soon as you do, a large stone block slams down behind you, trapping you in the darkness."
# First Puzzle:11 ends here
# Lets hide and lock this down:
# [[file:../../../projects/mud-adventure.org::*First Puzzle][First Puzzle:12]]
@set arched opening/hidden_tag = "hidden_archway"
#
@lock arched opening = view:tag(hidden_archway)
# First Puzzle:12 ends here
# Lets make this a dark room where one has to have two feel around for a splinter to light, or bring a torch.
# [[file:../../../projects/mud-adventure.org::*Puzzle Two][Puzzle Two:1]]
@create torch:typeclasses.lightables.LightSource
# Puzzle Two:1 ends here
# Now, we can go in:
# [[file:../../../projects/mud-adventure.org::*Puzzle Two][Puzzle Two:2]]
@teleport gr03
#
light torch
#
@desc here = A large room formed from intricately placed |Ystone blocks|n, tapered to form a |Ydome|n above you. In the center you see a large |Yobelisk|n with an empty |Ycavity|n halfway up.|/
A overly ornate |Ydoor|n with letters in rows and columns in a grid matrix indicates the exit. Placed above the door, a bronze |Yplaque|n, shows its age with a coat of patina.
# Puzzle Two:2 ends here
# And some details:
# [[file:../../../projects/mud-adventure.org::*Puzzle Two][Puzzle Two:3]]
@detail cavity = While empty, this opening, about a foot or so deep, looks like it may have held something in the past.
# Puzzle Two:3 ends here
# Dont know if the blocks are interesting:
# [[file:../../../projects/mud-adventure.org::*Puzzle Two][Puzzle Two:4]]
@detail stone blocks = While the stones are tightly placed, a curious blue fungus grows where moisture seeps between the seams.
# Puzzle Two:4 ends here
# Dome?
# [[file:../../../projects/mud-adventure.org::*Puzzle Two][Puzzle Two:5]]
@detail dome = Perhaps 30 feet high, but as shadows from the flame dance above you, you find it difficult to tell.
# Puzzle Two:5 ends here
# Each time this is “seen”, the response is different, so we use a [[file:~/src/moss-n-puddles/typeclasses/puzzles.py::class Changling(Object):][Changling]]:
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:1]]
@create/drop granite obelisk: typeclasses.puzzles.Changling
# Puzzle Three:1 ends here
# The default description will be replaced:
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:2]]
@desc obelisk = A granite obelisk grazes the top of the dome. Each of its four sides show a different image.
# Puzzle Three:2 ends here
# We build a /changlings/ description from a =_prefix=, a random =descs=, and a =_postfix=:
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:3]]
@set obelisk/desc_prefix = "A << granite ^ large ^ stone >> obelisk grazes the top of the dome. The surface of the obelisk seem to waver, shift and writhe under your gaze."
#
@set obelisk/desc_postfix = " And << quickly ^ soon ^ swiftly >>, the << image ^ visage ^ vision ^ carving >> << is gone ^ fades ^ disappears ^ vanishes >>."
# Puzzle Three:3 ends here
# And a list of differences that gives a hint to the password:
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:4]]
@set obelisk/descs = (
"You can briefly make out the image of ocean waves.",
"For the briefest moment you make out an icy engravings of crystals.",
"You think you can see the outline of grass covered in dew drops.",
"The surface for a moment seems to portray clouds passing by.",
"Trees bordering a flowing river show up on the surface.",
)
# Puzzle Three:4 ends here
# If they say the /magic word/, they get the maguffin.
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:5]]
@set obelisk/say = { r"^[Ww]ater[^A-z]*$": "gm Upon uttering that phrase, sparks of octarine magic and mist appear on the opening on the obelisk. ;; gm A conch materializes in the opening. ;; teleport conch = Antechamber", r"^tidal surge.*": "gm You hear an audible click from the ornate door! ;; tag_all open_ornate_door" }
# Puzzle Three:5 ends here
# Of course, we need a conch for this magic:
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:6]]
@create conch:typeclasses.sailing.CallingHorn
# Puzzle Three:6 ends here
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:7]]
@desc conch = A shimmering, otherworldly shell.
# Puzzle Three:7 ends here
# This conch _belongs_ in the obelisk:
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:8]]
@set obelisk/objects_here = "conch"
# Puzzle Three:8 ends here
# And put it away:
# [[file:../../../projects/mud-adventure.org::*Puzzle Three][Puzzle Three:9]]
py me.search('obelisk').cleanup()
# Puzzle Three:9 ends here
# We need another door in order to escape.
# [[file:../../../projects/mud-adventure.org::*Puzzle Four][Puzzle Four:1]]
@detail plaque = Through the green patina, you can barely make out the text:
A spoken word unseals me.
It starts with a T.
Halfway between twins.
The next character lies.
# Puzzle Four:1 ends here
# The door leads back out.
# [[file:../../../projects/mud-adventure.org::*Puzzle Four][Puzzle Four:2]]
@open ornate door;door = Lonely Island
# Puzzle Four:2 ends here
# The dock is locked:
# [[file:../../../projects/mud-adventure.org::*Puzzle Four][Puzzle Four:3]]
@lock ornate door = traverse:tag(open_ornate_door)
# Puzzle Four:3 ends here
# [[file:../../../projects/mud-adventure.org::*Puzzle Four][Puzzle Four:4]]
@set ornate door/err_traverse = "The door is locked."
# Puzzle Four:4 ends here
# Look at the grid to find the two Ts:
# [[file:../../../projects/mud-adventure.org::*Puzzle Four][Puzzle Four:5]]
@desc ornate door = The grid of letters look like:
| Y | B | Q | X | D | Q |
| Y | A | K | D | X | B |
| D | E | L | G | W | X |
| T | S | - | A | X | V |
| L | J | H | E | P | V |
| F | U | I | D | I | P |
| H | X | R | G | R | X |
| K | S | M | U | T | N |
| W | X | M | W | Z | Z |
# Puzzle Four:5 ends here
# If they say, /Tidal Surge/, the *Locker* unlocks the door, and they can go through.
# [[file:../../../projects/mud-adventure.org::*Puzzle Four][Puzzle Four:6]]
@set ornate door/traverse_msg = "You open the door, and see that the door exits next to the statue..."
# Puzzle Four:6 ends here