We now have a living Big Hairy Beast

This commit is contained in:
Howard Abrams 2025-04-13 21:43:26 -07:00
parent 588ffcdab8
commit 45959b01ce
9 changed files with 668 additions and 219 deletions

View file

@ -24,7 +24,6 @@ class CmdFeed(Command, NumberedTargetCommand):
self.caller.msg("Feed what?")
elif not self.rhs:
target = self.caller.search(self.lhs)
self.caller.msg(f"Feeding {target}")
if target:
target.feed(self.caller)
else:

View file

@ -90,8 +90,6 @@ class Character(Object, DefaultCharacter):
return
self.msg(f"{victim.key} doesn't have a {to_take} you can take.")
else:
self.msg(f"You don't see {from_whom}.")
def at_pre_move(self, destination, **kwargs):
"""
@ -143,12 +141,12 @@ class Character(Object, DefaultCharacter):
Where thing is the single name of the hidden object.
"""
hidden_tag = f"hidden_{target}"
lock_string = f"view:tag({hidden_tag}, mp)"
hidden_tag = target.db.hidden_tag or f"hidden_{target}"
lock_string = f"view:tag({hidden_tag})"
view_lock = target.locks.get("view")
if view_lock == lock_string:
self.tags.add(hidden_tag, category="mp")
self.tags.add(hidden_tag)
if target.is_typeclass("typeclasses.rooms.Room"):
self.db.tutorstate = self.db.tutorstate | TutorialState.LOOK.value

View file

@ -1,5 +1,8 @@
#!/usr/bin/env python
import datetime
from evennia import gametime
from evennia.utils import delay, logger
"""

View file

@ -16,12 +16,13 @@ from time import time
import random
from evennia import TICKER_HANDLER
from evennia.utils.gametime import schedule
from typeclasses.objects import Object
from typeclasses.characters import Character
# from typeclasses.lightables import LightSource
from commands.feedables import CmdFeedSet
from utils.word_list import squish, choices
from utils.word_list import squish, choices, split_party_msg
class Hunger(Enum):
@ -67,11 +68,8 @@ class Pet(Object):
self.db.hunger_level = Hunger.FULL.value
# 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(70, 90)
TICKER_HANDLER.add(interval=self.db.interval, callback=self.update_state)
# Update the state (whatever that may mean), each minute:
TICKER_HANDLER.add(interval=60, callback=self.update_state)
super().at_object_creation()
@ -92,9 +90,9 @@ class Pet(Object):
def update_state(self, *args, **kwargs):
"""
Called by the tickerhandler at regular intervals. Even so, we
Called by the TickerHandler at regular intervals. Even so, we
only update at a hungry_rate of the time, picking a random weather message
when we do. The tickerhandler requires that this hook accepts
when we do. The TickerHandler requires that this hook accepts
any arguments and keyword arguments (hence the *args, **kwargs
even though we don't actually use them in this example)
"""
@ -195,6 +193,9 @@ class Fire(Pet):
return True
# ----------------------------------------------------------------------
# Migrating Pets move from room to room during particular times
# ----------------------------------------------------------------------
# Friendly
@ -219,6 +220,9 @@ class Friendly(Pet):
"""
super().at_object_creation()
# We have a list of actions that were spammed to the room:
self.db.last_actions = []
# The higher this value the more "spammy" a pet is in making
# comments in the room:
self.db.active_amount = 3
@ -374,10 +378,8 @@ class Friendly(Pet):
self.adjust_all_locally(self.db.shyness_amount or 5)
# How spammy do we want the pet to be?
if random.randint(0, 100) < self.db.active_amount:
if random.randint(0, 100) < (self.db.active_amount or 3):
self.do_action()
else:
print("Nope")
def do_action(self):
# Do something based on the highest friendly level is the same area!
@ -396,8 +398,146 @@ class Friendly(Pet):
# If we have an ecstatic message, use it otherwise, grab the friendly:
msg = choices(self.db.ecstatic_actions or self.db.friendly_actions)
focus.msg(
sub("<You>", "You", sub("<you>", "you", msg))
)
self.location.msg_contents(sub("<[Yy]ou>", focus.name.title(), msg),
exclude=focus)
if msg and msg not in self.db.last_actions:
self.db.last_actions.append(msg)
# Limit this list so it doesn't grow too large:
self.db.last_actions = self.db.last_actions[-5:]
split_party_msg(focus, msg)
class BHB(Friendly):
def return_appearance(self, looker):
if self.db.is_awake:
return super().return_appearance(looker)
else:
return "It currently slumbers on its huge mattress."
def update_state(self):
wake_hour = self.db.wake_hour or 8
sleep_hour = self.db.sleep_hour or 20
(minute, hour, tod, season) = self.location.get_time()
if hour >= wake_hour and hour < sleep_hour and minute > 6:
self.db.is_awake = True
else:
self.db.is_awake = False
noun = random.choice(["beast", "BHB", "monstrous beast",
"big, hairy beast"])
if hour == wake_hour:
if minute == 0:
msg = f"The {noun} begins to stir from slumber."
elif minute == 3:
msg = f"The {noun} rises and stretches ..."
elif minute == 5:
msg = f"The {noun} lets loose a big yawn."
if hour == sleep_hour:
if minute == 0:
msg = f"The {noun} look up at the darkening sky."
elif minute == 3:
msg = f"The {noun} lets loose a big yawn."
if self.db.is_awake and self.location == self.global_search('The Cave'):
self.execute_cmd("leave")
elif not self.db.is_awake and self.location == self.global_search('Meadow'):
self.execute_cmd("cave")
if msg and self.location:
self.location.msg_contents(msg)
else:
# This might call the do_action():
super().update_state(*args, **kwargs)
def do_action(self):
"""
We only override the Friendly 'do_action' if the beast is
asleep on its huge pilla'.
"""
if self.db.is_awake:
super().do_action()
else:
msg = choices(self.db.sleeping_actions)
if msg != self.db.last_actions:
self.db.last_actions.append(msg)
# Limit this list so it doesn't grow too large:
self.db.last_actions = self.db.last_actions[-5:]
self.location.msg_contents(msg)
def feed(self, feeder, item=None):
"""
Feeding the beast. If item is None, we choose something
the character has, and go with that...
"""
# Categorize items that can be used to feed the beast:
def is_berry(item):
return (not item and feeder.has("berries")) or (item and item.key == 'berries')
def is_scone(item):
return (not item and feeder.has("scone")) or (item and item.key == 'scone')
# Based on the reaction to the feeder, the adjectives may alter:
noun = "The " + random.choice(["huge", "big, hairy"]) + " beast"
match self.friendly_reaction(feeder):
case Reaction.SCARED:
how_sniff = "carefully"
how_eat = "cautiously"
and_then = "an then runs away to the edge of the meadow"
case Reaction.CONCERNED:
how_sniff = "cautiously"
how_eat = "gingerly"
and_then = "slowly backs away to a safe distance"
case Reaction.INTERESTED:
how_sniff = "curiously"
how_eat = "quickly"
and_then = "waits to see what else you might have"
case _:
how_sniff = "eagerly"
how_eat = "excitedly"
and_then = "gives you a big lick as if to say, Thank you"
if is_scone(item):
msg = f"{noun} {how_sniff} sniffs your outstretched arm holding the scone. It {how_eat} eats it, and {and_then}."
self.adjust_character(feeder, 100)
feeder.has('scone').delete()
elif is_berry(item):
msg = f"{noun} {how_sniff} sniffs your outstretched arm with a handful of berries. It {how_eat} eats them, and {and_then}."
self.adjust_character(feeder, 40)
feeder.has('berries').delete()
else:
msg = f"{noun} doesn't appear interesting in anything you have."
feeder.msg(msg)
def thrown_stick(self, thrower):
"""
Called when thrower is in the vicinity of the beast, and
throws a stick they may have.
"""
match self.friendly_reaction(thrower):
case Reaction.SCARED:
msg = "The beast runs away when you throw the stick."
case Reaction.CONCERNED:
msg = "The beast walks over to the stick, sniffs it, and backs away."
self.adjust_character(thrower, 5)
case Reaction.INTERESTED:
msg = choices("""
The beast [runs|hurries] at the stick, then looks at <you>, [wondering|curious as to|pondering] what to do with a stick the flies back to its owner. ;;
The beast [rushes|runs at] the stick, and then veers off, thundering around the [field|meadow]. The stick returns to your hand.
""")
self.adjust_character(thrower, 30)
case _:
msg = choices("""
The [big hairy|] beast [dashes|runs|jumps|leaps into the air|leaps] and [catches|snags|grabs] the stick in midair, and drops it in front of <you>. ;;
The [big hairy|] beast [gallops|trumbles|trots] over to the stick and [dances|prances|hops] around the stick before bringing it back to <you>. ;;
The [big hairy|] beast [gallops|trumbles|trots] over to the stick and [dances|prances|hops] around the stick before bringing it back to <you>. ;;
""")
self.adjust_character(thrower, 10)
if msg:
split_party_msg(thrower, msg)

View file

@ -5,7 +5,10 @@ Rooms are simple containers that has no location of their own.
"""
import datetime
from evennia import utils
from evennia.utils import gametime
from evennia.contrib.grid.extended_room import ExtendedRoom
from django.conf import settings
@ -30,6 +33,16 @@ class Room(ObjectParent, ExtendedRoom):
is_dark = False
has_weather = False
def get_time(self):
"""
Return tuple of current time of this location.
(Minute, Hour, Time of Day, Season)
"""
timestamp = gametime.gametime(absolute=True)
datestamp = datetime.datetime.fromtimestamp(timestamp)
return (datestamp.minute, datestamp.hour,
self.get_time_of_day(), self.get_season())
class DabblersRoom(Room):
def get_display_desc(self, looker):

View file

@ -22,8 +22,8 @@ TIMEBASE_WEATHER_MSGS = [
"The clouds part and you can see stars glitter against the black velvet night.",
"You hear distant howls as the moon appears.",
# Cloudy
"The clouds gather making the night dark and hard to see."
"The rain slows while ghostly mists weave through the trees."
"The clouds gather making the night dark and hard to see.",
"The rain slows while ghostly mists weave through the trees.",
# Rainy
"The rain coming down from the inky, black sky intensifies.",
"For a moment it looks like the rain is slowing, then it begins anew with renewed force.",
@ -38,19 +38,19 @@ TIMEBASE_WEATHER_MSGS = [
# Morning
[ # Clear
"The clouds part creating a dazzling sunrise of colors.",
"Bird song fills the air with the dawn chorus."
"Bird song fills the air with the dawn chorus.",
# Cloudy
"The clouds accumulate making the morning last."
"The rain slows to a drizzle as the moss begins to glow in the brightening light."
"The clouds accumulate making the morning last.",
"The rain slows to a drizzle as the moss begins to glow in the brightening light.",
# Rainy
"The falling rain intensifies creating vibrating puddles.",
"The morning breeze smells sweet."
"The morning breeze smells sweet.",
# Windy
"The wind is picking up. Today may be a good day for a kite.",
],
# Noon
[
"The clouds part and the sun illuminates the scene. The morning's rain evaporates, warming the air."
"The clouds part and the sun illuminates the scene. The morning's rain evaporates, warming the air.",
],
# Afternoon
[
@ -62,10 +62,10 @@ TIMEBASE_WEATHER_MSGS = [
[
# Clear
"The clouds part creating a dazzling sunset of colors.",
"Bird song fills the air with the evening chorus."
"Bird song fills the air with the evening chorus.",
# Cloudy
"The clouds accumulate. Night is approaching."
"The rain slows while ghostly mist weave through the darkening gloom."
"The clouds accumulate. Night is approaching.",
"The rain slows while ghostly mist weave through the darkening gloom.",
# Rainy
"The falling rain intensifies making the evening somber.",
"For a moment it looks like the rain is slowing, then it begins anew with renewed force.",

View file

@ -42,8 +42,12 @@ class Stick(Object):
self.cmdset.add_default(CmdSetStick)
def do_throw(self, thrower):
beast = thrower.location.search('beast')
if beast and beast.db.is_awake:
beast.thrown_stick(thrower)
elif thrower.location.is_typeclass("typeclasses.rooms_weather.TimeWeatherRoom"):
thrower.db.thrown_times = (thrower.db.thrown_times or 0) + 1
if thrower.db.jumped_times == 1:
if thrower.db.thrown_times == 1:
thrower.msg("The stick flies through the air, and just as you thought, it comes back to you!")
else:
thrower.msg(routput(choice([
@ -54,6 +58,8 @@ class Stick(Object):
])))
self.location.msg_contents(f"{thrower.name} throws a stick.",
exclude=thrower)
else:
thrower.msg("I think you should be outside or a place with more room before you throw that stick around.")
class Puddle(Object):

View file

@ -22,6 +22,7 @@ def routput(text, substitution=None):
'This feels cozy.'
'This feels very comfortable.'
"""
if text:
acc = []
for s in text.split("["):
selections, *rest = s.split("]")
@ -35,12 +36,21 @@ def choices(text, substitution=None):
"""
Returns a section of 'text' based on separating semicolons.
"""
if text:
selections = text.split(';;')
return routput(random.choice(selections), substitution)
def split_party(text, viewer):
return text.sub(r"<you>", viewer.name)
def split_party_msg(viewer, msg):
# First a message for the view:
viewer.msg(
re.sub("<You>", "You", re.sub("<you>", "you", msg))
)
# Then the message for the rest of the area:
viewer.location.msg_contents(re.sub("<[Yy]ou>",
viewer.name.title(), msg),
exclude=viewer)
# def searsonal(text, **kwargs):
# season = kwargs['season'] or kwargs['location'].get_season()
# time_of_day = kwargs['time_of_day'] or kwargs['location'].get_time_of_day()

File diff suppressed because it is too large Load diff