Create a fishing pole ... and fish to be caught

This commit is contained in:
Howard Abrams 2025-02-17 13:31:02 -08:00
parent 5871745adc
commit 8fe6d6b8fa
4 changed files with 351 additions and 1 deletions

225
typeclasses/fishing.py Executable file
View file

@ -0,0 +1,225 @@
#!/usr/bin/env python
from evennia import (
Command,
CmdSet,
TICKER_HANDLER,
syscmdkeys,
create_script,
)
from evennia.prototypes.spawner import spawn
from enum import Enum
from urllib.request import urlopen
from typeclasses.objects import Object
from typeclasses.npcs import CarriableNPC
from utils.word_list import routput
import random
import requests
class Fishing(Enum):
"The acceptable states of fishing."
CAST = True
REEL = False
class CmdThrowFish(Command):
"""
Throw the fish back in the water.
"""
key = "throw"
def func(self):
self.obj.do_delete(self.caller)
class CmdSetFish(CmdSet):
def at_cmdset_creation(self):
self.add(CmdThrowFish)
class CmdCast(Command):
"""
Cast the pole.
"""
key = "cast"
def func(self):
self.obj.do_cast(self.caller)
class CmdReel(Command):
"""
Reel the pole.
"""
key = "reel"
def func(self):
self.obj.do_reel(self.caller)
class CmdSetFishing(CmdSet):
def at_cmdset_creation(self):
self.add(CmdCast)
self.add(CmdReel)
class Fish(CarriableNPC):
"""
Everyone wants a fish that tells dad jokes, right!?
"""
# The number of seconds to check for the time for a message:
fish_tick = 3
def at_object_creation(self):
self.cmdset.add_default(CmdSetFish)
self.db.name = Fish.get_name()
self.db.spoken = 0
TICKER_HANDLER.add(interval=self.fish_tick,
callback=self.do_speak)
def at_heard_say(self, message, from_obj):
"""
A simple listener and response. This makes it easy to change for
subclasses of NPCs reacting differently to says.
"""
return "say", "What was that? I must have water in my ear."
def do_speak(self):
"""
Called at a repeatable sequence by the ticker, and
it calls at_say() in order to do a type of monologue.
"""
if self.db.spoken == 1:
self.at_say("Whew! Thanks for removing the sharp hook. Not sure how it got on that.") # Change
elif self.db.spoken == 5:
self.at_say(f"My name's {self.db.name}. What's yours?")
self.db.desc = f"{self.db.name}. {self.db.desc}"
elif self.db.spoken == 10:
self.at_say("Did you say something? I think I have water in my ears, as I can't hear a thing.")
elif self.db.spoken == 20:
self.at_say("So... how're you getting along?")
elif self.db.spoken == 30:
self.at_say("Right, right. Still can't hear a things. Hrm.")
elif self.db.spoken == 60:
self.at_say("Do you know why we fish are so easy to weigh? ")
elif self.db.spoken == 61:
self.at_say("Because we have are own scales.")
elif self.db.spoken == 70:
self.at_say("I suppose you could |bthrow|n me back in the water at any time.")
elif self.db.spoken == 120:
self.at_say("I suppose I should pay you back for helping me out with that hook thing. I guess you know I keep all my money at ... the riverbank.")
elif self.db.spoken == 200:
self.at_say("You know the easiest way to catch a fish, right?")
elif self.db.spoken == 201:
self.at_say("Have someone toss it to you.")
elif self.db.spoken == 205:
self.at_say("Ouch. Tough crowd.")
elif self.db.spoken == 300:
self.at_say("Me and my friends started a musical band.")
elif self.db.spoken == 301:
self.at_say("We all play bass.")
elif self.db.spoken == 302:
self.at_say("Alright guys, I said, drop the instruments. We are singing aquapella.")
elif self.db.spoken == 400:
self.at_say("Did you meet the owners of that new fishing store?")
elif self.db.spoken == 401:
self.at_say("Their names are Rod and Annette.")
elif self.db.spoken == 800:
self.at_say("I'm not that smart.")
elif self.db.spoken == 801:
self.at_say("My friends tell me I'm a dumb bass.")
elif self.db.spoken == 803:
self.at_say("Sorry for all the puns. I feel so GILL-ty.")
elif self.db.spoken > 1000 and self.db.spoken % (24 * 60 * 60 / self.fish_tick):
self.at_say(get_joke())
self.db.spoken += 1
def do_delete(self, fisher):
"""
A visual way to delete the fish.
"""
if fisher.location == fisher.search("mp06"):
fisher.msg(routput("You [toss|heave|throw] the fish back into the [water|pond]."))
fisher.msg(routput("The fish says, \"Bye for now. If you want to talk again, just drop me a line!\""))
else:
fisher.msg(routput("You [toss|heave|throw] the fish, and an eagle swoops down and snatches it. I'm sure it will carry the fish back to the pond for you."))
self.delete()
def get_name():
return random.choice([
"Bennie", "Flipper", "Finegan", "Count Bassie"
])
def get_desc():
return routput(random.choice([
"A walleye with big bulbous eyes that clearly doesn't get no respect.",
"A bass with amazing neck confidence giving it a most excellent head bob."
"A rainbow trout missing the [red|yellow|green|blue] from its iridescent stripe.",
"A brown trout colored [red|blue|purple|orange].",
# "A spiny perch",
# "A salmon",
# "A pike",
]))
def get_joke():
"Fetch a random joke from the internet."
r = requests.get("https://icanhazdadjoke.com/",
headers={'Accept':'text/plain'})
return r.text
class FishingPole(Object):
"""
Can produce a Fish.
"""
failure_msgs = [
"You reel in an empty line.",
"You didn't catch anything.",
"Caught nothing but a bit of weeds, yeck.",
"Caught nothing, but this sure is enjoyable.",
"Did you catch a boot? Nah, it isn't even that good.",
"Anything better that sitting on the dock of the pond?",
]
def at_object_creation(self):
self.cmdset.add_default(CmdSetFishing)
def do_cast(self, fisher):
if fisher.location != fisher.search("mp06"):
fisher.msg("You can't do that without being near the pond.")
elif fisher.db.fishing == Fishing.CAST:
fisher.msg("You need to reel the line in first.")
else:
fisher.db.fishing = Fishing.CAST
fisher.msg(routput(random.choice([
"You cast out far into the [water|pond].",
"You cast close to the [dock|shore].",
"You cast off to the [right|left] where you [think you|] see a dark pocket.",
])))
def do_reel(self, fisher):
if fisher.db.fishing != Fishing.CAST:
fisher.msg("You need to |bcast|n before you can reel the line back in.")
else:
fisher.db.fishing = Fishing.REEL
if random.randint(1, 100) < 35:
self.give_fish(fisher)
else:
fisher.msg(random.choice(self.failure_msgs))
def give_fish(self, fisher):
fish = spawn({
"typeclass": self.db.make_class or "typeclasses.fishing.Fish",
"key": "fish",
"aliases": [Fish.get_name()],
"desc": Fish.get_desc(),
})[0]
fish.location = fisher
fisher.msg(f"You caught a fish!")

70
typeclasses/npcs.py Executable file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env python
from typeclasses.objects import Object
from utils.word_list import routput
class NPC(Object):
"""
An NPC is an NPC because it can react to what it _hears_.
To do this, implement 'at_heard_say', a function that
returns a string for a response.
"""
def at_heard_say(self, message, from_obj,
is_say=True, is_whisper=False):
"Override to return a string in response to message."
pass
def msg(self, text=None, from_obj=None, **kwargs):
"Custom msg() method reacting to say."
# make sure to not repeat what we ourselves said or we'll
# create a loop
if from_obj != self:
is_say = False
is_whisper = False
try:
say_text, is_say = text[0], text[1]['type'] == 'say'
is_whisper = text[1]['type'] == 'whisper'
except Exception:
pass
try:
# message will be on the form `<Person> says, "say_text"`
# we want to get only say_text without the quotes and any spaces
message = message.split('says, ')[1].strip(' "')
shout, response = self.at_heard_say(message, from_obj,
is_say, is_whisper)
if response != None:
self.at_say(response, just_owner=(shout == "shout"))
except Exception:
pass
# this is needed if anyone ever puppets this NPC - without it
# you would never get any feedback from the server (not even
# the results of look)
super().msg(text=text, from_obj=from_obj, **kwargs)
class CarriableNPC(NPC):
"""
A carriable NPC is like any other NPC, except that since it can
be carried and isn't locked down, it can't hear conversation in a
room.
"""
def at_say(self, message, msg_self=None, msg_location=None,
receivers=None, msg_receivers=None, just_owner=True,
**kwargs):
"Does the best it can to speak out loud."
owner = self.location
if self.location.is_typeclass("typeclasses.rooms.Room"):
super().at_say(message, msg_self=msg_self,
msg_location=msg_location,
receivers=receivers,
msg_receivers=msg_receivers)
elif just_owner:
owner.msg(f"The {self.name} says, \"{message}\"")
else:
owner.msg(
f"The {self.name}, you are carrying, says, \"{message}\"")
owner.location.msg(
f"The {self.name}, carried by {self.location.name}, says, \"{message}\"", exclude=owner)

View file

@ -136,7 +136,7 @@ class TimeWeatherRoom(Room):
# so as to not have all weather rooms update at the same time. # 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(50, 70)
TICKER_HANDLER.add( TICKER_HANDLER.add(
interval=self.db.interval, callback=self.update_weather, idstring="tutorial" interval=self.db.interval, callback=self.update_weather
) )
# this is parsed by the 'tutorial' command on TutorialRooms. # this is parsed by the 'tutorial' command on TutorialRooms.
self.db.tutorial_info = "This room has a Script running that has it echo a weather-related message at irregular intervals." self.db.tutorial_info = "This room has a Script running that has it echo a weather-related message at irregular intervals."

View file

@ -429,6 +429,61 @@ Looks good for being out in the weather.
# With a description:
# [[file:../../../projects/mud.org::*Fishing Pole][Fishing Pole:2]]
@desc pole = A nice pole for catching fish. It even has a hook, and is ready to go!
# Fishing Pole:2 ends here
# What about some details:
# [[file:../../../projects/mud.org::*Fishing Pole][Fishing Pole:3]]
@detail hook;bait = One of those shiny lures, made from gold coins. Curiouser.
#
@detail water;waves = Despite the weather, the water looks nice...well, nice for fishing.
#
@detail dock = Sturdy and well made. Bobs a little with the waves.
# Fishing Pole:3 ends here
# Need to make the fishing pole “stay” at the Dock. Maybe with a message about sticking around for the next person.
# [[file:../../../projects/mud.org::*Fishing Pole][Fishing Pole:4]]
@create/drop sign:typeclasses.readables.Readable
# Fishing Pole:4 ends here
# Should the description also be the message?
# [[file:../../../projects/mud.org::*Fishing Pole][Fishing Pole:5]]
@desc sign = You see a wood sign tied with a rope around the back of the chair. It reads, |wFish at your own annoyance. Please return pole when finished.|n
# Fishing Pole:5 ends here
# Might as well allow the user to read it:
# [[file:../../../projects/mud.org::*Fishing Pole][Fishing Pole:6]]
@set sign/inside = "Fish at your own annoyance. Please return pole when finished."
# Fishing Pole:6 ends here
# And lock down the sign:
# [[file:../../../projects/mud.org::*Fishing Pole][Fishing Pole:7]]
@lock sign = get:false()
#
@set sign/get_err_msg = "This granny knot holding the sign to the chair is serious. You can't take it."
# Fishing Pole:7 ends here
# Return to the forest: # Return to the forest:
# [[file:../../../projects/mud.org::*Forest Path][Forest Path:1]] # [[file:../../../projects/mud.org::*Forest Path][Forest Path:1]]