moss-n-puddles/commands/everyone.py
2026-04-27 15:50:52 -07:00

580 lines
17 KiB
Python
Executable file

#!/usr/bin/env python
from random import random
from re import match, split, sub, MULTILINE
from django.core.exceptions import ObjectDoesNotExist
from evennia.commands.default.general import CmdGet, NumberedTargetCommand
from evennia.commands.default.muxcommand import MuxCommand
from evennia.contrib.rpg.rpsystem import send_emote
from evennia.utils import iter_to_str, logger
from evennia.utils.evmore import EvMore
from commands.command import Command
from typeclasses.characters import Character
from typeclasses.tutorial import TutorBird, TutorialState
from utils.word_list import routput, paragraph, choices
class CmdUse(MuxCommand):
"""
Use an item.
The item, probably something in your inventory, could be something
in the local area.
Usage:
use <item> [= object]
This standard from the text adventurer tome is a catch-all command
allowing you to use or combine an object with another object.
"""
key = "use"
aliases = ["apply"]
rhs_split = ("=", " with ", " on ", " to ")
def func(self):
"""Call the 'do_use' method."""
if not self.args:
self.caller.msg("Use what?")
return
item = self.caller.search(self.lhs)
if item:
if self.rhs:
obj = self.caller.search(self.rhs)
else:
obj = None
if item.has_method('do_use'):
item.do_use(self.caller, obj)
else:
self.caller.msg(item.db.use_msg or f"You can't use {item.name}.")
class CmdPush(Command):
"""
Push an item in the area.
Usage:
push <item>
"""
key = "push"
def func(self):
"""Call the 'do_push' method."""
pusher = self.caller
to_push = self.args.strip()
if to_push == "":
pusher.msg("What do you want to push?")
return
item = pusher.search(self.args.strip())
if item:
if item.has_method('do_push'):
item.do_push(pusher)
else:
pusher.msg(item.db.push_msg or f"You can't push {item.name}.")
class CmdPull(Command):
"""
Pull on something.
Usage:
pull <item>
"""
key = "pull"
aliases = ["yank"]
def func(self):
"""Call the 'do_pull' method."""
puller = self.caller
to_pull = self.args.strip()
if to_pull == "":
puller.msg("What do you want to pull?")
return
item = puller.search(to_pull)
if item:
if item.has_method('do_pull'):
item.do_pull(puller)
else:
puller.msg(item.db.pull_msg or f"You can't pull {item.name}.")
class CmdOpen(Command):
"""
Open something.
Usage:
open <item>
"""
key = "open"
def func(self):
"""
Call an item's 'do_open' method.
Note that if the room the caller is in has a 'do_open', we
call that first to see if the room can open something (like an
exit).
"""
opener = self.caller
room = opener.location
to_open = self.args.strip()
if to_open == "":
opener.msg("What would you like to open?")
return
if room.has_method('do_open'):
if room.do_open(opener, to_open):
return
item = opener.search(to_open)
if item:
if item.has_method('do_open'):
item.do_open(opener)
else:
opener.msg(item.db.open_msg or f"You can't open {item.name}.")
class CmdClose(Command):
"""
Close something.
Usage:
close <item>
"""
key = "close"
def func(self):
"""Call the 'do_close' method.
Note that if the room the caller is in has a 'do_close', we
call that first to see if the room can close something (like
an exit).
"""
closer = self.caller
room = closer.location
to_close = self.args.strip()
if to_close == "":
closer.msg("What would you like to close?")
return
if room.has_method('do_close'):
if room.do_close(closer, to_close):
return
item = closer.search(to_close)
if item:
if item.has_method('do_close'):
item.do_close(closer)
else:
closer.msg(item.db.close_msg or f"You can't close {item.name}.")
class CmdWhisper(MuxCommand):
"""
Speak privately as your character to another.
Usage:
whisper <character> = <message>
whisper <char1>, <char2> = <message>
Talk privately to one or more characters in your current location, without
others in the room being informed.
"""
key = "whisper"
priority = 0
locks = "cmd:all()"
rhs_split = ("=")
def func(self):
"""Implement the new 'whisper' command."""
if not self.args:
self.caller.msg("What are you whispering?")
return
if not self.rhs:
self.caller.msg("Usage: whisper <character> = <message>")
return
targets = split(r" *, *", self.lhs)
chars = [self.caller.search(target) for target in targets]
for c in chars:
if not c:
return
self.caller.msg(f"Wishering to {chars}")
full_speech = f"/Me whispers to you, \"{self.rhs}\""
send_emote(self.caller, chars, full_speech, msg_type="say",
anonymous_add=None, quiet=True)
to_list = [target.get_display_name(self) for target in chars]
full_speech = f"You whisper to {iter_to_str(to_list, endsep='and')}, \"{self.rhs}\""
self.caller.msg(full_speech, from_obj=self.caller)
class CmdThink(Command):
"""Think a thought out loud.
Usage:
think <message>
Similar to the 'say' or 'pose' commands, this communicates an
inner monologue to other players on the 'public' channel.
"""
key = "think"
aliases = ["thinks", "("]
arg_regex = None
def func(self):
"""Implement the think out loud command."""
if not self.args:
self.caller.msg("What do you want to think out loud?")
else:
thought = self.args.strip()
if (self.caller.db.thinking_count or 0) < 3 or random() < 0.4:
msg = routput(
f"<< thinks ^ wonders >> << out loud ^ aloud >> "
f"... o O ( {thought} )"
)
else:
msg = f". o O ( {thought} )"
self.caller.db.thinking_count = (self.caller.db.thinking_count or 0) + 1
self.caller.execute_cmd(f"pub :{msg}")
class CmdRead(Command):
"""
Return the inside contents of a book or other readable object.
Usage:
read <target>
To add something to read on target, use the @set command:
@set <target>/inside = 'This is the text to read.'
"""
key = "read"
def find_readable(self, reader, readable_str):
"""Search the room for a readable item."""
if readable_str == "chalkboard":
readable_str = "shrub"
elif readable_str.startswith("cocktail") or \
readable_str.startswith("drink") or readable_str == "list":
readable_str = "sign"
targets = reader.search(readable_str, quiet=True)
if not targets:
# Let's get a better error message:
if reader.location.details and reader.location.details.get(readable_str):
reader.msg(f"You can't read {readable_str}.")
else:
reader.msg(f"You don't see {readable_str}.")
return None
label_targets = [t for t in targets if t.db.inside]
if len(label_targets) == 1:
return label_targets[0]
if len(label_targets) == 0:
reader.msg(f"You can't find anything readable on {readable_str}.")
else:
reader.msg(f"Too many things match, '{readable_str}'. "
"Can you narrow it down with a title, "
"or preface with a number, like '2-paper'?")
def func(self):
"""Return the 'inside' attribute."""
reader = self.caller
target_str = self.args.strip()
if target_str == "":
reader.msg("Usage: |gread <object>|n")
return
book = self.find_readable(reader, target_str)
if book:
contents = book.db.inside
prefix = book.db.prefix
if prefix:
prefix = prefix + "|/"
if contents.startswith("file:"):
self.show_file(reader, contents[5:], prefix,
self.client_width(), reader.client_height())
else:
reader.msg((prefix or "") + contents)
def show_file(self, reader, filename, prefix, width, height):
"""
Display a file to the user.
The file is _somewhat_ Markdown formatted.
"""
with open(filename, "r") as myfile:
buf = myfile.read()
session = reader.sessions.get()[0]
width = self.client_width()
if reader.is_webclient():
tidied = md_to_html(buf)
else:
tidied = md_to_evennia(buf, reader.is_utf(), width)
if prefix:
tidied = prefix + "\n\n" + tidied
EvMore(reader, tidied, session=session,
justify=True, justify_kwargs={"width": width})
def md_to_evennia(text, utf, width):
brk = '' if utf else '-'
line_brk = '|W' + (brk * width) + '|n'
breaks = [line_brk if line.startswith("#") else line
for line in md_preprocessor(text)]
return "\n".join(breaks)
def md_to_html(text):
brk = '───────────────────────────────────────────────────────'
breaks = [brk if line.startswith("#") else line
for line in md_preprocessor(text)]
return "\n".join(breaks)
def md_preprocessor(text):
lines = text.splitlines()
return [line for line in lines if not line.startswith(">")]
class CmdTake(CmdGet, NumberedTargetCommand):
"""
Get something, possibly from another character or NPC.
Usage:
get <thing> [ from <character> ]
Note that only some things can be stolen.
For instance, the brass ring from the door knocker.
"""
key = "take"
aliases = ["steal", "get"]
rhs_split = ("=", " from ")
def func(self):
"""
Implement the take command.
Since this command is designed to work on the object, we
operate only on self.obj.
"""
if not self.args:
self.caller.msg("What do you want to get?")
elif not self.rhs:
# This is soo bad to hard-code this game logic, but enough
# people complained about not being able to 'get' the
# ring, I need to:
if self.lhs == "ring" and self.caller.location.key == "Grotto":
self.caller.msg("From whom do you want to get this ring?")
elif self.lhs == "frog" and self.caller.location.key == "Frog Meadow":
self.caller.msg("The little guys are too quick to catch.")
else:
super().func() # Call the 'get' function instead.
else:
location = self.caller.search(self.rhs, quiet=True)
if len(location) == 0:
self.caller.msg(f"Can't take '{self.lhs}' from '{self.rhs}'.")
return
location = location[0]
if location.is_typeclass("typeclasses.rooms.Room"):
self.args = self.lhs
super().func() # Call the 'get' function instead.
return
self.caller.do_take(self.lhs, location)
class CmdDrink(Command):
"""
Drink a beverage in your inventory.
Usage:
drink [ container ]
If you are holding a teacup, or cocktail, and it is not empty,
you may drink.
This doesn't tell others of this particular activity.
"""
key = "drink"
aliases = ["sip", "quaff"]
def drink_item(self, name):
"""Find item in inventory, name, and call 'do_drink' on it."""
notfound = f"You don't have {name} in your inventory."
item = self.caller.search(name, location=self.caller,
nofound_string=notfound)
if item:
if item.has_method('do_drink'):
item.do_drink(self.caller)
else:
self.caller.msg(f"The {item.name} is not drinkable.")
def drink_anything(self):
"""Drink anything in your inventory, but only if you have one thing."""
def not_empty(item):
return (item.db.amount or 0) > 0
containers = self.search_contents_by_func('do_drink', not_empty)
if len(containers) == 1:
containers[0].do_drink(self.caller)
elif len(containers) > 1:
self.caller.msg("You have too many things you can drink. "
"Which one do you want?")
else:
self.caller.msg("You have nothing to drink.")
def func(self):
"""
If given the name of something to drink, find and drink it.
Otherwise, drink the first item you find in your inventory.
"""
goal = self.args.strip()
if goal and goal != "":
self.drink_item(goal)
else:
self.drink_anything()
class CmdEat(Command):
"""
Eat something edible in your inventory.
Usage:
eat [ food-item ]
This doesn't tell others of this particular activity.
"""
key = "eat"
aliases = ["consume", "bite"]
def eat_item(self, name):
"""Find item in inventory, name, and call 'do_eat' on it."""
notfound = f"You don't have {name} in your inventory."
item = self.caller.search(name, location=self.caller,
nofound_string=notfound)
if item:
if item.has_method('do_eat'):
item.do_eat(self.caller)
else:
self.caller.msg(f"The {item.name} is not edible.")
def eat_anything(self):
"""Eat something in your inventory, but only if you have one thing."""
def not_gone(item):
return (item.db.amount or 0) > 0
items = self.search_contents_by_func('do_eat', not_gone)
if len(items) == 0:
self.caller.msg("You have nothing to eat.")
else:
item = items[0]
if len(items) > 1:
self.caller.msg(f"Eating the {item.name}.")
item.do_eat(self.caller)
def func(self):
"""
If given the name of something to eat, find and eat it.
Otherwise, eat the first item you find in your inventory.
"""
goal = self.args.strip()
if goal and goal != "":
self.eat_item(goal)
else:
self.eat_anything()
class CmdFeed(MuxCommand):
"""
Feed or give something to an object that can eat.
Typically this is used to feed wood to a fire, or
berries to a beast.
Usage:
|gfeed <target>|n
Or:
|gfeed <food> to <target>|n
Where 'food' is something you have in your inventory,
and target is a pet or something that wants to eat.
If you don't specify the 'food', the target will eat
what you might have they're interested in.
"""
key = "feed"
rhs_split = ("=", " to ")
def func(self):
"""
Implements the feed (or give) command.
"""
if not self.args:
self.caller.msg("Feed what?")
return
feeder = self.caller
if self.rhs:
eater = feeder.search(self.rhs)
food = feeder.search(self.lhs, location=feeder)
if not food:
return
else:
eater = feeder.search(self.lhs)
food = None
if eater:
if eater.has_method('feed'):
eater.feed(feeder, food)
if food:
try:
food.delete()
# Allow the eater to delete the object.
except ObjectDoesNotExist:
pass
else:
name = eater.get_display_name(feeder)
feeder.msg(f"You can't feed, {name}.")
else:
feeder.msg(f"Don't see a '{self.lhs}' to feed.")
# Pass this off to CmdGive?
#
# feeder.execute_cmd("give " + self.args)
#
# supercmd = CmdGive()
# supercmd.caller = feeder
# supercmd.args = self.args
# supercmd.lhs = self.lhs
# supercmd.rhs = self.rhs
# supercmd.func()