diff --git a/game/gamesrc/commands/examples/cmdset_red_button.py b/game/gamesrc/commands/examples/cmdset_red_button.py index da5a83045f..f9402ad9f9 100644 --- a/game/gamesrc/commands/examples/cmdset_red_button.py +++ b/game/gamesrc/commands/examples/cmdset_red_button.py @@ -10,8 +10,6 @@ cmdset - this way you can often re-use the commands too. import random from src.commands.cmdset import CmdSet from game.gamesrc.commands.basecommand import Command -from game.gamesrc.scripts.examples import red_button_scripts as scriptexamples - # Some simple commands for the red button @@ -70,24 +68,20 @@ class CmdPush(Command): """ if self.obj.db.lid_open: - - # assign the blind state script to the caller. - # this will assign the restricted BlindCmdset to - # the caller at startup, and remove it again - # once the time has run out - self.caller.scripts.add(scriptexamples.BlindedState) - string = "You reach out to press the big red button ..." string += "\n\nA BOOM! A bright light blinds you!" string += "\nThe world goes dark ..." self.caller.msg(string) self.caller.location.msg_contents("%s presses the button. BOOM! %s is blinded by a flash!" % (self.caller.name, self.caller.name), exclude=self.caller) + # the button's method will handle all setup of scripts etc. + self.obj.press_button(self.caller) else: string = "You cannot push the button - there is a glass lid covering it." self.caller.msg(string) + class CmdSmashGlass(Command): """ smash glass @@ -142,22 +136,15 @@ class CmdOpenLid(Command): if self.obj.db.lid_locked: self.caller.msg("This lid seems locked in place for the moment.") return - string += "\nA ticking sound is heard, like a winding mechanism. Seems " + + string = "\nA ticking sound is heard, like a winding mechanism. Seems " string += "the lid will soon close again." - self.db.lid_open = False - # add the relevant cmdsets to button - self.obj.cmdset.add(LidClosedCmdsSet) - # add the lid-close ticker script - self.obj.scripts.add(scriptexamples.LidCloseTimer) - - # add more info to the button description - desc = self.obj.db.closed_desc - self.obj.db.temp_desc = desc - self.obj.db.desc = "%s\n%s" % (desc, "Its glass cover is open and the button exposed.") self.caller.msg(string) - self.caller.location.msg_contents("%s opens the lid of the button." % (self.caller.name), exclude=self.caller) + # add the relevant cmdsets to button + self.obj.cmdset.add(LidClosedCmdSet) + # call object method self.obj.open_lid() class CmdCloseLid(Command): @@ -176,12 +163,9 @@ class CmdCloseLid(Command): def func(self): "Close the lid" - if self.db.closed_desc: - self.obj.desc = self.db.closed_desc - self.obj.db.lid_open = False + self.obj.close_lid() # this will clean out scripts dependent on lid being open. - self.obj.scripts.validate() self.caller.msg("You close the button's lid. It clicks back into place.") self.caller.location.msg_contents("%s closes the button's lid." % (self.caller.name), exclude=self.caller) @@ -313,8 +297,8 @@ class BlindCmdSet(CmdSet): def at_cmdset_creation(self): "Setup the blind cmdset" - from game.gamesrc.commands.default.general import CmdSay - from game.gamesrc.commands.default.general import CmdPose + from src.commands.default.general import CmdSay + from src.commands.default.general import CmdPose self.add(CmdSay()) self.add(CmdPose()) self.add(CmdBlindLook()) diff --git a/game/gamesrc/objects/examples/red_button.py b/game/gamesrc/objects/examples/red_button.py index 873818955b..e030a7898e 100644 --- a/game/gamesrc/objects/examples/red_button.py +++ b/game/gamesrc/objects/examples/red_button.py @@ -25,6 +25,12 @@ class RedButton(Object): definition in game/gamesrc/events/example.py to blink at regular intervals. It also uses a series of script and commands to handle pushing the button and causing effects when doing so. + + The following attributes can be set on the button: + desc_lid_open - description when lid is open + desc_lid_closed - description when lid is closed + desc_lamp_broken - description when lamp is broken + """ def at_object_creation(self): """ @@ -43,11 +49,9 @@ class RedButton(Object): self.db.lamp_works = True self.db.lid_locked = False - # set the default cmdset to the object. This will by default surivive - # a server reboot (otherwise we could have used permanent=False). - self.cmdset.add_default(cmdsetexamples.DefaultCmdSet) + self.cmdset.add_default(cmdsetexamples.DefaultCmdSet, permanent=True) - # since the other cmdsets relevant to the button are added 'on the fly', + # since the cmdsets relevant to the button are added 'on the fly', # we need to setup custom scripts to do this for us (also, these scripts # check so they are valid (i.e. the lid is actually still closed)). # The AddClosedCmdSet script makes sure to add the Closed-cmdset. @@ -57,7 +61,7 @@ class RedButton(Object): # state-changing methods - def open_lid(self, feedback=True): + def open_lid(self): """ Opens the glass lid and start the timer so it will soon close again. @@ -65,18 +69,13 @@ class RedButton(Object): if self.db.lid_open: return - - desc = "This is a large red button, inviting yet evil-looking. " - desc += "Its glass cover is open and the button exposed." + desc = self.db.desc_lid_open + if not desc: + desc = "This is a large red button, inviting yet evil-looking. " + desc += "Its glass cover is open and the button exposed." self.db.desc = desc self.db.lid_open = True - - if feedback and self.location: - string = "The lid slides clear of the button with a click." - string += "\nA ticking sound is heard, suggesting the lid might have" - string += " some sort of timed locking mechanism." - self.location.msg_contents(string) - + # with the lid open, we validate scripts; this will clean out # scripts that depend on the lid to be closed. self.scripts.validate() @@ -86,7 +85,7 @@ class RedButton(Object): # (this one cleans itself after being called once) self.scripts.add(scriptexamples.CloseLidEvent) - def close_lid(self, feedback=True): + def close_lid(self): """ Close the glass lid. This validates all scripts on the button, which means that scripts only being valid when the lid is open @@ -95,16 +94,13 @@ class RedButton(Object): if not self.db.lid_open: return - - desc = "This is a large red button, inviting yet evil-looking. " - desc += "Its glass cover is closed, protecting it." + desc = self.db.desc_lid_closed + if not desc: + desc = "This is a large red button, inviting yet evil-looking. " + desc += "Its glass cover is closed, protecting it." self.db.desc = desc self.db.lid_open = False - if feedback and self.location: - string = "With a click the lid slides back, securing the button once again." - self.location.msg_contents(string) - # clean out scripts depending on lid to be open self.scripts.validate() # add scripts related to the closed state @@ -116,11 +112,14 @@ class RedButton(Object): """ self.db.lamp_works = False - self.db.desc = "The big red button has stopped blinking for the time being." + desc = self.db.desc_lamp_broken + if not desc: + self.db.desc += "\nThe big red button has stopped blinking for the time being." + else: + self.db.desc = desc if feedback and self.location: - string = "The lamp flickers, the button going dark." - self.location.msg_contents(string) + self.location.msg_contents("The lamp flickers, the button going dark.") self.scripts.validate() def press_button(self, pobject): diff --git a/game/gamesrc/scripts/examples/red_button_scripts.py b/game/gamesrc/scripts/examples/red_button_scripts.py index b31a130377..e0e864ba6f 100644 --- a/game/gamesrc/scripts/examples/red_button_scripts.py +++ b/game/gamesrc/scripts/examples/red_button_scripts.py @@ -251,7 +251,7 @@ class DeactivateButtonEvent(Script): (that just controls when at_repeat() is called) """ # closing the lid will also add the ClosedState script - self.obj.close_lid(feedback=False) + self.obj.close_lid() # lock the lid so other players can't access it until the # first one's effect has worn off. self.obj.db.lid_locked = True diff --git a/src/commands/cmdsethandler.py b/src/commands/cmdsethandler.py index 2c9ed038e3..79afeb4287 100644 --- a/src/commands/cmdsethandler.py +++ b/src/commands/cmdsethandler.py @@ -278,13 +278,11 @@ class CmdSetHandler(object): def add_default(self, cmdset, emit_to_obj=None, permanent=True): """ Add a new default cmdset. If an old default existed, - it is replaced. If permanent is set, a script will be created to - add the cmdset to the object. + it is replaced. If permanent is set, the set will survive a reboot. cmdset - can be a cmdset object or the python path to an instance of such an object. emit_to_obj - an object to receive error messages. - permanent - create a script that assigns this script every - startup/login. + permanent - save cmdset across reboots See also the notes for self.add(), which applies here too. """ if callable(cmdset): diff --git a/src/commands/default/batchprocess.py b/src/commands/default/batchprocess.py index fa85e3b0d6..2631645910 100644 --- a/src/commands/default/batchprocess.py +++ b/src/commands/default/batchprocess.py @@ -181,7 +181,7 @@ class CmdBatchCommands(MuxCommand): Build from batch-command file Usage: - @batchcommands[/interactive] + @batchcommands[/interactive] Switch: interactive - this mode will offer more control when diff --git a/src/locks/lockfuncs.py b/src/locks/lockfuncs.py index 65ac825e4d..4498126523 100644 --- a/src/locks/lockfuncs.py +++ b/src/locks/lockfuncs.py @@ -361,7 +361,8 @@ def holds(accessing_obj, accessed_obj, objid, *args, **kwargs): if dbref and any((True for obj in contains if obj.id == dbref)): return True objid = objid.lower() - return any((True for obj in contains if obj.name.lower() == objid)) + return any((True for obj in contains + if obj.name.lower() == objid or objid in [al.lower() for al in obj.aliases])) def carried(accessing_obj, accessed_obj): """ @@ -379,8 +380,8 @@ def objcarried(accessing_obj, accessed_obj): objcarried() Like carried, except this lock looks for a property "obj" on the accessed_obj - and tries to determing if *this* is carried by accessing_obj. This works well - for commands and scripts. + and tries to determine if *this* is carried by accessing_obj. This works well + for accessing commands and scripts. """ return hasattr(accessed_obj, "obj") and accessed_obj.obj and \ hasattr(accessed_obj.obj, "location") and accessed_obj.obj.location == accessing_obj diff --git a/src/settings_default.py b/src/settings_default.py index a648c08959..f0e1da97c4 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -175,9 +175,9 @@ CMDSET_OOC = "game.gamesrc.commands.basecmdset.OOCCmdSet" # Base paths for typeclassed object classes. These paths must be # defined relative evennia's root directory. They will be searched in # order to find relative typeclass paths. -OBJECT_TYPECLASS_PATHS = ["game.gamesrc.objects", "game.gamesrc.objects.examples"] -SCRIPT_TYPECLASS_PATHS = ["game.gamesrc.scripts", "game.gamesrc.scripts.examples"] -PLAYER_TYPECLASS_PATHS = ["game.gamesrc.objects"] +OBJECT_TYPECLASS_PATHS = ["game.gamesrc.objects", "game.gamesrc.objects.examples", "contrib"] +SCRIPT_TYPECLASS_PATHS = ["game.gamesrc.scripts", "game.gamesrc.scripts.examples", "contrib"] +PLAYER_TYPECLASS_PATHS = ["game.gamesrc.objects", "contrib"] # Typeclass for player objects (linked to a character) (fallback) BASE_PLAYER_TYPECLASS = "game.gamesrc.objects.baseobjects.Player" @@ -196,7 +196,7 @@ BASE_EXIT_TYPECLASS = "game.gamesrc.objects.baseobjects.Exit" # Python path to a directory to be searched for batch scripts # for the batch processors (.ev and/or .py files). -BASE_BATCHPROCESS_PATH = 'game.gamesrc.world' +BASE_BATCHPROCESS_PATHS = ['game.gamesrc.world', 'contrib'] ################################################### # Game Time setup diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 21fce760c0..4a70da8b3d 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -22,6 +22,7 @@ these to create custom managers. """ +import sys try: import cPickle as pickle except ImportError: @@ -664,6 +665,7 @@ class TypedObject(SharedMemoryModel): typeclass = object.__getattribute__(self, "_path_import")(tpath) if callable(typeclass): # don't return yet, we must cache this further down. + errstring = "" break elif hasattr(typeclass, '__file__'): errstring += "\n%s seems to be just the path to a module. You need" % tpath @@ -674,7 +676,7 @@ class TypedObject(SharedMemoryModel): if not callable(typeclass): # Still not a valid import. Fallback to default. defpath = object.__getattribute__(self, "default_typeclass_path") - errstring += " Using Default class '%s'." % defpath + errstring += "\n\nUsing Default class '%s'." % defpath self.db_typeclass_path = defpath self.save() logger.log_errmsg(errstring) @@ -716,16 +718,22 @@ class TypedObject(SharedMemoryModel): try: modpath, class_name = path.rsplit('.', 1) module = __import__(modpath, fromlist=[class_name]) - return module.__dict__[class_name] - except ImportError: - trc = traceback.format_exc() - errstring = "\n%s\nError importing '%s'." % (trc, path) + return module.__dict__[class_name] + except ImportError: + trc = sys.exc_traceback + if not trc.tb_next: + # we separate between not finding the module, and finding a buggy one. + errstring += "(Tried path '%s')." % path + else: + # a bug in the module is reported normally. + trc = traceback.format_exc() + errstring += "\n%sError importing '%s'." % (trc, path) except KeyError: errstring = "No class '%s' was found in module '%s'." errstring = errstring % (class_name, modpath) except Exception: trc = traceback.format_exc() - errstring = "\n%s\nImporting '%s' failed." % (trc, path) + errstring = "\n%sException importing '%s'." % (trc, path) # return the error. return errstring diff --git a/src/utils/batchprocessors.py b/src/utils/batchprocessors.py index bfddad8851..d91ce655b6 100644 --- a/src/utils/batchprocessors.py +++ b/src/utils/batchprocessors.py @@ -163,29 +163,30 @@ def read_batchfile(pythonpath, file_ending='.py'): """ # open the file - - if pythonpath and not (pythonpath.startswith('src.') or - pythonpath.startswith('game.')): - pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH, - pythonpath) - abspath = utils.pypath_to_realpath(pythonpath, file_ending) - + if pythonpath and not (pythonpath.startswith('src.') or pythonpath.startswith('game.')): + abspaths = [] + for basepath in settings.BASE_BATCHPROCESS_PATHS: + abspaths.append(utils.pypath_to_realpath("%s.%s" % (basepath, pythonpath), file_ending)) + else: + abspaths = [pythonpath] + fobj = None for file_encoding in ENCODINGS: # try different encodings, in order - err = None - try: - # we read the file directly into unicode. - fobj = codecs.open(abspath, 'r', encoding=file_encoding) - except IOError: - # try again without the appended file ending - abspath2 = utils.pypath_to_realpath(pythonpath, None) - try: + load_errors = [] + for abspath in abspaths: + # try different paths, until we get a match + try: + # we read the file directly into unicode. fobj = codecs.open(abspath, 'r', encoding=file_encoding) - except IOError: - string = "Could not open batchfile '%s', nor '%s'." - logger.log_errmsg(string % (abspath2, abspath)) - return None - + except IOError: + load_errors.append("Could not open batchfile '%s'." % abspath) + continue + break + if not fobj: + continue + + load_errors = [] + err =None # We have successfully found and opened the file. Now actually # try to decode it using the given protocol. try: @@ -208,6 +209,8 @@ def read_batchfile(pythonpath, file_ending='.py'): continue # if we get here, the encoding worked. Stop iteration. break + if load_errors: + logger.log_errmsg("\n".join(load_errors)) if err: return err else: diff --git a/src/utils/reloads.py b/src/utils/reloads.py index bd0db5312a..3e1699c0a4 100644 --- a/src/utils/reloads.py +++ b/src/utils/reloads.py @@ -104,8 +104,8 @@ def reload_modules(): if unsafe_dir_modified or unsafe_mod_modified: if unsafe_mod_modified: - string += "\n {rModules containing Script classes with a timer component" - string += "\n and which has already spawned instances cannot be reloaded safely.{n" + string += "\n {rModules containing Script classes with a timer component{n" + string += "\n {rand which has already spawned instances cannot be reloaded safely.{n" string += "\n {rThese module(s) can only be reloaded by server reboot:{n\n %s\n" string = string % ", ".join(unsafe_dir_modified + unsafe_mod_modified)