The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Games::Object - Provide a base class for game objects

SYNOPSIS

    package MyGameObject;
    use Games::Object;
    use vars qw(@ISA);
    @ISA = qw(Games::Object);

    sub new {
        # Create object
        my $proto = shift;
        my $class = ref($proto) || $proto;
        my $self = $class->SUPER::new(@_);
        bless $self, $class;

        # Add attributes
        $self->new_attr(-name => "hit_points",
                        -type => 'int',
                        -value => 20,
                        -tend_to_rate => 1);
        $self->new_attr(-name => "strength",
                        -type => 'int',
                        -value => 12,
                        -minimum => 3,
                        -maximum => 18);
        ...

        return $self;
    }

    package MyObjectManager;
    use Games::Object::Manager;
    use vars qw(@ISA);
    @ISA = qw(Games::Object::Manager);

    sub new {
        my $proto = shift;
        my $class = ref($proto) || $proto;
        my $self = $class->SUPER::new(<my args>, @_);
        bless $self, $class;
        ...
        return $self;
    }


    my $world = new MyObjectManager;
    my $object = new MyGameObject;
    $world->add($object);

ABSTRACT

The purpose of this module is to allow a programmer to write a game in Perl easily by providing a basic framework in the form of a module that can be either subclassed to a module of your own or used directly as its own object class. The most important items in this framework are:

Attributes

You can define arbitrary attributes on objects with rules on how they may be updated, as well as set up automatic update of attributes whenever the object's process() method is invoked. For example, you could set an attribute on an object such that:

  • It ranges from 0 to 100.

  • Internally it tracks fractional changes to the value but accessing the attribute will always round the result to an integer.

  • It will automatically tend towards the maximum by 1 every time process() is called on the object.

  • A method in your subclass will be invoked automatically if the value falls to 0.

This is just one example of what you can do with attributes.

Flags

You can define any number of arbitrarily-named flags on an object. A flag is a little like a boolean attribute, in that it can have a value of either true or false. Like attributes, flags can be created independently on different objects. No "global" flag list is imposed.

Load/Save functionality

Basic functionality is provided for saving data from an object to a file, and for loading data back into an object. This handles the bulk of load game / save game processing, freeing the programmer to worry about the mechanics of the game itself.

The load functionality can also be used to create objects from object templates. An object template would be a save file that contains a single object.

Object Managers

New to version 0.10 of this module is object managers. An object manager is a Perl object that allows you to manage groups of related game objects. The object manager allows you to relate objects together (for example, you could define a relationship that allows certain objects to act as containers for other objects). In effect, the object manager acts as your world or universe.

Like the game object class, the manager class can be subclassed, allowing you augment its functionality. An object manager can be loaded and saved, which in turn performs a load or save of the objects being managed by it.

NOTE

This documentation is a bit long. I sometimes find it easier to view this in HTML format. To do this, in the directory from which you installed the module, enter:

    pod2html Object.pod > filename

Replace filename with a filename of your choice in some directory that is writable. You should then be able to view it in most browsers by typing in the URL "file://filename".

REQUIREMENTS

Games::Object requires perl 5.6.0 or better. While nothing in the modules restrict what Perl is required anymore, I have not tested this under older versions and do not support them.

You also will need IO::String, at least version 1.02 or better. You MUST have at least this version, as I used features specific to this version.

CHANGES

Version 0.11 is a patch release only to fix major bugs, no new functionality implemented. The following bugs were fixed:

  • Fixed bug in save() where -filename was not parsed properly.

  • Fixed bug in define_relation() where the relate_method was being used as the name passed from the relator instead of the actual name of the relationship. Changed relate.t test to tickle this bug again if it ever resurfaces.

  • Adding manager code broke flag inheritance. Fixed. Also added missing tests for flag inheritance to inherit.t

  • Undefined flags no longer croak(), but instead simply return false, in order to be consistent with the way attributes work.

COMPATIBILITY

Version 0.11 is compatible with version 0.10. Any workarounds you might have done to get around the aforementioned bugs should still continue to work.

Version 0.10 was a major restructuring that broke compatibility with earlier versions.

DESCRIPTION

Using Games::Object as a base class

This is the optimal way to use Games::Object. You define a game object class of your own as a subclass of Games::Object. In your constructor, you create a Games::Object classed object first, then re-bless it into your class. You can then add your object class' customizations. To insure that all your customizations can be potentially save()ed at a later time, you should add all your data to the object as attributes.

The main reason this is the ideal way to use this class will become clear when you reach the section that talks about events. Briefly, an event is defined as some change to the object, such as an attribute being modified or a boundary condition being reached. If you wish to provide code to be executed when the event is triggered, you must define it in the form of a method call. This is due to the fact that you would want your event mappings to be save()ed as well as your attributes, and CODE references cannot be written out and read back in.

Using Games::Object as a standalone module

Nothing explicitly prohibits the use of this module in this fashion. Indeed, the very idea behind OOP is that a class does not need to know if it is being subclassed or not. It is permissable to use "raw" Games::Object objects in this manner.

The only limitation is that you may not be able to define event mappings, due to the limitation stated above.

Using the object manager class Games::Object::Manager

This is a new feature in 0.10. It allows you to manage your objects in a larger object that acts as the "world". Objects you create are added to this world, and relationships can be maintained between objects.

One of the main advantages of using an object manager is that it greatly reduces the need for circular references. Using the manager, only one circular reference exists for each object (the manager contains a reference to the game object, the game object back to the manager). This reference is carefully broken when removing objects from the manager.

Creating Game Objects

Creating an empty object

Creating an empty object can be done simply as follows:

    $obj = new Games::Object;

When an object is created in this fashion, it generally has nothing in it. No attributes, no flags, nothing. There are no options at this time in the constructor to automatically add such things at object creation.

You can optionally add an ID to the object. The ID matters only when you add your object to an object manager. You can either define the ID now when you create the object, or wait until you are ready to add the object to a manager. If you wish to specify the ID now, you do so like this:

    $obj = new Games::Object(-id => "id-string");

Creating an object from an open file

You can instantiate a new object from a point in an open file that contains Games::Object data that was previous saved with save() by using the load() constructor instead of new():

    $obj = load Games::Object(-file => \*INFILE);

The argument to -file can be a simple GLOB reference or an IO::File object or FileHandle object, so long as it has been opened for reading already. You can even use an IO::String object, and thus load from a string containing an object definition.

The constructor will by default use as the ID of the object the ID that was stored in the file when it was saved (if one was defined on the original object when it was first saved to the file).

A simple way to implement a load-game functionality that takes place at game initialization would thus be to open the save file, and make repeated calls to load() until the end of file was reached. Note however that this functionality is provided in the object manager module, which is covered later in this document.

You can choose to override the ID stored in the file by passing an -id option to the constructor along with the -file option. This would in essence allow you to create duplicate objects if you were so minded. Example:

    my $fpos = tell INFILE;
    my $obj1 = load Games::Object(-file => \*INFILE);
    seek(INFILE, $fpos, SEEK_SET);
    my $obj2 = load Games::Object(-file => \*INFILE, -id => $obj1->id() . "COPY");

Creating an object from a template file

In this case "template" is simply a fancy term for "a file that contains a single object definition". It is simply a convenience; rather than opening a file yourself and closing it afterward just to read one object, this does those operations for you:

    $obj = new Games::Object(-filename => "creatures/orc.object");

All it really is a wrapper around a call to open(), a call to the constructor with a -file argument whose value is the newly opened file, and a call to close(). As with -file, it obtains the ID of the object from the file, but you can specify an -id option to override this. Example:

    $obj = load Games::Object(-filename => "creatures/orc.object", -id => "Bob");

Obtaining the ID of a created object

The ID of a valid object is obtained with the id() method. Example:

    if ($obj->id() eq 'HugeRedDragon') {
        print "Oh-oh, NOW you've done it ...\n";
        ...
    }

(Note that as of 0.10, you can take advantage of overloaded operators and not have to invoke the id() method at all. See next section for details.)

Destroying objects

If you are not using a manager, you will generally have no need to explicitly destroy a game object, as it will be cleaned up when it goes out of scope. If you are using a manager, however, which makes the object persistent, you can call destroy() as an alternative to calling the manager's remove() method. In fact, destroy() will in turn call remove() for you if the object is managed.

The destroy() method will also invoke any on_destroy action defined on the object before calling the manager remove() method.

Comparing objects

As of version 0.10, basic string and numeric comparison operators (cmp and <=>) are overloaded. String comparisons are performed against the id() of the object, and numeric comparisons against the priority value. For example, this lets you do things like this:

    if ($object1 eq $object2) { ... }
    if ($object1 eq 'RedDragon') { ... }
    if ($object1 > $object2) { ... }

Which are the equivalents of:

    if ($object1->id() eq $object2->id()) { ... }
    if ($object1->id() eq 'RedDragon') { ... }
    if ($object1->priority() > $object2->priority()) { ... }

Where $object1 and $object2 are references to Games::Object objects, or objects of subclasses of Games::Object.

This overloading is accomplished via the use overload directive. This means your subclass is free to override these and provide your own overload definitions. This will not alter the operation of Games::Object, as these overloaded operators are provided for your convenience and are not used internally.

Using an object manager, the basics

Why use a manager?

Prior to version 0.10 of this module, all objects that you created were inherently managed by the Games::Object module. The main problem with this is that it limits your ability to augment or modify the way the module manages the objects. It also prevents you from having multiple groups of related objects independent of each other. Finally, the way it was implemented violated the spirit if not the letter of object-oriented programming rules.

Thus in version 0.10, all management functionality has been moved to a separate module called Games::Object::Manager. This module does what the various capitalized function calls did in the older versions of the module. Not only does this better adhere to OO programming rules, it allows you to much more easily augment manager functionality by simply subclassing from it.

Using an object manager will also allow you to establish relationships between objects and let the manager do the dirty work of maintaining these relationships and triggering the right actions when relationships are forged or broken.

Creating an Object Manager

To create an object manager is rather like creating a game object:

    my $manager = new Games::Object::Manager;

Likewise, you can subclass from Games::Object::Manager, and instead call the constructor of your subclass:

    my $world = new MyGames::Dungeons::World;

Creating an object manager in this manner creates it empty, meaning it has no objects in it. It does have one object relationship defined by default, which will be described in a later section.

Adding an Object to a Manager

Once you have a manager created, you can add objects to it. You first create an object using the Games::Object constructor (or the appropriate subclass constructor), then add it with the add() method:

    $manager->add($object);

In this form of the call, the manager will look on $object for an ID to use in its internal index. If you did not define an ID when you created the object, the manager will assign a guaranteed unique ID for you.

You can specify the exact ID you want when you call add():

    $manager->add($object, "brass lantern");

If $object already had an ID defined on it, this will both override it and cause the ID to be modified on the object to the new value.

Once you add an object to a manager, that object is now persistent. This means that, using the example above, if $object goes out of scope, the object will continue to exist, as the manager maintains a reference to the object. Thus the manager frees you from having to maintain your own object lists.

Retrieving a previously created object

As mentioned in the previous section, when using a manager, objects are persistent, even after the initial variable containing the reference to it goes out of scope. If you have the ID of the object, you can later retrieve a reference to the object via find():

    my $object = $manager->find('Sam the ogre');

If the ID specified is not a valid object, find() will return undef.

The find() method takes an additional optional argument that is the assertion flag. Setting this to a true value will cause find() to croak with an error of the format:

    Assertion failed: '$id' is not a valid/managed object ID

This is done via a call to confess() rather than croak() so that you can see a stack trace of the calls from your program that led to the invalid object.

Example:

    $manager->find('The Player', 1);

Note that the find() method can also be passed a game object reference instead of an ID. In this case, the method will verify that the passed object is indeed one being managed by that manager. If the object is indeed valid, it will return the object reference, otherwise it returns undef.

Removing an object from the manager

You can choose to remove an object from the manager by calling the remove() method on the manager, and pass it either the ID or the object reference of the object to remove. This method call will return the reference to the object it removed, or undef if the object was not found in the manager. Examples:

    $manager->remove($creature);
    $manager->remove("Sam the Ogre");

Naturally, you don't have to store the reference that is returned. You can simply call it in a void context (like above) and let the object go out of scope and into oblivion.

Validating an object ID

Likewise, there is an id() method on the manager that also validates an object in a manner similar to find(), except that it returns the ID on success rather than the object reference.

Using id() and find() allows you to build code that does not have to know whether it is receiving an object reference or an ID string as an argument. If variable $foo contains either an object or an ID, but you don't know which, then this:

    $manager->find($foo);

is guaranteed to return the object reference no matter what $foo is, and this:

    $manager->id($foo);

is guaranteed to return the ID no matter what, and both will return undef if $foo is neither.

Like find(), id() can take an assertion flag that will bomb the program with a stack trace if it is not valid.

User-defined Flags

Creating flags

A user-defined flag is any arbitrary string that you wish to use to represent some sort of condition on your objects. For example, you might want a flag that indicates if an object can be used as a melee weapon. Flags are defined on a per-object basis as you need them with the method new_flag():

    $object->new_flag(-name => "melee_weapon", -value => 1);

The only restriction on flag names is that they cannot contain characters that could be interpretted as file-control characters (thus you can't have imbedded newlines), or the "!" character (which is reserved for future planned functionality). If you stick to printable characters, you should be fine.

Note in this example that the initial value of the flag is set. If omitted, the default is for the flag to be off (or 0).

Because flags are not defined globally, you will have to define the flag on all the objects that require them, whether the initial value is on or off. Later, an easy way of doing this for large groups of objects will be presented.

Setting/clearing flags

You may set a user-defined flag on an object with the set() method:

    $obj->set('melee_weapon');

You can choose to set multiple flags at one time as well by specifying an array instead of a scalar:

    $obj->set( [ 'melee_weapon', 'magical', 'bladed' ] );

Setting a flag that is already set has no effect and is not an error. The method returns the reference to the object.

Clearing one or more flags is accomplished in similar fashion with the clear() method. Like set(), it can clear multiple flags at once:

    $obj->clear('cursed', 'wielded');

Fetching flag status

Two methods are provided for fetching flag status, is() and maybe().

The is() method returns true if the flag is set on the object. If more than one flag is specified, then ALL flags must be set. If even one is not set, false is returned. For example:

    if ($weapon->is('cursed', 'wielded')) {
        print "It is welded to your hand!\n";
        ...
    }

The maybe() method works the same as is() for a single flag. If multiple flags are present, however, it requires only that at least one of the specified flags be set to be true. Only if none of the flags are present will it return false. Example:

    if ($weapon->maybe('rusted', 'corroded', 'broken')) {
        print "It's not looking in good shape. Sure you want to use it?\n";
        ...
    }

Attributes

This is the heart of the module. Attributes allow you to assign arbitrary data to an object in a controlled fashion, as well as dictate the rules by which attributes are modified and updated.

Creating Attributes

Simple attributes

A simple attribute has a name that uniquely identifies it, a datatype, and the starting value. The name needs to be unique only in the confines of the object on which the attribute is defined. Two different objects with an attribute of the same name retain separate copies of the attribute. They do not even need to be the same datatype.

An attribute of type number can take on any valid decimal numeric value that Perl recognizes. Such an attribute can be created as follows:

    $obj->new_attr(-name => "price",
                   -type => "number",
                   -value => 1.99);

Any attempt to set this to a non-numeric value later would be treated as an error.

The datatype of int is similar to number except that it restricts the value to integers. Attempting to set the attribute to a numeric that is not an integer, either when created or later modified, is not an error, but the result will be silently truncated as if using the Perl int() function. An int attribute can be created as follows:

    $obj->new_attr(-name => "experience",
                   -type => "int",
                   -value => 0);

An attribute of type string is intended to contain any arbitrary, printable text. This text can contain newlines and other text formatting characters such as tabs. These will be treated correctly if the object is later saved to a file. No special interpretation is performed on the data. Such an attribute can be created as follows:

    $obj->new_attr(-name => "description",
                   -type => "string",
                   -value => "A long blade set in an ornamental scabbard of gold.");

The any datatype is used for data that does not fall into any of the above categories. No particular interpretation is performed on the data, and no special abilities are associated with it. Use this datatype when you wish to store references to arrays or hashes. The only caveat is that these complex data structures must eventually work down to simple scalar types for the data in the attribute to be save()d correctly later. Do not use this for object references, except for objects subclassed to Games::Object (this is covered in more detail in an upcoming section). Here is an example of using the any datatype:

    $obj->new_attr(-name => "combat_skill_levels",
                   -type => "any",
                   -value => {
                        melee           => 4,
                        ranged          => 2,
                        hand_to_hand    => 3,
                        magical         => 5,
                   });

There is one more datatype called object, which is intended to provided a way for storing an object reference in an attribute. However, as there are some special caveats and setup required, this is covered as a separate topic.

Split attributes

A "split" attribute is available only to datatypes number and int. An attribute that is split maintains two separate values for the attribute, a "real value" and a "current value" (or simply the "value"). An attribute that is split in this way has the following properties:

  • By default, when retrieving the value, the current value is returned.

  • The current value will "tend towards" the real value when the object's process() method is called (covered in a later section).

  • Both the current and real values can be manipulated independent of one another (except where noted above with regards to the "tend to" processing).

A split attribute is defined by specifying the additional parameter -tend_to_rate, as in this example:

    $obj->new_attr(-name => "health",
                   -type => "int",
                   -tend_to_rate => 1,
                   -value => 100);

This indicates that each time the object is processed, the current value will tend towards the real by 1. The tend-to rate is always treated as a positive number. Its sign is adjusted internally to reflect what direction the current needs to go to reach the real (thus in this case if the real were less than the current, 1 would be subtracted from the current when the object was processed).

Note in the above example that in the absense of specifying what the starting real value is, the real value will start off set to the current (in this case, the value of 100). If you wish to start off the real at a different value than the current, you add the -real_value option, as in this example:

    $obj->new_attr(-name => "power",
                   -type => "number",
                   -tend_to_rate => 0.2,
                   -value => 0,
                   -real_value => 250);
Limited attributes

An attribute's value can be "limited", in that it is not allowed to go beyond a certain range or a certain set of values.

Attributes of type number and int can be limited in range by adding the -minimum and -maximum options when the attribute is created. Note that you can choose to use one or the other or both. Example:

    $obj->new_attr(-name => "hit_points",
                   -type => "int",
                   -tend_to_rate => 1,
                   -value => 20,
                   -minimum => 0,
                   -maximum => 50);

By default, attempts to modify the attribute outside the range will cause the modifying value to be "used up" as much as possible until the value is pegged at the limit, and the remainder ignored. In the above example, if the current value were 5, and an attempt to modify it by -7 were attempted, it would be modified only by -5 as that would put it at the minimum of 0. This default behavior can be modified with the -out_of_bounds option, which is a string that has one of the following values:

use_up

Use up as much of the modifying value as possible (the default).

ignore

Ignore the modification entirely. The value of the attribute will not be changed.

track

Operates like use_up, except that the excess is tracked internally. Subsequent attempts to modify the attribute the other way will have to use up this amount first.

Using the track option when creating the attribute is exactly like specifying the -force option to every call to mod_attr().

Attributes of type string can be limited by specifying a set of allowed values for the attribute. This is done when the attribute is created by adding the -values option. This is a reference to an array of strings that constitute the only allowable values for this attribute. For example:

    $obj->new_attr(-name => "status",
                   -type => 'string',
                   -values => [ 'quiet', 'moving', 'attacking', 'dead' ],
                   -value => 'quiet');
Mapped attributes

This feature is available only to string attributes. This allows you to map the actual value of the attribute such that when it is retrieved normally, some other text is returned instead. This is done by adding a -map option when the attribute is created. The argument to -map is a reference to a hash containing the allowed values of the attribute as keys, and the corresponding values to be returned when the attribute is fetched as values. For example:

    $obj->new_attr(-name => "status",
                   -values => [ 'quiet', 'moving', 'attacking', 'dead' ],
                   -value => 'quiet',
                   -map => {
                        quiet   => "It appears quiescent.",
                        moving  => "It is moving stealthily.",
                        attacking => "It is attacking you!",
                        dead    => "It's dead, Jim.",
                   } );

Note that the above example used -map with -values, but you're not required to do this. With this setup, retrieving the value of this attribute when it is set internally to "dead" will cause "It's dead, Jim." to be returned instead.

Object reference attributes

Object references have been vastly simplified in version 0.10 of this module. Storing a reference is as easy as specifying the object data type:

    $other_obj = new Some::Other::Class;
    $obj->new_attr(-name => "some_other_object",
                   -type => "object",
                   -value => $other_obj);

And you can modify an existing value with mod_attr():

    $obj->mod_attr(-name => "some_other_object",
                   -value => $another_obj);

HOWEVER ...

There is one caveat with this. The class for that object MUST define the following methods:

load()

This is responsible for creating a new object of the appropriate class and loading its data from the file at the present file position. This is passed a single argument, the file object, which you can assume is compatible with a file glob, IO::File or related object, or FileHandle object.

This is called as a class method, so the code should expect the first argument to be the class name.

save()

This is responsible for writing the data of the object to the save file in a format that is readable by the corresponding load() method. As with load, it is passed a file object.

Do not store other Games::Object or Games::Object subclassed objects in the attribute. This will result in a possible infinite loop on a save of the file. If you MUST know something about another object, store the ID of the object, or set the DONTSAVE flag on the attribute (this flag is covered in the next item).

Other attribute tricks

There are a few more things you can do with attributes at creation time.

Recall above that I stated that by default, if you assign a fractional value to an attribute that is of type int that it stores it as if calling the Perl int() function. Well, this behavior can be modified. You can specify the -on_fractional option when creating the attribute. This can be set to one of "ceil", "floor", or "round". When a fractional value results from an assignment or modification, the corresponding function in the Perl POSIX module is called on the result (in the case of "round", which is not a POSIX function, a function that performs rouding is provided internally in the module). Example:

    $obj->new_attr(-name => "time",
                   -type => "int",
                   -on_fractional => "round",
                   -value => 0);

There's even more you can do with fractional amounts on integer attributes. You can instruct the object to track the fractional component rather than just throw it away. Retrieving the value will still result in an integer, which by default is derived by int()ing the factional value. For example, say that an attribute is defined like this initially:

    $obj->new_attr(-name => "level",
                   -type => "int",
                   -track_fractional => 1,
                   -value => 1,
                   -maximum => 10);

Initially, retrieving the value will result in 1. Say you later add 0.5 to it. Internally, 1.5 is stored, but 1 still results when retreiving the value. If later the value becomes 1.99999, 1 is still returned. Only when it reaches 2.0 or better will 2 be returned.

You can combine -track_fractional and -on_fractional. In this case, -on_fractional refers to how the value is retrieved rather than how it is stored. Say we change the above definition to:

    $obj->new_attr(-name => "level",
                   -type => "int",
                   -track_fractional => 1,
                   -on_fractional => "round",
                   -value => 1,
                   -maximum => 10);

Now if the internal value is 1.4, retrieving it will result in 1. But if the internal value reaches 1.5, now retrieving the value will return 2.

Finally, there is a special option to new_attr() called -flags. This allows you to specify one or more flags that affect attribute behavior. These flags are defined as single-bit flags, thus multiple flags are bitwise or'ed together (using the | operator). Constants for these values are defined in Games::Object but are not exported by default. You may import them into your namespace by specifying the :attrflags tag in the use statement. Example:

    use Games::Object qw(:attrflags);

This will define the following flags:

ATTR_STATIC

This indicates that the value of this attribute is static, meaning it will not change. Any attempt to modify it using mod_attr() will fail. You can circumvent this using attr_ref().

ATTR_DONTSAVE

This tells the object not to save this attribute to the save game file when the save() method is invoked. Likewise, if you perform a load() in place of an existing object, the current values of these attributes will be preserved.

ATTR_AUTOCREATE

This flag has an effect only when used with ATTR_DONTSAVE. Normally, if you create an object with a ATTR_DONTSAVE flag, then destroy and reload the object from a file fresh via new() with the -file option, this attribute will disappear. If the attribute were created with the ATTR_AUTOCREATE flag before it was saved to the file, then loading this object will cause the attribute to be auto-created for you.

The initial value of such an auto-created attribute depends on the datatype of the attribute:

int or number

The starting value is 0, unless the attribute specified a -minimum attribute, in which case this value is used.

string

The starting value is an empty string.

any

Dependent on the type of data first assigned to it when it was created. If this was a simple scalar, this will be set to an empty string. If an array or hash reference, it will be set to an empty array or hash reference.

object

Always initialized to undef. This behavior may change in the future.

Think of this feature as similar to autovivication, but extended to the attributes.

ATTR_NO_INHERIT

Do not allow objects inherting from this one to inherit this attribute. It will act as if the attribute does not exist. See the section "Attribute inheritance" for details on how this works.

ATTR_NO_ACCESSOR

Do not create an accessor method for this attribute if the accessor method global option is turned on. See "Attribute accessors" for more details.

Here is an example of creating an attribute that will not be saved to a file but will be rereated when the object is created fresh from a previous save file:

    $obj->new_attr(
        -name   => "widget_table",
        -type   => "any",
        -value  => {
            canvas      => $mainw->Canvas(...),
            title       => $mainw->Label(...),
        },
        -flags  => ATTR_DONTSAVE | ATTR_AUTOCREATE,
    );

If this object is later saved to a file, then destroyed, then recreated from that file, this attribute will be auto-created and the value set to a hash reference pointing to an empty hash.

Attribute Existence

You can check to see if an attribute exists with attr_exists():

    if ($obj->attr_exists('encounters')) {
        $obj->mod_attr(-name => 'encounters', -modify => 1);
    } else {
        $obj->new_attr(-name => 'encounters',
                       -type => 'int',
                       -value => 1);
    }

Fetching Attributes

An attribute's value is fetched with the attr() method:

    $str = $obj->attr('strength');

This is subject to all the interpretations mentioned above, which is summarized below:

  • If the attribute is split, the current value is returned.

  • If the attribute is an integer tracking fractionals, an integer is still returned, calculated according to the value of the -on_fractional option when the attribute was created.

  • If the attribute is mapped, and there is a valid entry in the map table, the mapped value is returned.

To retrieve the real value as opposed to the current value in a split attribute, specify the string "real_value" as the second argument:

    $realhp = $obj->attr('hit_points', 'real_value');

This is still subject to rules of factionals and mapping. To completely bypass all of this, retrieve the value with raw_attr() instead:

    $rawlev = $obj->raw_attr('level');
    $rawlev_real = $obj->raw_attr('level', 'real_value');

An important note when dealing with attributes of datatype any that are array or hash references: When you use either attr() or raw_attr() (which are effectively the same thing in this case), you get back the reference. This means you could use the reference to modify the elements of the array or keys of the hash. This is okay, but modifications will not generate events. Here is an example (building on the example above for creating an attribute of this type):

    $cskill = $obj->attr('combat_skill_levels');
    $cskill->{melee} ++;

In all cases, if the attribute specified does not exist, undef is returned.

Retrieving the reference to an attribute value

You may also retrieve a reference to the stored attribute value with the attr_ref() method. Example:

    $aref = $obj->attr_ref('hit_points');

This method can be used like attr(), in that you can select either current value or the real value in the case of a split attribute. If you attempt to retrieve a real_value when none exists, undef is returned instead. If you attempt to retrieve a reference to an attribute that does not exist, you also get back undef, but a warning is printed to STDERR, so that a subsequent "Can't use undef as a <X> reference" Perl error is not so mysterious.

This should be used with care. If you modify the value that the reference points to, you circumvent the event code (i.e. the change will not spawn attribute modified events). The purpose of this method is to allow hooks into other Perl modules that act on changes to a variable's value. A perfect example of this would be Tk. Say you design an interface to a game in Perl Tk and want to use a Tk::ProgressBar widget to show the player's current health. The constructor for this widget takes a -variable parameter, which is a reference to a variable whose value affects the position of the ProgressBar automatically. By specifying this parameter in the following way:

    -variable   => $player->attr_ref('hit_points'),

You now allow the ProgressBar to update automatically when the current value for the player's hit points change. Otherwise, you would have to create a package global variable to store the hit points, specify it in the the Tk::ProgressBar constructor, write a method to copy the hit points attribute value into the variable, and perform a bind_event() to call the method when the attribute changes. This is far too much work for something that should be simple.

Modifying Attributes

Modifying attributes is where a lot of the strengths of attributes lie, as the module tries to take into account typical modifier situations that are found in various games. For example, sometimes an attribute needs to be modified only temporarily. Or a modification could be thwarted by some other outside force and thus negated. And so on.

Simple modifiers

A simple modifier is defined as a modification that occurs immediately and is not "remembered" in any way. No provisions are made for preventing multiple modifications within a given cycle, either through time or code. The value of the attribute is changed and the deed is done.

There are two ways to perform a simple modification. One is to set the value directly, which would be done as in the following examples:

    $obj->mod_attr(-name => "color", -value => "red");
    $obj->mod_attr(-name => "price", -value => 2.58);
    $obj->mod_attr(-name => "description", -value => "A piece of junk.");

If an attribute is split, this would set the current value only. The real value could be set by using -real_value instead of -value:

    $obj->mod_attr(-name => "health", -real_value => 0);

The other way is to modify it relative to the current value. This is available only to numeric types (int and number) as in these examples:

    $obj->mod_attr(-name => "hit_points", -modify => -4);
    $obj->mod_attr(-name => "strength", -modify => -1);

In these cases, -modify modifies the current value if the attribute is split. To change the real value, you would use -modify_real instead.

Persistent modifiers

A persistent modifier is one that the object in question "remembers". This means that this modifier can later be cancelled, thus rescinding the blessing (or curse) that it bestowed on this attribute.

Currently, this type of modifier is limited to numeric types, and must be of the relative modifier type (via -modify or -modify_real). In addition, it should be noted that the results of a persistent modifier are NOT applied immediately. They are instead applied the next time the object is process()ed. That said, all that is needed to turn a modifier into a persistent one is adding a -persist_as option:

    $obj->mod_attr(-name => "strength",
                   -modify => 1,
                   -persist_as => "spell:increase_strength");

The value of -persist_as becomes the ID for that modifier, which needs to be unique for that object. The ID should be chosen such that it describes what the modification is, if for no other reason than your programming sanity.

What happens now is that the next time process() is called on the object, the "strength" attribute goes up by 1. This modification is done once. In other words, the next time after that that process() is called, it does NOT go up by another 1.

However, this does not mean you can't have it keep going up by 1 each time if that's what you really wanted. In order to accomplish this effect, add the -incremental option:

    $obj->mod_attr(-name => "health",
                   -modify => 3
                   -persist_as => "spell:super_healing",
                   -incremental => 1);

In this example, the "health" attribute will indeed increment by 3 EVERY time process() is called.

There is another important difference between incremental and non-incremental persistent modifiers. A non-incremental modifier's effect is removed when the modifer is later cancelled. Thus in the above example, if the "strength" modifier caused it to go from 15 to 16, when the modifier is removed, it will drop back from 16 to 15. However, in the case of the incremental modifier, the effects are permanent. When the "health" modifier goes away, it does not "take away" the accumulated additions to the attribute.

Note that the effects of modifiers and tend-to rates are cumulative. This needs to be taken into account to make sure modifiers are doing what you think they're doing. For instance, if the idea is to add a modifier that saps away health by -1 each time process() is called, but the health attribute has a -tend_to_rate of 1, the net effect will simply be to cancel out the tend-to, which may or may not be what you wanted. Future directions for this module may include ways to automatically nullify tend-to rates.

Also note that modifiers are still subject to limitations via -minimum and -maximum options on the attribute.

Self-limiting modifiers

It was noted above that persistent modifiers stay in effect until they are purposely cancelled. However, you can set up a modifier to cancel itself after a given amount of time by adding the -time option:

    $obj->mod_attr(-name => "wisdom",
                   -modify => 2,
                   -persist_as => "spell:increase_wisdom",
                   -time => 10);

In this case, -time refers to the number of times process() is called (rather than real time). The above indicates that the modification will last through the next 10 full calls to process(). These means that after the 10th call to process(), the modification is still in effect. Only when the 11th call is made is the modifier removed.

A self-limiting modifier can still be manually cancelled like any other persistent modifier.

Applying persistent modifier effects immediately

As stated above, the usual behavior of persistent modifiers is that they do not take effect immediately, but rather when the next process() call is made on the object.

With version 0.05 of Games::Object, you can force a persistent modifier to take effect immediately. This is done by using the -apply_now option to the mod_attr() call. Setting this to a true value will cause mod_attr() to apply the effects of the modifier right now. This also means that any events associated with modifying the attribute will be triggered at this time.

Be careful when using this feature with incremental modifiers. This means that the target attribute will be modified now, and then again when the process() is next called on the object.

Delayed-action modifiers

A persistent modifier, either one that is timed or not, can be set up such that it does not take effect for a given number of iterations through the process() method. This is done via the -delay option, as in this example:

    $obj->mod_attr(-name => "health",
                   -modify => -5,
                   -incremental => 1,
                   -persist_as => "food_poisoning",
                   -time => 5,
                   -delay => 3);

This means: For the next 3 calls to process(), do nothing. On the 4th, begin subtracting 5 from health for 5 more times through process(). The last decrement to health will take place on the 8th call to process(). On the 9th call, the modifier is removed.

Note that while this example combined -delay with -time and -incremental to show how they can work together, you do not have to combine all these options.

A delayed-action modifier can be cancelled even before it has taken effect.

Cancelling persistent modifiers

Any persistent modifier can be cancelled at will. There are two ways to cancel modifiers. One is to cancel one specific modifier:

    $obj->mod_attr(-cancel_modify => 'spell:increase_wisdom');

Note that the -name parameter is not needed. This is because this information is stored in the internal persistent modifier. You only need the ID that you specified when you created the modifier in the first place.

Or, you can choose to cancel a bunch of modifiers at once:

    $obj->mod_attr(-cancel_modify_re => '^spell:.*');

The value of the -cancel_modify_re option is treated as a Perl regular expression that is applied to every modifier ID in the object. Each that matches will be cancelled. Any matching modifiers on that object will be cancelled, no matter what attribute they are modifying. This makes it easy to cancel similar modifiers across multiple attributes.

For each non-incremental modifier that is cancelled, mod_attr() will reverse the modification that was made to the attribute, but not right away. It will instead take place the next time process() is called. To override this and force the change at the very moment the cancellation is done, include the -immediate option set to true, as in this example:

    $obj->mod_attr(-cancel_modify_re => '^spell:.*',
                   -immediate => 1);

The -force option

Any modification of an attribute via mod_attr() may take the -force option. Setting this to true will cause the modifier to ignore any bounds checking on the attribute value. In this manner you can force an attribute to take on a value that would normally be outside the range of the attribute.

For example, the following modification would force the value of the attribute to 110, even though the current maximum is 100:

    $obj->new_attr(-name => "endurance",
                   -value => 90,
                   -minimum => 0,
                   -maximum => 100);
    ...
    $obj->mod_attr(-name => "endurance",
                   -modify => 20,
                   -persist_as => "spell:super_endurance",
                   -force => 1);

At the same time, however, a call to attr() to return the value of the attribute will still only return values in the range of 0 to 100. This can be very useful in that you can allow modifications to go above or below the bounds internally, but still allow only the proper ranges from the point of view of the program.

Modifying attribute properties

Various properties of an attribute normally set at the time the attribute is created can be modified later. These changes always take effect immediately and cannot be "remembered". The general format is:

    $obj->mod_attr(-name => ATTRNAME,
                   -PROPERTY => VALUE);

where PROPERTY is one of "minimum", "maximum", "tend_to_rate", "on_fractional", "track_fractional", "out_of_bounds".

Deleting Attributes

To delete an attribute, use the del_attr() method:

    $obj->del_attr('xzyzzy');

This removes the attribute immediately. Any persistent modifiers on this attribute are removed at the same time.

Attribute accessors

New in version 0.10 is an optional feature called attribute accessors. This is a way of automatically defining new object methods when methods are first created or loaded into an object from a save file.

To turn on this feature, you will need to see a global variable to a true value before creating or loading objects:

    $Games::Object::AccessorMethod = 1;

When you turn this option on, generating a new attribute with new_attr() or by loading an object from a file will create two accessor methods to allow you to retrieve the value of the attribute and modify it in simple ways. For example, if you create an attribute called foo after turning on this option, you will be able to make the following calls:

$object->foo()

Returns the current value of attribute foo. This is the equivalent of:

    $object->attr('foo');
$object->foo($value)

Sets the attribute to the new value, which is the equivalent of this:

    $object->mod_attr(-name => "foo", -value => $value);
$object->mod_foo($value)

Modifies the value relative to the current value, as if this call were made:

    $object->mod_attr(-name => "foo", -modify => $value);

In the last form described above, if you wish to do things like set up a persistent modifier, you can do so by specifying the additional parameters that you would normally pass to mod_attr() at the end of the parameter list. For example, this:

    $object->mod_foo(2,
        -persist_as   => "foo_boost",
        -incremental  => 1,
        -time         => 5);

is the same as:

    $object->mod_attr(
        -name         => 'foo',
        -modify       => 2,
        -persist_as   => "foo_boost",
        -incremental  => 1,
        -time         => 5);

When accessors are turned on, you can selectively choose not to create accessors for certain attributes by using the ATTR_NO_ACCESSOR flag.

A note on using -minimum/-maximum and persistent modifiers

If you choose to use persistent modifiers combined with self-limiting attributes, there are a few pitfalls that you need to watch out for.

By default, modifications made to an attribute are done in the use_up mode. This means that any modifications beyond what is needed to bring the value to minimum or maximum is discarded. When combined with persistent modifiers, this may not result in what you want.

For example, say you have an attribute that ranges from 0 to 100 and is currently at 95. It was created with the default use_up option for its -out_of_bounds parameter. Say you apply a persistent modifier of 10 with no special options. Because of the use_up option, only 5 is added to the value, resulting in 100. If later you cancel the modifier, or it was timed and expires, it will apply -10 to the value, bringing it down to 90. This is probably not what you intended.

This problem can be solved one of two ways:

  • Use the -force option in the mod_attr() call. This will force the attribute to 105 internally, but a normal call to attr() will return 100.

  • Specify the track option with the -out_of_bounds parameter when the attribute was created. This will do the same thing, i.e. allow the value to increase intenally to 105 but report 100 to the user.

In retrospect, it appears that perhaps track is better as the default rather than use_up.

Object Actions

Introduction

Actions are user-defined bits of code that are designed to execute when certain things happen to your objects or attributes and flags on objects. This allows you to program into your objects a set of bahavior rules that automatically trigger when things happen, saving you the trouble of having to check for these events yourself.

Callback programming model

In order to understand how actions work, you must first understand the callback programming model.

Callback programming is a technique where you define a chunk of code not to be run directly by you, but indirectly when some external event occurs. If you've ever done any graphics or signal programming, you've done this before. For instance, in Tk you might define a button to call some arbitrary code when it is pressed:

    $mainw->Button(
        -text   => "Press me!",
        -command => sub {
            print "Hey, the button was pressed!\n";
            ...
        },
    )->pack();

Or you may have set up a signal handler to do something interesting:

    sub stop_poking_me
    {
        my $sig = shift;
        print "Someone poked me with signal $sig!\n";
    }

    $SIG{TERM} = \&stop_poking_me;
    $SIG{INT} = \&stop_poking_me;

These are examples of callback programming. Each example above defines a set of code to be run when a particular condition (or "event") occurs. This is very similar to the way it works in Games::Object, except you're dealing with events that have to do with Game::Object entities. There is only one crucial difference, which has to do with the way the module is structured, as you'll see in the next section.

Callback structure

A single callback is represented as a reference to an array that consists of the following elements, in this order:

  • A Games::Object or Games::Object subclassed object.

  • A method to call on that object.

  • Zero or more arguments to pass to the method call.

The first parameter, the object, is specified symbolically. To understand this, you must first understand that each action is considered to have from one to three objects involved, which go by the following symbolic names:

O:self

This refers to the object on which the action callback has been defined. This object will always be defined.

O:other

This is the object that caused the action to happen. This may or may not be defined depending on the nature of your action and the way it was invoked.

O:object

If an action involves the interaction of two objects, the other object can be referenced in this manner. Again, whether this is defined will depend on the action and how it was invoked.

Knowing this, consider this example of a callback:

    [ 'O:self', 'mod_hit_points', 5 ]

Assuming that you define an attribute name hit_points with attribute accessors turned on, this would invoke the following when called:

    $self->mod_hit_points(5);

where $self is the object on which the action was defined. You're not limited to calling methods defined only in Games::Object; you can call whatever methods you may have creating in your subclass.

As well as calling methods on them, you can also pass the other objects as parameters to your method call. For example:

    [ 'O:self', 'strike', 'O:object', 'O:other' ]

If we assume that strike() is a method that registers a blow with a weapon on a character, O:object could represent the weapon being used and O:other the creature striking the blow.

As a convienience, you can also reference O:manager as an object argument. This represents O:self's object manager, thus allowing you to invoke methods on the manager as well, if you wish your callback to manipulate the objects in the manager in some way.

You can specify arguments as the results of method calls or other calculations on object symbols. For example, you could do something like this:

    [ 'O:self', 'mod_hit_points', 'O:object->hit_power(O:other)' ]

This passes to mod_hit_points the result of the following when executed:

    $object->hit_power($other);

In this example, hit_power() could be a method you devised that computes the amount of striking power of weapon $object when wielded by $other.

Multiple callbacks

Anywhere that a callback is called for, you can specify multiple callbacks, defined as multiple callback array refs in a larger array. Example:

    [
      [ 'O:self', 'mod_hit_points', 'O:object->hit_power(O:other)' ],
      [ 'O:other', 'mod_melee_skill', 1 ],
    ]

When invoked, the above example will call two callbacks in the order they are defined. What's more, you can use the return code of a callback to affect whether remaining callbacks in the list will be executed. If a callback in a list returns true, the next callback will be invoked. If it returns false, no further callbacks on this list will be invoked. Thus, extending the example above, you could do something like this:

    [
      [ 'O:self', 'can_be_hit', 'O:object', 'O:other' ],
      [ 'O:self', 'mod_hit_points', 'O:object->hit_power(O:other)' ],
      [ 'O:other', 'mod_melee_skill', 1 ],
    ]

In this example, it is assumed that you have created a method called can_be_hit() in your subclass, which takes the two other object parameters and sees if this action can really be done. If can_be_hit() returns false, then the remaining two callbacks will not be invoked.

Failure callbacks

There is another option available to you when defining multiple callbacks: failure callbacks.

A failure callback is a callback invoked if and only if the previous callback fails (i.e. returns false). This is defined by prefacing the failure callback with the string FAIL. We can thus extend the previous example in the following manner:

    [
      [ 'O:self', 'can_be_hit', 'O:object', 'O:other' ],
      FAIL => [ 'O:manager', 'output', 'Your blow appears to have no effect!' ],
      [ 'O:self', 'mod_hit_points', 'O:object->hit_power(O:other)' ],
      [ 'O:other', 'mod_melee_skill', 1 ],
    ]

(Note that you do not have to use =>; I use it because it makes it so I don't have to put quotes around FAIL and makes the code easier to read).

In this example, we assume that you have created a class that you have subclassed to Games::Object::Manager, and it contains a method called output that sends text output to the player. With this setup, if the call to can_be_hit() returns false, then the following is invoked:

    $manager->output("Your blow appears to have no effect!");

The remaining callbacks after the failure callback are not invoked.

Remember when I stated that wherever you have a callback you can specify a group of callbacks? The failure callback is no exception:

    [
      [ 'O:self', 'can_be_hit', 'O:object', 'O:other' ],
      FAIL => [
        [ 'O:manager', 'output', 'Your blow appears to have no effect!' ],
        [ 'O:object', 'does_it_break' ],
        [ 'O:manager', 'Oh, no, your weapon breaks!' ],
        [ 'O:object', 'destroy' ],
      ],
      [ 'O:self', 'mod_hit_points', 'O:object->hit_power(O:other)' ],
      [ 'O:other', 'mod_melee_skill', 1 ],
    ]

In this example, if the failure callback is invoked, it will attempt to execute the four callbacks defined in the list in turn. The first one outputs the message, then the second one calls a method on the O:object parameter. If this returns true, the remaining callbacks are executed (note that this assumes that output() will return true all the time). There is no limit to how far you can "nest" callbacks in this manner. You could easily put yet another FAIL after the does_it_break test and do something else:

    [
      [ 'O:self', 'can_be_hit', 'O:object', 'O:other' ],
      FAIL => [
        [ 'O:manager', 'output', 'Your blow appears to have no effect!' ],
        [ 'O:object', 'does_it_break' ],
        FAIL => [ 'O:manager', 'output', 'Lucky for you, your weapon is still intact.' ],
        [ 'O:manager', 'Oh, no, your weapon breaks!' ],
        [ 'O:object', 'destroy' ],
      ],
      [ 'O:self', 'mod_hit_points', 'O:object->hit_power(O:other)' ],
      [ 'O:other', 'mod_melee_skill', 1 ],
    ]

Thus the callback structure can be extremely versatile, allowing you to set up complex behavior with a minimum of programming.

Turning off return code checking

Sometimes you may have a situation where you are not interested in having the return code checked for callbacks. Perhaps you know you always want to execute every callback in the list, or from a particular point on, but some methods inherently return 0 or false. Or you simply don't care if some actions fail or not.

To accomplish this, specify the string NOCHECK in the callback list. All callbacks after this point will be unconditionally executed, until the end of the callback list, or until it hits the string CHECK.

Defining an action on an object

You can define arbitrary actions on an object by specifying them as parameters to the new() constructor. An action callback is recognized as any parameter that starts with the characters on_ or try_. For example:

    my $object = Games::Object->new(
        -id       => "expensive camera",
        -on_use   => [
            [ 'O:self', 'pictures_left' ],
            FAIL => [ 'O:manager', 'output', 'Nothing happens when you press the button' ],
            [ 'O:self', 'take_picture', 'O:object' ],
            [ 'O:manager', 'output', 'Click! The camera takes a picture.' ],
            [ 'O:self', 'mod_pictures_left', -1 ],
        ],
    );

This now defines an arbitrary action called on_use on this particular object. This action will be saved on a save() like anything else on the object and reloaded with load(). This example assumes you will have an attribute on the object called pictures_left that indicates how much film is left in the camera.

Note that the first callback on the list above does not actually do anything except return the current value of the attribute. But note that any non-zero number is considered to be boolean true, thus in this example, if the attribute is non-zero, it will allow the camera to be used, otherwise it will do the failure callback instead and stop.

Invoking an action on an object

There are several ways to invoke an action on an object. One way is to use the Games::Object action() method, which would be invoked in the following manner:

    $self->action(
        action   => "object:$action",
        object   => $object,
        other    => $other,
    );

$self is the object on which the action is defined, and $other and $object are the objects to put in place of O:other and O:object respectively in the callbacks.

Using the example of the camera object we defined in the previous example, to invoke its on_use action, we would do something like this:

    $camera->action(
        action   => "object:on_use",
        object   => $plant,
        other    => $player,
    );

The idea here is that $player is taking a picture of a $plant using the $camera.

The only problem using the action() method is that it may not seem particularly intuitive. A better way is to have the module create action methods for you. To turn on this feature, you will need to set a global variable to true as you did with attribute accessors:

    $Games::Object::ActionMethod = 1;

What this will do is create two methods, one named after the action and one with the on_ prefix stripped. The former can be thought of as passive syntax, and the latter as active syntax. In the above example, the passive form would be expressed this way:

    $camera->on_use($player, $plant);

The active syntax, however, reads more like a subject-verb-object format (and yes, I know this is biased towards English language syntax). This format swaps $self and $other:

    $player->use($camera, $plant);

Which could be read as "The player uses the camera on the plant."

Thus the general syntax can be expressed as:

    $self->$passive_form($other, $object);
    $other->$active_form($self, $object);

Use whichever syntax make more sense to you.

It is possible to call either form of the syntax with only one argument. In this case, the passive syntax will assume the lone parameter is O:other and in the active syntax it will assume it is O:self.

Passing arbitrary arguments to actions

You may have a situation where you wish to vary what an action callback does depending on conditions other than the objects themselves. Using the camera example, you may wish to, say, do something different depending on whether the player remembered to use the flash or not.

This can be done by passing a reference to a hash of key-value pairs that represent the additional arguments. This can be done either by specifying the args parameter to action(), like this:

    $self->action(
        action   => "object:on_use",
        object   => $plant,
        other    => $player,
        args     => { flash => 'on' },
    );

Or it can be done by specifying the hash ref as the last parameter to an action method, like this:

    $camera->on_use($player, $plant, { flash => 'on' });

or this:

    $player->use($camera, $plant, { flash => 'on' });

To utilize this parameter in your callback, simply put A:flash wherever you want that argument. Example:

    [ 'O:self', 'take_picture', 'O:object', 'A:flash' ],

You can specify as many key-value pairs as you want in the hash and use as few or as many of them as you wish in your callbacks. Note that there is automatically one predefined argument, no matter whether you use this feature or not. A:action will return the action that triggered this callback. In the case of object-based actions, this will always have the format object:$action. In the above example, this would be object:on_use. This can be useful if you decide to code a method that is invoked for more than one action because of the commonality between them, but has a few crucial differences depending on the action being invoked.

Dealing with undefined objects

Note that if you reference an object in a callback, the callback mechanism will expect that object to be defined when the callback is invoked via an action trigger. If an object referenced from the method parameters is not defined, this merely results in undef being passed. But if the undefined object is the first member of a callback array (and thus the object on which the method call will take place), this is a problem.

By default, Games::Object will simply bomb if you do not define that object. For example, if you do not define O:object, then this would not bomb, since it is simply referenced in the args to the method call:

    [ 'O:self', 'foo_method', 'O:object' ]

But this would bomb, since there is nothing to call a method on:

    [ 'O:object', 'bar_method', 'O:self' ]

There are two ways you can deal with this. One is to insert a call to the manager find() method to validate the object before you attempt to access methods on it, like this:

    [
      [ 'O:manager', 'find', 'O:object' ],
      [ 'O:object', 'bar_method', 'O:self' ],
    ]

If find() fails, it will return undef, which is treated as false, and no further callbacks execute. Or, you could indicate that callbacks with missing objects can be simply skipped over by using the ACT_MISSING_OK flag when calling the action. If we take our camera example again, say we want to simulate someone taking a picture of the scenery rather than a particular object (thus no O:object parameter). We could do this in this manner:

    $self->action(
        action   => "object:on_use",
        other    => $player,
        flags    => ACT_MISSING_OK,
        args     => { flash => 'on' },
    );

Or as an extra parameter after the objects like this:

    $camera->on_use($player, ACT_MISSING_OK, { flash => 'on' });

or this:

    $player->use($camera, ACT_MISSING_OK, { flash => 'on' });

In the latter two examples, to say that the flag parameter takes the place of the object parameter would be wrong. This would also work:

    $player->use($camera, $plant, ACT_MISSING_OK, { flash => 'on' });

and the code is smart enough to know which parameter is what. If $plant happens to contain undef, the code works as if you didn't even specify it.

Special actions

Most actions are abitrary, in that you can conjure up whatever actions you need merely by defining them on an object. A few, however, are referenced internally and have special meaning:

on_remove

This is invoked on the object when it is removed from the object manager. It is invoke before the object is physically removed or any of its object relationships broken. The O:object object is always undefined in this case, and whether or not O:other is defined will depend on how the original remove() method was invoked.

on_destroy

This is invoked if the destroy() method is explicitly invoked on an object. Note that an on_remove() action will follow this if the object is managed.

on_load

This is invoked after an object has been loaded from a file. It allows you to do any special processing you may wish to do at this time. For instance, if you have special data that you saved with the object that is outside the object itself, you can use this to reload that data. A:file is set to the open file object.

An especially good use for on_load is when using a GUI with your game. For example, your game may have a window that represents the map of your world with the player at the center. On a game load, you would most likely need to redraw the map. You can trigger this by placing an on_load on the object tracking the map.

on_save

This is invoked after an object has been saved to a file. It allows you to save any special data outside the normal scope of the object.

Be VERY careful using the on_load and on_save actions. You manipulate the file at your own risk. In most cases, you can do this better by encapsulating the extra data in a Perl object, defining load() and save() methods on it, and storing the object on the game object in an attribute.

Attribute Actions

Introduction

As well as defining actions on object, you can also define actions on attributes within objects. These actions are defined exactly as they are on objects, and the callbacks have the same structure and parameters. There are a few important differences, however:

  • There are a predetermined set of actions, and are not invoked directly by the user but indirectly through changes applied to the attribute.

  • The argument list (i.e. the A:* parameters) is fixed and is dependent on the action type.

  • Accessors are not created for them, to discourage attempts to invoke the actions independently of the normal mechanism.

  • If you wish to reference O:object and O:other in your callbacks, you must use an object manager.

Defining an attribute action

Attribute actions are defined when the attribute is created with new_attr(). Like with object actions, these are recognized as parameters that start with on_ (no try_ style actions are currently implemented). The callback structure is exactly the same as for objects. O:self is always the object on which the attribute is defined. If the attribute action is inherited, O:self will always be the actual object that was referenced in the original call that modified the attribute, not the inherited object.

As mentioned in the previous section, there are a set number of action types, which are described below:

on_change

This is invoked when the attribute changes value. This is invoked right after the value has changed, and every time the value changes. This means if you have an attribute with a persistent incremental modifier, each turn it changes this action will be invoked.

The action is invoked if and only if the value changes. If you make a call to modify the attribute and specify the new value or modifier such that the value ultimately does not change, the action is not invoked.

The on_change action has the following fixed argument list:

A:name

The name of the affected attribute.

A:old

The old value before modification.

A:new

The new value after modification.

A:change

How much the attribute changed by. The sign indicates the direction of the change. This is set only for attributes of type number or int.

on_minimum

Applicable only to attribute types that can have a minimum value, this is invoked when the attribute reaches the minimum value. It is called only once when the attribute reaches minimum. It will not be called again until the attribute has been modified above minimum and then back down to minimum again.

This shares the same parameters as on_change with the following addition:

A:excess

How much under the minimum the final value is (or would be if the attribute is set up to truncate modifications under the minimum).

on_maximum

Applicable only to attribute types that can have a maximum value, this is invoked when the attribute reaches the maximum value. It is called only once when the attribute reaches maximum. It will not be called again until the attribute has been modified below maximum and then back up to maximum again.

This shares the same parameters as on_change with the following addition:

A:excess

How much over the maximum the final value is (or would be if the attribute is set up to truncate modifications over the maximum).

In the case where an attribute was changed and it reached minimum or maximum in the same operation, on_change is called first.

In the case of a split attribute, actions are invoked only on changes to the current value, never the real value.

Defining the object parameters on attribute actions

Mentioned previously is the fact that you do not invoke attribute actions directly yourself, but indirectly via changes to the attribute. How then can you pass in values for O:other or O:object? By specifying them when you modify the attribute, that's how.

The mod_attr() method (and any autocreated accessor methods that modify the attribute) can take addition parameters to set these values. Say you define an attribute called hit_points. To use mod_attr() to modify this attribute and pass the other values, simply specify these parameters in the call:

    $player->mod_attr(
        -name   => "hit_points",
        -modify => 5,
        -other  => $wizard,
        -object => $healing_spell,
    );

Or through the appropriate accessor method:

    $player->mod_hit_points(5, $wizard, $healing_spell);

Note that this is very much like invoking actions (passive syntax). And just like the action method, if you leave off an object, the one that is specified is assumed to be O:other, while O:object is left undefined.

Flag Actions

Introduction

You can define a small set of actions for flags in a similar fashion as you do for attributes.

Defining a flag action

Actions are defined when you create a new flag with the new_flag() method, in a manner similar to the way you define actions for attributes. Like with object actions, these are recognized as parameters that start with on_ (no try_ style actions are currently implemented). The callback structure is exactly the same as for objects. O:self is always the object on which the flag is defined. If the flag action is inherited, O:self will always be the actual object that was referenced in the original call that modified the flag, not the inherited object.

The following is a list of the available actions for flags:

on_set

Invoked when the flag is set, but only if it was not already set when the set() method was called. If the set() call actually had no effect because the flag was already set, this action is not invoked.

on_clear

Invoked when the flag is cleared, but only if it was not already cleared when the clear() method was called. If the clear() call actually had no effect because the flag was already cleared, this action is not invoked.

All actions above have a single predefined parameter. A:name refers to the name of the flag that was altered.

Processing objects

Processing a single object

In order for persistent modifiers and tend-to rate calculations to be performed on an object, the object in question must be processed. This is done by calling the process() method, which takes no arguments:

    $obj->process();

What this method really is is a wrapper around several other object methods and function calls that are invoked to perform the invidual tasks involved in processing the object. Specifically, process() performs the following sequence:

process_queue

This processes all queued actions for this object. These actions are generally deferred attribute modifications, changes to attributes resulting from cancelled persistent modifiers, or arbitrarily queued actions (using the queue() method).

The queue is continually processed until it is empty. This means that if new items are queued as process_queue() runs, these items will be processed as well. However, it is easy to see how such an arrangement could lead to an infinite loop (queued method A queues method B, method B queues method A, method A queues method B ...). To prevent this, process_queue() will not allow the same action to execute more than 100 times by default on a given call. If this limit is reached, a warning is issued to STDERR and any further invokations of this action are ignored for this time through process_queue() .

process_pmod

This processes all persistent modifiers in the order that they were defined on the object. Changes to attributes resulting from this processing may generate events that cause your event handlers to be queued up again for processing.

The default order that modifiers are processed can be altered. See the section "Attribute modifier priority" for further details.

process_tend_to

This processes all split attributes' tend-to rates in no particular order. Like the previous phase, this one can also generate events based on attribute changes.

You can exert some control over the order in which attributes are processed in this phase. See section "Attribute tend-to priority" for details.

Timing issues

There is a timing issue reguarding persistent modifiers added as a result of events generated during the process_pmod() and process_tend_to() phases.

Recall that when a persistent modifier is added, it does not take effect until the next call to process() . This means that if an event is generated in either of the aforementioned phases, and these events add more persistent modifiers, they will not take effect until the next call to process() .

Worse, if you choose to reverse the order and do process_tend_to() first, then new mods added from events generated there will be processing this turn (since process_pmod() has not yet run), but ones generated from process_pmod() will not.

You can work around this problem via the -apply_now option introduced in 0.05. Setting this to a true value in your mod_attr() call from the event handler will cause the modifier to be applied immediately, as if it had been generated before the process() call.

Processing multiple objects

More likely than not, you are going to want to process all the objects that have been created in a particular game at the same time. This can be done if you use a Games::Object::Manager-based object manager. If you do, then all the objects added to the manager can be processed by using the process() method on the manager itself:

    $manager->process();

That's all it takes. This will go through the list of objects (in no particular order by default - see section "Priorities" for details on changing this) and call the process() method on each object.

The nice thing about the manager's process() method is that it is a generic method. With no arguments, it calls the process() method on the individual objects. However, if you give it a single argument, it will call this method instead. For example, say you defined a method in your subclass called check_for_spells() , and you wish to execute it on every object being managed. You can call process() thusly:

    $manager->process('check_for_spells');

Then there is yet one more form of this function call that allows you to not only call the same method on all objects, but pass arguments to it as well. For example:

    $manager->process('check_for_spells', spell_type => 'healing');

As of version 0.10, you can provide a filter that restricts the list of objects that are actually processed. This allows you to selectively process your objects. To do this, you provide it with a CODE reference before the method name that is invoked for each active object. If the code returns boolean true, then the object will be processed; boolean false, and the object is skipped.

The CODE reference can be a reference to an existing function or it can be an anonymous sub. For example, the following will invoke method check_creature for only those objects who's object_type attribute is set to "creature":

    $manager->process( sub { shift->attr("object_type") eq "creature" }, "check_creature" );

The following does the same thing, just broken up over more lines for better readability:

    sub filter_creature {
        shift->attr("object_type") eq "creature";
    }

    $manager->process( \&filter_creature, "check_creature" );

The filter code will also be passed all the arguments that you are passing to process() that are in turn passed to the method to be called.

It is permissable to specify a method that may not exist on all of your objects. This can happen if your manager consists of objects of several different classes. In this way you're not forced to define the method on all classes. Thus this acts as a built-in filter (which is done BEFORE any custom filter you define is invoked, so you will see only objects for which the method really exists).

The return code of the manager's process() is the number of objects that was actually processed. Note that this can be zero if your filter eliminated all of the objects from consideration, or none had the specified method.

Modifying the default process sequence

You can modify the order in which the process_*() subfunctions are called, or you can define your own methods to be called instead of or in addition to the defaults. How do do this depends on how you are managing your objects.

Managing the objects yourself

If you are managing your objects yourself, you can control the process list via the function ProcessList() in Games::Object. This is not exported by default, so you will need to import it. Example:

    use Games::Object qw(ProcessList);

This function actually serves a dual purpose. With no arguments, it will return an array containing the names of the methods currently called when the object's process() method is called. With parameters, this will set the process list to that list of methods. Thus if you simply wish to add a new method to the existing list, you can do the following:

    ProcessList(ProcessList(), "check_for_spells");
Using the Games::Object::Manager object manager

If you use the Games::Object::Manager module to manage your objects, using its process() method will always override the settings in Games::Object. However, you can set the process list separately for each of your managers.

You can do this when you first construct the manager. For example, say you want to create a manager that uses the default Games::Object sequence but adds one more method call that you have customized in your game object subclass:

    use Games::Object qw(ProcessList);
    use Games::Object::Manager;

    my $manager = Games::Object::Manager->new(-process_list => [ ProcessList(), "check_for_spells" ]);

Or, you can modify the process list after the fact using the manager's process_list() method. This works like the ProcessList() function from Games::Object in that it can either be used to fetch the current list or modify it. Example:

    $manager->process_list($manager->process_list(), "check_for_spells");

Priorities

Object priority

Each object has what is called a priority value. This value controls what order the object is processed in relation to the other objects when the objects are managed, and the manager's process() method is called. When an object is first created new (as opposed to loading from a file, where it would get its priority there), it has a default priority of 0. This default can be modified via the priority() method:

    $object->priority(5);

The higher the priority number, the further to the head of the list the object is placed when process() is called on the manager. For example, say you created a series of objects with IDs Player1, RedDragon, PurpleWorm, HellHound, and then performed the following:

    $manager->find('Player1')->priority(5);
    $manager->find('RedDragon')->priority(3);
    $manager->find('PurpleWorm')->priority(3);
    $manager->find('HellHound')->priority(7);

If you then called process(), first the HellHound object would be processed, then the Player1 object, then the RedDragon and PurpleWorm objects (but in no guaranteed or reproducible order for these last two). Assuming that all other objects have a default priority, they would be processed at this point (again, in no particular order).

Object priority can be changed at will, even from a user action being executed from within a process() call (it will not affect the order that the objects are processed this time around). The current priority of an object can be obtained by specifying priority() with no arguments.

Object priority can be a nice way of defining initiative in dungeon-type games.

NOTE: Avoid using extremely large or extremely small priority numbers. Keep your priority numbers in the range of -999_999_999 to +999_999_999, which should be more than sufficient. Values outside this range are reserved for internal use.

Controlling order of same-priority objects

Starting in version 0.05 of this module, you can force the module to process objects in a more deterministic fashion when the priority values are the same. This can be controlled by modifying the exportable variable $CompareFunction from the Games::Object::Manager module.

Specifically, this contains the name of a sort subroutine to be passed to the Perl sort() function. By default this is set to the string _CompareDefault, which reproduces the behavior in 0.04 and prior versions. In this manner, compatibility is preserved with previous versions.

If you set $CompareFunction to the string _CompareAddOrder, then objects will be processed in the order that they were added to the manager when the priorities are the same. Note that this has been changed from previous versions, which used to call this _CompareCreationOrder.

Because the value of $CompareFunction is passed to the sort routine verbatim, you could theoretically place any function you wish in this variable, including one you define in your main program, though be sure to prefix it with main::. Example:

    use strict;
    use Games::Object;
    use Games::Object::Manager qw($CompareFunction);

    sub MyCompare {
        # Compare like the normal function (larger priorities go first)
        # but when same, pick randomly.
        my $cmp = $b->priority() <=> $a->priority();
        $cmp == 0 ? ( rand(100) < 50 ? 1 : -1 ) : $cmp;
    }

    $CompareFunction = 'main::MyCompare';

Important note: This method of altering the sort order is most effective when you load and save all of your game objects at the same time. Partial loads, saves, and creation of new objects may not lead to a consistent object ordering.

Attribute tend-to priority

By default, tend-to rates on attributes are processed in no particular order in process_tend_to(). This can be changed by specifying a -priority value when creating the attribute in question. For example:

    $obj->new_attr(-name => "endurance",
                   -value => 100,
                   -minimum => 0,
                   -maximum => 100,
                   -tend_to_rate => 1,
                   -priority => 10);

The priority can also be later changed if desired:

    $obj->mod_attr(-name => "endurance",
                   -priority => 5);

The higher the priority, the sooner it is processed. If a -priority is not specified, it defaults to 0. Attributes with the same priority do not process in any particular order that can be reliably reproduced between calls to process_tend_to() .

Attribute modifier priority

By default, persistent attribute modifiers are executed in process_pmod() in the order that they were created. This is can be altered when the modifier is first created by adding the -priority parameter. For example:

    $obj->mod_attr(-name => "health",
                   -modify => 2,
                   -incremental => 1,
                   -persist_as => "ability:extra_healing",
                   -priority => 10);

Assuming that other modifiers are added with the default priority of 0, or with priorities less than 10, this guarantees that the modifier above representing healing will execute before all other modifiers (like, for example, that -15 health modifier from one angry red dragon ...).

The only drawback is that a modifier priority is currently set in stone when it is first added. To change it, you would have to add the same modifier back again in its entirety. This will probably be changed in a future release.

Queueing arbitrary method calls

As explained above, there are many places where method calls are queued up in an object for later execution, such as when persistent modifiers are added. The module uses the queue() method to accomplish this, and you can use this method as well to queue up arbitrary actions. The caveat is the same with events, that the action must be a method name defined in your module.

The queue() method takes the action method name as the first parameter, followed by any arbitrary number of parameters to be passed to your method. For example, if you were to make the following call:

    $obj->queue('kill_creature', who => 'Player1', how => "Dragonsbane");

Then when process_queue() is next called on this object, the Games::Object module will do the following:

    $obj->kill_creature(who => 'Player1', how => "Dragonsbane");

This is especially useful in conjunction with action methods to achieve a delayed effect. For example, let's return to the example of the camera object from an earlier section. You could delay execution of the action to when process() is next called (maybe to achieve the effect of a timer on the camera, or simply to synchonize things with other game actions) by doing this:

    $player->queue('use', $camera, $plant, { flash => "on" } );

Which would call this when processed from the queue:

    $player->use($camera, $plant, { flash => "on" } );

Object relationships

Introduction

Object relationships refers to the ability to relate objects to one another in a well-defined manner. For example, you may wish to define a type of object that can contain other objects. The condition of objects being contained in another is an object relationship.

In order to use object relationships, you must use the Games::Object::Manager module. This is because relationships are tracked in the manager and not in the individual objects. This is to minimize the number of circular references that need to be maintained, as well as avoid trying to store object state information at the class level.

The relationship model used in Games::Object::Manager allows you to control how and when relationships occur, as well as trigger additional actions on forging or breaking relationships, via action callbacks. For this reason, the manager module mirrors the way the Games::Object module works in this regard. Specifically, the symbolic object references refer to the following:

O:object

This is the object being related. Using the example of an object relationship in which one object could contain others, this would refer to something you're trying to put into a container.

O:self

This is the object being related to. In the same example from above, this would be the container.

O:other

This is the one that instigated the action. Whenever this is not specified, this is set to the same object as O:self (i.e. the thing being related to is assumed to have triggered the action).

Defining an object relationship

An object relationship is defined by calling the define_relation() method on the manager. The best way to describe the syntax is to define the relationship that we've been using as an example, that of a container:

    $manager->define_relation(
        -name                   => "contain",
        -relate_method          => "insert",
        -unrelate_method        => "extract",
        -related_method         => "contained_in",
        -related_list_method    => "contains",
        -is_related_method      => "is_contained_in",
        -flags                  => REL_NO_CIRCLE,
    );

Here is an explanation for each of the parameters:

name

This is the unique name for the object relationship. This is the only required parameter in the list.

If you use a name that is already in use, you will not receive any warning or error. It will instead silently redefine the existing relationship. Note that if there are already objects related in this manner when you do this, they will continue to be related in this manner.

Other parameters are optional, and some default values are based on this one. In these cases, this parameter's value is refered to as ${name}

relate_method

This defines the name of the method you would like to use to relate two objects together in this manner. This is created in the namespace of the Games::Object::Manager module, much in the same way accessor methods or action methods are created in Games::Object.

If you do not define this parameter, ${name} is used instead.

unrelate_method

This defines the name of the method you would like to use to break an existing relationship from one object to another. If you omit this parameter, it defaults to un${name} instead.

This defines the name of the method you want to use to return what an object is related to in this manner, if anything. In our running example here, this means the container in which the object resides. If you do not define this parameter, it defaults to ${name}_to.

This defines the name of the method you want to use to return a list of objects that are related to a specified object. In our example, this means a list of objects currently inside the container. If you omit this parameter, it defaults to ${name}_list.

This defines the name of the method you want to use to see if one object is related to another in this manner. In our example, it would check to see if a specific object is contained in another. If this parameter is omitted, it defaults to is_${name}.

flags

This allows you to define flags that affect the behavior of the relationship. Right now, the only flag that is available is REL_NO_CIRCLE. This tells the manager to prohibit circular relationships. For example, if this flag is on, and you already have A related to B, and B related to C, an attempt to relate C to A will cause an error.

In the example of the container relationship, we do not want this to be circular, since it makes no sense to have two objects that are each other's container.

In order to use this flag, you will need to import it, like this:

    use Games::Object::Manager qw(REL_NO_CIRCLE);

Relating an object to another

Once you have your relationship defined, you can now relate two objects together. This can be done in one of two ways. One is to use the predefined relate() method on the manager. Say for this example we're attempting to have the player place an apple in a sack (the sack will contain the apple). You would do this:

    $manager->relate(
        -how    => "contain",
        -self   => $sack,
        -object => $apple,
        -other  => $player,
    );

But just like with object actions, it's easier to use the method that you told the manager to create for you to relate objects. Using things, you can simplify this to:

    $manager->insert($sack, $apple, $player);

Note something important here: The order of the first two arguments is exactly that of action methods. O:self comes before O:object. From a "grammatical" point of view (well, English grammar anyway), this may seem a little backwards, but it maintains consistency with the way Games::Object orders these parameters in the active action syntax.

For example, just like you might do this to take a picture of a plant with the camera:

    $player->use($camera, $plant);

You would also do this to put new film in the camera:

    $manager->insert($camera, $film, $player);

That aside, there is something you should know about relating two objects together: You can control at the object level whether the relation is allowed.

When you attempt to relate two objects together, the first thing that the manager will do is check to see if the relationship is allowed. In our example, this is done by invoking the try_insert method on $sack. Generically, the manager will look for try_ prefixed to the name of the relate method on the O:self object.

In the container example, you may wish to limit how much a container can hold by, say, comparing the size of the object in question to the total size of the container and the collective size of the objects already contained within. Thus you can define on $sack something like this:

    try_insert => [
        [ 'O:self', 'can_hold', 'O:object' ],
        FAIL => [ 'O:manager', 'output', "It won't fit." ],
    ]

Assuming that attribute size is the size of an object, max_hold is the max size that the container can hold, and is_held is the total size it is holding now, we could define can_hold like this:

    sub can_hold {
        my ($self, $object) = @_;
        ( $self->is_held() + $object->size() ) <= $self->max_hold();
    }

If, when you try to insert the $apple into the $sack, the try_insert action fails, the relate method will return 0. If it succeeds, it returns 1. If no try_insert exists on the container, this is treated as success.

If the try action succeeds, the object relationship is formed. But before success is returned to the caller, one more thing happens. The manager will now invoke on_insert on the $sack object. Generically, the manager will invoke an action by the name on_ prefixed to the relate method. To complete this example, you would define this on $sack:

    on_insert => [ 'O:self', 'mod_is_held', 'O:object->size()' ]

And in this case you don't even have to define a method! This will invoke the following if you successfully add the $apple to the $sack:

    $sack->mod_is_held($apple->size());

Now each time you add something to the sack, it automatically updates its internal counter that tracks the total size of all the objects in the container.

There are some variations on the relate method. You can omit the O:other object if you wish to say that the container instigates the action itself or it doesn't matter. For example, say we extend the container relationship to handle the player carrying objects in his inventory. You could do this to indicate the player picking up the apple:

    $manager->insert($player, $apple);

In which case, O:other will be set to $player. Or to indicate that the apple is a gift from some other creature:

    $manager->insert($player, $apple, $creature);

Finally, there is one last thing: If you remember the discussion on object actions, you can see that relationships follow the same style of syntax. So you may be wondering if you can pass arbitrary arguments to the try_insert and on_insert actions. Yes, you can, by either specifying the args parameter to relate() like this:

    $manager->relate(
        how     => "contain",
        self    => $sack,
        object  => $apple,
        other   => $player,
        args    => { how => "carefully" },
    )

Or by adding a hash ref as the final parameter to the relate method you defined, like this:

    $manager->insert($sack, $apple, $player, { how => "carefully" });

So you can do something like this on the sack:

    on_insert => [
        [ 'O:self', 'mod_is_held', 'O:object->size()' ],
        [ 'O:object', 'dropped', 'A:how' ],
    ]

And define a dropped() method on the apple object like this:

    sub dropped {
        my ($object, $how) = @_;
        if ($how ne 'carefully') {
            my $manager = $object->manager();
            $manager->output("The apple develops a nice bruise on it.");
            $object->set("damaged");
        }
    }

Breaking an object relationship

To break an established relationship between two objects, you can call the generic unrelate() method. Say we're going to now remove the apple from the sack of the previous example:

    $manager->unrelate(
        how     => "contain",
        object  => $apple,
        other   => $player,
    );

Note we do not need to define self, since this is already implied to be the sack, since this is what it is currently related to. Or, you could use the unrelate method you defined:

    $manager->extract($apple, $player);

Like with the relate method, the unrelate method will also attempt to call, in this example, try_extract on the sack first to see if the action is allowed. Here is an example of such an action definition:

    try_extract => [
        [ 'O:self', 'is', 'open' ],
        FAIL => [ 'O:manager', 'output', "You'll have to open the sack first." ],
    ]

If this succeeds, the unrelate will be done. If not, 0 is returned to the caller and the objects remain related.

And just like with relating objects, on a successful unrelate, it will call the corresponding on_extract on the sack:

    on_extract => [ 'O:self', 'mod_is_held', '-O:object->size()' ]

which does the opposite of on_insert, namely subtract the size of the object being extracted from the sack's running total of contained object sizes.

Sometimes you will need to relate one object to another, but need to break the existing relationship. You can let the manager do this automatically for you. If you attempt to relate an already related object, it will automatically unrelate the object first and then relate it to the new target. IMPORTANT: It is possible for the unrelate to fail. If this happens, the old relationship is left intact.

Using our example, say you have the apple in the sack and wish the player to take it out of the sack. Rather than doing two commands like this:

    if ($manager->extract($apple, $player)) {
        $manager->insert($player, $apple);
    }

You can just call this:

    $manager->insert($player, $apple);

Here's the sequence of actions taken by the manager:

    1) Invokes try_insert on $player to see if this relationship
       is allowed. Return 0 if not.

    2) Calls unrelate() to extract $apple from $sack. Return 0 if fails.

       2a) Unrelate first invokes try_extract on $sack. Return 0 if fails.

       2b) Unrelate unrelates $apple from $sack.

       2c) Unrelate invokes on_extract on $sack.

    3) Relate $apple to $player, caused by $player.

    4) Invoke on_insert on $player.

So if we assume a try_insert define on $sack as we had it in a previous example, then if the sack is not open, the unrelate will fail, and hence the new relate will fail, and insert() will return 0.

Note that this matters only if dealing with the same type of relationship. For example, if you have a "contain" relationship and an "owner" relationship, one will not affect the other unless you specifically do so in your relate on_ action. So if A is related to B in sense "owner", and you relate A to C in sense "contain", this does not affect A --> B.

To see if an object is related to anything in a particular sense, you can either use the related() method:

    my $container = $manager->related(how => "contain", object => $apple);

or using the method you defined for the purpose when you defined the relationship:

    my $container = $manager->contained_in($apple);

If the object is not related to anything in this manner, undef is returned.

To obtain a list of objects related to this one in a particular manner, use either the related_list() method:

    my @items = $manager->related_list($sack);

or the method name you selected:

    my @items = $manager->contains($sack);

Are these two objects related?

To check to see if one object is related to another, you can call either the generic is_related() method:

    $manager->is_related(
        -how    => "contain",
        -self   => $sack,
        -object => $apple,
    );

or use the method that you specified when you defined the relationship:

    $manager->is_contained_in($sack, $apple);

Forcing a relate or unrelate

You can choose to force a relationship to occur, or for an existing one to be broken. This means that the try_* actions will not be invoked. The on_* actions will still be invoked after the relate or unrelate is done, so in the container example, the internal counter will still be updated.

Forcing a relate or unrelate can only be done using the generic relate() and unrelate() methods (this may change in future versions of this module). This is done by adding force => 1 to the parameter list:

    $manager->relate(
        -how    => "contain",
        -self   => $sack,
        -object => $apple,
        -other  => $player,
        -force  => 1,
    );

In this example, we would bypass the try_insert action on $sack.

Removing an object that has relationships

You may sometimes wish to remove an object from the manager (perhaps simulating an object being destroyed or a creature being killed), and that object may have objects related to it or be related to other objects. In this case, the manager needs to break existing relationships.

When removing an object, the manager will first break relationships going to the object being removed, then will break relationships from the object to be removed to other objects. In each case, the manager goes down the list of object relationship types (in no particular order), and for each will perform the needed unrelates.

Each unrelate is done with force => 1, thus no try_* actions will be invoked, so no unrelates can fail. All on_* actions associated with the unrelate will be invoked, however.

How is the object being unrelated?

It can be seen from the above sections that it is possible for an unrelate operation to be triggered indirectly, either through relating to another object or removing an object. You may want to do something different in your try_* or on_* actions depending on what caused the unrelate.

To allow you to do this, your actions associated with the unrelate will be passed an argument that can be referenced by A:source in your callback parameters. This will be set to one of the following strings:

direct

This was called directly from the user, thus it can be considered a "pure" unrelate operation.

relate

This was invoked while attempting to relate an object already related to another. This is thus the call to unrelate() to break the existing relationship.

remove:to

This is being triggered from remove(), and is a result of the manager breaking existing relationships to the object being removed. This means that O:self is the object being removed, and O:object is an object that was related to it.

remove:from

This is being triggered from remove(), and is a result of the manager breaking existing relationships from the object being removed. This means that O:object is the object being removed and O:self is the object from which it is being unrelated.

This is most useful when dealing with a relationship broken as the result of an object being removed. To illustrate, let's say we decide to extend the container relationship to also track items in rooms. Say, then, that the sack object is being held by a creature (hence contained in the creature), and the creature is removed from the game. We would like to have the objects that the creature is holding appear in whatever room the creature was in. Assuming that the original on_extract action is defined on the creature, you could it like this:

    on_extract => [
        [ 'O:self', 'mod_is_held', '-O:object->size()' ],
        [ 'O:object', 'drop_if_removed', 'A:source', 'O:self->contained()' ],
    ]

The first callback would do as in the original example, which is to update the is_held counter. The second callback then invokes drop_if_removed() on the object being unrelated from the creature. This method is passed the A:source string, plus the object that the creature itself is contained in (which would ostensibly be a room). The object's method could be defined to do this:

    sub drop_if_removed {
        my ($object, $source, $contained) = @_;
        my $manager = $object->manager();
        if ($source eq 'remove:to') {
            if ($contained) {
                $manager->insert($contained, $object);
            } else {
                # Oops, creature was in limbo, remove the object instead.
                $manager->remove($object);
            }
        }
        1;
    }

This is the reason why remove() always breaks relations to the removed object first, thus preserving the relationships from the removed object in case you wish to do exactly as we did in the example above.

Inheritance

Introduction

Inheritance is an object relationship that is defined automatically for you when you create an object manager. This relationship is used in a special way in the Games::Object module to allow objects to inherit things from other objects.

Inheritance can be used to create classes of objects. You could create an object, for example, that represents a master object for all creatures, then create specific creature objects that inherit a set of common characteristics from this one. Inheritance has the potential to reduce the amount of memory that your game objects use.

For convenience, the object from which you are inheriting will be referred to as a class.

The inherit relationship can be controlled like any other relationship. The following methods are defined for this relationship:

inherit

Make one object inherit from another. Remember that O:self is the thing being inherited from. Thus if wish $object to inherit from $class, you would do this:

    $manager->inherit($class, $object);
disinherit

Break an inheritance relationship. Example:

    $manager->disinherit($object);
inheriting_from

Return the class that an object is inheriting from. In the examples above, this:

    $manager->inheriting_from($object);

would return $class.

has_inheriting

Returns a list of objects that are inheriting from this one. In our example above, this:

    my @items = $manager->has_inheriting($class);

would have $object as one of the array members.

is_inheriting_from

Check to see if one object is inheriting from another. In the above examples, this would return true:

    $manager->is_inheriting_from($class, $object);

Attribute inheritance

Once an object is inheriting from another via the inherit() method on the manager, you can inherit attributes from the class. For example, say you were designing a game that had lots of dragons modeled after a particular class, in which many attributes were shared among them. You could do something like this:

    my $manager = Games::Object::Manager->new();
    my $class = Games::Object->new(-id => "Class Dragon");
    $class->new_attr(-name => "hit_points",
                     -type => "int",
                     -value => 50);
    ...

    my $dragon = Games::Object->new(-id => "Red Dragon");
    $manager->inherit($class, $dragon);

If you do no further processing with the $dragon object, you can access all the attributes defined on $class from here. For example, this:

    my $hp = $dragon->attr("hit_points");

would set $hp to 50. For all intents and purposes, hit_points exists on $dragon. Even attr_exists() will tell you that the attribute exists even though it is really getting it from $class. If you really need to tell whether the attribute really physically exists on the object as opposed to being inherited, you can call attr_exists_here() instead. In the above example, this method would return false.

Inheritance ceases, however, the moment you modify the attribute. In this case, the attribute becomes localized to the inheriting object. If you performed a relative modification, it is made relative to the current value of the inherited attribute at the time it was modified. For example, if you did this:

    $dragon->mod_attr(-name => "hit_points",
                      -modify => -8);

Then $dragon will now have its own copy of hit_points set to 42. attr_exists_here() will return true. However, if at a later time you delete the attribute from the dragon object, it will go back to inheriting the attribute from the class object.

You can have mutiple layers of inheritance. For example $class could itself inherit from a larger class of objects. When you attempt to access an attribute on an object, it will continue checking successive inheritance until it finds an object with the attribute defined. This means that defined attributes on objects lower down on the chain will mask the values of those higher up the chain.

It should be noted that persistent modifiers are NOT inherited. If $class has a persistent modifier on hit_points that decreased it by 1 each turn, $dragon would see this change in the attribute until such time that it attempted a modify, in which case it has its own local copy that has no persistent modifiers on it.

You can choose to prohibit objects from inheriting an attribute. In order to do this, set the ATTR_NO_INHERIT flag on the attribute when you create it on the class object. It will be treated as if it does not exist. You will need to import this symbol into your code if you need it, since it is not exported by default.

Also be aware that the ATTR_STATIC flag is honored for inherited attributes. This means if the class has an attribute that is marked ATTR_STATIC, and the inheriting object attempts to modify it, this will be treated as an error just as if you attempted to modify it on the class object directly.

Flag inheritance

Flags are inherited in the exact same way as attributes. Like attributes, they do not physically exist on the inheriting object until such time that you modify the flag value, in which case the object gets its own copy of the flag independent of the class.

Action inheritance

Actions are inherited in the exact same manner as attributes. In fact, actions are stored as attributes with special names.

When you inherit an action, please note that O:self will always refer to the inheriting object, NEVER the class. For all intents and purposes, everything acts as if the action were defined directly on the inheriting object.

Loading and saving of object data

Individual objects

This is one of the more powerful features of Games::Object. This essentially provides the functionality for a save-game action, freeing you from having to worry about how to represent the data and so on.

Saving the data in an object is simple. Open a file for write, and then call the save() method on the object:

    $obj->save(-file => \*SAVEFILE);

You can pass it anything that qualifies as a file, so long as it is opened for writing. Thus, for example, you could use an IO::File object. Essentially anything that can be used between <> in a print() statement is valid.

You can also do this instead:

    $obj->save(-filename => "object.save");

In this case, it will create and open a file for you, save the data, and then close it again. This is useful in programs that generate object templates for use with the load() constructor, which was covered in an earlier section.

Managed objects

A far more useful way to load and save objects is via the object manager.

The Games::Object::Manager object contains a save() method just like the Games::Object module. It is called exactly the same as well, which means you can do this:

    $manager->save(-file => \*SAVEFILE);

or this:

    $manager->save(-filename => "game.save");

When you save an object manager, not only will it save all the objects that it contains (by calling save() on each), but it will also save its own internal settings, including things like the process list, the next available unique object ID, object relationships, etc. In effect, it creates a complete snapshot of the manager. Assuming that you're using one object manager for your game, you can save the game to a file with one command.

You can reload saved managed data in the same way you would load an individual object. The Games::Object::Manager module has a load() constructor that will completely rebuild the manager and all its objects from a file. Since it is called exactly like the Games::Object version, you can do this:

    my $manager = Games::Object::Manager->load(-file => \*LOADFILE);

or this:

    my $manager = Games::Object::Manager->load(-filename => "game.save");

You will then have a manager restored to the exact state it was in when it was first saved.

You can also choose to do a "load in place", which means you wish to set an existing manager to the contents of a save file. In this case, simply call load() as a method from an existing manager object.

destroy() vs. remove()

Several times in this document, the object method destroy() and the manager method remove() are mentioned, both in the doc itself and in some of the callback examples. What exactly is the difference between these?

One difference is, assuming the object is managed, calling destroy() will cause an exact action to be invoked. on_destroy() will be called on the object prior to on_remove(). Besides this, any differences depend on how you use it.

If destroy() is invoked from a callback, the object is managed, and you are not maintaining any other references to it, then other than the call to an additional action, there is no difference between destroy() and remove(). The object will go out of scope once the callback finishes executing, thus the object and its structures go away.

Where a difference arises is when you call remove() yourself from your main program, and preserve the object reference that is returned. In this case, even though the object is removed from the manager and will no longer interact with it, the object still exists. If it has persistent modifiers or split attributes, these will continue to update if you elect to call process() on that object. You could even add the object to another manager. destroy(), however, will specifically delete all the internal structure of the object, including attributes and actions.

EXAMPLES

Please refer to specific sections above for examples of use.

Example data was chosen to approximate what one might program in a game, but is not meant to show how it should be done with regards to attribute names or programming style. Most names were chosen out of thin air, but with at least the suggestion of how it might look.

WARNINGS AND CAVEATS

This is currently an alpha version of this module. While the latest major changes will hopefully be the last interface changes, there's no guarantees while this module is still alpha.

If you look at the code, you will find some extra functionality that is not explained in the documentation. This functionality is NOT to be considered stable. There are no tests for them in the module's test suite. I left it out of the doc for the alpha release until I have had time to refine it. If you find it and want to use it, do so at your own risk. I hope to have this functionality fully working and documented in the beta.

BUGS

The load and save features are sorely in need of error checking and diagnostics. For instance, saving is an all-or-nothing process on the object manager, so your best bet is to run it through eval() to prevent the program from aborting on nasty errors. This will be a priority for the next version.

The test suite is getting better, but still has a few gaps, largely the result of creeping featurism.

No testing whatsoever has been done on Windows as I don't have access to a Windows environment (only Linux and Solaris are available to me). Some of the documentation is hence UNIX-centric. The module SHOULD work on Windows, as there is nothing UNIX-centric in the code itself. If you get it to work (or not) on Windows, please let me know.

If you find a bug not documented above

Please send email to the author of the module at Peter J. Stewart <p.stewart@comcast.net> with a description of the bug. Please include any error messages you may have received. Snippets of code that trigger the bug would be helpful as well.

TO DO

Top priority for the next set of releases will be performance testing and improvement (if needed). I have done very little in this area, so for realtime application YMMV with this module.

Cloning an object would be useful functionality to have.

Processing order for objects has improved, but still could use some more extensions.

A form of "undo" functionality would be WAY cool. I have something like this in another (but non-CPAN) module. I just need to port it over.

ACKNOWLEDGEMENTS

Many, MANY thanks to Tels (http://bloodgate.com/) for helping me make some great improvements to this module and allowing me to pick his brain for ideas.

AUTHOR

Note to users of previous versions: I have a (yet another) new email address.

Please send all comments and bug reports to Peter J. Stewart <p.stewart@comcast.net>. Also feel free to drop me a comment if you decided to use this module for designing a game. It will help me plan for future functionality.