Scripts are the out-of-character siblings to the in-character Objects. The name “Script” might suggest that they can only be used to script the game but this is only part of their usefulness (in the end we had to pick a single name for them). Scripts are persistent in the database just like Objects and you can attach Attributes to it.
Scripts can be used for many different things in Evennia:
- They can attach to Objects to influence them in various ways - or exist independently of any one in-game entity.
- They can work as timers and tickers - anything that may change with Time. But they can also have no time dependence at all.
- They can describe State changes.
- They can act as data stores for storing game data persistently in the database.
- They can be used as OOC stores for sharing data between groups of objects.
A Script is an excellent platform for hosting a persistent, but unique system handler. For example, a Script could be used as the base to track the state of a turn-based combat system. Since Scripts can also operate on a timer they can also update themselves regularly to perform various actions.
A Script is very powerful and together with its ability to hold Attributes you can use it for data storage in various ways. However, if all you want is just to have an object method called repeatedly, you should consider using the TickerHandler which is more limited but is specialized on just this task.
How to create and test your own Script types¶
In-game you can try out scripts using the
@script command. Try the
following to apply the script to your character.
> @script self = bodyfunctions.BodyFunctions
Or, if you want to inflict your flatulence script on another person, place or thing, try something like the following:
> @py self.location.search('matt').scripts.add('bodyfunctions.BodyFunctions')
This should cause some random messages to appear at random intervals.
You can find this example in
> @script/stop self = bodyfunctions.BodyFunctions
This will kill the script again. You can use the
@scripts command to
list all active scripts in the game, if any (there are none by default).
If you add scripts to Objects the script can then manipulate the
object as desired. The script is added to the object’s script handler,
scripts. The handler takes care of all initialization
and startup of the script for you.
# add script to myobj's scripthandler myobj.scripts.add("myscripts.CoolScript") # alternative way from evennia import create_script create_script("myscripts.CoolScript", obj=myobj)
A script does not have to be connected to an in-game object. If not it is called a Global script. You can create global scripts by simply not supplying an object to store it on:
# adding a global script from evennia import create_script create_script("typeclasses.globals.MyGlobalEconomy", key="economy", persistent=True, obj=None)
You can create a global script manually using
@py or by putting the
above for example in
means the script will be created only once, when the server is started
for the very first time (there are other files in the
mygame/server/conf/ folder that triggers at other times).
Properties and functions defined on Scripts¶
A Script has all the properties of a typeclassed object, such as
ndb(see Typeclasses). Setting
key is useful in order to
manage scripts (delete them by name etc). These are usually set up in
the Script’s typeclass, but can also be assigned on the fly as keyword
desc- an optional description of the script’s function. Seen in script listings.
interval- how often the script should run. If
interval == 0(default), it runs forever, without any repeating (it will not accept a negative value).
start_delay- (bool), if we should wait
intervalseconds 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 reset or server shutdown. (You don’t need to set this for it to survive a normal reload - the script will be paused and seamlessly restart after the reload is complete).
There is one special property:
obj- the Object this script is attached to (if any). You should not need to set this manually. If you add the script to the Object with
myobjas an argument to the
objproperty will be set to
at_script_creation()- this is usually where the script class sets things like
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 is also called by Evennia 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 starts or is unpaused. For persistent scripts this is at least once ever server startup. Note that this will always be called right away, also if
at_repeat()- this is called every
intervalseconds, or not at all. It is called right away at startup, unless
True, in which case the system will wait
intervalseconds before calling.
at_stop()- this is called when the script stops for whatever reason. It’s a good place to do custom cleanup.
at_server_reload()- this is called whenever the server is warm-rebooted (e.g. with the
@reloadcommand). It’s a good place to save non-persistent data you might want to survive a reload.
at_server_shutdown()- this is called when a system reset or systems shutdown is invoked.
Running methods (usually called automatically by the engine, but possible to also invoke manually)
start()- this will start the script. This is called automatically whenever you add a new script to a handler.
at_start()will be called.
stop()- this will stop the script and delete it. Removing a script from a handler will stop it automatically.
at_stop()will be called.
pause()- this pauses a running script, rendering it inactive, but not deleting it. All properties are saved and timers can be resumed. This is called automatically when the server reloads and will not lead to the at_stop() hook being called. This is a suspension of the script, not a change of state.
unpause()- resumes a previously paused script. The
at_start()hook will be called to allow it to reclaim its internal state. Timers etc are restored to what they were before pause. The server automatically un-pauses all paused scripts after a server reload.
force_repeat()- this will forcibly step the script, regardless of when it would otherwise have fired. The timer will reset and the
at_repeat()hook is called as normal. This also counts towards the total number of repeats, if limited.
time_until_next_repeat()- for timed scripts, this returns the time in seconds until it next fires. Returns
remaining_repeats()- if the Script should run a limited amount of times, this tells us how many are currently left.
reset_callcount(value=0)- this allows you to reset the number of times the Script has fired. It only makes sense if
repeats > 0.
restart(interval=None, repeats=None, start_delay=None)- this method allows you to restart the Script in-place with different run settings. If you do, the
at_stophook will be called and the Script brought to a halt, then the
at_starthook will be called as the Script starts up with your (possibly changed) settings. Any keyword left at
Nonemeans to not change the original setting.
Defining new Scripts¶
There are two ways to create a new Script type:
- Creating a Script instance using
evennia.create_script(). This function takes all the important script parameters (
start_delayetc) as keyword arguments. It will return a newly instanced (and started) script object. If you set the keyword
persistent=True, the returned Script will survive a server reset/reboot too.
- Define a new Script Typeclass. This you can do for example in the
evennia/typeclasses/scripts.py. Below is an example Script Typeclass.
import random from evennia import DefaultScript class Weather(DefaultScript): """Displays weather info. Meant to be attached to a room.""" def at_script_creation(self): self.key = "weather_script" self.desc = "Gives random weather messages." self.interval = 60 * 5 # every 5 minutes self.persistent = True def 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. That object becomes
self.obj in the example above. Here we
put it on a room called
Once you have the typeclass written you can feed your Typeclass to the
create_script function directly:
from evennia import create_script create_script('typeclasses.weather.Weather', obj=myroom)
Note that if you were to give a keyword argument to
that would override the default value in your Typeclass. So for example:
create_script('typeclasses.weather.Weather', obj=myroom, persistent=False, interval=10*60)
This particular instance of the Weather Script would run with a 10 minute interval. It would also not survive a server reset/reboot.
From in-game you can use the
@script command as usual to get to your
@script here = weather.Weather
You can conveniently view and kill running Scripts by using the
@scripts command in-game.
Dealing with Errors¶
Errors inside an executing script can sometimes be rather terse or point to parts of the execution mechanism that is hard to interpret. One way to make it easier to debug scripts is to import Evennia’s native logger and wrap your functions in a try/catch block. Evennia’s logger can show you where the traceback occurred in your script.
from evennia.utils import logger class Weather(DefaultScript): # [...] def at_repeat(self): try: # [...] code as above except Exception: # logs the error logger.log_trace()