Working on cleaning some strange behavior when trying to submitting faulty typeclasses to script system. Also fixing bugs here and there.

This commit is contained in:
Griatch 2011-07-03 21:01:06 +00:00
parent dae375d1c4
commit 6cb2b8b745
12 changed files with 301 additions and 141 deletions

View file

@ -99,16 +99,18 @@ class AttackTimer(Script):
"Called every self.interval seconds."
if self.obj.db.inactive:
return
if self.obj.db.roam_mode:
#print "attack timer: at_repeat", self.dbobj.id, self.ndb.twisted_task, id(self.ndb.twisted_task)
if self.obj.db.roam_mode:
self.obj.roam()
return
#return
elif self.obj.db.battle_mode:
#print "attack"
self.obj.attack()
return
elif self.obj.db.pursue_mode:
#print "pursue"
self.obj.pursue()
return
#return
else:
#dead mode. Wait for respawn.
dead_at = self.db.dead_at
@ -250,14 +252,13 @@ class Enemy(Mob):
those that previously attacked it.
"""
last_attacker = self.db.last_attacker
players = [obj for obj in self.location.contents if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS)]
players = [obj for obj in self.location.contents if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
if players:
# we found players in the room. Maybe we caught up with some, or some walked in on us
# before we had time to pursue them. Switch to battle mode.
self.battle_mode = True
self.roam_mode = False
self.pursue_mode = False
#self.attack()
else:
# find all possible destinations.
destinations = [ex.destination for ex in self.location.exits if ex.access(self, "traverse")]

View file

@ -242,7 +242,12 @@ class StateLightSourceOn(Script):
prematurely, this hook will also be called in that case.
"""
# calculate remaining burntime
time_burnt = time.time() - self.db.script_started
try:
time_burnt = time.time() - self.db.script_started
except TypeError:
# can happen if script_started is not defined
time_burnt = self.interval
burntime = self.interval - time_burnt
self.obj.db.burntime = burntime
if burntime <= 0:
@ -732,12 +737,7 @@ class CmdAttack(Command):
# should return True if target is defeated, False otherwise.
return target.at_hit(self.obj, self.caller, damage)
elif target.db.health:
target.db.health -= damage
if target.db.health <= 0:
# enemy is down!
return True
else:
return False
target.db.health -= damage
else:
# sorry, impossible to fight this enemy ...
self.caller.msg("The enemy seems unaffacted.")

View file

@ -466,7 +466,7 @@ class CmdLookBridge(Command):
def func(self):
"Looking around, including a chance to fall."
bridge_position = self.caller.db.tutorial_bridge_position
messages =("You are standing {wvery close to the the bridge's western foundation{n. If you go west you will be back on solid ground ...",
"The bridge slopes precariously where it extends eastwards towards the lowest point - the center point of the hang bridge.",
@ -486,8 +486,8 @@ class CmdLookBridge(Command):
self.caller.msg(message)
# there is a chance that we fall if we are on the western or central part of the bridge.
if bridge_position < 3 and random.random() < 0.2 and not self.caller.is_superuser:
# we fall on 20% of the times.
if bridge_position < 3 and random.random() < 0.05 and not self.caller.is_superuser:
# we fall on 5% of the times.
fexit = ObjectDB.objects.object_search(self.obj.db.fall_exit)
if fexit:
string = "\n Suddenly the plank you stand on gives way under your feet! You fall!"
@ -500,7 +500,8 @@ class CmdLookBridge(Command):
# at_object_leave hook manually (otherwise this is done by move_to()).
self.caller.msg("{r%s{n" % string)
self.obj.at_object_leave(self.caller, fexit)
self.caller.location = fexit[0] # stealth move, without any other hook calls.
self.caller.location = fexit[0] # stealth move, without any other hook calls.
self.obj.msg_contents("A plank gives way under %s's feet and they fall from the bridge!" % self.caller.key)
# custom help command
class CmdBridgeHelp(Command):

View file

@ -42,6 +42,7 @@ class IrregularEvent(Script):
rand = random.random()
if rand <= self.db.random_chance:
try:
#self.obj.msg_contents("irregular event for %s(#%i)" % (self.obj, self.obj.id))
self.obj.update_irregular()
except Exception:
pass

View file

@ -23,15 +23,17 @@ class BodyFunctions(Script):
self.key = "bodyfunction"
self.desc = "Adds various timed events to a character."
self.interval = 20 # seconds
#self.repeats = 5 # repeat only a certain number of times
self.start_delay = True # wait self.interval until first call
self.persistent = False
self.persistent = False
def at_repeat(self):
"""
This gets called every self.interval seconds. We make
a random check here so as to only return 30% of the time.
a random check here so as to only return 33% of the time.
"""
if random.random() > 0.66:
#)
if random.random() < 0.33:
# no message this time
return
rand = random.random()

View file

@ -1861,7 +1861,7 @@ class CmdScript(MuxCommand):
# adding a new script, and starting it
ok = obj.scripts.add(self.rhs, autostart=True)
if not ok:
string += "\nScript %s could not be added." % self.rhs
string += "\nScript %s could not be added and/or started." % self.rhs
else:
string = "Script successfully added and started."

View file

@ -165,7 +165,7 @@ class CmdScripts(MuxCommand):
if not hasattr(script, 'repeats') or not script.repeats:
table[5].append("--")
else:
table[5].append("%ss" % script.repeats)
table[5].append("%s" % script.repeats)
if script.persistent:
table[6].append("*")
else:

View file

@ -53,18 +53,22 @@ class ScriptManager(TypedObjectManager):
for script in scripts:
script.stop()
def remove_non_persistent(self):
def remove_non_persistent(self, obj=None):
"""
This cleans up the script database of all non-persistent
scripts. It is called every time the server restarts.
scripts, or only those on obj. It is called every time the server restarts
and
"""
nr_deleted = 0
for script in [script for script in self.get_all_scripts()
if not script.persistent]:
if obj:
to_stop = self.filter(db_persistent=False, db_obj=obj)
else:
to_stop = self.filter(db_persistent=False)
nr_deleted = to_stop.count()
for script in to_stop.filter(db_is_active=True):
script.stop()
nr_deleted += 1
return nr_deleted
for script in to_stop.filter(db_is_active=False):
script.delete()
return nr_deleted
def validate(self, scripts=None, obj=None, key=None, dbref=None,
init_mode=False):
@ -111,7 +115,7 @@ class ScriptManager(TypedObjectManager):
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()
nr_stopped += self.remove_non_persistent(obj=obj)
if dbref and self.dbref(dbref):
scripts = self.get_id(dbref)

View file

@ -6,6 +6,8 @@ It also defines a few common scripts.
"""
from time import time
from twisted.internet.defer import maybeDeferred
from twisted.internet.task import LoopingCall
from twisted.internet import task
from src.server.sessionhandler import SESSIONS
from src.typeclasses.typeclass import TypeClass
@ -18,54 +20,67 @@ from src.utils import logger
#
class ScriptClass(TypeClass):
"""
Base class for all Scripts.
Base class for scripts
"""
# private methods for handling timers.
# private methods
def __eq__(self, other):
"""
This has to be located at this level, having it in the
parent doesn't work.
"""
if other:
try:
return other.id == self.id
return False
except Exception:
return False
def _start_task(self):
"start the task runner."
if self.interval > 0:
#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()
"start task runner"
#print "_start_task: self.interval:", self.key, self.interval, self.dbobj.db_interval
self.ndb.twisted_task = LoopingCall(self._step_task)
self.ndb.twisted_task.start(self.interval, now=not self.start_delay)
self.ndb.time_last_called = int(time())
def _stop_task(self):
"stop the task runner"
if hasattr(self.ndb, "twisted_task"):
"stop task runner"
try:
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')
except Exception:
pass
def _step_err_callback(self, e):
"callback for runner errors"
cname = self.__class__.__name__
estring = "Script %s(#%i) of type '%s': at_repeat() error '%s'." % (self.key, self.id, cname, e.getErrorMessage())
try:
self.dbobj.db_obj.msg(estring)
except Exception:
pass
logger.log_errmsg(estring)
def _step_succ_callback(self):
"step task runner. No try..except needed due to defer wrap."
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()
self.at_repeat()
repeats = self.dbobj.db_repeats
if repeats <= 0:
pass # infinite repeat
elif repeats == 1:
self.stop()
return
else:
self.dbobj.db_repeats -= 1
self.ndb.time_last_called = int(time())
self.save()
def _step_task(self):
"step task"
try:
d = maybeDeferred(self._step_succ_callback)
d.addErrback(self._step_err_callback)
return d
except Exception:
logger.log_trace()
self._stop_task()
logger.log_trace()
def time_until_next_repeat(self):
"""
@ -75,9 +90,9 @@ class ScriptClass(TypeClass):
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:
try:
return max(0, (self.ndb.time_last_called + self.dbobj.db_interval) - int(time()))
except Exception:
return None
def start(self, force_restart=False):
@ -87,42 +102,37 @@ class ScriptClass(TypeClass):
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.
"""
#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:
if self.dbobj.db_is_active and not force_restart:
# script already runs.
return 0
if self.obj:
# check so the scripted object is valid and initalized
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()
# this means the object is not initialized.
self.dbobj.db_is_active = False
return 0
# try to start the script
try:
self.dbobj.db_is_active = True
self.dbobj.save()
self.at_start()
if self.dbobj.db_interval > 0:
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
return 1
except Exception:
logger.log_trace()
self.dbobj.db_is_active = False
self.dbobj.save()
return 0
def stop(self, kill=False):
"""
Called to stop the script from running.
@ -136,18 +146,21 @@ class ScriptClass(TypeClass):
self.at_stop()
except Exception:
logger.log_trace()
if self.interval:
if self.dbobj.db_interval > 0:
try:
self._stop_task()
except Exception:
pass
self.is_running = False
try:
self.delete()
self.dbobj.delete()
except AssertionError:
return 0
return 1
# hooks
def at_script_creation(self):
"placeholder"
pass
def is_valid(self):
"placeholder"
pass
@ -162,6 +175,153 @@ class ScriptClass(TypeClass):
pass
# class ScriptClassOld(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."
# print "self_interval:", self.interval
# if self.interval > 0:
# #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, 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
# if not kill:
# 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:
# return 0
# return 1
# 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
#
@ -178,9 +338,9 @@ class Script(ScriptClass):
"""
self.key = "<unnamed>"
self.desc = ""
self.interval = 0
self.interval = 0 # infinite
self.start_delay = False
self.repeats = 0
self.repeats = 0 # infinite
self.persistent = False
def is_valid(self):
@ -212,18 +372,17 @@ class Script(ScriptClass):
"""
pass
# Some useful default Script types
# Some useful default Script types used by Evennia.
class DoNothing(Script):
"An script that does nothing. Used as default."
"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
"Setup the script"
self.key = "sys_do_nothing"
self.desc = "This does nothing."
class CheckSessions(Script):
"Check sessions regularly."
@ -272,17 +431,9 @@ class ValidateChannelHandler(Script):
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)
to an object whenever it is started. This is not
used by the core system anymore, it's here mostly
as an example.
"""
def at_script_creation(self):
"Setup the script"

View file

@ -291,6 +291,9 @@ class SessionBase(object):
if character:
# normal operation.
character.execute_cmd(command_string)
#import cProfile
#cProfile.runctx("character.execute_cmd(command_string)",
# {"command_string":command_string,"character":character}, {}, "execute_cmd.profile")
else:
if self.logged_in:
# there is no character, but we are logged in. Use player instead.

View file

@ -629,6 +629,7 @@ class TypedObject(SharedMemoryModel):
cname = infochan.key
cmessage = "\n".join(["[%s]: %s" % (cname, line) for line in message.split('\n')])
infochan.msg(message)
logger.log_errmsg(cmessage)
else:
# no mudinfo channel is found. Log instead.
cmessage = "\n".join(["[NO MUDINFO CHANNEL]: %s" % line for line in message.split('\n')])
@ -639,8 +640,8 @@ class TypedObject(SharedMemoryModel):
else:
logger.log_trace(cmessage)
#path = self.db_typeclass_path
path = object.__getattribute__(self, 'db_typeclass_path')
#print "typeclass_loading:", id(self), path
errstring = ""
if not path:
@ -666,7 +667,7 @@ class TypedObject(SharedMemoryModel):
if callable(typeclass):
# don't return yet, we must cache this further down.
errstring = ""
break
break # leave test loop
elif hasattr(typeclass, '__file__'):
errstring += "\n%s seems to be just the path to a module. You need" % tpath
errstring += " to specify the actual typeclass name inside the module too."
@ -674,34 +675,32 @@ class TypedObject(SharedMemoryModel):
errstring += "\n%s" % typeclass # this will hold an error message.
if not callable(typeclass):
# Still not a valid import. Fallback to default.
# Still not a valid import. Fallback to default.
# Note that we don't save to this changed path! Fix the typeclass
# definition instead.
defpath = object.__getattribute__(self, "default_typeclass_path")
errstring += "\n\nUsing Default class '%s'." % defpath
self.db_typeclass_path = defpath
self.save()
logger.log_errmsg(errstring)
typeclass = object.__getattribute__(self, "_path_import")(defpath)
errmsg(errstring)
if not callable(typeclass):
# if typeclass still doesn't exist at this point, we're in trouble.
# fall back to hardcoded core class.
# fall back to hardcoded core class which is wrong for e.g. scripts/players etc.
errstring = " %s\n%s" % (typeclass, errstring)
errstring += " Default class '%s' failed to load." % defpath
defpath = "src.objects.objects.Object"
errstring += "\n Using Evennia's default class '%s'." % defpath
self.db_typeclass_path = defpath
self.save()
logger.log_errmsg(errstring)
errstring += "\n Using Evennia's default class '%s'." % defpath
typeclass = object.__getattribute__(self, "_path_import")(defpath)
errmsg(errstring)
else:
TYPECLASS_CACHE[path] = typeclass
TYPECLASS_CACHE[path] = typeclass
return typeclass
#@typeclass.deleter
def typeclass_del(self):
"Deleter. Allows for del self.typeclass (don't allow deletion)"
raise Exception("The typeclass property should never be deleted!")
# typeclass property
typeclass = property(typeclass_get, fdel=typeclass_del)

View file

@ -47,7 +47,7 @@ def create_object(typeclass, key=None, location=None,
from src.objects.models import ObjectDB
#print "in create_object", typeclass
if isinstance(typeclass, ObjectDB):
# this is already an object instance!
# this is already an objectdb instance!
new_db_object = typeclass
typeclass = new_db_object.typeclass
elif isinstance(typeclass, Object):
@ -147,11 +147,11 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True):
#print "in create_script", typeclass
from src.scripts.models import ScriptDB
if isinstance(typeclass, ScriptDB):
#print "this is already a script instance!", typeclass, typeclass.__class__
#print "this is already a scriptdb instance!"
new_db_object = typeclass
typeclass = new_db_object.typeclass
elif isinstance(typeclass, Script):
#print "this is already an object typeclass!"
#print "this is already an object typeclass!", typeclass, typeclass.__class__
new_db_object = typeclass.dbobj
typeclass = typeclass.__class__
else:
@ -159,19 +159,18 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True):
new_db_object = ScriptDB()
#new_db_object = ScriptDB()
if not callable(typeclass):
# try to load this in case it's a path
# try to load this in case it's a path
if typeclass:
typeclass = utils.to_unicode(typeclass)
new_db_object.typeclass_path = typeclass
typeclass = utils.to_unicode(typeclass)
new_db_object.db_typeclass_path = typeclass
new_db_object.save()
# this will load either the typeclass or the default one
typeclass = new_db_object.typeclass
new_db_object.save()
new_db_object.save()
# the typeclass is initialized
new_script = typeclass(new_db_object)
# store variables on the typeclass (which means
# it's actually transparently stored on the db object)
if not key:
if typeclass and hasattr(typeclass, '__name__'):
@ -200,7 +199,6 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True):
# a new created script should usually be started.
if autostart:
new_script.start()
return new_script
#