Adding dark rooms
Decided that a Fireplace that is both a Pet and a Lightsource doesn't get saved into the database. Might be a bug, but in the meantime, Dabbler's house will softly be lit.
This commit is contained in:
parent
af1655fadb
commit
cec2de230d
5 changed files with 411 additions and 7 deletions
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
|
||||
from commands.command import Command
|
||||
from evennia import CmdSet
|
||||
|
||||
|
|
|
|||
40
commands/lighting.py
Executable file
40
commands/lighting.py
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from commands.command import Command
|
||||
from evennia import CmdSet
|
||||
|
||||
class CmdLight(Command):
|
||||
"""
|
||||
Creates light where there was none. Something to burn.
|
||||
"""
|
||||
|
||||
key = "light"
|
||||
aliases = ["burn"]
|
||||
# only allow this command if command.obj is carried by caller.
|
||||
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]
|
||||
)
|
||||
else:
|
||||
self.caller.msg("%s is already burning." % self.obj.key)
|
||||
|
||||
|
||||
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())
|
||||
|
|
@ -9,7 +9,9 @@ with a location in the game world (like Characters, Rooms, Exits).
|
|||
"""
|
||||
|
||||
from evennia.objects.objects import DefaultObject
|
||||
from evennia.utils import delay, search
|
||||
|
||||
from commands.lighting import CmdSetLight
|
||||
|
||||
class ObjectParent:
|
||||
"""
|
||||
|
|
@ -215,3 +217,97 @@ class Object(ObjectParent, DefaultObject):
|
|||
"""
|
||||
|
||||
pass
|
||||
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Each level of pet requires more aspects for interaction.
|
|||
|
||||
"""
|
||||
|
||||
from typeclasses.objects import Object
|
||||
from typeclasses.objects import Object, LightSource
|
||||
from commands.feedables import CmdFeed, CmdFeedSet
|
||||
from utils.word_list import squish
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ class Pet(Object):
|
|||
# subscribe ourselves to a ticker to repeatedly call the hook
|
||||
# "update_weather" on this object. The interval is randomized
|
||||
# so as to not have all weather rooms update at the same time.
|
||||
self.db.interval = random.randint(50, 70)
|
||||
self.db.interval = random.randint(70, 90)
|
||||
TICKER_HANDLER.add(interval=self.db.interval, callback=self.update_hunger)
|
||||
|
||||
def update_hunger(self, *args, **kwargs):
|
||||
|
|
@ -120,11 +120,20 @@ class Pet(Object):
|
|||
"""
|
||||
curr = self.hunger()
|
||||
amount = kwargs.get('amount', self.hungry_rate)
|
||||
|
||||
# self.location.msg_contents(f"Feeding a pet: {amount} {isinstance(amount, str)}-> {self.db.hunger_level} / {self.hungry_rate}")
|
||||
|
||||
self.db.hunger_level = self.db.hunger_level + amount
|
||||
if self.db.hunger_level < 0:
|
||||
self.db.hunger_level = 0
|
||||
# if self.db.hunger_level < 5:
|
||||
# self.db.is_giving_light = False
|
||||
# else:
|
||||
# self.db.is_giving_light = True
|
||||
|
||||
if self.hunger() != curr:
|
||||
self.location.msg_contents("|w%s|n\n" % self.hunger_appearance())
|
||||
self.location.check_light_state()
|
||||
|
||||
def return_appearance(self, looker, **kwargs):
|
||||
"""
|
||||
|
|
@ -138,6 +147,11 @@ class Pet(Object):
|
|||
"""
|
||||
return f"{self.db.desc} {self.hunger_appearance()}"
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Fireplace, both a pet that is hungry and consumes food,
|
||||
# but also emits light when it isn't starving.
|
||||
# ------------------------------------------------------------
|
||||
|
||||
class Fire(Pet):
|
||||
"""
|
||||
Fire in this world, is a cute fireplace pet.
|
||||
|
|
@ -164,8 +178,6 @@ class Fire(Pet):
|
|||
}
|
||||
|
||||
def feed(self, feeder, args):
|
||||
self.update_hunger(feeder=feeder, amount=300)
|
||||
|
||||
now = time()
|
||||
last_fed = feeder.ndb.fire_last_fed # could be None
|
||||
if last_fed and (now - last_fed < 30):
|
||||
|
|
@ -181,6 +193,15 @@ class Fire(Pet):
|
|||
get_up = "get up and"
|
||||
gets_up = "gets up and"
|
||||
|
||||
feeder.msg(squish(f"You {get_up} put some {adj} wood on the fire in the fireplace."))
|
||||
self.location.msg_contents(squish(f"{feeder.name} {gets_up} puts {adj} wood on the fire."),
|
||||
if self.db.hunger_level < 5:
|
||||
feeder.msg(squish(f"You {get_up} put some {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 "
|
||||
f"fire in the fireplace."))
|
||||
self.location.msg_contents(squish(f"{feeder.name} {gets_up} puts {adj} wood on the fire."),
|
||||
exclude=feeder)
|
||||
|
||||
self.update_hunger(feeder=feeder, amount=300)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ Rooms are simple containers that has no location of their own.
|
|||
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
|
||||
from .objects import LightSource
|
||||
|
||||
import random
|
||||
|
||||
# the system error-handling module is defined in the settings. We load the
|
||||
# given setting here using utils.object_from_module. This way we can use
|
||||
# it regardless of if we change settings later.
|
||||
|
|
@ -45,3 +49,247 @@ class Room(ObjectParent, DefaultRoom):
|
|||
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 _heal(self, character):
|
||||
"""
|
||||
Heal a character.
|
||||
"""
|
||||
health = character.db.health_max or 20
|
||||
character.db.health = health
|
||||
|
||||
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):
|
||||
pass
|
||||
|
|
|
|||
Loading…
Reference in a new issue