moss-n-puddles/commands/everyone.py

754 lines
22 KiB
Python
Executable file

#!/usr/bin/env python
from random import random
from re import 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
def speech_effect(speech, verb, target, effects):
"""
Return speech after applying 'effects'.
Return a tuple for what a user thinks he said, and what
others actually here. If no effect, just return speech twice.
Effects:
- mute ... can't talk
- donkied ... target and others hear the value of effect
- sloshed ... slurred speech
To administer:
@set woman/donkied:effect = "Heehaw! ;; Heehaw, heehaw!"
"""
for effect in effects:
if effect:
if effect.db_key == 'donkied':
msg = choices(effect.value)
return (msg, msg, "bray")
return (speech, speech, verb)
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 CmdSay(MuxCommand):
"""
Say something to the characters in the same area.
For instance:
|gsay Good evening!|n
However, you can use an alias and simply type:
|g"Good evening!
Or:
|g'Good evening!
Creatures and objects in this game can sometimes respond to what you say.
Usage:
say phrase
say/to char1 [, char2 ...] = phrase
say[/switches] phrase
Where switches can be any of the following:
- exclaim : To replace 'says' with 'exclaims'
- yell : To replace 'says' with 'yells'
- scream : To replace 'says' with 'screams'
- ask : To replace 'says' with 'asks'
- to : Directs phrase to one or more characters
in the same area. Note others can still hear
the statement (see the 'whisper' command).
- adverb : Any adverb-like word that ends in '-ly'
is added to the say command, for instance:
say/quietly Hi there.
Shows as:
You quietly say, "Hi there."
"""
key = "say"
aliases = ["says", "speak", "shout", "yell", "exclaim", "scream", "ask",
"reply", "respond", "\"", "'"]
priority = 0
locks = "cmd:all()"
rhs_split = ("=")
arg_regex = None
def func(self):
"""Implement the new 'say' command with switches."""
speaker = self.caller
if not self.args:
speaker.msg("Say what?")
return
if 'to' in self.switches and not self.rhs:
speaker.msg(paragraph("""
When attempting to say something to one or more
characters, use the '=' character to identify what you
want to say.
For instance, |gsay/to elf = Hi there.|n """))
return
if self.rhs:
speech = self.rhs
else:
speech = self.args
# If speech is empty, stop here
if not speaker.at_pre_say(speech):
return
adverb = ''
for switch in self.switches:
if switch.endswith('ly'):
adverb = switch + ' '
if 'scream' in self.switches or self.cmdstring == 'scream':
verb = 'scream'
elif 'shout' in self.switches or self.cmdstring == 'shout':
verb = 'shout'
elif 'respond' in self.switches or self.cmdstring == 'respond':
verb = 'respond'
elif 'reply' in self.switches or self.cmdstring == 'reply':
verb = 'reply'
elif 'yell' in self.switches or self.cmdstring == 'yell':
verb = 'yell'
elif 'exclaim' in self.switches or self.cmdstring == 'exclaim' or speech.endswith('!'):
verb = 'exclaim'
elif 'ask' in self.switches or self.cmdstring == 'ask' or speech.endswith('?'):
verb = 'ask'
else:
verb = "say"
for_me, for_others, verb = \
speech_effect(speech, verb,
speaker,
speaker.attributes.get(category="effect",
return_obj=True,
return_list=True))
to_who = to_whom = ''
if 'to' not in self.switches:
for char in speaker.location.contents:
if hasattr(char, 'other_say') and callable(char.other_say):
char.other_say(speaker, for_others)
else:
# Send the message to 'puppets' that have the special 'other_sayto'
# method (if they are the object of the message):
targets = split(r" *, *", self.lhs)
to_chars = [speaker.search(c, quiet=True) for c in targets]
to_chars = [c[0] for c in to_chars if len(c) > 0]
if to_chars == []:
speaker.msg(f"No match for {', '.join(targets)}")
return
for char in to_chars:
if hasattr(char, 'other_sayto') and callable(char.other_sayto):
logger.info(f"Found {char.key}: {for_others}")
char.other_sayto(speaker, for_others)
who = iter_to_str([c.get_display_name(speaker)
if isinstance(c, Character) else c
for c in to_chars])
whom = iter_to_str([f"/{c}" for c in targets])
if verb == "ask":
to_who = " " + who
to_whom = " " + whom
else:
to_who = " to " + who
to_whom = " to " + whom
# The `send_emote` is _global_, so if we want to say to 'Bob',
# 'You say ...', we have to both send him a message with the
# 'You' as well as everyone else with the 'send_emote'.
full_speech = f"You {adverb}{verb}{to_who}, \"{for_me}\""
speaker.msg(full_speech, from_obj=speaker)
# English is weird...
if verb == 'reply':
verb = 'replie'
targets = [item for item in speaker.location.contents
if item != speaker]
full_speech = f"/me {adverb}{verb}s{to_whom}, \"{for_others}\""
send_emote(speaker, targets, full_speech, msg_type="say",
anonymous_add=None)
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:
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?")
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()