evennia/docs/sphinx/source/wiki/Scripts.rst

160 lines
7.8 KiB
ReStructuredText

Scripts
=======
*Scripts* are the way to implement everything in Evennia that may change
with time.
The most obvious use of Scripts may be to use them as *timers* or
*Events*. Consider a script running on the object ``Grandfather Clock``.
The script has a timer that tells it to fire every hour. This allows the
clock to chime regularly. The script might even regulate how often the
clock must be rewound so it won't stop.
Scripts may act as changeable *States*. Consider for example creating a
'dark' room. It has two scripts assigned on it - one ``DarkState``
script, and one ``BrightState`` script. When characters enters the dark
room, it assigns a custom `Cmdset <Commands.html>`_ to them - this
command set (say) limits their actions because of the darkness. After
the characters have stumbled around for a while, someone brings up a
torch. As a light source is now in the room, ``DarkState`` reacts to
this by shutting down itself and handing over control to the
``BrightState`` script that restores normal commands. Finally, when the
character with the torch leaves the room, the ``BrightState`` script
detects this and obediently hands control back to the ``DarkState``,
leaving the remaining poor characters in darkness once again.
By combining state-changes with timers one can make a room look
different during nighttime than it does during the day. Weather and
seasons might come and go. But one can also achieve more complex things
such as state-AI systems that make mobs move around and possibly persue
characters between rooms.
... In short, Scripts make the game world *tick*. Scripts are
database-persistent objects and are `TypeClassed <Typeclasses.html>`_
entities, with all the advantages that this entails.
How to create your own Script types
-----------------------------------
An Evennia Script is, per definition, a Python class that includes
``src.scripts.scripts.Script`` among its parents (if you are aware of
how typeclasses work, this is a typeclass linked to the ``ScriptDB``
database model). Scripts have no in-game representation and you cannot
define them with any default commands. They have to be created in python
code modules and imported from there into the game.
The vast majority of scripts are always run 'on'
`Objects <Objects.html>`_ affecting that object and maybe its
surroundings or contents. Scripts unrelated to objects are called
*Global* scripts and could handle things like game-time, weather and
other tickers.
Custom script modules are usually stored in ``game/gamesrc/scripts``. As
a convenience you can inherit all scripts from
``game.gamesrc.scripts.basescript.Script``.
You can try out scripts an add them to objects by use of the ``@script``
command (not to the confused with ``@scripts`` which lists scripts). You
can try it out with an example script:
::
> @script self = examples.bodyfunctions.BodyFunctions
This should cause some random messages. The ``/stop`` switch will kill
the script again.
In code, you add scripts to `Objects <Objects.html>`_ and the script can
then manipulate the object as desired. The script is added to the
object's *script handler*, called simply ``scripts``. The handler takes
care of all initialization and startup of the script for you.
::
# adding a script to an existing object 'myobj'myobj.scripts.add("game.gamesrc.scripts.myscripts.CoolScript")
The ``myobj.scripts.add()`` method also takes an argument *key* that
allows you to name your script uniquely before adding it. This is not
necessary, but is useful if you add many scripts of the same class and
later plan to use ``myobj.scripts.delete`` to remove individual scripts.
Properties and functions defined on Scripts
-------------------------------------------
It's important to know the variables controlling the script before one
can create one. Beyond those properties assigned to all typeclassed
objects (see `Typeclasses <Typeclasses.html>`_), such as ``key``,
``db``, ``ndb`` etc, all Scripts also has the following properties:
- ``desc`` - an optional description of the script's function. Seen in
listings.
- ``interval`` - how often the script should run. If ``interval == 0``
(default), it runs forever (it will not accept a negative value).
- ``start_delay`` - (bool), if we should wait ``interval`` seconds
before firing for the first time or not.
- ``repeats`` - How many times we should repeat, assuming
``interval > 0``. If repeats is set to ``<= 0``, the script will
repeat indefinitely.
- ``persistent``- if this script should survive a server reboot.
There is one special property:
- ``obj`` - the `Object <Objects.html>`_ this script is attached to (if
any). You should not need to set this manually. If you add the script
to the Object with ``myobj.scripts.add(myscriptpath)``, the ``obj``
property will be set to ``myobj`` for you.
It's also imperative to know the hook functions. Normally, overriding
these are all the customization you'll need to do in Scripts. You can
find longer descriptions of these in ``gamesrc/scripts/basescript.py``.
- ``at_script_creation()`` - this is usually where the script class
sets things like ``interval`` and ``repeats``; things that control
how the script runs. It is only called once - when the script is
first created.
- ``is_valid()`` - determines if the script should still be running or
not. This is called when running ``obj.scripts.validate()``, which
you can run manually, but which also Evennia calls during certain
situations such as reloads. This is also useful for using scripts as
state managers. If the method returns ``False``, the script is
stopped and cleanly removed.
- ``at_start()`` - this is called when the script first starts. For
persistent scripts this is at least once ever server startup. Note
that this will *always* be called right away, also if ``start_delay``
is ``True``.
- ``at_repeat()`` - this is called every ``interval`` seconds, or not
at all. It is called right away at startup, unless ``start_delay`` is
``True``, in which case the system will wait ``interval`` seconds
before calling.
- ``at_stop()`` - this is called when the script stops for whatever
reason. It's a good place to do custom cleanup.
::
import random
from game.gamesrc.scripts.basescript import Scriptclass Weather(Script): "Displays weather info. Meant to be attached to a room." def at_script_creation(self): "Called once, during initial creation" self.key = "weather_script" self.desc = "Gives random weather messages." self.interval = 60 * 5 # every 5 minutes self.persistent = True self.at_repeat(self): "called every self.interval seconds." rand = random.random() if rand < 0.5: weather = "A faint breeze is felt." elif rand < 0.7: weather = "Clouds sweep across the sky." else: weather = "There is a light drizzle of rain." # send this message to everyone inside the object this # script is attached to (likely a room) self.obj.msg_contents(weather)
This is a simple weather script that we can put on an object. Every 5
minutes it will tell everyone inside that object how the weather is.
To activate it, just add it to the script handler (``scripts``) on an
`Room <Objects.html>`_. That object becomes ``self.obj`` in the example
above. Here we put it on a room called ``myroom``:
::
# Assuming Script is found in game/gamesrc/scripts/weather.pymyroom.scripts.add(weather.Weather)
Or, from in-game, use the ``@script`` command:
::
@script here = weather.Weather
Further notes
-------------
Should you *really* need to create a script unrelated to a particular
Object (this is called a *Global* script), you can create it with
``src.utils.create.create_script()`` and refrain from supplying an
object to store it on. See that module for more info.