313 lines
12 KiB
Python
313 lines
12 KiB
Python
"""
|
|
Scripts
|
|
|
|
Scripts are powerful jacks-of-all-trades. They have no in-game
|
|
existence and can be used to represent persistent game systems in some
|
|
circumstances. Scripts can also have a time component that allows them
|
|
to "fire" regularly or a limited number of times.
|
|
|
|
There is generally no "tree" of Scripts inheriting from each other.
|
|
Rather, each script tends to inherit from the base Script class and
|
|
just overloads its hooks to have it perform its function.
|
|
|
|
"""
|
|
from enum import Enum
|
|
from itertools import cycle
|
|
from pathlib import Path
|
|
from random import choice
|
|
|
|
from evennia.scripts.scripts import DefaultScript
|
|
from evennia.prototypes.spawner import spawn
|
|
from evennia.utils import logger, delay
|
|
from evennia.utils.search import search_object
|
|
from typeclasses.characters import Character
|
|
|
|
|
|
class Script(DefaultScript):
|
|
"""
|
|
This is the base TypeClass for all Scripts. Scripts describe
|
|
all entities/systems without a physical existence in the game world
|
|
that require database storage (like an economic system or
|
|
combat tracker). They
|
|
can also have a timer/ticker component.
|
|
|
|
A script type is customized by redefining some or all of its hook
|
|
methods and variables.
|
|
|
|
* available properties (check docs for full listing, this could be
|
|
outdated).
|
|
|
|
key (string) - name of object
|
|
name (string)- same as key
|
|
aliases (list of strings) - aliases to the object. Will be saved
|
|
to database as AliasDB entries but returned as strings.
|
|
dbref (int, read-only) - unique #id-number. Also "id" can be used.
|
|
date_created (string) - time stamp of object creation
|
|
permissions (list of strings) - list of permission strings
|
|
|
|
desc (string) - optional description of script, shown in listings
|
|
obj (Object) - optional object that this script is connected to
|
|
and acts on (set automatically by obj.scripts.add())
|
|
interval (int) - how often script should run, in seconds. <0 turns
|
|
off ticker
|
|
start_delay (bool) - if the script should start repeating right away or
|
|
wait self.interval seconds
|
|
repeats (int) - how many times the script should repeat before
|
|
stopping. 0 means infinite repeats
|
|
persistent (bool) - if script should survive a server shutdown or not
|
|
is_active (bool) - if script is currently running
|
|
|
|
* Handlers
|
|
|
|
locks - lock-handler: use locks.add() to add new lock strings
|
|
db - attribute-handler: store/retrieve database attributes on this
|
|
self.db.myattr=val, val=self.db.myattr
|
|
ndb - non-persistent attribute handler: same as db but does not
|
|
create a database entry when storing data
|
|
|
|
* Helper methods
|
|
|
|
create(key, **kwargs)
|
|
start() - start script (this usually happens automatically at creation
|
|
and obj.script.add() etc)
|
|
stop() - stop script, and delete it
|
|
pause() - put the script on hold, until unpause() is called. If script
|
|
is persistent, the pause state will survive a shutdown.
|
|
unpause() - restart a previously paused script. The script will continue
|
|
from the paused timer (but at_start() will be called).
|
|
time_until_next_repeat() - if a timed script (interval>0), returns time
|
|
until next tick
|
|
|
|
* Hook methods (should also include self as the first argument):
|
|
|
|
at_script_creation() - called only once, when an object of this
|
|
class is first created.
|
|
is_valid() - is called to check if the script is valid to be running
|
|
at the current time. If is_valid() returns False, the running
|
|
script is stopped and removed from the game. You can use this
|
|
to check state changes (i.e. an script tracking some combat
|
|
stats at regular intervals is only valid to run while there is
|
|
actual combat going on).
|
|
at_start() - Called every time the script is started, which for persistent
|
|
scripts is at least once every server start. Note that this is
|
|
unaffected by self.delay_start, which only delays the first
|
|
call to at_repeat().
|
|
at_repeat() - Called every self.interval seconds. It will be called
|
|
immediately upon launch unless self.delay_start is True, which
|
|
will delay the first call of this method by self.interval
|
|
seconds. If self.interval==0, this method will never
|
|
be called.
|
|
at_pause()
|
|
at_stop() - Called as the script object is stopped and is about to be
|
|
removed from the game, e.g. because is_valid() returned False.
|
|
at_script_delete()
|
|
at_server_reload() - Called when server reloads. Can be used to
|
|
save temporary variables you want should survive a reload.
|
|
at_server_shutdown() - called at a full server shutdown.
|
|
at_server_start()
|
|
|
|
"""
|
|
|
|
|
|
class KnockScript(Script):
|
|
"""
|
|
A script to wake the dead.
|
|
"""
|
|
def at_start(self, **kwargs):
|
|
knocker = self.attributes.get("knocker")
|
|
if knocker:
|
|
room = search_object(self.attributes.get("room")).first()
|
|
if room:
|
|
room.msg_contents("Someone is knocking on the door...")
|
|
|
|
gnome = search_object("Dabbler").first()
|
|
if gnome:
|
|
delay(3, gnome.msg,
|
|
f"With your seer stone, you see the knocker is {knocker.key}, the {knocker.db._sdesc}.")
|
|
|
|
def at_repeat(self, **kwargs):
|
|
waker = self.attributes.get("waker")
|
|
if waker:
|
|
waker.knocked_timed_out()
|
|
self.delete()
|
|
|
|
|
|
class CreateSticks(Script):
|
|
"""
|
|
Script to create sticks.
|
|
"""
|
|
def at_repeat(self, **kwargs):
|
|
woods = self.attributes.get("destination")
|
|
results = woods.search('stick')
|
|
if results and len(results) > 0 and results[0].location == woods:
|
|
pass
|
|
else:
|
|
stick = spawn({
|
|
"typeclass": "typeclasses.things.Stick",
|
|
"key": "stick",
|
|
"desc": """Its brown and sticky.|/|/Well, by sticky, we mean, wizardly-sticky...an absolutely amazing looking stick...definitely a wizardly stick.""",
|
|
})[0]
|
|
stick.location = woods
|
|
woods.msg_contents("A stick falls from one of the trees and lands on the ground near your feet.")
|
|
|
|
|
|
class CreateHorns(Script):
|
|
"""
|
|
Script to create calling horns.
|
|
"""
|
|
def at_repeat(self, **kwargs):
|
|
hut = self.attributes.get("destination")
|
|
results = hut.search('horn', location=hut, quiet=True)
|
|
if len(results) < 1:
|
|
horn = spawn({
|
|
"typeclass": "typeclasses.sailing.CallingHorn",
|
|
"key": "horn",
|
|
"desc": "While physical, this curved horn seems made from sea mist, and has an amorphous quality. Wonder what would happen if you |gblow|n this horn? And where?",
|
|
})[0]
|
|
horn.location = hut
|
|
hut.msg_contents("The misty smell of brine wafts in through a window. The mists congeal to form a horn, hanging on a hook near the window.")
|
|
|
|
|
|
class Spell(Script):
|
|
"""
|
|
A script to clean up the effects of a spell.
|
|
"""
|
|
def at_stop(self, **kwargs):
|
|
target = self.attributes.get("target")
|
|
target.attributes.clear(category="effect")
|
|
self.delete()
|
|
|
|
|
|
class DonkeyHeadSpell(Spell):
|
|
def at_start(self, **kwargs):
|
|
target = self.attributes.get("target")
|
|
target.attributes.add(
|
|
"donkied",
|
|
"Heehaw! ;; Heehaw, heehaw!",
|
|
category="effect")
|
|
target.attributes.add(
|
|
"post_desc",
|
|
"\nOh, and |s has a donkey's head!",
|
|
category="effect")
|
|
target.msg("You suddenly feel quite peculiar.")
|
|
|
|
def at_repeat(self, **kwargs):
|
|
self.stop()
|
|
|
|
|
|
class Muttering(Script):
|
|
"""
|
|
Script to have an objects 'emote' phrases in sequence.
|
|
|
|
First add settings to the 'npc', for instance:
|
|
|
|
@set npc/muttering_interval = 120 # for 2 minutes
|
|
@set npc/muttering_gap = 5 # for 5 seconds per sequence
|
|
@set npc/muttering_formats = [
|
|
"sings to |oself as if no one is listening, \"{0}\"",
|
|
"continues to sing to |oself, \"{0}\"",
|
|
"croons to |oself, \"{0}\"",
|
|
"finishes |p verse, \"{0}\"|/",
|
|
]
|
|
@set npc/muttering_file = "song-lyrics.txt"
|
|
|
|
Then start the script by running the following:
|
|
|
|
@script npc = typeclasses.scripts.Muttering
|
|
"""
|
|
def at_script_creation(self):
|
|
self.key = "muttering"
|
|
self.desc = "NPCs that Mutter"
|
|
self.interval = self.obj.db.muttering_interval or 300 # seconds
|
|
self.start_delay = False
|
|
self.persistent = True
|
|
self.reload()
|
|
|
|
def reload(self):
|
|
self.db.mutter_delay_gap = self.obj.db.muttering_gap or 7
|
|
self.db.mutter_sequence_index = 0
|
|
mutter_file = self.obj.db.muttering_file
|
|
if mutter_file:
|
|
logger.info(f"Reading muttering file, {mutter_file}")
|
|
self.db.mutters = self.load_mutter(mutter_file)
|
|
|
|
def at_repeat(self, **kwargs):
|
|
"""
|
|
Time to mutter something...
|
|
"""
|
|
self.mutter_sequence()
|
|
|
|
def mutter_sequence(self):
|
|
# if not hasattr(self, 'mutter_sequence_index'):
|
|
# self.db.mutter_sequence_index = 0
|
|
|
|
if self.db.mutter_sequence_index < len(self.db.mutters):
|
|
sequences = self.db.mutters[self.db.mutter_sequence_index]
|
|
self.db.mutter_sequence_index += 1
|
|
else:
|
|
sequences = self.db.mutters[0]
|
|
self.db.mutter_sequence_index = 1
|
|
|
|
formats = self.refresh_mutter_formats()
|
|
zip_list = zip(sequences, cycle(formats)) \
|
|
if len(sequences) > len(formats) \
|
|
else zip(sequences, formats[:len(sequences)])
|
|
|
|
for idx, tup in enumerate(zip_list):
|
|
# logger.info(f"delay({idx} * {self.db.mutter_delay_gap}, self.mutter, {tup[0]}, {tup[1]})")
|
|
delay(idx * self.db.mutter_delay_gap,
|
|
self.mutter, tup[0], tup[1])
|
|
|
|
def mutter(self, phrase, msg_format):
|
|
"""
|
|
Mutter something aloud to the room the NPC resides.
|
|
|
|
Why wait for input from other characters when the NPC can
|
|
pretend to be real.
|
|
"""
|
|
thing = self.obj
|
|
room = thing.location
|
|
name = choice([
|
|
thing.key,
|
|
thing.db._sdesc or thing.name,
|
|
thing.key.split(" ")[-1]
|
|
])
|
|
|
|
if thing.db.article:
|
|
prefix = f"{thing.db.article} {name} {msg_format}"
|
|
else:
|
|
prefix = f"{name} {msg_format}"
|
|
|
|
if phrase:
|
|
room.msg_contents(
|
|
prefix.format(phrase),
|
|
from_obj=self.obj)
|
|
|
|
def refresh_mutter_formats(self):
|
|
return self.obj.db.muttering_formats or [
|
|
"mutters as if no one is listening, \"{0}\"",
|
|
"grumbles to |oself, \"{0}\"",
|
|
"continues to mutter to |oself, \"{0}\"",
|
|
"murmurs to |oself, \"{0}\"",
|
|
"mutters to |oself, \"{0}\"|/",
|
|
]
|
|
|
|
def load_mutter(self, data_file):
|
|
"""
|
|
Return 'data_file' and return a list of lists.
|
|
|
|
Where a blank line in the data file separates the list of
|
|
lines from others.
|
|
"""
|
|
path = Path(__file__).with_name(data_file)
|
|
mutters = []
|
|
with open(path) as file:
|
|
curr_mutter = []
|
|
for line in file:
|
|
if line.strip() == "":
|
|
mutters = mutters + [curr_mutter]
|
|
curr_mutter = []
|
|
else:
|
|
curr_mutter.append(line.strip())
|
|
return mutters
|