diff --git a/contrib/tutorial_world/mob.py b/contrib/tutorial_world/mob.py index 6c0f7a3373..b675da55c0 100644 --- a/contrib/tutorial_world/mob.py +++ b/contrib/tutorial_world/mob.py @@ -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")] diff --git a/contrib/tutorial_world/objects.py b/contrib/tutorial_world/objects.py index c2974283db..9e4fb5cbde 100644 --- a/contrib/tutorial_world/objects.py +++ b/contrib/tutorial_world/objects.py @@ -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.") diff --git a/contrib/tutorial_world/rooms.py b/contrib/tutorial_world/rooms.py index 14d66fd469..584ab11dbc 100644 --- a/contrib/tutorial_world/rooms.py +++ b/contrib/tutorial_world/rooms.py @@ -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): diff --git a/contrib/tutorial_world/scripts.py b/contrib/tutorial_world/scripts.py index 14cd9d6413..6ede52c657 100644 --- a/contrib/tutorial_world/scripts.py +++ b/contrib/tutorial_world/scripts.py @@ -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 diff --git a/game/gamesrc/scripts/examples/bodyfunctions.py b/game/gamesrc/scripts/examples/bodyfunctions.py index c4c0c3d73a..605d87df41 100644 --- a/game/gamesrc/scripts/examples/bodyfunctions.py +++ b/game/gamesrc/scripts/examples/bodyfunctions.py @@ -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() diff --git a/src/commands/default/building.py b/src/commands/default/building.py index fa0147bf3c..0546e9dd25 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -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." diff --git a/src/commands/default/system.py b/src/commands/default/system.py index 88517b4214..86d2df7171 100644 --- a/src/commands/default/system.py +++ b/src/commands/default/system.py @@ -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: diff --git a/src/scripts/manager.py b/src/scripts/manager.py index a6cf7418a5..417fc31279 100644 --- a/src/scripts/manager.py +++ b/src/scripts/manager.py @@ -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) diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index c10f922290..5364a6998f 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -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 = "" 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" diff --git a/src/server/session.py b/src/server/session.py index ce72ce77f7..69cae89818 100644 --- a/src/server/session.py +++ b/src/server/session.py @@ -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. diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 4a70da8b3d..7a344be967 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -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) diff --git a/src/utils/create.py b/src/utils/create.py index 2a51461f0e..fe49521289 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -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 #