Reworked object command tables.

Object commands used to require re-adding every call in the script parent's __init__ or factory functions, adding the commands to a new command table directly on the object. Since all other attributes can be set up in at_object_creation(), this was both inconsistent and a bit confusing to work with. There is now a method add_commands() directly defined on all objects. It takes the same arguments as the normal add_command()o but use a reserved attribute to create and update a command table on the object. This has the advantange of completely removing the __init__ call in the script parent, all definitions can now be kept in at_object_creation() and are, more importantly, persistent without having to be recreated every call.
- I updated the examine command to show all the commands defined on an object (if any).
- I updated gamesrc/parents/examples/red_button.py considerably using the new command methodology and also using the updated Events.
.
Griatch
This commit is contained in:
Griatch 2009-12-03 00:41:53 +00:00
parent 5f6454ea1e
commit c7cbc4854e
7 changed files with 252 additions and 53 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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.

View file

@ -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:

View file

@ -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:

View file

@ -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):

View file

@ -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):
"""