diff --git a/docs/0.9.5/api/evennia.commands.default.system.html b/docs/0.9.5/api/evennia.commands.default.system.html
index 43786ef2c3..30e3fb2e20 100644
--- a/docs/0.9.5/api/evennia.commands.default.system.html
+++ b/docs/0.9.5/api/evennia.commands.default.system.html
@@ -293,7 +293,7 @@ required since whole classes of scripts often have the same name.
diff --git a/docs/0.9.5/api/evennia.commands.default.unloggedin.html b/docs/0.9.5/api/evennia.commands.default.unloggedin.html
index a54d9f8a86..de6460a971 100644
--- a/docs/0.9.5/api/evennia.commands.default.unloggedin.html
+++ b/docs/0.9.5/api/evennia.commands.default.unloggedin.html
@@ -207,7 +207,7 @@ All it does is display the connect screen.
diff --git a/docs/0.9.5/api/evennia.contrib.chargen.html b/docs/0.9.5/api/evennia.contrib.chargen.html
index 0c51c9ce03..8279191a21 100644
--- a/docs/0.9.5/api/evennia.contrib.chargen.html
+++ b/docs/0.9.5/api/evennia.contrib.chargen.html
@@ -76,7 +76,7 @@ at them with this command.
diff --git a/docs/0.9.5/api/evennia.contrib.dice.html b/docs/0.9.5/api/evennia.contrib.dice.html
index 366e7900eb..091a1c7b56 100644
--- a/docs/0.9.5/api/evennia.contrib.dice.html
+++ b/docs/0.9.5/api/evennia.contrib.dice.html
@@ -148,7 +148,7 @@ everyone but the person rolling.
diff --git a/docs/0.9.5/api/evennia.contrib.email_login.html b/docs/0.9.5/api/evennia.contrib.email_login.html
index 19f3656d0c..1259524c13 100644
--- a/docs/0.9.5/api/evennia.contrib.email_login.html
+++ b/docs/0.9.5/api/evennia.contrib.email_login.html
@@ -210,7 +210,7 @@ All it does is display the connect screen.
diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.cmdset_red_button.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.cmdset_red_button.html
index 303eb1338d..089dba0d23 100644
--- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.cmdset_red_button.html
+++ b/docs/0.9.5/api/evennia.contrib.tutorial_examples.cmdset_red_button.html
@@ -104,7 +104,7 @@ push the lid of the button away.
diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button_scripts.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button_scripts.html
index 759a90fdef..c8f5e6107b 100644
--- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button_scripts.html
+++ b/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button_scripts.html
@@ -423,7 +423,7 @@ called directly, regardless of the value of self.start_delay
diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html b/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html
index 2d1f98a350..d4f85b0f8f 100644
--- a/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html
+++ b/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html
@@ -350,7 +350,7 @@ of the object. We overload it with our own version.
diff --git a/docs/0.9.5/api/evennia.utils.evmore.html b/docs/0.9.5/api/evennia.utils.evmore.html
index 581b67aef5..b410dde9aa 100644
--- a/docs/0.9.5/api/evennia.utils.evmore.html
+++ b/docs/0.9.5/api/evennia.utils.evmore.html
@@ -74,7 +74,7 @@ the caller.msg() construct every time the page is updated.
diff --git a/docs/1.0-dev/.buildinfo b/docs/1.0-dev/.buildinfo
index 750fddbd5b..6790f7ebdc 100644
--- a/docs/1.0-dev/.buildinfo
+++ b/docs/1.0-dev/.buildinfo
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
-config: 729ce4db33e156590f593ca473a70187
+config: 06b2b3a1f62a31cf544bb68a25ac299e
tags: 645f666f9bcd5a90fca523b33c5a78b7
diff --git a/docs/1.0-dev/Components/Scripts.html b/docs/1.0-dev/Components/Scripts.html
index 53ee793e7f..5f089da2cd 100644
--- a/docs/1.0-dev/Components/Scripts.html
+++ b/docs/1.0-dev/Components/Scripts.html
@@ -39,55 +39,66 @@
Scripts are the out-of-character siblings to the in-character
-Objects. Scripts are so flexible that the “Script” is a bit limiting
+Objects. Scripts are so flexible that the name “Script” is a bit limiting
+in itself - but we had to pick something to name them. Other possible names
+(depending on what you’d use them for) would be OOBObjects, StorageContainers or TimerObjects.
+
If you ever consider creating an Object with a None-location just to store some game data,
+you should really be using a Script instead.
-
we had to pick something to name them after all. Other possible names
-(depending on what you’d use them for) would be OOBObjects,
-StorageContainers or TimerObjects.
+
Scripts are full Typeclassed entities - they have Attributes and
+can be modified in the same way. But they have no in-game existence, so no
+location or command-execution like Objects and no connection to a particular
+player/session like Accounts. This means they are perfectly suitable for acting
+as database-storage backends for game systems: Storing the current state of the economy,
+who is involved in the current fight, tracking an ongoing barter and so on. They are great as
+persistent system handlers.
+
Scripts have an optional timer component. This means that you can set up the script
+to tick the at_repeat hook on the Script at a certain interval. The timer can be controlled
+independently of the rest of the script as needed. This component is optional
+and complementary to other timing functions in Evennia, like
+evennia.utils.delay and
+evennia.utils.repeat.
+
Scripts can attach to Objects and Accounts via e.g. obj.scripts.add/remove. In the
+script you can then access the object/account as self.obj or self.account. This can be used to
+dynamically extend other typeclasses but also to use the timer component to affect the parent object
+in various ways. For historical reasons, a Script not attached to an object is referred to as a
+Global Script.
-
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 (so-called Global Scripts).
-
They can work as timers and tickers - anything that may change with Time. But
-they can also have no time dependence at all. Note though that 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.
-
They can describe State changes. 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.
-
They can act as data stores for storing game data persistently in the database
-(thanks to its ability to have Attributes).
-
They can be used as OOC stores for sharing data between groups of objects, for
-example for tracking the turns in a turn-based combat system or barter exchange.
-
-
Scripts are Typeclassed entities and are manipulated in a similar
-way to how it works for other such Evennia entities:
-
1
-2
-3
-4
-5
# create a new script
-new_script=evennia.create_script(key="myscript",typeclass=...)
-
-# search (this is always a list, also if there is only one match)
-list_of_myscript=evennia.search_script("myscript")
+
+
Changed in version 1.0: In previus Evennia versions, stopping the Script’s timer also meant deleting the Script object.
+Starting with this version, the timer can be start/stopped separately and .delete() must be called
+on the Script explicitly to delete it.
A Script is defined as a class and is created in the same way as other
-typeclassed entities. The class has several properties
-to control the timer-component of the scripts. These are all optional -
-leaving them out will just create a Script with no timer components (useful to act as
-a database store or to hold a persistent game system, for example).
-
This you can do for example in the module
-evennia/typeclasses/scripts.py. Below is an example Script
-Typeclass.
+
Create script with timer component:
1
2
3
@@ -97,22 +108,210 @@ Typeclass.
7
8
9
-10
fromevenniaimportDefaultScript
+10
+11
# (note that this will call `timed_script.at_repeat` which is empty by default)
+timed_script=evennia.create_script(key="Timed script",
+ interval=34,# seconds <=0 means off
+ start_delay=True,# wait interval before first call
+ autostart=True)# start timer (else needing .start() )
-classMyScript(DefaultScript):
+# manipulate the script's timer
+timed_script.stop()
+timed_script.start()
+timed_script.pause()
+timed_script.unpause()
+
# regular search (this is always a list, also if there is only one match)
+list_of_myscripts=evennia.search_script("myscript")
+
+# search through Evennia's GLOBAL_SCRIPTS container (based on
+# script's key only)
+fromevenniaimportGLOBAL_SCRIPTS
+
+myscript=GLOBAL_SCRIPTS.myscript
+GLOBAL_SCRIPTS.get("Timed script").db.foo="bar"
+
+
+
Delete the Script (this will also stop its timer):
In mygame/typeclasses/scripts.py is an empty Script class already set up. You
+can use this as a base for your own scripts.
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
# in mygame/typeclasses/scripts.py
+
+fromevenniaimportDefaultScript
+
+classScript(DefaultScript):
+ # stuff common for all your scripts goes here
+
+classMyScript(Script):
+ defat_script_creation(selfself):
+ """Called once, when script is first created"""
+ self.key="myscript"
+ self.db.foo="bar"
+
+
+
Once created, this simple Script could act as a global storage:
There are several properties one can set on the Script to control its timer component.
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
# in mygame/typeclasses/scripts.py
+
+classTimerScript(Script):defat_script_creation(self):self.key="myscript"
+ self.desc="An example script"self.interval=60# 1 min repeatdefat_repeat(self):# do stuff every minute
-
In mygame/typeclasses/scripts.py is the Script class which inherits from DefaultScript
-already. This is provided as your own base class to do with what you like: You can tweak Script if
-you want to change the default behavior and it is usually convenient to inherit from this instead.
-Here’s an example:
+
This example will call at_repeat every minute. The create_script function has an autostart=True keyword
+set by default - this means the script’s timer component will be started automatically. Otherwise
+.start() must be called separately.
+
Supported properties are:
+
+
key (str): The name of the script. This makes it easier to search for it later. If it’s a script
+attached to another object one can also get all scripts off that object and get the script that way.
+
desc (str): Note - not .db.desc! This is a database field on the Script shown in script listings
+to help identifying what does what.
+
interval (int): The amount of time (in seconds) between every ‘tick’ of the timer. Note that
+it’s generally bad practice to use sub-second timers for anything in a text-game - the player will
+not be able to appreciate the precision (and if you print it, it will just spam the screen). For
+calculations you can pretty much always do them on-demand, or at a much slower interval without the
+player being the wiser.
+
start_delay (bool): If timer should start right away or wait interval seconds first.
+
repeats (int): If >0, the timer will only run this many times before stopping. Otherwise the
+number of repeats are infinite. If set to 1, the Script mimics a delay action.
+
persistent (bool): This defaults to True and means the timer will survive a server reload/reboot.
+If not, a reload will have the timer come back in a stopped state. Setting this to False will not
+delete the Script object itself (use .delete() for this).
+
+
The timer component is controlled with methods on the Script class:
+
+
.at_repeat() - this method is called every interval seconds while the timer is
+active.
+
.is_valid() - this method is called by the timer just before at_repeat(). If it returns False
+the timer is immediately stopped.
+
.start() - start/update the timer. If keyword arguments are given, they can be used to
+change interval, start_delay etc on the fly. This calls the .at_start() hook.
+This is also called after a server reload assuming the timer was not previously stopped.
+
.update() - legacy alias for .start.
+
.stop() - stops and resets the timer. This calls the .at_stop() hook.
+
.pause() - pauses the timer where it is, storing its current position. This calls
+the .at_pause(manual_pause=True) hook. This is also called on a server reload/reboot,
+at which time the manual_pause will be False.
+
.unpause() - unpause a previously paused script. This will call the at_start hook.
+
.time_until_next_repeat() - get the time until next time the timer fires.
+
.remaining_repeats() - get the number of repeats remaining, or None if repeats are infinite.
+
.reset_callcount() - this resets the repeat counter to start over from 0. Only useful if repeats>0.
+
.force_repeat() - this prematurely forces at_repeat to be called right away. Doing so will reset the
+countdown so that next call will again happen after interval seconds.
If the only goal is to get a repeat/delay effect, the
+evennia.utils.delay and
+evennia.utils.repeat functions
+should generally be considered first. A Script is a lot ‘heavier’ to create/delete on the fly.
+In fact, for making a single delayed call (script.repeats==1), the utils.delay call is
+probably always the better choice.
+
For repeating tasks, the utils.repeat is optimized for quick repeating of a large number of objects. It
+uses the TickerHandler under the hood. Its subscription-based model makes it very efficient to
+start/stop the repeating action for an object. The side effect is however that all objects set to tick
+at a given interval will all do so at the same time. This may or may not look strange in-game depending
+on the situation. By contrast the Script uses its own ticker that will operate independently from the
+tickers of all other Scripts.
+
It’s also worth noting that once the script object has already been created,
+starting/stopping/pausing/unpausing the timer has very little overhead. The pause/unpause and update
+methods of the script also offers a bit more fine-control than using utils.delays/repeat.
Scripts can be attached to an Account or (more commonly) an Object.
+If so, the ‘parent object’ will be available to the script as either .obj or .account.
# for example in mygame/typeclasses/scripts.py
+28
# mygame/typeclasses/scripts.py # Script class is defined at the top of this moduleimportrandom
@@ -156,7 +354,6 @@ Here’s an example:
self.key="weather_script"self.desc="Gives random weather messages."self.interval=60*5# every 5 minutes
- self.persistent=True# will survive reloaddefat_repeat(self):"called every self.interval seconds."
@@ -172,169 +369,91 @@ Here’s an example:
self.obj.msg_contents(weather)
-
If we put this script on a room, it will randomly report some weather
+
If attached to a room, this Script will randomly report some weather
to everyone in the room every 5 minutes.
-
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 myroom:
-
myroom.scripts.add(scripts.Weather)
+
1
myroom.scripts.add(scripts.Weather)
-
+
Note that typeclasses in your game dir is added to the setting TYPECLASS_PATHS.
Therefore we don’t need to give the full path (typeclasses.scripts.Weather
but only scripts.Weather above.
-
You can also create scripts using the evennia.create_script function:
Note that if you were to give a keyword argument to create_script, that would
-override the default value in your Typeclass. So for example, here is an instance
-of the weather script that runs every 10 minutes instead (and also not survive
-a server reload):
A Script has all the properties of a typeclassed object, such as db and 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 arguments to evennia.create_script.
-
desc - an optional description of the script’s function. Seen in script listings.
-
interval - how often the script should run. If interval==0 (default), this script has no
-timing component, will not repeat and will exist forever. This is useful for Scripts used for
-storage or acting as bases for various non-time dependent game systems.
-
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. Note that each firing of the script (including the first one)
-counts towards this value. So a Script with start_delay=False and repeats=1 will start,
-immediately fire and shut down right away.
-
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 myobj.scripts.add(myscriptpath) or give
-myobj as an argument to the utils.create.create_script function, 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 src/scripts/scripts.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 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
-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.
+
at_script_creation() - this is only called once - when the script is first created.
at_server_reload() - this is called whenever the server is warm-rebooted (e.g. with the
-@reload command). It’s a good place to save non-persistent data you might want to survive a
+reload command). 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.
+
at_server_start() - this is called when the server comes back (from reload/shutdown/reboot). It
+can be usuful for initializations and caching of non-persistent data when starting up a script’s
+functionality.
+
at_repeat()
+
at_start()
+
at_pause()
+
at_stop()
+
delete() - same as for other typeclassed entities, this will delete the Script. Of note is that
+it will also stop the timer (if it runs), leading to the at_stop hook being called.
-
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 unpauses 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 None if interval==0.
-
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_stop hook will be called and the
-Script brought to a halt, then the at_start hook will be called as the Script starts up with your
-(possibly changed) settings. Any keyword left at None means to not change the original setting.
-
+
In addition, Scripts support Attributes, Tags and Locks etc like other
+Typeclassed entities.
+
See also the methods involved in controlling a Timed Script above.
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:
-
1
-2
-3
-4
# adding a global script
- fromevenniaimportcreate_script
- create_script("typeclasses.globals.MyGlobalEconomy",
- key="economy",persistent=True,obj=None)
-
-
-
Henceforth you can then get it back by searching for its key or other identifier with
-evennia.search_script. In-game, the scripts command will show all scripts.
-
Evennia supplies a convenient “container” called GLOBAL_SCRIPTS that can offer an easy
-way to access global scripts. If you know the name (key) of the script you can get it like so:
A Script not attached to another entity is commonly referred to as a Global script since it’t available
+to access from anywhere. This means they need to be searched for in order to be used.
+
Evennia supplies a convenient “container” evennia.GLOBAL_SCRIPTS to help organize your global
+scripts. All you need is the Script’s key.
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
fromevenniaimportGLOBAL_SCRIPTS
+# access as a property on the container, named the same as the keymy_script=GLOBAL_SCRIPTS.my_script# needed if there are spaces in name or name determined on the flyanother_script=GLOBAL_SCRIPTS.get("another script")
-# get all global scripts (this returns a Queryset)
+# get all global scripts (this returns a Django Queryset)all_scripts=GLOBAL_SCRIPTS.all()# you can operate directly on the scriptGLOBAL_SCRIPTS.weather.db.current_weather="Cloudy"
-
-
Note that global scripts appear as properties on GLOBAL_SCRIPTS based on their key.
-If you were to create two global scripts with the same key (even with different typeclasses),
-the GLOBAL_SCRIPTS container will only return one of them (which one depends on order in
+
+
Warning
+
Note that global scripts appear as properties on GLOBAL_SCRIPTS based on their key.
+If you were to create two global scripts with the same key (even with different typeclasses),
+the GLOBAL_SCRIPTS container will only return one of them (which one depends on order in
the database). Best is to organize your scripts so that this does not happen. Otherwise, use
-evennia.search_script to get exactly the script you want.
-
-
There are two ways to make a script appear as a property on GLOBAL_SCRIPTS. The first is
-to manually create a new global script with create_script as mentioned above. Often you want this
-to happen automatically when the server starts though. For this you can use the setting
-GLOBAL_SCRIPTS:
+evennia.search_scripts to get exactly the script you want.
+
+
There are two ways to make a script appear as a property on GLOBAL_SCRIPTS:
+
+
Manually create a new global script with a key using create_script.
+
Define the script’s properties in the GLOBAL_SCRIPTS settings variable. This tells Evennia
+that it should check if a script with that key exists and if not, create it for you.
+This is very useful for scripts that must always exist and/or should be auto-created with your server.
+
+
Here’s how to tell Evennia to manage the script in settings:
1
2
3
@@ -345,36 +464,30 @@ to happen automatically when the server starts though. For this you can use the
8
9
10
-11
-12
-13
Here the key (myscript and storagescript above) is required, all other fields are optional. If
-typeclass is not given, a script of type settings.BASE_SCRIPT_TYPECLASS is assumed. The keys
-related to timing and intervals are only needed if the script is timed.
-
Evennia will use the information in settings.GLOBAL_SCRIPTS to automatically create and start
-these
-scripts when the server starts (unless they already exist, based on their key). You need to reload
-the server before the setting is read and new scripts become available. You can then find the key
-you gave as properties on evennia.GLOBAL_SCRIPTS
-(such as evennia.GLOBAL_SCRIPTS.storagescript).
-
-
Note: Make sure that your Script typeclass does not have any critical errors. If so, you’ll see
-errors in your log and your Script will temporarily fall back to being a DefaultScript type.
-
+
Above we add two scripts with keys myscript and storagescriptrespectively. The following dict
+can be empty - the settings.BASE_SCRIPT_TYPECLASS will then be used. Under the hood, the provided
+dict (along with the key) will be passed into create_script automatically, so
+all the same keyword arguments as for create_script are
+supported here.
+
+
Warning
+
Before setting up Evennia to manage your script like this, make sure that your Script typeclass
+does not have any critical errors (test it separately). If there are, you’ll see errors in your log
+and your Script will temporarily fall back to being a DefaultScript type.
+
Moreover, a script defined this way is guaranteed to exist when you try to access it:
1
2
@@ -382,26 +495,19 @@ errors in your log and your Script will temporarily fall back to being a
fromevenniaimportGLOBAL_SCRIPTS
-# first stop the script
-GLOBAL_SCRIPTS.storagescript.stop()
+# Delete the script
+GLOBAL_SCRIPTS.storagescript.delete()# running the `scripts` command now will show no storagescript
-# but below now it's recreated again!
+# but below it's automatically recreated again! storage=GLOBAL_SCRIPTS.storagescript
-
That is, if the script is deleted, next time you get it from GLOBAL_SCRIPTS, it will use the
-information
-in settings to recreate it for you.
-
-
Note that if your goal with the Script is to store persistent data, you should set it as
-persistent=True, either in settings.GLOBAL_SCRIPTS or in the Scripts typeclass. Otherwise any
-data you wanted to store on it will be gone (since a new script of the same name is restarted
-instead).
-
+
That is, if the script is deleted, next time you get it from GLOBAL_SCRIPTS, Evennia will use the
+information in settings to recreate it for you on the fly.
Errors inside a timed, 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
@@ -418,54 +524,22 @@ traceback occurred in your script.
10
11
12
-13
-14
-15
+13
fromevennia.utilsimportlogger
-classWeather(DefaultScript):
+classWeather(Script):# [...]defat_repeat(self):try:
- # [...] code as above
+ # [...]exceptException:
- # logs the error logger.log_trace()
-
In-game you can try out scripts using the @script command. In the
-evennia/contrib/tutorial_examples/bodyfunctions.py is a little example script
-that makes you do little ‘sounds’ at random intervals. Try the following to apply an
-example time-based script to your character.
-
>@scriptself=bodyfunctions.BodyFunctions
-
-
-
-
Note: Since evennia/contrib/tutorial_examples is in the default setting
-TYPECLASS_PATHS, we only need to specify the final part of the path,
-that is, bodyfunctions.BodyFunctions.
-
-
If you want to inflict your flatulence script on another person, place or
-thing, try something like the following:
[docs]defadd(self,cmdset,emit_to_obj=None,persistent=True,permanent=True,default_cmdset=False,**kwargs):""" Add a cmdset to the handler, on top of the old ones, unless it is set as the default one (it will then end up at the bottom of the stack)
@@ -472,7 +472,9 @@
cmdset (CmdSet or str): Can be a cmdset object or the python path to such an object. emit_to_obj (Object, optional): An object to receive error messages.
- permanent (bool, optional): This cmdset will remain across a server reboot.
+ persistent (bool, optional): Let cmdset remain across server reload.
+ permanent (bool, optional): DEPRECATED. This has the same use as
+ `persistent`. default_cmdset (Cmdset, optional): Insert this to replace the default cmdset position (there is only one such position, always at the bottom of the stack).
@@ -489,6 +491,12 @@
it's a 'quirk' that has to be documented. """
+ if"permanent"inkwargs:
+ logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed to "
+ "'persistent' and now defaults to True.")
+
+ permanent=persistentorpermanent
+
ifnot(isinstance(cmdset,str)orutils.inherits_from(cmdset,CmdSet)):string=_("Only CmdSets can be added to the cmdsethandler!")raiseException(string)
diff --git a/docs/1.0-dev/_modules/evennia/commands/default/batchprocess.html b/docs/1.0-dev/_modules/evennia/commands/default/batchprocess.html
index d02953eaf6..d59bfb7ba6 100644
--- a/docs/1.0-dev/_modules/evennia/commands/default/batchprocess.html
+++ b/docs/1.0-dev/_modules/evennia/commands/default/batchprocess.html
@@ -225,7 +225,7 @@
# something went wrong. Purge cmdset except defaultcaller.cmdset.clear()
- caller.scripts.validate()# this will purge interactive mode
+ # caller.scripts.validate() # this will purge interactive mode# -------------------------------------------------------------
diff --git a/docs/1.0-dev/_modules/evennia/commands/default/building.html b/docs/1.0-dev/_modules/evennia/commands/default/building.html
index 8799e37071..a017a1d9f5 100644
--- a/docs/1.0-dev/_modules/evennia/commands/default/building.html
+++ b/docs/1.0-dev/_modules/evennia/commands/default/building.html
@@ -3101,7 +3101,7 @@
attach a script to an object Usage:
- script[/switch] <obj> [= script_path or <scriptkey>]
+ addscript[/switch] <obj> [= script_path or <scriptkey>] Switches: start - start all non-running scripts on object, or a given script only
@@ -3116,8 +3116,8 @@
the object. """
- key="script"
- aliases="addscript"
+ key="addscript"
+ aliases=["attachscript"]switch_options=("start","stop")locks="cmd:perm(script) or perm(Builder)"help_category="Building"
@@ -3162,7 +3162,6 @@
%(script.get_display_name(caller),obj.get_display_name(caller)))script.stop()
- obj.scripts.validate()else:# rhs existsifnotself.switches:# adding a new script, and starting it
diff --git a/docs/1.0-dev/_modules/evennia/commands/default/system.html b/docs/1.0-dev/_modules/evennia/commands/default/system.html
index 32abf4b428..8363c95c5e 100644
--- a/docs/1.0-dev/_modules/evennia/commands/default/system.html
+++ b/docs/1.0-dev/_modules/evennia/commands/default/system.html
@@ -478,7 +478,6 @@
"|wintval|n","|wnext|n","|wrept|n",
- "|wdb","|wtypeclass|n","|wdesc|n",align="r",
@@ -490,9 +489,10 @@
nextrep=script.time_until_next_repeat()ifnextrepisNone:
- nextrep="PAUSED"ifscript.db._paused_timeelse"--"
+ nextrep=script.db._paused_time
+ nextrep=f"PAUSED {int(nextrep)}s"ifnextrepelse"--"else:
- nextrep="%ss"%nextrep
+ nextrep=f"{nextrep}s"maxrepeat=script.repeatsremaining=script.remaining_repeats()or0
@@ -502,7 +502,7 @@
rept="-/-"table.add_row(
- script.id,
+ f"#{script.id}",f"{script.obj.key}({script.obj.dbref})"if(hasattr(script,"obj")andscript.obj)else"<Global>",
@@ -510,7 +510,6 @@
script.intervalifscript.interval>0else"--",nextrep,rept,
- "*"ifscript.persistentelse"-",script.typeclass_path.rsplit(".",1)[-1],crop(script.desc,width=20),)
@@ -520,16 +519,19 @@
[docs]classCmdScripts(COMMAND_DEFAULT_CLASS):"""
- list and manage all running scripts
+ List and manage all running scripts. Allows for creating new global
+ scripts. Usage:
- scripts[/switches] [#dbref, key, script.path or <obj>]
+ script[/switches] [#dbref, key, script.path or <obj>] Switches:
- start - start a script (must supply a script path)
- stop - stops an existing script
- kill - kills a script - without running its cleanup hooks
- validate - run a validation on the script(s)
+ create - create a new global script of given typeclass path. This will
+ auto-start the script's timer if it has one.
+ start - start/unpause an existing script's timer.
+ stop - stops an existing script's timer
+ pause - pause a script's timer
+ delete - deletes script. This will also stop the timer as needed If no switches are given, this command just views all active scripts. The argument can be either an object, at which point it
@@ -537,83 +539,100 @@
or #dbref. For using the /stop switch, a unique script #dbref is required since whole classes of scripts often have the same name.
- Use script for managing commands on objects.
+ Use the `script` build-level command for managing scripts attached to
+ objects.
+
"""key="scripts"
- aliases=["globalscript","listscripts"]
- switch_options=("start","stop","kill","validate")
+ aliases=["scripts"]
+ switch_options=("create","start","stop","pause","delete")locks="cmd:perm(listscripts) or perm(Admin)"help_category="System"excluded_typeclass_paths=["evennia.prototypes.prototypes.DbPrototype"]
+ switch_mapping={
+ "create":"|gCreated|n",
+ "start":"|gStarted|n",
+ "stop":"|RStopped|n",
+ "pause":"|Paused|n",
+ "delete":"|rDeleted|n"
+ }
+
+ def_search_script(self,args):
+ # test first if this is a script match
+ scripts=ScriptDB.objects.get_all_scripts(key=args)
+ ifscripts:
+ returnscripts
+ # try typeclass path
+ scripts=ScriptDB.objects.filter(db_typeclass_path__iendswith=args)
+ ifscripts:
+ returnscripts
+ # try to find an object instead.
+ objects=ObjectDB.objects.object_search(args)
+ ifobjects:
+ scripts=ScriptDB.objects.filter(db_obj__in=objects)
+ returnscripts
+
[docs]deffunc(self):"""implement method"""caller=self.callerargs=self.args
- ifargs:
- if"start"inself.switches:
- # global script-start mode
- new_script=create.create_script(args)
- ifnew_script:
- caller.msg("Global script %s was started successfully."%args)
- else:
- caller.msg("Global script %s could not start correctly. See logs."%args)
+ if"create"inself.switches:
+ # global script-start mode
+ verb=self.switch_mapping['create']
+ ifnotargs:
+ caller.msg("Usage script/create <key or typeclass>")return
-
- # test first if this is a script match
- scripts=ScriptDB.objects.get_all_scripts(key=args)
- ifnotscripts:
- # try to find an object instead.
- objects=ObjectDB.objects.object_search(args)
- ifobjects:
- scripts=[]
- forobjinobjects:
- # get all scripts on the object(s)
- scripts.extend(ScriptDB.objects.get_all_scripts_on_obj(obj))
- else:
- # we want all scripts.
- scripts=ScriptDB.objects.get_all_scripts()
- ifnotscripts:
- caller.msg("No scripts are running.")
- return
- # filter any found scripts by tag category.
- scripts=scripts.exclude(db_typeclass_path__in=self.excluded_typeclass_paths)
-
- ifnotscripts:
- string="No scripts found with a key '%s', or on an object named '%s'."%(args,args)
- caller.msg(string)
+ new_script=create.create_script(args)
+ ifnew_script:
+ caller.msg(f"Global Script {verb} - {new_script.key} ({new_script.typeclass_path})")
+ ScriptEvMore(caller,[new_script],session=self.session)
+ else:
+ caller.msg(f"Global Script |rNOT|n {verb} |r(see log)|n - arguments: {args}")return
- ifself.switchesandself.switches[0]in("stop","del","delete","kill"):
- # we want to delete something
- iflen(scripts)==1:
- # we have a unique match!
- if"kill"inself.switches:
- string="Killing script '%s'"%scripts[0].key
- scripts[0].stop(kill=True)
- else:
- string="Stopping script '%s'."%scripts[0].key
- scripts[0].stop()
- # import pdb # DEBUG
- # pdb.set_trace() # DEBUG
- ScriptDB.objects.validate()# just to be sure all is synced
- caller.msg(string)
- else:
- # multiple matches.
- ScriptEvMore(caller,scripts,session=self.session)
- caller.msg("Multiple script matches. Please refine your search")
- elifself.switchesandself.switches[0]in("validate","valid","val"):
- # run validation on all found scripts
- nr_started,nr_stopped=ScriptDB.objects.validate(scripts=scripts)
- string="Validated %s scripts. "%ScriptDB.objects.all().count()
- string+="Started %s and stopped %s scripts."%(nr_started,nr_stopped)
- caller.msg(string)
+ # all other switches require existing scripts
+ ifargs:
+ scripts=self._search_script(args)
+ ifnotscripts:
+ caller.msg(f"No scripts found matching '{args}'.")
+ returnelse:
- # No stopping or validation. We just want to view things.
+ scripts=ScriptDB.objects.all()
+ ifnotscripts:
+ caller.msg("No scripts found.")
+ return
+
+ ifargsandself.switches:
+ # global script-modifying mode
+ ifscripts.count()>1:
+ caller.msg("Multiple script matches. Please refine your search.")
+ return
+ script=scripts[0]
+ script_key=script.key
+ script_typeclass_path=script.typeclass_path
+ forswitchinself.switches:
+ verb=self.switch_mapping[switch]
+ msgs=[]
+ try:
+ getattr(script,switch)()
+ exceptException:
+ logger.log_trace()
+ msgs.append(f"Global Script |rNOT|n {verb} |r(see log)|n - "
+ f"{script_key} ({script_typeclass_path})|n")
+ else:
+ msgs.append(f"Global Script {verb} - "
+ f"{script_key} ({script_typeclass_path})")
+ caller.msg("\n".join(msgs))
+ if"delete"notinself.switches:
+ ScriptEvMore(caller,[script],session=self.session)
+ return
+ else:
+ # simply show the found scriptsScriptEvMore(caller,scripts.order_by("id"),session=self.session)
diff --git a/docs/1.0-dev/_modules/evennia/commands/default/tests.html b/docs/1.0-dev/_modules/evennia/commands/default/tests.html
index fc259f1dbb..48e5de6bde 100644
--- a/docs/1.0-dev/_modules/evennia/commands/default/tests.html
+++ b/docs/1.0-dev/_modules/evennia/commands/default/tests.html
@@ -1205,8 +1205,8 @@
self.call(building.CmdScript(),"Obj ","dbref ")self.call(
- building.CmdScript(),"/start Obj","0 scripts started on Obj"
- )# because it's already started
+ building.CmdScript(),"/start Obj","1 scripts started on Obj"
+ )# we allow running start again; this should still happenself.call(building.CmdScript(),"/stop Obj","Stopping script")self.call(
@@ -1562,7 +1562,10 @@
[docs]@patch("evennia.contrib.tutorial_examples.red_button.repeat")
+ @patch("evennia.contrib.tutorial_examples.red_button.delay")
+ deftest_batch_commands(self,mock_delay,mock_repeat):# cannot test batchcode here, it must run inside the server processself.call(batchprocess.CmdBatchCommands(),
@@ -1573,7 +1576,8 @@
confirm=building.CmdDestroy.confirmbuilding.CmdDestroy.confirm=Falseself.call(building.CmdDestroy(),"button","button was destroyed.")
- building.CmdDestroy.confirm=confirm
[docs]defat_server_start(self):"""Set up the event system when starting. Note that this hook is called every time the server restarts
diff --git a/docs/1.0-dev/_modules/evennia/contrib/ingame_python/tests.html b/docs/1.0-dev/_modules/evennia/contrib/ingame_python/tests.html
index c631c96010..8b0c295d42 100644
--- a/docs/1.0-dev/_modules/evennia/contrib/ingame_python/tests.html
+++ b/docs/1.0-dev/_modules/evennia/contrib/ingame_python/tests.html
@@ -90,7 +90,7 @@
"""Stop the event handler."""OLD_EVENTS.clear()OLD_EVENTS.update(self.handler.ndb.events)
- self.handler.stop()
+ self.handler.delete()CallbackHandler.script=Nonesuper().tearDown()
[docs]defat_defeat(defeated):""" Announces the defeat of a fighter in combat.
-
+
Args: defeated (obj): Fighter that's been defeated.
-
+
Notes: All this does is announce a defeat message by default, but if you want anything else to happen to defeated fighters (like putting them
@@ -524,6 +524,7 @@
ifdisengage_check:# All characters have disengagedself.obj.msg_contents("All fighters have disengaged! Combat is over!")self.stop()# Stop this script and end combat.
+ self.delete()return# Check to see if only one character is left standing. If so, end combat.
@@ -539,6 +540,7 @@
LastStanding=fighter# Pick the one fighter left with HP remainingself.obj.msg_contents("Only %s remains! Combat is over!"%LastStanding)self.stop()# Stop this script and end combat.
+ self.delete()return# Cycle to the next turn.
diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorial_examples/cmdset_red_button.html b/docs/1.0-dev/_modules/evennia/contrib/tutorial_examples/cmdset_red_button.html
deleted file mode 100644
index ac28b4c318..0000000000
--- a/docs/1.0-dev/_modules/evennia/contrib/tutorial_examples/cmdset_red_button.html
+++ /dev/null
@@ -1,440 +0,0 @@
-
-
-
-
-
-
-
- evennia.contrib.tutorial_examples.cmdset_red_button — Evennia 1.0-dev documentation
-
-
-
-
-
-
-
-
-
-
-
-
Source code for evennia.contrib.tutorial_examples.cmdset_red_button
-"""
-This defines the cmdset for the red_button. Here we have defined
-the commands and the cmdset in the same module, but if you
-have many different commands to merge it is often better
-to define the cmdset separately, picking and choosing from
-among the available commands as to what should be included in the
-cmdset - this way you can often re-use the commands too.
-"""
-
-importrandom
-fromevenniaimportCommand,CmdSet
-
-# Some simple commands for the red button
-
-# ------------------------------------------------------------
-# Commands defined on the red button
-# ------------------------------------------------------------
-
-
-
[docs]classCmdNudge(Command):
- """
- Try to nudge the button's lid
-
- Usage:
- nudge lid
-
- This command will have you try to
- push the lid of the button away.
- """
-
- key="nudge lid"# two-word command name!
- aliases=["nudge"]
- locks="cmd:all()"
-
-
[docs]deffunc(self):
- """
- nudge the lid. Random chance of success to open it.
- """
- rand=random.random()
- ifrand<0.5:
- self.caller.msg("You nudge at the lid. It seems stuck.")
- elifrand<0.7:
- self.caller.msg("You move the lid back and forth. It won't budge.")
- else:
- self.caller.msg("You manage to get a nail under the lid.")
- self.caller.execute_cmd("open lid")
[docs]deffunc(self):
- """
- Note that we choose to implement this with checking for
- if the lid is open/closed. This is because this command
- is likely to be tried regardless of the state of the lid.
-
- An alternative would be to make two versions of this command
- and tuck them into the cmdset linked to the Open and Closed
- lid-state respectively.
-
- """
-
- ifself.obj.db.lid_open:
- string="You reach out to press the big red button ..."
- string+="\n\nA BOOM! A bright light blinds you!"
- string+="\nThe world goes dark ..."
- self.caller.msg(string)
- self.caller.location.msg_contents(
- "%s presses the button. BOOM! %s is blinded by a flash!"
- %(self.caller.name,self.caller.name),
- exclude=self.caller,
- )
- # the button's method will handle all setup of scripts etc.
- self.obj.press_button(self.caller)
- else:
- string="You cannot push the button - there is a glass lid covering it."
- self.caller.msg(string)
-
-
-
[docs]classCmdSmashGlass(Command):
- """
- smash glass
-
- Usage:
- smash glass
-
- Try to smash the glass of the button.
- """
-
- key="smash glass"
- aliases=["smash lid","break lid","smash"]
- locks="cmd:all()"
-
-
[docs]deffunc(self):
- """
- The lid won't open, but there is a small chance
- of causing the lamp to break.
- """
- rand=random.random()
-
- ifrand<0.2:
- string="You smash your hand against the glass"
- string+=" with all your might. The lid won't budge"
- string+=" but you cause quite the tremor through the button's mount."
- string+="\nIt looks like the button's lamp stopped working for the time being."
- self.obj.lamp_works=False
- elifrand<0.6:
- string="You hit the lid hard. It doesn't move an inch."
- else:
- string="You place a well-aimed fist against the glass of the lid."
- string+=" Unfortunately all you get is a pain in your hand. Maybe"
- string+=" you should just try to open the lid instead?"
- self.caller.msg(string)
- self.caller.location.msg_contents(
- "%s tries to smash the glass of the button."%(self.caller.name),exclude=self.caller
- )
-
-
-
[docs]classCmdOpenLid(Command):
- """
- open lid
-
- Usage:
- open lid
-
- """
-
- key="open lid"
- aliases=["open button","open"]
- locks="cmd:all()"
-
-
[docs]deffunc(self):
- "simply call the right function."
-
- ifself.obj.db.lid_locked:
- self.caller.msg("This lid seems locked in place for the moment.")
- return
-
- string="\nA ticking sound is heard, like a winding mechanism. Seems "
- string+="the lid will soon close again."
- self.caller.msg(string)
- self.caller.location.msg_contents(
- "%s opens the lid of the button."%(self.caller.name),exclude=self.caller
- )
- # add the relevant cmdsets to button
- self.obj.cmdset.add(LidClosedCmdSet)
- # call object method
- self.obj.open_lid()
-
-
-
[docs]classCmdCloseLid(Command):
- """
- close the lid
-
- Usage:
- close lid
-
- Closes the lid of the red button.
- """
-
- key="close lid"
- aliases=["close"]
- locks="cmd:all()"
-
-
[docs]deffunc(self):
- "Close the lid"
-
- self.obj.close_lid()
-
- # this will clean out scripts dependent on lid being open.
- self.caller.msg("You close the button's lid. It clicks back into place.")
- self.caller.location.msg_contents(
- "%s closes the button's lid."%(self.caller.name),exclude=self.caller
- )
-
-
-
[docs]classCmdBlindLook(Command):
- """
- Looking around in darkness
-
- Usage:
- look <obj>
-
- ... not that there's much to see in the dark.
-
- """
-
- key="look"
- aliases=["l","get","examine","ex","feel","listen"]
- locks="cmd:all()"
-
-
[docs]deffunc(self):
- "This replaces all the senses when blinded."
-
- # we decide what to reply based on which command was
- # actually tried
-
- ifself.cmdstring=="get":
- string="You fumble around blindly without finding anything."
- elifself.cmdstring=="examine":
- string="You try to examine your surroundings, but can't see a thing."
- elifself.cmdstring=="listen":
- string="You are deafened by the boom."
- elifself.cmdstring=="feel":
- string="You fumble around, hands outstretched. You bump your knee."
- else:
- # trying to look
- string="You are temporarily blinded by the flash. "
- string+="Until it wears off, all you can do is feel around blindly."
- self.caller.msg(string)
- self.caller.location.msg_contents(
- "%s stumbles around, blinded."%(self.caller.name),exclude=self.caller
- )
-
-
-
[docs]classCmdBlindHelp(Command):
- """
- Help function while in the blinded state
-
- Usage:
- help
-
- """
-
- key="help"
- aliases="h"
- locks="cmd:all()"
-
-
[docs]deffunc(self):
- "Give a message."
- self.caller.msg("You are beyond help ... until you can see again.")
-
-
-# ---------------------------------------------------------------
-# Command sets for the red button
-# ---------------------------------------------------------------
-
-# We next tuck these commands into their respective command sets.
-# (note that we are overdoing the cdmset separation a bit here
-# to show how it works).
-
-
-
[docs]classDefaultCmdSet(CmdSet):
- """
- The default cmdset always sits
- on the button object and whereas other
- command sets may be added/merge onto it
- and hide it, removing them will always
- bring it back. It's added to the object
- using obj.cmdset.add_default().
- """
-
- key="RedButtonDefault"
- mergetype="Union"# this is default, we don't really need to put it here.
-
-
[docs]defat_cmdset_creation(self):
- "Init the cmdset"
- self.add(CmdPush())
-
-
-
[docs]classLidClosedCmdSet(CmdSet):
- """
- A simple cmdset tied to the redbutton object.
-
- It contains the commands that launches the other
- command sets, making the red button a self-contained
- item (i.e. you don't have to manually add any
- scripts etc to it when creating it).
- """
-
- key="LidClosedCmdSet"
- # default Union is used *except* if we are adding to a
- # cmdset named LidOpenCmdSet - this one we replace
- # completely.
- key_mergetype={"LidOpenCmdSet":"Replace"}
-
-
[docs]defat_cmdset_creation(self):
- "Populates the cmdset when it is instantiated."
- self.add(CmdNudge())
- self.add(CmdSmashGlass())
- self.add(CmdOpenLid())
-
-
-
[docs]classLidOpenCmdSet(CmdSet):
- """
- This is the opposite of the Closed cmdset.
- """
-
- key="LidOpenCmdSet"
- # default Union is used *except* if we are adding to a
- # cmdset named LidClosedCmdSet - this one we replace
- # completely.
- key_mergetype={"LidClosedCmdSet":"Replace"}
-
-
[docs]defat_cmdset_creation(self):
- "setup the cmdset (just one command)"
- self.add(CmdCloseLid())
-
-
-
[docs]classBlindCmdSet(CmdSet):
- """
- This is the cmdset added to the *account* when
- the button is pushed.
- """
-
- key="BlindCmdSet"
- # we want it to completely replace all normal commands
- # until the timed script removes it again.
- mergetype="Replace"
- # we want to stop the account from walking around
- # in this blinded state, so we hide all exits too.
- # (channel commands will still work).
- no_exits=True# keep account in the same room
- no_objs=True# don't allow object commands
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/1.0-dev/_modules/evennia/contrib/tutorial_examples/red_button.html b/docs/1.0-dev/_modules/evennia/contrib/tutorial_examples/red_button.html
index 9c64045585..58fc1e8890 100644
--- a/docs/1.0-dev/_modules/evennia/contrib/tutorial_examples/red_button.html
+++ b/docs/1.0-dev/_modules/evennia/contrib/tutorial_examples/red_button.html
@@ -48,14 +48,381 @@
Create this button with
- @create/drop examples.red_button.RedButton
+ create/drop red_button.RedButtonNote that you must drop the button before you can see its messages!
+
+## Technical
+
+The button's functionality is controlled by CmdSets that gets added and removed
+depending on the 'state' the button is in.
+
+- Lid-closed state: In this state the button is covered by a glass cover and trying
+ to 'push' it will fail. You can 'nudge', 'smash' or 'open' the lid.
+- Lid-open state: In this state the lid is open but will close again after a certain
+ time. Using 'push' now will press the button and trigger the Blind-state.
+- Blind-state: In this mode you are blinded by a bright flash. This will affect your
+ normal commands like 'look' and help until the blindness wears off after a certain
+ time.
+
+Timers are handled by persistent delays on the button. These are examples of
+`evennia.utils.utils.delay` calls that wait a certain time before calling a method -
+such as when closing the lid and un-blinding a character.
+
"""importrandomfromevenniaimportDefaultObject
-fromevennia.contrib.tutorial_examplesimportred_button_scriptsasscriptexamples
-fromevennia.contrib.tutorial_examplesimportcmdset_red_buttonascmdsetexamples
+fromevenniaimportCommand,CmdSet
+fromevennia.utils.utilsimportdelay,repeat,interactive
+
+
+# Commands on the button (not all awailable at the same time)
+
+
+# Commands for the state when the lid covering the button is closed.
+
+
[docs]deffunc(self):
+ """
+ This is the version of push used when the lid is closed.
+
+ An alternative would be to make a 'push' command in a default cmdset
+ that is always available on the button and then use if-statements to
+ check if the lid is open or closed.
+
+ """
+ self.caller.msg("You cannot push the button = there is a glass lid covering it.")
+
+
+
[docs]classCmdNudge(Command):
+ """
+ Try to nudge the button's lid.
+
+ Usage:
+ nudge lid
+
+ This command will have you try to push the lid of the button away.
+
+ """
+
+ key="nudge lid"# two-word command name!
+ aliases=["nudge"]
+ locks="cmd:all()"
+
+
[docs]deffunc(self):
+ """
+ Nudge the lid. Random chance of success to open it.
+
+ """
+ rand=random.random()
+ ifrand<0.5:
+ self.caller.msg("You nudge at the lid. It seems stuck.")
+ elifrand<0.7:
+ self.caller.msg("You move the lid back and forth. It won't budge.")
+ else:
+ self.caller.msg("You manage to get a nail under the lid.")
+ # self.obj is the button object
+ self.obj.to_open_state()
+
+
+
[docs]classCmdSmashGlass(Command):
+ """
+ Smash the protective glass.
+
+ Usage:
+ smash glass
+
+ Try to smash the glass of the button.
+
+ """
+
+ key="smash glass"
+ aliases=["smash lid","break lid","smash"]
+ locks="cmd:all()"
+
+
[docs]deffunc(self):
+ """
+ The lid won't open, but there is a small chance of causing the lamp to
+ break.
+
+ """
+ rand=random.random()
+ self.caller.location.msg_contents(
+ f"{self.caller.name} tries to smash the glass of the button.",
+ exclude=self.caller)
+
+ ifrand<0.2:
+ string=("You smash your hand against the glass"
+ " with all your might. The lid won't budge"
+ " but you cause quite the tremor through the button's mount."
+ "\nIt looks like the button's lamp stopped working for the time being, "
+ "but the lid is still as closed as ever.")
+ # self.obj is the button itself
+ self.obj.break_lamp()
+ elifrand<0.6:
+ string="You hit the lid hard. It doesn't move an inch."
+ else:
+ string=("You place a well-aimed fist against the glass of the lid."
+ " Unfortunately all you get is a pain in your hand. Maybe"
+ " you should just try to just ... open the lid instead?")
+ self.caller.msg(string)
+
+
+
[docs]classCmdOpenLid(Command):
+ """
+ open lid
+
+ Usage:
+ open lid
+
+ """
+
+ key="open lid"
+ aliases=["open button"]
+ locks="cmd:all()"
+
+
[docs]deffunc(self):
+ "simply call the right function."
+
+ ifself.obj.db.lid_locked:
+ self.caller.msg("This lid seems locked in place for the moment.")
+ return
+
+ string="\nA ticking sound is heard, like a winding mechanism. Seems "
+ string+="the lid will soon close again."
+ self.caller.msg(string)
+ self.caller.location.msg_contents(
+ f"{self.caller.name} opens the lid of the button.",
+ exclude=self.caller)
+ self.obj.to_open_state()
+
+
+
[docs]classLidClosedCmdSet(CmdSet):
+ """
+ A simple cmdset tied to the redbutton object.
+
+ It contains the commands that launches the other
+ command sets, making the red button a self-contained
+ item (i.e. you don't have to manually add any
+ scripts etc to it when creating it).
+
+ Note that this is given with a `key_mergetype` set. This
+ is set up so that the cmdset with merge with Union merge type
+ *except* if the other cmdset to merge with is LidOpenCmdSet,
+ in which case it will Replace that. So these two cmdsets will
+ be mutually exclusive.
+
+ """
+
+ key="LidClosedCmdSet"
+
+
[docs]defat_cmdset_creation(self):
+ "Populates the cmdset when it is instantiated."
+ self.add(CmdPushLidClosed())
+ self.add(CmdNudge())
+ self.add(CmdSmashGlass())
+ self.add(CmdOpenLid())
+
+
+# Commands for the state when the button's protective cover is open - now the
+# push command will work. You can also close the lid again.
+
+
[docs]classCmdPushLidOpen(Command):
+ """
+ Push the red button
+
+ Usage:
+ push button
+
+ """
+
+ key="push button"
+ aliases=["push","press button","press"]
+ locks="cmd:all()"
+
+ @interactive
+ deffunc(self):
+ """
+ This version of push will immediately trigger the next button state.
+
+ The use of the @interactive decorator allows for using `yield` to add
+ simple pauses in how quickly a message is returned to the user. This
+ kind of pause will not survive a server reload.
+
+ """
+ # pause a little between each message.
+ self.caller.msg("You reach out to press the big red button ...")
+ yield(2)# pause 2s before next message
+ self.caller.msg("\n\n|wBOOOOM! A bright light blinds you!|n")
+ yield(1)# pause 1s before next message
+ self.caller.msg("\n\n|xThe world goes dark ...|n")
+
+ name=self.caller.name
+ self.caller.location.msg_contents(
+ f"{name} presses the button. BOOM! {name} is blinded by a flash!",
+ exclude=self.caller)
+ self.obj.blind_target(self.caller)
+
+
+
[docs]classCmdCloseLid(Command):
+ """
+ Close the lid
+
+ Usage:
+ close lid
+
+ Closes the lid of the red button.
+ """
+
+ key="close lid"
+ aliases=["close"]
+ locks="cmd:all()"
+
+
[docs]deffunc(self):
+ "Close the lid"
+
+ self.obj.to_closed_state()
+
+ # this will clean out scripts dependent on lid being open.
+ self.caller.msg("You close the button's lid. It clicks back into place.")
+ self.caller.location.msg_contents(
+ f"{self.caller.name} closes the button's lid.",
+ exclude=self.caller)
+
+
+
[docs]classLidOpenCmdSet(CmdSet):
+ """
+ This is the opposite of the Closed cmdset.
+
+ Note that this is given with a `key_mergetype` set. This
+ is set up so that the cmdset with merge with Union merge type
+ *except* if the other cmdset to merge with is LidClosedCmdSet,
+ in which case it will Replace that. So these two cmdsets will
+ be mutually exclusive.
+
+ """
+
+ key="LidOpenCmdSet"
+
+
[docs]defat_cmdset_creation(self):
+ """Setup the cmdset"""
+ self.add(CmdPushLidOpen())
+ self.add(CmdCloseLid())
+
+
+# Commands for when the button has been pushed and the player is blinded. This
+# replaces commands on the player making them 'blind' for a while.
+
+
[docs]classCmdBlindLook(Command):
+ """
+ Looking around in darkness
+
+ Usage:
+ look <obj>
+
+ ... not that there's much to see in the dark.
+
+ """
+
+ key="look"
+ aliases=["l","get","examine","ex","feel","listen"]
+ locks="cmd:all()"
+
+
[docs]deffunc(self):
+ "This replaces all the senses when blinded."
+
+ # we decide what to reply based on which command was
+ # actually tried
+
+ ifself.cmdstring=="get":
+ string="You fumble around blindly without finding anything."
+ elifself.cmdstring=="examine":
+ string="You try to examine your surroundings, but can't see a thing."
+ elifself.cmdstring=="listen":
+ string="You are deafened by the boom."
+ elifself.cmdstring=="feel":
+ string="You fumble around, hands outstretched. You bump your knee."
+ else:
+ # trying to look
+ string=("You are temporarily blinded by the flash. "
+ "Until it wears off, all you can do is feel around blindly.")
+ self.caller.msg(string)
+ self.caller.location.msg_contents(
+ f"{self.caller.name} stumbles around, blinded.",
+ exclude=self.caller)
+
+
+
[docs]classCmdBlindHelp(Command):
+ """
+ Help function while in the blinded state
+
+ Usage:
+ help
+
+ """
+
+ key="help"
+ aliases="h"
+ locks="cmd:all()"
+
+
[docs]deffunc(self):
+ """
+ Just give a message while blinded. We could have added this to the
+ CmdBlindLook command too if we wanted to keep things more compact.
+
+ """
+ self.caller.msg("You are beyond help ... until you can see again.")
+
+
+
[docs]classBlindCmdSet(CmdSet):
+ """
+ This is the cmdset added to the *account* when
+ the button is pushed.
+
+ Since this has mergetype Replace it will completely remove the commands of
+ all other cmdsets while active. To allow some limited interaction
+ (pose/say) we import those default commands and add them too.
+
+ We also disable all exit-commands generated by exits and
+ object-interactions while blinded by setting `no_exits` and `no_objs` flags
+ on the cmdset. This is to avoid the player walking off or interfering with
+ other objects while blinded. Account-level commands however (channel messaging
+ etc) will not be affected by the blinding.
+
+ """
+
+ key="BlindCmdSet"
+ # we want it to completely replace all normal commands
+ # until the timed script removes it again.
+ mergetype="Replace"
+ # we want to stop the player from walking around
+ # in this blinded state, so we hide all exits too.
+ # (channel commands will still work).
+ no_exits=True# keep player in the same room
+ no_objs=True# don't allow object commands
+
+
+
## Definition of the object itself
@@ -64,149 +431,192 @@
[docs]classRedButton(DefaultObject):"""
- This class describes an evil red button. It will use the script
- definition in contrib/examples/red_button_scripts to blink at regular
- intervals. It also uses a series of script and commands to handle
- pushing the button and causing effects when doing so.
+ This class describes an evil red button. It will blink invitingly and
+ temporarily blind whomever presses it.
- The following attributes can be set on the button:
- desc_lid_open - description when lid is open
- desc_lid_closed - description when lid is closed
- desc_lamp_broken - description when lamp is broken
+ The button can take a few optional attributes controlling how things will
+ be displayed in its various states. This is a useful way to give builders
+ the option to customize a complex object from in-game. Actual return messages
+ to event-actions are (in this example) left with each command, but one could
+ also imagine having those handled via Attributes as well, if one wanted a
+ completely in-game customizable button without needing to tweak command
+ classes.
+
+ Attributes:
+ - `desc_closed_lid`: This is the description to show of the button
+ when the lid is closed.
+ - `desc_open_lid`": Shown when the lid is open
+ - `auto_close_msg`: Message to show when lid auto-closes
+ - `desc_add_lamp_broken`: Extra desc-line added after normal desc when lamp
+ is broken.
+ - blink_msg: A list of strings to randomly choose from when the lamp
+ blinks.
+
+ Notes:
+ The button starts with lid closed. To set the initial description,
+ you can either set desc after creating it or pass a `desc` attribute
+ when creating it, such as
+ `button = create_object(RedButton, ..., attributes=[('desc', 'my desc')])`. """
+ # these are the pre-set descriptions. Setting attributes will override
+ # these on the fly.
+
+ desc_closed_lid=("This is a large red button, inviting yet evil-looking. "
+ "A closed glass lid protects it.")
+ desc_open_lid=("This is a large red button, inviting yet evil-looking. "
+ "Its glass cover is open and the button exposed.")
+ auto_close_msg="The button's glass lid silently slides back in place."
+ lamp_breaks_msg="The lamp flickers, the button going dark."
+ desc_add_lamp_broken="\nThe big red button has stopped blinking for the time being."
+ # note that this is a list. A random message will display each time
+ blink_msgs=["The red button flashes briefly.",
+ "The red button blinks invitingly.",
+ "The red button flashes. You know you wanna push it!"]
[docs]defat_object_creation(self):"""
- This function is called when object is created. Use this
- instead of e.g. __init__.
- """
- # store desc (default, you can change this at creation time)
- desc="This is a large red button, inviting yet evil-looking. "
- desc+="A closed glass lid protects it."
- self.db.desc=desc
+ This function is called (once) when object is created.
- # We have to define all the variables the scripts
- # are checking/using *before* adding the scripts or
- # they might be deactivated before even starting!
- self.db.lid_open=False
+ """self.db.lamp_works=True
- self.db.lid_locked=False
- self.cmdset.add_default(cmdsetexamples.DefaultCmdSet,permanent=True)
+ # start closed
+ self.to_closed_state()
- # since the cmdsets relevant to the button are added 'on the fly',
- # we need to setup custom scripts to do this for us (also, these scripts
- # check so they are valid (i.e. the lid is actually still closed)).
- # The AddClosedCmdSet script makes sure to add the Closed-cmdset.
- self.scripts.add(scriptexamples.ClosedLidState)
- # the script EventBlinkButton makes the button blink regularly.
- self.scripts.add(scriptexamples.BlinkButtonEvent)
+ # start blinking every 35s.
+ repeat(35,self._do_blink,persistent=True)
- # state-changing methods
-
-
[docs]defopen_lid(self):
+ def_do_blink(self):"""
- Opens the glass lid and start the timer so it will soon close
- again.
+ Have the button blink invitingly unless it's broken. """
+ ifself.locationandself.db.lamp_works:
+ possible_messages=self.db.blink_msgsorself.blink_msgs
+ self.location.msg_contents(random.choice(possible_messages))
- ifself.db.lid_open:
- return
- desc=self.db.desc_lid_open
- ifnotdesc:
- desc="This is a large red button, inviting yet evil-looking. "
- desc+="Its glass cover is open and the button exposed."
- self.db.desc=desc
- self.db.lid_open=True
-
- # with the lid open, we validate scripts; this will clean out
- # scripts that depend on the lid to be closed.
- self.scripts.validate()
- # now add new scripts that define the open-lid state
- self.scripts.add(scriptexamples.OpenLidState)
- # we also add a scripted event that will close the lid after a while.
- # (this one cleans itself after being called once)
- self.scripts.add(scriptexamples.CloseLidEvent)
-
-
[docs]defclose_lid(self):
+ def_set_desc(self,attrname=None):"""
- Close the glass lid. This validates all scripts on the button,
- which means that scripts only being valid when the lid is open
- will go away automatically.
-
- """
-
- ifnotself.db.lid_open:
- return
- desc=self.db.desc_lid_closed
- ifnotdesc:
- desc="This is a large red button, inviting yet evil-looking. "
- desc+="Its glass cover is closed, protecting it."
- self.db.desc=desc
- self.db.lid_open=False
-
- # clean out scripts depending on lid to be open
- self.scripts.validate()
- # add scripts related to the closed state
- self.scripts.add(scriptexamples.ClosedLidState)
-
-
[docs]defbreak_lamp(self,feedback=True):
- """
- Breaks the lamp in the button, stopping it from blinking.
+ Set a description, based on the attrname given, taking the lamp-status
+ into account. Args:
- feedback (bool): Show a message about breaking the lamp.
+ attrname (str, optional): This will first check for an Attribute with this name,
+ secondly for a property on the class. So if `attrname="auto_close_msg"`,
+ we will first look for an attribute `.db.auto_close_msg` and if that's
+ not found we'll use `.auto_close_msg` instead. If unset (`None`), the
+ currently set desc will not be changed (only lamp will be checked).
+
+ Notes:
+ If `self.db.lamp_works` is `False`, we'll append
+ `desc_add_lamp_broken` text.
+
+ """
+ ifattrname:
+ # change desc
+ desc=self.attributes.get(attrname)orgetattr(self,attrname)
+ else:
+ # use existing desc
+ desc=self.db.desc
+
+ ifnotself.db.lamp_works:
+ # lamp not working. Add extra to button's desc
+ desc+=self.db.desc_add_lamp_brokenorself.desc_add_lamp_broken
+
+ self.db.desc=desc
+
+ # state-changing methods and actions
+
+
[docs]defto_closed_state(self,msg=None):
+ """
+ Switches the button to having its lid closed.
+
+ Args:
+ msg (str, optional): If given, display a message to the room
+ when lid closes.
+
+ This will first try to get the Attribute (self.db.desc_closed_lid) in
+ case it was set by a builder and if that was None, it will fall back to
+ self.desc_closed_lid, the default description (note that lack of .db).
+ """
+ self._set_desc("desc_closed_lid")
+ # remove lidopen-state, if it exists
+ self.cmdset.remove(LidOpenCmdSet)
+ # add lid-closed cmdset
+ self.cmdset.add(LidClosedCmdSet)
+
+ ifmsgandself.location:
+ self.location.msg_contents(msg)
+
+
[docs]defto_open_state(self):
+ """
+ Switches the button to having its lid open. This also starts a timer
+ that will eventually close it again.
+
+ """
+ self._set_desc("desc_open_lid")
+ # remove lidopen-state, if it exists
+ self.cmdset.remove(LidClosedCmdSet)
+ # add lid-open cmdset
+ self.cmdset.add(LidOpenCmdSet)
+
+ # wait 20s then call self.to_closed_state with a message as argument
+ delay(35,self.to_closed_state,
+ self.db.auto_close_msgorself.auto_close_msg,
+ persistent=True)
+
+ def_unblind_target(self,caller):
+ """
+ This is called to un-blind after a certain time.
+
+ """
+ caller.cmdset.remove(BlindCmdSet)
+ caller.msg("You blink feverishly as your eyesight slowly returns.")
+ self.location.msg_contents(
+ f"{caller.name} seems to be recovering their eyesight, blinking feverishly.",
+ exclude=caller)
+
+
[docs]defblind_target(self,caller):
+ """
+ Someone was foolish enough to press the button! Blind them
+ temporarily.
+
+ Args:
+ caller (Object): The one to be blinded.
+
+ """
+
+ # we don't need to remove other cmdsets, this will replace all,
+ # then restore whatever was there when it goes away.
+ caller.cmdset.add(BlindCmdSet)
+
+ # wait 20s then call self._unblind to remove blindness effect. The
+ # persistent=True means the delay should survive a server reload.
+ delay(20,self._unblind_target,caller,
+ persistent=True)
+
+ def_unbreak_lamp(self):
+ """
+ This is called to un-break the lamp after a certain time.
+
+ """
+ # we do this quietly, the user will just notice it starting blinking again
+ self.db.lamp_works=True
+ self._set_desc()
+
+
[docs]defbreak_lamp(self):
+ """
+ Breaks the lamp in the button, stopping it from blinking for a while """self.db.lamp_works=False
- desc=self.db.desc_lamp_broken
- ifnotdesc:
- self.db.desc+="\nThe big red button has stopped blinking for the time being."
- else:
- self.db.desc=desc
+ # this will update the desc with the info about the broken lamp
+ self._set_desc()
+ self.location.msg_contents(self.db.lamp_breaks_msgorself.lamp_breaks_msg)
- iffeedbackandself.location:
- self.location.msg_contents("The lamp flickers, the button going dark.")
- self.scripts.validate()
-
-
[docs]defpress_button(self,pobject):
- """
- Someone was foolish enough to press the button!
-
- Args:
- pobject (Object): The person pressing the button
-
- """
- # deactivate the button so it won't flash/close lid etc.
- self.scripts.add(scriptexamples.DeactivateButtonEvent)
- # blind the person pressing the button. Note that this
- # script is set on the *character* pressing the button!
- pobject.scripts.add(scriptexamples.BlindedState)
-
- # script-related methods
-
-
[docs]defblink(self):
- """
- The script system will regularly call this
- function to make the button blink. Now and then
- it won't blink at all though, to add some randomness
- to how often the message is echoed.
- """
- loc=self.location
- ifloc:
- rand=random.random()
- ifrand<0.2:
- string="The red button flashes briefly."
- elifrand<0.4:
- string="The red button blinks invitingly."
- elifrand<0.6:
- string="The red button flashes. You know you wanna push it!"
- else:
- # no blink
- return
- loc.msg_contents(string)
+ # wait 21s before unbreaking the lamp again
+ delay(21,self._unbreak_lamp)
Source code for evennia.contrib.tutorial_examples.red_button_scripts
-"""
-Example of scripts.
-
-These are scripts intended for a particular object - the
-red_button object type in contrib/examples. A few variations
-on uses of scripts are included.
-
-"""
-fromevenniaimportDefaultScript
-fromevennia.contrib.tutorial_examplesimportcmdset_red_buttonascmdsetexamples
-
-#
-# Scripts as state-managers
-#
-# Scripts have many uses, one of which is to statically
-# make changes when a particular state of an object changes.
-# There is no "timer" involved in this case (although there could be),
-# whenever the script determines it is "invalid", it simply shuts down
-# along with all the things it controls.
-#
-# To show as many features as possible of the script and cmdset systems,
-# we will use three scripts controlling one state each of the red_button,
-# each with its own set of commands, handled by cmdsets - one for when
-# the button has its lid open, and one for when it is closed and a
-# last one for when the player pushed the button and gets blinded by
-# a bright light. The last one also has a timer component that allows it
-# to remove itself after a while (and the player recovers their eyesight).
-
-
-
[docs]classClosedLidState(DefaultScript):
- """
- This manages the cmdset for the "closed" button state. What this
- means is that while this script is valid, we add the RedButtonClosed
- cmdset to it (with commands like open, nudge lid etc)
- """
-
-
[docs]defat_script_creation(self):
- "Called when script first created."
- self.key="closed_lid_script"
- self.desc="Script that manages the closed-state cmdsets for red button."
- self.persistent=True
-
-
[docs]defat_start(self):
- """
- This is called once every server restart, so we want to add the
- (memory-resident) cmdset to the object here. is_valid is automatically
- checked so we don't need to worry about adding the script to an
- open lid.
- """
- # All we do is add the cmdset for the closed state.
- self.obj.cmdset.add(cmdsetexamples.LidClosedCmdSet)
-
-
[docs]defis_valid(self):
- """
- The script is only valid while the lid is closed.
- self.obj is the red_button on which this script is defined.
- """
- returnnotself.obj.db.lid_open
-
-
[docs]defat_stop(self):
- """
- When the script stops we must make sure to clean up after us.
-
- """
- self.obj.cmdset.delete(cmdsetexamples.LidClosedCmdSet)
-
-
-
[docs]classOpenLidState(DefaultScript):
- """
- This manages the cmdset for the "open" button state. This will add
- the RedButtonOpen
- """
-
-
[docs]defat_script_creation(self):
- "Called when script first created."
- self.key="open_lid_script"
- self.desc="Script that manages the opened-state cmdsets for red button."
- self.persistent=True
-
-
[docs]defat_start(self):
- """
- This is called once every server restart, so we want to add the
- (memory-resident) cmdset to the object here. is_valid is
- automatically checked, so we don't need to worry about
- adding the cmdset to a closed lid-button.
- """
- self.obj.cmdset.add(cmdsetexamples.LidOpenCmdSet)
-
-
[docs]defis_valid(self):
- """
- The script is only valid while the lid is open.
- self.obj is the red_button on which this script is defined.
- """
- returnself.obj.db.lid_open
-
-
[docs]defat_stop(self):
- """
- When the script stops (like if the lid is closed again)
- we must make sure to clean up after us.
- """
- self.obj.cmdset.delete(cmdsetexamples.LidOpenCmdSet)
-
-
-
[docs]classBlindedState(DefaultScript):
- """
- This is a timed state.
-
- This adds a (very limited) cmdset TO THE ACCOUNT, during a certain time,
- after which the script will close and all functions are
- restored. It's up to the function starting the script to actually
- set it on the right account object.
- """
-
-
[docs]defat_script_creation(self):
- """
- We set up the script here.
- """
- self.key="temporary_blinder"
- self.desc="Temporarily blinds the account for a little while."
- self.interval=20# seconds
- self.start_delay=True# we don't want it to stop until after 20s.
- self.repeats=1# this will go away after interval seconds.
- self.persistent=False# we will ditch this if server goes down
-
-
[docs]defat_start(self):
- """
- We want to add the cmdset to the linked object.
-
- Note that the RedButtonBlind cmdset is defined to completly
- replace the other cmdsets on the stack while it is active
- (this means that while blinded, only operations in this cmdset
- will be possible for the account to perform). It is however
- not persistent, so should there be a bug in it, we just need
- to restart the server to clear out of it during development.
- """
- self.obj.cmdset.add(cmdsetexamples.BlindCmdSet)
-
-
[docs]defat_stop(self):
- """
- It's important that we clear out that blinded cmdset
- when we are done!
- """
- self.obj.msg("You blink feverishly as your eyesight slowly returns.")
- self.obj.location.msg_contents(
- "%s seems to be recovering their eyesight."%self.obj.name,exclude=self.obj
- )
- self.obj.cmdset.delete()# this will clear the latest added cmdset,
- # (which is the blinded one).
-
-
-#
-# Timer/Event-like Scripts
-#
-# Scripts can also work like timers, or "events". Below we
-# define three such timed events that makes the button a little
-# more "alive" - one that makes the button blink menacingly, another
-# that makes the lid covering the button slide back after a while.
-#
-
-
-
[docs]classCloseLidEvent(DefaultScript):
- """
- This event closes the glass lid over the button
- some time after it was opened. It's a one-off
- script that should be started/created when the
- lid is opened.
- """
-
-
[docs]defat_script_creation(self):
- """
- Called when script object is first created. Sets things up.
- We want to have a lid on the button that the user can pull
- aside in order to make the button 'pressable'. But after a set
- time that lid should auto-close again, making the button safe
- from pressing (and deleting this command).
- """
- self.key="lid_closer"
- self.desc="Closes lid on a red buttons"
- self.interval=20# seconds
- self.start_delay=True# we want to pospone the launch.
- self.repeats=1# we only close the lid once
- self.persistent=True# even if the server crashes in those 20 seconds,
- # the lid will still close once the game restarts.
-
-
[docs]defis_valid(self):
- """
- This script can only operate if the lid is open; if it
- is already closed, the script is clearly invalid.
-
- Note that we are here relying on an self.obj being
- defined (and being a RedButton object) - this we should be able to
- expect since this type of script is always tied to one individual
- red button object and not having it would be an error.
- """
- returnself.obj.db.lid_open
-
-
[docs]defat_repeat(self):
- """
- Called after self.interval seconds. It closes the lid. Before this method is
- called, self.is_valid() is automatically checked, so there is no need to
- check this manually.
- """
- self.obj.close_lid()
-
-
-
[docs]classBlinkButtonEvent(DefaultScript):
- """
- This timed script lets the button flash at regular intervals.
- """
-
-
[docs]defat_script_creation(self):
- """
- Sets things up. We want the button's lamp to blink at
- regular intervals, unless it's broken (can happen
- if you try to smash the glass, say).
- """
- self.key="blink_button"
- self.desc="Blinks red buttons"
- self.interval=35# seconds
- self.start_delay=False# blink right away
- self.persistent=True# keep blinking also after server reboot
-
-
[docs]defis_valid(self):
- """
- Button will keep blinking unless it is broken.
- """
- returnself.obj.db.lamp_works
-
-
[docs]defat_repeat(self):
- """
- Called every self.interval seconds. Makes the lamp in
- the button blink.
- """
- self.obj.blink()
-
-
-
[docs]classDeactivateButtonEvent(DefaultScript):
- """
- This deactivates the button for a short while (it won't blink, won't
- close its lid etc). It is meant to be called when the button is pushed
- and run as long as the blinded effect lasts. We cannot put these methods
- in the AddBlindedCmdSet script since that script is defined on the *account*
- whereas this one must be defined on the *button*.
- """
-
-
[docs]defat_script_creation(self):
- """
- Sets things up.
- """
- self.key="deactivate_button"
- self.desc="Deactivate red button temporarily"
- self.interval=21# seconds
- self.start_delay=True# wait with the first repeat for self.interval seconds.
- self.persistent=True
- self.repeats=1# only do this once
-
-
[docs]defat_start(self):
- """
- Deactivate the button. Observe that this method is always
- called directly, regardless of the value of self.start_delay
- (that just controls when at_repeat() is called)
- """
- # closing the lid will also add the ClosedState script
- self.obj.close_lid()
- # lock the lid so other accounts can't access it until the
- # first one's effect has worn off.
- self.obj.db.lid_locked=True
- # breaking the lamp also sets a correct desc
- self.obj.break_lamp(feedback=False)
-
-
[docs]defat_repeat(self):
- """
- When this is called, reset the functionality of the button.
- """
- # restore button's desc.
-
- self.obj.db.lamp_works=True
- desc="This is a large red button, inviting yet evil-looking. "
- desc+="Its glass cover is closed, protecting it."
- self.db.desc=desc
- # re-activate the blink button event.
- self.obj.scripts.add(BlinkButtonEvent)
- # unlock the lid
- self.obj.db.lid_locked=False
- self.obj.scripts.validate()
-
-
-
\ No newline at end of file
diff --git a/docs/1.0-dev/_modules/evennia/objects/objects.html b/docs/1.0-dev/_modules/evennia/objects/objects.html
index 7b46a97c7c..dd8fbe351f 100644
--- a/docs/1.0-dev/_modules/evennia/objects/objects.html
+++ b/docs/1.0-dev/_modules/evennia/objects/objects.html
@@ -62,7 +62,6 @@
fromevennia.scripts.scripthandlerimportScriptHandlerfromevennia.commandsimportcmdset,commandfromevennia.commands.cmdsethandlerimportCmdSetHandler
-fromevennia.commandsimportcmdhandlerfromevennia.utilsimportcreatefromevennia.utilsimportsearchfromevennia.utilsimportlogger
@@ -83,6 +82,7 @@
_ScriptDB=None_SESSIONS=None
+_CMDHANDLER=None_AT_SEARCH_RESULT=variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".",1))_COMMAND_DEFAULT_CLASS=class_from_module(settings.COMMAND_DEFAULT_CLASS)
@@ -657,12 +657,17 @@
command structure. """
+ # break circular import issues
+ global_CMDHANDLER
+ ifnot_CMDHANDLER:
+ fromevennia.commands.cmdhandlerimportcmdhandleras_CMDHANDLER
+
# nick replacement - we require full-word matching.# do text encoding conversionraw_string=self.nicks.nickreplace(raw_string,categories=("inputline","channel"),include_account=True)
- returncmdhandler.cmdhandler(
+ return_CMDHANDLER(self,raw_string,callertype="object",session=session,**kwargs)
@@ -1154,7 +1159,7 @@
self.account=Noneforscriptin_ScriptDB.objects.get_all_scripts_on_obj(self):
- script.stop()
+ script.delete()# Destroy any exits to and from this room, if anyself.clear_exits()
diff --git a/docs/1.0-dev/_modules/evennia/scripts/manager.html b/docs/1.0-dev/_modules/evennia/scripts/manager.html
index 752eaedb88..e86c0153de 100644
--- a/docs/1.0-dev/_modules/evennia/scripts/manager.html
+++ b/docs/1.0-dev/_modules/evennia/scripts/manager.html
@@ -146,112 +146,22 @@
scripts=self.get_id(dbref)forscriptinmake_iter(scripts):script.stop()
-
- defremove_non_persistent(self,obj=None):
- """
- This cleans up the script database of all non-persistent
- scripts. It is called every time the server restarts.
-
- Args:
- obj (Object, optional): Only remove non-persistent scripts
- assigned to this object.
-
- """
- ifobj:
- to_stop=self.filter(db_obj=obj,db_persistent=False,db_is_active=True)
- to_delete=self.filter(db_obj=obj,db_persistent=False,db_is_active=False)
- else:
- to_stop=self.filter(db_persistent=False,db_is_active=True)
- to_delete=self.filter(db_persistent=False,db_is_active=False)
- nr_deleted=to_stop.count()+to_delete.count()
- forscriptinto_stop:
- script.stop()
- forscriptinto_delete:script.delete()
- returnnr_deleted
- defvalidate(self,scripts=None,obj=None,key=None,dbref=None,init_mode=None):
+ defupdate_scripts_after_server_start(self):"""
- This will step through the script database and make sure
- all objects run scripts that are still valid in the context
- they are in. This is called by the game engine at regular
- intervals but can also be initiated by player scripts.
-
- Only one of the arguments are supposed to be supplied
- at a time, since they are exclusive to each other.
-
- Args:
- scripts (list, optional): A list of script objects to
- validate.
- obj (Object, optional): Validate only scripts defined on
- this object.
- key (str): Validate only scripts with this key.
- dbref (int): Validate only the single script with this
- particular id.
- init_mode (str, optional): This is used during server
- upstart and can have three values:
- - `None` (no init mode). Called during run.
- - `"reset"` - server reboot. Kill non-persistent scripts
- - `"reload"` - server reload. Keep non-persistent scripts.
- Returns:
- nr_started, nr_stopped (tuple): Statistics on how many objects
- where started and stopped.
-
- Notes:
- This method also makes sure start any scripts it validates
- which should be harmless, since already-active scripts have
- the property 'is_running' set and will be skipped.
+ Update/sync/restart/delete scripts after server shutdown/restart. """
+ forscriptinself.filter(db_is_active=True,db_persistent=False):
+ script._stop_task()
- # we store a variable that tracks if we are calling a
- # validation from within another validation (avoids
- # loops).
+ forscriptinself.filter(db_is_active=True):
+ script._unpause_task(auto_unpause=True)
+ script.at_server_start()
- globalVALIDATE_ITERATION
- ifVALIDATE_ITERATION>0:
- # we are in a nested validation. Exit.
- VALIDATE_ITERATION-=1
- returnNone,None
- VALIDATE_ITERATION+=1
-
- # not in a validation - loop. Validate as normal.
-
- nr_started=0
- nr_stopped=0
-
- ifinit_mode:
- ifinit_mode=="reset":
- # special mode when server starts or object logs in.
- # This deletes all non-persistent scripts from database
- nr_stopped+=self.remove_non_persistent(obj=obj)
- # turn off the activity flag for all remaining scripts
- scripts=self.get_all_scripts()
- forscriptinscripts:
- script.is_active=False
-
- elifnotscripts:
- # normal operation
- ifdbrefandself.dbref(dbref,reqhash=False):
- scripts=self.get_id(dbref)
- elifobj:
- scripts=self.get_all_scripts_on_obj(obj,key=key)
- else:
- scripts=self.get_all_scripts(key=key)
-
- ifnotscripts:
- # no scripts available to validate
- VALIDATE_ITERATION-=1
- returnNone,None
-
- forscriptinscripts:
- ifscript.is_valid():
- nr_started+=script.start(force_restart=init_mode)
- else:
- script.stop()
- nr_stopped+=1
- VALIDATE_ITERATION-=1
- returnnr_started,nr_stopped
+ forscriptinself.filter(db_is_active=False):
+ script.at_server_start()defsearch_script(self,ostring,obj=None,only_timed=False,typeclass=None):"""
diff --git a/docs/1.0-dev/_modules/evennia/scripts/models.html b/docs/1.0-dev/_modules/evennia/scripts/models.html
index 3d0d8d7164..5a5916b18f 100644
--- a/docs/1.0-dev/_modules/evennia/scripts/models.html
+++ b/docs/1.0-dev/_modules/evennia/scripts/models.html
@@ -143,7 +143,7 @@
# how often to run Script (secs). -1 means there is no timerdb_interval=models.IntegerField(
- "interval",default=-1,help_text="how often to repeat script, in seconds. -1 means off."
+ "interval",default=-1,help_text="how often to repeat script, in seconds. <= 0 means off.")# start script right away or wait interval seconds firstdb_start_delay=models.BooleanField(
@@ -152,7 +152,7 @@
# how many times this script is to be repeated, if interval!=0.db_repeats=models.IntegerField("number of repeats",default=0,help_text="0 means off.")# defines if this script should survive a reboot or not
- db_persistent=models.BooleanField("survive server reboot",default=False)
+ db_persistent=models.BooleanField("survive server reboot",default=True)# defines if this script has already been started in this sessiondb_is_active=models.BooleanField("script active",default=False)
diff --git a/docs/1.0-dev/_modules/evennia/scripts/scripthandler.html b/docs/1.0-dev/_modules/evennia/scripts/scripthandler.html
index 53ccb67e33..fcb57f84f1 100644
--- a/docs/1.0-dev/_modules/evennia/scripts/scripthandler.html
+++ b/docs/1.0-dev/_modules/evennia/scripts/scripthandler.html
@@ -150,7 +150,8 @@
scripts=ScriptDB.objects.get_all_scripts_on_obj(self.obj,key=key)num=0forscriptinscripts:
- num+=script.start()
+ script.start()
+ num+=1returnnum
# alias to delete
@@ -196,22 +198,7 @@
Get all scripts stored in this handler. """
- returnScriptDB.objects.get_all_scripts_on_obj(self.obj)
-
-
[docs]defvalidate(self,init_mode=False):
- """
- Runs a validation on this object's scripts only. This should
- be called regularly to crank the wheels.
-
- Args:
- init_mode (str, optional): - This is used during server
- upstart and can have three values:
- - `False` (no init mode). Called during run.
- - `"reset"` - server reboot. Kill non-persistent scripts
- - `"reload"` - server reload. Keep non-persistent scripts.
-
- """
- ScriptDB.objects.validate(obj=self.obj,init_mode=init_mode)
+ returnScriptDB.objects.get_all_scripts_on_obj(self.obj)
diff --git a/docs/1.0-dev/_modules/evennia/scripts/scripts.html b/docs/1.0-dev/_modules/evennia/scripts/scripts.html
index 1cdcde8f74..0b75dfc63a 100644
--- a/docs/1.0-dev/_modules/evennia/scripts/scripts.html
+++ b/docs/1.0-dev/_modules/evennia/scripts/scripts.html
@@ -49,7 +49,6 @@
fromtwisted.internet.deferimportDeferred,maybeDeferredfromtwisted.internet.taskimportLoopingCall
-fromdjango.core.exceptionsimportObjectDoesNotExistfromdjango.utils.translationimportgettextas_fromevennia.typeclasses.modelsimportTypeclassBasefromevennia.scripts.modelsimportScriptDB
@@ -59,21 +58,11 @@
__all__=["DefaultScript","DoNothing","Store"]
-FLUSHING_INSTANCES=False# whether we're in the process of flushing scripts from the cache
-SCRIPT_FLUSH_TIMERS={}# stores timers for scripts that are currently being flushed
-
-
-defrestart_scripts_after_flush():
- """After instances are flushed, validate scripts so they're not dead for a long period of time"""
- globalFLUSHING_INSTANCES
- ScriptDB.objects.validate()
- FLUSHING_INSTANCES=False
-
-
classExtendedLoopingCall(LoopingCall):"""
- LoopingCall that can start at a delay different
- than `self.interval`.
+ Custom child of LoopingCall that can start at a delay different than
+ `self.interval` and self.count=0. This allows it to support pausing
+ by resuming at a later period. """
@@ -91,10 +80,10 @@
interval (int): Repeat interval in seconds. now (bool, optional): Whether to start immediately or after `start_delay` seconds.
- start_delay (int): The number of seconds before starting.
- If None, wait interval seconds. Only valid if `now` is `False`.
- It is used as a way to start with a variable start time
- after a pause.
+ start_delay (int, optional): This only applies is `now=False`. It gives
+ number of seconds to wait before starting. If `None`, use
+ `interval` as this value instead. Internally, this is used as a
+ way to start with a variable start time after a pause. count_start (int): Number of repeats to start at. The count goes up every time the system repeats. This is used to implement something repeating `N` number of times etc.
@@ -173,7 +162,7 @@
of start_delay into account. Returns:
- next (int or None): The time in seconds until the next call. This
+ int or None: The time in seconds until the next call. This takes `start_delay` into account. Returns `None` if the task is not running.
@@ -181,7 +170,7 @@
ifself.runningandself.interval>0:total_runtime=self.clock.seconds()-self.starttimeinterval=self.start_delayorself.interval
- returninterval-(total_runtime%self.interval)
+ returnmax(0,interval-(total_runtime%self.interval))classScriptBase(ScriptDB,metaclass=TypeclassBase):
@@ -189,6 +178,8 @@
Base class for scripts. Don't inherit from this, inherit from the class `DefaultScript` below instead.
+ This handles the timer-component of the Script.
+
"""objects=ScriptManager()
@@ -199,36 +190,176 @@
def__repr__(self):returnstr(self)
- def_start_task(self):
+ defat_idmapper_flush(self):"""
- Start task runner.
+ If we're flushing this object, make sure the LoopingCall is gone too.
+ """
+ ret=super().at_idmapper_flush()
+ ifretandself.ndb._task:
+ self.ndb._pause_task(auto_pause=True)
+ # TODO - restart anew ?
+ returnret
+
+ def_start_task(self,interval=None,start_delay=None,repeats=None,force_restart=False,
+ auto_unpause=False,**kwargs):
+ """
+ Start/Unpause task runner, optionally with new values. If given, this will
+ update the Script's fields.
+
+ Keyword Args:
+ interval (int): How often to tick the task, in seconds. If this is <= 0,
+ no task will start and properties will not be updated on the Script.
+ start_delay (int): If the start should be delayed.
+ repeats (int): How many repeats. 0 for infinite repeats.
+ force_restart (bool): If set, always create a new task running even if an
+ old one already was running. Otherwise this will only happen if
+ new script properties were passed.
+ auto_unpause (bool): This is an automatic unpaused (used e.g by Evennia after
+ a reload) and should not un-pause manually paused Script timers.
+ Note:
+ If setting the `start-delay` of a *paused* Script, the Script will
+ restart exactly after that new start-delay, ignoring the time it
+ was paused at. If only changing the `interval`, the Script will
+ come out of pause comparing the time it spent in the *old* interval
+ with the *new* interval in order to determine when next to fire.
+
+ Examples:
+ - Script previously had an interval of 10s and was paused 5s into that interval.
+ Script is now restarted with a 20s interval. It will next fire after 15s.
+ - Same Script is restarted with a 3s interval. It will fire immediately. """
+ ifself.pkisNone:
+ # script object already deleted from db - don't start a new timer
+ raiseScriptDB.DoesNotExist
+
+ # handle setting/updating fields
+ update_fields=[]
+ old_interval=self.db_interval
+ ifintervalisnotNone:
+ self.db_interval=interval
+ update_fields.append("db_interval")
+ ifstart_delayisnotNone:
+ self.db_start_delay=start_delay
+ update_fields.append("db_start_delay")
+ ifrepeatsisnotNone:
+ self.db_repeats=repeats
+ update_fields.append("db_repeats")
+
+ # validate interval
+ ifself.db_intervalandself.db_interval>0:
+ ifnotself.is_active:
+ self.db_is_active=True
+ update_fields.append("db_is_active")
+ else:
+ # no point in starting a task with no interval.
+ return
+
+ restart=bool(update_fields)orforce_restart
+ self.save(update_fields=update_fields)
+
+ ifself.ndb._taskandself.ndb._task.running:
+ ifrestart:
+ # a change needed/forced; stop/remove old task
+ self._stop_task()
+ else:
+ # task alreaady running and no changes needed
+ return
+
ifnotself.ndb._task:
+ # we should have a fresh task after this pointself.ndb._task=ExtendedLoopingCall(self._step_task)
- ifself.db._paused_time:
- # the script was paused; restarting
- callcount=self.db._paused_callcountor0
- self.ndb._task.start(
- self.db_interval,now=False,start_delay=self.db._paused_time,count_start=callcount
- )
- delself.db._paused_time
- delself.db._paused_repeats
+ self._unpause_task(interval=interval,start_delay=start_delay,
+ auto_unpause=auto_unpause,
+ old_interval=old_interval)
- elifnotself.ndb._task.running:
- # starting script anew
+ ifnotself.ndb._task.running:
+ # if not unpausing started it, start script anew with the new valuesself.ndb._task.start(self.db_interval,now=notself.db_start_delay)
- def_stop_task(self):
+ self.at_start(**kwargs)
+
+ def_pause_task(self,auto_pause=False,**kwargs):"""
- Stop task runner
+ Pause task where it is, saving the current status.
+
+ Args:
+ auto_pause (str):
+
+ """
+ ifnotself.db._paused_time:
+ # only allow pause if not already paused
+ task=self.ndb._task
+ iftask:
+ self.db._paused_time=task.next_call_time()
+ self.db._paused_callcount=task.callcount
+ self.db._manually_paused=notauto_pause
+ iftask.running:
+ task.stop()
+ self.ndb._task=None
+
+ self.at_pause(auto_pause=auto_pause,**kwargs)
+
+ def_unpause_task(self,interval=None,start_delay=None,auto_unpause=False,
+ old_interval=0,**kwargs):
+ """
+ Unpause task from paused status. This is used for auto-paused tasks, such
+ as tasks paused on a server reload.
+
+ Args:
+ interval (int): How often to tick the task, in seconds.
+ start_delay (int): If the start should be delayed.
+ auto_unpause (bool): If set, this will only unpause scripts that were unpaused
+ automatically (useful during a system reload/shutdown).
+ old_interval (int): The old Script interval (or current one if nothing changed). Used
+ to recalculate the unpause startup interval.
+
+ """
+ paused_time=self.db._paused_time
+ ifpaused_time:
+ ifauto_unpauseandself.db._manually_paused:
+ # this was manually paused.
+ return
+
+ # task was paused. This will use the new values as needed.
+ callcount=self.db._paused_callcountor0
+ ifstart_delayisNoneandintervalisnotNone:
+ # adjust start-delay based on how far we were into previous interval
+ start_delay=max(0,interval-(old_interval-paused_time))
+ else:
+ start_delay=paused_time
+
+ ifnotself.ndb._task:
+ self.ndb._task=ExtendedLoopingCall(self._step_task)
+
+ self.ndb._task.start(
+ self.db_interval,now=False,start_delay=start_delay,count_start=callcount
+ )
+ delself.db._paused_time
+ delself.db._paused_callcount
+ delself.db._manually_paused
+
+ self.at_start(**kwargs)
+
+ def_stop_task(self,**kwargs):
+ """
+ Stop task runner and delete the task. """task=self.ndb._taskiftaskandtask.running:task.stop()self.ndb._task=None
+ self.db_is_active=False
+
+ # make sure this is not confused as a paused script
+ delself.db._paused_time
+ delself.db._paused_callcount
+ delself.db._manually_paused
+
+ self.save(update_fields=["db_is_active"])
+ self.at_stop(**kwargs)def_step_errback(self,e):"""
@@ -281,12 +412,7 @@
logger.log_trace()returnNone
- defat_script_creation(self):
- """
- Should be overridden in child.
-
- """
- pass
+ # Access methods / hooksdefat_first_save(self,**kwargs):"""
@@ -348,12 +474,185 @@
forkey,valueincdict["nattributes"]:self.nattributes.add(key,value)
- ifnotcdict.get("autostart"):
- # don't auto-start the script
- return
+ ifcdict.get("autostart"):
+ # autostart the script
+ self._start_task(force_restart=True)
- # auto-start script (default)
- self.start()
+ defdelete(self):
+ """
+ Delete the Script. Makes sure to stop any timer tasks first.
+
+ """
+ self._stop_task()
+ self.at_script_delete()
+ super().delete()
+
+ defat_script_creation(self):
+ """
+ Should be overridden in child.
+
+ """
+ pass
+
+ defat_script_delete(self):
+ """
+ Called when script is deleted, after at_stop.
+
+ """
+ pass
+
+ defis_valid(self):
+ """
+ If returning False, `at_repeat` will not be called and timer will stop
+ updating.
+ """
+ returnTrue
+
+ defat_repeat(self,**kwargs):
+ """
+ Called repeatedly every `interval` seconds, once `.start()` has
+ been called on the Script at least once.
+
+ Args:
+ **kwargs (dict): Arbitrary, optional arguments for users
+ overriding the call (unused by default).
+
+ """
+ pass
+
+ defat_start(self,**kwargs):
+ pass
+
+ defat_pause(self,**kwargs):
+ pass
+
+ defat_stop(self,**kwargs):
+ pass
+
+
+ defstart(self,interval=None,start_delay=None,repeats=None,**kwargs):
+ """
+ Start/Unpause timer component, optionally with new values. If given,
+ this will update the Script's fields. This will start `at_repeat` being
+ called every `interval` seconds.
+
+ Keyword Args:
+ interval (int): How often to fire `at_repeat` in seconds.
+ start_delay (int): If the start of ticking should be delayed.
+ repeats (int): How many repeats. 0 for infinite repeats.
+ **kwargs: Optional (default unused) kwargs passed on into the `at_start` hook.
+
+ Notes:
+ If setting the `start-delay` of a *paused* Script, the Script will
+ restart exactly after that new start-delay, ignoring the time it
+ was paused at. If only changing the `interval`, the Script will
+ come out of pause comparing the time it spent in the *old* interval
+ with the *new* interval in order to determine when next to fire.
+
+ Examples:
+ - Script previously had an interval of 10s and was paused 5s into that interval.
+ Script is now restarted with a 20s interval. It will next fire after 15s.
+ - Same Script is restarted with a 3s interval. It will fire immediately.
+
+ """
+ self._start_task(interval=interval,start_delay=start_delay,repeats=repeats,**kwargs)
+
+ # legacy alias
+ update=start
+
+ defstop(self,**kwargs):
+ """
+ Stop the Script's timer component. This will not delete the Sctipt,
+ just stop the regular firing of `at_repeat`. Running `.start()` will
+ start the timer anew, optionally with new settings..
+
+ Args:
+ **kwargs: Optional (default unused) kwargs passed on into the `at_stop` hook.
+
+ """
+ self._stop_task(**kwargs)
+
+ defpause(self,**kwargs):
+ """
+ Manually the Script's timer component manually.
+
+ Args:
+ **kwargs: Optional (default unused) kwargs passed on into the `at_pause` hook.
+
+ """
+ self._pause_task(manual_pause=True,**kwargs)
+
+ defunpause(self,**kwargs):
+ """
+ Manually unpause a Paused Script.
+
+ Args:
+ **kwargs: Optional (default unused) kwargs passed on into the `at_start` hook.
+
+ """
+ self._unpause_task(**kwargs)
+
+ deftime_until_next_repeat(self):
+ """
+ Get time until the script fires it `at_repeat` hook again.
+
+ Returns:
+ int or None: Time in seconds until the script runs again.
+ If not a timed script, return `None`.
+
+ Notes:
+ This hook is not used in any way by the script's stepping
+ system; it's only here for the user to be able to check in
+ on their scripts and when they will next be run.
+
+ """
+ task=self.ndb._task
+ iftask:
+ try:
+ returnint(round(task.next_call_time()))
+ exceptTypeError:
+ pass
+ returnNone
+
+ defremaining_repeats(self):
+ """
+ Get the number of returning repeats for limited Scripts.
+
+ Returns:
+ int or None: The number of repeats remaining until the Script
+ stops. Returns `None` if it has unlimited repeats.
+
+ """
+ task=self.ndb._task
+ iftask:
+ returnmax(0,self.db_repeats-task.callcount)
+ returnNone
+
+ defreset_callcount(self,value=0):
+ """
+ Reset the count of the number of calls done.
+
+ Args:
+ value (int, optional): The repeat value to reset to. Default
+ is to set it all the way back to 0.
+
+ Notes:
+ This is only useful if repeats != 0.
+
+ """
+ task=self.ndb._task
+ iftask:
+ task.callcount=max(0,int(value))
+
+ defforce_repeat(self):
+ """
+ Fire a premature triggering of the script callback. This
+ will reset the timer and count down repeats as if the script
+ had fired normally.
+ """
+ task=self.ndb._task
+ iftask:
+ task.force_repeat()
[docs]deftime_until_next_repeat(self):
- """
- Get time until the script fires it `at_repeat` hook again.
-
- Returns:
- next (int): Time in seconds until the script runs again.
- If not a timed script, return `None`.
-
- Notes:
- This hook is not used in any way by the script's stepping
- system; it's only here for the user to be able to check in
- on their scripts and when they will next be run.
-
- """
- task=self.ndb._task
- iftask:
- try:
- returnint(round(task.next_call_time()))
- exceptTypeError:
- pass
- returnNone
-
-
[docs]defremaining_repeats(self):
- """
- Get the number of returning repeats for limited Scripts.
-
- Returns:
- remaining (int or `None`): The number of repeats
- remaining until the Script stops. Returns `None`
- if it has unlimited repeats.
-
- """
- task=self.ndb._task
- iftask:
- returnmax(0,self.db_repeats-task.callcount)
- returnNone
-
-
[docs]defat_idmapper_flush(self):
- """If we're flushing this object, make sure the LoopingCall is gone too"""
- ret=super(DefaultScript,self).at_idmapper_flush()
- ifretandself.ndb._task:
- try:
- fromtwisted.internetimportreactor
-
- globalFLUSHING_INSTANCES
- # store the current timers for the _task and stop it to avoid duplicates after cache flush
- paused_time=self.ndb._task.next_call_time()
- callcount=self.ndb._task.callcount
- self._stop_task()
- SCRIPT_FLUSH_TIMERS[self.id]=(paused_time,callcount)
- # here we ensure that the restart call only happens once, not once per script
- ifnotFLUSHING_INSTANCES:
- FLUSHING_INSTANCES=True
- reactor.callLater(2,restart_scripts_after_flush)
- exceptException:
- importtraceback
-
- traceback.print_exc()
- returnret
-
-
[docs]defstart(self,force_restart=False):
- """
- Called every time the script is started (for persistent
- scripts, this is usually once every server start)
-
- Args:
- force_restart (bool, optional): Normally an already
- started script will not be started again. if
- `force_restart=True`, the script will always restart
- the script, regardless of if it has started before.
-
- Returns:
- result (int): 0 or 1 depending on if the script successfully
- started or not. Used in counting.
-
- """
- ifself.is_activeandnotforce_restart:
- # The script is already running, but make sure we have a _task if
- # this is after a cache flush
- ifnotself.ndb._taskandself.db_interval>0:
- self.ndb._task=ExtendedLoopingCall(self._step_task)
- try:
- start_delay,callcount=SCRIPT_FLUSH_TIMERS[self.id]
- delSCRIPT_FLUSH_TIMERS[self.id]
- now=False
- except(KeyError,ValueError,TypeError):
- now=notself.db_start_delay
- start_delay=None
- callcount=0
- self.ndb._task.start(
- self.db_interval,now=now,start_delay=start_delay,count_start=callcount
- )
- return0
-
- obj=self.obj
- ifobj:
- # check so the scripted object is valid and initalized
- try:
- obj.cmdset
- exceptAttributeError:
- # this means the object is not initialized.
- logger.log_trace()
- self.is_active=False
- return0
-
- # try to restart a paused script
- try:
- ifself.unpause(manual_unpause=False):
- return1
- exceptRuntimeError:
- # manually paused.
- return0
-
- # start the script from scratch
- self.is_active=True
- try:
- self.at_start()
- exceptException:
- logger.log_trace()
-
- ifself.db_interval>0:
- self._start_task()
- return1
-
-
[docs]defstop(self,kill=False):
- """
- Called to stop the script from running. This also deletes the
- script.
-
- Args:
- kill (bool, optional): - Stop the script without
- calling any relevant script hooks.
-
- Returns:
- result (int): 0 if the script failed to stop, 1 otherwise.
- Used in counting.
-
- """
- ifnotkill:
- try:
- self.at_stop()
- exceptException:
- logger.log_trace()
- self._stop_task()
- try:
- self.delete()
- exceptAssertionError:
- logger.log_trace()
- return0
- exceptObjectDoesNotExist:
- return0
- return1
-
-
[docs]defpause(self,manual_pause=True):
- """
- This stops a running script and stores its active state.
- It WILL NOT call the `at_stop()` hook.
-
- """
- self.db._manual_pause=manual_pause
- ifnotself.db._paused_time:
- # only allow pause if not already paused
- task=self.ndb._task
- iftask:
- self.db._paused_time=task.next_call_time()
- self.db._paused_callcount=task.callcount
- self._stop_task()
- self.is_active=False
-
-
[docs]defunpause(self,manual_unpause=True):
- """
- Restart a paused script. This WILL call the `at_start()` hook.
-
- Args:
- manual_unpause (bool, optional): This is False if unpause is
- called by the server reload/reset mechanism.
- Returns:
- result (bool): True if unpause was triggered, False otherwise.
-
- Raises:
- RuntimeError: If trying to automatically resart this script
- (usually after a reset/reload), but it was manually paused,
- and so should not the auto-unpaused.
-
- """
- ifnotmanual_unpauseandself.db._manual_pause:
- # if this script was paused manually (by a direct call of pause),
- # it cannot be automatically unpaused (e.g. by a @reload)
- raiseRuntimeError
-
- # Ensure that the script is fully unpaused, so that future calls
- # to unpause do not raise a RuntimeError
- self.db._manual_pause=False
-
- ifself.db._paused_time:
- # only unpause if previously paused
- self.is_active=True
-
- try:
- self.at_start()
- exceptException:
- logger.log_trace()
-
- self._start_task()
- returnTrue
-
-
[docs]defrestart(self,interval=None,repeats=None,start_delay=None):
- """
- Restarts an already existing/running Script from the
- beginning, optionally using different settings. This will
- first call the stop hooks, and then the start hooks again.
- Args:
- interval (int, optional): Allows for changing the interval
- of the Script. Given in seconds. if `None`, will use the already stored interval.
- repeats (int, optional): The number of repeats. If unset, will
- use the previous setting.
- start_delay (bool, optional): If we should wait `interval` seconds
- before starting or not. If `None`, re-use the previous setting.
-
- """
- try:
- self.at_stop()
- exceptException:
- logger.log_trace()
- self._stop_task()
- self.is_active=False
- # remove all pause flags
- delself.db._paused_time
- delself.db._manual_pause
- delself.db._paused_callcount
- # set new flags and start over
- ifintervalisnotNone:
- interval=max(0,interval)
- self.interval=interval
- ifrepeatsisnotNone:
- self.repeats=repeats
- ifstart_delayisnotNone:
- self.start_delay=start_delay
- self.start()
-
-
[docs]defreset_callcount(self,value=0):
- """
- Reset the count of the number of calls done.
-
- Args:
- value (int, optional): The repeat value to reset to. Default
- is to set it all the way back to 0.
-
- Notes:
- This is only useful if repeats != 0.
-
- """
- task=self.ndb._task
- iftask:
- task.callcount=max(0,int(value))
-
-
[docs]defforce_repeat(self):
- """
- Fire a premature triggering of the script callback. This
- will reset the timer and count down repeats as if the script
- had fired normally.
- """
- task=self.ndb._task
- iftask:
- task.force_repeat()
-
[docs]defis_valid(self):"""
- Is called to check if the script is valid to run at this time.
- Should return a boolean. The method is assumed to collect all
- needed information from its related self.obj.
+ Is called to check if the script's timer is valid to run at this time.
+ Should return a boolean. If False, the timer will be stopped. """
- returnnotself._is_deleted
+ returnTrue
[docs]defat_start(self,**kwargs):"""
- Called whenever the script is started, which for persistent
- scripts is at least once every server start. It will also be
- called when starting again after a pause (such as after a
- server reload)
+ Called whenever the script timer is started, which for persistent
+ timed scripts is at least once every server start. It will also be
+ called when starting again after a pause (including after a
+ server reload). Args: **kwargs (dict): Arbitrary, optional arguments for users
@@ -697,18 +729,38 @@
**kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default).
+ """
+ pass
+
+
[docs]defat_pause(self,manual_pause=True,**kwargs):
+ """
+ Called when this script's timer pauses.
+
+ Args:
+ manual_pause (bool): If set, pausing was done by a direct call. The
+ non-manual pause indicates the script was paused as part of
+ the server reload.
+
"""pass
[docs]defat_stop(self,**kwargs):"""
- Called whenever when it's time for this script to stop (either
- because is_valid returned False or it runs out of iterations)
+ Called whenever when it's time for this script's timer to stop (either
+ because is_valid returned False, it ran out of iterations or it was manuallys
+ stopped.
- Args
+ Args: **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default).
+ """
+ pass
+
+
[docs]defat_script_delete(self):
+ """
+ Called when the Script is deleted, after at_stop().
+
"""pass
@@ -725,6 +777,15 @@
""" This hook is called whenever the server is shutting down fully (i.e. not for a restart).
+ """
+ pass
+
+
[docs]defat_server_start(self):
+ """
+ This hook is called after the server has started. It can be used to add
+ post-startup setup for Scripts without a timer component (for which at_start
+ could be used).
+
"""pass
[docs]defcrop(text,width=None,suffix="[...]"):""" Crop text to a certain width, throwing away text from too-long lines.
@@ -204,7 +208,7 @@
returnto_str(text)
[docs]defjustify(text,width=None,align="f",indent=0):""" Fully justify a text so that it fits inside `width`. When using full justification (default) this will be done by padding between
@@ -330,7 +334,7 @@
return"\n".join([indentstring+lineforlineinlines])
[docs]defcolumnize(string,columns=2,spacing=4,align="l",width=None):""" Break a string into a number of columns, using as little vertical space as possible.
@@ -386,7 +390,7 @@
return"\n".join(rows)
[docs]defiter_to_str(initer,endsep="and",addquote=False):""" This pretty-formats an iterable list as string output, adding an optional alternative separator to the second to last entry. If `addquote`
@@ -438,7 +442,7 @@
iter_to_string=iter_to_str
-
[docs]defwildcard_to_regexp(instring):""" Converts a player-supplied string that may have wildcards in it to regular expressions. This is useful for name matching.
@@ -470,7 +474,7 @@
returnregexp_string
[docs]defpypath_to_realpath(python_path,file_ending=".py",pypath_prefixes=None):""" Converts a dotted Python path to an absolute path under the Evennia library directory or under the current game directory.
@@ -738,7 +742,7 @@
returnlist(set(pforpinpathsifos.path.isfile(p)))
[docs]deflatinify(string,default="?",pure_ascii=False):""" Convert a unicode string to "safe" ascii/latin-1 characters. This is used as a last resort when normal encoding does not work.
@@ -876,7 +880,7 @@
return"".join(converted)
[docs]defto_bytes(text,session=None):""" Try to encode the given text to bytes, using encodings from settings or from Session. Will always return a bytes, even if given something that is not str or bytes.
@@ -919,7 +923,7 @@
returntext.encode(default_encoding,errors="replace")
[docs]defto_str(text,session=None):""" Try to decode a bytestream to a python str, using encoding schemas from settings or from Session. Will always return a str(), also if not given a str/bytes.
@@ -958,7 +962,7 @@
returntext.decode(default_encoding,errors="replace")
[docs]defvalidate_email_address(emailaddress):""" Checks if an email address is syntactically correct. Makes use of the django email-validator for consistency.
@@ -981,7 +985,7 @@
returnTrue
[docs]definherits_from(obj,parent):""" Takes an object and tries to determine if it inherits at *any* distance from parent.
@@ -1018,7 +1022,7 @@
returnany(1forobj_pathinobj_pathsifobj_path==parent_path)
[docs]defserver_services():""" Lists all services active on the Server. Observe that since services are launched in memory, this function will only return
@@ -1039,7 +1043,7 @@
returnserver
[docs]defuses_database(name="sqlite3"):""" Checks if the game is currently using a given database. This is a shortcut to having to use the full backend name.
@@ -1058,10 +1062,9 @@
returnengine=="django.db.backends.%s"%name
[docs]defdelay(timedelay,callback,*args,**kwargs):""" Delay the return of a value.
@@ -1092,18 +1095,86 @@
"""global_TASK_HANDLER
- # Do some imports here to avoid circular import and speed things upif_TASK_HANDLERisNone:fromevennia.scripts.taskhandlerimportTASK_HANDLERas_TASK_HANDLER
+
return_TASK_HANDLER.add(timedelay,callback,*args,**kwargs)
+
[docs]defrepeat(interval,callback,persistent=True,idstring="",stop=False,
+ store_key=None,*args,**kwargs):
+ """
+ Start a repeating task using the TickerHandler.
+
+ Args:
+ interval (int): How often to call callback.
+ callback (callable): This will be called with `*args, **kwargs` every
+ `interval` seconds. This must be possible to pickle regardless
+ of if `persistent` is set or not!
+ persistent (bool, optional): If ticker survives a server reload.
+ idstring (str, optional): Separates multiple tickers. This is useful
+ mainly if wanting to set up multiple repeats for the same
+ interval/callback but with different args/kwargs.
+ stop (bool, optional): If set, use the given parameters to _stop_ a running
+ ticker instead of creating a new one.
+ store_key (tuple, optional): This is only used in combination with `stop` and
+ should be the return given from the original `repeat` call. If this
+ is given, all other args except `stop` are ignored.
+ *args, **kwargs: Used as arguments to `callback`.
+
+ Returns:
+ tuple or None: This is the `store_key` - the identifier for the created ticker.
+ Store this and pass into unrepat() in order to to stop this ticker
+ later. It this lost you need to stop the ticker via `TICKER_HANDLER.remove`
+ by supplying all the same arguments directly. No return if `stop=True`
+
+ Raises:
+ KeyError: If trying to stop a ticker that was not found.
+
+ """
+ global_TICKER_HANDLER
+ if_TICKER_HANDLERisNone:
+ fromevennia.scripts.tickerhandlerimportTICKER_HANDLERas_TICKER_HANDLER
+
+ ifstop:
+ # we pass all args, but only store_key matters if given
+ _TICKER_HANDLER.remove(interval=interval,
+ callback=callback,
+ idstring=idstring,
+ persistent=persistent,
+ store_key=store_key)
+ else:
+ return_TICKER_HANDLER.add(interval=interval,
+ callback=callback,
+ idstring=idstring,
+ persistent=persistent)
+
+
[docs]defunrepeat(store_key):
+ """
+ This is used to stop a ticker previously started with `repeat`.
+
+ Args:
+ store_key (tuple): This is the return from `repeat`, used to uniquely
+ identify the ticker to stop.
+
+ Returns:
+ bool: True if a ticker was stopped, False if not (for example because no
+ matching ticker was found or it was already stopped).
+
+ """
+ try:
+ repeat(None,None,stop=True,store_key=store_key)
+ returnTrue
+ exceptKeyError:
+ returnFalse
+
+
_PPOOL=None_PCMD=None_PROC_ERR="A process has ended with a probable error condition: process ended by signal 9."
-
[docs]defrun_async(to_execute,*args,**kwargs):""" Runs a function or executes a code snippet asynchronously.
@@ -1161,7 +1232,7 @@
deferred.addErrback(errback,**errback_kwargs)
[docs]defcheck_evennia_dependencies():""" Checks the versions of Evennia's dependencies including making some checks for runtime libraries.
@@ -1205,7 +1276,7 @@
returnnot_error
[docs]defall_from_module(module):""" Return all global-level variables defined in a module.
@@ -1311,7 +1382,7 @@
returndict((key,val)forkey,valinmembersifnotkey.startswith("_"))
[docs]defcallables_from_module(module):""" Return all global-level callables defined in a module.
@@ -1334,7 +1405,7 @@
returndict((key,val)forkey,valinmembersifnotkey.startswith("_"))
[docs]defvariable_from_module(module,variable=None,default=None):""" Retrieve a variable or list of variables from a module. The variable(s) must be defined globally in the module. If no variable
@@ -1381,7 +1452,7 @@
returnresult
[docs]defstring_from_module(module,variable=None,default=None):""" This is a wrapper for `variable_from_module` that requires return value to be a string to pass. It's primarily used by login screen.
@@ -1410,7 +1481,7 @@
returndefault
[docs]defrandom_string_from_module(module):""" Returns a random global string from a module.
@@ -1423,7 +1494,7 @@
returnrandom.choice(string_from_module(module))
[docs]deffuzzy_import_from_module(path,variable,default=None,defaultpaths=None):""" Import a variable based on a fuzzy path. First the literal `path` will be tried, then all given `defaultpaths` will be
@@ -1455,7 +1526,7 @@
returndefault
[docs]defclass_from_module(path,defaultpaths=None,fallback=None):""" Return a class from a module, given the class' full python path. This is primarily used to convert db_typeclass_path:s to classes.
@@ -1535,7 +1606,7 @@
object_from_module=class_from_module
-
[docs]definit_new_account(account):""" Deprecated. """
@@ -1544,7 +1615,7 @@
logger.log_dep("evennia.utils.utils.init_new_account is DEPRECATED and should not be used.")
[docs]defstring_similarity(string1,string2):""" This implements a "cosine-similarity" algorithm as described for example in *Proceedings of the 22nd International Conference on Computation
@@ -1574,7 +1645,7 @@
return0
[docs]defstring_suggestions(string,vocabulary,cutoff=0.6,maxnum=3):""" Given a `string` and a `vocabulary`, return a match or a list of suggestions based on string similarity.
@@ -1603,7 +1674,7 @@
][:maxnum]
[docs]defstring_partial_matching(alternatives,inp,ret_index=True):""" Partially matches a string based on a list of `alternatives`. Matching is made from the start of each subword in each
@@ -1655,7 +1726,7 @@
return[]
[docs]defpercent(value,minval,maxval,formatting="{:3.1f}%"):""" Get a value in an interval as a percentage of its position in that interval. This also understands negative numbers.
@@ -1769,7 +1840,7 @@
importfunctools# noqa
-
[docs]defformat_grid(elements,width=78,sep=" ",verbatim_elements=None):""" This helper function makes a 'grid' output, where it distributes the given string-elements as evenly as possible to fill out the given width.
@@ -1893,7 +1964,7 @@
returnrows
[docs]defget_evennia_pids():""" Get the currently valid PIDs (Process IDs) of the Portal and Server by trying to access a PID file.
@@ -1926,7 +1997,7 @@
returnNone,None
[docs]defdeepsize(obj,max_depth=4):""" Get not only size of the given object, but also the size of objects referenced by the object, down to `max_depth` distance
@@ -1968,7 +2039,7 @@
_missing=object()
-
[docs]classlazy_property(object):""" Delays loading of property until first access. Credit goes to the Implementation in the werkzeug suite:
@@ -1988,7 +2059,7 @@
"""
-
[docs]defcalledby(callerdepth=1):""" Only to be used for debug purposes. Insert this debug function in another function; it will print which function called it.
@@ -2054,7 +2125,7 @@
return"[called by '%s': %s:%s%s]"%(frame[3],path,frame[2],frame[4])
[docs]defm_len(target):""" Provides length checking for strings with MXP patterns, and falls back to normal len for other objects.
@@ -2075,7 +2146,7 @@
returnlen(target)
[docs]defdisplay_len(target):""" Calculate the 'visible width' of text. This is not necessarily the same as the number of characters in the case of certain asian characters. This will also
@@ -2109,7 +2180,7 @@
# Replace this hook function by changing settings.SEARCH_AT_RESULT.
-
[docs]defat_search_result(matches,caller,query="",quiet=False,**kwargs):""" This is a generic hook for handling all processing of a search result, including error reporting. This is also called by the cmdhandler
@@ -2179,7 +2250,7 @@
returnmatches
[docs]classLimitedSizeOrderedDict(OrderedDict):""" This dictionary subclass is both ordered and limited to a maximum number of elements. Its main use is to hold a cache that can never
@@ -2187,7 +2258,7 @@
"""
-
[docs]defget_game_dir_path():""" This is called by settings_default in order to determine the path of the game directory.
@@ -2254,7 +2325,7 @@
raiseRuntimeError("server/conf/settings.py not found: Must start from inside game dir.")
[docs]definteractive(func):""" Decorator to make a method pausable with `yield(seconds)` and able to ask for user-input with `response=yield(question)`.
diff --git a/docs/1.0-dev/_modules/index.html b/docs/1.0-dev/_modules/index.html
index 2b72b62fb2..580eee8efd 100644
--- a/docs/1.0-dev/_modules/index.html
+++ b/docs/1.0-dev/_modules/index.html
@@ -127,10 +127,8 @@
diff --git a/docs/1.0-dev/_sources/Components/Scripts.md.txt b/docs/1.0-dev/_sources/Components/Scripts.md.txt
index 2526bf017c..f8eb121e45 100644
--- a/docs/1.0-dev/_sources/Components/Scripts.md.txt
+++ b/docs/1.0-dev/_sources/Components/Scripts.md.txt
@@ -1,75 +1,262 @@
# Scripts
+[Script API reference](api:evennia.scripts.scripts)
*Scripts* are the out-of-character siblings to the in-character
-[Objects](./Objects). Scripts are so flexible that the "Script" is a bit limiting
-- we had to pick something to name them after all. Other possible names
-(depending on what you'd use them for) would be `OOBObjects`,
-`StorageContainers` or `TimerObjects`.
+[Objects](./Objects). Scripts are so flexible that the name "Script" is a bit limiting
+in itself - but we had to pick _something_ to name them. Other possible names
+(depending on what you'd use them for) would be `OOBObjects`, `StorageContainers` or `TimerObjects`.
-Scripts can be used for many different things in Evennia:
+If you ever consider creating an [Object](./Objects) with a `None`-location just to store some game data,
+you should really be using a Script instead.
-- They can attach to Objects to influence them in various ways - or exist
- independently of any one in-game entity (so-called *Global Scripts*).
-- They can work as timers and tickers - anything that may change with Time. But
- they can also have no time dependence at all. Note though that if all you want
- is just to have an object method called repeatedly, you should consider using
- the [TickerHandler](./TickerHandler) which is more limited but is specialized on
- just this task.
-- They can describe State changes. 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.
-- They can act as data stores for storing game data persistently in the database
-(thanks to its ability to have [Attributes](./Attributes)).
-- They can be used as OOC stores for sharing data between groups of objects, for
-example for tracking the turns in a turn-based combat system or barter exchange.
+- Scripts are full [Typeclassed](./Typeclasses) entities - they have [Attributes](./Attributes) and
+ can be modified in the same way. But they have _no in-game existence_, so no
+ location or command-execution like [Objects](./Objects) and no connection to a particular
+ player/session like [Accounts](./Accounts). This means they are perfectly suitable for acting
+ as database-storage backends for game _systems_: Storing the current state of the economy,
+ who is involved in the current fight, tracking an ongoing barter and so on. They are great as
+ persistent system handlers.
+- Scripts have an optional _timer component_. This means that you can set up the script
+ to tick the `at_repeat` hook on the Script at a certain interval. The timer can be controlled
+ independently of the rest of the script as needed. This component is optional
+ and complementary to other timing functions in Evennia, like
+ [evennia.utils.delay](api:evennia.utils.utils#evennia.utils.utils.delay) and
+ [evennia.utils.repeat](api:evennia.utils.utils#evennia.utils.utils.repeat).
+- Scripts can _attach_ to Objects and Accounts via e.g. `obj.scripts.add/remove`. In the
+ script you can then access the object/account as `self.obj` or `self.account`. This can be used to
+ dynamically extend other typeclasses but also to use the timer component to affect the parent object
+ in various ways. For historical reasons, a Script _not_ attached to an object is referred to as a
+ _Global_ Script.
+
+```versionchanged:: 1.0
+ In previus Evennia versions, stopping the Script's timer also meant deleting the Script object.
+ Starting with this version, the timer can be start/stopped separately and `.delete()` must be called
+ on the Script explicitly to delete it.
-Scripts are [Typeclassed](./Typeclasses) entities and are manipulated in a similar
-way to how it works for other such Evennia entities:
+```
+
+### In-game command examples
+
+There are two main commands controlling scripts in the default cmdset:
+
+The `addscript` command is used for attaching scripts to existing objects:
+
+ > addscript obj = bodyfunctions.BodyFunctions
+
+The `scripts` command is used to view all scripts and perform operations on them:
+
+ > scripts
+ > scripts/stop bodyfunctions.BodyFunctions
+ > scripts/start #244
+ > scripts/pause #11
+ > scripts/delete #566
+
+```versionchanged:: 1.0
+ The `addscript` command used to be only `script` which was easy to confuse with `scripts`.
+```
+
+### Code examples
+
+Here are some examples of working with Scripts in-code (more details to follow in later
+sections).
+
+Create a new script:
+```python
+new_script = evennia.create_script(key="myscript", typeclass=...)
+```
+
+Create script with timer component:
```python
-# create a new script
-new_script = evennia.create_script(key="myscript", typeclass=...)
+# (note that this will call `timed_script.at_repeat` which is empty by default)
+timed_script = evennia.create_script(key="Timed script",
+ interval=34, # seconds <=0 means off
+ start_delay=True, # wait interval before first call
+ autostart=True) # start timer (else needing .start() )
-# search (this is always a list, also if there is only one match)
-list_of_myscript = evennia.search_script("myscript")
+# manipulate the script's timer
+timed_script.stop()
+timed_script.start()
+timed_script.pause()
+timed_script.unpause()
+```
+Attach script to another object:
+
+```python
+myobj.scripts.add(new_script)
+myobj.scripts.add(evennia.DefaultScript)
+all_scripts_on_obj = myobj.scripts.all()
+```
+
+Search/find scripts in various ways:
+
+```python
+# regular search (this is always a list, also if there is only one match)
+list_of_myscripts = evennia.search_script("myscript")
+
+# search through Evennia's GLOBAL_SCRIPTS container (based on
+# script's key only)
+from evennia import GLOBAL_SCRIPTS
+
+myscript = GLOBAL_SCRIPTS.myscript
+GLOBAL_SCRIPTS.get("Timed script").db.foo = "bar"
+```
+
+Delete the Script (this will also stop its timer):
+
+```python
+new_script.delete()
+timed_script.delete()
```
## Defining new Scripts
A Script is defined as a class and is created in the same way as other
-[typeclassed](./Typeclasses) entities. The class has several properties
-to control the timer-component of the scripts. These are all _optional_ -
-leaving them out will just create a Script with no timer components (useful to act as
-a database store or to hold a persistent game system, for example).
+[typeclassed](./Typeclasses) entities. The parent class is `evennia.DefaultScript`.
-This you can do for example in the module
-`evennia/typeclasses/scripts.py`. Below is an example Script
- Typeclass.
+
+### Simple storage script
+
+In `mygame/typeclasses/scripts.py` is an empty `Script` class already set up. You
+can use this as a base for your own scripts.
```python
+# in mygame/typeclasses/scripts.py
+
from evennia import DefaultScript
-class MyScript(DefaultScript):
+class Script(DefaultScript):
+ # stuff common for all your scripts goes here
+
+class MyScript(Script):
+ def at_script_creation(selfself):
+ """Called once, when script is first created"""
+ self.key = "myscript"
+ self.db.foo = "bar"
+
+```
+
+Once created, this simple Script could act as a global storage:
+
+```python
+evennia.create_script('typeclasses.scripts.MyScript')
+
+# from somewhere else
+
+myscript = evennia.search_script("myscript")
+bar = myscript.db.foo
+myscript.db.something_else = 1000
+
+```
+
+Note that if you give keyword arguments to `create_script` you can override the values
+you set in your `at_script_creation`:
+
+```python
+
+evennia.create_script('typeclasses.scripts.MyScript', key="another name",
+ attributes=[("foo", "bar-alternative")])
+
+
+```
+
+See the [create_script](api:evennia.utils.create#evennia.utils.create.create_script) and
+[search_script](api:evennia.utils.search#evennia.utils.search.search_script) API documentation for more options
+on creating and finding Scripts.
+
+
+### Timed Scripts
+
+There are several properties one can set on the Script to control its timer component.
+
+```python
+# in mygame/typeclasses/scripts.py
+
+class TimerScript(Script):
def at_script_creation(self):
self.key = "myscript"
+ self.desc = "An example script"
self.interval = 60 # 1 min repeat
def at_repeat(self):
# do stuff every minute
+
```
-In `mygame/typeclasses/scripts.py` is the `Script` class which inherits from `DefaultScript`
-already. This is provided as your own base class to do with what you like: You can tweak `Script` if
-you want to change the default behavior and it is usually convenient to inherit from this instead.
-Here's an example:
+This example will call `at_repeat` every minute. The `create_script` function has an `autostart=True` keyword
+set by default - this means the script's timer component will be started automatically. Otherwise
+`.start()` must be called separately.
+
+Supported properties are:
+
+- `key` (str): The name of the script. This makes it easier to search for it later. If it's a script
+ attached to another object one can also get all scripts off that object and get the script that way.
+- `desc` (str): Note - not `.db.desc`! This is a database field on the Script shown in script listings
+ to help identifying what does what.
+- `interval` (int): The amount of time (in seconds) between every 'tick' of the timer. Note that
+ it's generally bad practice to use sub-second timers for anything in a text-game - the player will
+ not be able to appreciate the precision (and if you print it, it will just spam the screen). For
+ calculations you can pretty much always do them on-demand, or at a much slower interval without the
+ player being the wiser.
+- `start_delay` (bool): If timer should start right away or wait `interval` seconds first.
+- `repeats` (int): If >0, the timer will only run this many times before stopping. Otherwise the
+ number of repeats are infinite. If set to 1, the Script mimics a `delay` action.
+- `persistent` (bool): This defaults to `True` and means the timer will survive a server reload/reboot.
+ If not, a reload will have the timer come back in a stopped state. Setting this to `False` will _not_
+ delete the Script object itself (use `.delete()` for this).
+
+The timer component is controlled with methods on the Script class:
+
+- `.at_repeat()` - this method is called every `interval` seconds while the timer is
+ active.
+- `.is_valid()` - this method is called by the timer just before `at_repeat()`. If it returns `False`
+ the timer is immediately stopped.
+- `.start()` - start/update the timer. If keyword arguments are given, they can be used to
+ change `interval`, `start_delay` etc on the fly. This calls the `.at_start()` hook.
+ This is also called after a server reload assuming the timer was not previously stopped.
+- `.update()` - legacy alias for `.start`.
+- `.stop()` - stops and resets the timer. This calls the `.at_stop()` hook.
+- `.pause()` - pauses the timer where it is, storing its current position. This calls
+ the `.at_pause(manual_pause=True)` hook. This is also called on a server reload/reboot,
+ at which time the `manual_pause` will be `False`.
+- `.unpause()` - unpause a previously paused script. This will call the `at_start` hook.
+- `.time_until_next_repeat()` - get the time until next time the timer fires.
+- `.remaining_repeats()` - get the number of repeats remaining, or `None` if repeats are infinite.
+- `.reset_callcount()` - this resets the repeat counter to start over from 0. Only useful if `repeats>0`.
+- `.force_repeat()` - this prematurely forces `at_repeat` to be called right away. Doing so will reset the
+ countdown so that next call will again happen after `interval` seconds.
+
+#### Script timers vs delay/repeat
+
+If the _only_ goal is to get a repeat/delay effect, the
+[evennia.utils.delay](api:evennia.utils.utils#evennia.utils.utils.delay) and
+[evennia.utils.repeat](api:evennia.utils.utils#evennia.utils.utils.repeat) functions
+should generally be considered first. A Script is a lot 'heavier' to create/delete on the fly.
+In fact, for making a single delayed call (`script.repeats==1`), the `utils.delay` call is
+probably always the better choice.
+
+For repeating tasks, the `utils.repeat` is optimized for quick repeating of a large number of objects. It
+uses the TickerHandler under the hood. Its subscription-based model makes it very efficient to
+start/stop the repeating action for an object. The side effect is however that all objects set to tick
+at a given interval will _all do so at the same time_. This may or may not look strange in-game depending
+on the situation. By contrast the Script uses its own ticker that will operate independently from the
+tickers of all other Scripts.
+
+It's also worth noting that once the script object has _already been created_,
+starting/stopping/pausing/unpausing the timer has very little overhead. The pause/unpause and update
+methods of the script also offers a bit more fine-control than using `utils.delays/repeat`.
+
+### Script attached to another object
+
+Scripts can be attached to an [Account](./Accounts) or (more commonly) an [Object](./Objects).
+If so, the 'parent object' will be available to the script as either `.obj` or `.account`.
+
```python
- # for example in mygame/typeclasses/scripts.py
+ # mygame/typeclasses/scripts.py
# Script class is defined at the top of this module
import random
@@ -84,7 +271,6 @@ Here's an example:
self.key = "weather_script"
self.desc = "Gives random weather messages."
self.interval = 60 * 5 # every 5 minutes
- self.persistent = True # will survive reload
def at_repeat(self):
"called every self.interval seconds."
@@ -100,14 +286,10 @@ Here's an example:
self.obj.msg_contents(weather)
```
-If we put this script on a room, it will randomly report some weather
+If attached to a room, this Script will randomly report some weather
to everyone in the room every 5 minutes.
-To activate it, just add it to the script handler (`scripts`) on an
-[Room](./Objects). That object becomes `self.obj` in the example above. Here we
-put it on a room called `myroom`:
-
-```
+```python
myroom.scripts.add(scripts.Weather)
```
@@ -115,204 +297,122 @@ put it on a room called `myroom`:
> Therefore we don't need to give the full path (`typeclasses.scripts.Weather`
> but only `scripts.Weather` above.
-You can also create scripts using the `evennia.create_script` function:
+You can also attach the script as part of creating it:
```python
- from evennia import create_script
create_script('typeclasses.weather.Weather', obj=myroom)
```
-Note that if you were to give a keyword argument to `create_script`, that would
-override the default value in your Typeclass. So for example, here is an instance
-of the weather script that runs every 10 minutes instead (and also not survive
-a server reload):
-
-```python
- create_script('typeclasses.weather.Weather', obj=myroom,
- persistent=False, interval=10*60)
-```
-
-From in-game you can use the `@script` command to launch the Script on things:
-
-```
- @script here = typeclasses.scripts.Weather
-```
-
-You can conveniently view and kill running Scripts by using the `@scripts`
-command in-game.
-
-## Properties and functions defined on Scripts
+## Other Script methods
A Script has all the properties of a typeclassed object, such as `db` and `ndb`(see
[Typeclasses](./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 arguments to `evennia.create_script`.
-- `desc` - an optional description of the script's function. Seen in script listings.
-- `interval` - how often the script should run. If `interval == 0` (default), this script has no
-timing component, will not repeat and will exist forever. This is useful for Scripts used for
-storage or acting as bases for various non-time dependent game systems.
-- `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. Note that *each* firing of the script (including the first one)
-counts towards this value. So a `Script` with `start_delay=False` and `repeats=1` will start,
-immediately fire and shut down right away.
-- `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](./Objects) 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)` or give
-`myobj` as an argument to the `utils.create.create_script` function, 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 `src/scripts/scripts.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 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
-`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.
+- `at_script_creation()` - this is only called once - when the script is first created.
- `at_server_reload()` - this is called whenever the server is warm-rebooted (e.g. with the
-`@reload` command). It's a good place to save non-persistent data you might want to survive a
+`reload` command). 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.
+- `at_server_start()` - this is called when the server comes back (from reload/shutdown/reboot). It
+ can be usuful for initializations and caching of non-persistent data when starting up a script's
+ functionality.
+- `at_repeat()`
+- `at_start()`
+- `at_pause()`
+- `at_stop()`
+- `delete()` - same as for other typeclassed entities, this will delete the Script. Of note is that
+ it will also stop the timer (if it runs), leading to the `at_stop` hook being called.
-Running methods (usually called automatically by the engine, but possible to also invoke manually)
+In addition, Scripts support [Attributes](./Attributes), [Tags](./Tags) and [Locks](./Locks) etc like other
+Typeclassed entities.
-- `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 unpauses 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 `None` if `interval==0`.
-- `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_stop` hook will be called and the
-Script brought to a halt, then the `at_start` hook will be called as the Script starts up with your
-(possibly changed) settings. Any keyword left at `None` means to not change the original setting.
+See also the methods involved in controlling a [Timed Script](#Timed_Scripts) above.
+## The GLOBAL_SCRIPTS container
-## Global Scripts
+A Script not attached to another entity is commonly referred to as a _Global_ script since it't available
+to access from anywhere. This means they need to be searched for in order to be used.
-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:
+Evennia supplies a convenient "container" `evennia.GLOBAL_SCRIPTS` to help organize your global
+scripts. All you need is the Script's `key`.
-```python
- # adding a global script
- from evennia import create_script
- create_script("typeclasses.globals.MyGlobalEconomy",
- key="economy", persistent=True, obj=None)
-```
-Henceforth you can then get it back by searching for its key or other identifier with
-`evennia.search_script`. In-game, the `scripts` command will show all scripts.
-
-Evennia supplies a convenient "container" called `GLOBAL_SCRIPTS` that can offer an easy
-way to access global scripts. If you know the name (key) of the script you can get it like so:
```python
from evennia import GLOBAL_SCRIPTS
+# access as a property on the container, named the same as the key
my_script = GLOBAL_SCRIPTS.my_script
# needed if there are spaces in name or name determined on the fly
another_script = GLOBAL_SCRIPTS.get("another script")
-# get all global scripts (this returns a Queryset)
+# get all global scripts (this returns a Django Queryset)
all_scripts = GLOBAL_SCRIPTS.all()
# you can operate directly on the script
GLOBAL_SCRIPTS.weather.db.current_weather = "Cloudy"
```
-> Note that global scripts appear as properties on `GLOBAL_SCRIPTS` based on their `key`.
-If you were to create two global scripts with the same `key` (even with different typeclasses),
-the `GLOBAL_SCRIPTS` container will only return one of them (which one depends on order in
-the database). Best is to organize your scripts so that this does not happen. Otherwise, use
-`evennia.search_script` to get exactly the script you want.
+```warning::
+ Note that global scripts appear as properties on `GLOBAL_SCRIPTS` based on their `key`.
+ If you were to create two global scripts with the same `key` (even with different typeclasses),
+ the `GLOBAL_SCRIPTS` container will only return one of them (which one depends on order in
+ the database). Best is to organize your scripts so that this does not happen. Otherwise, use
+ `evennia.search_scripts` to get exactly the script you want.
+```
-There are two ways to make a script appear as a property on `GLOBAL_SCRIPTS`. The first is
-to manually create a new global script with `create_script` as mentioned above. Often you want this
-to happen automatically when the server starts though. For this you can use the setting
-`GLOBAL_SCRIPTS`:
+There are two ways to make a script appear as a property on `GLOBAL_SCRIPTS`:
+
+1. Manually create a new global script with a `key` using `create_script`.
+2. Define the script's properties in the `GLOBAL_SCRIPTS` settings variable. This tells Evennia
+ that it should check if a script with that `key` exists and if not, create it for you.
+ This is very useful for scripts that must always exist and/or should be auto-created with your server.
+
+Here's how to tell Evennia to manage the script in settings:
```python
+# in mygame/server/conf/settings.py
+
GLOBAL_SCRIPTS = {
"my_script": {
"typeclass": "scripts.Weather",
"repeats": -1,
"interval": 50,
"desc": "Weather script"
- "persistent": True
},
- "storagescript": {
- "typeclass": "scripts.Storage",
- "persistent": True
- }
+ "storagescript": {}
}
```
-Here the key (`myscript` and `storagescript` above) is required, all other fields are optional. If
-`typeclass` is not given, a script of type `settings.BASE_SCRIPT_TYPECLASS` is assumed. The keys
-related to timing and intervals are only needed if the script is timed.
+Above we add two scripts with keys `myscript` and `storagescript`respectively. The following dict
+can be empty - the `settings.BASE_SCRIPT_TYPECLASS` will then be used. Under the hood, the provided
+dict (along with the `key`) will be passed into `create_script` automatically, so
+all the [same keyword arguments as for create_script](api:evennia.utils.create.create_script) are
+supported here.
-Evennia will use the information in `settings.GLOBAL_SCRIPTS` to automatically create and start
-these
-scripts when the server starts (unless they already exist, based on their `key`). You need to reload
-the server before the setting is read and new scripts become available. You can then find the `key`
-you gave as properties on `evennia.GLOBAL_SCRIPTS`
-(such as `evennia.GLOBAL_SCRIPTS.storagescript`).
-
-> Note: Make sure that your Script typeclass does not have any critical errors. If so, you'll see
-errors in your log and your Script will temporarily fall back to being a `DefaultScript` type.
+```warning::
+ Before setting up Evennia to manage your script like this, make sure that your Script typeclass
+ does not have any critical errors (test it separately). If there are, you'll see errors in your log
+ and your Script will temporarily fall back to being a `DefaultScript` type.
+```
Moreover, a script defined this way is *guaranteed* to exist when you try to access it:
+
```python
from evennia import GLOBAL_SCRIPTS
-# first stop the script
-GLOBAL_SCRIPTS.storagescript.stop()
+# Delete the script
+GLOBAL_SCRIPTS.storagescript.delete()
# running the `scripts` command now will show no storagescript
-# but below now it's recreated again!
+# but below it's automatically recreated again!
storage = GLOBAL_SCRIPTS.storagescript
```
-That is, if the script is deleted, next time you get it from `GLOBAL_SCRIPTS`, it will use the
-information
-in settings to recreate it for you.
-> Note that if your goal with the Script is to store persistent data, you should set it as
-`persistent=True`, either in `settings.GLOBAL_SCRIPTS` or in the Scripts typeclass. Otherwise any
-data you wanted to store on it will be gone (since a new script of the same name is restarted
-instead).
+That is, if the script is deleted, next time you get it from `GLOBAL_SCRIPTS`, Evennia will use the
+information in settings to recreate it for you on the fly.
-## Dealing with Errors
-Errors inside an timed, executing script can sometimes be rather terse or point to
+## Hints: Dealing with Script Errors
+
+Errors inside a timed, 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
@@ -322,45 +422,13 @@ traceback occurred in your script.
from evennia.utils import logger
-class Weather(DefaultScript):
+class Weather(Script):
# [...]
def at_repeat(self):
try:
- # [...] code as above
+ # [...]
except Exception:
- # logs the error
- logger.log_trace()
-
-```
-
-## Example of a timed script
-
-In-game you can try out scripts using the `@script` command. In the
-`evennia/contrib/tutorial_examples/bodyfunctions.py` is a little example script
-that makes you do little 'sounds' at random intervals. Try the following to apply an
-example time-based script to your character.
-
- > @script self = bodyfunctions.BodyFunctions
-
-> Note: Since `evennia/contrib/tutorial_examples` is in the default setting
-> `TYPECLASS_PATHS`, we only need to specify the final part of the path,
-> that is, `bodyfunctions.BodyFunctions`.
-
-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')
-
-Here's how you stop it on yourself.
-
- > @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).
-
-
-For another example of a Script in use, check out the [Turn Based Combat System
-tutorial](https://github.com/evennia/evennia/wiki/Turn%20based%20Combat%20System).
\ No newline at end of file
+ logger.log_trace()
\ No newline at end of file
diff --git a/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.cmdset_red_button.rst.txt b/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.cmdset_red_button.rst.txt
deleted file mode 100644
index 4686f928d2..0000000000
--- a/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.cmdset_red_button.rst.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-evennia.contrib.tutorial\_examples.cmdset\_red\_button
-=============================================================
-
-.. automodule:: evennia.contrib.tutorial_examples.cmdset_red_button
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.red_button_scripts.rst.txt b/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.red_button_scripts.rst.txt
deleted file mode 100644
index 157747b84e..0000000000
--- a/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.red_button_scripts.rst.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-evennia.contrib.tutorial\_examples.red\_button\_scripts
-==============================================================
-
-.. automodule:: evennia.contrib.tutorial_examples.red_button_scripts
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.rst.txt b/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.rst.txt
index 96abd21912..b6935063e2 100644
--- a/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.rst.txt
+++ b/docs/1.0-dev/_sources/api/evennia.contrib.tutorial_examples.rst.txt
@@ -12,9 +12,7 @@ evennia.contrib.tutorial\_examples
:maxdepth: 6
evennia.contrib.tutorial_examples.bodyfunctions
- evennia.contrib.tutorial_examples.cmdset_red_button
evennia.contrib.tutorial_examples.example_batch_code
evennia.contrib.tutorial_examples.mirror
evennia.contrib.tutorial_examples.red_button
- evennia.contrib.tutorial_examples.red_button_scripts
evennia.contrib.tutorial_examples.tests
diff --git a/docs/1.0-dev/api/evennia-api.html b/docs/1.0-dev/api/evennia-api.html
index 79356a54d8..2323ce23e1 100644
--- a/docs/1.0-dev/api/evennia-api.html
+++ b/docs/1.0-dev/api/evennia-api.html
@@ -191,11 +191,12 @@
diff --git a/docs/1.0-dev/api/evennia.commands.cmdsethandler.html b/docs/1.0-dev/api/evennia.commands.cmdsethandler.html
index bc4fbf2e1c..87d3c3a215 100644
--- a/docs/1.0-dev/api/evennia.commands.cmdsethandler.html
+++ b/docs/1.0-dev/api/evennia.commands.cmdsethandler.html
@@ -176,7 +176,7 @@ to the central cmdhandler.get_and_merge_cmdsets()!
Add a cmdset to the handler, on top of the old ones, unless it
is set as the default one (it will then end up at the bottom of the stack)
@@ -185,7 +185,9 @@ is set as the default one (it will then end up at the bottom of the stack)
cmdset (CmdSet or str) – Can be a cmdset object or the python path
to such an object.
emit_to_obj (Object, optional) – An object to receive error messages.
-
permanent (bool, optional) – This cmdset will remain across a server reboot.
+
persistent (bool, optional) – Let cmdset remain across server reload.
+
permanent (bool, optional) – DEPRECATED. This has the same use as
+persistent.
default_cmdset (Cmdset, optional) – Insert this to replace the
default cmdset position (there is only one such position,
always at the bottom of the stack).
diff --git a/docs/1.0-dev/api/evennia.commands.default.account.html b/docs/1.0-dev/api/evennia.commands.default.account.html
index d740d24319..79fc4d4d79 100644
--- a/docs/1.0-dev/api/evennia.commands.default.account.html
+++ b/docs/1.0-dev/api/evennia.commands.default.account.html
@@ -70,7 +70,7 @@ method. Otherwise all text will be returned to all connected sessions.
@@ -101,7 +101,7 @@ method. Otherwise all text will be returned to all connected sessions.
-search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look while out-of-character\n\n Usage:\n look\n\n Look in the ooc state.\n '}¶
+search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look while out-of-character\n\n Usage:\n look\n\n Look in the ooc state.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.commands.default.building.html b/docs/1.0-dev/api/evennia.commands.default.building.html
index 3e7d66b78a..0c8d439e25 100644
--- a/docs/1.0-dev/api/evennia.commands.default.building.html
+++ b/docs/1.0-dev/api/evennia.commands.default.building.html
@@ -529,7 +529,7 @@ You can specify the /force switch to bypass this confirmation.
@@ -570,7 +570,7 @@ You can specify the /force switch to bypass this confirmation.
-search_index_entry = {'aliases': 'del delete', 'category': 'building', 'key': 'destroy', 'tags': '', 'text': '\n permanently delete objects\n\n Usage:\n destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]\n\n Switches:\n override - The destroy command will usually avoid accidentally\n destroying account objects. This switch overrides this safety.\n force - destroy without confirmation.\n Examples:\n destroy house, roof, door, 44-78\n destroy 5-10, flower, 45\n destroy/force north\n\n Destroys one or many objects. If dbrefs are used, a range to delete can be\n given, e.g. 4-10. Also the end points will be deleted. This command\n displays a confirmation before destroying, to make sure of your choice.\n You can specify the /force switch to bypass this confirmation.\n '}¶
+search_index_entry = {'aliases': 'delete del', 'category': 'building', 'key': 'destroy', 'tags': '', 'text': '\n permanently delete objects\n\n Usage:\n destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]\n\n Switches:\n override - The destroy command will usually avoid accidentally\n destroying account objects. This switch overrides this safety.\n force - destroy without confirmation.\n Examples:\n destroy house, roof, door, 44-78\n destroy 5-10, flower, 45\n destroy/force north\n\n Destroys one or many objects. If dbrefs are used, a range to delete can be\n given, e.g. 4-10. Also the end points will be deleted. This command\n displays a confirmation before destroying, to make sure of your choice.\n You can specify the /force switch to bypass this confirmation.\n '}¶
-search_index_entry = {'aliases': 'type update swap parent', 'category': 'building', 'key': 'typeclass', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object.\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}¶
+search_index_entry = {'aliases': 'parent update swap type', 'category': 'building', 'key': 'typeclass', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object.\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}¶
@@ -1452,7 +1452,7 @@ If object is not specified, the current location is examined.
@@ -1549,7 +1549,7 @@ non-persistent data stored on object
-search_index_entry = {'aliases': 'exam ex', 'category': 'building', 'key': 'examine', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}¶
+search_index_entry = {'aliases': 'ex exam', 'category': 'building', 'key': 'examine', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}¶
@@ -1703,7 +1703,7 @@ teleported to the target location.
-search_index_entry = {'aliases': 'addscript', 'category': 'building', 'key': 'script', 'tags': '', 'text': '\n attach a script to an object\n\n Usage:\n script[/switch] <obj> [= script_path or <scriptkey>]\n\n Switches:\n start - start all non-running scripts on object, or a given script only\n stop - stop all scripts on objects, or a given script only\n\n If no script path/key is given, lists all scripts active on the given\n object.\n Script path can be given from the base location for scripts as given in\n settings. If adding a new script, it will be started automatically\n (no /start switch is needed). Using the /start or /stop switches on an\n object without specifying a script key/path will start/stop ALL scripts on\n the object.\n '}¶
+search_index_entry = {'aliases': 'attachscript', 'category': 'building', 'key': 'addscript', 'tags': '', 'text': '\n attach a script to an object\n\n Usage:\n addscript[/switch] <obj> [= script_path or <scriptkey>]\n\n Switches:\n start - start all non-running scripts on object, or a given script only\n stop - stop all scripts on objects, or a given script only\n\n If no script path/key is given, lists all scripts active on the given\n object.\n Script path can be given from the base location for scripts as given in\n settings. If adding a new script, it will be started automatically\n (no /start switch is needed). Using the /start or /stop switches on an\n object without specifying a script key/path will start/stop ALL scripts on\n the object.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.commands.default.comms.html b/docs/1.0-dev/api/evennia.commands.default.comms.html
index 392a542161..1e8fe5bd19 100644
--- a/docs/1.0-dev/api/evennia.commands.default.comms.html
+++ b/docs/1.0-dev/api/evennia.commands.default.comms.html
@@ -64,7 +64,7 @@ aliases to an already joined channel.
@@ -95,7 +95,7 @@ aliases to an already joined channel.
-search_index_entry = {'aliases': 'chanalias aliaschan', 'category': 'comms', 'key': 'addcom', 'tags': '', 'text': '\n add a channel alias and/or subscribe to a channel\n\n Usage:\n addcom [alias=] <channel>\n\n Joins a given channel. If alias is given, this will allow you to\n refer to the channel by this alias rather than the full channel\n name. Subsequent calls of this command can be used to add multiple\n aliases to an already joined channel.\n '}¶
+search_index_entry = {'aliases': 'aliaschan chanalias', 'category': 'comms', 'key': 'addcom', 'tags': '', 'text': '\n add a channel alias and/or subscribe to a channel\n\n Usage:\n addcom [alias=] <channel>\n\n Joins a given channel. If alias is given, this will allow you to\n refer to the channel by this alias rather than the full channel\n name. Subsequent calls of this command can be used to add multiple\n aliases to an already joined channel.\n '}¶
@@ -234,7 +234,7 @@ Use addcom/delcom to join and leave channels
@@ -265,7 +265,7 @@ Use addcom/delcom to join and leave channels
-search_index_entry = {'aliases': 'clist comlist all channels channellist chanlist', 'category': 'comms', 'key': 'channels', 'tags': '', 'text': "\n list all channels available to you\n\n Usage:\n channels\n clist\n comlist\n\n Lists all channels available to you, whether you listen to them or not.\n Use 'comlist' to only view your current channel subscriptions.\n Use addcom/delcom to join and leave channels\n "}¶
+search_index_entry = {'aliases': 'chanlist clist all channels comlist channellist', 'category': 'comms', 'key': 'channels', 'tags': '', 'text': "\n list all channels available to you\n\n Usage:\n channels\n clist\n comlist\n\n Lists all channels available to you, whether you listen to them or not.\n Use 'comlist' to only view your current channel subscriptions.\n Use addcom/delcom to join and leave channels\n "}¶
-search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look at location or object\n\n Usage:\n look\n look <obj>\n look *<account>\n\n Observes your location or objects in your vicinity.\n '}¶
+search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look at location or object\n\n Usage:\n look\n look <obj>\n look *<account>\n\n Observes your location or objects in your vicinity.\n '}¶
@@ -205,7 +205,7 @@ for everyone to use, you need build privileges and the alias command.
@@ -237,7 +237,7 @@ for everyone to use, you need build privileges and the alias command.
-search_index_entry = {'aliases': 'nickname nicks', 'category': 'general', 'key': 'nick', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}¶
+search_index_entry = {'aliases': 'nicks nickname', 'category': 'general', 'key': 'nick', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}¶
@@ -561,7 +561,7 @@ placing it in their inventory.
-search_index_entry = {'aliases': '" \'', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n speak as your character\n\n Usage:\n say <message>\n\n Talk to those in your current location.\n '}¶
+search_index_entry = {'aliases': '\' "', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n speak as your character\n\n Usage:\n say <message>\n\n Talk to those in your current location.\n '}¶
@@ -641,7 +641,7 @@ automatically begin with your name.
-search_index_entry = {'aliases': ': emote', 'category': 'general', 'key': 'pose', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}¶
+search_index_entry = {'aliases': 'emote :', 'category': 'general', 'key': 'pose', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}¶
@@ -700,7 +700,7 @@ which permission groups you are a member of.
@@ -731,7 +731,7 @@ which permission groups you are a member of.
-search_index_entry = {'aliases': 'hierarchy groups', 'category': 'general', 'key': 'access', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}¶
+search_index_entry = {'aliases': 'groups hierarchy', 'category': 'general', 'key': 'access', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.commands.default.system.html b/docs/1.0-dev/api/evennia.commands.default.system.html
index e15d2b95b0..163fdba970 100644
--- a/docs/1.0-dev/api/evennia.commands.default.system.html
+++ b/docs/1.0-dev/api/evennia.commands.default.system.html
@@ -291,14 +291,19 @@ should only be accessible by trusted server admins/superusers.|n
class evennia.commands.default.system.CmdScripts(**kwargs)[source]¶
scripts[/switches] [#dbref, key, script.path or <obj>]
+
List and manage all running scripts. Allows for creating new global
+scripts.
+
+
Usage:
script[/switches] [#dbref, key, script.path or <obj>]
-
Switches:
start - start a script (must supply a script path)
-stop - stops an existing script
-kill - kills a script - without running its cleanup hooks
-validate - run a validation on the script(s)
+
Switches:
+
create - create a new global script of given typeclass path. This will
auto-start the script’s timer if it has one.
+
+
+
start - start/unpause an existing script’s timer.
+stop - stops an existing script’s timer
+pause - pause a script’s timer
+delete - deletes script. This will also stop the timer as needed
If no switches are given, this command just views all active
@@ -306,7 +311,8 @@ scripts. The argument can be either an object, at which point it
will be searched for all scripts defined on it, or a script name
or #dbref. For using the /stop switch, a unique script #dbref is
required since whole classes of scripts often have the same name.
-
Use script for managing commands on objects.
+
Use the script build-level command for managing scripts attached to
+objects.
@@ -337,6 +343,11 @@ required since whole classes of scripts often have the same name.
excluded_typeclass_paths = ['evennia.prototypes.prototypes.DbPrototype']¶
@@ -350,7 +361,7 @@ required since whole classes of scripts often have the same name.
-search_index_entry = {'aliases': 'globalscript listscripts', 'category': 'system', 'key': 'scripts', 'tags': '', 'text': '\n list and manage all running scripts\n\n Usage:\n scripts[/switches] [#dbref, key, script.path or <obj>]\n\n Switches:\n start - start a script (must supply a script path)\n stop - stops an existing script\n kill - kills a script - without running its cleanup hooks\n validate - run a validation on the script(s)\n\n If no switches are given, this command just views all active\n scripts. The argument can be either an object, at which point it\n will be searched for all scripts defined on it, or a script name\n or #dbref. For using the /stop switch, a unique script #dbref is\n required since whole classes of scripts often have the same name.\n\n Use script for managing commands on objects.\n '}¶
+search_index_entry = {'aliases': '', 'category': 'system', 'key': 'scripts', 'tags': '', 'text': "\n List and manage all running scripts. Allows for creating new global\n scripts.\n\n Usage:\n script[/switches] [#dbref, key, script.path or <obj>]\n\n Switches:\n create - create a new global script of given typeclass path. This will\n auto-start the script's timer if it has one.\n start - start/unpause an existing script's timer.\n stop - stops an existing script's timer\n pause - pause a script's timer\n delete - deletes script. This will also stop the timer as needed\n\n If no switches are given, this command just views all active\n scripts. The argument can be either an object, at which point it\n will be searched for all scripts defined on it, or a script name\n or #dbref. For using the /stop switch, a unique script #dbref is\n required since whole classes of scripts often have the same name.\n\n Use the `script` build-level command for managing scripts attached to\n objects.\n\n "}¶
-search_index_entry = {'aliases': 'listobjs db listobjects stats', 'category': 'system', 'key': 'objects', 'tags': '', 'text': '\n statistics on objects in the database\n\n Usage:\n objects [<nr>]\n\n Gives statictics on objects in database as well as\n a list of <nr> latest objects in database. If not\n given, <nr> defaults to 10.\n '}¶
+search_index_entry = {'aliases': 'stats listobjects db listobjs', 'category': 'system', 'key': 'objects', 'tags': '', 'text': '\n statistics on objects in the database\n\n Usage:\n objects [<nr>]\n\n Gives statictics on objects in database as well as\n a list of <nr> latest objects in database. If not\n given, <nr> defaults to 10.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.commands.default.tests.html b/docs/1.0-dev/api/evennia.commands.default.tests.html
index 0db17a1d1d..ce41b15eb7 100644
--- a/docs/1.0-dev/api/evennia.commands.default.tests.html
+++ b/docs/1.0-dev/api/evennia.commands.default.tests.html
@@ -512,7 +512,7 @@ output sent to caller.msg in the game
@@ -94,7 +94,7 @@ there is no object yet before the account has logged in)
-search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶
+search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶
@@ -173,7 +173,7 @@ version is a bit more complicated.
@@ -199,7 +199,7 @@ version is a bit more complicated.
-search_index_entry = {'aliases': 'qu q', 'category': 'general', 'key': 'quit', 'tags': '', 'text': '\n quit when in unlogged-in state\n\n Usage:\n quit\n\n We maintain a different version of the quit command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}¶
+search_index_entry = {'aliases': 'q qu', 'category': 'general', 'key': 'quit', 'tags': '', 'text': '\n quit when in unlogged-in state\n\n Usage:\n quit\n\n We maintain a different version of the quit command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}¶
@@ -223,7 +223,7 @@ All it does is display the connect screen.
@@ -249,7 +249,7 @@ All it does is display the connect screen.
-search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
+search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
@@ -272,7 +272,7 @@ for simplicity. It shows a pane of info.
@@ -298,7 +298,7 @@ for simplicity. It shows a pane of info.
-search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n get help when in unconnected-in state\n\n Usage:\n help\n\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}¶
+search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n get help when in unconnected-in state\n\n Usage:\n help\n\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.chargen.html b/docs/1.0-dev/api/evennia.contrib.chargen.html
index dcdb762965..4bf64fbf93 100644
--- a/docs/1.0-dev/api/evennia.contrib.chargen.html
+++ b/docs/1.0-dev/api/evennia.contrib.chargen.html
@@ -77,7 +77,7 @@ at them with this command.
@@ -109,7 +109,7 @@ that is checked by the @ic command directly.
-search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n ooc look\n\n Usage:\n look\n look <character>\n\n This is an OOC version of the look command. Since an Account doesn\'t\n have an in-game existence, there is no concept of location or\n "self".\n\n If any characters are available for you to control, you may look\n at them with this command.\n '}¶
+search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n ooc look\n\n Usage:\n look\n look <character>\n\n This is an OOC version of the look command. Since an Account doesn\'t\n have an in-game existence, there is no concept of location or\n "self".\n\n If any characters are available for you to control, you may look\n at them with this command.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.dice.html b/docs/1.0-dev/api/evennia.contrib.dice.html
index c5f9272a8c..e0c088a700 100644
--- a/docs/1.0-dev/api/evennia.contrib.dice.html
+++ b/docs/1.0-dev/api/evennia.contrib.dice.html
@@ -149,7 +149,7 @@ everyone but the person rolling.
@@ -175,7 +175,7 @@ everyone but the person rolling.
-search_index_entry = {'aliases': 'roll @dice', 'category': 'general', 'key': 'dice', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}¶
+search_index_entry = {'aliases': '@dice roll', 'category': 'general', 'key': 'dice', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.email_login.html b/docs/1.0-dev/api/evennia.contrib.email_login.html
index 57ef4f37b9..783fe88f97 100644
--- a/docs/1.0-dev/api/evennia.contrib.email_login.html
+++ b/docs/1.0-dev/api/evennia.contrib.email_login.html
@@ -74,7 +74,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE.
@@ -104,7 +104,7 @@ there is no object yet before the account has logged in)
-search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}¶
+search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}¶
@@ -181,7 +181,7 @@ version is a bit more complicated.
@@ -207,7 +207,7 @@ version is a bit more complicated.
-search_index_entry = {'aliases': 'qu q', 'category': 'general', 'key': 'quit', 'tags': '', 'text': '\n We maintain a different version of the `quit` command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}¶
+search_index_entry = {'aliases': 'q qu', 'category': 'general', 'key': 'quit', 'tags': '', 'text': '\n We maintain a different version of the `quit` command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}¶
@@ -226,7 +226,7 @@ All it does is display the connect screen.
@@ -252,7 +252,7 @@ All it does is display the connect screen.
-search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
+search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
@@ -270,7 +270,7 @@ for simplicity. It shows a pane of info.
@@ -296,7 +296,7 @@ for simplicity. It shows a pane of info.
-search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}¶
+search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.evscaperoom.commands.html b/docs/1.0-dev/api/evennia.contrib.evscaperoom.commands.html
index 72c475e420..8c6c5cf9b6 100644
--- a/docs/1.0-dev/api/evennia.contrib.evscaperoom.commands.html
+++ b/docs/1.0-dev/api/evennia.contrib.evscaperoom.commands.html
@@ -147,7 +147,7 @@ the operation will be general or on the room.
-search_index_entry = {'aliases': 'abort quit chicken out q', 'category': 'evscaperoom', 'key': 'give up', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}¶
+search_index_entry = {'aliases': 'quit abort q chicken out', 'category': 'evscaperoom', 'key': 'give up', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}¶
-search_index_entry = {'aliases': 'ls l', 'category': 'evscaperoom', 'key': 'look', 'tags': '', 'text': '\n Look at the room, an object or the currently focused object\n\n Usage:\n look [obj]\n\n '}¶
+search_index_entry = {'aliases': 'l ls', 'category': 'evscaperoom', 'key': 'look', 'tags': '', 'text': '\n Look at the room, an object or the currently focused object\n\n Usage:\n look [obj]\n\n '}¶
-search_index_entry = {'aliases': 'unfocus e ex examine', 'category': 'evscaperoom', 'key': 'focus', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}¶
+search_index_entry = {'aliases': 'e examine unfocus ex', 'category': 'evscaperoom', 'key': 'focus', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}¶
-search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look\n\n Usage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects in your vicinity.\n '}¶
+search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look\n\n Usage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects in your vicinity.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.html b/docs/1.0-dev/api/evennia.contrib.html
index cf1c4e2547..74ccfa3111 100644
--- a/docs/1.0-dev/api/evennia.contrib.html
+++ b/docs/1.0-dev/api/evennia.contrib.html
@@ -152,11 +152,12 @@ useful but are deemed too game-specific to go into the core library.
Note that this hook is called every time the server restarts
(including when it’s reloaded). This hook performs the following
diff --git a/docs/1.0-dev/api/evennia.contrib.rpsystem.html b/docs/1.0-dev/api/evennia.contrib.rpsystem.html
index 45e512171c..ab8a2fe89b 100644
--- a/docs/1.0-dev/api/evennia.contrib.rpsystem.html
+++ b/docs/1.0-dev/api/evennia.contrib.rpsystem.html
@@ -636,7 +636,7 @@ a different language.
-search_index_entry = {'aliases': '" \'', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n speak as your character\n\n Usage:\n say <message>\n\n Talk to those in your current location.\n '}¶
+search_index_entry = {'aliases': '\' "', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n speak as your character\n\n Usage:\n say <message>\n\n Talk to those in your current location.\n '}¶
This defines the cmdset for the red_button. Here we have defined
-the commands and the cmdset in the same module, but if you
-have many different commands to merge it is often better
-to define the cmdset separately, picking and choosing from
-among the available commands as to what should be included in the
-cmdset - this way you can often re-use the commands too.
-search_index_entry = {'aliases': 'nudge', 'category': 'general', 'key': 'nudge lid', 'tags': '', 'text': "\n Try to nudge the button's lid\n\n Usage:\n nudge lid\n\n This command will have you try to\n push the lid of the button away.\n "}¶
Note that we choose to implement this with checking for
-if the lid is open/closed. This is because this command
-is likely to be tried regardless of the state of the lid.
-
An alternative would be to make two versions of this command
-and tuck them into the cmdset linked to the Open and Closed
-lid-state respectively.
-search_index_entry = {'aliases': 'close', 'category': 'general', 'key': 'close lid', 'tags': '', 'text': '\n close the lid\n\n Usage:\n close lid\n\n Closes the lid of the red button.\n '}¶
-search_index_entry = {'aliases': 'get l feel ex examine listen', 'category': 'general', 'key': 'look', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}¶
-search_index_entry = {'aliases': 'h', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n Help function while in the blinded state\n\n Usage:\n help\n\n '}¶
The default cmdset always sits
-on the button object and whereas other
-command sets may be added/merge onto it
-and hide it, removing them will always
-bring it back. It’s added to the object
-using obj.cmdset.add_default().
It contains the commands that launches the other
-command sets, making the red button a self-contained
-item (i.e. you don’t have to manually add any
-scripts etc to it when creating it).
-
-
-
\ No newline at end of file
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorial_examples.html b/docs/1.0-dev/api/evennia.contrib.tutorial_examples.html
index f52e1f7001..a02a820e64 100644
--- a/docs/1.0-dev/api/evennia.contrib.tutorial_examples.html
+++ b/docs/1.0-dev/api/evennia.contrib.tutorial_examples.html
@@ -42,11 +42,12 @@
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorial_examples.red_button.html b/docs/1.0-dev/api/evennia.contrib.tutorial_examples.red_button.html
index 525734819a..ca3e61e58b 100644
--- a/docs/1.0-dev/api/evennia.contrib.tutorial_examples.red_button.html
+++ b/docs/1.0-dev/api/evennia.contrib.tutorial_examples.red_button.html
@@ -44,74 +44,627 @@ script.examples as well as commands.examples to make an interactive
button typeclass.
Create this button with
-
@create/drop examples.red_button.RedButton
+
create/drop red_button.RedButton
Note that you must drop the button before you can see its messages!
The button’s functionality is controlled by CmdSets that gets added and removed
+depending on the ‘state’ the button is in.
+
+
Lid-closed state: In this state the button is covered by a glass cover and trying
+to ‘push’ it will fail. You can ‘nudge’, ‘smash’ or ‘open’ the lid.
+
Lid-open state: In this state the lid is open but will close again after a certain
+time. Using ‘push’ now will press the button and trigger the Blind-state.
+
Blind-state: In this mode you are blinded by a bright flash. This will affect your
+normal commands like ‘look’ and help until the blindness wears off after a certain
+time.
+
+
Timers are handled by persistent delays on the button. These are examples of
+evennia.utils.utils.delay calls that wait a certain time before calling a method -
+such as when closing the lid and un-blinding a character.
This is the version of push used when the lid is closed.
+
An alternative would be to make a ‘push’ command in a default cmdset
+that is always available on the button and then use if-statements to
+check if the lid is open or closed.
+search_index_entry = {'aliases': 'nudge', 'category': 'general', 'key': 'nudge lid', 'tags': '', 'text': "\n Try to nudge the button's lid.\n\n Usage:\n nudge lid\n\n This command will have you try to push the lid of the button away.\n\n "}¶
+search_index_entry = {'aliases': 'break lid smash lid smash', 'category': 'general', 'key': 'smash glass', 'tags': '', 'text': '\n Smash the protective glass.\n\n Usage:\n smash glass\n\n Try to smash the glass of the button.\n\n '}¶
It contains the commands that launches the other
+command sets, making the red button a self-contained
+item (i.e. you don’t have to manually add any
+scripts etc to it when creating it).
+
Note that this is given with a key_mergetype set. This
+is set up so that the cmdset with merge with Union merge type
+except if the other cmdset to merge with is LidOpenCmdSet,
+in which case it will Replace that. So these two cmdsets will
+be mutually exclusive.
This is the actual executing part of the command. It is
+called directly after self.parse(). See the docstring of this
+module for which object properties are available (beyond those
+set in self.parse())
+search_index_entry = {'aliases': 'close', 'category': 'general', 'key': 'close lid', 'tags': '', 'text': '\n Close the lid\n\n Usage:\n close lid\n\n Closes the lid of the red button.\n '}¶
Note that this is given with a key_mergetype set. This
+is set up so that the cmdset with merge with Union merge type
+except if the other cmdset to merge with is LidClosedCmdSet,
+in which case it will Replace that. So these two cmdsets will
+be mutually exclusive.
+search_index_entry = {'aliases': 'l examine ex listen feel get', 'category': 'general', 'key': 'look', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}¶
+search_index_entry = {'aliases': 'h', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n Help function while in the blinded state\n\n Usage:\n help\n\n '}¶
This is the cmdset added to the account when
+the button is pushed.
+
Since this has mergetype Replace it will completely remove the commands of
+all other cmdsets while active. To allow some limited interaction
+(pose/say) we import those default commands and add them too.
+
We also disable all exit-commands generated by exits and
+object-interactions while blinded by setting no_exits and no_objs flags
+on the cmdset. This is to avoid the player walking off or interfering with
+other objects while blinded. Account-level commands however (channel messaging
+etc) will not be affected by the blinding.
This class describes an evil red button. It will use the script
-definition in contrib/examples/red_button_scripts to blink at regular
-intervals. It also uses a series of script and commands to handle
-pushing the button and causing effects when doing so.
-
-
The following attributes can be set on the button:
desc_lid_open - description when lid is open
-desc_lid_closed - description when lid is closed
-desc_lamp_broken - description when lamp is broken
-
-
+
This class describes an evil red button. It will blink invitingly and
+temporarily blind whomever presses it.
+
The button can take a few optional attributes controlling how things will
+be displayed in its various states. This is a useful way to give builders
+the option to customize a complex object from in-game. Actual return messages
+to event-actions are (in this example) left with each command, but one could
+also imagine having those handled via Attributes as well, if one wanted a
+completely in-game customizable button without needing to tweak command
+classes.
+
Attributes:
+- desc_closed_lid: This is the description to show of the button
+
+
when the lid is closed.
+
+
+
desc_open_lid”: Shown when the lid is open
+
auto_close_msg: Message to show when lid auto-closes
+
desc_add_lamp_broken: Extra desc-line added after normal desc when lamp
+is broken.
+
blink_msg: A list of strings to randomly choose from when the lamp
+blinks.
+
+
Notes:
+The button starts with lid closed. To set the initial description,
+you can either set desc after creating it or pass a desc attribute
+when creating it, such as
+button = create_object(RedButton, …, attributes=[(‘desc’, ‘my desc’)]).
+
+
+desc_closed_lid = 'This is a large red button, inviting yet evil-looking. A closed glass lid protects it.'¶
+
+
+
+
+desc_open_lid = 'This is a large red button, inviting yet evil-looking. Its glass cover is open and the button exposed.'¶
+
+
+
+
+auto_close_msg = "The button's glass lid silently slides back in place."¶
+
+
+
+
+lamp_breaks_msg = 'The lamp flickers, the button going dark.'¶
+
+
+
+
+desc_add_lamp_broken = '\nThe big red button has stopped blinking for the time being.'¶
+
+
+
+
+blink_msgs = ['The red button flashes briefly.', 'The red button blinks invitingly.', 'The red button flashes. You know you wanna push it!']¶
msg (str, optional) – If given, display a message to the room
+
lid closes. (when) –
+
+
+
+
This will first try to get the Attribute (self.db.desc_closed_lid) in
+case it was set by a builder and if that was None, it will fall back to
+self.desc_closed_lid, the default description (note that lack of .db).
Close the glass lid. This validates all scripts on the button,
-which means that scripts only being valid when the lid is open
-will go away automatically.
The script system will regularly call this
-function to make the button blink. Now and then
-it won’t blink at all though, to add some randomness
-to how often the message is echoed.
These are scripts intended for a particular object - the
-red_button object type in contrib/examples. A few variations
-on uses of scripts are included.
This manages the cmdset for the “closed” button state. What this
-means is that while this script is valid, we add the RedButtonClosed
-cmdset to it (with commands like open, nudge lid etc)
This is called once every server restart, so we want to add the
-(memory-resident) cmdset to the object here. is_valid is automatically
-checked so we don’t need to worry about adding the script to an
-open lid.
This is called once every server restart, so we want to add the
-(memory-resident) cmdset to the object here. is_valid is
-automatically checked, so we don’t need to worry about
-adding the cmdset to a closed lid-button.
This adds a (very limited) cmdset TO THE ACCOUNT, during a certain time,
-after which the script will close and all functions are
-restored. It’s up to the function starting the script to actually
-set it on the right account object.
Note that the RedButtonBlind cmdset is defined to completly
-replace the other cmdsets on the stack while it is active
-(this means that while blinded, only operations in this cmdset
-will be possible for the account to perform). It is however
-not persistent, so should there be a bug in it, we just need
-to restart the server to clear out of it during development.
This event closes the glass lid over the button
-some time after it was opened. It’s a one-off
-script that should be started/created when the
-lid is opened.
Called when script object is first created. Sets things up.
-We want to have a lid on the button that the user can pull
-aside in order to make the button ‘pressable’. But after a set
-time that lid should auto-close again, making the button safe
-from pressing (and deleting this command).
This script can only operate if the lid is open; if it
-is already closed, the script is clearly invalid.
-
Note that we are here relying on an self.obj being
-defined (and being a RedButton object) - this we should be able to
-expect since this type of script is always tied to one individual
-red button object and not having it would be an error.
Called after self.interval seconds. It closes the lid. Before this method is
-called, self.is_valid() is automatically checked, so there is no need to
-check this manually.
This deactivates the button for a short while (it won’t blink, won’t
-close its lid etc). It is meant to be called when the button is pushed
-and run as long as the blinded effect lasts. We cannot put these methods
-in the AddBlindedCmdSet script since that script is defined on the account
-whereas this one must be defined on the button.
Deactivate the button. Observe that this method is always
-called directly, regardless of the value of self.start_delay
-(that just controls when at_repeat() is called)
-
-
-
\ No newline at end of file
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorial_world.objects.html b/docs/1.0-dev/api/evennia.contrib.tutorial_world.objects.html
index 0b2b49c545..f62e3af643 100644
--- a/docs/1.0-dev/api/evennia.contrib.tutorial_world.objects.html
+++ b/docs/1.0-dev/api/evennia.contrib.tutorial_world.objects.html
@@ -361,7 +361,7 @@ of the object. We overload it with our own version.
@@ -741,7 +741,7 @@ parry - forgoes your attack but will make you harder to hit on next
-search_index_entry = {'aliases': 'stab fight slash bash thrust pierce parry chop kill defend hit', 'category': 'tutorialworld', 'key': 'attack', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}¶
+search_index_entry = {'aliases': 'kill parry thrust hit defend fight bash stab pierce slash chop', 'category': 'tutorialworld', 'key': 'attack', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorial_world.rooms.html b/docs/1.0-dev/api/evennia.contrib.tutorial_world.rooms.html
index 62bb82d397..6514f193ee 100644
--- a/docs/1.0-dev/api/evennia.contrib.tutorial_world.rooms.html
+++ b/docs/1.0-dev/api/evennia.contrib.tutorial_world.rooms.html
@@ -184,7 +184,7 @@ code except for adding in the details.
@@ -199,7 +199,7 @@ code except for adding in the details.
-search_index_entry = {'aliases': 'ls l', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n looks at the room and on details\n\n Usage:\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects\n in your vicinity.\n\n Tutorial: This is a child of the default Look command, that also\n allows us to look at "details" in the room. These details are\n things to examine and offers some extra description without\n actually having to be actual database objects. It uses the\n return_detail() hook on TutorialRooms for this.\n '}¶
+search_index_entry = {'aliases': 'l ls', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n looks at the room and on details\n\n Usage:\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects\n in your vicinity.\n\n Tutorial: This is a child of the default Look command, that also\n allows us to look at "details" in the room. These details are\n things to examine and offers some extra description without\n actually having to be actual database objects. It uses the\n return_detail() hook on TutorialRooms for this.\n '}¶
@@ -893,7 +893,7 @@ random chance of eventually finding a light source.
-search_index_entry = {'aliases': 'feel around fiddle l feel search', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}¶
+search_index_entry = {'aliases': 'l search feel around feel fiddle', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.scripts.scripthandler.html b/docs/1.0-dev/api/evennia.scripts.scripthandler.html
index 35a759521e..09f56f6fdc 100644
--- a/docs/1.0-dev/api/evennia.scripts.scripthandler.html
+++ b/docs/1.0-dev/api/evennia.scripts.scripthandler.html
@@ -138,25 +138,6 @@ If no key is given, delete all scripts on the object!
Runs a validation on this object’s scripts only. This should
-be called regularly to crank the wheels.
-
-
Parameters
-
init_mode (str, optional) –
-
This is used during server
-
-
upstart and can have three values:
-- False (no init mode). Called during run.
-- “reset” - server reboot. Kill non-persistent scripts
-- “reload” - server reload. Keep non-persistent scripts.
-
-
-
-
-
diff --git a/docs/1.0-dev/api/evennia.scripts.scripts.html b/docs/1.0-dev/api/evennia.scripts.scripts.html
index 40ede15adc..acedf4711d 100644
--- a/docs/1.0-dev/api/evennia.scripts.scripts.html
+++ b/docs/1.0-dev/api/evennia.scripts.scripts.html
@@ -70,182 +70,20 @@ errors (list): A list of errors in string form, if any.
Get time until the script fires it at_repeat hook again.
-
-
Returns
-
next (int) –
-
-
Time in seconds until the script runs again.
If not a timed script, return None.
-
-
-
-
-
-
Notes
-
This hook is not used in any way by the script’s stepping
-system; it’s only here for the user to be able to check in
-on their scripts and when they will next be run.
Called every time the script is started (for persistent
-scripts, this is usually once every server start)
-
-
Parameters
-
force_restart (bool, optional) – Normally an already
-started script will not be started again. if
-force_restart=True, the script will always restart
-the script, regardless of if it has started before.
Restart a paused script. This WILL call the at_start() hook.
-
-
Parameters
-
manual_unpause (bool, optional) – This is False if unpause is
-called by the server reload/reset mechanism.
-
-
Returns
-
result (bool) – True if unpause was triggered, False otherwise.
-
-
Raises
-
RuntimeError – If trying to automatically resart this script
-(usually after a reset/reload), but it was manually paused,
-and so should not the auto-unpaused.
Restarts an already existing/running Script from the
-beginning, optionally using different settings. This will
-first call the stop hooks, and then the start hooks again.
-:param interval: Allows for changing the interval
-
-
of the Script. Given in seconds. if None, will use the already stored interval.
-
-
-
Parameters
-
-
repeats (int, optional) – The number of repeats. If unset, will
-use the previous setting.
-
start_delay (bool, optional) – If we should wait interval seconds
-before starting or not. If None, re-use the previous setting.
Is called to check if the script is valid to run at this time.
-Should return a boolean. The method is assumed to collect all
-needed information from its related self.obj.
+
Is called to check if the script’s timer is valid to run at this time.
+Should return a boolean. If False, the timer will be stopped.
Called whenever the script is started, which for persistent
-scripts is at least once every server start. It will also be
-called when starting again after a pause (such as after a
-server reload)
+
Called whenever the script timer is started, which for persistent
+timed scripts is at least once every server start. It will also be
+called when starting again after a pause (including after a
+server reload).
Parameters
**kwargs (dict) – Arbitrary, optional arguments for users
@@ -266,20 +104,39 @@ overriding the call (unused by default).
Called whenever when it’s time for this script to stop (either
-because is_valid returned False or it runs out of iterations)
-
-
Args
-
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
-
-
+
Called whenever when it’s time for this script’s timer to stop (either
+because is_valid returned False, it ran out of iterations or it was manuallys
+stopped.
+
+
Parameters
+
**kwargs (dict) – Arbitrary, optional arguments for users
+overriding the call (unused by default).
This hook is called after the server has started. It can be used to add
+post-startup setup for Scripts without a timer component (for which at_start
+could be used).
diff --git a/docs/1.0-dev/api/evennia.utils.evmore.html b/docs/1.0-dev/api/evennia.utils.evmore.html
index 7748c6aadf..3f8833feb5 100644
--- a/docs/1.0-dev/api/evennia.utils.evmore.html
+++ b/docs/1.0-dev/api/evennia.utils.evmore.html
@@ -75,7 +75,7 @@ the caller.msg() construct every time the page is updated.
@@ -101,7 +101,7 @@ the caller.msg() construct every time the page is updated.
-search_index_entry = {'aliases': 'abort a next top t end q back b quit n e', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Manipulate the text paging\n '}¶
+search_index_entry = {'aliases': 't a top e b q end quit abort next back n', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Manipulate the text paging\n '}¶
diff --git a/docs/1.0-dev/api/evennia.utils.utils.html b/docs/1.0-dev/api/evennia.utils.utils.html
index a1269e0d6e..7e24575879 100644
--- a/docs/1.0-dev/api/evennia.utils.utils.html
+++ b/docs/1.0-dev/api/evennia.utils.utils.html
@@ -712,6 +712,63 @@ a server restart/reload, taking into account the specified delay
(and server down time).
callback (callable) – This will be called with *args, **kwargs every
+interval seconds. This must be possible to pickle regardless
+of if persistent is set or not!
+
persistent (bool, optional) – If ticker survives a server reload.
+
idstring (str, optional) – Separates multiple tickers. This is useful
+mainly if wanting to set up multiple repeats for the same
+interval/callback but with different args/kwargs.
+
stop (bool, optional) – If set, use the given parameters to _stop_ a running
+ticker instead of creating a new one.
+
store_key (tuple, optional) – This is only used in combination with stop and
+should be the return given from the original repeat call. If this
+is given, all other args except stop are ignored.
+
*args – Used as arguments to callback.
+
**kwargs –
Used as arguments to callback.
+
+
+
+
Returns
+
tuple or None – This is the store_key - the identifier for the created ticker.
+Store this and pass into unrepat() in order to to stop this ticker
+later. It this lost you need to stop the ticker via TICKER_HANDLER.remove
+by supplying all the same arguments directly. No return if stop=True
+
+
Raises
+
KeyError – If trying to stop a ticker that was not found.