diff --git a/commands/lighting.py b/commands/lighting.py
index 9572a54..be4a9f7 100755
--- a/commands/lighting.py
+++ b/commands/lighting.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
-from commands.command import Command
-from evennia import CmdSet
+from evennia import Command, CmdSet
+# from .command import Command
class CmdLight(Command):
"""
@@ -11,19 +11,17 @@ class CmdLight(Command):
key = "light"
aliases = ["burn"]
# only allow this command if command.obj is carried by caller.
- locks = "cmd:holds()"
+ # locks = "cmd:holds()"
def func(self):
"""
Implements the light command. Since this command is designed
to sit on a "lightable" object, we operate only on self.obj.
"""
-
if self.obj.light():
self.caller.msg("You light %s." % self.obj.key)
self.caller.location.msg_contents(
- "%s lights %s!" % (self.caller, self.obj.key), exclude=[self.caller]
- )
+ "%s lights %s!" % (self.caller, self.obj.key), exclude=[self.caller])
else:
self.caller.msg("%s is already burning." % self.obj.key)
@@ -31,10 +29,6 @@ class CmdLight(Command):
class CmdSetLight(CmdSet):
"""CmdSet for the lightsource commands"""
- key = "lightsource_cmdset"
- # this is higher than the dark cmdset - important!
- priority = 3
-
def at_cmdset_creation(self):
"""called at cmdset creation"""
- self.add(CmdLight())
+ self.add(CmdLight)
diff --git a/typeclasses/lightables.py b/typeclasses/lightables.py
new file mode 100755
index 0000000..7620b74
--- /dev/null
+++ b/typeclasses/lightables.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+
+from commands.lighting import CmdSetLight
+
+# -------------------------------------------------------------
+#
+# LightSource
+#
+# This object emits light. Once it has been turned on it
+# cannot be turned off. When it burns out it will delete
+# itself.
+#
+# This could be implemented using a single-repeat Script or by
+# registering with the TickerHandler. We do it simpler by
+# using the delay() utility function. This is very simple
+# to use but does not survive a server @reload. Because of
+# where the light matters (in the Dark Room where you can
+# find new light sources easily), this is okay here.
+#
+# -------------------------------------------------------------
+
+class LightSource(Object):
+ """
+ This implements a light source object.
+
+ When burned out, the object will be deleted.
+ """
+
+ def at_init(self):
+ """
+ If this is called with the Attribute is_giving_light already
+ set, we know that the timer got killed by a server
+ reload/reboot before it had time to finish. So we kill it here
+ instead. This is the price we pay for the simplicity of the
+ non-persistent delay() method.
+ """
+ if self.db.is_giving_light:
+ self.delete()
+
+ def at_object_creation(self):
+ """Called when object is first created."""
+ super().at_object_creation()
+
+ self.db.is_giving_light = False
+ 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."
+ # add the Light command
+ self.cmdset.add_default(CmdSetLight, persistent=True)
+
+ def _burnout(self):
+ """
+ This is called when this light source burns out. We make no
+ use of the return value.
+ """
+ # delete ourselves from the database
+ self.db.is_giving_light = False
+ try:
+ self.location.location.msg_contents(
+ "%s's %s flickers and dies." % (self.location, self.key), exclude=self.location
+ )
+ self.location.msg("Your %s flickers and dies." % self.key)
+ self.location.location.check_light_state()
+ except AttributeError:
+ try:
+ self.location.msg_contents("A %s on the floor flickers and dies." % self.key)
+ self.location.location.check_light_state()
+ except AttributeError:
+ # Mainly happens if we happen to be in a None location
+ pass
+ self.delete()
+
+ def light(self):
+ """
+ Light this object - this is called by Light command.
+ """
+ if self.db.is_giving_light:
+ return False
+ # burn for 3 minutes before calling _burnout
+ self.db.is_giving_light = True
+ # if we are in a dark room, trigger its light check
+ try:
+ self.location.location.check_light_state()
+ except AttributeError:
+ try:
+ # maybe we are directly in the room
+ self.location.check_light_state()
+ except AttributeError:
+ # we are in a None location
+ pass
+ finally:
+ # start the burn timer. When it runs out, self._burnout
+ # will be called. We store the deferred so it can be
+ # killed in unittesting.
+ self.deferred = delay(60 * 3, self._burnout)
+ return True
diff --git a/typeclasses/objects.py b/typeclasses/objects.py
old mode 100644
new mode 100755
index 662276c..6f7971e
--- a/typeclasses/objects.py
+++ b/typeclasses/objects.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
"""
Object
@@ -9,9 +11,8 @@ with a location in the game world (like Characters, Rooms, Exits).
"""
from evennia.objects.objects import DefaultObject
-from evennia.utils import delay, search
+from evennia.utils import delay, logger, search
-from commands.lighting import CmdSetLight
class ObjectParent:
"""
@@ -231,96 +232,27 @@ class Object(ObjectParent, DefaultObject):
elif i == item:
return i
-# -------------------------------------------------------------
-#
-# LightSource
-#
-# This object emits light. Once it has been turned on it
-# cannot be turned off. When it burns out it will delete
-# itself.
-#
-# This could be implemented using a single-repeat Script or by
-# registering with the TickerHandler. We do it simpler by
-# using the delay() utility function. This is very simple
-# to use but does not survive a server @reload. Because of
-# where the light matters (in the Dark Room where you can
-# find new light sources easily), this is okay here.
-#
-# -------------------------------------------------------------
-
-class LightSource(Object):
- """
- This implements a light source object.
-
- When burned out, the object will be deleted.
- """
-
- def at_init(self):
+ def at_look(self, target, **kwargs):
"""
- If this is called with the Attribute is_giving_light already
- set, we know that the timer got killed by a server
- reload/reboot before it had time to finish. So we kill it here
- instead. This is the price we pay for the simplicity of the
- non-persistent delay() method.
- """
- if self.db.is_giving_light:
- self.delete()
+ When we look at something that _might_ be hidden, we check
+ if we are looking at something that _is_ hidden, i.e. has a
+ view lock of a particular tag, and if so, we interpret this as
+ 'you are looking at something by name', therefore you see it.
+ So we give ourselves the right to see it from now on.
- def at_object_creation(self):
- """Called when object is first created."""
- super().at_object_creation()
+ To use this, simply add the following lock:
- self.db.is_giving_light = False
- 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."
- # add the Light command
- self.cmdset.add_default(CmdSetLight, persistent=True)
+ @lock thing = view:tag(hidden_thing, mp)
- def _burnout(self):
+ Where thing is the single name of the hidden object.
"""
- This is called when this light source burns out. We make no
- use of the return value.
- """
- # delete ourselves from the database
- self.db.is_giving_light = False
- try:
- self.location.location.msg_contents(
- "%s's %s flickers and dies." % (self.location, self.key), exclude=self.location
- )
- self.location.msg("Your %s flickers and dies." % self.key)
- self.location.location.check_light_state()
- except AttributeError:
- try:
- self.location.msg_contents("A %s on the floor flickers and dies." % self.key)
- self.location.location.check_light_state()
- except AttributeError:
- # Mainly happens if we happen to be in a None location
- pass
- self.delete()
+ hidden_tag = f"hidden_{target}"
+ lock_string = f"view:tag({hidden_tag}, mp)"
+ view_lock = target.locks.get("view")
- def light(self):
- """
- Light this object - this is called by Light command.
- """
- if self.db.is_giving_light:
- return False
- # burn for 3 minutes before calling _burnout
- self.db.is_giving_light = True
- # if we are in a dark room, trigger its light check
- try:
- self.location.location.check_light_state()
- except AttributeError:
- try:
- # maybe we are directly in the room
- self.location.check_light_state()
- except AttributeError:
- # we are in a None location
- pass
- finally:
- # start the burn timer. When it runs out, self._burnout
- # will be called. We store the deferred so it can be
- # killed in unittesting.
- self.deferred = delay(60 * 3, self._burnout)
- return True
+ if view_lock == lock_string:
+ self.tags.add(hidden_tag, category="mp")
+
+ # Regardless of what happened before, we return the normal
+ # function call.
+ return super().at_look(target)
diff --git a/typeclasses/pets.py b/typeclasses/pets.py
index 74afd92..80bd447 100755
--- a/typeclasses/pets.py
+++ b/typeclasses/pets.py
@@ -7,7 +7,8 @@ Each level of pet requires more aspects for interaction.
"""
-from typeclasses.objects import Object, LightSource
+from typeclasses.objects import Object
+# from typeclasses.lightables import LightSource
from commands.feedables import CmdFeed, CmdFeedSet
from utils.word_list import squish
@@ -180,12 +181,12 @@ class Fire(Pet):
gets_up = "gets up and"
if self.db.hunger_level < 5:
- feeder.msg(squish(f"You {get_up} put some {adj} wood in the "
+ feeder.msg(squish(f"You {get_up} put {adj} wood in the "
f"fireplace, and start a fire."))
self.location.msg_contents(squish(f"{feeder.name} {gets_up} starts a fire."),
exclude=feeder)
else:
- feeder.msg(squish(f"You {get_up} put some {adj} wood on the "
+ feeder.msg(squish(f"You {get_up} put {adj} wood on the "
f"fire in the fireplace."))
self.location.msg_contents(squish(f"{feeder.name} {gets_up} puts {adj} wood on the fire."),
exclude=feeder)
diff --git a/typeclasses/rooms.py b/typeclasses/rooms.py
index a386f22..ccd4093 100644
--- a/typeclasses/rooms.py
+++ b/typeclasses/rooms.py
@@ -9,7 +9,7 @@ from evennia.objects.objects import DefaultRoom
from evennia.contrib.grid.extended_room import ExtendedRoom
from evennia.prototypes.spawner import spawn
-from .objects import LightSource
+# from .objects import LightSource
from .drinkables import TEACUP_DESCS
from .pets import Hunger
from commands.drinkables import CmdSetTrolley
@@ -35,7 +35,6 @@ from evennia import (
utils,
)
-# from .objects import LightSource
_SEARCH_AT_RESULT = utils.object_from_module(settings.SEARCH_AT_RESULT)
from .objects import ObjectParent
@@ -54,242 +53,6 @@ class Room(ObjectParent, ExtendedRoom):
is_dark = False
has_weather = False
- pass
-
-# -------------------------------------------------------------------------------
-#
-# Dark Room - a room with states
-#
-# This room limits the movemenets of its denizens unless they carry an active
-# LightSource object (LightSource is defined in 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 something on the floor.",
- "You are completely blind. For a moment you think you hear someone breathing nearby ... "
- "\n ... surely you must be mistaken.",
-)
-
-ALREADY_LIGHTSOURCE = (
- "You don't want to stumble around in blindness anymore. You already "
- "found what you need. Let's get the fireplace lit already!"
-)
-
-FOUND_LIGHTSOURCE = (
- "Your fingers bump against a candle on a candleabra."
- "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
-
- Usage:
- look
-
- Look around in the darkness, trying
- to find something.
- """
-
- key = "look"
- aliases = ["l", "feel", "search", "feel around", "fiddle"]
- locks = "cmd:all()"
-
- def func(self):
- """
- 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
-
- # count how many searches we've done
- nr_searches = caller.ndb.dark_searches
- if nr_searches is None:
- nr_searches = 0
- caller.ndb.dark_searches = nr_searches
-
- if nr_searches < 4 and random.random() < 0.90:
- # we don't find anything
- caller.msg(random.choice(DARK_MESSAGES))
- caller.ndb.dark_searches += 1
- else:
- # 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:
- # don't have a light source, create a new one.
- create_object(LightSource, key="candle", location=caller)
- caller.msg(FOUND_LIGHTSOURCE)
-
-
-class CmdDarkHelp(Command):
- """
- Help command for the dark state.
- """
- key = "help"
- locks = "cmd:all()"
-
- def func(self):
- """
- 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)
-
-
-class CmdDarkNoMatch(Command):
- """
- 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):
- """Implements the command."""
- self.caller.msg(
- "Until you find some light, there's not much you can do. "
- "Try feeling around, maybe you'll find something helpful!"
- )
-
-
-class DarkCmdSet(CmdSet):
- """
- 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"
- priority = 2
-
- def at_cmdset_creation(self):
- """populate the cmdset."""
- self.add(CmdLookDark())
- self.add(CmdDarkHelp())
- self.add(CmdDarkNoMatch())
- self.add(default_cmds.CmdSay())
- self.add(default_cmds.CmdQuit())
- self.add(default_cmds.CmdHome())
-
-
-class DarkRoom(Room):
- """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).
-
- 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):
- """
- Called when object is first created.
- """
- super().at_object_creation()
- # the room starts dark.
- self.db.is_lit = False
- self.cmdset.add(DarkCmdSet, persistent=True)
-
- def at_init(self):
- """
- Called when room is first recached (such as after a reload)
- """
- self.check_light_state()
-
- def _carries_light(self, obj):
- """
- Checks if the given object carries anything that gives light.
-
- 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. We also accept
- if there is a light-giving object in the room overall (like if
- a candle was dropped in the room or the fireplace is lit).
- """
- return (
- obj.is_superuser
- or obj.db.is_giving_light
- or any(o for o in obj.contents if o.db.is_giving_light)
- )
-
- def check_light_state(self, exclude=None):
- """
- 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.
-
- Args:
- exclude (Object): An object to not include in the light check.
- """
- if any(self._carries_light(obj) for obj in self.contents if obj != exclude):
- self.locks.add("view:all()")
- self.cmdset.remove(DarkCmdSet)
- self.db.is_lit = True
- for char in (obj for obj in self.contents if obj.has_account):
- # this won't do anything if it is already removed
- char.msg("The room is lit up.")
- else:
- # noone is carrying light - darken the room
- self.db.is_lit = False
- self.locks.add("view:false()")
- self.cmdset.add(DarkCmdSet, persistent=True)
- for char in (obj for obj in self.contents if obj.has_account):
- if char.is_superuser:
- char.msg("You are Superuser, so you are not affected by the dark state.")
- else:
- # put players in darkness
- char.msg("The room is completely dark.")
-
- def at_object_receive(self, obj, source_location, move_type="move", **kwargs):
- """
- Called when an object enters the room.
- """
- if obj.has_account:
- # 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, move_type="move", **kwargs):
- """
- In case people leave with the light, we make sure to clear the
- DarkCmdSet if necessary. This also works if they are
- teleported away.
- """
- # since this hook is called while the object is still in the room,
- # we exclude it from the light check, to ignore any light sources
- # it may be carrying.
- self.check_light_state(exclude=obj)
-
class DabblersRoom(Room):
teacup_prototype = {
diff --git a/typeclasses/rooms_dark.py b/typeclasses/rooms_dark.py
new file mode 100755
index 0000000..39433bd
--- /dev/null
+++ b/typeclasses/rooms_dark.py
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+
+# -------------------------------------------------------------------------------
+#
+# Dark Room - a room with states
+#
+# This room limits the movemenets of its denizens unless they carry an active
+# LightSource object (LightSource is defined in 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 something on the floor.",
+ "You are completely blind. For a moment you think you hear someone breathing nearby ... "
+ "\n ... surely you must be mistaken.",
+)
+
+ALREADY_LIGHTSOURCE = (
+ "You don't want to stumble around in blindness anymore. You already "
+ "found what you need. Let's get the fireplace lit already!"
+)
+
+FOUND_LIGHTSOURCE = (
+ "Your fingers bump against a candle on a candleabra."
+ "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
+
+ Usage:
+ look
+
+ Look around in the darkness, trying
+ to find something.
+ """
+
+ key = "look"
+ aliases = ["l", "feel", "search", "feel around", "fiddle"]
+ locks = "cmd:all()"
+
+ def func(self):
+ """
+ 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
+
+ # count how many searches we've done
+ nr_searches = caller.ndb.dark_searches
+ if nr_searches is None:
+ nr_searches = 0
+ caller.ndb.dark_searches = nr_searches
+
+ if nr_searches < 4 and random.random() < 0.90:
+ # we don't find anything
+ caller.msg(random.choice(DARK_MESSAGES))
+ caller.ndb.dark_searches += 1
+ else:
+ # 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:
+ # don't have a light source, create a new one.
+ create_object(LightSource, key="candle", location=caller)
+ caller.msg(FOUND_LIGHTSOURCE)
+
+
+class CmdDarkHelp(Command):
+ """
+ Help command for the dark state.
+ """
+ key = "help"
+ locks = "cmd:all()"
+
+ def func(self):
+ """
+ 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)
+
+
+class CmdDarkNoMatch(Command):
+ """
+ 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):
+ """Implements the command."""
+ self.caller.msg(
+ "Until you find some light, there's not much you can do. "
+ "Try feeling around, maybe you'll find something helpful!"
+ )
+
+
+class DarkCmdSet(CmdSet):
+ """
+ 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"
+ priority = 2
+
+ def at_cmdset_creation(self):
+ """populate the cmdset."""
+ self.add(CmdLookDark())
+ self.add(CmdDarkHelp())
+ self.add(CmdDarkNoMatch())
+ self.add(default_cmds.CmdSay())
+ self.add(default_cmds.CmdQuit())
+ self.add(default_cmds.CmdHome())
+
+
+class DarkRoom(Room):
+ """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).
+
+ 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):
+ """
+ Called when object is first created.
+ """
+ super().at_object_creation()
+ # the room starts dark.
+ self.db.is_lit = False
+ self.cmdset.add(DarkCmdSet, persistent=True)
+
+ def at_init(self):
+ """
+ Called when room is first recached (such as after a reload)
+ """
+ self.check_light_state()
+
+ def _carries_light(self, obj):
+ """
+ Checks if the given object carries anything that gives light.
+
+ 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. We also accept
+ if there is a light-giving object in the room overall (like if
+ a candle was dropped in the room or the fireplace is lit).
+ """
+ return (
+ obj.is_superuser
+ or obj.db.is_giving_light
+ or any(o for o in obj.contents if o.db.is_giving_light)
+ )
+
+ def check_light_state(self, exclude=None):
+ """
+ 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.
+
+ Args:
+ exclude (Object): An object to not include in the light check.
+ """
+ if any(self._carries_light(obj) for obj in self.contents if obj != exclude):
+ self.locks.add("view:all()")
+ self.cmdset.remove(DarkCmdSet)
+ self.db.is_lit = True
+ for char in (obj for obj in self.contents if obj.has_account):
+ # this won't do anything if it is already removed
+ char.msg("The room is lit up.")
+ else:
+ # noone is carrying light - darken the room
+ self.db.is_lit = False
+ self.locks.add("view:false()")
+ self.cmdset.add(DarkCmdSet, persistent=True)
+ for char in (obj for obj in self.contents if obj.has_account):
+ if char.is_superuser:
+ char.msg("You are Superuser, so you are not affected by the dark state.")
+ else:
+ # put players in darkness
+ char.msg("The room is completely dark.")
+
+ def at_object_receive(self, obj, source_location, move_type="move", **kwargs):
+ """
+ Called when an object enters the room.
+ """
+ if obj.has_account:
+ # 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, move_type="move", **kwargs):
+ """
+ In case people leave with the light, we make sure to clear the
+ DarkCmdSet if necessary. This also works if they are
+ teleported away.
+ """
+ # since this hook is called while the object is still in the room,
+ # we exclude it from the light check, to ignore any light sources
+ # it may be carrying.
+ self.check_light_state(exclude=obj)
diff --git a/typeclasses/sittables.py b/typeclasses/sittables.py
index be6c13e..19633f7 100755
--- a/typeclasses/sittables.py
+++ b/typeclasses/sittables.py
@@ -69,6 +69,8 @@ class Sittables(Sittable):
multiple = True
def sit_msg(self):
+ aliases = self.aliases.all()
+
adjective = self.db.adjective or "on"
article = self.db.article or "the"
name = aliases[-1] # Last alias is singular
@@ -76,8 +78,6 @@ class Sittables(Sittable):
singular = self.db.singular or default
extra = self.db.extra or ""
- aliases = self.aliases.all()
-
return routput(f"You sit {adjective} {singular}. {extra}")
def stand_msg(self):
diff --git a/typeclasses/things.py b/typeclasses/things.py
index 69755b3..dc1ba53 100755
--- a/typeclasses/things.py
+++ b/typeclasses/things.py
@@ -20,6 +20,12 @@ from random import choice, random
import re
+class Unhider(Object):
+ """
+ This thing has the ability to "unhide" other things.
+ The idea is .... ugh.
+ @set thing/
+ """
class Returnable(Object):
"""
This object can't go far from one or two locations.
diff --git a/world/version1.ev b/world/version1.ev
new file mode 100644
index 0000000..3388e4c
--- /dev/null
+++ b/world/version1.ev
@@ -0,0 +1,884 @@
+# [[file:../../../projects/mud.org::*Shell][Shell:4]]
+# # -*- mode:ruby; -*-
+# Shell:4 ends here
+
+
+# Can I rename myself?
+
+
+# [[file:../../../projects/mud.org::*Character: Dabble][Character: Dabble:1]]
+@name self = Dabbler;gnome;old gnome
+# Character: Dabble:1 ends here
+
+
+
+# And a good description that I can rework:
+
+
+# [[file:../../../projects/mud.org::*Character: Dabble][Character: Dabble:2]]
+@desc self = A small, hunched old man with a gray vandyke and an eye
+ twinkle. Spectacles perched precariously on the end of his hooked
+ nose, wobble with his head. A jaunty crimson cap contrasts with his
+ dark brown cloak.
+# Character: Dabble:2 ends here
+
+
+# Rename the Limbo (or starting place) with the name *Forest*. Note the term =mp01= as a global label that matches my map.
+
+
+# [[file:../../../projects/mud.org::*The Forest][The Forest:1]]
+@name here = The Forest;mp01
+# The Forest:1 ends here
+
+
+
+# The description will take advantage of the /seasons/ and /times/ of the day:
+
+
+# [[file:../../../projects/mud.org::*The Forest][The Forest:2]]
+@desc here = A giant, moss-covered boulder stands among immense trees
+ that etch the sky and slice the clouds in the darkening
+ twilightawakening dawnlazy
+ afternoonnight sky. A footpath winds around
+ the giant, moss-covered tree roots to the East and West. To the south,
+ a dock lounges on a large pond.
+# The Forest:2 ends here
+
+
+
+# Need to add weather and time data to this:
+
+# [[file:../../../projects/mud.org::*The Forest][The Forest:3]]
+# from evennia.contrib.tutorials.tutorial_world.rooms import WeatherRoom
+@update here = typeclasses.rooms_weather.TimeWeatherRoom
+# The Forest:3 ends here
+
+
+# More details about the room that describes aspects of the boulder in the =desc= above. First, the symbol:
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:1]]
+@detail symbol = You move an ivy runner to see three curves join
+ together as if it were three legs.
+# Boulder:1 ends here
+
+
+
+# The moss is purdy:
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:2]]
+@detail moss = "Even in this light, the moss radiates a surreal color of green."
+# Boulder:2 ends here
+
+
+
+# And the ivy is … uh.
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:3]]
+@detail ivy = "You see...well, ivy. Uhm, it's green, and..."
+# Boulder:3 ends here
+
+
+
+# The runes use multibyte unicode which screws up the =telnet= client.
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:4]]
+@detail runes = "The runes read ᛞ ᚪ ᛒ ᛚ ᚱ"
+# Boulder:4 ends here
+
+
+
+# The top of the boulder should be another room, accessible by the =climb=, but maybe it isn’t “seen” until the boulder has been looked? First, we put some weather at the top of the boulder:
+
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:5]]
+@dig Top of Boulder;mp02 : typeclasses.rooms_weather.TimeWeatherRoom = boulder,climb
+# Boulder:5 ends here
+
+
+
+# The ability to /climb/ the boulder isn’t immediately obvious, so let’s make it a bit of a secret:
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:6]]
+@desc boulder = A boulder with patches of moss and delicate clover.
+ A carved symbol and even some runes try to hide behind tendrils of ivy
+ as if keeping a secret. Wait! You notice a foot hold, and then
+ another. You can |bclimb|n this boulder!
+# Boulder:6 ends here
+
+
+
+# To take advantage of our [[Hidden Things]], we put the right tag on it:
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:7]]
+@lock boulder = view:tag(hidden_boulder, mp)
+# Boulder:7 ends here
+
+
+
+# And give it some aliases:
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:8]]
+@name boulder = boulder;climb;climb up
+# Boulder:8 ends here
+
+# [[file:../../../projects/mud.org::*Boulder][Boulder:9]]
+@set boulder/traverse_msg = "You move some ivy out of the way and pick your way up the boulder from one hold to another..."
+# Boulder:9 ends here
+
+
+
+# Let’s jump to the top of the boulder to proceed:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:1]]
+@teleport mp02
+# @name here = Top of Boulder;mp02
+# Top of Boulder:1 ends here
+
+
+
+# And the description:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:2]]
+@desc here = While high, the trees still tower over you. Still, a nice view. Lots of patches of moss to sit down and relax.
+# Top of Boulder:2 ends here
+
+
+
+# Describe the climb down:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:3]]
+@name climb = climb back down;climb down;climb;down
+# Top of Boulder:3 ends here
+
+
+# And a description of the climb:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:4]]
+@desc climb = The climb seemed easy, but you're not sure you can
+ handle the footholds going the other way around.
+# Top of Boulder:4 ends here
+
+
+# And describe the journey:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:5]]
+@set climb/traverse_msg = "You crawl backwards, feeling with your foot for a hold. Got it! Then another one...hoping the moss' rhizoids hold..."
+# Top of Boulder:5 ends here
+
+
+
+# Let’s make a nice spot to sit down on:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:6]]
+@create/drop moss;patch:typeclasses.sittables.Sittable
+# Top of Boulder:6 ends here
+
+
+# With a nice description:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:7]]
+@desc moss = A cushioned patch of moss of the most vibrant green.
+# Top of Boulder:7 ends here
+
+
+# Can’t take the moss with you:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:8]]
+@lock moss = get:false()
+# Top of Boulder:8 ends here
+
+
+# Can we hide it too?
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:9]]
+@lock moss = view:tag(hidden_moss, mp)
+# Top of Boulder:9 ends here
+
+
+# And a lovely message about why you can’t steal moss:
+
+# [[file:../../../projects/mud.org::*Top of Boulder][Top of Boulder:10]]
+@set moss/get_err_msg = The moss is a bryophyte, and as such, has
+ attached itself to boulder by rhizoids, which are one cell thick root
+ structures. While this helps with water absorption, you didn't think
+ you were going to get a biology lesson, did you? tl;dr You can't get it.
+# Top of Boulder:10 ends here
+
+
+# To the east, let’s make a nice meadow. Start at the Forest:
+
+# [[file:../../../projects/mud.org::*Field][Field:1]]
+@teleport mp01
+# Field:1 ends here
+
+
+
+# And we use the =tunnel= command this time:
+
+# [[file:../../../projects/mud.org::*Field][Field:2]]
+@tunnel e = path
+# Field:2 ends here
+
+
+# And a nice journey message to go east out of the forest:
+
+# [[file:../../../projects/mud.org::*Field][Field:3]]
+@set east/traverse_msg = "The mossy tree roots that borders this
+footpath begin to thin out to clover and flowers..."
+# Field:3 ends here
+
+
+
+# Before we label it, we follow =east=:
+
+# [[file:../../../projects/mud.org::*Field][Field:4]]
+east
+# Field:4 ends here
+
+
+
+# And we give this area a name:
+
+# [[file:../../../projects/mud.org::*Field][Field:5]]
+@name here = Meadow;mp05
+# Field:5 ends here
+
+
+
+# And a description that takes advantage of the time of day. We should get busy and get some seasonal description here.
+
+
+# [[file:../../../projects/mud.org::*Field][Field:6]]
+@desc here = The giant trees ring this meadow full of tall grass and
+ large yellow flowers, who nod their heads to you as you pass by in the
+ darkening twilightawakening
+ dawnlazy afternoonnight
+ sky. A gap in the trees show how you can return to the
+ footpath in the forest.
+# Field:6 ends here
+
+
+
+# Since we use the =tunnel= command, we need to update the weather system:
+
+# [[file:../../../projects/mud.org::*Field][Field:7]]
+@update here = typeclasses.rooms_weather.TimeWeatherRoom
+# Field:7 ends here
+
+
+# And describe the way out:
+
+# [[file:../../../projects/mud.org::*Field][Field:8]]
+@name west = footpath;forest;west
+# Field:8 ends here
+
+
+# Along with a mesage:
+
+# [[file:../../../projects/mud.org::*Field][Field:9]]
+@set footpath/traverse_msg = "You leave the open meadow for the footpath through the giant trees. The yellow flowers nod goodbye to you..."
+# Field:9 ends here
+
+
+# The dock leads out into the large pond. The break in the trees lets you see the sky. Looks like a nice place to relax.
+
+# Return to the Forest:
+
+# [[file:../../../projects/mud.org::*The Dock][The Dock:1]]
+@teleport mp01
+# The Dock:1 ends here
+
+
+# And tunnel to the pond:
+
+# [[file:../../../projects/mud.org::*The Dock][The Dock:2]]
+@tunnel s = dock
+# The Dock:2 ends here
+
+
+# With a mesage about leaving the trees so that I don’t have to repeat that in the room description:
+
+# [[file:../../../projects/mud.org::*The Dock][The Dock:3]]
+@set south/traverse_msg = "You follow a path down and step out from under giant trees to see the sky."
+# The Dock:3 ends here
+
+
+# And move ourselves there:
+
+# [[file:../../../projects/mud.org::*The Dock][The Dock:4]]
+south
+# The Dock:4 ends here
+
+
+
+# Since we are on a dock in the pond, we’ll emphasize that:
+
+# [[file:../../../projects/mud.org::*The Dock][The Dock:5]]
+@name here = Lazy Dock;mp06
+# The Dock:5 ends here
+
+
+# And describe this.
+
+# [[file:../../../projects/mud.org::*The Dock][The Dock:6]]
+@desc here = The dock you stand on juts into an immense pond. Someone has set a nice chair for viewing.
+# The Dock:6 ends here
+
+
+
+# And describe the walk back into the forest:
+
+# [[file:../../../projects/mud.org::*The Dock][The Dock:7]]
+@set north/traverse_msg = "You walk up a path and back into the forest of giant trees."
+# The Dock:7 ends here
+
+
+# A nice chair to sit on the dock by the bay, watching the clouds as they drift away.
+
+# [[file:../../../projects/mud.org::*Chair][Chair:1]]
+@create/drop chair;lounge chair:typeclasses.sittables.Sittable
+# Chair:1 ends here
+
+
+
+# How descriptive:
+
+# [[file:../../../projects/mud.org::*Chair][Chair:2]]
+@desc chair = A cushioned wood chair of craftsmanship.
+Looks good for being out in the weather.
+# Chair:2 ends here
+
+
+
+# Can’t steal this chair either:
+
+# [[file:../../../projects/mud.org::*Chair][Chair:3]]
+@lock chair = get:false()
+# Chair:3 ends here
+
+# [[file:../../../projects/mud.org::*Chair][Chair:4]]
+@set chair/get_err_msg = "It's way too heavy for you to lift."
+# Chair:4 ends here
+
+
+# And a fishing pole?
+
+# First we need to make a =PermanentObject= (from the regular =Object=), but one that offers a
+
+
+# [[file:../../../projects/mud.org::*Fishing Pole][Fishing Pole:1]]
+@create/drop fishing pole;pole:typeclasses.things.FishingPole
+@desc pole = A nice pole for catching fish. It even has a hook.
+@detail hook = One of those shiny lures, made from gold coins. Curiouser.
+@detail water = Despite the weather, the water looks fine. Perhaps there is a hole
+# Fishing Pole:1 ends here
+
+
+
+# Return to the forest:
+
+# [[file:../../../projects/mud.org::*Forest Path][Forest Path:1]]
+@teleport mp01
+# Forest Path:1 ends here
+
+
+# Let’s travel west along the path in the forest:
+
+# [[file:../../../projects/mud.org::*Forest Path][Forest Path:2]]
+@tunnel w = path
+# Forest Path:2 ends here
+
+
+# With a nice message about wandering:
+
+# [[file:../../../projects/mud.org::*Forest Path][Forest Path:3]]
+@set west/traverse_msg = "You meander between mossy tree roots along a
+footpath for a while..."
+# Forest Path:3 ends here
+
+
+# And some aliases:
+
+# [[file:../../../projects/mud.org::*Forest Path][Forest Path:4]]
+@name west = west;w;footpath
+# Forest Path:4 ends here
+
+
+
+# Jump into the new room:
+
+# [[file:../../../projects/mud.org::*Forest Path][Forest Path:5]]
+west
+# Forest Path:5 ends here
+
+
+# And name it:
+
+# [[file:../../../projects/mud.org::*Forest Path][Forest Path:6]]
+@name here = Forest Path;mp04
+# Forest Path:6 ends here
+
+
+
+# And describe the tree and the door:
+
+
+# [[file:../../../projects/mud.org::*Forest Path][Forest Path:7]]
+@desc here = Moss-covered tree roots border the meandering footpath. A
+ red door with a round top lies at the base of a giant tree, a carved
+ sign over it reads, Dabblers.
+# Forest Path:7 ends here
+
+
+# The knocker has the ability to make the door “open” using [[https://www.evennia.com/docs/latest/api/evennia.objects.objects.html][on_traverse]] hooks?
+
+# Most of the work of the /knocker/ is in the Python code:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:1]]
+@create/drop knocker:typeclasses.things.Knocker
+# Knocker:1 ends here
+
+
+# If it /looks/ like a goblin…
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:2]]
+@name knocker = door knocker;knocker;goblin
+# Knocker:2 ends here
+
+
+
+# The knocker shouldn’t be stealable:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:3]]
+@lock knocker = get:false()
+#
+@set knocker/get_err_msg = "It appears firmly attached to the door."
+# Knocker:3 ends here
+
+
+
+# Since we can remove the ring, let’s create it:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:4]]
+@create ring = typeclasses.things.Returnable
+# Knocker:4 ends here
+
+
+
+# And give it an alias:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:5]]
+@name ring = brass ring;ring
+# Knocker:5 ends here
+
+
+
+# And a description:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:6]]
+@desc ring = "A brass ring that should be in the door knocker's mouth.
+How else are you going to knock on a door?"
+# Knocker:6 ends here
+
+
+
+# Although we can interact with it, let’s not make it obvious that it is an object:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:7]]
+@lock ring = view:tag(hidden_ring, mp)
+# Knocker:7 ends here
+
+
+
+# And put the ring in the knocker’s mouth:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:8]]
+@give ring to knocker
+# Knocker:8 ends here
+
+
+
+# The description is dynamic from the Python code, so we don’t need this statement:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:9]]
+@desc knocker = The brass face looks at you and winks.
+# Knocker:9 ends here
+
+
+
+# In order for us to /knock/ on the door, we have to give a =knock= command to the /room/.
+
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:10]]
+@update here = typeclasses.rooms_weather.KnockableOutsideRoom
+# Knocker:10 ends here
+
+
+
+# The python object has all the knowledge about knocking and whatnot, but we should have the descriptions here. First, what the knocker reads:
+
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:11]]
+@set here/knock_msg = "You grab the ring and knock firmly on the door."
+# Knocker:11 ends here
+
+
+
+# Then, what the other’s in the room read:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:12]]
+@set here/knock_other_msg = "grabs the ring and knocks firmly on the door."
+# Knocker:12 ends here
+
+
+
+# And an error message if the ring is not with the goblin:
+
+# [[file:../../../projects/mud.org::*Knocker][Knocker:13]]
+@set here/knock_err_msg = "This door knocker is defective, as it
+doesn't have a ring to...er, do the knockin'."
+# Knocker:13 ends here
+
+# [[file:../../../projects/mud.org::*Cozy Tea House][Cozy Tea House:1]]
+@dig house = red door;door;house;inside,outside;leave
+# Cozy Tea House:1 ends here
+
+
+# The door description should match the artwork on the website:
+
+# [[file:../../../projects/mud.org::*Red Door][Red Door:1]]
+@desc red door = A painted red door where the round top of the door
+ fits snugly in the bark of the tree. Along with a large brass
+ doorknob, the door sports the most curious door knocker in the shape
+ of a goblin's face.
+# Red Door:1 ends here
+
+
+
+# This exit should be special. First, it is locked unless a character has the =open_red_door= tag.
+
+# [[file:../../../projects/mud.org::*Red Door][Red Door:2]]
+@lock door = traverse:tag(open_red_door, mp)
+# Red Door:2 ends here
+
+
+# Needs a message why one can’t walk through:
+
+# [[file:../../../projects/mud.org::*Red Door][Red Door:3]]
+@set door/err_traverse = "The door seems locked."
+# Red Door:3 ends here
+
+
+
+# If you do figure out how to get through the door:
+
+# [[file:../../../projects/mud.org::*Red Door][Red Door:4]]
+@set door/traverse_msg = "You stoop as you step through the doorway..."
+# Red Door:4 ends here
+
+
+
+# Fix the inside of the “House”:
+
+# [[file:../../../projects/mud.org::*Inside][Inside:1]]
+@teleport red door
+# Inside:1 ends here
+
+
+# Add the Python /special-ness/ for [[file:~/src/moss-n-puddles/typeclasses/rooms.py::class DabblersRoom(Room):][this room]]:
+
+# [[file:../../../projects/mud.org::*Inside][Inside:2]]
+@update here = typeclasses.rooms.DabblersRoom
+# Inside:2 ends here
+
+
+# Give it a title:
+
+# [[file:../../../projects/mud.org::*Inside][Inside:3]]
+@name here = Dabbler's House;mp03
+# Inside:3 ends here
+
+
+# Might as well stay a while:
+
+# [[file:../../../projects/mud.org::*Inside][Inside:4]]
+@sethome here
+# Inside:4 ends here
+
+
+
+# And the best description ever:
+
+# [[file:../../../projects/mud.org::*Inside][Inside:5]]
+@desc here = An enormous stone hearth overshadows this round room with
+ dark paneling. A subtle smell of tea and incense. Large, overstuffed
+ chairs sit invitingly by the fireplace. Oddly angled shelves with
+ books and knickknackery adorn the walls while a trolley supports a
+ large kettle, cups and scones.
+# Inside:5 ends here
+
+
+
+# Since we want the description to include the state of the fire, we need some /parts/ to assemble. Still not sure how this should be done.
+
+
+# [[file:../../../projects/mud.org::*Inside][Inside:6]]
+@set here/initial_desc = "You found a cozy, cornerless room."
+
+# Ravenous State
+@set here/fire_out = "The room is dim, but you see large, overstuffed chairs placed
+ by a dark fireplace in a large stone hearth. Perhaps you could light a fire?"
+
+# Hungry State
+@set here/fire_dim = "Large, overstuffed chairs sit invitingly close to the
+ dimly glowing embers in the fireplace of a stone hearth."
+
+# Fed State
+@set here/fire_on = "Large, overstuffed chairs sit invitingly by a fire casting
+ shadows that dance on the dark paneling."
+
+# Full State
+@set here/fire_full = "Large, overstuffed chairs slightly shield you
+ from the bright light of the roaring fire in the fireplace of a stone hearth."
+
+# And a final description:
+@set here/final_desc = "Oddly angled shelves with books and
+ knickknackery adorn the walls around a tapestry. The subtle smell of
+ wood smoke, incense and tea leads you to a trolley supporting a large
+ teapot, cups and freshly baked scones."
+# Inside:6 ends here
+
+
+
+# Granted, none of the dynamic description will work until we create the [[Fire]].
+
+# Let’s come up with a lot of descriptions:
+
+# [[file:../../../projects/mud.org::*Inside][Inside:7]]
+@detail tapestry = The muted colors of the tapestry either show its
+ age or its location over the sometimes smokey hearth. It shows a
+ gallant stag surrounded by woodland creatures. A racoon holds aloft a
+ gold box while a wolf has a gnarled staff. A raven perched on the
+ stag's antlers grips a blue ball in its beak.
+# Inside:7 ends here
+
+
+
+# We should describe all the objects in the tapestry, eh?
+
+# [[file:../../../projects/mud.org::*Inside][Inside:8]]
+@detail raven = The raven winks at you, and says, “Nevermore.” Really?
+ It probably didn't. Your eyes must be playing trickster.
+# Inside:8 ends here
+
+
+
+# And the other animals:
+
+# [[file:../../../projects/mud.org::*Inside][Inside:9]]
+@detail wolf = The gnarled staff the wolf holds looks a lot like the staff the gnome, Dabbler, has.
+#
+@detail stag = A majestic looking deer with gold fur and antlers that look more like tree branches.
+#
+@detail raccoon = The box he's holding seems to be a distraction, for he's pocketing a gold coin with his other paw.
+# Inside:9 ends here
+
+
+
+# Will this staff get confused at some point?
+
+# [[file:../../../projects/mud.org::*Inside][Inside:10]]
+@detail staff = A gnarled staff made of oak has a dark petina from age. Three small leaves has sprouted from the side.
+# Inside:10 ends here
+
+
+
+# Touch up the exit:
+
+# [[file:../../../projects/mud.org::*Inside][Inside:11]]
+@desc leave = A wood door with a large brass knob leads to the outside.
+#
+@set leave/traverse_msg = "You open the door and step outside..."
+# Inside:11 ends here
+
+
+# And a fire in the fireplace is a type of /pet/, since we can feed it:
+
+# [[file:../../../projects/mud.org::*Fireplace][Fireplace:1]]
+@create/drop fireplace : typeclasses.pets.Fire
+# Fireplace:1 ends here
+
+
+# How about some aliases:
+
+# [[file:../../../projects/mud.org::*Fireplace][Fireplace:2]]
+@name fireplace = fireplace;fire;embers
+# Fireplace:2 ends here
+
+
+# And a description that will be overridden:
+
+# [[file:../../../projects/mud.org::*Fireplace][Fireplace:3]]
+@desc fireplace = A stone fireplace with a carved wooden mantel supporting
+ small pictures, some books and a black statue.
+# Fireplace:3 ends here
+
+
+# Since we mentioned some details, we better mention them:
+
+# [[file:../../../projects/mud.org::*Fireplace][Fireplace:4]]
+@detail pictures = Two small framed pictures perch above the fireplace
+ mantle. One is of a satyr playing a saxophone, and the other is of a
+ fish with a big smile.
+# Fireplace:4 ends here
+
+
+
+# This reference in this detail is obviously, only for me:
+
+# [[file:../../../projects/mud.org::*Fireplace][Fireplace:5]]
+@detail statue = A small, black statue of a salamander curled around a
+ book, engraved with a title, "Seeing Stones".
+# Fireplace:5 ends here
+
+
+
+# I imagine a giant picture over the fireplace … need to do something interesting with it.
+
+# [[file:../../../projects/mud.org::*Fireplace][Fireplace:6]]
+@detail picture = Above the fireplace is a large, somewhat abstract
+ painting stretching its arm-like branches with shadowing that looks
+ like a yawn.
+# Fireplace:6 ends here
+
+
+# What sort of non-books do we want to look at?
+
+# In the meantime, let’s just have some descriptions:
+
+
+# [[file:../../../projects/mud.org::*Knickknacks][Knickknacks:1]]
+@detail knickknacks;things;knick-knacks;doodads;stuff;crap = An odd
+ assortment of knickknacks and doodads that decorate the minimal
+ space between the askewed books on the skewampus shelves.
+# Knickknacks:1 ends here
+
+
+
+# We mentioned shelves of books:
+
+# [[file:../../../projects/mud.org::*Books][Books:1]]
+@detail shelves;bookshelf;bookshelves = Shelves at various angles
+ embellish the walls of this small, cozy room. Leatherbound books
+ weigh each shelf, while some stacks of books support other shelves.
+ Dabbler has decorated some shelves with odd trinkets.
+# Books:1 ends here
+
+
+
+# This should be an object of “books”, but looking should pull up a random list of books.
+
+# [[file:../../../projects/mud.org::*Books][Books:2]]
+@create/drop books:typeclasses.readables.Books
+# Books:2 ends here
+
+
+# I start with the name, =books=, but when we look in the room, I want to see /collection/:
+
+# [[file:../../../projects/mud.org::*Books][Books:3]]
+@name books = collection of books;books
+# Books:3 ends here
+
+
+
+# With a good description:
+
+# [[file:../../../projects/mud.org::*Books][Books:4]]
+@desc books = Books of different sizes and colors. So many books.
+ Perhaps you want to simply |blook|n at a |bbook|n at random?
+# Books:4 ends here
+
+
+# We mentioned a /trolley/ with tea, cups and scones:
+
+# [[file:../../../projects/mud.org::*Tea Service][Tea Service:1]]
+@detail trolley = A tea trolley, complete with a small collection of
+ teacups, a magical teapot, as well as an assortment of scones and pastries.
+# Tea Service:1 ends here
+
+
+
+# The “room” gives the teacups, and the “teapot” fills the cups, so we just need descriptions:
+
+# [[file:../../../projects/mud.org::*Tea Service][Tea Service:2]]
+@detail teacups = An odd, yet interesting assortment of teacups. Take one.
+#
+@detail teacup = Each cup is unique. Take one to look further.
+# Tea Service:2 ends here
+
+
+
+# A [[file:~/src/moss-n-puddles/typeclasses/drinkables.py::class Teapot(Object):][teapot]] will contain a type of tea … either given or randomly chosen.
+# A teapot will “fill” a teacup forever, until it is “remade”.
+
+
+# [[file:../../../projects/mud.org::*Tea Service][Tea Service:3]]
+@create/drop teapot;pot;tea pot:typeclasses.drinkables.Teapot
+#
+@desc teapot = An adorable brown teapot, waiting for you to |bmake|n some tea.
+# Tea Service:3 ends here
+
+
+# And of course, you can’t steal the tea pot:
+
+# [[file:../../../projects/mud.org::*Tea Service][Tea Service:4]]
+@lock teapot = get:false()
+#
+@set teapot/get_err_msg = "You don't need to carry it around to make tea."
+# Tea Service:4 ends here
+
+
+# And [[file:~/src/moss-n-puddles/typeclasses/sittables.py::class Sittables(Sittable):][the chairs]] is a /plural/ location to sit, allowing anyone to sit down.
+
+
+# [[file:../../../projects/mud.org::*Chairs][Chairs:1]]
+@create/drop chairs:typeclasses.sittables.Sittables
+# Chairs:1 ends here
+
+
+# Add aliases afterwards:
+
+# [[file:../../../projects/mud.org::*Chairs][Chairs:2]]
+@name chairs = few overstuffed chairs;overstuffed chairs;chairs;chair
+# Chairs:2 ends here
+
+
+# And the description:
+
+# [[file:../../../projects/mud.org::*Chairs][Chairs:3]]
+@desc chairs = A few, dark leather, overstuffed chairs, each with a soft, colorful blanket and pillow. An invitation for a cozy nap.
+# Chairs:3 ends here
+
+
+
+# Can’t steal ‘em:
+
+# [[file:../../../projects/mud.org::*Chairs][Chairs:4]]
+@lock chairs = get:false()
+#
+@set chairs/get_err_msg = "It's way too heavy for you to lift."
+# Chairs:4 ends here
+
+
+
+# And textual descriptions the object can use:
+
+# [[file:../../../projects/mud.org::*Chairs][Chairs:5]]
+@set chairs/adjective = "in"
+#
+@set chairs/article = "the"
+#
+@set chairs/singular = "an overstuffed chair"
+#
+@set chairs/extra = "This feels [|very|quite] [nice|cozy|comfortable].|n"
+# Chairs:5 ends here