• Advertisement

Archived

This topic is now archived and is closed to further replies.

Thoughts on spell "stacking" in an RPG

This topic is 5807 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I''m looking for a good way to handle spell stacking in an RPG environment. Let me explain what I mean (for those that may not know). Let''s say the player has 2 spells available. First is a +20 to AC spell, and the second one is +10 AC, +20 HP, and levitation. The effect I''m trying to create is one where the player can cast them both on himself and have the effects blend together, so he gets the 20AC from the first one, and the 20 HP/Levitation from the second one. My question is, how is a good way to handle this in code? I''m working in a multi-player Cooperative RPG environment (not MMORPG), but something you could play with friends over a network. My thought is, the structure that contains the player''s data should have elements to track current buff/debuff status of every stat that can be changed by magic. Then as each spell is cast, it checks the stat it''s changing and sees if something else has already changed that stat. Might work, but I''m looking for a better way. Plus, if a player has a curse on him that changes a stat, I may not want any spells to be able to change that stat until the curse wears off. Anyone have any thoughts on this?

Share this post


Link to post
Share on other sites
Advertisement
Interesting idea, as for implementing it may be kind of tough. But here is what i think...
...you could have states that would define the spells. You can pass this into some function that checks for spell mixing, the spell mixing function could then check bool variables to see if they are on or off. Then if you have more then one on, you can handle the code that way, by changing the stats.
SImple, dont know if that was what you were lookin for, but hopefully it helps, later-


what we do in life... echoes in eternity...

Share this post


Link to post
Share on other sites
I would guess that first you''d have to have some kind of list for a spell to see what it could or couldn''t stack with. Iterate through the current list of buffs/debuffs on a person to see if the spell can stack. A curse would work the same as a buff in this case.

Breakaway Games

Share this post


Link to post
Share on other sites
It sounds like you''ve already got a perfectly good solution in mind. Basically just keep a list of buffable stats. The list contains the current modification to the base stat. When a new buff is cast lookup the stat(s) it effects and adjust the modifier to be the the max of the old modifier and the new one.

"regular" debuffs could be a seperate entry in the list. So the final stat would be base+buff-debuff.

Curses could be handled by having a negative value in the buff slot and checking for that before adjusting the modifier.

-Mike

Share this post


Link to post
Share on other sites
Pick up the code base for your favorite MUD, and grep the code for affects.

Share this post


Link to post
Share on other sites
Thanks for the info. Haven''t played many muds. Anyone got a good example of some effects that stack like I''m looking for?

Share this post


Link to post
Share on other sites
Most MUD code is broken with regards to the 'affects' - hence the 'fixchar' command to restore the characters after the affects have broken them So I wouldn't recommend copying that stuff. It also doesn't do what OldRod asks, as the affects stack cumulatively.

I suggest you just store the spell effects in a list. The effects would be a load of attribute:modifier pairs, saying which attribute is modified and by how much. Now, when you call Player.GetAC() for example, this function takes your base AC, and then iterates through the spell effect list, totalling up any modifiers to AC as it goes. If you want the effects of some spells to override the effects of others, rather than be cumulative, you do this in the function that applies the spell to the character, by checking if there is already a modifier to the given stat, and if so, checking to see which spell takes precedence (removing the old effect if it should be replaced). This is simple enough to implement, and assuming you're not gonna have that many spells on you at any one time, this will usually be efficient enough.

Now, if you want cumulative spell effects (eg. where your example would have yielded 30 bonus AC points rather than 20) then it's easiest to just use a parallel set of 'modified stats' as already suggested and return (normal_stat + modified_stat) when the value is requested.

[ MSVC Fixes | STL | SDL | Game AI | Sockets | C++ Faq Lite | Boost ]

Edited by - Kylotan on February 25, 2002 3:18:03 PM

Share this post


Link to post
Share on other sites
quote:
Original post by Kylotan
Most MUD code is broken with regards to the ''affects'' - hence the ''fixchar'' command to restore the characters after the affects have broken them So I wouldn''t recommend copying that stuff. It also doesn''t do what OldRod asks, as the affects as the affects stack cumulatively.



Well, we used to treat the affects as a queue of function objects hanging off each of the character''s stats, so there was no need for a fixchar. The topmost object would cache the value so that you don''t have 15 function evaluations each time, only when the queue is altered. Ok, the affects did stack, but the architecture is flexible enough that you can change that by writing the appropriate functions in the function objects.

Share this post


Link to post
Share on other sites
quote:
Original post by Fruny
Well, we used to treat the affects as a queue of function objects hanging off each of the character''s stats, so there was no need for a fixchar. The topmost object would cache the value so that you don''t have 15 function evaluations each time, only when the queue is altered. Ok, the affects did stack, but the architecture is flexible enough that you can change that by writing the appropriate functions in the function objects.

Your system sounds a lot better than the code available in freely available MUD source. I was going to mention caching above, but I felt that overcomplicated the issue - after all, the speed hit in this part of the code is unlikely to be that big.

Ass you probably know, most MUDs out there just apply ''affects'' by adding their value to the player''s current stat. This goes haywire if the stat is altered through some other means, especially if there is some sort of floor or ceiling. (Example: my Strength is 20. Cast a Strength spell, giving me +6 making it currently 26. However, the system limits me to 25. Then, whe the spell wears off, its effect is applied in reverse (eg. subtract 6 points) and I''m down to 19 - broken system.

This is why I don''t recommend basing your code on existing freely available code.



[ MSVC Fixes | STL | SDL | Game AI | Sockets | C++ Faq Lite | Boost ]

Share this post


Link to post
Share on other sites
Ok, coding from memoy because I unfortunately do not have a backup (or maybe an old one somewhere). So be kind to my code A spell or similar effect would create pointers to derived affect classes and apply them to the relevant attributes, then remove them when deleted. The code I''ve posted here has flaws but that''s about what I remember from it.

  
template<class T>
class Affect // Base Affect class for stats of type T;

{
public:
virtual ~Affect() = 0;
virtual T& ApplyTo( T& stat ) = 0;
}

template<class T>
class Attribute // Class for stats of type T

{
friend class Affect<T>;
typedef Affect<T> aff_t;
typedef std::list<aff_t*> afflist_t;
private:
T base_stat;
afflis_t affs;
mutable bool cached;
mutable T cached_stat;
public:
Attribute( const T& stat )
: base_stat( stat ), cached( false )
{}

const T& () const { return base_stat; }
void Base( const T& stat )
{
base_stat = stat;
cached = false;
}

const T& Stat() const
{
Refresh();
return cached_stat;
}

void Refresh() const
{
if ( !cached )
{
cached_stat = base_stat;
afflist_t::iterator itor;
for( itor = affs.begin(); itor != affs.end(); ++itor )
(*itor)->ApplyTo( cached_stat )
cached = true;
}
}

void AddAffect( aff_t* affect )
{
affs.push_back( affect );
cached = false;
// lazy refresh

}

void DelAffect( aff_t* affect )
{
affs.remove( affect );
cached = false;
}
}

Share this post


Link to post
Share on other sites
I would try something like this:


struct modStates
{
int strChge
int hpChge
//and so on
};

class Creature //for monsters and characters
{
Private:
modStats mod
int baseStr
int baseHP //and so on

Public:
//constuctor and baseMod get/set functions
};


then if you have an item or a spell that moded your base steps you could just alter the the states in the struct and when calculating or testing a conditionyou could do something like this:

if (baseStr + strChge >= 10)
{
//do the stuff
}

this is kidda the n00b way to go about it i bet (gotta be a better way) but i seems easy to manage at least.

Share this post


Link to post
Share on other sites
Fruny - that code is pretty good, but I''d be interested to see how you can cleanly implement non-cumulative affects (presumably a combination of modifying the Refresh function and overriding the ApplyTo function in a derived class).

Additionally, why does everyone call them Affects? That''s the term they used in DIKU MUD, and technically it should be Effects (affect is a verb, effect is a noun). Oh well.

[ MSVC Fixes | STL | SDL | Game AI | Sockets | C++ Faq Lite | Boost ]

Share this post


Link to post
Share on other sites
this is off-topic, sorry, one of my hobbies is the misuse of the english language (well, more accurately, pointing out its misuse)...
affect (n): the conscious subjective aspect of an emotion considered apart from bodily changes.
affect (v): to make a display of liking or using OR to put on a pretense of
effect (n): something that inevitably follows an antecedent (as a cause or agent)
effect (v): to cause to come into being

Share this post


Link to post
Share on other sites
I think one of the best ways to logically and easily develop a non-cumulative buff system is to come up with buff categories. The categorization is very subjective, but an idea would be something like the following... (a simple example)

There are three categories of buffs, mental, physical, and spiritual...

You can have a mental buff to AC, a physical buff to AC, or a spiritual buff to AC. The mental buff would affect your mind''s ability to process information, increasing your ability to react to any actions taken against you. The physical buff would make your skin physically hard. The spiritual buff would create some sort of spirit shield around you. Now, you can have all 3 AC buffs on you at the same time. You couldn''t have to physical buffs on you at the same time, whether that physical buff was another AC buff, or was a str buff. Now you have to prioritize what sort of buffs you have access to, and which ones you want on you. You can choose to focus on gettings as much AC as possible, as much str as possible, or a combination of both if you have spells from the different categories. Of course, the gameplay itself needs to be such that there will be noticable pros and cons to every choice. Full offense vs full defense. Protection from physical attacks vs protection from magical attacks. etc.

Share this post


Link to post
Share on other sites
quote:
Original post by Kylotan
Additionally, why does everyone call them Affects?


The Answer is 42. I too started on a DIKU.

Ok, here's just a quick hack. There may be an issue as to the order of evaluation of the effects (multiplicative, thresholds...), which was the point of our affect system (in layering spells, the order would matter, spells could be cast to protect other spells from being dispelled...). This example assumes, as you seem to want it, that effects from differents sources are indeed additive (say, from invisibility and shield). One restriction will be that the modified value is computed from the base value, not from other modifiers : if you have base STR = 5, a STR+1, a STRx2, and a STR<=3 that do add together, the STRx2 is computed from the original stat as a 'STR+5' and the STR<=3 becomes a 'STR-2'.

Beware of the memory requirements though (that can create a LOT of objects).

            
template<class StatType> class Effect; // forward declaration


templace<class StatType>
class EffectCombiner
// These are used as global objects

// and represent the various types of

// effects which would add together.

{
// convenience typedefs

typedef Effect<StatType> eff_t;
typedef std::list<eff_t*> efflist_t;
public:
virtual StatType Modifier( StatType& stat, const efflist_t& list ) const
// returns the modifier of the effect that does apply.


// example for a maximum, when StatType is a numeric type

{
if (list.empty() ) return 0;

StatType max = - std::numeric_limits<StatType>::infinity();
efflist_t::iterator itor;
for( itor = list.begin(); itor != list.end(); ++itor )
{
StatType mod = (*itor)->Modifier( stat );
if (max < mod) max = mod;
}

return max;
}

template<class StatType>
class Effect
// Base Effect class for stats of type StatType

// Would carry information used by EffectCombiner<StatType>

// to take the decision on which effect to apply

{
friend class EffectCombiner;
public:
EffectCombiner<StatType>& combiner;
Effect( EffectCombiner<StatType>& _combiner )
: combiner( _combiner )
{}
virtual ~Effect() = 0;
virtual StatType Modifier( StatType& stat ) = 0;
// now returns the modifier only, which won't work

// as well for description modifying effects.

// May have to derive another hierarchy for those,

// or use Attribute::Modify()

}

template<class StatType>
class Attribute
// Class for stats of type StatType

{
// convenience typedefs

typedef Effect<StatType> eff_t;
typedef std::list<eff_t*> efflist_t;
typedef std::map<EffectCombiner<StatType>*, efflist_t> effmap_t;
private:
StatType base_stat;
effmap_t effs;
mutable bool cached;
mutable StatType cached_stat;
protected:
virtual StatType& Modify( const StatType& mod )
// example for a numeric StatType, where effects add up.

// strings would compare effects timestamps, or something.

{
return cached + mod;
}
public:
Attribute( const StatType& stat )
: base_stat( stat ), cached( false )
{}

const StatType& () const { return base_stat; }
void Base( const StatType& stat )
{
base_stat = stat;
cached = false;
}

const StatType& Stat() const
{
Refresh();
return cached_stat;
}

void Refresh() const
{
if ( !cached )
{
cached_stat = base_stat;
effmap_t::iterator itor;
// get each effect combiner to apply its selected effect to the stat.

for( itor = effs.begin(); itor != effs.end(); ++itor )
cached_stat = Modify( itor->first->Modifier( base_stat, itor->second ) );
cached = true;
}
}

void AddEffect( eff_t* effect )
{
efflist_t& list = affs[&effect.combiner]; // will create if key doesn't exist

list.push_back( effect );
cached = false;
// lazy refresh

}

void DelEffect( eff_t* effect )
{
effmap_t::iterator itor = effs.find( &effect.combiner );
if( itor == effs.end() ) return;
itor->remove( effect );
if( itor->empty )
effs.remove( &effect.combiner );
cached = false;
}
}


This code is probably full of bugs, shouldn't be templated (litteral vs. numeric type issues) and should probably have more checks in it. But hey, I warned you it's a hack.

Note to GDNet staff : could you make the input form wider (e.g. proportional)

Edit: s/T/StatType/g for Kylotan

Edited by - Fruny on February 26, 2002 3:23:26 PM

Share this post


Link to post
Share on other sites
quote:
Original post by Kylotan
Hmm, I think I see how it works, but it''s a bit complex. You should try using better typenames than ''T'' though



What''s wrong with T ? Oh, well, that''s only because you asked nicely. I don''t think it is overly complex, but I agree it could probably be done much better.

Share this post


Link to post
Share on other sites

  • Advertisement