If 'at is a moose Ah woods hate tae see a rat

In my last D&D post I had at least the first problems worked out for Character race. Now I think I will move into the most challenging of all the 'Class' of a character.

In D&D a 'Class' is the role (JOB) a character plays in the game ie fighter, thief, mage etc. There are 5 primary Classes and 5 sub classes which is easy enough to model with Moose a 'Role' for each of the 10 and we are done.

5509980361_487e13de4c_b.jpg

Unlike the epic adventures above a character's 'Class' is not that easy to nail down. The rules around class are a mix subtle complexities, there may be only 5 basic 'Character' 'Classes' for 'Players' and 'NPCs' but each Character falls into one of these types

  • NPC with no class ie (0 level human farmer, Halfling barkeep, etc)
  • NPC or Player with a single class (Fighter, MU, Cleric)
  • NPC or Player with multi-classes (Fighter/MU, Cleric/Assassin)
  • NPC or Player with two Classes (Fighter-MU, Cleric-Fighter)

Fortunately I can again ignore, for now, some parts. For example only humans can have two-classes and only non-humans can be mulit-classed, some races can be one class but not another and some class/race combinations can only be NPCs. So I am going to add this sort of rule into the 'Character-Builder' black box. I can also ignore at this stage any actions involved in advancing up a 'Level'. This always happens after the adventure is over (at the Adventures Inn) and not during regular game-play or at least it shouldn't.

Lets have a look at the fighter first. The have the exception strength possibility, they share the 'To Hit' and 'Saving Throw' table with '0' levels and the other sub fighters (Paladin & Ranger) and the get a small XP bonus if they have high strength.

So lets go with the above but if I look at my earlier post I had the fighter as a separate 'class' not a 'role' so I guess I will have to figure out some way to fix that first as there is no 'BUILD' in a role!

Fortunately this is perl and we have CPAN in our bag of tricks and a good suggestion from an earlier post gave me the fix I wanted

so here goes

Fists my new 'Fighter' Role


package Fighter;
use MooseX::Role::Parameterized;
use Moose::Util qw(apply_all_roles );

parameter self => (
        isa      => 'Character',
        required => 1,
 );
 
 parameter exceptional => (
        isa      => 'Int',
  );
role {
      my $params = shift;
      requires 'strength';
      if ($params->self()->strength() == 18){
	  	apply_all_roles($params->self(), qw(ExceptionalStrength));
		$params->self()->exceptional($params->exceptional());
		if ($params->self()->exceptional() >90 and $params->self()->exceptional() <=99){
			apply_all_roles($params->{self}, qw(OpenBarredDoorsOnA1));
		}
		elsif ($params->self()->exceptional()  ==0 ){
			apply_all_roles($params->self(), qw(OpenBarredDoorsOnA1));
		}
	  }
	 
  };

and in my Character's BUILD


BUILD {
      my $self = shift;
      my ($attr) = @_;
      my $race = $self->race();
      eval{
		apply_all_roles($self, $self->race());
       };
      if ($@){
      	throw_exception( "NeitherRoleNorRoleNameIsGiven", {message=>"Race: ".$self->race()." is Invalid! Perhaps ".$self->race().".pm in not in the Path?"})
		    	unless (blessed $@);
		die $@;
	   };
 	  $attr->{self} = $self;
	  apply_all_roles($self,( "Fighter",=>$attr));
  };
and with my little perl script

use Character;
use Data::Dumper;
my $str = Character->new({constitution=>18,race=>'Dwarf',name=>'Sir Cumferace',strength=>18,exceptional=>91});

print $str->name()."\n";
print "strength=".$str->strength();
print " (".$str->exceptional().")\n"
	if($str->can('exceptional'));
print "opens a door on a ". $str->open_door_on_a()."\n";
print "opens a barred door on a ".$str->open_barred_doors_on_a()."\n"
  if($str->can('open_barred_doors_on_a'));
print "Saves vs WSR at +".$str->save_vs_WSR();

I get


Sir Cumferace
strength=18 (91)
opens a door on a 4
opens a barred door on a 1
Saves vs WSR at +4

moose-1-nobody-moved-demotivational-poster-1284056678.jpg

Sweet!!

Now what did I do here?

Well I used 'MooseX::Role:::Parameterized' to create a role that takes, as a required param, my 'Character' class and an optional param 'exceptional' it then checks the strength of the passed in 'self' ( I will have to clean that name up, me thinks) and sets things up like my old Fighter class BUILD did.

Now in the Character's 'BUILD' I simply add in the 'self=>$self' to the $attr (@_) hash-ref and then pass that along to the 'apply_all_roles', that way any Role that I might include in the future gets all of the init $attr so I can just keep this nice simple interface.

Still have to get rid of that hard coded 'Fighter' but that is a simple problem to fix.

I was presently surprised that this worked so esily. I wonder if Shawn M Moore ever though it would be used like this???

Looking at some of his example code;



 package MyGame::Weapon;
    use Moose;

    with Counter => { name => 'enchantment' };

    package MyGame::Wand;
    use Moose;

    with Counter => { name => 'zapped' };

Perhaps he had the same problem I had??


5 Comments

Hi byterock,

Love your series on Moose. I'm doing some of my first serious programming with Moose and your posts have been invaluable in helping me get my head wrapped around the concepts.

A few editorial comments:

What's a 'Halfling barkeek'? :-)

I think this code is incorrect:
apply_all_roles($self,( "Fighter",=>$attr));
or what does ",=>" do?

Thanks
--[Lance]

Generally speaking ,=> is used as an operator which has the same meaning as the comma operator, but the visual appearance of an arrow.

You might use it for example if you were defining a hash where the keys are constants (i.e. use constant). This is because the arrow is visually appealing when defining a hash, making it clear which items are keys and which are values, but because you're using constants for the hash keys, you wish to avoid the automatic bareword quoting behaviour of the arrow, and want it to behave more like a comma.

Is 'barkeek' an actual word or a typo for 'barkeep'? ;-)

Re: the code snippet. I put together a small test script and it sure does work either way.
#!/usr/bin/env perl
use Data::Dumper::Simple;
sub bar {
    my $self = shift;
    my %args = @_;
    print Dumper($self);
    print Dumper(%args);
}
my $attr->{self} = 'attrself';
bar('selfarg1', ( "Fighter",=>$attr));
bar('selfarg2', ( "Fighter" => $attr));
bar('selfarg3', ( "Fighter" , $attr));
Output:
$self = 'selfarg1';
%args = (
          'Fighter' => {
                         'self' => 'attrself'
                       }
        );
$self = 'selfarg2';
%args = (
          'Fighter' => {
                         'self' => 'attrself'
                       }
        );
$self = 'selfarg3';
%args = (
          'Fighter' => {
                         'self' => 'attrself'
                       }
        );

Leave a comment

About byterock

user-pic Long time Perl guy, a few CPAN mods allot of work on DBD::Oracle and a few YAPC presentations