From 126e2ea61f21dbab17f30f260ab273f42e6e7241 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 20 Mar 2011 19:45:56 +0000 Subject: [PATCH] OBS: You need to resync your database! Moved cmdsets into the database rather than being dependent on scripts. Moved the creation of the cmdset- and cmdset-handlers into ObjectDB.__init__ rather than bootstrapping it from the typeclass. Added some more script functionality for testing, includong the @script command for assigning a script to an object. --- .../gamesrc/scripts/examples/bodyfunctions.py | 62 +++++++ src/commands/cmdhandler.py | 5 +- src/commands/cmdsethandler.py | 172 +++++++++--------- src/commands/default/building.py | 111 ++++++++--- src/commands/default/cmdset_default.py | 1 + src/commands/default/system.py | 16 +- src/locks/lockhandler.py | 9 +- src/objects/models.py | 37 +++- src/objects/objects.py | 56 ++---- src/scripts/manager.py | 8 +- src/scripts/models.py | 1 - src/scripts/scripthandler.py | 54 +++--- src/scripts/scripts.py | 3 +- src/typeclasses/models.py | 3 +- src/typeclasses/typeclass.py | 7 + src/utils/create.py | 20 +- src/utils/reloads.py | 21 ++- 17 files changed, 370 insertions(+), 216 deletions(-) create mode 100644 game/gamesrc/scripts/examples/bodyfunctions.py diff --git a/game/gamesrc/scripts/examples/bodyfunctions.py b/game/gamesrc/scripts/examples/bodyfunctions.py new file mode 100644 index 0000000000..abad6dfd73 --- /dev/null +++ b/game/gamesrc/scripts/examples/bodyfunctions.py @@ -0,0 +1,62 @@ +""" +Example script for testing. This adds a simple timer that +has your character make observations and noices at irregular +intervals. + +To test, use + @script me = examples.bodyfunctions.BodyFunctions + +The script will only send messages to the object it +is stored on, so make sure to put it on yourself +or you won't see any messages! + +""" +import random +from game.gamesrc.scripts.basescript import Script + + +class BodyFunctions(Script): + """ + This class defines the script itself + """ + + def at_script_creation(self): + self.key = "bodyfunction" + self.desc = "Adds various timed events to a character." + self.interval = 20 # seconds + self.start_delay # wait self.interval until first call + 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. + """ + if random.random() > 0.66: + # no message this time + return + rand = random.random() + # return a random message + if rand < 0.1: + string = "You tap your foot, looking around." + elif rand < 0.2: + string = "You have an itch. Hard to reach too." + elif rand < 0.3: + string = "You think you hear someone behind you. ... but when you look there's noone there." + elif rand < 0.4: + string = "You inspect your fingernails. Nothing to report." + elif rand < 0.5: + string = "You cough discreetly into your hand." + elif rand < 0.6: + string = "You scratch your head, looking around." + elif rand < 0.7: + string = "You blink, forgetting what it was you were going to do." + elif rand < 0.8: + string = "You feel lonely all of a sudden." + elif rand < 0.9: + string = "You get a great idea. Of course you won't tell anyone." + else: + string = "You suddenly realize how much you love Evennia!" + + # echo the message to the object + self.obj.msg(string) diff --git a/src/commands/cmdhandler.py b/src/commands/cmdhandler.py index 3e1d00c2bb..dc1e4507c0 100644 --- a/src/commands/cmdhandler.py +++ b/src/commands/cmdhandler.py @@ -114,10 +114,7 @@ def get_and_merge_cmdsets(caller): local_objlist = location.contents + caller.contents local_objects_cmdsets = [obj.cmdset.current for obj in local_objlist - if obj.cmdset.outside_access] - # print "used objs: %s" % ([obj.name - # for obj in local_objlist - # if obj.cmdset.outside_access]) + if obj.locks.check(caller, 'call', no_superuser_bypass=True)] # Merge all command sets into one # (the order matters, the higher-prio cmdsets are merged last) diff --git a/src/commands/cmdsethandler.py b/src/commands/cmdsethandler.py index d4a9ebb58d..a5323e49b3 100644 --- a/src/commands/cmdsethandler.py +++ b/src/commands/cmdsethandler.py @@ -65,9 +65,7 @@ the 'Fishing' set. Fishing from a boat? No problem! """ import traceback from src.utils import logger -from src.utils import create from src.commands.cmdset import CmdSet -from src.scripts.scripts import AddCmdSet CACHED_CMDSETS = {} @@ -128,7 +126,7 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False): class CmdSetHandler(object): """ - The CmdSetHandler is always stored on an object, supplied as the argument. + The CmdSetHandler is always stored on an object, this object is supplied as an argument. The 'current' cmdset is the merged set currently active for this object. This is the set the game engine will retrieve when determining which @@ -137,35 +135,29 @@ class CmdSetHandler(object): the 'current' cmdset. """ - def __init__(self, obj, outside_access=True): + def __init__(self, obj): """ This method is called whenever an object is recreated. obj - this is a reference to the game object this handler belongs to. - outside_access - if false, the cmdparser will only retrieve - this cmdset when it is its obj itself that is calling for it. - (this is is good to use for player objects, since they - should not have access to the cmdsets of other player - objects). """ self.obj = obj - # if false, only the object itself may use this handler - # (this should be set especially by character objects) - self.outside_access = outside_access # the id of the "merged" current cmdset for easy access. self.key = None # this holds the "merged" current command set self.current = None - # this holds a history of CommandSets + # this holds a history of CommandSets self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")] # this tracks which mergetypes are actually in play in the stack self.mergetype_stack = ["Union"] - self.update() - #print "cmdsethandler init. id:%s, obj:%s, cmdsetstack:%s " % (id(self), self.obj.key, [cmdset.key for cmdset in self.cmdset_stack]) - + # the subset of the cmdset_paths that are to be stored in the database + self.permanent_paths = [""] + + # self.update(init_mode=True) is then called from the object __init__. + def __str__(self): "Display current commands" @@ -180,8 +172,8 @@ class CmdSetHandler(object): if mergetype != cmdset.mergetype: mergetype = "%s^" % (mergetype) string += "\n %i: <%s (%s, prio %i)>: %s" % \ - (snum, cmdset.key, mergetype, - cmdset.priority, cmdset) + (snum, cmdset.key, mergetype, + cmdset.priority, cmdset) string += "\n (combining %i cmdsets):" % (num+1) else: string += "\n " @@ -195,22 +187,40 @@ class CmdSetHandler(object): mergetype, self.current) return string.strip() - def update(self): + def update(self, init_mode=False): """ Re-adds all sets in the handler to have an updated current set. + + init_mode is used right after this handler was + created; it imports all permanent cmdsets from db. """ - updated = None + if init_mode: + self.cmdset_stack = [] + # reimport all permanent cmdsets + self.permanent_paths = self.obj.cmdset_storage + new_permanent_paths = [] + + for pos, path in enumerate(self.permanent_paths): + if pos == 0 and not path: + self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")] + else: + cmdset = self.import_cmdset(path) + if cmdset: + self.cmdset_stack.append(cmdset) + + # merge the stack into a new merged cmdset + new_current = None self.mergetype_stack = [] for cmdset in self.cmdset_stack: try: # for cmdset's '+' operator, order matters. - updated = cmdset + updated + new_current = cmdset + new_current except TypeError: continue - self.mergetype_stack.append(updated.actual_mergetype) - self.current = updated - + self.mergetype_stack.append(new_current.actual_mergetype) + self.current = new_current + def import_cmdset(self, cmdset_path, emit_to_obj=None): """ load a cmdset from a module. @@ -247,23 +257,17 @@ class CmdSetHandler(object): cmdset = cmdset(self.obj) elif isinstance(cmdset, basestring): # this is (maybe) a python path. Try to import from cache. - cmdset = self.import_cmdset(cmdset, emit_to_obj) + cmdset = self.import_cmdset(cmdset)#, emit_to_obj) if cmdset: self.cmdset_stack.append(cmdset) + if permanent: + # store the path permanently + self.permanent_paths.append(cmdset.path) + self.obj.cmdset_storage = self.permanent_paths + else: + # store an empty entry and don't save (this makes it easy to delete). + self.permanent_paths.append("") self.update() - if permanent: - # create a script to automatically add this cmdset at - # startup. We don't start it here since the cmdset was - # already added above. - try: - cmdset = "%s.%s" % (cmdset.__module__, cmdset.__name__) - except Exception: - logger.log_trace() - return - script = create.create_script(AddCmdSet) - script.db.cmdset = cmdset - script.db.add_default = False - self.obj.scripts.add(script, autostart=False) def add_default(self, cmdset, emit_to_obj=None, permanent=False): """ @@ -281,70 +285,74 @@ class CmdSetHandler(object): cmdset = cmdset(self.obj) elif isinstance(cmdset, basestring): # this is (maybe) a python path. Try to import from cache. - cmdset = self.import_cmdset(cmdset, emit_to_obj) + cmdset = self.import_cmdset(cmdset) if cmdset: - self.cmdset_stack[0] = cmdset - self.mergetype_stack[0] = cmdset.mergetype - self.update() - #print "add_default:", permanent - if permanent: - # create a script to automatically add this cmdset at - # startup. We don't start it here since the cmdset was - # already added above. - try: - cmdset = "%s.%s" % (cmdset.__module__, cmdset.__class__.__name__) - except Exception: - #print traceback.format_exc() - logger.log_trace() - return - #print "cmdset to add:", cmdset - script = create.create_script(AddCmdSet) - script.db.cmdset = cmdset - script.db.add_default = True - self.obj.scripts.add(script, key="add_default_cmdset", autostart=False) + if self.cmdset_stack: + self.cmdset_stack[0] = cmdset + self.mergetype_stack.insert[0] = cmdset.mergetype + else: + self.cmdset_stack = [cmdset] + self.mergetype_stack = cmdset.mergetype + + if permanent: + if self.permanent_paths: + self.permanent_paths[0] = cmdset.path + else: + self.permanent_paths = [cmdset.path] + self.obj.cmdset_storage = self.permanent_paths + else: + if self.permanent_paths: + self.permanent_paths[0] = "" + else: + self.permanent_paths = [""] + self.update() - def delete(self, key_or_class=None): + def delete(self, cmdset=None): """ - Remove a cmdset from the handler. If a key is supplied, - it attempts to remove this. If no key is given, + Remove a cmdset from the handler. + + cmdset can be supplied either as a cmdset-key, + an instance of the CmdSet or a python path + to the cmdset. If no key is given, the last cmdset in the stack is removed. Whenever the cmdset_stack changes, the cmdset is updated. The default cmdset (first entry in stack) is never removed - remove it explicitly with delete_default. - key_or_class - a specific cmdset key or a cmdset class (in - the latter case, *all* cmdsets of this class - will be removed from handler!) """ if len(self.cmdset_stack) < 2: # don't allow deleting default cmdsets here. return - if not key_or_class: + if not cmdset: # remove the last one in the stack (except the default position) self.cmdset_stack.pop() - else: - # argument key is given, is it a key or a class? - - default_cmdset = self.cmdset_stack[0] - - if callable(key_or_class) and hasattr(key_or_class, '__name__'): - # this is a callable with __name__ - we assume it's a class - self.cmdset_stack = [cmdset for cmdset in self.cmdset_stack[1:] - if cmdset.__class__.__name__ != key_or_class.__name__] + self.permanent_paths.pop() + else: + # try it as a callable + if callable(cmdset) and hasattr(cmdset, 'path'): + indices = [i+1 for i, cset in enumerate(self.cmdset_stack[1:]) if cset.path == cmdset.path] else: - # try it as a string - self.cmdset_stack = [cmdset for cmdset in self.cmdset_stack[1:] - if cmdset.key != key_or_class] - - self.cmdset_stack.insert(0, default_cmdset) - + # try it as a path or key + indices = [i+1 for i, cset in enumerate(self.cmdset_stack[1:]) if cset.path == cmdset or cset.key == cmdset] + + for i in indices: + del self.cmdset_stack[i] + del self.permanent_paths[i] + self.obj.cmdset_storage = self.permanent_paths + # re-sync the cmdsethandler. self.update() def delete_default(self): "This explicitly deletes the default cmdset. It's the only command that can." - self.cmdset_stack[0] = CmdSet(cmdsetobj=self.obj, key="Empty") + if self.cmdset_stack: + self.cmdset_stack[0] = CmdSet(cmdsetobj=self.obj, key="Empty") + self.permanent_paths[0] = "" + else: + self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")] + self.permanent_paths = [""] + self.obj.cmdset_storage = self.permanent_paths self.update() def all(self): @@ -360,6 +368,8 @@ class CmdSetHandler(object): """ self.cmdset_stack = [self.cmdset_stack[0]] self.mergetype_stack = [self.cmdset_stack[0].mergetype] + self.permanent_paths[0] = [self.permanent_paths[0]] + self.obj.cmdset_storage = self.permanent_paths self.update() def reset(self): diff --git a/src/commands/default/building.py b/src/commands/default/building.py index be5c18e122..c7d9586931 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -750,6 +750,40 @@ class CmdLink(MuxCommand): # give feedback caller.msg(string) +class CmdUnLink(CmdLink): + """ + @unlink - unconnect objects + + Usage: + @unlink + + Unlinks an object, for example an exit, disconnecting + it from whatever it was connected to. + """ + # this is just a child of CmdLink + + key = "@unlink" + locks = "cmd:perm(unlink) or perm(Builders)" + help_key = "Building" + + def func(self): + """ + All we need to do here is to set the right command + and call func in CmdLink + """ + + caller = self.caller + + if not self.args: + caller.msg("Usage: @unlink ") + return + + # This mimics '@link = ' which is the same as @unlink + self.rhs = "" + + # call the @link functionality + super(CmdUnLink, self).func() + class CmdListCmdSets(MuxCommand): """ @@ -1585,36 +1619,67 @@ class CmdTeleport(MuxCommand): caller.msg("Teleported.") -class CmdUnLink(CmdLink): +class CmdScript(MuxCommand): """ - @unlink - unconnect objects + attach scripts Usage: - @unlink + @script[/switch] = + + Switches: + start - start a previously added script + stop - stop a previously added script - Unlinks an object, for example an exit, disconnecting - it from whatever it was connected to. + Attaches the given script to the object and starts it. Script path can be given + from the base location for scripts as given in settings. + If stopping/starting an already existing script, the script's key + can be given instead (if giving a path, *all* scripts with this path + on will be affected). """ - # this is just a child of CmdLink + + key = "@script" + aliases = "@addscript" - key = "@unlink" - locks = "cmd:perm(unlink) or perm(Builders)" - help_key = "Building" + locks = "cmd:perm(script) or perm(Wizards)" def func(self): - """ - All we need to do here is to set the right command - and call func in CmdLink - """ - - caller = self.caller - - if not self.args: - caller.msg("Usage: @unlink ") - return + "Do stuff" - # This mimics '@link = ' which is the same as @unlink - self.rhs = "" + caller = self.caller + + if not self.rhs: + string = "Usage: @script[/switch] = " + caller.msg(string) + return - # call the @link functionality - super(CmdUnLink, self).func() + inp = self.rhs + if not inp.startswith('src.') and not inp.startswith('game.'): + # append the default path. + inp = "%s.%s" % (settings.BASE_SCRIPT_PATH, inp) + + obj = caller.search(self.lhs) + if not obj: + return + string = "" + if "stop" in self.switches: + # we are stopping an already existing script + ok = obj.scripts.stop(inp) + if not ok: + string = "Script %s could not be stopped. Does it exist?" % inp + else: + string = "Script stopped and removed from object." + if "start" in self.switches: + # we are starting an already existing script + ok = obj.scripts.start(inp) + if not ok: + string = "Script %s could not be (re)started." % inp + else: + string = "Script started successfully." + if not self.switches: + # adding a new script, and starting it + ok = obj.scripts.add(inp, autostart=True) + if not ok: + string = "Script %s could not be added." % inp + else: + string = "Script successfully added and started." + caller.msg(string) diff --git a/src/commands/default/cmdset_default.py b/src/commands/default/cmdset_default.py index dc204653ec..5b8879c34b 100644 --- a/src/commands/default/cmdset_default.py +++ b/src/commands/default/cmdset_default.py @@ -77,6 +77,7 @@ class DefaultCmdSet(CmdSet): self.add(building.CmdExamine()) self.add(building.CmdTypeclass()) self.add(building.CmdLock()) + self.add(building.CmdScript()) # Comm commands self.add(comms.CmdAddCom()) diff --git a/src/commands/default/system.py b/src/commands/default/system.py index 4f714174bb..b69ca7d25b 100644 --- a/src/commands/default/system.py +++ b/src/commands/default/system.py @@ -70,7 +70,9 @@ class CmdPy(MuxCommand): Usage: @py - In this limited python environment. + In this limited python environment, there are a + few variables made available to give access to + the system. available_vars: 'self','me' : caller 'here' : caller.location @@ -82,7 +84,7 @@ class CmdPy(MuxCommand): 'ConfigValue' ConfigValue class only two variables are defined: 'self'/'me' which refers to one's - own object, and 'here' which refers to the current + own object, and 'here' which refers to self's current location. """ key = "@py" @@ -102,10 +104,11 @@ class CmdPy(MuxCommand): return # create temporary test objects for playing with script = create.create_script("src.scripts.scripts.DoNothing", - 'testscript') + key = 'testscript') obj = create.create_object("src.objects.objects.Object", - 'testobject') + key='testobject') conf = ConfigValue() # used to access conf values + available_vars = {'self':caller, 'me':caller, 'here':caller.location, @@ -131,7 +134,10 @@ class CmdPy(MuxCommand): ret = "\n".join("<<< %s" % line for line in errlist if line) caller.msg(ret) obj.delete() - script.delete() + try: + script.delete() + except AssertionError: # this is a strange thing; the script looses its id somehow..? + pass class CmdScripts(MuxCommand): """ diff --git a/src/locks/lockhandler.py b/src/locks/lockhandler.py index dc7503636f..501c3ac0b2 100644 --- a/src/locks/lockhandler.py +++ b/src/locks/lockhandler.py @@ -287,7 +287,7 @@ class LockHandler(object): """ self.reset_flag = True - def check(self, accessing_obj, access_type, default=False): + def check(self, accessing_obj, access_type, default=False, no_superuser_bypass=False): """ Checks a lock of the correct type by passing execution off to the lock function(s). @@ -295,14 +295,17 @@ class LockHandler(object): accessing_obj - the object seeking access access_type - the type of access wanted default - if no suitable lock type is found, use this + no_superuser_bypass - don't use this unless you really, really need to + """ if self.reset_flag: # rebuild cache self._cache_locks(self.obj.lock_storage) self.reset_flag = False - if (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'is_superuser') and accessing_obj.player.is_superuser) \ - or (hasattr(accessing_obj, 'get_player') and (accessing_obj.get_player()==None or accessing_obj.get_player().is_superuser)): + if (not no_superuser_bypass and (hasattr(accessing_obj, 'player') + and hasattr(accessing_obj.player, 'is_superuser') and accessing_obj.player.is_superuser) + or (hasattr(accessing_obj, 'get_player') and (accessing_obj.get_player()==None or accessing_obj.get_player().is_superuser))): # we grant access to superusers and also to protocol instances that not yet has any player assigned to them (the # latter is a safety feature since superuser cannot be authenticated at some point during the connection). return True diff --git a/src/objects/models.py b/src/objects/models.py index a76cf8a87f..602f440a7b 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -22,7 +22,8 @@ from src.typeclasses.models import Attribute, TypedObject from src.typeclasses.typeclass import TypeClass from src.objects.manager import ObjectManager from src.config.models import ConfigValue - +from src.commands.cmdsethandler import CmdSetHandler +from src.scripts.scripthandler import ScriptHandler from src.utils import logger from src.utils.utils import is_iter @@ -216,18 +217,24 @@ class ObjectDB(TypedObject): # a safety location, this usually don't change much. db_home = models.ForeignKey('self', related_name="homes_set", blank=True, null=True) + # database storage of persistant cmdsets. + db_cmdset_storage = models.TextField(null=True) # Database manager objects = ObjectManager() # Add the object-specific handlers - # (scripts and cmdset must be added from - # typeclass, so not added here) + def __init__(self, *args, **kwargs): "Parent must be initialized first." TypedObject.__init__(self, *args, **kwargs) + # handlers + self.cmdset = CmdSetHandler(self) + self.cmdset.update(init_mode=True) + self.scripts = ScriptHandler(self) + self.scripts.validate(init_mode=True) self.nicks = NickHandler(self) - + # 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() @@ -381,6 +388,28 @@ class ObjectDB(TypedObject): query.delete() aliases = property(aliases_get, aliases_set, aliases_del) + # cmdset_storage property + #@property + def cmdset_storage_get(self): + "Getter. Allows for value = self.name. Returns a list of cmdset_storage." + if self.db_cmdset_storage: + return [path.strip() for path in self.db_cmdset_storage.split(',')] + return [] + #@cmdset_storage.setter + def cmdset_storage_set(self, value): + "Setter. Allows for self.name = value. Stores as a comma-separated string." + if is_iter(value): + value = ",".join([str(val).strip() for val in value]) + self.db_cmdset_storage = value + self.save() + #@cmdset_storage.deleter + def cmdset_storage_del(self): + "Deleter. Allows for del self.name" + self.db_cmdset_storage = "" + self.save() + cmdset_storage = property(cmdset_storage_get, cmdset_storage_set, cmdset_storage_del) + + class Meta: "Define Django meta options" verbose_name = "Object" diff --git a/src/objects/objects.py b/src/objects/objects.py index ab33a68c8c..26e630a0d4 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -15,12 +15,9 @@ That an object is controlled by a player/user is just defined by its they control by simply linking to a new object's user property. """ -from django.conf import settings from src.typeclasses.typeclass import TypeClass -from src.commands.cmdsethandler import CmdSetHandler -from src.scripts.scripthandler import ScriptHandler from src.objects.exithandler import EXITHANDLER -from src.utils import utils + # # Base class to inherit from. @@ -33,33 +30,6 @@ class Object(TypeClass): objects in the game. """ - def __init__(self, dbobj): - """ - Set up the Object-specific handlers. Note that we must - be careful to run the parent's init function too - or typeclasses won't work! - """ - # initialize typeclass system. This sets up self.dbobj. - super(Object, self).__init__(dbobj) - # create the command- and scripthandlers as needed - try: - dummy = object.__getattribute__(dbobj, 'cmdset') - create_cmdset = type(dbobj.cmdset) != CmdSetHandler - except AttributeError: - create_cmdset = True - try: - dummy = object.__getattribute__(dbobj, 'scripts') - create_scripts = type(dbobj.scripts) != ScriptHandler - except AttributeError: - create_scripts = True - - if create_cmdset: - dbobj.cmdset = CmdSetHandler(dbobj) - if utils.inherits_from(self, settings.BASE_CHARACTER_TYPECLASS) or utils.inherits_from(self, Character): - dbobj.cmdset.outside_access = False - if create_scripts: - dbobj.scripts = ScriptHandler(dbobj) - def __eq__(self, other): """ This has be located at this level, having it in the @@ -87,11 +57,12 @@ class Object(TypeClass): dbref = self.dbobj.dbref - self.locks.add("control:id(%s) or perm(Immortals)" % dbref) - self.locks.add("examine:perm(Builders)") - self.locks.add("edit:perm(Wizards)") - self.locks.add("delete:perm(Wizards)") - self.locks.add("get:all()") + self.locks.add("control:id(%s) or perm(Immortals)" % dbref) # edit locks/permissions, delete + self.locks.add("examine:perm(Builders)") # examine properties + self.locks.add("edit:perm(Wizards)") # edit properties/attributes + self.locks.add("delete:perm(Wizards)") # delete object + self.locks.add("get:all()") # pick up object + self.locks.add("call:true()") # allow to call commands on this object def at_object_creation(self): """ @@ -341,11 +312,15 @@ class Character(Object): Setup character-specific security """ super(Character, self).basetype_setup() - self.locks.add("puppet:id(%s) or perm(Immortals); get:false()" % self.dbobj.dbref) + self.locks.add("puppet:id(%s) or perm(Immortals)" % self.dbobj.dbref) # who may become this object's player + self.locks.add("get:false()") # noone can pick up the character + self.locks.add("call:false()") # no commands can be called on character # add the default cmdset - from settings import CMDSET_DEFAULT + from settings import CMDSET_DEFAULT self.cmdset.add_default(CMDSET_DEFAULT, permanent=True) + # no other character should be able to call commands on the Character. + self.cmdset.outside_access = False def at_object_creation(self): """ @@ -399,11 +374,12 @@ class Exit(Object): """ # the lock is open to all by default super(Exit, self).basetype_setup() - self.locks.add("traverse:all(); get:false()") + self.locks.add("traverse:all()") # who can pass through exit + self.locks.add("get:false()") # noone can pick up the exit def at_object_creation(self): """ - Another example just for show; the _destination attribute + An example just for show; the _destination attribute is usually set at creation time, not as part of the class definition (unless you want an entire class of exits all leadning to the same hard-coded place ...) diff --git a/src/scripts/manager.py b/src/scripts/manager.py index a754fc3fcc..23c7d025cd 100644 --- a/src/scripts/manager.py +++ b/src/scripts/manager.py @@ -20,14 +20,14 @@ class ScriptManager(TypedObjectManager): return [] scripts = self.filter(db_obj=obj) if key: - return [script for script in scripts if script.key == key] + return scripts.filter(db_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. + scripts with a certain key/dbref or path. """ if key: dbref = self.dbref(key) @@ -39,7 +39,7 @@ class ScriptManager(TypedObjectManager): # not a dbref. Normal key search scripts = self.filter(db_key=key) else: - scripts = self.all() + scripts = list(self.all()) return scripts def delete_script(self, dbref): @@ -120,7 +120,7 @@ class ScriptManager(TypedObjectManager): elif obj: scripts = self.get_all_scripts_on_obj(obj, key=key) else: - scripts = self.get_all_scripts(key=key) + scripts = self.model.get_all_cached_instances()#get_all_scripts(key=key) if not scripts: VALIDATE_ITERATION -= 1 return None, None diff --git a/src/scripts/models.py b/src/scripts/models.py index 04b65b6f0c..da71619a9b 100644 --- a/src/scripts/models.py +++ b/src/scripts/models.py @@ -28,7 +28,6 @@ from django.conf import settings from django.db import models from src.typeclasses.models import Attribute, TypedObject from src.scripts.manager import ScriptManager -#from src.locks.lockhandler import LockHandler #------------------------------------------------------------ # diff --git a/src/scripts/scripthandler.py b/src/scripts/scripthandler.py index 608409e897..6b9e13b2f8 100644 --- a/src/scripts/scripthandler.py +++ b/src/scripts/scripthandler.py @@ -24,22 +24,6 @@ class ScriptHandler(object): """ 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) @@ -72,39 +56,51 @@ class ScriptHandler(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) + return False + return True - def start(self, scriptkey): + def start(self, scriptid): """ Find an already added script and force-start it """ - scripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=scriptkey) + scripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=scriptid) + num = 0 for script in scripts: - script.start() + num += script.start() + return num - def delete(self, scriptkey): + def delete(self, scriptid): """ Forcibly delete a script from this object. + + scriptid can be a script key or the path to a script (in the + latter case all scripts with this path will be deleted!) + """ - delscripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=scriptkey) + delscripts = ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=scriptid) + if not delscripts: + delscripts = [script for script in ScriptDB.objects.get_all_scripts_on_obj(self.obj) if script.path == scriptid] + num = 0 for script in delscripts: - script.stop() + num += script.stop() + return num - def stop(self, scriptkey): + def stop(self, scriptid): """ - Alias for delete. + Alias for delete. scriptid can be a script key or a script path string. """ - self.delete(scriptkey) + return self.delete(scriptid) - def all(self, scriptkey=None): + def all(self, scriptid=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) + return ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=scriptid) - def validate(self): + def validate(self, init_mode=False): """ Runs a validation on this object's scripts only. This should be called regularly to crank the wheels. """ - ScriptDB.objects.validate(obj=self.obj) + ScriptDB.objects.validate(obj=self.obj, init_mode=init_mode) diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index af5a205889..8148174354 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -142,7 +142,8 @@ class ScriptClass(TypeClass): try: self.delete() except AssertionError: - pass + return 0 + return 1 def is_valid(self): "placeholder" diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index fa736523c7..2412ebbad9 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -753,7 +753,8 @@ class TypedObject(SharedMemoryModel): for nattr in self.ndb.all(): del nattr - # run hook for this new typeclass + # run hooks for this new typeclass + new_typeclass.basetype_setup() new_typeclass.at_object_creation() diff --git a/src/typeclasses/typeclass.py b/src/typeclasses/typeclass.py index ef98d8fb50..c18d81e6a9 100644 --- a/src/typeclasses/typeclass.py +++ b/src/typeclasses/typeclass.py @@ -35,6 +35,13 @@ class MetaTypeClass(type): printed in a nicer way (it might end up having no name at all otherwise due to the magics being done with get/setattribute). """ + def __init__(mcs, *args, **kwargs): + """ + Adds some features to typeclassed objects + """ + super(MetaTypeClass, mcs).__init__(*args, **kwargs) + mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__) + def __str__(cls): return "%s" % cls.__name__ diff --git a/src/utils/create.py b/src/utils/create.py index 2850df8e69..4b5dbc1809 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -170,13 +170,13 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True): new_script = typeclass(new_db_object) # store variables on the typeclass (which means # it's actually transparently stored on the db object) - if key: - new_db_object.name = key - else: + + + if not key: if typeclass and hasattr(typeclass, '__name__'): - new_db_object.name = "%s" % typeclass.__name__ + new_script.key = "%s" % typeclass.__name__ else: - new_db_object.name = "#%i" % new_db_object.id + new_script.key = "#%i" % new_db_object.id # call the hook method. This is where all at_creation # customization happens as the typeclass stores custom @@ -184,6 +184,9 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True): new_script.at_script_creation() # custom-given variables override the hook + if key: + new_script.key = key + if locks: new_script.locks.add(locks) @@ -191,12 +194,9 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True): try: new_script.obj = obj except ValueError: - new_script.obj = obj.dbobj - - new_script.save() + new_script.obj = obj.dbobj - # a new created script should always be started, so - # we do this now. + # a new created script should usually be started. if autostart: new_script.start() diff --git a/src/utils/reloads.py b/src/utils/reloads.py index 9b16798f4c..a0e458bfc2 100644 --- a/src/utils/reloads.py +++ b/src/utils/reloads.py @@ -33,11 +33,11 @@ def start_reload_loop(): def run_loop(): "" cemit_info('-'*50) - cemit_info(" Starting asynchronous server reload ...") + cemit_info(" Starting asynchronous server reload.") reload_modules() # this must be given time to finish wait_time = 5 - cemit_info(" Wait for %ss to give modules time to fully re-cache ..." % wait_time) + cemit_info(" Waiting %ss to give modules time to fully re-cache ..." % wait_time) time.sleep(wait_time) reload_scripts() @@ -90,7 +90,7 @@ def reload_modules(): "Check so modpath is not in an unsafe module" return not any(mpath.startswith(modpath) for mpath in unsafe_modules) - cemit_info("\n Cleaning module caches ...") + cemit_info(" Cleaning module caches ...") # clean as much of the caches as we can cache = AppCache() @@ -149,15 +149,15 @@ def reload_scripts(scripts=None, obj=None, key=None, cleaned out. All persistent scripts are force-started. """ - cemit_info(" Validating scripts ...") + nr_started, nr_stopped = ScriptDB.objects.validate(scripts=scripts, obj=obj, key=key, dbref=dbref, init_mode=init_mode) - - string = " Started %s script(s). Stopped %s invalid script(s)." % \ - (nr_started, nr_stopped) - cemit_info(string) + if nr_started or nr_stopped: + string = " Started %s script(s). Stopped %s invalid script(s)." % \ + (nr_started, nr_stopped) + cemit_info(string) def reload_commands(): from src.commands import cmdsethandler @@ -170,12 +170,13 @@ def reset_loop(): cemit_info(" Running resets on database entities ...") t1 = time.time() - [s.locks.reset() for s in ScriptDB.objects.all()] - [p.locks.reset() for p in PlayerDB.objects.all()] [h.locks.reset() for h in HelpEntry.objects.all()] [m.locks.reset() for m in Msg.objects.all()] [c.locks.reset() for c in Channel.objects.all()] + [s.locks.reset() for s in ScriptDB.objects.all()] + [p.locks.reset() for p in PlayerDB.objects.all()] [(o.typeclass(o), o.cmdset.reset(), o.locks.reset()) for o in ObjectDB.get_all_cached_instances()] + t2 = time.time() cemit_info(" ... Loop finished in %g seconds." % (t2-t1))