Sounds like a fun (and educational) project. A couple of things you may want to look further into:<bl>
A common pattern for handling Undo is to have each undoable operation return an object that knows how to undo that operation. These objects carry enough context (perhaps the old value of whatever was changed) that they can be told to undo (but only in order). For more discussion on this, see the discussion of the Command pattern in the book "Design Patterns: Elements of Reusable Object-Oriented Software" or in the many web sites dealing with Design Patterns.
One kind of software architecture that has been useful is what is known as a "prototype based" system. This is a kind of object oriented system that does not depend on "classes" for inheriting (reusing) behavior, but rather on maintaining references to objects whose behavior is similar.
This works better than traditional class-based inheritance if you have many objects (or characters or other game artifacts in your system) that are almost but not quite the same in their behavior. For instance, a cursed sword of a certain kind might have subtle changes in its damage. Rather than testing this in the code for computing sword damage, you can instead attach the proper code bits for dealing with cursed swords.
This is a more flexible and fine-grained way to deal with the many kinds of behavior than a class-based system.
Perl's object model is flexible enough to support prototype based programming.
To understand how to do this, you first have to understand how Perl's method lookup works for an object: when you call a method on an object like this:
my $sword = Sword->new("Excalibur");
my $damage = $sword->damage($someOtherPlayer);
Perl will first look in the Sword package for a subroutine called "damage". Failing this, it will search each of the packages named in the @Sword::ISA list for a subroutine called "damage". If it still doesn't find damage(), then it will call AUTOLOAD with the name of the missing method ("damage") as well as the arguments to that method.
How can you do prototype based programming with this? Simple: by exclusively using AUTOLOAD to call whatever functions need to be overridden on a fine-grained basis (that is, anything you didn't define in the package of the object itself or one of the packages named in its class's @ISA array).
You can do this by maintaining named slots for overridable methods, putting subroutine references into these slots, and then calling them from your AUTOLOAD method.
Where the "prototype" comes in is that an object could refer to another object whose behavior it can share.
Of course, being lazy, one could look at CPAN and find the modules Class::SelfMethods or Class::Classless, which provide for a couple of different kinds of prototype-based programming support.
Best of luck with this project!
updated: clarified use of prototype