#!/usr/bin/env python from random import choice from re import match, findall from evennia import CmdSet, create_script from evennia.utils import delay, logger from evennia.commands.default.muxcommand import MuxCommand from evennia.contrib.rpg.rpsystem import send_emote from evennia.prototypes.spawner import spawn from evennia.utils.utils import int2str from .command import Command from typeclasses.scripts import DonkeyHeadSpell from typeclasses.drinkables import Cocktail from utils.word_list import routput, pluralize class CmdFly(Command): """Cast the 'fly' spell. Make sure you set the following properties, for instance: @set self/disappear_msg = "The wizard disappears in a puff of smoke." @set self/reappear_msg = "A plume of <> smoke appears... ;; When the smoke clears, a wizard <>." @set self/appear_delay = 3 The last setting is the number of seconds between message segments (those are separated by double semicolons). """ key = "fly" locks = "cmd:holds()" def func(self): """ Call the 'do_fly' method on the caller. """ self.caller.do_fly(self.args.strip()) class CmdMagic(Command): """ Cast a generic 'magic' spell. Usage: wave [ effect(s) ] Where 'effects' is a statement of what happens after the magic erupts. Note that you can have multiple effects separated by two semicolons. You can tailor the effects to your character with the following: - |y$you()|n: Replaced by "you" and your name for others, like "old gnome" - |y$your()|n: Replaced by "your" and your possessive name for others, like "old gnome's" - |y$conj(verb)|n: Replaced by "verb" for you, and "verbs" for others. - |y$pron(you)|n: Replaced by "you" for you, but "he" or "she" for others. - |y$pron(your)|n: Replaced by "your" for you, but "his" or "her" for others. While flexible, you would use this complex replacement in either |gnicks|n or as part of a standard magical prefix, by setting the property: @set self/magic_msg = "$You() $conj(shake) $pron(your) necklace of bones!" """ key = "magic" aliases = ["wave"] locks = "cmd:holds()" def func(self): """ Call the 'do_magic' method on the caller. """ wizard = self.caller msgs = wizard.db.magic_msg if msgs: msgs = msgs.split(';;') else: msgs = [ "$You() $conj(wave) $pron(your) " + self.obj.key + ".", "<< Sparks ^ Colored lights ^ Flashes ^ Flashes >> of |yoctarine|n << appear ^ emerge ^ materialize >> as << the ^ >> magic << coalesces into an amorphous show of power ^ blends into swirling patterns ^ weaves together>>." ] if self.args: msgs = msgs + self.args.strip().split(';;') wizard.spell_sequence(None, msgs, wizard.db.magic_delay or 3) class CmdMakeItem(Command): """ Create one or more items from thin air. Usage: make [ number ] name [ description ] Where 'effects' is a statement of what happens after the magic erupts. Note that you can have multiple effects separated by two semicolons. You can tailor the effects to your character with the following: - |y$you()|n: Replaced by "you" and your name for others, like "old gnome" - |y$your()|n: Replaced by "your" and your possessive name for others, like "old gnome's" - |y$conj(verb)|n: Replaced by "verb" for you, and "verbs" for others. - |y$pron(you)|n: Replaced by "you" for you, but "he" or "she" for others. - |y$pron(your)|n: Replaced by "your" for you, but "his" or "her" for others. While flexible, you would use this complex replacement in either |gnicks|n or as part of a standard magical prefix, by setting the property: @set self/magic_msg = "$You() $conj(shake) $pron(your) necklace of bones!" """ key = "make" locks = "cmd:holds()" def parse(self): """ Allows the following phrases: candy 2 candy Piece of Turkish Delight. candy Piece of Turkish Delight. "turkish delight" Piece of Turkish Delight. 4 "turkish delight" Piece of Turkish Delight. """ pattern = r'"(.*?)"|(\S+)' matches = findall(pattern, self.args.strip()) try: self.item_number = int(matches[0][0] or matches[0][1]) self.item_name = matches[1][0] or matches[1][1] start = 2 except ValueError: self.item_number = 1 self.item_name = matches[0][0] or matches[0][1] start = 1 words = [m[0] or m[1] for m in matches[start:]] self.item_desc = " ".join(words) def func(self): """ Call the 'do_magic' method on the caller. """ wizard = self.caller msgs = wizard.db.make_msg or wizard.db.magic_msg if msgs: msgs = msgs.split(';;') else: msgs = [ "$You() $conj(snap) $pron(your) fingers.", ] if self.item_number == 1: if match(r"^[aeiou]", self.item_name): name = f"an {self.item_name}" else: name = f"a {self.item_name}" else: name = int2str(self.item_number) + " " + \ pluralize(self.item_name) # from evennia.utils.utils import int2str ; print(int2str("2")) msgs = msgs + [ name + " appear in $pron(your) hand." ] wizard.spell_sequence(None, msgs, wizard.db.magic_delay or 3) # FIXME So that the cat will eat it and it can't be littered: if not self.item_desc or self.item_desc == "": self.item_desc = f"Conjured by {wizard.get_name()}." for _ in range(self.item_number): item = spawn({ "typeclass": "typeclasses.consumables.Litterable", "key": self.item_name, "desc": self.item_desc })[0] item.location = wizard class CmdSetWand(CmdSet): """ All wizard spells are tied to a 'wand' that might be flavored. """ def at_cmdset_creation(self): super().at_cmdset_creation() self.add(CmdFly) self.add(CmdMagic) self.add(CmdMakeItem) class CmdScry(Command): """Cast the 'scry' spell to view a room.""" key = "scry" locks = "cmd:holds()" def func(self): """ Call the 'do_show_room' method on the object. """ self.obj.do_show_room(self.caller, self.args.strip()) class CmdSetScry(CmdSet): """ The set containing the 'scry' command. """ def at_cmdset_creation(self): super().at_cmdset_creation() self.add(CmdScry) class CmdMakeCocktail(MuxCommand): """ For the 'Bartender' especially. Usage: shake |wcocktail|n = |wpatron|n If patron is not given or not found, the drink will be in your inventory, and you can call |ggive|n to pass it along. If cocktail name isn't given (or matches anything), a random one will be created. """ key = 'shake' locks = "cmd:perm(gm) or perm(Admin)" def func(self): dest = self.caller if self.rhs: dest = self.caller.search(self.rhs) Cocktail.make(dest, self.caller, self.lhs) class CmdGift(MuxCommand): """ Give a special gift to a character. Usage: gift to [ : name : desc ] """ key = "gift" locks = "cmd:perm(gm) or perm(Admin)" def func(self): m = match(r"([A-z]+) *?( to|=)? *(.+)( *: *[A-z]+( *: *[A-z]+)?)?", self.args.strip()) if m: # logger.info(f"Gift: {m.group(1)} to {m.group(3)}") self.caller.do_gift(m.group(3), m.group(1), m.group(4), m.group(5)) else: self.caller.msg("Usage: gift to [ : name : desc ]") class CmdGM(MuxCommand): """ The gm command allows anything to be emoted into a room. Usage: gm A bat flies into the room! gm/gnome You hear a distant ringing """ key = "gm" aliases = ["#"] locks = "cmd:perm(gm) or perm(Admin)" def func(self): from typeclasses.things import Scribe send_to = [] for switch in self.switches: o = self.caller.search(switch, global_search=True) if o: send_to = send_to + [o] if not send_to: send_to = [self.caller.location] me = self.caller msg = routput(self.args) for o in send_to: if o.is_typeclass('typeclasses.rooms.Room'): # Send the message to all characters and any recording # scribes in attendance: chars = o.contents_get(None, 'character') + [o.has(Scribe)] send_emote(me, chars, msg, 'say', None) elif o.is_typeclass('typeclasses.characters.Character'): o.msg(msg) class CmdSpell(Command): """ Cast one of the few spells we've created that affect others. Usage: spell donkey on lizardman """ key = "spell" aliases = ['cast'] locks = "cmd:perm(gm) or perm(Admin)" def parse(self): self.spell = None self.target = None m = match(r"([^ ]+)( +on +(.+))?", self.args.strip()) if m: self.spell = m.group(1) self.target = m.group(3) def func(self): caster = self.caller if not self.spell: caster.msg('Usage: cast [on ]') return char = None if self.target: char = caster.search(self.target) if not char: return if self.spell == 'donkey' and char: create_script(key="donkey_head", typeclass=DonkeyHeadSpell, interval=130, start_delay=True, attributes=[("target", char)]) caster.msg(f"You cast |wHead of Donkey|n on {char}") else: caster.msg(f"You fail to cast {self.spell}") class CmdGMTrigger(Command): """The trigger command kicks off a series of named events. Usage: trigger trigger-name trigger :game trigger-name trigger/character trigger-name trigger/character:game trigger-name Where 'game' defaults to the value previously set: @set npc/currentgame = "session1" Triggers are typically set on the NPC (which would be in the room with the PCs) or the room. Using the command: @set npc/triggers:session1 = {"darkness": {"desc": "Make the room go black", "timer": 1, "events": [ "The room gets dark", "And then pitch-black.", ("You can't help it, but scream!", "You hear a scream!")]}} The 'set' command, as a complicated data structure, should be set in a batchcommand. """ key = "trigger" aliases = ["trig"] locks = "cmd:perm(gm) or perm(Admin)" def parse(self): m = match(r" *(/[^: ]+)?(:[^ ]+)? *(.*)", self.args) if m: self.name = m.group(3) if m.group(2): self.game = m.group(2)[1:] else: self.game = None if m.group(1): self.switches = m.group(1)[1:].split(',') else: self.switches = [] else: self.caller.msg("Usage trigger/dest:game trigger") return False def func(self): npc = self.caller self.send_to = [] for switch in self.switches: o = npc.search(switch) if o: self.send_to = self.send_to + [o] else: return game = self.game or npc.db.currentgame if not game: npc.msg("Specify the game, or set a default with |g@set self/currentgame = ") return triggers = dict(npc.attributes.get(key='triggers', category=game)) if not self.name: npc.msg("What event do you want to trigger?") for name, details in triggers.items(): npc.msg(f" - {name} : {details['desc']}") return trigger = triggers[self.name] if trigger: npc.msg(f"Triggering: |w{trigger['desc']}") self.trigger(trigger['events'], trigger.get('timer', 5), self.send_to) else: npc.msg(f"Didn't find '{self.name}' to trigger.") def trigger(self, events, time_delay, dests=[]): """ Given a list of events, send each events at an interval. If an event is a tuple instead of a string, the first element goes to 'dests' and the second goes to the room (based on the caller's location). """ target = None if len(self.send_to) > 0: target = self.send_to[0] else: targets = self.caller.characters_here() if targets: target = choice(targets) if target: name = target.db._sdesc or target.key else: name = '' for idx, event in enumerate(events): if isinstance(event, str): if event.startswith("@"): self.caller.execute_cmd(event[1:]) else: if target: msg = target.gendered_text(routput(event, name)) else: msg = routput(event) delay(time_delay * idx, self.caller.location.msg_contents, "\n" + msg) else: char = target.gendered_text(routput(event[0], name)) room = target.gendered_text(routput(event[1], name)) if room: delay(time_delay * idx, self.caller.location.msg_contents, "\n" + routput(room), exclude=dests) if char: for dest in dests: delay(time_delay * idx, dest.msg, "\n" + routput(char))