diff --git a/game/gamesrc/events/example.py b/game/gamesrc/events/example.py index 2bca3ec034..67aae71104 100644 --- a/game/gamesrc/events/example.py +++ b/game/gamesrc/events/example.py @@ -6,18 +6,21 @@ the red_button parent to see its effects (e.g. @create button=examples/red_butto Technically the event don't contain any game logics, all it does is locate all objects inheriting to a particular script parent and calls one of its functions at a regular interval. + +Note that this type of event will cause *all* red buttons to blink at the same +time, regardless when they were created. This is a very efficient way +to do it (it is also very useful for global events like weather patterns +and day-night cycles), but you can also add events directly to individual objecs +(see the example event in gamesrc/parents/examples/red_button) """ -import sys +import traceback from src.events import IntervalEvent -from src.scheduler import add_event +from src import scheduler from src.objects.models import Object #the logger is useful for debugging -from src.logger import log_errmsg - -#Example of the event system. This example adds an event to the red_button parent -#in parents/examples. It makes the button blink temptingly at a regular interval. +from src import logger class EventBlinkButton(IntervalEvent): """ @@ -25,8 +28,9 @@ class EventBlinkButton(IntervalEvent): """ def __init__(self): """ - A custom init method also storing the source object. - + Note that we do NOT make this event persistent across + reboots since we are actually creating it (i.e. restarting it) + every time the module is reloaded. """ super(EventBlinkButton, self).__init__() self.name = 'event_blink_red_button' @@ -34,13 +38,16 @@ class EventBlinkButton(IntervalEvent): self.interval = 30 #the description is seen when you run @ps in-game. self.description = "Blink red buttons regularly." - + def event_function(self): """ This stub function is automatically fired every self.interval seconds. In this case we do a search for all objects inheriting from the correct parent and call a function on them. + + Note that we must make sure to handle all tracebacks in this + function to avoid trouble. """ #find all objects inheriting from red_button (parents are per definition #stored with the gamesrc/parent/ drawer as a base) @@ -51,11 +58,10 @@ class EventBlinkButton(IntervalEvent): try: b.scriptlink.blink() except: - #show other tracebacks to log and owner of object. - #This is important, we must handle these exceptions gracefully! - b.get_owner().emit_to(sys.exc_info()[1]) - log_errmsg(sys.exc_info()[1]) - + # Print all tracebacks to the log instead of letting them by. + # This is important, we must handle these exceptions gracefully! + logger.log_errmsg(traceback.print_exc()) + #create and add the event to the global handler blink_event = EventBlinkButton() -add_event(blink_event) +scheduler.add_event(blink_event) diff --git a/game/gamesrc/parents/examples/red_button.py b/game/gamesrc/parents/examples/red_button.py index 92f3fc9c3f..6b65e505a4 100644 --- a/game/gamesrc/parents/examples/red_button.py +++ b/game/gamesrc/parents/examples/red_button.py @@ -8,41 +8,134 @@ Assuming this script remains in gamesrc/parents/examples, create an object of this type using @create button:examples.red_button This file also shows the use of the Event system to make the button -send a message to the players at regular intervals. Note that if you create a -test button you must drop it before you will see its messages! +send a message to the players at regular intervals. To show the use of +Events, we are tying two types of events to the red button, one which cause ALL +red buttons in the game to blink in sync (gamesrc/events/example.py) and one +event which cause the protective glass lid over the button to close +again some time after it was opened. +Note that if you create a test button you must drop it before you can +see its messages! """ +import traceback from game.gamesrc.parents.base.basicobject import BasicObject +from src.objects.models import Object +from src.events import IntervalEvent +from src import scheduler +from src import logger -# you have to import the event definition(s) from somewhere -# covered by @reload, and this is as good a place as any. -# Doing this will start the event ticking. - -import game.gamesrc.events.example # -# commands for using the button object. These are added to +# Events +# + +# Importing this will start the blink event ticking, only one +# blink event is used for all red buttons. +import game.gamesrc.events.example + +# We also create an object-specific event. + +class EventCloselid(IntervalEvent): + """ + This event closes the glass lid over the button + some time after it was opened. + """ + def __init__(self, obj): + """ + Note how we take an object as an argument, + this will allow instances of this event to + operate on this object only. + """ + # we must call super to make sure things work! + super(EventCloselid, self).__init__() + # store the object reference + self.obj_dbref = obj.dbref() + # This is used in e.g. @ps to show what the event does + self.description = "Close lid on %s" % obj + # We make sure that this event survives a reboot + self.persistent = True + # How many seconds from event creation to closing + # the lid + self.interval = 20 + # We only run the event one time before it deletes itself. + self.repeats = 1 + + def event_function(self): + """ + This function is called every self.interval seconds. + Note that we must make sure to handle all errors from + this call to avoid trouble. + """ + try: + # if the lid is open, close it. We have to find the object + # again since it might have changed. + obj = Object.objects.get_object_from_dbref(self.obj_dbref) + if obj.has_flag("LID_OPEN"): + obj.scriptlink.close_lid() + retval = "The glass cover over the button silently closes by itself." + obj.get_location().emit_to_contents(retval) + except: + # send the traceback to the log instead of letting it by. + # It is important that we handle exceptions gracefully here! + logger.log_errmsg(traceback.print_exc()) + + +# +# Object commands +# +# Commands for using the button object. These are added to # the object in the class_factory function at the # bottom of this module. # +def cmd_open_lid(command): + """ + Open the glass lid cover over the button. + """ + # In the case of object commands, you can use this to + # get the object the command is defined on. + obj = command.scripted_obj + + if obj.has_flag("LID_OPEN"): + retval = "The lid is already open." + else: + retval = "You lift the lid, exposing the tempting button." + obj.scriptlink.open_lid() + command.source_object.emit_to(retval) + +def cmd_close_lid(command): + """ + Close the lid again. + """ + obj = command.scripted_obj + if not obj.has_flag("LID_OPEN"): + retval = "The lid is already open." + else: + retval = "You secure the glass cover over the button." + obj.scriptlink.close_lid() + command.source_object.emit_to(retval) + def cmd_push_button(command): """ This is a simple command that handles a user pressing the - button by returning a message. + button by returning a message. The button can only be """ - retval = "There is a loud bang: BOOOM!" - command.source_object.emit_to(retval) - -def cmd_pull_button(command): - """ - An example of a second defined command (for those who - don't know how a button works ... ;) ) - """ - retval = "A button is meant to be pushed, not pulled!" + obj = command.scripted_obj + + if obj.has_flag("LID_OPEN"): + retval = "You press the button ..." + retval += "\n ..." + retval += "\n BOOOOOM!" + obj.scriptlink.close_lid() + else: + retval = "There is a glass lid covering " + retval += "the button as a safety measure. If you " + retval += "want to press the button you need to open " + retval += "the lid first." command.source_object.emit_to(retval) + # # Definition of the object itself # @@ -53,7 +146,8 @@ class RedButton(BasicObject): It will use the event definition in game/gamesrc/events/example.py to blink at regular intervals until the lightbulb - breaks. + breaks. It also use the EventCloselid event defined + above to close the lid """ def at_object_creation(self): """ @@ -63,10 +157,31 @@ class RedButton(BasicObject): #get stored object related to this class obj = self.scripted_obj - obj.set_attribute('desc', "This is your standard big red button.") - obj.set_attribute("breakpoint", 10) + obj.set_attribute('desc', "This is a big red button. It has a glass cover.") + obj.set_attribute("breakpoint", 5) obj.set_attribute("count", 0) + + # add the object-based commands to the button + obj.add_command("open lid", cmd_open_lid) + obj.add_command("lift lid", cmd_open_lid) + obj.add_command("close lid", cmd_close_lid) + obj.add_command("push button", cmd_push_button) + obj.add_command("push the button", cmd_push_button) + def open_lid(self): + """ + Open the glass lid and start the timer so it will + soon close again. + """ + self.scripted_obj.set_flag("LID_OPEN") + scheduler.add_event(EventCloselid(self.scripted_obj)) + + def close_lid(self): + """ + Close the glass lid + """ + self.scripted_obj.unset_flag("LID_OPEN") + def blink(self): """ If the event system is active, it will regularly call this @@ -102,8 +217,4 @@ def class_factory(source_obj): This is a good place for adding new commands to the button since this is where it is actually instantiated. """ - button = RedButton(source_obj) - # add the object-based commands to the button - button.command_table.add_command("push button", cmd_push_button) - button.command_table.add_command("pull button", cmd_pull_button) - return button + return RedButton(source_obj) diff --git a/src/cmdhandler.py b/src/cmdhandler.py index 647e707367..fca74374a7 100755 --- a/src/cmdhandler.py +++ b/src/cmdhandler.py @@ -6,7 +6,7 @@ something. #import time from traceback import format_exc from django.conf import settings -from django.contrib.contenttypes.models import ContentType +#from django.contrib.contenttypes.models import ContentType from objects.models import Object import defines_global import cmdtable @@ -420,9 +420,10 @@ def match_neighbor_ctables(command,test=False): neighbors = location.get_contents() + [location] + source_object.get_contents() for neighbor in neighbors: #print "neighbor:", neighbor - if command_table_lookup(command, - neighbor.scriptlink.command_table, - test=test, neighbor=neighbor): + obj_cmdtable = neighbor.get_cmdtable() + if obj_cmdtable and command_table_lookup(command, obj_cmdtable, + test=test, + neighbor=neighbor): # If there was a command match, set the scripted_obj attribute # for the script parent to pick up. diff --git a/src/commands/objmanip.py b/src/commands/objmanip.py index a92cd5bcb1..a27a43c18d 100644 --- a/src/commands/objmanip.py +++ b/src/commands/objmanip.py @@ -241,12 +241,16 @@ def cmd_set(command): if not attrib_name: source_object.emit_to("Cannot set an empty attribute name.") return + if attrib_name == "__command_table__": + string = "This is a protected attribute, choose another name." + source_object.emit_to(string) + return if not Attribute.objects.is_modifiable_attrib(attrib_name): # In global_defines.py, see NOSET_ATTRIBS for protected attribute names. source_object.emit_to("You can't modify that attribute.") return if attrib_value: - # An attribute value was specified, create or set the attribute. + # An attribute value was specified, create or set the attribute. target.set_attribute(attrib_name, attrib_value) s = "Attribute %s=%s set to '%s'" % (target_name, attrib_name, attrib_value) else: @@ -1630,8 +1634,16 @@ def cmd_examine(command): string += str("Location: %s" % target_obj.get_location()) + newl # Render other attributes + cmd_string = "" + attr_string = "" for attribute in target_obj.get_all_attributes(): - string += str(attribute.get_attrline()) + newl + if attribute.get_name() == "__command_table__": + cmdtable = attribute.get_value() + cmd_string = "Commands: " + cmd_string += ", ".join(cmdtable.ctable.keys()) + newl + else: + attr_string += str(attribute.get_attrline()) + newl + string += cmd_string + attr_string # Render Contents display. if con_players or con_things: diff --git a/src/objects/managers/object.py b/src/objects/managers/object.py index 032441d8a0..91047a22d4 100644 --- a/src/objects/managers/object.py +++ b/src/objects/managers/object.py @@ -64,6 +64,8 @@ class ObjectManager(models.Manager): """ Returns an object when given a dbref. """ + if len(dbref)>1 and dbref[0]=="#": + dbref = dbref[1:] try: return self.get(id=dbref) except self.model.DoesNotExist: diff --git a/src/objects/models.py b/src/objects/models.py index ace06f6bc5..58fde3b3fb 100755 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -60,9 +60,9 @@ class Attribute(models.Model): """ Returns an attribute's value. """ - attr_value = str(self.attr_value) + attr_value = self.attr_value if self.attr_ispickled: - attr_value = pickle.loads(attr_value) + attr_value = pickle.loads(str(attr_value)) return attr_value def set_value(self, new_value): @@ -650,6 +650,11 @@ class Object(models.Model): a str, the object will be stored as a pickle. """ + if attribute == "__command_table__": + # protect the command table attribute, + # this is only settable by self.add_command() + return + attrib_obj = None if self.has_attribute(attribute): attrib_obj = \ @@ -689,6 +694,14 @@ class Object(models.Model): return attrib.get_value() else: return default + + def get_attribute(self, attrib, default=None): + """ + Convenience function (to keep compatability). While + get_attribute_value() is a correct name, it is not really + consistent with set_attribute() anyway. + """ + return self.get_attribute_value(attrib, default) def get_attribute_obj(self, attrib, auto_create=False): """ @@ -697,7 +710,7 @@ class Object(models.Model): attrib: (str) The attribute's name. """ if self.has_attribute(attrib): - return Attribute.objects.filter(attr_object=self).filter(attr_name=attrib) + return Attribute.objects.filter(attr_object=self).filter(attr_name=attrib)[0] else: if auto_create: new_attrib = Attribute() @@ -909,8 +922,7 @@ class Object(models.Model): (self.name,self.id,self.location_id)) return False - - + def get_scriptlink(self): """ Returns an object's script parent. @@ -1167,6 +1179,59 @@ class Object(models.Model): otype = int(self.type) return defines_global.OBJECT_TYPES[otype][1][0] + # object custom commands + + def add_command(self, command_string, function, + priv_tuple=None, extra_vals=None, + help_category="", priv_help_tuple=None, + auto_help_override=False): + """ + Add an object-based command to this object. The command + definition is added to an attribute-stored command table + (this table is created when adding the first command) + + command_string: (string) Command string (IE: WHO, QUIT, look). + function: (reference) The command's function. + priv_tuple: (tuple) String tuple of permissions required for command. + extra_vals: (dict) Dictionary to add to the Command object. + + By default object commands are NOT added to the global help system + with auto-help. You have to actively set auto_help_override to True + if you explicitly want auto-help for your object command. + + help_category (str): An overall help category where auto-help will place + the help entry. If not given, 'General' is assumed. + priv_help_tuple (tuple) String tuple of permissions required to view this + help entry. If nothing is given, priv_tuple is used. + auto_help_override (bool): If True, use auto-help. If None, use setting + in settings.AUTO_HELP_ENABLED. Default is False + for object commands. + """ + + # we save using the attribute object to avoid + # the protection on the __command_table__ keyword + # in set_attribute_value() + attrib_obj = self.get_attribute_obj("__command_table__", + auto_create=True) + cmdtable = attrib_obj.get_value() + if not cmdtable: + # create new table if we didn't have one before + from src.cmdtable import CommandTable + had_table = False + cmdtable = CommandTable() + # add the command to the object's command table. + cmdtable.add_command(command_string, function, priv_tuple, extra_vals, + help_category, priv_help_tuple, + auto_help_override) + # store the cmdtable again + attrib_obj.set_value(cmdtable) + + def get_cmdtable(self): + """ + Return this object's local command table, if it exists. + """ + return self.get_attribute("__command_table__") + #state access functions def get_state(self): diff --git a/src/script_parents/basicobject.py b/src/script_parents/basicobject.py index 3312606fbc..778e6442d6 100644 --- a/src/script_parents/basicobject.py +++ b/src/script_parents/basicobject.py @@ -27,7 +27,9 @@ class EvenniaBasicObject(object): scripted_obj: (Object) A reference to the object being scripted (the child). """ self.scripted_obj = scripted_obj - self.command_table = CommandTable() + # below is now handled by self.scripted_obj.add_command() in + # self.at_object_creation(). + #self.command_table = CommandTable() def at_object_creation(self): """