#!/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 [= 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 """ 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 """ 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 """ 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 """ 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 = 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 = 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 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 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 |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 [ from ] 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 |n Or: |gfeed to |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()