diff --git a/evennia/scripts/scripts.py b/evennia/scripts/scripts.py index 1f7ef065e7..9814262b53 100644 --- a/evennia/scripts/scripts.py +++ b/evennia/scripts/scripts.py @@ -1,8 +1,8 @@ """ -This module contains the base DefaultScript class that all -scripts are inheriting from. +This module defines Scripts, out-of-character entities that can store +data both on themselves and on other objects while also having the +ability to run timers. -It also defines a few common scripts. """ from twisted.internet.defer import Deferred, maybeDeferred @@ -23,6 +23,7 @@ class ExtendedLoopingCall(LoopingCall): """ LoopingCall that can start at a delay different than `self.interval`. + """ start_delay = None callcount = 0 @@ -31,18 +32,32 @@ class ExtendedLoopingCall(LoopingCall): """ Start running function every interval seconds. - This overloads the LoopingCall default by offering - the start_delay keyword and ability to repeat. + This overloads the LoopingCall default by offering the + start_delay keyword and ability to repeat. + + Args: + interval (int): Repeat interval in seconds. + now (bool, optional): Whether to start immediately or after + `start_delay` seconds. + start_delay (bool: 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. + 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. + + Raises: + AssertError: if trying to start a task which is already running. + ValueError: If interval is set to an invalid value < 0. + + Notes: + As opposed to Twisted's inbuilt count mechanism, this + system will count also if force_repeat() was called rather + than just the number of `interval` seconds since the start. + This allows us to force-step through a limited number of + steps if we want. - start_delay: The number of seconds before starting. - If None, wait interval seconds. Only - valid is now is False. - repeat_start: the task will track how many times it has run. - this will change where it starts counting from. - Note that as opposed to Twisted's inbuild - counter, this will count also if force_repeat() - was called (so it will not just count the number - of interval seconds since start). """ assert not self.running, ("Tried to start an already running " "ExtendedLoopingCall.") @@ -72,21 +87,29 @@ class ExtendedLoopingCall(LoopingCall): return d def __call__(self): - "tick one step" + """ + Tick one step + """ self.callcount += 1 super(ExtendedLoopingCall, self).__call__() def _reschedule(self): """ - Handle call rescheduling including - nulling `start_delay` and stopping if - number of repeats is reached. + Handle call rescheduling including nulling `start_delay` and + stopping if number of repeats is reached. """ self.start_delay = None super(ExtendedLoopingCall, self)._reschedule() def force_repeat(self): - "Force-fire the callback" + """ + Force-fire the callback + + Raises: + AssertionError: When trying to force a task that is not + running. + + """ assert self.running, ("Tried to fire an ExtendedLoopingCall " "that was not running.") if self.call is not None: @@ -96,8 +119,13 @@ class ExtendedLoopingCall(LoopingCall): def next_call_time(self): """ - Return the time in seconds until the next call. This takes - `start_delay` into account. + Get the next call time. + + Returns: + next (int): The time in seconds until the next call. This + takes `start_delay` into account. Returns `None` if + the task is not running. + """ if self.running: currentTime = self.clock.seconds() @@ -107,10 +135,10 @@ class ExtendedLoopingCall(LoopingCall): # # Base script, inherit from DefaultScript below instead. # -class ScriptBase(ScriptDB): +class _ScriptBase(ScriptDB): """ - Base class for scripts. Don't inherit from this, inherit - from the class `DefaultScript` below instead. + Base class for scripts. Don't inherit from this, inherit from the + class `DefaultScript` below instead. """ __metaclass__ = TypeclassBase @@ -118,8 +146,11 @@ class ScriptBase(ScriptDB): def __eq__(self, other): """ - This has to be located at this level, having it in the - parent doesn't work. + Compares two Scripts. Compares dbids. + + Args: + other (Script): A script to compare with. + """ try: return other.dbid == self.dbid @@ -127,7 +158,10 @@ class ScriptBase(ScriptDB): return False def _start_task(self): - "start task runner" + """ + Start task runner. + + """ self.ndb._task = ExtendedLoopingCall(self._step_task) @@ -146,13 +180,19 @@ class ScriptBase(ScriptDB): now=not self.db_start_delay) def _stop_task(self): - "stop task runner" + """ + Stop task runner + + """ task = self.ndb._task if task and task.running: task.stop() def _step_errback(self, e): - "callback for runner errors" + """ + Callback for runner errors + + """ cname = self.__class__.__name__ estring = _("Script %(key)s(#%(dbid)s) of type '%(cname)s': at_repeat() error '%(err)s'.") % \ {"key": self.key, "dbid": self.dbid, "cname": cname, @@ -164,7 +204,10 @@ class ScriptBase(ScriptDB): logger.log_errmsg(estring) def _step_callback(self): - "step task runner. No try..except needed due to defer wrap." + """ + Step task runner. No try..except needed due to defer wrap. + + """ if not self.is_valid(): self.stop() @@ -181,7 +224,9 @@ class ScriptBase(ScriptDB): self.stop() def _step_task(self): - "Step task. This groups error handling." + """ + Step task. This groups error handling. + """ try: return maybeDeferred(self._step_callback).addErrback(self._step_errback) except Exception: @@ -191,11 +236,17 @@ class ScriptBase(ScriptDB): def time_until_next_repeat(self): """ - Returns the time in seconds until the script will be - run again. If this is not a stepping script, returns `None`. - This 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. + 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 if task: @@ -206,21 +257,34 @@ class ScriptBase(ScriptDB): return None def remaining_repeats(self): - "Get the number of returning repeats. Returns `None` if unlimited repeats." + """ + 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 if task: return max(0, self.db_repeats - task.callcount) def start(self, force_restart=False): """ - Called every time the script is started (for - persistent scripts, this is usually once every server start) + Called every time the script is started (for persistent + scripts, this is usually once every server start) - force_restart - if True, will always restart the script, regardless - of if it has started before. + 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. - returns 0 or 1 to indicated the script has been started or not. - Used in counting. """ if self.is_active and not force_restart: @@ -255,14 +319,18 @@ class ScriptBase(ScriptDB): def stop(self, kill=False): """ - Called to stop the script from running. - This also deletes the script. + 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. - kill - don't call finishing hooks. """ - #print "stopping script %s" % self.key - #import pdb - #pdb.set_trace() if not kill: try: self.at_stop() @@ -317,116 +385,13 @@ class ScriptBase(ScriptDB): if task: task.force_repeat() - # hooks - def at_script_creation(self): - "placeholder" - pass - def is_valid(self): - "placeholder" - pass - - def at_start(self): - "placeholder." - pass - - def at_stop(self): - "placeholder" - pass - - def at_repeat(self): - "placeholder" - pass - - def at_init(self): - "called when typeclass re-caches. Usually not used for scripts." - pass - -# -# Base Script - inherit from this -# - -class DefaultScript(ScriptBase): +class DefaultScript(_ScriptBase): """ This is the base TypeClass for all Scripts. Scripts describe events, timers and states in game, they can have a time component or describe a state that changes under certain conditions. - Script API: - - * Available properties (only available on initiated Typeclass objects) - - key (string) - name of object - name (string)- same as key - aliases (list of strings) - aliases to the object. Will be saved to - database as AliasDB entries but returned as strings. - dbref (int, read-only) - unique #id-number. Also "id" can be used. - date_created (string) - time stamp of object creation - permissions (list of strings) - list of permission strings - - desc (string) - optional description of script, shown in listings - obj (Object) - optional object that this script is connected to - and acts on (set automatically - by `obj.scripts.add()`) - interval (int) - how often script should run, in seconds. - <=0 turns off ticker - start_delay (bool) - if the script should start repeating right - away or wait self.interval seconds - repeats (int) - how many times the script should repeat before - stopping. <=0 means infinite repeats - persistent (bool) - if script should survive a server shutdown or not - is_active (bool) - if script is currently running - - * Handlers - - locks - lock-handler: use locks.add() to add new lock strings - db - attribute-handler: store/retrieve database attributes on this - self.db.myattr=val, val=self.db.myattr - ndb - non-persistent attribute handler: same as db but does not - create a database entry when storing data - - * Helper methods - - start() - start script (this usually happens automatically at creation - and obj.script.add() etc) - stop() - stop script, and delete it - pause() - put the script on hold, until unpause() is called. If script - is persistent, the pause state will survive a shutdown. - unpause() - restart a previously paused script. The script will - continue as if it was never paused. - force_repeat() - force-step the script, regardless of how much remains - until next step. This counts like a normal firing in all ways. - time_until_next_repeat() - if a timed script (interval>0), returns - time until next tick - remaining_repeats() - number of repeats remaining, if limited - - * Hook methods - - at_script_creation() - called only once, when an object of this - class is first created. - is_valid() - is called to check if the script is valid to be running - at the current time. If is_valid() returns False, the - running script is stopped and removed from the game. You - can use this to check state changes (i.e. an script - tracking some combat stats at regular intervals is only - valid to run while there is actual combat going on). - at_start() - Called every time the script is started, which for - persistent scripts is at least once every server start. - Note that this is unaffected by self.delay_start, which - only delays the first call to at_repeat(). It will also - be called after a pause, to allow for setting up the script. - at_repeat() - Called every self.interval seconds. It will be called - immediately upon launch unless self.delay_start is True, - which will delay the first call of this method by - self.interval seconds. If self.interval<=0, this method - will never be called. - at_stop() - Called as the script object is stopped and is about to - be removed from the game, e.g. because is_valid() - returned False or self.stop() was called manually. - at_server_reload() - Called when server reloads. Can be used to save - temporary variables you want should survive a reload. - at_server_shutdown() - called at a full server shutdown. - """ def at_first_save(self): """ @@ -481,38 +446,39 @@ class DefaultScript(ScriptBase): def is_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. + Should return a boolean. The method is assumed to collect all + needed information from its related self.obj. """ return not self._is_deleted def at_start(self): """ 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) + 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) """ pass def at_repeat(self): """ - Called repeatedly if this Script is set to repeat - regularly. + Called repeatedly if this Script is set to repeat regularly. """ pass def at_stop(self): """ - 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 to stop (either + because is_valid returned False or it runs out of iterations) """ pass def at_server_reload(self): """ This hook is called whenever the server is shutting down for - restart/reboot. If you want to, for example, save non-persistent - properties across a restart, this is the place to do it. + restart/reboot. If you want to, for example, save + non-persistent properties across a restart, this is the place + to do it. """ pass @@ -527,16 +493,24 @@ class DefaultScript(ScriptBase): # Some useful default Script types used by Evennia. class DoNothing(DefaultScript): - "An script that does nothing. Used as default fallback." + """ + A script that does nothing. Used as default fallback. + """ def at_script_creation(self): - "Setup the script" + """ + Setup the script + """ self.key = "sys_do_nothing" - self.desc = _("This is an empty placeholder script.") + self.desc = "This is an empty placeholder script." class Store(DefaultScript): - "Simple storage script" + """ + Simple storage script + """ def at_script_creation(self): - "Setup the script" + """ + Setup the script + """ self.key = "sys_storage" - self.desc = _("This is a generic storage container.") + self.desc = "This is a generic storage container."