This commit gives an "AI" capability to any object (still triggered by the 'say' command), so now the Wee Beastie can do more than purr. Also fixes the Witch and the Dragon's movements throughout the Realm. We can also Scry into rooms in order to watch the behavior of the Chatbots.
469 lines
14 KiB
Python
Executable file
469 lines
14 KiB
Python
Executable file
#!/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 <<white ^ light blue ^ gray ^ >>
|
|
smoke appears... ;; When the smoke clears, a
|
|
wizard <<emerges ^ materializes>>."
|
|
@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 <gift> to <char> [ : 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 <gift> to <char> [ : 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 <spell> [on <target>]')
|
|
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 = <game>")
|
|
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))
|