diff --git a/src/commands/cmdhandler.py b/src/commands/cmdhandler.py index 1ca93789ea..a7acc76b4d 100644 --- a/src/commands/cmdhandler.py +++ b/src/commands/cmdhandler.py @@ -84,6 +84,10 @@ def get_and_merge_cmdsets(caller): that this is only relevant for logged-in callers. """ # The calling object's cmdset + try: + caller.at_cmdset_get() + except Exception: + logger.log_trace() try: caller_cmdset = caller.cmdset.current except AttributeError: @@ -103,6 +107,12 @@ def get_and_merge_cmdsets(caller): # Gather all cmdsets stored on objects in the room and # also in the caller's inventory and the location itself local_objlist = location.contents_get(exclude=caller.dbobj) + caller.contents + [location] + for obj in local_objlist: + try: + # call hook in case we need to do dynamic changing to cmdset + obj.at_cmdset_get() + except Exception: + logger.log_trace() local_objects_cmdsets = [obj.cmdset.current for obj in local_objlist if (obj.cmdset.current and obj.locks.check(caller, 'call', no_superuser_bypass=True))] for cset in local_objects_cmdsets: diff --git a/src/commands/cmdsethandler.py b/src/commands/cmdsethandler.py index 9240be2018..1f3dcf7a00 100644 --- a/src/commands/cmdsethandler.py +++ b/src/commands/cmdsethandler.py @@ -409,6 +409,17 @@ class CmdSetHandler(object): self.obj.cmdset_storage = storage self.update() + def has_cmdset(self, cmdset_key, must_be_default=False): + """ + checks so the cmdsethandler contains a cmdset with the given key. + must_be_default - only match against the default cmdset. + """ + if must_be_default: + return self.cmdset_stack and self.cmdset_stack[0].key == cmdset_key + else: + return any([cmdset.key == cmdset_key for cmdset in self.cmdset_stack]) + + def all(self): """ Returns all cmdsets. diff --git a/src/commands/default/system.py b/src/commands/default/system.py index ea32af7071..5d4854c3d7 100644 --- a/src/commands/default/system.py +++ b/src/commands/default/system.py @@ -226,6 +226,8 @@ class CmdScripts(MuxCommand): else: string = "Stopping script '%s'." % scripts[0].key scripts[0].stop() + #import pdb + #pdb.set_trace() ScriptDB.objects.validate() #just to be sure all is synced else: # multiple matches. diff --git a/src/objects/models.py b/src/objects/models.py index c49f62cfc3..3148ff5b53 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -191,7 +191,6 @@ class ObjectDB(TypedObject): self.cmdset = CmdSetHandler(self) self.cmdset.update(init_mode=True) self.scripts = ScriptHandler(self) - self.scripts.validate(init_mode=True) self.nicks = ObjectNickHandler(self) # Wrapper properties to easily set database fields. These are @@ -804,6 +803,9 @@ class ObjectDB(TypedObject): if object.__getattribute__(self, 'player') and self.player: self.player.character = None self.player = None + + for script in self.scripts.all(): + script.stop() # if self.player: # self.player.user.is_active = False diff --git a/src/objects/objects.py b/src/objects/objects.py index 72055ffa79..2d82ae734b 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -75,12 +75,30 @@ class Object(TypeClass): """ pass + def basetype_posthook_setup(self): + """ + Called once, after basetype_setup and at_object_creation. This should generally not be overloaded unless + you are redefining how a room/exit/object works. It allows for basetype-like setup + after the object is created. An example of this is EXITs, who need to know keys, aliases, locks + etc to set up their exit-cmdsets. + """ + pass + def at_cache(self): """ - Called whenever this object is cached or reloaded. + Called whenever this object is cached to the idmapper backend. """ pass + def at_cmdset_get(self): + """ + Called just before cmdsets on this object are requested by the + command handler. If changes need to be done on the fly to the cmdset + before passing them on to the cmdhandler, this is the place to do it. + This is called also if the object currently have no cmdsets. + """ + pass + def at_first_login(self): """ Only called once, the very first @@ -423,11 +441,15 @@ class Exit(Object): """ Helper function for creating an exit command set + command. - Note that exitdbobj is an ObjectDB instance. This is necessary for - handling reloads and avoid tracebacks while the typeclass system - is rebooting. - """ + The command of this cmdset has the same name as the Exit object + and allows the exit to react when the player enter the exit's name, + triggering the movement between rooms. + Note that exitdbobj is an ObjectDB instance. This is necessary + for handling reloads and avoid tracebacks if this is called while + the typeclass system is rebooting. + """ + #print "Exit:create_exit_cmdset " class ExitCommand(command.Command): """ This is a command that simply cause the caller @@ -476,7 +498,6 @@ class Exit(Object): return exit_cmdset # Command hooks - def basetype_setup(self): """ Setup exit-security @@ -485,21 +506,26 @@ class Exit(Object): overload the default locks (it is called after this one). """ super(Exit, self).basetype_setup() - - # this is the fundamental thing for making the Exit work: - self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False) - # an exit should have a destination (this is replaced at creation time) - if self.dbobj.location: - self.destination = self.dbobj.location # setting default locks (overload these in at_object_creation() self.locks.add("puppet:false()") # would be weird to puppet an exit ... self.locks.add("traverse:all()") # who can pass through exit by default - self.locks.add("get:false()") # noone can pick up the exit - - def at_cache(self): - "Called when the typeclass is re-cached or reloaded. Should usually not be edited." - self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False) + self.locks.add("get:false()") # noone can pick up the exit + + # an exit should have a destination (this is replaced at creation time) + if self.dbobj.location: + self.destination = self.dbobj.location + + def at_cmdset_get(self): + """ + Called when the cmdset is requested from this object, just before the cmdset is + actually extracted. If no Exit-cmdset is cached, create it now. + """ + + if self.ndb.exit_reset or not self.cmdset.has_cmdset("_exitset", must_be_default=True): + # we are resetting, or no exit-cmdset was set. Create one dynamically. + self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False) + self.ndb.exit_reset = False # this and other hooks are what usually can be modified safely. diff --git a/src/scripts/manager.py b/src/scripts/manager.py index 4bd36cb52a..ea03c8a88c 100644 --- a/src/scripts/manager.py +++ b/src/scripts/manager.py @@ -88,8 +88,8 @@ class ScriptManager(TypedObjectManager): 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 + init_mode - This is used during server upstart. It causes non-persistent + scripts to be removed and persistent scripts to be force-restarted. This method also makes sure start any scripts it validates, @@ -117,26 +117,30 @@ class ScriptManager(TypedObjectManager): # This deletes all non-persistent scripts from database nr_stopped += self.remove_non_persistent(obj=obj) # turn off the activity flag for all remaining scripts - for script in self.all(): - script.is_active = False + scripts = self.get_all_scripts() + for script in scripts: + script.dbobj.is_active = False + + elif not scripts: + # normal operation + if dbref and self.dbref(dbref): + scripts = self.get_id(dbref) + elif obj: + scripts = self.get_all_scripts_on_obj(obj, key=key) + else: + scripts = self.get_all_scripts(key=key) #self.model.get_all_cached_instances() - 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.model.get_all_cached_instances()#get_all_scripts(key=key) if not scripts: + # no scripts available to validate 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)) + #print "validating %s (%i) (init_mode=%s)" % (script.key, id(script.dbobj), init_mode) nr_started += script.start(force_restart=init_mode) - #print "back from start." + #print "back from start. nr_started=", nr_started else: script.stop() nr_stopped += 1 diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index eeeafb77ef..271a91388a 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -44,9 +44,10 @@ class ScriptClass(TypeClass): def _stop_task(self): "stop task runner" try: - self.ndb.twisted_task.stop() + #print "stopping twisted task:", id(self.ndb.twisted_task), self.obj + self.ndb.twisted_task.stop() except Exception: - pass + logger.log_trace() def _step_err_callback(self, e): "callback for runner errors" cname = self.__class__.__name__ @@ -74,7 +75,7 @@ class ScriptClass(TypeClass): self.save() def _step_task(self): "step task" - try: + try: d = maybeDeferred(self._step_succ_callback) d.addErrback(self._step_err_callback) return d @@ -107,30 +108,30 @@ class ScriptClass(TypeClass): """ #print "Script %s (%s) start (active:%s, force:%s) ..." % (self.key, id(self.dbobj), # self.is_active, force_restart) - if self.dbobj.db_is_active and not force_restart: - # script already runs. + + if self.dbobj.is_active and not force_restart: + # script already runs and should not be restarted. return 0 - - if self.obj: + + obj = self.obj + if obj: # check so the scripted object is valid and initalized try: - dummy = object.__getattribute__(self.obj, 'cmdset') + dummy = object.__getattribute__(obj, 'cmdset') except AttributeError: # this means the object is not initialized. - self.dbobj.db_is_active = False + self.dbobj.is_active = False return 0 # try to start the script try: - self.dbobj.db_is_active = True - self.dbobj.save() + self.dbobj.is_active = True self.at_start() if self.dbobj.db_interval > 0: self._start_task() return 1 except Exception: logger.log_trace() - self.dbobj.db_is_active = False - self.dbobj.save() + self.dbobj.is_active = False return 0 def stop(self, kill=False): @@ -141,6 +142,8 @@ class ScriptClass(TypeClass): kill - don't call finishing hooks. """ #print "stopping script %s" % self.key + #import pdb + #pdb.set_trace() if not kill: try: self.at_stop() @@ -149,11 +152,13 @@ class ScriptClass(TypeClass): if self.dbobj.db_interval > 0: try: self._stop_task() - except Exception: + except Exception, e: + logger.log_trace("Stopping script %s(%s)" % (self.key, self.id)) pass try: self.dbobj.delete() except AssertionError: + logger.log_trace() return 0 return 1 @@ -175,7 +180,7 @@ class ScriptClass(TypeClass): pass -# class ScriptClassOld(TypeClass): +# class ScriptClass(TypeClass): # """ # Base class for all Scripts. # """ diff --git a/src/server/server.py b/src/server/server.py index 959f7d52a3..f830c568a4 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -22,7 +22,7 @@ from twisted.internet import protocol, reactor, defer from twisted.web import server, static from django.db import connection from django.conf import settings -from src.utils import reloads +from src.scripts.models import ScriptDB from src.server.models import ServerConfig from src.server.sessionhandler import SESSIONS from src.server import initial_setup @@ -99,7 +99,7 @@ class Evennia(object): channelhandler.CHANNELHANDLER.update() # init all global scripts - reloads.reload_scripts(init_mode=True) + ScriptDB.objects.validate(init_mode=True) # Make info output to the terminal. self.terminal_output() diff --git a/src/server/session.py b/src/server/session.py index 69cae89818..ea3cadbe14 100644 --- a/src/server/session.py +++ b/src/server/session.py @@ -182,7 +182,7 @@ class SessionBase(object): self.log('Logged in: %s' % self) # start (persistent) scripts on this object - reloads.reload_scripts(obj=self.player.character, init_mode=True) + reloads.reload_scripts(obj=self.player.character) #add session to connected list SESSIONS.add_loggedin_session(self) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index dff0621910..cd09972a09 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -210,7 +210,7 @@ class Attribute(SharedMemoryModel): "Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead""" logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self) lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del) - + # # @@ -747,8 +747,8 @@ class TypedObject(SharedMemoryModel): defpath = "src.objects.objects.Object" typeclass = object.__getattribute__(self, "_path_import")(defpath) if not silent: - errstring += " %s\n%s" % (typeclass, errstring) - errstring += " Default class '%s' failed to load." % failpath + #errstring = " %s\n%s" % (typeclass, errstring) + errstring = " Default class '%s' failed to load." % failpath errstring += "\n Using Evennia's default class '%s'." % defpath object.__getattribute__(self, "_display_errmsg")(errstring) if not callable(typeclass): @@ -1083,7 +1083,12 @@ class TypedObject(SharedMemoryModel): def all(self): return [val for val in self.__dict__.keys() if not val.startswith['_']] - pass + def __getattribute__(self, key): + # return None if no matching attribute was found. + try: + return object.__getattribute__(self, key) + except AttributeError: + return None self._ndb_holder = NdbHolder() return self._ndb_holder #@ndb.setter diff --git a/src/utils/create.py b/src/utils/create.py index 56a4cf50ac..58d5c55dbb 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -91,20 +91,20 @@ def create_object(typeclass, key=None, location=None, player.obj = new_object new_object.destination = destination - + # call the hook method. This is where all at_creation # customization happens as the typeclass stores custom - # things on its database object. + # things on its database object. new_object.basetype_setup() # setup the basics of Exits, Characters etc. new_object.at_object_creation() - - # custom-given variables override the hook + + # custom-given perms/locks overwrite hooks if permissions: new_object.permissions = permissions - if aliases: - new_object.aliases = aliases if locks: new_object.locks.add(locks) + if aliases: + new_object.aliases = aliases # perform a move_to in order to display eventual messages. if home: @@ -114,6 +114,10 @@ def create_object(typeclass, key=None, location=None, else: # rooms would have location=None. new_object.location = None + + # post-hook setup (mainly used by Exits) + new_object.basetype_posthook_setup() + new_object.save() return new_object diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 27b75af886..b03f94ca77 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -102,8 +102,9 @@ class SharedMemoryModel(Model): if instance._get_pk_val() is not None: cls.__instance_cache__[instance._get_pk_val()] = instance try: - object.__getattribute__(instance, "at_cache")() - except (TypeError, AttributeError): + object.__getattribute__(instance, "at_cache")() + except (TypeError, AttributeError), e: + #print e, instance._get_pk_val() pass #key = "%s-%s" % (cls, instance.pk) @@ -118,7 +119,7 @@ class SharedMemoryModel(Model): def _flush_cached_by_key(cls, key): - del cls.__instance_cache__[key] + del cls.__instance_cache__[key] _flush_cached_by_key = classmethod(_flush_cached_by_key) def flush_cached_instance(cls, instance): diff --git a/src/utils/reloads.py b/src/utils/reloads.py index 4c189cacf9..6b92872c90 100644 --- a/src/utils/reloads.py +++ b/src/utils/reloads.py @@ -128,23 +128,20 @@ def reload_modules(): # run through all objects in database, forcing re-caching. -def reload_scripts(scripts=None, obj=None, key=None, - dbref=None, init_mode=False): +def reload_scripts(scripts=None, obj=None, key=None, dbref=None): """ Run a validation of the script database. obj - only validate scripts on this object key - only validate scripts with this key dbref - only validate the script with this unique idref emit_to_obj - which object to receive error message - init_mode - during init-mode, non-persistent scripts are - cleaned out. All persistent scripts are force-started. """ nr_started, nr_stopped = ScriptDB.objects.validate(scripts=scripts, obj=obj, key=key, dbref=dbref, - init_mode=init_mode) + init_mode=False) if nr_started or nr_stopped: string = " Started %s script(s). Stopped %s invalid script(s)." % \ (nr_started, nr_stopped)