Monday, July 9, 2018

c++ - How to implement buffs / debuffs / temporary stat changes in an RPG?




Possible Duplicate:
What’s a way to implement a flexible buff/debuff system?



In the context of creating an engine for a RPG I want to implement a generic way to give effects / buffs / debuffs to the player, for instance:


strength boost : +15 to strength last for 10 mins
sickness : all attributes are divided by 2. Permanent until removed


Now the problem is that effects can stack upon each others but they can be removed independently. For instance you could have both above-mentioned effects and only remove the "sickness" one leaving the other one.


Are there any existing solutions to this problem? My idea is to always recompute the "final" statistics by applying each effect in chronological order to the "initial" stat, but I wonder: is there any downside?



Answer



Given the fact that you have this sickness divisor, you'll get in trouble if you try to stack and destack your buffs chronologically. Arithmetic will trick you, e.g. this is what could happen:


        player base strength: 5
strength boost applied (+15): 20
sickness applied (/2): 10
strength boost removed (-15): -5 (--> oops?!)

To avoid this problem I would maintain a simple std::list of all the active buffs, update them and recompute the stats each frame. This is what a single buff/debuff could look like, with stats an array of size STAT_COUNT:



struct Buff
{
enum Type
{
Type_BonusMalus,
Type_Multiplier,
};

Type type;
int value[STAT_COUNT];

float timeLeft; // FLT_INFINITY = permanent

void updateAndApply(float dt, int* stats)
{
for (int i=0; i {
switch (type)
{
case Type_BonusMalus: stats[i] += value[i]; break;
case Type_Multiplier: stats[i] *= value[i]; break;

}
}
if (timeLeft != FLT_INFINITY)
{
timeLeft -= dt;
}
}

bool hasTimedOut()
{

return (timeLeft <= 0.0f);
}
};

Let's assume that buffList is the current std::list. Your update function would look something like this:


void updateAndApplyBuffs(float dt, int* stats)
{
// set base stats
setToBase(stats);


// update & apply buffs
std::list::iterator it;
for (it = buffList.begin(); it != buffList.end(); ++it)
{
it->updateAndApply(dt, stats);
}

// remove the ones that timed out
// SuperCool® STL stolen from http://stackoverflow.com/a/596708/1005455
buffList.remove_if(std::mem_fun(&Buff::hasTimedOut));

}

When your player picks up a new buff, you simply create it and push it to the back of the list. When you need to cancel a buff, remove it from the list. The buffs with a duration will time out by themselves after a while.


This will apply the buffs "chronologically", but I personally don't recommend this gameplay-wise. For instance the sickness divisor would be more or less penalizing depending on if you get it before or after a boost. To circumvent this issue, I would rather always apply the divisors/multipliers first, and the bonuses/maluses second:


void updateAndApplyBuffs(float dt, int* stats)
{
// set base stats
setToBase(stats);

std::list::iterator it;


// apply multipliers first
for (it = buffList.begin(); it != buffList.end(); ++it)
{
if (it->type == Buff::Type_Multiplier)
it->updateAndApply(dt, stats);
}

// then apply bonuses/maluses
for (it = buffList.begin(); it != buffList.end(); ++it)

{
if (it->type == Buff::Type_BonusMalus)
it->updateAndApply(dt, stats);
}

// remove buffs that timed out
// SuperCool® STL stolen from http://stackoverflow.com/a/596708/1005455
buffList.remove_if(std::mem_fun(&Buff::hasTimedOut));
}


This should give you a solid basis, not over-engineered, not too hacky.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...