RPG class design question

Started by
9 comments, last by Quasimojo 13 years, 11 months ago
I'm working on a basic role-playing game. I have the following class from which all characters will inherit (drastically simplified for illustration):

class Entity
{
public:
    inline const string GetName() { return m_Name; }
    inline const long GetHP() { return m_HP; }
    inline const long GetMana() { return m_Mana; }
    inline const long GetSTR() { return m_STR; }
private:
    string m_Name;
    long m_HP;
    long m_Mana;
    long m_STR;
}

Of course there will be many other member variables such as intelligence, dexterity, etc. My question is, what would be a good method for implementing multiple distinct modifiers to individual stats? For example, the adventurer may equip a sword that provides +10 to strength (STR). I would also like to always be able to reference the base value and current bonuses from items (swords, armor pieces, etc.) and spells as three separate numbers, so that each could be modified by other factors at any time. Continuing the example, an adventurer with a base STR of 50 picks up that sword (+10 STR), giving him an effective STR stat of 60. Then a kindly sorcerer casts a Strength of the Bear spell on him, which provides another 20-point boost to STR, bringing his effective STR stat to 80. Of course multiple items and spells could be providing multiple modifications to multiple stats at once. Obviously, the first option is to add two modifier member variables for each stat member variable. Of course that would triple the number of member variables in use. Is there a better way to go about it? Perhaps a particular design pattern that could be used to address this more efficiently?
Advertisement
I would do the following:

Make a struct called modifer==>
struct Modifier{     std::string type;      // type of modifier (ie strength)     int modifierValue;     // the value of modifier (positive/negative)     unsigned int duration; // duration of effect; how long it has left (turns)};

You could then have a function called MakeModifier, that creates this struct instance===>
Modifier MakeModifier( std::string t, int val, unsigned int d ){     Modifier m;     m.type = t;     m.modifierValue = val;     m.duration = d;     return m;}

Then, as a private member variable of the Entity class you could have a member:
std::vector<Modifier> modifiers.

Every time you needed to add a modifier, you could say:
#include <vector>#include <iterator>Modifier m = MakeModifier( "strength", 10, 5 );modifiers.push_back( m );// to update modifiers:for ( std::vector<Modifier>::iterator i = modifiers.begin(); i != modifiers.end(); ){     // do any updating whatever...     (*i)->duration--; // decrement time left for effect     if( (*i)->duration <= 0 )     {          // effect duration is over; delete modifier          i = modifiers.erase(i); // no need to incr i,                                   // since next iterator is returned.     }     else          ++i; // increment iterator to next Modifier}


Where I have the comment "// do any updating whatever..." you could perform any needed updating or interaction there. Obviously, you can change the Modifier struct to include any other attributes/functionality that you would need.
That's a good idea. Then the total modifiers could be referenced by iterating through the modifiers list by type.

At the risk of getting a bit too ambitious with this, how might you suggest adding the ability to implement an additional type of modifier that applies itself using a different method of aggregation? For example, if the current aggregate spell modifier to STR were 20 points, and another spell were cast that increases all spell enhancements by 50%. Add an aggregation type type to the modifier, perhaps?
Quote:Original post by Quasimojo
That's a good idea. Then the total modifiers could be referenced by iterating through the modifiers list by type.

At the risk of getting a bit too ambitious with this, how might you suggest adding the ability to implement an additional type of modifier that applies itself using a different method of aggregation? For example, if the current aggregate spell modifier to STR were 20 points, and another spell were cast that increases all spell enhancements by 50%. Add an aggregation type type to the modifier, perhaps?
That would probably be one way to do it.

If you're going to go down that road you could even make everything a modifier. An attack would be a modifier. All items/spells/equipement would be a modifier. Heck, even gaining a level could be a modifier. Some modifiers would be applied to attributes permanently while others would be equipable/unequipable and others would expire after a certain period of time.

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

Yeah, you could have another vector called aggregateModifiers, perhaps.

Then you could have another member variable called totalStrengthMod. You would then modify the update function to:
// to update modifiers:totalStrengthMod = 0; // set to zero before calculating total effect.for ( std::vector<Modifier>::iterator i = modifiers.begin(); i != modifiers.end(); ){     // do any updating whatever...     if ( (*i)->type == "strength" )     {          totalStrengthMod += (*i)->modifierValue;     }     (*i)->duration--; // decrement time left for effect     if( (*i)->duration <= 0 )     {          // effect duration is over; delete modifier          i = modifiers.erase(i); // no need to incr i,                                   // since next iterator is returned.     }     else          ++i; // increment iterator to next Modifier}// then perform a similar operation as above for the aggregateModifiers:// basically something like:totalStrengthMod *= aggregateStrengthModifier;


Your aggregate modifiers could just be another layer on top of the regular modifiers. In fact, you could add another flag to Modifier--> bool aggregate. On a first pass thru the loop only process modifiers with the aggregate flag set to false. Then the second pass, process the modifiers with the aggregate flag set to true, calculating totals, like the totalStrengthMod calculation above.
Another way to do it would be to use decorators [wikipedia]
The trouble I see with decorators is that each successive decorator hides the previous state -- in other words, once its been decorated, there's no way to undecorate it without knowing how it was decorated in the first place.

Imagine this extension to the previous scenario:

An adventurer with a base STR of 50 picks up that sword (+10 STR), giving him an effective STR stat of 60. Then a kindly sorcerer casts a Strength of the Bear spell on him, which provides another 20-point boost to STR, bringing his effective STR stat to 80. Then, in battle, an evil warlock casts a powerful spell which negates the effect of magic cast upon the adventurer for 2 turns.

Using decorator, we don't have a good way of removing *just* the effect of the kindly sorcerer's magic unless the quantity is known. If we're happy having one such Strength of the Bear spell, and the purpose of the counter spell is specific to that one spell, then all is well and good, but this is very limiting.

For one, I think Strength of the Bear would be much more interesting as a percentage boost, rather than a purely quantitative one. Perhaps even one which tapers off or ramps up logarithmically, and perhaps to some limit. This would make it even more difficult for the warlock's spell to know how much strength to negate.

Secondly, decorator limits us by the order in which it is applied. So, do we decorate with the magic effects prior to the sword's stat boost (enchanting just the character), or do we apply magic afterward (enchanting the sword as well). What if we want to have a spell which enchants just the sword? Now what if an enchanted character is holding an enchanted weapon, and how does the evil warlock's spell negate both effects?


If I were writing this system, I would probably define a new "Stat" type, rather than trying to use the native types nakedly -- this makes sense, as we're essentially defining new semantics around the type such that it has optional, transient properties (and can be easily extended to have spacial properties -- say 'white magic is twice as effective inside a temple').

The stat type would consist of some scalar value, be it 'long' or 'double', and a list of Effects, both good and bad.

An Effect essentially consists of a mapping function that applies a simple transform on the base value (it might scale by a known quantity, or by percentage, logarithm, or whatever), returning the difference (in other words, the amount that the individual affect contributes to the final value.) Then, the overal stat is the sum of the base stat plus all of its effects. In its most basic form, an Effect might well be a simple function pointer, as no state is necessary -- however using a functor-like object is probably the better route, as you can define simple Effect 'classes' and then build variations using a factory model. Furthermore, certain effects might be temporal (time-limited), or require other information, and the Effect itself is the right place to store the necessary information. This system is very extensible as new effects can be made and slotted in easily if no current 'Effect class' satisfies the need.

Effects could, for the most part, be statically allocated, perhaps in a std::map for easy lookup. Then things with stats simply maintain a reference -- either a key to the map, or a direct pointer (with static allocation we can assume effects will continue to exist for some known scope). If memory is a concern, it is easy enough to toss a shared-resource system into the mix (perhaps based on shared_ptr), and in any case something similar would be benefitial, as it would allow you to have one-off effects (an Effect which is defined relative to some runtime state, such as a player's identity, health, or some other stat) as well.

I would then describe not only characters as having stats, but objects and weapons as well -- this makes it easy to apply Effects selectively. If the sorcerer casts Claw of the Tiger (a Spell which boosts edged weapons effectiveness by 20%) on the adventurer, we just check whether the weapon(s) he has equiped are 'edged' weapons and, if so, apply the effect to the (each) weapon. Easy as pie.

This is the basic system, though some details are left to be filled in -- I've already alluded that weapons might have additional, non-stat properties to query them by, and it might make sense that Effects would have them as well -- say at the end of a battle you want to clear all effects that last the duration of the battle. Extending this system a little further, it could be used to represent 'ailments' as well -- like Final Fantasy's 'Mute' or 'Stone' by having an Effect which puts the associated stats into some kind of nullified state -- or the system could just be duplicated whole-stock at the character or party level.

I also mentioned before that this can be extended to spacial effects -- Simply define an area in which a particular effect holds and add the effect when the character enters the area -- likewise, remove it when they leave the area.

Now, before anyone says that this sounds a lot like the decorator pattern, its similar to the extent that decorator is an application of mapping one value to another (optionally applied several times in sequence). The system here is also an application of mapping one value to another, but is applied here in parallel and then summed. I've also separated stats between character and object, but that change is orthogonal to to what I've presented here and applies equally to the standard decorator pattern. The other key thing is that the base stat always remains what it is, which gives us the working knowlege we need to impliment things like counter-spells (and if we want to actually negate specific effects, we can search the relevant stats for applied Effects that meet some criteria).

throw table_exception("(? ???)? ? ???");

Unrelated to the question but related to the OP code snippet. I'm pretty new to programming and I haven't found the need to use 'inline' or 'const' functions (i.e. I haven't been forced to by errors :))

Searching I found the following explanation:
Quote:
advantages of inline-no function calls need to be
generated.no stack required.
disadvantage -inline functions require more space..
the whole definition is copied.
by using const we insure the value of variable is not goin
to change..


Well now I might have been vaguely aware there is some sort of function stack, does anyone know of a good explanation of this online somewhere? I don't really know what they mean by 'more space' more space where? and of what? memory? bundled in the exe?

And for the 'const' part, what variable do they mean? Is it just there to indicate that the function is not going to change any member variables of the class? Does it prevent any changes to member variables in the case of something being coded wrong in the function?

Thanks.
Quote:Original post by PrestoChung
...stuff

You should post these questions in a new thread. I'd be happy to answer them, but I don't want to derail the original thread here.
Quote:
If you're going to go down that road you could even make everything a modifier. An attack would be a modifier. All items/spells/equipement would be a modifier. Heck, even gaining a level could be a modifier.
One should be aware that this denotes the "uber" approach if meant literally, and as such it will probably explode in your face. I.e. a spell is just a spell, but not a modifier. It may provide a modifier, though. That way modifiers can have their own inheritence tree, what is useful to implement different modifier behaviours (e.g. the said attribute modifiers vs modifier modifiers). It further allows a source (e.g. the spell) to create more than a single modifier, e.g. one for each party member or one for each entity in a vicinity range.

For a similar reason should the Modifier class not make concrete modification related data members publicly available, and the effect of modifiers should not directly be handled in a loop inside the owners code. Instead use a notification system, perhaps like the following:

The smallest set of notifications would be Modifier::added(Entity &) and Modifier::removed() which are obviously invoked on addition (i.e. the modifier makes self effective) and removal (i.e. the modifier makes self ineffective). Storing the moment when the effect of a modifier vanishes allows to sequentially link all modifiers in a sorted manner, so that usually just the front-most modifier needs to be checked for expiration.

If modifiers should be able to change their behaviour over time, then another notification is needed: Modifier::update(time). This is in fact nothing else than updating other time related stuff like animations.


Just my 2 EUR-Cent.

This topic is closed to new replies.

Advertisement