#!/usr/bin/env python from random import random from re import split from commands.command import Command from evennia.commands.default.general import 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 typeclasses.readables import find_book from typeclasses.characters import Character 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 CmdWhisper(MuxCommand): """ Speak privately as your character to another. Usage: whisper = whisper , = 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 = ") return targets = [self.caller.search(target) for target in split(r" *, *", self.lhs)] full_speech = f"/Me whispers to you, \"{self.rhs}\"" send_emote(self.caller, targets, full_speech, msg_type="say", anonymous_add=None, quiet=True) to_list = [target.get_display_name(self) for target in targets] 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 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 To add something to read on target, use the @set command: @set /inside = 'This is the text to read.' """ key = "read" def func(self): """Return the 'inside' attribute.""" target_str = self.args.strip() if target_str == "": self.caller.msg("Usage: |gread |n") return book = find_book(self.caller, target_str) if book: self.caller.msg(book.db.inside) class CmdTake(Command, NumberedTargetCommand): """ Take an object from another character or NPC. Usage: take from Note that only some things can be stolen. For instance, the brass ring from the door knocker. """ key = "take" aliases = ["steal"] 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 take?") elif not self.rhs: self.caller.msg(f"You want to take {self.lhs}, but from whom?") else: self.caller.do_take(self.lhs, self.rhs) 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 not_empty(self, item): """Return true is the cup has some drink left in it.""" return (item.db.amount or 0) > 0 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.""" containers = [item for item in self.caller.contents if item.has_method('do_drink') and self.not_empty(item)] if len(containers) == 1: containers[0].do_drink(self.caller) elif len(containers) > 1: self.caller.msg("You have two 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 not_gone(self, item): """Return true is the cup has some eat left in it.""" return (item.db.amount or 0) > 0 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.""" items = [item for item in self.caller.contents if item.has_method('do_eat') and self.not_gone(item)] if len(items) == 1: items[0].do_eat(self.caller) elif len(items) > 1: self.caller.msg("You have too many things to eat. " "Which one do you want?") else: self.caller.msg("You have nothing to eat.") 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()