From a9dbac8aae9547c363ce6c9775033524624cbae1 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 25 Apr 2009 20:51:12 +0000 Subject: [PATCH] - Made many small bugfixes to the @parent and @create functions as well as their underlying methods. - Made it so user #1 is also affected by the on_player_creation() function. - Added an event folder for custom events, including a working example - Expanded the example commands and parents to include the changes to how they should be initialized. - Added an optional ansi scheme (not active by default) --- game/gamesrc/commands/example.py | 75 +++++++++++++-- game/gamesrc/events/__init__.py | 0 game/gamesrc/events/example.py | 55 +++++++++++ game/gamesrc/parents/base/basicobject.py | 2 +- .../parents/examples/custom_basicobject.py | 55 +++++++++++ .../parents/examples/custom_basicplayer.py | 67 +++++++++++++ game/gamesrc/parents/examples/red_button.py | 95 +++++++++++++++++-- src/ansi.py | 36 ++++++- src/commands/general.py | 2 +- src/commands/parents.py | 5 +- src/helpsys/models.py | 2 +- src/initial_setup.py | 4 +- src/objects/managers/object.py | 11 ++- src/objects/models.py | 10 +- src/scheduler.py | 11 ++- src/scripthandler.py | 2 +- 16 files changed, 397 insertions(+), 35 deletions(-) create mode 100644 game/gamesrc/events/__init__.py create mode 100644 game/gamesrc/events/example.py create mode 100644 game/gamesrc/parents/examples/custom_basicobject.py create mode 100644 game/gamesrc/parents/examples/custom_basicplayer.py diff --git a/game/gamesrc/commands/example.py b/game/gamesrc/commands/example.py index eade593a46..30e9a90583 100644 --- a/game/gamesrc/commands/example.py +++ b/game/gamesrc/commands/example.py @@ -1,18 +1,48 @@ """ -This is an example command module that may be copied and used to serve as the -basis to newly created modules. You'll need to make sure that this or any new -modules are added to settings.py under CUSTOM_COMMAND_MODULES or -CUSTOM_UNLOGGED_COMMAND_MODULES, which are tuples of module import path strings. +This is an example command module for showing the pluggable command system +in action. + +You'll need to make sure that this or any new modules you create are added to +game/settings.py under CUSTOM_COMMAND_MODULES or CUSTOM_UNLOGGED_COMMAND_MODULES, +which are tuples of module import path strings. See src/config_defaults.py for more details. + +E.g. to add this example command for testing, your entry in game/settings.py would +look like this: + +CUSTOM_COMMAND_MODULES = ('game.gamesrc.commands.example',) + +(note the extra comma at the end to make this into a Python tuple. It's only +needed if you have only one entry.) + """ + # This is the common global CommandTable object which we'll be adding the -# example command to. +# example command to. We can add any number of commands this way in the +# same file. from src.cmdtable import GLOBAL_CMD_TABLE def cmd_example(command): + """ + This is the help text for the 'example' command, a command to + show how the pluggable command system works. + + For testing, you can try calling this with different switches and + arguments, like + > example/test/test2 Hello + and see what is returned. + + <> + + This is a subtopic to the main example command help entry. + + Note that this text is auto-added since auto_help=True + was set in the call to add_function. Any number of subtopics like + this one can be added on the fly using the auto-help system. See + help topics on 'help' and 'help_staff' for more information and + options. """ - An example command to show how the pluggable command system works. - """ + # By building one big string and passing it at once, we cut down on a lot # of emit_to() calls, which is generally a good idea. retval = "----- Example Command -----\n\r" @@ -33,5 +63,32 @@ def cmd_example(command): # Extra variables passed with cmdtable.py's add_command(). retval += " Extra vars: %s\n\r" % command.extra_vars command.source_object.emit_to(retval) -# Add the command to the common global command table. -GLOBAL_CMD_TABLE.add_command("example", cmd_example), \ No newline at end of file + +# Add the command to the common global command table. Note that +# since auto_help=True, help entries named "example" and +# "example_auto_help" (as defined in the __doc__ string) will +# automatically be created for us. +GLOBAL_CMD_TABLE.add_command("example", cmd_example, auto_help=True), + + + +#another simple example + +def cmd_emote_smile(command): + """ + Simplistic 'smile' emote. + """ + #get the source object (that is, the player using the command) + caller = command.source_object + #find name of caller + name = caller.get_name(show_dbref=False) + #get the location caller is at + location = caller.get_location() + #build the emote + text = "%s smiles." % name + #emit the emote to everyone at the current location + location.emit_to_contents(text) + +#add to global command table (no auto_help activated) +GLOBAL_CMD_TABLE.add_command('smile', cmd_emote_smile) + diff --git a/game/gamesrc/events/__init__.py b/game/gamesrc/events/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/game/gamesrc/events/example.py b/game/gamesrc/events/example.py new file mode 100644 index 0000000000..ba1714933c --- /dev/null +++ b/game/gamesrc/events/example.py @@ -0,0 +1,55 @@ +""" +Example of the event system. To try it out, make sure to import it from somewhere +covered by @reload (like the script parent). Create an object inheriting +the red_button parent to see its effects (e.g. @create button=examples/red_button) + +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. +""" + +from src.events import IntervalEvent +from src.scheduler import add_event +from src.objects.models import Object + +#the logger is useful for debugging since there is no source object to send to +from src.logger import log_infomsg + +#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. + +class EventBlinkButton(IntervalEvent): + """ + This event lets the button flash at regular intervals. + """ + def __init__(self): + """ + A custom init method also storing the source object. + + """ + super(EventBlinkButton, self).__init__() + self.name = 'event_blink_red_button' + #how often to blink, in seconds + 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. + """ + #find all objects inheriting from red_button (parents are per definition + #stored with the gamesrc/parent/ drawer as a base) + parent = 'examples.red_button' + buttons = Object.objects.global_object_script_parent_search(parent) + log_infomsg("buttons found: %s" % buttons) + + for b in buttons: + b.scriptlink.blink() + +#create and add the event to the global handler +blink_event = EventBlinkButton() +add_event(blink_event) diff --git a/game/gamesrc/parents/base/basicobject.py b/game/gamesrc/parents/base/basicobject.py index e193375f2c..f991467670 100644 --- a/game/gamesrc/parents/base/basicobject.py +++ b/game/gamesrc/parents/base/basicobject.py @@ -19,4 +19,4 @@ def class_factory(source_obj): source_obj: (Object) A reference to the object being scripted (the child). """ - return BasicObject(source_obj) \ No newline at end of file + return BasicObject(source_obj) diff --git a/game/gamesrc/parents/examples/custom_basicobject.py b/game/gamesrc/parents/examples/custom_basicobject.py new file mode 100644 index 0000000000..f49e2c8911 --- /dev/null +++ b/game/gamesrc/parents/examples/custom_basicobject.py @@ -0,0 +1,55 @@ +""" +Simple example of a custom modified object, derived from the base object. + +If you want to make this your new default object type, move this into +gamesrc/parents and set SCRIPT_DEFAULT_OBJECT = 'custom_basicobject' +in game/settings.py. + +Generally, if you want to conveniently set future objects to inherit from this +script parent (not as a default), this files and others like it need to be +located under the game/gamesrc/parent directory. +""" +from game.gamesrc.parents.base.basicobject import BasicObject + +class CustomBasicObject(BasicObject): + + def at_object_creation(self): + """ + This function is called whenever the object is created. Use + this instead of __init__ to set start attributes etc on a + particular object type. + """ + + #Set an "sdesc" (short description) attribute on object, + #defaulting to its given name + + #get the stored object related to this class + obj = self.scripted_obj + + #find out the object's name + name = obj.get_name(fullname=False, + show_dbref=False, + show_flags=False) + #assign the name to the new attribute + obj.set_attribute('sdesc',name) + + def at_object_destruction(self, pobject=None): + """ + This is triggered when an object is about to be destroyed via + @destroy ONLY. If an object is deleted via delete(), it is assumed + that this method is to be skipped. + + values: + * pobject: (Object) The object requesting the action. + """ + pass + + +def class_factory(source_obj): + """ + This method is called by any script you retrieve (via the scripthandler). It + creates an instance of the class and returns it transparently. + + source_obj: (Object) A reference to the object being scripted (the child). + """ + return CustomBasicObject(source_obj) diff --git a/game/gamesrc/parents/examples/custom_basicplayer.py b/game/gamesrc/parents/examples/custom_basicplayer.py new file mode 100644 index 0000000000..691d38c470 --- /dev/null +++ b/game/gamesrc/parents/examples/custom_basicplayer.py @@ -0,0 +1,67 @@ +""" +This is an example of customizing the basic player character object. +You will want to do this to add all sorts of custom things like +attributes, skill values, injuries and so on. + +If you want to make this the default player object for all players, move it +into gamesrc/parents and set SCRIPT_DEFAULT_PLAYER = 'custom_basicplayer' +in game/settings.py. +""" + +from game.gamesrc.parents.base.basicplayer import BasicPlayer + +class CustomBasicPlayer(BasicPlayer): + + def at_player_creation(self): + """ + Called when player object is first created. Use this + instead of __init__ to define any custom attributes + all your player characters should have. + """ + + #Example: Adding a default sdesc (short description) + + #get the stored object related to this class + pobject = self.scripted_obj + #set the attribute + pobject.set_attribute('sdesc', 'A normal person') + + def at_pre_login(self, session): + """ + Called when the player has entered the game but has not + logged in yet. + """ + pass + + def at_post_login(self, session): + """ + This command is called after the player has logged in but + before he is allowed to give any commands. + """ + #get the object linked to this class + pobject = self.scripted_obj + + #find out more about our object + name = pobject.get_name(fullname=False, + show_dbref=False, + show_flags=False) + sdesc = pobject.get_attribute_value('sdesc') + + #send a greeting using our new sdesc attribute + pobject.emit_to("You are now logged in as %s - %s." % (name, sdesc)) + + #tell everyone else we're here + pobject.get_location().emit_to_contents("%s - %s, has connected." % + (name, sdesc), exclude=pobject) + #show us our surroundings + pobject.execute_cmd("look") + + +def class_factory(source_obj): + """ + This method is called by any script you retrieve (via the scripthandler). It + creates an instance of the class and returns it transparently. + + source_obj: (Object) A reference to the object being scripted (the child). + """ + return CustomBasicPlayer(source_obj) diff --git a/game/gamesrc/parents/examples/red_button.py b/game/gamesrc/parents/examples/red_button.py index 529eed18b4..c161351242 100644 --- a/game/gamesrc/parents/examples/red_button.py +++ b/game/gamesrc/parents/examples/red_button.py @@ -1,35 +1,110 @@ """ -An example script parent for a +An example script parent for a nice red button object. It has +custom commands defined on itself that are only useful in relation to this +particular object. See example.py in gamesrc/commands for more info +on the pluggable command system. + +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! + """ from game.gamesrc.parents.base.basicobject import BasicObject +#you have to import the event definition(s) from somewhere covered by @reload, +# - this is as good a place as any. Uncomment to turn off event system. +import game.gamesrc.events.example + +# +#commands on the button object +# + def cmd_push_button(command): """ - An example command to show how the pluggable command system works. - """ - # By building one big string and passing it at once, we cut down on a lot - # of emit_to() calls, which is generally a good idea. - retval = "You have pushed the button on: %s" % (command.scripted_obj.get_name()) + + This is a simple command that handles a user pressing the + button by returning a message. + """ + 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!" + command.source_object.emit_to(retval) + +# +#The object itself +# + class RedButton(BasicObject): + def __init__(self, scripted_obj, *args, **kwargs): """ This is called when class_factory() instantiates a temporary instance of the script parent. This is typically not something you want to mess with much. """ - # Calling the super classes __init__ is critical! Never forget to do + # Calling the super class' __init__ is critical! Never forget to do # this or everything else from here on out will fail. super(RedButton, self).__init__(scripted_obj, args, kwargs) - # Add the command to the object's command table. + # Add the commands to the object's command table (this is about + #the only thing you should use the __init__ for). self.command_table.add_command("pushbutton", cmd_push_button) + self.command_table.add_command("pullbutton", cmd_pull_button) + + + def at_object_creation(self): + """ + This function is called when object is created. Use this + preferably over __init__. + + In this case all we do is add the commandtable + to the object's own command_table variable; this makes + the commands we've added to COMMAND_TABLE available to + the user whenever the object is around. + """ + #get stored object related to this class + obj = self.scripted_obj + + obj.set_description("This is your standard big red button.") + obj.set_attribute("breakpoint", 10) + obj.set_attribute("count", 0) + + def blink(self): + """If the event system is active, it will regularly call this function to make + the button blink. Note the use of attributes to store the variable count and + breakpoint in a persistent way.""" + obj = self.scripted_obj + + try: + count = int(obj.get_attribute_value("count")) + breakpoint = int(obj.get_attribute_value("breakpoint")) + except TypeError: + return + + if count <= breakpoint: + if int(count) == int(breakpoint): + s = "The button flashes, then goes dark. " + s += "Looks like the lamp just broke." + else: + s = "The red button flashes, demanding your attention." + count += 1 + obj.set_attribute("count",count) + obj.get_location().emit_to_contents(s) def class_factory(source_obj): """ - This method is called any script you retrieve (via the scripthandler). It + This method is called by any script you retrieve (via the scripthandler). It creates an instance of the class and returns it transparently. source_obj: (Object) A reference to the object being scripted (the child). """ - return RedButton(source_obj) \ No newline at end of file + return RedButton(source_obj) + diff --git a/src/ansi.py b/src/ansi.py index 3cf9ad19b1..d4ab08e5e3 100755 --- a/src/ansi.py +++ b/src/ansi.py @@ -105,10 +105,42 @@ class MuxANSIParser(BaseParser): (r'%cw', ANSITable.ansi["white"]), (r'%cW', ANSITable.ansi["back_white"]), ] + +class ExtendedANSIParser(MuxANSIParser): + """ + Extends the standard mux colour commands with {-style commands + (shortcuts for writing light/dark text without background) + """ + def __init__(self): + super(ExtendedANSIParser, self).__init__() + hilite = ANSITable.ansi['hilite'] + normal = ANSITable.ansi['normal'] + self.ansi_subs.extend( [ + (r'{r', hilite + ANSITable.ansi['red']), + (r'{R', normal + ANSITable.ansi['red']), + (r'{g', hilite + ANSITable.ansi['green']), + (r'{G', normal + ANSITable.ansi['green']), + (r'{y', hilite + ANSITable.ansi['yellow']), + (r'{Y', normal + ANSITable.ansi['yellow']), + (r'{b', hilite + ANSITable.ansi['blue']), + (r'{B', normal + ANSITable.ansi['blue']), + (r'{m', hilite + ANSITable.ansi['magenta']), + (r'{M', normal + ANSITable.ansi['magenta']), + (r'{c', hilite + ANSITable.ansi['cyan']), + (r'{C', normal + ANSITable.ansi['cyan']), + (r'{w', hilite + ANSITable.ansi['white']), #white + (r'{W', normal + ANSITable.ansi['white']), #light grey + (r'{x', hilite + ANSITable.ansi['black']), #dark grey + (r'{X', normal + ANSITable.ansi['black']), #pure black + (r'{n', normal) #reset + ] ) -def parse_ansi(string, strip_ansi=False, strip_formatting=False, parser=MuxANSIParser()): +ANSI_PARSER = MuxANSIParser() +#ANSI_PARSER = ExtendedANSIParser() + +def parse_ansi(string, strip_ansi=False, strip_formatting=False, parser=ANSI_PARSER): """ Parses a string, subbing color codes as needed. """ return parser.parse_ansi(string, strip_ansi=strip_ansi, - strip_formatting=strip_formatting) \ No newline at end of file + strip_formatting=strip_formatting) diff --git a/src/commands/general.py b/src/commands/general.py index 37ae4a1693..c122b68552 100644 --- a/src/commands/general.py +++ b/src/commands/general.py @@ -303,7 +303,7 @@ def cmd_examine(command): con_exits.append(obj) elif obj.is_thing(): con_things.append(obj) - + # Render Contents display. if con_players or con_things: s += str("%sContents:%s" % (ANSITable.ansi["hilite"], diff --git a/src/commands/parents.py b/src/commands/parents.py index cbcca91270..8a5fdf079e 100644 --- a/src/commands/parents.py +++ b/src/commands/parents.py @@ -51,17 +51,18 @@ def cmd_parent(command): # Clear parent if command was @parent obj= or obj=none if not parent_name or parent_name.lower() == "none": target_obj.set_script_parent(None) + target_obj.scriptlink.at_object_creation() new_parent = target_obj.scriptlink() source_object.emit_to("%s reverted to its default parent (%s)." % (target_obj, new_parent)) return - # If we reach this point, attempt to change parent. - + # If we reach this point, attempt to change parent. former_parent = target_obj.get_scriptlink() if target_obj.set_script_parent(parent_name): #new script path added; initialize the parent target_obj.scriptlink.at_object_creation() + s = "%s's parent is now %s (instead of %s).\n\r" s += "Note that the new parent type could have overwritten " s += "same-named attributes on the existing object." diff --git a/src/helpsys/models.py b/src/helpsys/models.py index 936330e3e4..706366434b 100644 --- a/src/helpsys/models.py +++ b/src/helpsys/models.py @@ -32,4 +32,4 @@ class HelpEntry(models.Model): """ Gets the entry text for in-game viewing. """ - return ansi.parse_ansi(self.entrytext) \ No newline at end of file + return ansi.parse_ansi(self.entrytext) diff --git a/src/initial_setup.py b/src/initial_setup.py index 4080ff3ace..1f721d0590 100644 --- a/src/initial_setup.py +++ b/src/initial_setup.py @@ -32,6 +32,7 @@ def create_objects(): # Create the matching PLAYER object in the object DB. god_user_obj = Object(id=1, type=defines_global.OTYPE_PLAYER) god_user_obj.set_name(god_user.username) + god_user_obj.scriptlink.at_player_creation() god_user_obj.save() # Limbo is the initial starting room. @@ -39,6 +40,7 @@ def create_objects(): limbo_obj.set_owner(god_user_obj) limbo_obj.set_name('%ch%ccLimbo%cn') limbo_obj.set_description("Welcome to your new Evennia-based game. From here you are ready to begin development. If you should need help or would like to participate in community discussions, visit http://evennia.com.") + limbo_obj.scriptlink.at_object_creation() limbo_obj.save() # Now that Limbo exists, set the user up in Limbo. @@ -71,7 +73,7 @@ def create_config_values(): Creates the initial config values. """ ConfigValue(conf_key="default_home", conf_value="2").save() - ConfigValue(conf_key="idle_timeout", conf_value="1800").save() + ConfigValue(conf_key="idle_timeout", conf_value="3600").save() ConfigValue(conf_key="money_name_singular", conf_value="Credit").save() ConfigValue(conf_key="money_name_plural", conf_value="Credits").save() ConfigValue(conf_key="player_dbnum_start", conf_value="2").save() diff --git a/src/objects/managers/object.py b/src/objects/managers/object.py index 3e42b8a528..3a0f53ab50 100644 --- a/src/objects/managers/object.py +++ b/src/objects/managers/object.py @@ -79,6 +79,15 @@ class ObjectManager(models.Manager): return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE, defines_global.OTYPE_GOING]) + def global_object_script_parent_search(self, script_parent): + """ + Searches through all objects returning those which has a certain script parent. + """ + o_query = self.filter(script_parent__exact=script_parent) + return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE, + defines_global.OTYPE_GOING]) + + def list_search_object_namestr(self, searchlist, ostring, dbref_only=False, limit_types=False, match_type="fuzzy"): """ @@ -350,4 +359,4 @@ class ObjectManager(models.Manager): user_object.emit_to("Welcome to %s, %s.\n\r" % ( ConfigValue.objects.get_configvalue('site_name'), user_object.get_name(show_dbref=False))) - command.session.add_default_channels() \ No newline at end of file + command.session.add_default_channels() diff --git a/src/objects/models.py b/src/objects/models.py index fe97d7fa2a..d8700a4898 100755 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -18,6 +18,8 @@ from src import logger import src.flags from src.util import functions_general +from src.logger import log_infomsg + class Attribute(models.Model): """ Attributes are things that are specific to different types of objects. For @@ -598,8 +600,8 @@ class Object(models.Model): attrib_obj.attr_value = new_value attrib_obj.save() else: - if not new_value: - # Attribute object and we have given a doesn't exist, create it. + if new_value: + # No object currently exist, so create it. new_attrib = Attribute() new_attrib.attr_name = attribute new_attrib.attr_value = new_value @@ -800,10 +802,10 @@ class Object(models.Model): attrib: (str) The attribute's name. """ - if self.has_attribute(attrib): + if self.has_attribute(attrib): attrib = Attribute.objects.filter(attr_object=self).filter(attr_name=attrib) return attrib[0].attr_value - else: + else: return default def get_attribute_obj(self, attrib): diff --git a/src/scheduler.py b/src/scheduler.py index b1a27dcc50..bcba681be3 100644 --- a/src/scheduler.py +++ b/src/scheduler.py @@ -20,5 +20,12 @@ def add_event(event): Args: * event: (IntervalEvent) The event to add to the scheduler. """ - schedule.append(event) - event.start_event_loop() \ No newline at end of file + + #don't add multiple instances of the same event + if event in schedule: + return + #i = schedule.index(event) + #schedule[i] = event + else: + schedule.append(event) + event.start_event_loop() diff --git a/src/scripthandler.py b/src/scripthandler.py index 028b4981c2..cabd271300 100644 --- a/src/scripthandler.py +++ b/src/scripthandler.py @@ -58,7 +58,7 @@ def scriptlink(source_obj, scriptname): # Store the module reference for later fast retrieval. CACHED_SCRIPTS[scriptname] = modreference except ImportError: - logger.log_infomsg('Error importing %s: %s' % (modname, format_exc())) + logger.log_infomsg('Error importing %s: %s' % (scriptname, format_exc())) os.chdir(settings.BASE_PATH) return except OSError: