moss-n-puddles/typeclasses/sailing.py
2026-04-23 20:53:15 -07:00

449 lines
16 KiB
Python
Executable file

#!/usr/bin/env python
from random import choice
from re import match
from evennia import CmdSet
from evennia.utils import logger, delay, int2str, search
from commands.command import Command
from typeclasses.puzzles import StoryCube
from typeclasses.scripts import Script
from typeclasses.objects import Object
from utils.scoring import Scores
PORT = "Lazy Dock"
PORT_EXIT = "dock"
BOAT = "Leaf Boat"
BOAT_EXIT = "leaf boat"
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.
Usage:
blow [ object ]
"""
key = "blow"
locks = "holds()"
def func(self):
self.obj.do_blow(self.caller)
class CmdSetHorn(CmdSet):
def at_cmdset_creation(self):
self.add(CmdBlow)
class CallingHorn(Object):
"""
A thing to move the ship to the caller's location.
"""
def at_object_creation(self):
self.cmdset.add_default(CmdSetHorn)
def do_blow(self, blower):
"""
The act of blowing a horn, calls the ship.
"""
if self.db.blowing_msg:
blower.announce_action(self.db.blowing_msg)
else:
blower.announce_action("$You() $conj(blow) $pron(your) horn "
"and $conj(create) a long, resonating "
"sound echoing over the water.")
blower.score(Scores.blow_horn)
script_results = search.scripts("sailing")
if script_results:
script = script_results[0]
if not script.db.is_sailing:
if blower:
if blower.location.key == PORT and \
script.db.sailing_direction == "port":
script.to_dock()
elif match(r".*Isl(and|e).*", blower.location.key) and \
script.db.sailing_direction != "port":
script.to_isle(blower.location)
else:
script.to_dock()
# Just make sure the script is running:
script.start()
class Boat(Script):
"""
Start the script by running the following:
@teleport gr01
@script here = typeclasses.sailing.Boat
"""
def at_script_creation(self):
self.key = "sailing"
self.desc = "Sailing the Boat"
self.interval = 60 # seconds
self.start_delay = True # wait self.interval until first call
self.persistent = True
self.db.sailing_direction = "port"
self.db.is_sailing = False
def at_repeat(self, **kwargs):
"""
If we have passengers, we need to sail...
"""
if len(self.characters_on(self.obj)) > 0 and not self.db.is_sailing:
if self.db.sailing_direction == "port":
self.to_dock()
else:
self.to_isle(self.db.sailing_direction)
# ----------------------------------------------------------------------
# FINDING THE OBJECTS FROM THE DATABASE
# ----------------------------------------------------------------------
def sailing_objects(self, default_island=None):
"""
Convenience method for returning a tuple of all needed objects.
"""
# boat = self.boat()
dock = self.dock()
boat = self.boat()
return (dock, boat, self.isle(dock, boat, default_island),
self.boat_exit(), self.dock_exit(), self.isle_exit())
def dock(self):
"""
Return reference to the 'Lazy Dock'.
"""
dock = search.search_object(PORT)
return dock[0]
def boat(self):
"""
Return reference to the 'Leaf Boat'.
"""
if self.obj:
return self.obj
boat = search.objects(BOAT, typeclass="typeclasses.rooms.Room")
return boat[0]
def isle(self, dock, boat, default=None):
"""
Return the island next to the boat.
Since we can have multiple islands "moored" at shore, we
need to figure this object out by using the boat's exit.
"""
exits = [e for e in boat.exits if e.destination != dock]
if exits:
return exits[0].destination
if default:
return default
results = search.objects(DEFAULT_ISLE)
if results:
return results[0]
return None
def boat_exit(self):
"""
Return reference to the exit from the dock to the boat.
Note that this *must* remain named, "port".
"""
boat_exit = search.objects(BOAT_EXIT, typeclass="typeclasses.exits.Exit")
return boat_exit[0]
def dock_exit(self):
"""
Return reference to the exit from the boat to the dock.
Note that this *must* remain named, "port".
"""
dock_exit = search.objects(PORT_EXIT, typeclass="typeclasses.exits.Exit")
return dock_exit[0]
def isle_exit(self):
"""
Return reference to the exit from the boat to the shore.
Note that this *must* remain named, "shore".
We need to make this more flexible.
"""
shore_exit = search.objects(ISLE_EXIT, typeclass="typeclasses.exits.Exit")
return shore_exit[0]
# ----------------------------------------------------------------------
# HELPER FUNCTIONS FOR SAILING THE BOAT
# ----------------------------------------------------------------------
standard_delay = 7
def characters_on(self, location):
"""
Return a list of players on the location.
"""
return [item for item in location.contents
if item.is_typeclass("typeclasses.characters.Character")]
def send_to(self, to_location, message, delayed_by=0):
"""
Abstraction on sending a delayed message to a location.
"""
if delayed_by == 0:
to_location.msg_contents("\n" + message)
else:
delay(delayed_by * self.standard_delay,
to_location.msg_contents, "\n" + message)
# ----------------------------------------------------------------------
# SAILING TO AN ISLAND...
# ----------------------------------------------------------------------
sailing_events = [
"Suddenly, the air is filled with fish with glowing fins, flying over the waves!",
"Suddenly, you see a huge sea serpent swimming towards the fragile leaf. " +
"As it gets closer, it dives, and swims under the boat. " +
"The colorful patterns of its scales form mesmerizing patterns as it passes.",
"Suddenly, you see a pair of horses, pulling a chariot with two warriors. " +
"They look at you confused, as you sail past.",
"Suddenly, a pod of dolphins swim briefly alongside your makeshift boat. " +
"As they begin to leave, one turns and waves.",
]
def to_isle_boat_view(self, boat):
"""
Delayed messages sent to passengers on the boat.
- Start with a message about leaving.
- Then a message about the peaceful journey.
- Followed by a random event.
- Finished with a view of a distant island.
"""
boat.msg_contents("The leaf boat sets sail onto the lavender sea.")
# Nice stuff
tod = boat.get_time_of_day()
second_msg = "Only eddies from the subtle wake, intrude on the " \
"lavender sea's tranquility, as the giant leaf floats along invisible currents. "
if tod == "morning":
second_msg += "The morning sun peaks above the colossal trees and " \
"the snow-capped mountains beyond."
elif tod == "afternoon":
second_msg += "The afternoon sun peaks out briefly from misty clouds."
elif tod == "afternoon":
second_msg += "The evening sun begins to set on a watery horizon."
else:
second_msg += "The moon peaks out from behind wispy clouds."
self.send_to(boat, second_msg, 1)
# Random event
third_msg = choice(self.sailing_events)
self.send_to(boat, third_msg, 3)
self.send_to(boat, "In the distance, you see an island.", 5)
def to_isle_isle_view(self, isle, num_sailors):
"""
Delayed messages sent to anyone on the island.
"""
self.send_to(isle, "You see a giant leaf sailing on the lavender sea.", 4)
msg = "The leaf sails ever closer."
if num_sailors == 1:
msg = msg + " You can see it carries a passenger."
elif num_sailors > 1:
msg = msg + \
f" You can see it carries {int2str(num_sailors)} passengers."
self.send_to(isle, msg, 5)
def to_isle_dock_view(self, dock, num_sailors):
"""
Message sent to anyone standing on the dock when the boat leaves.
"""
msg = "The leaf boat sets sail"
if num_sailors == 0:
msg += ", leaving you behind."
elif num_sailors == 1:
msg += " with its passenger."
else:
msg += f" with its {int2str(num_sailors)} passengers."
dock.msg_contents(msg)
def to_isle(self, default_isle=None):
"""
Message sequence from sailing from the Island to the Dock.
"""
logger.info("Setting sail for the Lazy Dock...")
(dock, boat, isle, boat_exit, dock_exit, isle_exit) = \
self.sailing_objects(default_isle)
self.db.sailing_direction = isle
self.db.is_sailing = True
num_sailors = len(self.characters_on(boat))
boat.remove_room_state("docked")
boat.add_room_state("sailing")
dock.remove_room_state("boat")
dock_exit.move_to(None, to_none=True, quiet=True)
boat_exit.move_to(None, to_none=True, quiet=True)
self.to_isle_dock_view(dock, num_sailors)
self.to_isle_boat_view(boat)
self.to_isle_isle_view(isle, num_sailors)
delay(6 * self.standard_delay, self.shore_procedure, isle, boat, isle_exit, boat_exit)
def shore_procedure(self, island, boat, shore_exit, boat_exit):
"""
Final shore landing procedure for the boat.
"""
island.msg_contents("\nThe giant leaf gently bobs in the surf, "
"now close enough that you could climb aboard "
"and return from your sailing adventure.")
boat.msg_contents("\nThe giant leaf gently bobs in the surf of this island,"
" allowing you to disembark.")
boat_exit.move_to(island, quiet=True)
shore_exit.move_to(boat, quiet=True)
island.add_room_state("boat")
boat.add_room_state("ashore")
# For the next time we need to sail, we go to:
self.db.sailing_direction = "port"
self.db.is_sailing = False
# ----------------------------------------------------------------------
# SAILING BACK TO THE DOCK...
# ----------------------------------------------------------------------
def to_dock_boat_view(self, boat, num_dockers):
"""
Delayed messages from the point of view of the boat's passengers.
"""
self.send_to(boat, "The leaf boat sets sail into the lavender sea.")
self.send_to(boat, "Soon you see dark shapes of "
"colossal trees on the horizon.", 1)
self.send_to(boat, "In the shadows of the trees sits a dock.", 2)
# Since we can assume that raven is always perched on tree
# near the dock, we will always have one too many figures:
if num_dockers < 2:
msg = "You can make out an inviting chair perched on the dock."
elif num_dockers == 2:
msg = "You can see a figure on the dock."
else:
msg = f"You can see {int2str(num_dockers)} figures standing on the dock."
self.send_to(boat, msg, 3)
def to_dock_dock_view(self, dock, num_sailors):
"""
Delayed messages from the point of view of people on the dock.
"""
self.send_to(dock, "In the distance, you notice a large leaf floating on the lavender sea.", 1)
self.send_to(dock, "The leaf seems to be floating towards the dock.", 2)
msg = "The leaf seems to be large enough to carry passengers."
if num_sailors == 1:
msg = msg + " In fact, it does carry a passenger."
elif num_sailors > 1:
msg = msg + f" In fact, it carries {int2str(num_sailors)} passengers."
self.send_to(dock, msg, 3)
def to_dock_isle_view(self, isle, num_sailors):
"""
Delayed messages from the point of view of people left on the island.
"""
msg = "The leaf boat sets sail"
if num_sailors == 0:
msg += ", leaving you behind."
elif num_sailors == 1:
msg += " with its passenger."
else:
msg += f" with its {int2str(num_sailors)} passengers."
self.send_to(isle, msg)
def to_dock(self):
"""
Message sequence from sailing from the Island to the Dock.
"""
logger.info("Setting sail for the Lazy Dock...")
(dock, boat, isle, boat_exit, dock_exit, isle_exit) = self.sailing_objects()
logger.info(f"Got {dock}")
self.db.is_sailing = True
isle.remove_room_state("boat")
boat.remove_room_state("ashore")
boat.add_room_state("sailing")
isle_exit.move_to(None, to_none=True, quiet=True)
boat_exit.move_to(None, to_none=True, quiet=True)
num_sailors = len(self.characters_on(boat))
num_dockers = len(self.characters_on(dock))
self.to_dock_dock_view(dock, num_sailors)
self.to_dock_boat_view(boat, num_dockers)
self.to_dock_isle_view(isle, num_sailors)
delay(4 * self.standard_delay, self.docking_procedure, dock, boat, dock_exit, boat_exit)
def docking_procedure(self, dock, boat, dock_exit, boat_exit):
"""
Final docking procedure for the boat.
"""
dock.msg_contents("\nThe giant leaf slows as it arrives next to the dock.")
boat.msg_contents("\nThe giant leaf slows as it arrives and docks allowing you to disembark.")
boat_exit.move_to(dock, to_none=True, quiet=True)
dock_exit.move_to(boat, to_none=True, quiet=True)
dock.add_room_state("boat")
boat.add_room_state("docked")
# For the next time we need to sail, we go to a default island:
self.db.sailing_direction = None
self.db.is_sailing = False
class ResetBoat(Script):
"""
Script to pull the boat back out to sea and dock it at an island.
"""
def at_script_creation(self):
self.key = "boat-reset"
self.desc = "Sailing the Boat"
self.interval = 60 * 60 # reset every hour
self.start_delay = True # wait self.interval until first call
self.persistent = True
def at_repeat(self, **kwargs):
script_results = search.scripts("sailing")
if script_results:
script = script_results[0]
if not script.db.is_sailing:
script.to_isle()
# Just make sure the script is running:
script.start()