74
\$\begingroup\$

Overview:

Lots of games with RPG-like statistics allow for character "buffs", ranging from simple "Deal 25% extra damage" to more complicated things like "Deal 15 damage back to attackers when hit."

The specifics of each type of buff aren't really relevant. I'm looking for a (presumably object-oriented) way to handle arbitrary buffs.

Details:

In my particular case, I have multiple characters in a turn-based battle environment, so I envisioned buffs being tied to events like "OnTurnStart", "OnReceiveDamage", etc. Perhaps each buff is a subclass of a main Buff abstract class, where only the relevant events are overloaded. Then each character could have a vector of buffs currently applied.

Does this solution make sense? I can certainly see dozens of event types being necessary, it feels like making a new subclass for each buff is overkill, and it doesn't seem to allow for any buff "interactions". That is, if I wanted to implement a cap on damage boosts so that even if you had 10 different buffs which all give 25% extra damage, you only do 100% extra instead of 250% extra.

And there's more complicated situations that ideally I could control. I'm sure everyone can come up with examples of how more sophisticated buffs can potentially interact with each other in a way that as a game developer I may not want.

As a relatively inexperienced C++ programmer (I generally have used C in embedded systems), I feel like my solution is simplistic and probably doesn't take full advantage of the object-oriented language.

Thoughts? Has anyone here designed a fairly robust buff system before?

Edit: Regarding Answer(s):

I selected an answer primarily based on good detail and a solid answer to the question I asked, but reading the responses gave me some more insight.

Perhaps unsurprisingly, the different systems or tweaked systems seem to apply better to certain situations. What system works best for my game will depend on the types, variance, and number of buffs I intend to be able to apply.

For a game like Diablo 3 (mentioned below), where nearly any bit of equipment can change a buff's strength, the buffs are just character stats system seems like a good idea whenever possible.

For the turn-based situation I'm in, the event-based approach may be more suitable.

In any case, I'm still hoping someone comes along with a fancy "OO" magic bullet which will allow for me to apply a +2 move distance per turn buff, a deal 50% of damage taken back to the attacker buff, and a automatically teleport to a nearby tile when attacked from 3 or more tiles away buff in a single system without turning a +5 strength buff into its own subclass.

I think the closest thing is the answer I marked, but the floor is still open. Thanks to everyone for the input.

\$\endgroup\$
6
  • \$\begingroup\$ I'm not posting this as an answer, as I'm just brainstorming, but how about a list of buffs? Each buff has a constant and a factor modifier. Constant would be +10 damage, factor would be 1.10 for a +10% damage boost. In your damage calculations, you iterate all the buffs, to get a total modifier, and then you impose any limitations you want. You'd do this for any kind of modifiable attribute. You'd need a special case method for complicated things though. \$\endgroup\$
    – William
    Commented Jun 1, 2012 at 19:10
  • \$\begingroup\$ Incidentally I'd already implemented something like that for my Stats object when I was making a system for equip-able weapons and accessories. As you said, it's a decent enough solution for buffs which only modify existing attributes, but of course even then I'll want certain buffs to expire after X turns, others to expire once the effect occurs Y times, etc. I didn't mention this in the main question as it was getting really long already. \$\endgroup\$
    – gkimsey
    Commented Jun 1, 2012 at 19:15
  • 1
    \$\begingroup\$ if you have a "onReceiveDamage" method that gets called by a messaging system, or manually, or some other way, it should be easy enough to include a reference to who/what you are taking damage from. So then you could make this information available to your buff \$\endgroup\$
    – user13213
    Commented Jun 1, 2012 at 19:16
  • \$\begingroup\$ Right, I had expected each event template for the abstract Buff class would include relevant parameters like that. It would certainly work, but I'm hesitant because it feels like it won't scale well. I have a hard time imagining an MMORPG with several hundred different buffs has a separate class defined for each buff, picking from a hundred different events. Not that I am making that many buffs (probably closer to 30), but if there's a simpler, more elegant, or more flexible system, I'd like to use it. More flexible system = more interesting buffs/abilities. \$\endgroup\$
    – gkimsey
    Commented Jun 1, 2012 at 19:25
  • 4
    \$\begingroup\$ This is not a good answer to the interaction problem, but it seems to me that the decorator pattern applies well here; just apply more buffs (decorators) on top of each other. Maybe with a system to handle interaction by "merging" buffs together (eg. 10x 25% merges into one 100% buff). \$\endgroup\$
    – ashes999
    Commented Jun 1, 2012 at 20:52

9 Answers 9

37
\$\begingroup\$

This is a complicated issue, because you're talking about a few different things that (these days) get lumped together as 'buffs':

  • modifiers to a player's attributes
  • special effects that happen on certain events
  • combinations of the above.

I always implement the first with a list of active effects for a certain character. Removal from the list, whether based on duration or explicitly is fairly trivial so I won't cover that here. Each Effect contains a list of attribute modifiers, and can apply it to the underlying value via simple multiplication.

Then I wrap it with functions to access the modified attributes. eg.:

def get_current_attribute_value(attribute_id, criteria):
    val = character.raw_attribute_value[attribute_id]
    # Accumulate the modifiers
    for effect in character.all_effects:
        val = effect.apply_attribute_modifier(attribute_id, val, criteria)
    # Make sure it doesn't exceed game design boundaries
    val = apply_capping_to_final_value(val)
    return val

class Effect():
    def apply_attribute_modifier(attribute_id, val, criteria):
        if attribute_id in self.modifier_list:
            modifier = self.modifier_list[attribute_id]
            # Does the modifier apply at this time?
            if modifier.criteria == criteria:
                # Apply multiplicative modifier
                return val * modifier.amount
        else:
            return val

class Modifier():
    amount = 1.0 # default that has no effect
    criteria = None # applies all of the time

That lets you apply multiplicative effects easily enough. If you need additive effects also, decide what order you're going to apply them in (probably additive last) and run through the list twice. (I'd probably have separate modifier lists in Effect, one for multiplicative, one for additive).

The criteria value is to let you implement "+20% vs Undead" - set the UNDEAD value on the Effect and only pass the UNDEAD value to get_current_attribute_value() when you're calculating a damage roll against an undead foe.

Incidentally, I wouldn't be tempted to try and write a system that applies and unapplies values directly to the underlying attribute value - the end result is that your attributes are very likely to drift away from the intended value due to error. (eg. if you multiply something by 2, but then cap it, when you divide it by 2 again, it'll be lower than it started with.)

As for event-based effects, such as "Deal 15 damage back to attackers when hit", you can add methods on the Effect class for that. But if you want distinct and arbitrary behaviour (eg. some effects for the above event might reflect damage back, some might heal you, it might teleport you away randomly, whatever) you'll need custom functions or classes to handle it. You can assign functions to event handlers on the effect, then you can just call the event handlers on any active effects.

# This is a method on a Character, called during combat
def on_receive_damage(damage_info):
    for effect in character.all_effects:
        effect.on_receive_damage(character, damage_info)

class Effect():
    self.on_receive_damage_handler = DoNothing # a default function that does nothing
    def on_receive_damage(character, damage_info):
        self.on_receive_damage_handler(character, damage_info)

def reflect_damage(character, damage_info):
    damage_info.attacker.receive_damage(15)

reflect_damage_effect = new Effect()
reflect_damage_effect.on_receive_damage_handler = reflect_damage
my_character.all_effects.add(reflect_damage_effect)

Obviously your Effect class will have an event handler for every type of event, and you can assign handler functions to as many as you need in each case. You don't need to subclass Effect, as each one is defined by the composition of the attribute modifiers and event handlers it contains. (It will probably also contain a name, a duration, etc.)

\$\endgroup\$
2
  • 2
    \$\begingroup\$ +1 for excellent detail. This is the closest response to officially answering my question as I've seen. The basic setup here seems to allow for a lot of flexibility, and a small abstraction of what could otherwise be messy game logic. As you said, the more funky effects would still need their own classes, but this handles the bulk of a typical "buff" system's needs, I think. \$\endgroup\$
    – gkimsey
    Commented Jun 4, 2012 at 20:29
  • \$\begingroup\$ +1 for pointing out the conceptual differences hidden here. Not all of them will work with the same event-based update logic. See @Ross's answer for a totally different application. Both will have to exist next to one another. \$\endgroup\$
    – ctietze
    Commented Aug 12, 2015 at 11:05
25
\$\begingroup\$

In a game that I worked on with a friend for a class we made a buff/debuff system for when the user gets trapped in tall grass and speed-up tiles and what not, and some minor things like bleeds and poisons.

The idea was simple, and while we applied it in Python, it was rather effective.

Basically, here is how it went :

  • User had a list of currently applied buffs and debuffs (note that a buff and debuff are relatively the same, it's just the effect that has a different outcome)
  • Buffs have a variety of attributes such as duration, name, and text for displaying information, and time alive. The important ones are time alive, duration, and a reference to the actor this buff is applied to.
  • For the Buff, when it is attached to the player via player.apply(buff/debuff), it would call a start() method, this would apply the critical changes to the player such as increasing speed or slowing down.
  • We would then iterate through each buff in an update loop and the buffs would update, this would increase their time alive. Subclasses would implement things like poisoning the player, giving the player HP over time, etc.
  • When the buff was done for, meaning timeAlive >= duration, the update logic would remove the buff and call a finish() method, which would vary from removing the speed limitations on a player to causing a small radius (think of a bomb effect after a DoT)

Now how to actually apply buffs from the world is a different story. Here is my food for thought though.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ This sounds like a better explanation of what I was trying to describe above. It's relatively simple, certainly easy to understand. You essentially mentioned three "events" there (OnApply, OnTimeTick, OnExpired) to further associate it with my thinking. As-is, it wouldn't support things like returning damage when hit and so forth, but it does scale better for lots of buffs. I'd rather not limit what my buffs can do (which = limiting the number of events I come up with that have to be called by the main game logic), but buff scalability may be more important. Thanks for your input! \$\endgroup\$
    – gkimsey
    Commented Jun 1, 2012 at 19:42
  • \$\begingroup\$ Yeah we didn't implement anything like that. It sounds really neat and a great concept (kind of like a Thorns buff). \$\endgroup\$
    – Ross
    Commented Jun 1, 2012 at 19:44
  • \$\begingroup\$ @gkimsey For things like Thorns and other passive buffs, I'd implement the logic in your Mob class as a passive stat similar to damage or health and increase this stat when applying the buff. This simplifies a lot the case when you have multiple thorns buffs as well as keeping the interface clean (10 buffs would show 1 return damage rather than 10) and lets the buff system remain simple. \$\endgroup\$
    – 3Doubloons
    Commented Jun 2, 2012 at 14:31
  • \$\begingroup\$ This is an almost counterintuitively simple approach, but I started thinking about myself when playing Diablo 3. I noticed the life steal, life on hit, damage to melee attackers, etc were all their own stats in the character window. Granted, D3 doesn't have the most complicated buffing system or interactions in the world, but it's hardly trivial. This makes a lot of sense. Still, there are potentially 15 different buffs with 12 different effects that would fall into this. Seems weird padding out the character stats sheet.... \$\endgroup\$
    – gkimsey
    Commented Jun 4, 2012 at 20:17
15
\$\begingroup\$

I am not sure if you are reading this still but I have struggled with this sort of problem for a long time.

I've designed numerous different types of affect systems. I'll briefly go over them now. This is all based on my experience. I don't claim to know all of the answers.


Static Modifiers

This type of system mostly relies on simple integers to determine any modifications. For example, +100 to Max HP, +10 to attack and so on. This system could also handle percents as well. You just need to make sure that the stacking doesn't get out of control.

I never really cached the generated values for this type of system. For example, if I wanted to display the max health of something, I would generate the value on the spot. This prevented things from being error prone and just easier to understand for everyone involved.

(I work in Java so the what follows is Java based but it should work with some modifications for other languages) This system can easily be done using enums for the modification types, and then integers. The end result can be placed into some sort of collection that has key,value ordered pairs. This will be fast lookup and calculations, so the performance is very good.

Overall, it works very well with just flat out static modifiers. Though, code must exist in the proper places for the modifiers to be used: getAttack, getMaxHP, getMeleeDamage, and so on and so forth.

Where this method fails (for me) is very complex interaction between buffs. There isn't a real easy way to have interaction except by ghettoing it a up a bit. It does have some simple interaction possibilities. In order to do that, you must make a modification to the way you store the static modifiers. Instead of using an enum as the key, you use a String. This String would be the Enum name + extra variable. 9 times out of 10, the extra variable is not used, so you still retain the enum name as the key.

Let's do a quick example: If you wanted to be able to modify damage against undead creatures, you could have an ordered pair like this: (DAMAGE_Undead, 10) The DAMAGE is the Enum and the Undead is the extra variable. So during your combat, you can do something like:

dam += attacker.getMod(Mod.DAMAGE + npc.getRaceFamily()); //in this case the race family would be undead

Anyways, it works fairly well and is fast. But it fails at complex interactions and having “special” code everywhere. For example, consider the situation of “25% chance to teleport on death”. This is a “fairly” complex one. The above system can handle it, but not easily, as you need the following:

  1. Determine if the player has this mod.
  2. Somewhere, have some code to execute the teleportation, if successful. The location of this code is a discussion in itself!
  3. Get the right data from the Mod map. What does the value mean? Is it the room where they teleport too? What if a player has two teleport mods on them?? Won't the amounts get added together?????? FAILURE!

So this brings me to my next one:


The Ultimate Complex Buff System

I once tried to write a 2D MMORPG by myself. This was an awful mistake but I learned a lot!

I rewrote the affect system 3 times. The first one used a less powerful variation of the above. The second one was what I'm going to talk about.

This system had a series of classes for each modification, so things like: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent. I had a million of these guys – even things like TeleportOnDeath.

My classes had things that would do the following:

  • applyAffect
  • removeAffect
  • checkForInteraction <--- important

Apply and remove explain themselves (though for things like percents, the affect would keep track of how much it increased the HP by to ensure when the affect wore off, it'd only remove the amount it added. This was buggy, lol, and took me a long time to make sure it was right. I still didn't get a good feeling about it.).

The checkForInteraction method was a horrodeously complex piece of code. In each of the affects (ie: ChangeHP) classes, it would have code to determine if this should be modified by the input affect. So for example, if you had something like....

  • Buff 1: Deals 10 Fire damage on attack
  • Buff 2: Increases all fire damage by 25%.
  • Buff 3: Increases all fire damage by 15.

The checkForInteraction method would handle all of these affects. In order to do this, each affect on ALL of the players near by had to be checked!! This is because the type of affects I had dealt with multiple players over a span of an area. This means the code NEVER HAD any special statements like above - “if we just died, we should check for teleport on death”. This system would automatically handle it correctly at the right time.

Trying to write this system took me like 2 months and made by head explode several times. HOWEVER, it was REALLY powerful and could do an insane amount of stuff – especially when you take into account the following two facts for abilities in my game: 1. They had target ranges (ie: single, self, group only, PB AE self, PB AE target, targetted AE, and so on). 2. Abilities could have more than 1 affect on them.

As I mentioned above, this was the 2nd of 3rd affect system for this game. Why did I move away from this?

This system had the worst performance I've ever seen! It was awfully slow as it had to do so much checking for each thing that went on. I tried to improve it, but deemed it a failure.

So we come to my third version (and another type of buff system):


Complex Affect Class with Handlers

So this is pretty much a combination of the first two: We can have static variables in an Affect class that contains a lot of functionality and extra data. Then just call handlers (for me, pretty much some static utility methods instead of subclasses for specific actions. But I'm sure you could go with subclasses for actions if you wanted too) when we want to do something.

The Affect class would have all of the juicy good stuffs, like target types, duration, number of uses, chance to execute and so on and so forth.

We would still have to add special codes in order to handle the situations, for example, teleport on death. We would still have to check for this in the combat code manually, and then if it existed, we would get a list of affects. This list of affects contains all of the currently applied affects on the player that dealt with teleporting on death. Then we would just look at each one and check to see if it executed and was successful (We'd stop at the first successful one). It it was successful, we'd just call the handler to take care of this.

Interaction can be done, if you want too. It'd just have to write the code to look for specific buffs on the players/etc. Because it has good performance (see below), it should fairly be efficient to do that. It just would need more complex handlers and so on.

So it has a lot of the performance of the first system and still a lot of complexity like the second (but not AS much). In Java at least, you can do some tricky things to get the performance of almost the first one in MOST cases (ie: having an enum map (http://docs.oracle.com/javase/6/docs/api/java/util/EnumMap.html) with Enums as the keys and ArrayList of affects as the values. This lets you to see if you quickly have affects [since the list would be 0 or the map wouldn't have the enum] and not having to continually iterate over player's affect lists for no reason. I don't mind iterating over affects if we need them at this time. I'll optimize later if it becomes a problem).

I'm currently re-opening (rewriting the game in Java instead of the FastROM code base it was originally in) my MUD that ended in 2005 and I have recently ran into how do I want to implement my buff system? I am going to be using this system because it worked nicely in my previous failed game.

Well, hopefully someone, somewhere, will find a few of these insights useful.

\$\endgroup\$
7
\$\begingroup\$

A different class (or addressable function) for each buff is not overkill if the behavior of those buffs is different from one another. One thing would be having +10% or +20% buffs (that, of course, would be better represented as two objects of the same class), other would be implementing wildly different effects that would require custom code anyway. However, I believe it's better to have standard ways of customizing the game logic instead of letting each buff do whatever it pleases (and possibliy interfering with each other in unforeseen ways, disturbing game balance).

I'd suggest dividing each "attack cycle" into steps, where each step has a base value, an ordered list of modifications that can be applied to that value (maybe capped), and a final cap. Each modification has an identity transformation as default, and can be influenced by zero or more buffs/debuffs. The specifics of each modification would depend on the step applied. How the cycle is implemented is up to you (including the option of an event-driven architecture, as you've been discussing).

One example of attack cycle could be:

  • calculate player attack (base + mods);
  • calculate opponent defense (base + mods);
  • do the difference (and apply mods) and determine the base damage;
  • calculate any parry/armor effects (mods on base damage) and apply damage;
  • calculate any recoil effect (mods on base damage) and apply to the attacker.

The important thing to note is that the earlier in the cycle a buff is applied the more effect it will have in the outcome. So, if you want a more "tactical" combat (where the player skill is more important than the character level) create many buffs/debuffs on the basic stats. If you want a more "balanced" combat (where the level matters more - important in MMOGs to limit rate of progress) only use buffs/debuffs later in the cycle.

The distinction between "Modifications" and "Buffs" I mentioned earlier has a purpose: decisions about rules and balance can be implemented on the former, so any changes on those don't need to reflect in changes to every class of the latter. OTOH, the numbers and kinds of buffs are only limited by your imagination, since each of them can express their desired behavior without having to take into account any possible interaction between them and the others (or even the existence of others at all).

So, answering the question: don't create a class for each Buff, but one for each (type of) Modification, and tie the Modification to the attack cycle, not to the character. The buffs can be simply a list of (Modification,key,value) tuples, and you can apply a buff to a character by simply adding/removing it to the character's set of buffs. This also reduces the window for error, since the character's stats don't need to be changed at all when the buffs are applied (so there's less risk to restore a stat to the wrong value after a buff expires).

\$\endgroup\$
1
  • \$\begingroup\$ This is an interesting approach because it falls somewhere between the two implementations I had considered -- that is, either just restricting buffs to fairly simple stat and result damage modifiers, or making a very robust but high-overhead system that could handle about anything. This is sort of an expansion of the former to allow the "thorns" while maintaining a simple interface. While I don't think it's the magic bullet for what I need, it certainly looks like it makes balancing much easier than other approaches, so it may be the way to go. Thanks for your input! \$\endgroup\$
    – gkimsey
    Commented Jun 4, 2012 at 20:26
3
\$\begingroup\$

I worked on a small MMO and all items, powers, buffs etc had 'effects'. An effect was a class that had variables for 'AddDefense', 'InstantDamage', 'HealHP', etc. The powers, items, etc would handle the duration for that effect.

When you cast a power or put on an item it would apply the effect to the character for the specified duration. Then the main attack, etc calculations would take into account the applied effects.

For example, you have a buff that adds defense. There would be at minimum a EffectID and Duration for that buff. When casting it, it would apply the EffectID to the character for the specified duration.

Another example for an item, would have the same fields. But the duration would be infinite or until the effect is removed by taking the item off the character.

This method allows you to iterate over a list of effects that are currently applied.

Hope I explained this method clearly enough.

\$\endgroup\$
1
  • \$\begingroup\$ As I understand it with my minimal experience, this is the traditional way to implement stat mods in RPG games. It works well and is easy to understand and implement. The downside is it doesn't seem to leave me any room to do things like the "thorns" buff, or something more advanced or situational. It also has historically been the cause of some exploits in RPGs, although they're pretty rare, and since I'm making a single player game, if someone finds an exploit I'm not really that concerned. Thanks for the input. \$\endgroup\$
    – gkimsey
    Commented Jun 4, 2012 at 20:40
3
\$\begingroup\$

I don't know if you are still reading it, but here is how I'm doing it now (code is based on UE4 and C++). After pondering on the problem for more than two weeks (!!), I finally found this:

http://gamedevelopment.tutsplus.com/tutorials/using-the-composite-design-pattern-for-an-rpg-attributes-system--gamedev-243

And I thought, that well, encapsulating single attribute within class/struct is not that bad idea after all. Keep in mind though, that I'm taking really big advantage of UE4 build in code reflection system, so without some rework, this might not be suitable everywhere.

In anyway, I started from wrapping attribute into single struct:

USTRUCT(BlueprintType)
struct GAMEATTRIBUTES_API FGAAttributeBase
{
    GENERATED_USTRUCT_BODY()
public:
    UPROPERTY()
        FName AttributeName;
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value")
        float BaseValue;
    /*
        This is maxmum value of this attribute.
    */
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value")
        float ClampValue;
protected:
    float BonusValue;
    //float OldCurrentValue;
    float CurrentValue;
    float ChangedValue;

    //map of modifiers.
    //It could be TArray, but map seems easier to use in this case
    //we need to keep track of added/removed effects, and see 
    //if this effect affected this attribute.
    TMap<FGAEffectHandle, FGAModifier> Modifiers;

public:

    inline float GetFinalValue(){ return BaseValue + BonusValue; };
    inline float GetCurrentValue(){ return CurrentValue; };
    void UpdateAttribute();

    void Add(float ValueIn);
    void Subtract(float ValueIn);

    //inline float GetCurrentValue()
    //{
    //  return FMath::Clamp<float>(BaseValue + BonusValue + AccumulatedBonus, 0, GetFinalValue());;
    //}

    void AddBonus(const FGAModifier& ModifiersIn, const FGAEffectHandle& Handle);
    void RemoveBonus(const FGAEffectHandle& Handle);

    void InitializeAttribute();

    void CalculateBonus();

    inline bool operator== (const FGAAttributeBase& OtherAttribute) const
    {
        return (OtherAttribute.AttributeName == AttributeName);
    }

    inline bool operator!= (const FGAAttributeBase& OtherAttribute) const
    {
        return (OtherAttribute.AttributeName != AttributeName);
    }

    inline bool IsValid() const
    {
        return !AttributeName.IsNone();
    }
    friend uint32 GetTypeHash(const FGAAttributeBase& AttributeIn)
    {
        return AttributeIn.AttributeName.GetComparisonIndex();
    }
};

It's still not finished but the base idea, is that this struct keep track of it's internal state. Attributes can be modified only by Effects. Trying to modify them directly is unsafe and not exposed to designers. I'm assuming that everything, that can interact with attributes is Effect. Including flat bonuses from items. When new item is equipped, new effect (along with handle) is created, and it is added to dedicated map, which handle infinite duration bonuses (those that must be manually removed by player). When new Effect is being applied, new Handle for it is created (handle is just int, wrapped with struct), and then that handle is passed all around as a means to interacting with this effect, as well as keep track if the effect is still active. When effect is removed, it's handle is broadcasted to all interested objects, for clean up.

The real important part of this is TMap (TMap is hashed map). FGAModifier is very simple struct:

struct FGAModifier
{
    EGAAttributeOp AttributeMod;
    float Value;
};

It contains type of modification:

UENUM()
enum class EGAAttributeOp : uint8
{
    Add,
    Subtract,
    Multiply,
    Divide,
    Set,
    Precentage,

    Invalid
};

And Value which is final calculated value we are going to apply to attribute.

We add new effect using simple function, and then call:

void FGAAttributeBase::CalculateBonus()
{
    float AdditiveBonus = 0;
    auto ModIt = Modifiers.CreateConstIterator();
    for (ModIt; ModIt; ++ModIt)
    {
        switch (ModIt->Value.AttributeMod)
        {
        case EGAAttributeOp::Add:
            AdditiveBonus += ModIt->Value.Value;
                break;
            default:
                break;
        }
    }
    float OldBonus = BonusValue;
    //calculate final bonus from modifiers values.
    //we don't handle stacking here. It's checked and handled before effect is added.
    BonusValue = AdditiveBonus; 
    //this is absolute maximum (not clamped right now).
    float addValue = BonusValue - OldBonus;
    //reset to max = 200
    CurrentValue = CurrentValue + addValue;
}

This function is supposed to recalculate entire stack of bonuses, each time effect is added or removed. Function is still not finished (as you can see), but you can get the general idea.

My biggest gripe right now is handling Damaging/Healing attribute (without involving recalculating entire stack), I think I have this somewhat solved, but it still require more testing to be 100%.

In anycase Atttributes are defined like this(+Unreal macros, omitted here):

FGAAttributeBase Health;
FGAAttributeBase Energy;

etc.

Also I'm not 100% sure about handling CurrentValue of attribute, but it should work. They way it is now.

In any case I hope it will save some people head caches, not sure if this is best or even good solution, but I like it more, than tracking effects independently of attributes. Making each attribute tracking it's own state is much easier in this case, and should be less error prone. There is essentially only one point of failure, which is fairly short and simple class.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the link and explanation of your work! I think you're moving towards essentially what I was asking for. A few things that spring to mind are order of operations (for instance, 3 "add" effects and 2 "multiply" effects on the same attribute, which should happen first?), and this is purely attribute support. There's also the notion of triggers (like "lose 1 AP when hit" type of effects) to address, but that'd likely be a separate investigation. \$\endgroup\$
    – gkimsey
    Commented Feb 11, 2015 at 15:49
  • \$\begingroup\$ The order of operation, in case for just calculating bonus of attribute is easy to do. You can see here that I have there for and switch. For iterate over all current bonuses (which can be add, subtract, multiply, divide etc), and then just accumulate them. The you do something like BonusValue = (BonusValue*MultiplyBonus + AddBonus-SubtractBonus) / DivideBonus, Or however you want to look this equation. Due to single point of entry it's easy to experiment with it. As for triggers, I haven't written about it, because that is the another problem I ponder around, and I already tried 3-4 (limit) \$\endgroup\$ Commented Feb 12, 2015 at 6:51
  • \$\begingroup\$ solutions, none of them worked they way I wanted (my main aim, is for them to be designer friendly). My general idea, is to use Tags, and check, incoming effects against tags. If tag match, effect can trigger other effect. (tag is simple human readble name, like Damage.Fire, Attack.Physical etc). On the core is very easy concept, the issue is organizing data, to be easily accessible (fast for search) and ease of adding new effects. You can check code here github.com/iniside/ActionRPGGame (GameAttributes is the module you will be interested) \$\endgroup\$ Commented Feb 12, 2015 at 6:55
2
\$\begingroup\$
  1. If you´re a unity user, here is something to get started: http://www.stevegargolinski.com/armory-a-free-and-unfinished-stat-inventory-and-buffdebuff-framework-for-unity/

Im using ScriptableOjects as buffs/spells/talents

public class Spell : ScriptableObject 
{
    public SpellType SpellType = SpellType.Ability;
    public SpellTargetType SpellTargetType = SpellTargetType.SingleTarget;
    public SpellCategory SpellCategory = SpellCategory.Ability;
    public MagicSchools MagicSchool = MagicSchools.Physical;
    public CharacterClass CharacterClass = CharacterClass.None;
    public string Description = "no description available";
    public SpellDragType DragType = SpellDragType.Active; 
    public bool Active = false;
    public int TargetCount = 1;
    public float CastTime = 0;
    public uint EffectRange = 3;
    public int RequiredLevel = 1;
    public virtual void OnGUI()
    {
    }
}

using UnityEngine; using System.Collections.Generic;

public enum BuffType { Buff, Debuff } [System.Serializable] public class BuffStat { public Stat Stat = Stat.Strength; public float ModValueInPercent = 0.1f; }

public class Buff : Spell
{
    public BuffType BuffType = BuffType.Buff;
    public BuffStat[] ModStats;
    public bool PersistsThroughDeath = false;
    public int AmountPerTick = 3;
    public bool UseTickTimer = false;
    public float TickTime = 1.5f;
    [HideInInspector]
    public float Ticktimer = 0;
    public float Duration = 360; // in seconds
    public float ModifierPerStack = 1.1f;
    [HideInInspector]
    public float Timer = 0;
    public int Stack = 1;
    public int MaxStack = 1;
}

BuffModul:

using System;
using RPGCore;
using UnityEngine;

public class Buff_Modul : MonoBehaviour
{
    private Unit _unit;

    // Use this for initialization
    private void Awake()
    {
        _unit = GetComponent<Unit>();
    }

    #region BUFF MODUL

    public virtual void RUN_BUFF_MODUL()
    {
        try
        {
            foreach (var buff in _unit.Attr.Buffs)
            {
                CeckBuff(buff);
            }
        }
        catch(Exception e) {throw new Exception(e.ToString());}
    }

    #endregion BUFF MODUL

    public void ClearBuffs()
    {
        _unit.Attr.Buffs.Clear();
    }

    public void AddBuff(string buffName)
    {
        var buff = Instantiate(Resources.Load("Scriptable/Buff/" + buffName, typeof(Buff))) as Buff;
        if (buff == null) return;
        buff.name = buffName;
        buff.Timer = buff.Duration;
        _unit.Attr.Buffs.Add(buff);
        foreach (var buffStat in buff.ModStats)
        {
            switch (buff.BuffType)
            {
                case BuffType.Buff:
                    _unit.Attr.AddBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] + _unit.Attr.StatsItem[buffStat.Stat]) * buffStat.ModValueInPercent));
                    break;
                case BuffType.Debuff:
                    _unit.Attr.RemoveBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] /*+ unit.character.StatsItem[_stat.stat]*/) * buffStat.ModValueInPercent));
                    break;
            }
            Core.StatController(_unit.Attr, buffStat.Stat);
        }
    }

    public void RemoveBuff(Buff buff)
    {
        foreach (var buffStat in buff.ModStats)
        {
            switch (buff.BuffType)
            {
                case BuffType.Buff:
                    _unit.Attr.RemoveBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] + _unit.Attr.StatsItem[buffStat.Stat]) * buffStat.ModValueInPercent));
                    break;
                case BuffType.Debuff:
                    _unit.Attr.AddBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat]  /*+ unit.character.StatsItem[_stat.stat]*/) * buffStat.ModValueInPercent));
                    break;
            }
            Core.StatController(_unit.Attr, buffStat.Stat);
        }
        _unit.Attr.Buffs.Remove(buff);
    }

    void CeckBuff(Buff buff)
    {
        buff.Timer -= Time.deltaTime;
        if (!_unit.IsAlive && !buff.PersistsThroughDeath)
        {
            if (buff.ModStats != null)
                foreach (var stat in buff.ModStats)
                {
                    _unit.Attr.StatsBuff[stat.Stat] = 0;
                }

            RemoveBuff(buff);
        }
        if (_unit.IsAlive && buff.Timer <= 0)
        {
            RemoveBuff(buff);
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

I know this is fairly old but it was linked in a newer post and I have some thoughts on it I'd like to share. Unfortunately I don't have my notes with me at the moment, so I'll try to give a general overview of what I'm talking about and I'll edit in the details and some example code when I have it in front of me.

First, I think that from a design perspective most people are getting too caught up in what types of buffs can be created and how they are applied and forgetting the basic principles of object oriented programming.

What do I mean? It doesn't really matter whether something is a buff or a debuff, they are both modifiers that just affect something in either a positive or negative manner. The code doesn't care which is which. For that matter it doesn't ultimately matter whether something is adding stats or multiplying them, those are just different operators and again the code doesn't care which is which.

So where am I going with this? That designing a good (read: simple, elegant) buff/debuff class isn't all that difficult, what's hard is designing the systems that calculate and maintain the game state.

If I were designing a buff/debuff system here are some things I'd consider:

  • A buff/debuff class to represent the effect itself.
  • A buff/debuff type class to contain the information about what the buff affects and how.
  • Characters, Items and possibly Locations would all need to have a list or collection property to contain buffs and debuffs.

Some specifics for what buff/debuff types should contain:

  • Who/what it can be applied to, IE: player, monster, location, item, etc.
  • What type of effect it is (positive, negative), whether it is multiplicative or additive, and what type of stat it impacts, IE: attack, defense, movement, etc.
  • When it should be checked (combat, time of day, etc).
  • Whether it can be removed, and if so how it can be removed.

That's just a start, but from there you're just defining what you want and acting on it using your normal game state. For example, say you wanted to create a cursed item that reduces movement speed...

As long as I have put the proper types in place it's simple to create a buff record that says:

  • Type: Curse
  • ObjectType: Item
  • StatCategory: Utility
  • StatAffected: MovementSpeed
  • Duration: Infinite
  • Trigger: OnEquip

And so forth, and when I create a buff I just assign it the BuffType of Curse and everything else is up to the engine...

\$\endgroup\$
1
\$\begingroup\$

This was an actual question for me. I have one idea about it.

  1. As previously said, we need to implement a Buff list and a logic updater for the buffs.
  2. We then need to change all specific player settings every frame in subclasses of the Buff class.
  3. We then get the current player settings from changable settings field.

class Player {
  settings: AllPlayerStats;

  private buffs: Array<Buff> = [];
  private baseSettings: AllPlayerStats;

  constructor(settings: AllPlayerStats) {
    this.baseSettings = settings;
    this.resetSettings();
  }

  addBuff(buff: Buff): void {
    this.buffs.push(buff);
    buff.start(this);
  }

  findBuff(predcate(buff: Buff) => boolean): Buff {...}

  removeBuff(buff: Buff): void {...}

  update(dt: number): void {
    this.resetSettings();
    this.buffs.forEach((item) => item.update(dt));
  }

  private resetSettings(): void {
    //some way to copy base to settings
    this.settings = this.baseSettings.copy();
  }
}

class Buff {
    private owner: Player;        

    start(owner: Player) { this.owner = owner; }

    update(dt: number): void {
      //here we change anything we want in subclasses like
      this.owner.settings.hp += 15;
      //if we need base value, just make owner.baseSettings public but don't change it! only read

      //also here logic for removal buff by time or something
    }
}

In this way, it can be easy to add new player stats, with no change to the logic of the Buff subclasses.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .