Friday, June 26, 2015

c++ - What's a way to implement a flexible buff/debuff system?


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.



Answer



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.)


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...