mirror of
https://github.com/evennia/evennia.git
synced 2026-03-20 06:46:31 +01:00
Reshuffling the Evennia package into the new template paradigm.
This commit is contained in:
parent
2846e64833
commit
2b3a32e447
371 changed files with 17250 additions and 304 deletions
|
|
@ -1,610 +0,0 @@
|
|||
"""
|
||||
This module contains the base Script class that all
|
||||
scripts are inheriting from.
|
||||
|
||||
It also defines a few common scripts.
|
||||
"""
|
||||
|
||||
from twisted.internet.defer import Deferred, maybeDeferred
|
||||
from twisted.internet.task import LoopingCall
|
||||
from django.conf import settings
|
||||
from src.typeclasses.models import TypeclassBase
|
||||
from django.utils.translation import ugettext as _
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.scripts.manager import ScriptManager
|
||||
from src.comms import channelhandler
|
||||
from src.utils import logger
|
||||
|
||||
__all__ = ["Script", "DoNothing", "CheckSessions",
|
||||
"ValidateScripts", "ValidateChannelHandler"]
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_SESSIONS = None
|
||||
|
||||
class ExtendedLoopingCall(LoopingCall):
|
||||
"""
|
||||
LoopingCall that can start at a delay different
|
||||
than self.interval.
|
||||
"""
|
||||
start_delay = None
|
||||
callcount = 0
|
||||
|
||||
def start(self, interval, now=True, start_delay=None, count_start=0):
|
||||
"""
|
||||
Start running function every interval seconds.
|
||||
|
||||
This overloads the LoopingCall default by offering
|
||||
the start_delay keyword and ability to repeat.
|
||||
|
||||
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.")
|
||||
if interval < 0:
|
||||
raise ValueError, "interval must be >= 0"
|
||||
|
||||
self.running = True
|
||||
d = self.deferred = Deferred()
|
||||
self.starttime = self.clock.seconds()
|
||||
self._expectNextCallAt = self.starttime
|
||||
self.interval = interval
|
||||
self._runAtStart = now
|
||||
self.callcount = max(0, count_start)
|
||||
|
||||
if now:
|
||||
self()
|
||||
else:
|
||||
if start_delay is not None and start_delay >= 0:
|
||||
# we set start_delay after the _reshedule call to make
|
||||
# next_call_time() find it until next reshedule.
|
||||
self.interval = start_delay
|
||||
self._reschedule()
|
||||
self.interval = interval
|
||||
self.start_delay = start_delay
|
||||
else:
|
||||
self._reschedule()
|
||||
return d
|
||||
|
||||
def __call__(self):
|
||||
"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.
|
||||
"""
|
||||
self.start_delay = None
|
||||
super(ExtendedLoopingCall, self)._reschedule()
|
||||
|
||||
def force_repeat(self):
|
||||
"Force-fire the callback"
|
||||
assert self.running, ("Tried to fire an ExtendedLoopingCall "
|
||||
"that was not running.")
|
||||
if self.call is not None:
|
||||
self.call.cancel()
|
||||
self._expectNextCallAt = self.clock.seconds()
|
||||
self()
|
||||
|
||||
def next_call_time(self):
|
||||
"""
|
||||
Return the time in seconds until the next call. This takes
|
||||
start_delay into account.
|
||||
"""
|
||||
if self.running:
|
||||
currentTime = self.clock.seconds()
|
||||
return self._expectNextCallAt - currentTime
|
||||
return None
|
||||
|
||||
#
|
||||
# Base script, inherit from Script below instead.
|
||||
#
|
||||
class ScriptBase(ScriptDB):
|
||||
"""
|
||||
Base class for scripts. Don't inherit from this, inherit
|
||||
from the class 'Script' instead.
|
||||
"""
|
||||
__metaclass__ = TypeclassBase
|
||||
objects = ScriptManager()
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
This has to be located at this level, having it in the
|
||||
parent doesn't work.
|
||||
"""
|
||||
try:
|
||||
return other.dbid == self.dbid
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _start_task(self):
|
||||
"start task runner"
|
||||
|
||||
self.ndb._task = ExtendedLoopingCall(self._step_task)
|
||||
|
||||
if self.db._paused_time:
|
||||
# the script was paused; restarting
|
||||
callcount = self.db._paused_callcount or 0
|
||||
self.ndb._task.start(self.db_interval,
|
||||
now=False,
|
||||
start_delay=self.db._paused_time,
|
||||
count_start=callcount)
|
||||
del self.db._paused_time
|
||||
del self.db._paused_repeats
|
||||
else:
|
||||
# starting script anew
|
||||
self.ndb._task.start(self.db_interval,
|
||||
now=not self.db_start_delay)
|
||||
|
||||
def _stop_task(self):
|
||||
"stop task runner"
|
||||
task = self.ndb._task
|
||||
if task and task.running:
|
||||
task.stop()
|
||||
|
||||
def _step_errback(self, e):
|
||||
"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,
|
||||
"err": e.getErrorMessage()}
|
||||
try:
|
||||
self.db_obj.msg(estring)
|
||||
except Exception:
|
||||
pass
|
||||
logger.log_errmsg(estring)
|
||||
|
||||
def _step_callback(self):
|
||||
"step task runner. No try..except needed due to defer wrap."
|
||||
|
||||
if not self.is_valid():
|
||||
self.stop()
|
||||
return
|
||||
|
||||
# call hook
|
||||
self.at_repeat()
|
||||
|
||||
# check repeats
|
||||
callcount = self.ndb._task.callcount
|
||||
maxcount = self.db_repeats
|
||||
if maxcount > 0 and maxcount <= callcount:
|
||||
#print "stopping script!"
|
||||
self.stop()
|
||||
|
||||
def _step_task(self):
|
||||
"Step task. This groups error handling."
|
||||
try:
|
||||
return maybeDeferred(self._step_callback).addErrback(self._step_errback)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
||||
# Public methods
|
||||
|
||||
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.
|
||||
"""
|
||||
task = self.ndb._task
|
||||
if task:
|
||||
try:
|
||||
return int(round(task.next_call_time()))
|
||||
except TypeError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def remaining_repeats(self):
|
||||
"Get the number of returning repeats. Returns None if 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)
|
||||
|
||||
force_restart - if True, will always restart the script, regardless
|
||||
of if it has started before.
|
||||
|
||||
returns 0 or 1 to indicated the script has been started or not.
|
||||
Used in counting.
|
||||
"""
|
||||
|
||||
if self.is_active and not force_restart:
|
||||
# script already runs and should not be restarted.
|
||||
return 0
|
||||
|
||||
obj = self.obj
|
||||
if obj:
|
||||
# check so the scripted object is valid and initalized
|
||||
try:
|
||||
obj.cmdset
|
||||
except AttributeError:
|
||||
# this means the object is not initialized.
|
||||
logger.log_trace()
|
||||
self.is_active = False
|
||||
return 0
|
||||
|
||||
# try to restart a paused script
|
||||
if self.unpause():
|
||||
return 1
|
||||
|
||||
# start the script from scratch
|
||||
self.is_active = True
|
||||
try:
|
||||
self.at_start()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
||||
if self.db_interval > 0:
|
||||
self._start_task()
|
||||
return 1
|
||||
|
||||
def stop(self, kill=False):
|
||||
"""
|
||||
Called to stop the script from running.
|
||||
This also deletes the script.
|
||||
|
||||
kill - don't call finishing hooks.
|
||||
"""
|
||||
#print "stopping script %s" % self.key
|
||||
#import pdb
|
||||
#pdb.set_trace()
|
||||
if not kill:
|
||||
try:
|
||||
self.at_stop()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
self._stop_task()
|
||||
try:
|
||||
self.delete()
|
||||
except AssertionError:
|
||||
logger.log_trace()
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def pause(self):
|
||||
"""
|
||||
This stops a running script and stores its active state.
|
||||
It WILL NOT call that at_stop() hook.
|
||||
"""
|
||||
if not self.db._paused_time:
|
||||
# only allow pause if not already paused
|
||||
task = self.ndb._task
|
||||
if task:
|
||||
self.db._paused_time = task.next_call_time()
|
||||
self.db._paused_callcount = task.callcount
|
||||
self._stop_task()
|
||||
self.is_active = False
|
||||
|
||||
def unpause(self):
|
||||
"""
|
||||
Restart a paused script. This WILL call the at_start() hook.
|
||||
"""
|
||||
if self.db._paused_time:
|
||||
# only unpause if previously paused
|
||||
self.is_active = True
|
||||
|
||||
try:
|
||||
self.at_start()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
||||
self._start_task()
|
||||
return True
|
||||
|
||||
def force_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
|
||||
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 Script(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):
|
||||
"""
|
||||
This is called after very first time this object is saved.
|
||||
Generally, you don't need to overload this, but only the hooks
|
||||
called by this method.
|
||||
"""
|
||||
self.at_script_creation()
|
||||
|
||||
if hasattr(self, "_createdict"):
|
||||
# this will only be set if the utils.create_script
|
||||
# function was used to create the object. We want
|
||||
# the create call's kwargs to override the values
|
||||
# set by hooks.
|
||||
cdict = self._createdict
|
||||
updates = []
|
||||
if not cdict.get("key"):
|
||||
if not self.db_key:
|
||||
self.db_key = "#%i" % self.dbid
|
||||
updates.append("db_key")
|
||||
elif self.db_key != cdict["key"]:
|
||||
self.db_key = cdict["key"]
|
||||
updates.append("db_key")
|
||||
if cdict.get("interval") and self.interval != cdict["interval"]:
|
||||
self.db_interval = cdict["interval"]
|
||||
updates.append("db_interval")
|
||||
if cdict.get("start_delay") and self.start_delay != cdict["start_delay"]:
|
||||
self.db_start_delay = cdict["start_delay"]
|
||||
updates.append("db_start_delay")
|
||||
if cdict.get("repeats") and self.repeats != cdict["repeats"]:
|
||||
self.db_repeats = cdict["repeats"]
|
||||
updates.append("db_repeats")
|
||||
if cdict.get("persistent") and self.persistent != cdict["persistent"]:
|
||||
self.db_persistent = cdict["persistent"]
|
||||
updates.append("db_persistent")
|
||||
if updates:
|
||||
self.save(update_fields=updates)
|
||||
if not cdict.get("autostart"):
|
||||
# don't auto-start the script
|
||||
return
|
||||
|
||||
# auto-start script (default)
|
||||
self.start()
|
||||
|
||||
|
||||
def at_script_creation(self):
|
||||
"""
|
||||
Only called once, by the create function.
|
||||
"""
|
||||
pass
|
||||
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_repeat(self):
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_server_shutdown(self):
|
||||
"""
|
||||
This hook is called whenever the server is shutting down fully
|
||||
(i.e. not for a restart).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Some useful default Script types used by Evennia.
|
||||
|
||||
class DoNothing(Script):
|
||||
"An script that does nothing. Used as default fallback."
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_do_nothing"
|
||||
self.desc = _("This is an empty placeholder script.")
|
||||
|
||||
|
||||
class Store(Script):
|
||||
"Simple storage script"
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_storage"
|
||||
self.desc = _("This is a generic storage container.")
|
||||
|
||||
|
||||
class CheckSessions(Script):
|
||||
"Check sessions regularly."
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_session_check"
|
||||
self.desc = _("Checks sessions so they are live.")
|
||||
self.interval = 60 # repeat every 60 seconds
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
"called every 60 seconds"
|
||||
global _SESSIONS
|
||||
if not _SESSIONS:
|
||||
from src.server.sessionhandler import SESSIONS as _SESSIONS
|
||||
#print "session check!"
|
||||
#print "ValidateSessions run"
|
||||
_SESSIONS.validate_sessions()
|
||||
|
||||
_FLUSH_CACHE = None
|
||||
_IDMAPPER_CACHE_MAX_MEMORY = settings.IDMAPPER_CACHE_MAXSIZE
|
||||
class ValidateIdmapperCache(Script):
|
||||
"""
|
||||
Check memory use of idmapper cache
|
||||
"""
|
||||
def at_script_creation(self):
|
||||
self.key = "sys_cache_validate"
|
||||
self.desc = _("Restrains size of idmapper cache.")
|
||||
self.interval = 61 * 5 # staggered compared to session check
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
"Called every ~5 mins"
|
||||
global _FLUSH_CACHE
|
||||
if not _FLUSH_CACHE:
|
||||
from src.utils.idmapper.base import conditional_flush as _FLUSH_CACHE
|
||||
_FLUSH_CACHE(_IDMAPPER_CACHE_MAX_MEMORY)
|
||||
|
||||
class ValidateScripts(Script):
|
||||
"Check script validation regularly"
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_scripts_validate"
|
||||
self.desc = _("Validates all scripts regularly.")
|
||||
self.interval = 3600 # validate every hour.
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
"called every hour"
|
||||
#print "ValidateScripts run."
|
||||
ScriptDB.objects.validate()
|
||||
|
||||
|
||||
class ValidateChannelHandler(Script):
|
||||
"Update the channelhandler to make sure it's in sync."
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_channels_validate"
|
||||
self.desc = _("Updates the channel handler")
|
||||
self.interval = 3700 # validate a little later than ValidateScripts
|
||||
self.persistent = True
|
||||
|
||||
def at_repeat(self):
|
||||
"called every hour+"
|
||||
#print "ValidateChannelHandler run."
|
||||
channelhandler.CHANNELHANDLER.update()
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue