325 lines
13 KiB
Python
Executable file
325 lines
13 KiB
Python
Executable file
#!/usr/bin/python
|
||
|
||
from datetime import datetime
|
||
from random import choice
|
||
from re import sub
|
||
|
||
from evennia.utils import logger
|
||
from evennia.prototypes.spawner import spawn
|
||
|
||
from typeclasses.objects import Object
|
||
from typeclasses.consumables import Producer
|
||
from utils.scoring import Scores
|
||
from utils.user_info import location
|
||
from utils.word_list import routput
|
||
|
||
from commands.misc import CmdSetWrite
|
||
|
||
|
||
class Readable(Object):
|
||
"""
|
||
Create with a command:
|
||
|
||
@create/drop sign : typeclasses.readables.Readable
|
||
@desc sign = A tall wooden post.
|
||
@set sign/inside = "What happens when you read it."
|
||
"""
|
||
|
||
|
||
class Letter(Readable):
|
||
"""
|
||
Something that can be read, but is _personal_, so if it is
|
||
dropped, it is destroyed.
|
||
"""
|
||
def at_pre_drop(self, dropper, **kwargs):
|
||
dropper.msg(f"You throw your {self.get_display_name(dropper)} away.")
|
||
self.delete()
|
||
|
||
|
||
class Book(Readable):
|
||
"""
|
||
A book is a readable thing (has an 'inside' attribute),
|
||
but if dropped near a bookshelf, it is deleted instead of left.
|
||
"""
|
||
def do_read(self, reader):
|
||
"""
|
||
Announces reading this book.
|
||
Doesn't tell the 'reader' what is inside, however. ;-)
|
||
"""
|
||
reader.announce_action("$You() $conj(read) $pron(your) book.")
|
||
|
||
def at_pre_drop(self, dropper, **kwargs):
|
||
"""
|
||
If book is dropped next to a Bookshelf (that created it),
|
||
the book is _returned_ (that is, deleted).
|
||
"""
|
||
if dropper.location.key == "Cozy House":
|
||
dropper.msg("You drop the book, but flapping its pages, flies back to its section on a shelf.")
|
||
self.delete()
|
||
return False
|
||
|
||
shelf = dropper.search_first("", quiet=True,
|
||
typeclass="typeclasses.readables.Bookshelf")
|
||
if shelf:
|
||
dropper.announce_action(f"$You() $conj(return) a book to {shelf.name}.")
|
||
self.delete()
|
||
return False
|
||
|
||
return True
|
||
|
||
|
||
class WriteableBook(Book):
|
||
"""
|
||
Along with reading, the user can write in this book.
|
||
|
||
Actually, append to the text file associated with it.
|
||
"""
|
||
def at_object_creation(self):
|
||
"""
|
||
called at creation
|
||
"""
|
||
self.cmdset.add_default(CmdSetWrite)
|
||
|
||
def do_read(self, reader):
|
||
"""
|
||
Announces reading this book.
|
||
Doesn't tell the 'reader' what is inside, however. ;-)
|
||
"""
|
||
reader.announce_action(f"$You() $conj(read) the {self.name}.")
|
||
|
||
def do_write_begin(self, writer):
|
||
"""
|
||
Announces writing in this.
|
||
"""
|
||
writer.announce_action(
|
||
self.db.pre_write_msg or
|
||
"$You() $conj(grab) the quill, $conj(dip) it in the ink well, "
|
||
"and $conj(begin) to pen a message in the book...")
|
||
writer.msg("|/<< Pardon the quaint, twentieth-century approach to "
|
||
"capturing your prose (but this works with all MUD "
|
||
"clients). Easiest approach: just type your message, hit "
|
||
"the |wReturn|n key, type |g:q|n, and his the "
|
||
"|wReturn|n key again...>>|/")
|
||
return ""
|
||
|
||
def do_write(self, writer, message):
|
||
"""
|
||
Stores the message and metadata in file mentioned 'inside'.
|
||
This assumes the object's 'inside' starts with 'file:', as in:
|
||
|
||
@set book/inside = "file:somefile.md"
|
||
"""
|
||
session = writer.sessions.get()[0]
|
||
contents = self.db.inside
|
||
|
||
if contents.startswith("file:"):
|
||
filename = contents[5:]
|
||
with open(filename, "a") as myfile:
|
||
myfile.write(f"# {writer.name}\n\n")
|
||
myfile.write(message)
|
||
myfile.write("\n\n")
|
||
myfile.write(f"> User: {writer.account.name}\n")
|
||
myfile.write(f"> Desc: {writer.db._sdesc}\n")
|
||
|
||
(city, region, country, _) = location(self)
|
||
if country:
|
||
myfile.write(f"> From: {city}, {region}, {country}\n")
|
||
else:
|
||
myfile.write("> From: localhost\n")
|
||
myfile.write(f"> Cmds: {session.cmd_total}\n\n")
|
||
|
||
def do_write_end(self, writer):
|
||
"""
|
||
Announces the end of writing.
|
||
"""
|
||
writer.announce_action(
|
||
self.db.post_write_msg or
|
||
"$You() $conj(put) down the quill, and $conj(stop) writing.")
|
||
|
||
writer.score(Scores.sign_guest_book)
|
||
|
||
class Journal(WriteableBook):
|
||
"""
|
||
A special writeable book, that stores the contents
|
||
in a different file based on the date.
|
||
"""
|
||
def do_write(self, writer, message):
|
||
"""
|
||
Change the self.db.inside to a date.
|
||
"""
|
||
today = datetime.now()
|
||
self.db.inside = 'file:/home/howard/howardabrams/thoughts/' + \
|
||
today.strftime("%Y%m%d")
|
||
super().do_write(writer, message)
|
||
|
||
|
||
# ------------------------------------------------------------------------
|
||
# RANDOM BOOKS
|
||
# ------------------------------------------------------------------------
|
||
|
||
BOOK_EMOTIONS = [
|
||
"sad",
|
||
"heartfelt",
|
||
"intense",
|
||
"plot twisting",
|
||
"confusing",
|
||
"fascinating",
|
||
"riveting",
|
||
"humorous",
|
||
]
|
||
|
||
BOOK_TYPES = [
|
||
"story",
|
||
"narrative",
|
||
"chronicle",
|
||
"tale",
|
||
"instructional",
|
||
"essay collection",
|
||
]
|
||
|
||
BOOKS = {
|
||
# Each value should begin as if it follows an "of":
|
||
"I, Golem": "a non-carbon-based person who is also a non-conformist",
|
||
"Do Golems Dream of Awakened Sheep?": "a golem reaches out to its creator after years of therapy reveal his anger of his existential crisis is justified",
|
||
"The 13 Habits of Highly Effective Necromancers": "",
|
||
"Invisible Man": "when spells become permanent",
|
||
"Little Women": "famous female gnomes and hobbits through the centuries",
|
||
"Are You My Mummy?": "choosing your undead servants",
|
||
"How I Ate My Mother": "a lycanthrope confessional",
|
||
"Zen and the Art of Punching Dragons in the Face": "a pacifist monk, who's temple is burned, seeks revenge on the dragon who had spicy tacos and indigestion",
|
||
|
||
"The Very Hungry Carrion Crawler": "an adorable little larve who grows and grows after consuming graveyard after graveyard",
|
||
"Pride and Phylacteries": "a group of narcissistic liches and their struggle for dominance and control of the land of lesser creatures",
|
||
"For Whom the Die Rolls": "a fool’s guide to dice games and gambling",
|
||
"Life and Mines of a Dwarf Prospector": "a adopted human and his attempt to blend in with her adopted dwarvish parents by starting a mining company. All works well until she delved too deep",
|
||
"The Pied Piper": "why you shouldn’t trust bards",
|
||
"Around the World in 80 Dimension Doors": "a contest between two wizards who, after a drunken bet, attempt to reach the Ninth Level of Hell and return",
|
||
"Along Came a Spider": "a dungeoneer’s guide to driders and the drow",
|
||
"The Boy Who Cried Roc": "a classic children’s tale about a boy that thought it funny to announce to the village there was a Roc nearby. One day there was, and the nobody believed him. He was the only survivor",
|
||
"Some Like it Hot": "a guide to fire elementals",
|
||
"Girls Gone Feywild": "a group of girls who discover a portal to the Wyldwoods, and find themselves attempting to navigate the drama of the Seelie Court of the Summer Queen. Hilarity ensues when they fail to bawk like a chicken when an Archfey utter the secret word, Pancakes",
|
||
"50 Shades of Fey": "young elf who discovers more about herself after making a pact with an Archfey of the Unseelie Court",
|
||
"A Handmaid’s Tail": "a collection of short stories by notable Tieflings",
|
||
"Adventures with Muck, Berries, and Fins": "a herbalist’s guide to useful foraging",
|
||
"Bond with the Wind": "a how to summon Air elementals",
|
||
"Eat, Prey, Club": "a young orc and her quest for true dominance",
|
||
"Fantastic Drinks and Where to Buy Them": "all the taverns in the land (obviously written by Nori Doublefist under the pseudonym, The Drunk Dwarf), cataloging the best and most curious drinks each tavern has to offer",
|
||
"For Whom the Bridge Troll": "a tragic love story of a misunderstood troll who lived under a bridge",
|
||
"Frank and his Stein": "a poor drunk named Frank",
|
||
"Harry Otter": "a young orphan druid who lives with his boring fighter uncle and aunt who denies the existence of druids. The story sees Harry start at the Druid school led by Albus Bumbledoor, the archdruid who loves to be a Bumblebee. Bumbledoor takes Harry under his gravity defying wings and tells him the true story about how his parents died and that he is known by all druids as The Otter Who Lived. He must now meet his destiny and fight the most evil druid of all time, Voldemoth. Featuring the great cast of: Ron the Weasel, Hermione Ranger (some people can never choose only one class), Hagrid the lover of all creatures, Severus Snake and many more",
|
||
"Harry Potter and the Sorcerer’s Kidney Stone": "how an ordinarily normal surgery gets complicated due to wild magic leaving Harry with unexpected results. Spoiler Harry gets magic and becomes Dr. Strange after prolonged contact with the Sorcerer’s insides",
|
||
"Pride and Prestidigitation": "a coven of witches as they vie for the attention, and affection, of a traveling wizard",
|
||
"The Ruler of the Bracelets": "two gnomes who find a bracelet of power, and they have to take it to the Burning Steppes and cast it into the Cauldron. They form the Brotherhood of the Bracelet. Along the way they’re trailed by a murloc named Gottom, who’s obsessed with the bracelet, and nine bracelet bogeymen. It could be a three-parter, called ‘Ruler of the Bracelet’. The first part would be called ‘The Brotherhood of the Bracelet’, followed by ‘A Couple of Towers’, with the climactic ending called ‘Hey, the King’s Back!’ Not bad",
|
||
"What to Expect When You’re Spectating": "the history of the four nations written by Xanathar, a famed Beholder and crime-boss",
|
||
"The Wyrm in the Willows": "small dragon from the countryside who learns to cook without burning the meal first",
|
||
}
|
||
|
||
|
||
class Bookshelf(Producer):
|
||
"""
|
||
Special producer,
|
||
|
||
Keeps a "state" of time, the bookshelf was "looked" at,
|
||
and creates _that_ book when the "get" command is issued.
|
||
"""
|
||
|
||
book_prototype = {
|
||
"typeclass": "typeclasses.readables.Book",
|
||
"key": "book",
|
||
"desc": "An old, worn, leather-bound book.",
|
||
}
|
||
|
||
def new_book_details(self):
|
||
"""
|
||
Choose a random book, and return tuple of details.
|
||
|
||
Return: (title, description, inside_description)
|
||
"""
|
||
title = choice(list(BOOKS.keys()))
|
||
return (
|
||
title,
|
||
routput("""a <<worn ^ barely read ^ pristine copy of a ^ >>
|
||
<<leather-bound ^ vellum-bound ^ >> book, titled, |w{0}|n
|
||
""", title),
|
||
routput(
|
||
"""
|
||
You <<open ^ open up ^ crack open>> your book, entitled, |w{0}|n, and read the {1} {2} of {3}.
|
||
""",
|
||
title,
|
||
choice(BOOK_EMOTIONS),
|
||
choice(BOOK_TYPES),
|
||
BOOKS[title]
|
||
)
|
||
)
|
||
|
||
def new_book(self):
|
||
"""
|
||
Return the description of a newly seen book.
|
||
This also _stores_ the details of that book for later.
|
||
"""
|
||
(title, desc, inside) = self.new_book_details()
|
||
|
||
self.db.last_title = title
|
||
self.db.last_desc = desc
|
||
self.db.last_inside = inside
|
||
|
||
return desc
|
||
|
||
def return_appearance(self, looker, **kwargs):
|
||
"""
|
||
Only the first time looker view this, do we return the description.
|
||
Otherwise, we return a new book.
|
||
"""
|
||
|
||
book_desc = self.new_book()
|
||
|
||
this_id = sub(r" +", "_", self.location.key.lower()) + \
|
||
"_" + sub(r" +", "_", self.key.lower())
|
||
num_books = looker.nattributes.get(this_id, 0)
|
||
|
||
looker.nattributes.add(this_id, num_books + 1)
|
||
|
||
filler = routput(""" You
|
||
<< browse ^ browse through ^ scan ^ scan through ^ examine ^
|
||
peruse ^ peruse through ^ inspect >>
|
||
the books << on the shelf ^ >>, and
|
||
<< find ^ discover ^ encounter ^ spot ^ stumble upon ^
|
||
this book catches your eye, >> """.replace("\n", " "))
|
||
|
||
if num_books == 0:
|
||
return self.db.desc + " " + filler + " " + book_desc + "."
|
||
else:
|
||
return filler + " " + book_desc + "."
|
||
|
||
def do_make(self, reader):
|
||
"""
|
||
Create a book, and give it to the 'reader'.
|
||
|
||
If the reader has seen a book previously, with the 'look'
|
||
command, give them that book, otherwise, give them a new one
|
||
at random.
|
||
"""
|
||
if not self.db.last_title:
|
||
filler = "a"
|
||
(title, desc, inside) = self.new_book_details()
|
||
else:
|
||
filler = "the"
|
||
title = self.db.last_title
|
||
desc = self.db.last_desc
|
||
inside = self.db.last_inside
|
||
|
||
booktype = self.book_prototype
|
||
booktype['desc'] = desc
|
||
booktype['aliases'] = [title]
|
||
|
||
book = spawn(booktype)[0]
|
||
book.db.inside = inside
|
||
book.location = reader
|
||
|
||
reader.msg(f"You pick up {filler} book, |w{title}|n.")
|
||
reader.score(Scores.read_a_book)
|
||
|
||
self.db.last_title = None
|
||
self.db.last_desc = None
|
||
self.db.last_inside = None
|