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 YourGameObject;
    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;
    }

    ...

    1;

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. Flags can be added to the overall "universe" in which your objects exist such that new objects created automatically get certain flags set.

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.

It should be noted that many of the features of this module have definitely been geared more towards RPG, strategy, and D&D-like games. However, there is enough generic functionality for use in many other genres. Suggestions at ways to add more generalized functionality are always welcome.

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.

The Constructor

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.

There is one exception to this rule, however. If you have creatad user-defined flags (see "User-defined Flags" for details) with the autoset option, these flags will automatically be set on the object when it is created.

Each object that is created must have a unique ID. When you create an empty object in this manner, a guaranteed unique ID is selected for the object, which can be retrieved with the id() method. If you wish to specify your own ID, you can specify it as an argument to the constructor:

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

Specifying an ID that already exists is a fatal error. You can check ahead of time if a particular ID exists by using the Find() function. Given an ID, it will return the reference to the Games::Object that this identifies, or undef if the ID is unused.

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 passing the open file to the constructor:

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

The argument to -file can be any sort of file reference, from a GLOB reference to an IO::File object reference. So long as it has been opened for reading already.

The constructor will use as the ID of the object the ID that was stored in the file when it was saved. This means that this ID cannot already exist or it is a fatal error.

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 new() until the end of file was reached.

Note that when loading an object from a file, autoset options on flags are ignored. Instead, flags are set or cleared according to the data stored in the file. Thus the object is guaranteed to look exactly like it does when it was saved.

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 = new Games::Object(-file => \*INFILE);
    seek(INFILE, $fpos, SEEK_SET);
    my $obj2 = new 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 = new Games::Object(-filename => "creatures/orc.object", -id => "Bob");

Objects are persistent

It is important to note that when you create an object, the object is persistent even when the variable to which you have assigned the reference goes out of scope. Thus when you do something like this:

    my $obj = new Games::Object;

At that moment, two references to the object exists. One is in $obj, while the other is stored in a hash internal to the Games::Object module. This is needed so as to be able to later map the ID back to the object. It also frees the game programmer from having to maintain his/her own similar list.

Retrieving a previously created object

As mentioned in the previous section, 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(), which can be called either as a function like this:

    my $obj = Find('Sam the ogre');

Or as a class-level method:

    my $obj = Games::Object->Find('Sam the ogre');

This will work no matter how the object was created, either through creating a new object or loading an object from a file.

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

Destroying objects

As mentioned in the previous section, objects are persistent, thus they never get destroyed simply by going out of scope. In order to effect purposeful destruction of an object, you must call the destroy() method:

    $obj->destroy();

This will empty the object of all its data and remove it from the internal table in Games::Object such that future calls to Find() will return undef. In addition, the object will no longer be found on any class-level methods of functions that operate on the entire list of objects (such as Process()). In the above example, once $obj goes out of scope, the actual memory inside Perl for the object will be freed. You could conceivably simply avoid the middleman and not even assign it to a local variable, so long as you're confident that the object exists:

    Find('Dragonsbane')->destroy();

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 globally, rather than on an object-per-object basis. This is done with the function CreateFlag():

    CreateFlag(-name => "melee_weapon");

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.

You can choose to set up a flag such that it is automatically set on new objects that are created from that point in time forward by using the -autoset option:

    CreateFlag(-name => "melee_weapon", -autoset => 1);

If you later with to turn off -autoset, you can do so with ModifyFlag():

    ModifyFlag(-name => "melee_weapon",
               -option => "autoset",
               -value => 0);

There is currently no requirement as to what order you perform you calls to CreateFlag() or new(), other than you will not be able to set or clear a flag until it has been defined. It is probably good practice to define all your flags first and then create your objects.

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:

    $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 [NOTE: This is currently not implemented].

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",
                   -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

Games::Object offers a way to store object references in your attributes. Object references in this case are broken down into two areas: easy and hard.

The easy references are references to Games::Object objects or objects from subclasses of Games::Object. These references may be stored either as an any datatype, or as a scalar value from a complex data structure on an any datatype. When you store these references, upon saving the data to an external file, the references are converted to ID strings and then back again when reloaded. The code handles cases of objects not yet loaded but references in an attribute automatically.

BEWARE! If objects in your game frequently get created and destroyed, it is probably NOT a good idea to store objects as references in your attributes. This could set up a memory leak condition, as the memory for such objects may never be freed back to Perl if references to them exists inside attributes of other objects. You're really better off using the ID string and storing that instead. You can always use Find() to retrieve the object reference again later.

The hard ones are references to other arbitrary object classes. This involves a bit more work.

First, before you do anything, you must register the class. This gives the Games::Object module information on how to deal with objects of this class in terms of manipulating its data. This will require that the object class in question:

  • Assign a unique ID to each object, much in the same way that Games::Object does.

  • Provide an object method for retrieval of an object's ID, as well as a class method to convert an ID back to a reference.

  • Provide methods for loading and saving object data at the point in the file where it is stored in the attribute. This means the load method must properly create and bless the object.

Thus this is not for the faint of heart. Registering a class requires calling the RegisterClass function like so:

    RegisterClass(-class => "Your::Class::Name");

This is the simplest way to register a class, and makes broad assumptions about the names of the methods and functions, specifically:

  • The object method to retrieve the Id of an object is id(), which is called with no arguments.

  • The class method to find a reference given an ID is Find(), which is called with a single argument (the ID string).

  • The class method to load an object is load(), which is called with the -file parameter as the Games::Object method would be.

  • The object method to save an object is save(),.

These assumptions can be modified with extra parameters to RegisterClass():

-id

Specify the name of the ID object method.

-find

Specify the name of the object find class method.

-load

Specify the name of the object load class method.

-save

Specify the name of the object save object method.

For example:

    RegisterClass(-class => "Some::Other::Class",
                  -id => "identifer",
                  -find => "toObject",
                  -load => "read",
                  -save => "write");

Once you have registered the class, you can now place references to these objects inside your attributes in the following manner:

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

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

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

But both new_attr() and mod_attr() give you a neat feature: you can specify the -value parameter as either the object reference, or the object ID. If you give either one a non-reference, it will assume that this is the ID of the object and call the find method behind the scenes to obtain the real object reference.

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

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} ++;

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.

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

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

Events

Callback programming model

This section shows you how you can set up code to trigger automatically when changes take place to objects. First, however, you must understand the concept of "callback programming".

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.

Registering an event handler

In order to deal with an event, you must define what is referred to as an "event handler". But rather than a function or arbitrary CODE block, you are required to specify a method name, i.e. a method that you have written in your subclass to handle the event. Here is an example of registering an event to deal with the modification of an attribute using the RegisterEvent() function:

    RegisterEvent('attrValueModified', 'attr_modified');

Your attr_modified method would then look something like this:

    sub attr_modified {
        my $obj = shift;
        my %args = @_;

        ...
    }

The $obj variable would contain the reference to the object that was affected (in this case, by an attribute value modification). %args would contain a series of parameters that describe the event that took place. While most of the parameters will vary according to the event that was generated, one parameter will be the same for each one. event will be set to the name of the event in question. In the above example, this would be "attrValueModified".

List of events and parameters

The following is a list of events and the parameters that are passed to the event handler:

attrValueModified

This is invoked when the value (the current value in a split attribute) is modified by whatever means. The event is generated at the time that the value actually changes; thus adding a persistent modifier will not generate the event until the modifier is actually applied.

The parameters passed are:

name

Name of the attribute that was modified.

old

The old value of the attribute before it was modified.

new

The new value after it was modified.

attrRealValueModified

This is the same as attrValueModified except that it is invoked when the real value rather than the current value of a split attribute is modified. It is subject to the same rules as attrValueModified and also has the same parameters.

attrValueOutOfBounds

This event occurs when an attribute value (current value in a split attribute) has been forced to take on a value outside its normal minimum and maximum, generally through the use of the -force option in a call to mod_attr. This is ONLY called when a value actually goes out of bounds, not every time -force is used. The modification has to actually result in a value that is outside the bounds.

The following parameters are passed to the handler:

name

The name of the attribute that went out of bounds.

old

The old value of the attribute prior to modification.

new

The new value of the attribute after modification.

attrRealValueOutOfBounds

This is the same as attrValueOutOfBounds, except for the real value of a split attribute. It is subject to the same rules and conditions as attrValueOutOfBounds, and the parameters passed are the same.

attrValueAttemptedOutOfBounds

This event occurs when a modification of an attribute would have resulted in an out of bounds value had the issue been forced, but in reality the value was kept within the bounds of the attribute's defined range. Note that this event is generated only if the attribute was defined with an -out_of_bounds option of "use_up" (which is the default). If it was defined as "ignore", then this event will never be generated.

The following parameters are passed to the event handler:

name

The name of the affected attribute.

old

The old value of the attribute. It is possible that this may be the same as the new value, if the modification was attempted when the value was already pegged at the boundary.

new

The new value of the attribute, which will be at one of the bounds.

excess

This is the excess amount that was not applied to the attribute due to the boundary condition.

attrRealValueAttemptedOutOfBounds

This is the same as attrValueAttemptedOutOfBounds, except for the real value of a split attribute. This is subject to the same rules and conditions as attrValueAttemptedOutOfBounds, and the parameters passed to the handler are the same.

flagModified

This is generated when the flag on an object changes value, via either the set() or clear() methods. It is generated ONLY if the flag actually was modified; doing a set() on a flag that is already set, or clear() on one that is already cleared will not generate an event.

The following parameters are passed to the handler:

flag

The flag that was modified.

old

The old value of the flag (either 1 or 0 for true or false, respectively);

new

The new value of the flag (either 1 or 0 for true or false, respectively);

When you register an event, you can choose to add your own arguments that will be passed to the event handler. This is done by specifying these arguments after the name of the handler in the call to RegisterEvent():

    RegisterEvent('attrValueModified', 'attr_modified',
                  foo => 'bar', this => 'that');

The values that can be passed in this manner are subject to the same rules as the any attribute data type. This means they can be simple scalars or references to Games::Object-subclassed objects, or complex data structures that ultimately result in simple scalars or Games::Object-subclassed objects. This is so that queued events can be properly save()d to a file.

Processing objects

Processing a single object

In order for events, 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 events generated after the previous call to process() completed, deferred attribute modifications, and changes to attributes resulting from cancelled persistent modifiers.

If event handlers perform actions that themselves generate more events, these events get added to the end of the queue, and will also be processed until the queue is empty. However, it is easy to see how such an arrangement could lead to an infinite loop (event handler A generates event B, event handler B is queued, event handler B generates event A, event handler A is queued, event handler A generates event 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.

process_queue

This is a repeat of the first phase to handle the events that were generated during the previous two phases. It acts exactly like the first phase in that it processes the queue until it is exhausted, and will not allow a method to be executed more than a given number of times.

Timing issues

There is one timing issue with the way events are processed in the default process() phase list.

Note that process_queue() is called twice, once at the start and again at the end. Your event handlers could potentially call mod_attr() and add a new persistent modifier. If the modifier is added as a result of an event handler executed in the first call to process_queue(), the modifier will be processed this time through process(). But if the modifier is instead added during the second call to process_queue(), then the modifier will not be considered until the NEXT call to process().

This problem is considered to be a design flaw. Expect this to change in later versions of this module. In the meantime, you can affect a workaround by modifying the processing list for process(), which is coming up in the subsection "Modifying the default process sequence".

Processing all 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 with the Process() function:

    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.

The nice thing about Process() is that it is a generic function. With no arguments, it calls the process() method. 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 in the game. You can call Process() thusly:

    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 instance, here's how you can achieve a save of your game in one command (assuming a file has been opened for the purpose in *SAVEFILE):

    Process('save', -file => \*SAVEFILE);

The Process() function returns the number of objects that were processed, which is the number of objects in the game as a whole.

Modifying the default process sequence

There are two ways to do this. The first is you can define your own process() method in your subclass, overriding the built-in one, and thus call the other methods (and/or methods of your own devising) in any order you choose.

Another way you can do it is by altering the internal list that Games::Object uses to determine what methods to call. This can be done with the SetProcessList() function. For example, if you wished to remove processing of events from the initial phases and reverse the order of processing persistent modifiers and tend-to rates, you could make the following call:

    SetProcessList('process_tend_to', 'process_pmod', 'process_queue');

Note that these method calls can either be ones in Games::Object, or they can be ones that you define. You have complete control over exactly how your objects get processed.

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 Process() function 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:

    $obj->priority(5);

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

    Find('Player1')->priority(5);
    Find('RedDragon')->priority(3);
    Find('PurpleWorm')->priority(3);
    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). 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.

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 actions

As explained above, there are many places where actions are queued up in an object for later execution, such as when events are triggered, or 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");

Your method would look something like this:

    sub kill_creature
    {
        my $obj = shift;
        my %args = @_;
        my $obj_who = Games::Object::Find($args{who});
        my $obj_how = Games::Object::Find($args{how});

        $obj_who->mod_attr(-name => "experience",
                           -modify => $obj->attr('kill_xp') +
                                      $obj_how->attr('use_for_kill_xp') );
        ...
    }

Of course, you don't have to use queue() to execute a particular method in your class. Use queue() only if you're specifically looking to defer the action until the next time process() is called on the object, for the purposes of synchronization.

Saving object data to a file

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.

All the objects in the game can be easily saved at once by using the Process() function:

    Process('save', -file => \*SAVEFILE);

Loading a game could be accomplished by simply reading a file and creating new objects from it until the file is empty. Here is a code snippet that would do this:

    open(LOADFILE, "<./game.save") or die "Cannot open game save file\n";
    while (!eof(LOADFILE)) {
        my $obj = new Games::Object(-file => \*LOADFILE);
    }
    close(LOADFILE);

Note something about the above code: We called the constructor for the Games::Object class, NOT your subclass. This is because on a save(), the subclass is saved to the file, and when the object is re-created from the file, the Games::Object constructor is smart enough to re-bless it into your subclass. This means you can define more than one subclass from Games::Object and freely mix them in your game.

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. Interfaces may and probably will change in the immediate future as I improve on the design.

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

Oh, plenty, I'm sure.

TO DO

Attributes currently cannot be deleted once added to an object.

There needs to be an option to mod_attr() that forces a persistent mod to take effect immediately.

Cloning an object would be useful functionality to have.

Modifier cancel functionality needs improvement. There's no provision to check for the case of where the attribute was already at max/min before it was applied; in such a case, rescinding the modifier should rescind only what was applied, if any.

Need to expand event functionality. I would like to model it like Tk's bind() method. For example, I'd like to be able to register an event handler for an attrValueModified for a specifically-named attribute, while at the same time still retaining the functionality of defining an event handler for the much broader case.

Processing order for objects in Process() needs to be more consistent with attribute persistent mods. The former has no defined order when priorities are the same, while the latter specifies the order in which the mods were added. What might be nice would be a way to choose a truly random order for processing.

There needs to be a way to "encode" the game save data. Right now its in clear ASCII, which would make it easy to cheat.

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.