#!/usr/bin/env python from random import choice from re import match 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 .command import Command from typeclasses.scripts import DonkeyHeadSpell from typeclasses.drinkables import Cocktail from utils.word_list import routput 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 CmdSetWand(CmdSet): """ All wizard spells are tied to a 'wand' that might be flavored. """ key = "Wand" def at_cmdset_creation(self): super().at_cmdset_creation() self.add(CmdFly) 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.lhs) 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): 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'): chars = o.contents_get(None, 'character') 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))