mirror of
https://github.com/evennia/evennia.git
synced 2026-03-29 12:07:17 +02:00
Trunk: Merged the Devel-branch (branches/griatch) into /trunk. This constitutes a major refactoring of Evennia. Development will now continue in trunk. See the wiki and the past posts to the mailing list for info. /Griatch
This commit is contained in:
parent
df29defbcd
commit
f83c2bddf8
222 changed files with 22304 additions and 14371 deletions
0
src/scripts/__init__.py
Normal file
0
src/scripts/__init__.py
Normal file
29
src/scripts/admin.py
Normal file
29
src/scripts/admin.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from src.scripts.models import ScriptAttribute, ScriptDB
|
||||
from django.contrib import admin
|
||||
|
||||
class ScriptAttributeAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'db_key', 'db_value', 'db_mode', 'db_obj', 'db_permissions')
|
||||
list_display_links = ("id", 'db_key')
|
||||
ordering = ["db_obj", 'db_key']
|
||||
readonly_fields = ['db_permissions']
|
||||
search_fields = ['id', 'db_key', 'db_obj']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
admin.site.register(ScriptAttribute, ScriptAttributeAdmin)
|
||||
|
||||
class ScriptDBAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'db_key', 'db_typeclass_path', 'db_obj', 'db_interval', 'db_repeats', 'db_persistent')
|
||||
list_display_links = ('id', 'db_key')
|
||||
ordering = ['db_obj', 'db_typeclass_path']
|
||||
readonly_fields = ['db_permissions']
|
||||
search_fields = ['^db_key', 'db_typeclass_path']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
admin.site.register(ScriptDB, ScriptDBAdmin)
|
||||
171
src/scripts/manager.py
Normal file
171
src/scripts/manager.py
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
"""
|
||||
The custom manager for Scripts.
|
||||
"""
|
||||
|
||||
from src.typeclasses.managers import TypedObjectManager
|
||||
from src.typeclasses.managers import returns_typeclass_list
|
||||
|
||||
VALIDATE_ITERATION = 0
|
||||
|
||||
class ScriptManager(TypedObjectManager):
|
||||
"""
|
||||
ScriptManager get methods
|
||||
"""
|
||||
@returns_typeclass_list
|
||||
def get_all_scripts_on_obj(self, obj, key=None):
|
||||
"""
|
||||
Returns as result all the Scripts related to a particular object
|
||||
"""
|
||||
if not obj:
|
||||
return []
|
||||
scripts = self.filter(db_obj=obj)
|
||||
if key:
|
||||
return [script for script in scripts if script.key == key]
|
||||
return scripts
|
||||
|
||||
@returns_typeclass_list
|
||||
def get_all_scripts(self, key=None):
|
||||
"""
|
||||
Return all scripts, alternative only
|
||||
scripts with a certain key/dbref.
|
||||
"""
|
||||
if key:
|
||||
dbref = self.dbref(key)
|
||||
if dbref:
|
||||
# try to see if this is infact a dbref
|
||||
script = self.dbref_search(dbref)
|
||||
if script:
|
||||
return script
|
||||
# not a dbref. Normal key search
|
||||
scripts = self.filter(db_key=key)
|
||||
else:
|
||||
scripts = self.all()
|
||||
return scripts
|
||||
|
||||
def delete_script(self, dbref):
|
||||
"""
|
||||
This stops and deletes a specific script directly
|
||||
from the script database. This might be
|
||||
needed for global scripts not tied to
|
||||
a specific game object.
|
||||
"""
|
||||
scripts = self.get_id(dbref)
|
||||
for script in scripts:
|
||||
script.stop()
|
||||
|
||||
def remove_non_persistent(self):
|
||||
"""
|
||||
This cleans up the script database of all non-persistent
|
||||
scripts. It is called every time the server restarts.
|
||||
"""
|
||||
nr_deleted = 0
|
||||
for script in [script for script in self.get_all_scripts()
|
||||
if not script.persistent]:
|
||||
script.stop()
|
||||
nr_deleted += 1
|
||||
return nr_deleted
|
||||
|
||||
|
||||
def validate(self, scripts=None, obj=None, key=None, dbref=None,
|
||||
init_mode=False):
|
||||
"""
|
||||
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.
|
||||
If key and/or obj is given, only update the related
|
||||
script/object.
|
||||
|
||||
Only one of the arguments are supposed to be supplied
|
||||
at a time, since they are exclusive to each other.
|
||||
|
||||
scripts = a list of scripts objects obtained somewhere.
|
||||
obj = validate only scripts defined on a special object.
|
||||
key = validate only scripts with a particular key
|
||||
dbref = validate only the single script with this particular id.
|
||||
|
||||
init_mode - When this mode is active, non-persistent scripts
|
||||
will be removed and persistent scripts will be
|
||||
force-restarted.
|
||||
|
||||
This method also makes sure start any scripts it validates,
|
||||
this should be harmless, since already-active scripts
|
||||
have the property 'is_running' set and will be skipped.
|
||||
"""
|
||||
|
||||
# we store a variable that tracks if we are calling a
|
||||
# validation from within another validation (avoids
|
||||
# loops).
|
||||
global VALIDATE_ITERATION
|
||||
if VALIDATE_ITERATION > 0:
|
||||
# we are in a nested validation. Exit.
|
||||
VALIDATE_ITERATION -= 1
|
||||
return None, None
|
||||
VALIDATE_ITERATION += 1
|
||||
|
||||
# not in a validation - loop. Validate as normal.
|
||||
|
||||
nr_started = 0
|
||||
nr_stopped = 0
|
||||
|
||||
if init_mode:
|
||||
# special mode when server starts or object logs in.
|
||||
# This deletes all non-persistent scripts from database
|
||||
nr_stopped += self.remove_non_persistent()
|
||||
|
||||
if dbref and self.dbref(dbref):
|
||||
scripts = self.get_id(dbref)
|
||||
elif scripts:
|
||||
pass
|
||||
elif obj:
|
||||
scripts = self.get_all_scripts_on_obj(obj, key=key)
|
||||
else:
|
||||
scripts = self.get_all_scripts(key=key)
|
||||
if not scripts:
|
||||
VALIDATE_ITERATION -= 1
|
||||
return None, None
|
||||
#print "scripts to validate: [%s]" % (", ".join(script.key for script in scripts))
|
||||
for script in scripts:
|
||||
if script.is_valid():
|
||||
#print "validating %s (%i)" % (script.key, id(script.dbobj))
|
||||
nr_started += script.start(force_restart=init_mode)
|
||||
#print "back from start."
|
||||
else:
|
||||
script.stop()
|
||||
nr_stopped += 1
|
||||
VALIDATE_ITERATION -= 1
|
||||
return nr_started, nr_stopped
|
||||
|
||||
@returns_typeclass_list
|
||||
def script_search(self, ostring, obj=None, only_timed=False):
|
||||
"""
|
||||
Search for a particular script.
|
||||
|
||||
ostring - search criterion - a script ID or key
|
||||
obj - limit search to scripts defined on this object
|
||||
only_timed - limit search only to scripts that run
|
||||
on a timer.
|
||||
"""
|
||||
|
||||
ostring = ostring.strip()
|
||||
|
||||
dbref = self.dbref(ostring)
|
||||
if dbref:
|
||||
# this is a dbref, try to find the script directly
|
||||
dbref_match = self.dbref_search(dbref)
|
||||
if dbref_match:
|
||||
ok = True
|
||||
if obj and obj != dbref_match.obj:
|
||||
ok = False
|
||||
if only_timed and dbref_match.interval:
|
||||
ok = False
|
||||
if ok:
|
||||
return [dbref_match]
|
||||
# not a dbref; normal search
|
||||
scripts = self.filter(db_key__iexact=ostring)
|
||||
|
||||
if obj:
|
||||
scripts = scripts.exclude(db_obj=None).filter(db_obj__db_key__iexact=ostring)
|
||||
if only_timed:
|
||||
scripts = scripts.exclude(interval=0)
|
||||
return scripts
|
||||
255
src/scripts/models.py
Normal file
255
src/scripts/models.py
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
"""
|
||||
Scripts are entities that perform some sort of action, either only
|
||||
once or repeatedly. They can be directly linked to a particular
|
||||
Evennia Object or be stand-alonw (in the latter case it is considered
|
||||
a 'global' script). Scripts can indicate both actions related to the
|
||||
game world as well as pure behind-the-scenes events and
|
||||
effects. Everything that has a time component in the game (i.e. is not
|
||||
hard-coded at startup or directly created/controlled by players) is
|
||||
handled by Scripts.
|
||||
|
||||
Scripts have to check for themselves that they should be applied at a
|
||||
particular moment of time; this is handled by the is_valid() hook.
|
||||
Scripts can also implement at_start and at_end hooks for preparing and
|
||||
cleaning whatever effect they have had on the game object.
|
||||
|
||||
Common examples of uses of Scripts:
|
||||
- load the default cmdset to the player object's cmdhandler
|
||||
when logging in.
|
||||
- switch to a different state, such as entering a text editor,
|
||||
start combat or enter a dark room.
|
||||
- Weather patterns in-game
|
||||
- merge a new cmdset with the default one for changing which
|
||||
commands are available at a particular time
|
||||
- give the player/object a time-limited bonus/effect
|
||||
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from src.objects.models import ObjectDB
|
||||
from src.typeclasses.models import Attribute, TypedObject
|
||||
from src.scripts.manager import ScriptManager
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# ScriptAttribute
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class ScriptAttribute(Attribute):
|
||||
"Attributes for ScriptDB objects."
|
||||
db_obj = models.ForeignKey("ScriptDB")
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Script Attribute"
|
||||
verbose_name_plural = "Script Attributes"
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# ScriptDB
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class ScriptDB(TypedObject):
|
||||
"""
|
||||
The Script database representation.
|
||||
|
||||
The TypedObject supplies the following (inherited) properties:
|
||||
key - main name
|
||||
name - alias for key
|
||||
typeclass_path - the path to the decorating typeclass
|
||||
typeclass - auto-linked typeclass
|
||||
date_created - time stamp of object creation
|
||||
permissions - perm strings
|
||||
dbref - #id of object
|
||||
db - persistent attribute storage
|
||||
ndb - non-persistent attribute storage
|
||||
|
||||
The ScriptDB adds the following properties:
|
||||
desc - optional description of script
|
||||
obj - the object the script is linked to, if any
|
||||
interval - how often script should run
|
||||
start_delay - if the script should start repeating right away
|
||||
repeats - how many times the script should repeat
|
||||
persistent - if script should survive a server reboot
|
||||
is_active - bool if script is currently running
|
||||
|
||||
"""
|
||||
|
||||
|
||||
#
|
||||
# ScriptDB Database Model setup
|
||||
#
|
||||
# These databse fields are all set using their corresponding properties,
|
||||
# named same as the field, but withtou the db_* prefix.
|
||||
|
||||
# inherited fields (from TypedObject):
|
||||
# db_key, db_typeclass_path, db_date_created, db_permissions
|
||||
|
||||
# optional description.
|
||||
db_desc = models.CharField(max_length=255, blank=True)
|
||||
# A reference to the database object affected by this Script, if any.
|
||||
db_obj = models.ForeignKey(ObjectDB, null=True, blank=True)
|
||||
# how often to run Script (secs). 0 means there is no timer
|
||||
db_interval = models.IntegerField(default=0)
|
||||
# start script right away or wait interval seconds first
|
||||
db_start_delay = models.BooleanField(default=False)
|
||||
# how many times this script is to be repeated, if interval!=0.
|
||||
db_repeats = models.IntegerField(default=0)
|
||||
# defines if this script should survive a reboot or not
|
||||
db_persistent = models.BooleanField(default=False)
|
||||
# defines if this script has already been started in this session
|
||||
db_is_active = models.BooleanField(default=False)
|
||||
|
||||
# Database manager
|
||||
objects = ScriptManager()
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Script"
|
||||
verbose_name_plural = "Scripts"
|
||||
|
||||
# Wrapper properties to easily set database fields. These are
|
||||
# @property decorators that allows to access these fields using
|
||||
# normal python operations (without having to remember to save()
|
||||
# etc). So e.g. a property 'attr' has a get/set/del decorator
|
||||
# defined that allows the user to do self.attr = value,
|
||||
# value = self.attr and del self.attr respectively (where self
|
||||
# is the script in question).
|
||||
|
||||
# desc property (wraps db_desc)
|
||||
#@property
|
||||
def desc_get(self):
|
||||
"Getter. Allows for value = self.desc"
|
||||
return self.db_desc
|
||||
#@desc.setter
|
||||
def desc_set(self, value):
|
||||
"Setter. Allows for self.desc = value"
|
||||
self.db_desc = value
|
||||
self.save()
|
||||
#@desc.deleter
|
||||
def desc_del(self):
|
||||
"Deleter. Allows for del self.desc"
|
||||
self.db_desc = ""
|
||||
self.save()
|
||||
desc = property(desc_get, desc_set, desc_del)
|
||||
|
||||
# obj property (wraps db_obj)
|
||||
#@property
|
||||
def obj_get(self):
|
||||
"Getter. Allows for value = self.obj"
|
||||
return self.db_obj
|
||||
#@obj.setter
|
||||
def obj_set(self, value):
|
||||
"Setter. Allows for self.obj = value"
|
||||
self.db_obj = value
|
||||
self.save()
|
||||
#@obj.deleter
|
||||
def obj_del(self):
|
||||
"Deleter. Allows for del self.obj"
|
||||
self.db_obj = None
|
||||
self.save()
|
||||
obj = property(obj_get, obj_set, obj_del)
|
||||
|
||||
# interval property (wraps db_interval)
|
||||
#@property
|
||||
def interval_get(self):
|
||||
"Getter. Allows for value = self.interval"
|
||||
return self.db_interval
|
||||
#@interval.setter
|
||||
def interval_set(self, value):
|
||||
"Setter. Allows for self.interval = value"
|
||||
self.db_interval = int(value)
|
||||
self.save()
|
||||
#@interval.deleter
|
||||
def interval_del(self):
|
||||
"Deleter. Allows for del self.interval"
|
||||
self.db_interval = 0
|
||||
self.save()
|
||||
interval = property(interval_get, interval_set, interval_del)
|
||||
|
||||
# start_delay property (wraps db_start_delay)
|
||||
#@property
|
||||
def start_delay_get(self):
|
||||
"Getter. Allows for value = self.start_delay"
|
||||
return self.db_start_delay
|
||||
#@start_delay.setter
|
||||
def start_delay_set(self, value):
|
||||
"Setter. Allows for self.start_delay = value"
|
||||
self.db_start_delay = value
|
||||
self.save()
|
||||
#@start_delay.deleter
|
||||
def start_delay_del(self):
|
||||
"Deleter. Allows for del self.start_delay"
|
||||
self.db_start_delay = False
|
||||
self.save()
|
||||
start_delay = property(start_delay_get, start_delay_set, start_delay_del)
|
||||
|
||||
# repeats property (wraps db_repeats)
|
||||
#@property
|
||||
def repeats_get(self):
|
||||
"Getter. Allows for value = self.repeats"
|
||||
return self.db_repeats
|
||||
#@repeats.setter
|
||||
def repeats_set(self, value):
|
||||
"Setter. Allows for self.repeats = value"
|
||||
self.db_repeats = int(value)
|
||||
self.save()
|
||||
#@repeats.deleter
|
||||
def repeats_del(self):
|
||||
"Deleter. Allows for del self.repeats"
|
||||
self.db_repeats = 0
|
||||
self.save()
|
||||
repeats = property(repeats_get, repeats_set, repeats_del)
|
||||
|
||||
# persistent property (wraps db_persistent)
|
||||
#@property
|
||||
def persistent_get(self):
|
||||
"Getter. Allows for value = self.persistent"
|
||||
return self.db_persistent
|
||||
#@persistent.setter
|
||||
def persistent_set(self, value):
|
||||
"Setter. Allows for self.persistent = value"
|
||||
self.db_persistent = value
|
||||
self.save()
|
||||
#@persistent.deleter
|
||||
def persistent_del(self):
|
||||
"Deleter. Allows for del self.persistent"
|
||||
self.db_persistent = False
|
||||
self.save()
|
||||
persistent = property(persistent_get, persistent_set, persistent_del)
|
||||
|
||||
# is_active property (wraps db_is_active)
|
||||
#@property
|
||||
def is_active_get(self):
|
||||
"Getter. Allows for value = self.is_active"
|
||||
return self.db_is_active
|
||||
#@is_active.setter
|
||||
def is_active_set(self, value):
|
||||
"Setter. Allows for self.is_active = value"
|
||||
self.db_is_active = value
|
||||
self.save()
|
||||
#@is_active.deleter
|
||||
def is_active_del(self):
|
||||
"Deleter. Allows for del self.is_active"
|
||||
self.db_is_active = False
|
||||
self.save()
|
||||
is_active = property(is_active_get, is_active_set, is_active_del)
|
||||
|
||||
#
|
||||
#
|
||||
# ScriptDB class properties
|
||||
#
|
||||
#
|
||||
|
||||
# this is required to properly handle typeclass-dependent attributes
|
||||
attribute_model_path = "src.scripts.models"
|
||||
attribute_model_name = "ScriptAttribute"
|
||||
|
||||
# this is used by all typedobjects as a fallback
|
||||
try:
|
||||
default_typeclass_path = settings.DEFAULT_SCRIPT_TYPECLASS
|
||||
except:
|
||||
default_typeclass_path = "src.scripts.scripts.DoNothing"
|
||||
158
src/scripts/scripthandler.py
Normal file
158
src/scripts/scripthandler.py
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
"""
|
||||
The script handler makes sure to check through all stored scripts
|
||||
to make sure they are still relevant.
|
||||
An scripthandler is automatically added to all game objects. You
|
||||
access it through the property 'scripts' on the game object.
|
||||
"""
|
||||
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.utils import create
|
||||
from src.utils import logger
|
||||
|
||||
def format_script_list(scripts):
|
||||
"Takes a list of scripts and format the output."
|
||||
if not scripts:
|
||||
return "<No scripts>"
|
||||
string = "id obj\t\tkey\t\tinterval\trepeats\tnext\tpersistent"
|
||||
|
||||
for script in scripts:
|
||||
obj = None
|
||||
interval = None
|
||||
repeats = None
|
||||
try:
|
||||
obj = script.obj
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
interval = script.interval
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
repeats = script.repeats
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
next_repeat = script.time_until_next_repeat()
|
||||
except:
|
||||
pass
|
||||
|
||||
if not obj:
|
||||
obj = "<Global>"
|
||||
if not interval:
|
||||
interval = "N/A"
|
||||
else:
|
||||
interval = "%ss" % interval
|
||||
if not repeats:
|
||||
repeats = "N/A"
|
||||
else:
|
||||
repeats = "%ss" % repeats
|
||||
if not next_repeat:
|
||||
next_repeat = "N/A"
|
||||
else:
|
||||
next_repeat = "%ss" % next_repeat
|
||||
|
||||
string += "\n %s %s\t\t%s\t%s\t%s\t%s\t%s\n %s (%s)" % \
|
||||
(script.id, obj, script.key, interval,
|
||||
repeats, next_repeat, script.persistent,
|
||||
script.typeclass_path, script.desc)
|
||||
return string
|
||||
|
||||
class ScriptHandler(object):
|
||||
"""
|
||||
Implements the handler. This sits on each game object.
|
||||
"""
|
||||
def __init__(self, obj):
|
||||
"""
|
||||
Set up internal state.
|
||||
obj - a reference to the object this handler is attached to.
|
||||
|
||||
We retrieve all scripts attached to this object and check
|
||||
if they are all peristent. If they are not, they are just
|
||||
cruft left over from a server shutdown.
|
||||
"""
|
||||
self.obj = obj
|
||||
|
||||
# this is required to stop a nasty loop in some situations that
|
||||
# has the handler infinitely recursively re-added to its object.
|
||||
self.obj.scripts = self
|
||||
|
||||
scripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj)
|
||||
#print "starting scripthandler. %s has scripts %s" % (self.obj, scripts)
|
||||
if scripts:
|
||||
okscripts = [script for script in scripts if script.persistent == True]
|
||||
delscripts = [script for script in scripts if script not in okscripts]
|
||||
for script in delscripts:
|
||||
#print "stopping script %s" % script
|
||||
script.stop()
|
||||
for script in okscripts:
|
||||
#print "starting script %s" % script
|
||||
script.start()
|
||||
|
||||
def __str__(self):
|
||||
"List the scripts tied to this object"
|
||||
scripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj)
|
||||
string = ""
|
||||
for script in scripts:
|
||||
interval = "inf"
|
||||
next_repeat = "inf"
|
||||
repeats = "inf"
|
||||
if script.interval:
|
||||
interval = script.interval
|
||||
if script.repeats:
|
||||
repeats = script.repeats
|
||||
try: next_repeat = script.time_until_next_repeat()
|
||||
except: next_repeat = "?"
|
||||
string += "\n '%s' (%s/%s, %s repeats): %s" % (script.key,
|
||||
next_repeat,
|
||||
interval,
|
||||
repeats,
|
||||
script.desc)
|
||||
return string.strip()
|
||||
|
||||
def add(self, scriptclass, key=None, autostart=True):
|
||||
"""
|
||||
Add an script to this object. The scriptclass
|
||||
argument can be either a class object
|
||||
inheriting from Script, an instantiated script object
|
||||
or a python path to such a class object.
|
||||
|
||||
"""
|
||||
script = create.create_script(scriptclass, key=key, obj=self.obj, autostart=autostart)
|
||||
if not script:
|
||||
logger.log_errmsg("Script %s failed to be created/start." % scriptclass)
|
||||
|
||||
def start(self, scriptkey):
|
||||
"""
|
||||
Find an already added script and force-start it
|
||||
"""
|
||||
scripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=scriptkey)
|
||||
for script in scripts:
|
||||
script.start()
|
||||
|
||||
def delete(self, scriptkey):
|
||||
"""
|
||||
Forcibly delete a script from this object.
|
||||
"""
|
||||
delscripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=scriptkey)
|
||||
for script in delscripts:
|
||||
script.stop()
|
||||
|
||||
def stop(self, scriptkey):
|
||||
"""
|
||||
Alias for delete.
|
||||
"""
|
||||
self.delete(scriptkey)
|
||||
|
||||
def all(self, scriptkey=None):
|
||||
"""
|
||||
Get all scripts stored in the handler, alternatively all matching a key.
|
||||
"""
|
||||
return ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=scriptkey)
|
||||
|
||||
def validate(self):
|
||||
"""
|
||||
Runs a validation on this object's scripts only.
|
||||
This should be called regularly to crank the wheels.
|
||||
"""
|
||||
ScriptDB.objects.validate(obj=self.obj)
|
||||
|
||||
303
src/scripts/scripts.py
Normal file
303
src/scripts/scripts.py
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
"""
|
||||
This module contains the base Script class that all
|
||||
scripts are inheriting from.
|
||||
|
||||
It also defines a few common scripts.
|
||||
"""
|
||||
|
||||
from time import time
|
||||
from twisted.internet import task
|
||||
from src.server import sessionhandler
|
||||
from src.typeclasses.typeclass import TypeClass
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.comms import channelhandler
|
||||
from src.utils import logger
|
||||
|
||||
#
|
||||
# Base script, inherit from Script below instead.
|
||||
#
|
||||
class ScriptClass(TypeClass):
|
||||
"""
|
||||
Base class for all Scripts.
|
||||
"""
|
||||
|
||||
# private methods for handling timers.
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
This has to be located at this level, having it in the
|
||||
parent doesn't work.
|
||||
"""
|
||||
if other:
|
||||
return other.id == self.id
|
||||
return False
|
||||
|
||||
def _start_task(self):
|
||||
"start the task runner."
|
||||
if self.interval:
|
||||
#print "Starting task runner"
|
||||
start_now = not self.start_delay
|
||||
self.ndb.twisted_task = task.LoopingCall(self._step_task)
|
||||
self.ndb.twisted_task.start(self.interval, now=start_now)
|
||||
self.ndb.time_last_called = int(time())
|
||||
#self.save()
|
||||
def _stop_task(self):
|
||||
"stop the task runner"
|
||||
if hasattr(self.ndb, "twisted_task"):
|
||||
self.ndb.twisted_task.stop()
|
||||
def _step_task(self):
|
||||
"perform one repeat step of the script"
|
||||
#print "Stepping task runner (obj %s)" % id(self)
|
||||
#print "Has dbobj: %s" % hasattr(self, 'dbobj')
|
||||
if not self.is_valid():
|
||||
#the script is not valid anymore. Abort.
|
||||
self.stop()
|
||||
return
|
||||
try:
|
||||
self.at_repeat()
|
||||
if self.repeats:
|
||||
if self.repeats <= 1:
|
||||
self.stop()
|
||||
return
|
||||
else:
|
||||
self.repeats -= 1
|
||||
self.ndb.time_last_called = int(time())
|
||||
self.save()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
self._stop_task()
|
||||
|
||||
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.
|
||||
"""
|
||||
if self.interval and hasattr(self.ndb, 'time_last_called'):
|
||||
return max(0, (self.ndb.time_last_called + self.interval) - int(time()))
|
||||
else:
|
||||
return None
|
||||
|
||||
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.
|
||||
"""
|
||||
#print "Script %s (%s) start (active:%s, force:%s) ..." % (self.key, id(self.dbobj),
|
||||
# self.is_active, force_restart)
|
||||
if force_restart:
|
||||
self.is_active = False
|
||||
|
||||
should_start = True
|
||||
if self.obj:
|
||||
try:
|
||||
#print "checking cmdset ... for obj", self.obj
|
||||
dummy = object.__getattribute__(self.obj, 'cmdset')
|
||||
#print "... checked cmdset"
|
||||
except AttributeError:
|
||||
#print "self.obj.cmdset not found. Setting is_active=False."
|
||||
self.is_active = False
|
||||
should_start = False
|
||||
if self.is_active and not force_restart:
|
||||
should_start = False
|
||||
|
||||
if should_start:
|
||||
#print "... starting."
|
||||
try:
|
||||
self.is_active = True
|
||||
self.at_start()
|
||||
self._start_task()
|
||||
return 1
|
||||
except Exception:
|
||||
#print ".. error when starting"
|
||||
logger.log_trace()
|
||||
self.is_active = False
|
||||
return 0
|
||||
else:
|
||||
# avoid starting over.
|
||||
#print "... Start cancelled (invalid start or already running)."
|
||||
return 0 # this is used by validate() for counting started scripts
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Called to stop the script from running.
|
||||
This also deletes the script.
|
||||
"""
|
||||
#print "stopping script %s" % self.key
|
||||
try:
|
||||
self.at_stop()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
if self.interval:
|
||||
try:
|
||||
self._stop_task()
|
||||
except Exception:
|
||||
pass
|
||||
self.is_running = False
|
||||
try:
|
||||
self.delete()
|
||||
except AssertionError:
|
||||
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
|
||||
|
||||
|
||||
#
|
||||
# Base Script - inherit from this
|
||||
#
|
||||
|
||||
class Script(ScriptClass):
|
||||
"""
|
||||
This is the class you should inherit from, it implements
|
||||
the hooks called by the script machinery.
|
||||
"""
|
||||
|
||||
def at_script_creation(self):
|
||||
"""
|
||||
Only called once, by the create function.
|
||||
"""
|
||||
self.key = "<unnamed>"
|
||||
self.desc = ""
|
||||
self.interval = 0
|
||||
self.start_delay = False
|
||||
self.repeats = 0
|
||||
self.persistent = False
|
||||
|
||||
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 True
|
||||
|
||||
def at_start(self):
|
||||
"""
|
||||
Called whenever the script is started, which for persistent
|
||||
scripts is at least once every server start.
|
||||
"""
|
||||
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 )
|
||||
"""
|
||||
pass
|
||||
|
||||
# Some useful default Script types
|
||||
|
||||
class DoNothing(Script):
|
||||
"An script that does nothing. Used as default."
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
self.key = "sys_do_nothing"
|
||||
self.desc = "This does nothing."
|
||||
self.persistent = False
|
||||
def is_valid(self):
|
||||
"This script disables itself as soon as possible"
|
||||
return False
|
||||
|
||||
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"
|
||||
#print "session check!"
|
||||
sessionhandler.check_all_sessions()
|
||||
|
||||
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"
|
||||
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+"
|
||||
channelhandler.CHANNELHANDLER.update()
|
||||
|
||||
class AddCmdSet(Script):
|
||||
"""
|
||||
This script permanently assigns a command set
|
||||
to an object. This is called automatically by the cmdhandler
|
||||
when an object is assigned a persistent cmdset.
|
||||
|
||||
To use, create this script, then assign to the two attributes
|
||||
'cmdset' and 'add_default' as appropriate:
|
||||
> from src.utils import create
|
||||
> script = create.create_script('src.scripts.scripts.AddCmdSet')
|
||||
> script.db.cmdset = 'game.gamesrc.commands.mycmdset.MyCmdSet'
|
||||
> script.db.add_default = False
|
||||
> obj.scripts.add(script)
|
||||
|
||||
"""
|
||||
def at_script_creation(self):
|
||||
"Setup the script"
|
||||
if not self.key:
|
||||
self.key = "add_cmdset"
|
||||
if not self.desc:
|
||||
self.desc = "Adds a cmdset to an object."
|
||||
self.persistent = True
|
||||
|
||||
# this needs to be assigned to upon creation.
|
||||
# It should be a string pointing to the right
|
||||
# cmdset module and cmdset class name, e.g.
|
||||
# 'examples.cmdset_redbutton.RedButtonCmdSet'
|
||||
# self.db.cmdset = <cmdset_path>
|
||||
# self.db.add_default = <bool>
|
||||
|
||||
def at_start(self):
|
||||
"Get cmdset and assign it."
|
||||
cmdset = self.db.cmdset
|
||||
if cmdset:
|
||||
if self.db.add_default:
|
||||
self.obj.cmdset.add_default(cmdset)
|
||||
else:
|
||||
self.obj.cmdset.add(cmdset)
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue