In Defender's Quest, each of your party members is a placeable "tower" in the battle system, but also a unique and persistent character who levels up and gains skills over the course of the entire game. Some of these skills cause the defender's attacks to inflict status effects .
Here's a short list of fun things you can do to monsters:
- Poison - X damage / second for Y seconds.
- On Fire - X damage / second for Y seconds, cancelled by ice
- Chilled - creep slowed by X% for Y seconds, cancelled by fire
- Frozen - creep stopped for Y seconds, cancelled by fire
- Stun - creep stopped for Y seconds
- Bleed - creep takes X% extra damage for Y seconds
- Confuse - creep walks backwards for X seconds
- Blind - creep's attacks miss X% of the time for Y seconds
- ...and many more!
Every status effect has two main variables - potency, and duration. Potency takes on a different meaning depending on the effect - for example, in poison, "potency" means damage per second (dps); for slow, it represents a speed multiplier. Duration, however, always means how many seconds the effect lasts for. So, stacking status effects is all about combining potency and duration in a way that:
- Is mathematically sound
- Doesn't look like a glitch
- Doesn't seem "unfair"
- Doesn't lead to weird edge-case exploits
There's several unique cases to consider, and ideally, one algorithm should be able to cover all of them. Let's go through the cases one by one as I step through my problem-solving methodology and come to a final decision.
1. Different effect types
This is the easiest case. First, check to see if the new status effect "interacts" with whatever is already applied to the enemy. If there's no interaction, then just apply the second effect - ie, an enemy can be bleeding and poisoned at the same time. However, if the enemy was on fire, and we just hit them with a chill (slow) spell, then we need to remove the fire first, since one of our rules is that fire/ice spells cancel each other. If we hit them with a freeze (stop) spell however, we'd cancel the fire, reduce the freeze to chill, and then apply that.
2. Same effect, identical stats
Not quite - the enemy has already been walking for 1 second before the second poisoned shot landed, so (A) is now actually (10dps | 4s). Even with identical starting stats, duration for any two effects will almost always be different in practice.
In a world where we can expect potencies to remain constant, the natural choice is to add durations together. Not only is this simple and intuitive to the player, the alternative method of making potency increase while keeping duration constant could make dps effects like poison really difficult to balance.
So, in our above example, we just add another 5 seconds to the poison clock and now our creep is poisoned to the tune of (10dps | 9s). All is well with the world!
3. Same effect, different stats
This is where it starts getting complicated. What if two different archers with different poison stats hit the same enemy? The prior example was easy because potencies matched, but here both numbers are different so simple addition is out the window.
A naive way to "combine" the values is just to average the two effects, but this fails to "conserve" the full power of both. Individually, the first effect will deal 40 damage total, and the second 50, for a total of 90. Averaging both effects to a single (15 dps | 3.5s) will only yield 52.5 damage, not what we want at all!
Conservation of PotencyDuration
Mathematically, the principle can be stated like this:
PDA + PDB = PDC
where (P: potency, D: duration, A, B: original effects, C: combined effect)
So, the first thing to do is redefine Poison A and Poison B in terms of PD values:
Now, we just have to convert the PD of Poison C back into potency and duration. There's several approaches we can take here - do we want to match the highest duration of the two effects, or the highest potency, or something else entirely? All it takes to satisfy equivalence is two values that multiply to make 90, but each approach will have unique subtle effects on gameplay and feedback that we need to consider as well.
1. Match Highest Duration
We matched the duration of Poison B, the longest-lasting effect, and converted Poison A's PD of 40 into an extra 8 dps. This dilutes Poison A's potency by stretching it out over a longer time. We're good, right?
Well, maybe not.
Take a look at this - here's what the player sees:
What???? In the player's eyes, the additional poison shot now causes the creep to take less damage. Sure, everything is still mathematically hunky-dory, and the player isn't really being screwed over - the same total damage will be inflicted, just over a longer time.
However, I'm not sure any of that will be clear - I can easily see players refusing to let two archers have overlapping targets, thinking this is "better" strategically since they've noticed that mixing rangers results in "lower" poison damage.
This is even worse if we were dealing with a slow effect instead of a poison effect. In this case, mage A casts slow (50% | 2s) on a creep, and then mage B hits the same creep with slow (30% | 10s).
- One mage slows the enemy down
- Another mage hits it with a second slow spell
- The enemy speeds up!
2. Match Highest Potency
Final ConsiderationsBefore we conclude things, let's compare our solution to our original goals.
Does it look like a glitch? Nope! By matching the highest potency, the player won't see enemies speeding up when hit by a slow spell, or poisoned arrows "reducing" poison damage. Now, if an enemy is hit by a stronger status effect, they will see the enemy either slowing down even more, or taking more poison damage, but this seems like a natural result.
Does it seem unfair? Generally this approach seems fair. In fact - it might be a gift to the player. By concentrating weaker effects to match the potency of stronger ones, the player gets more of their effect's strength up-front. Although the overall damage (in the case of poison) is the same over the effect's life-time, getting it done faster is almost always better in a tower defense game, since killing even one tile earlier can mean the difference between a close call and letting the creep reach the exit.
However, this "gift" to the player varies a bit with the effect. A bleeding enemy takes more damage when hit. This effect is more useful the longer it lasts, because there are more opportunities to hit the enemy and score bonus damage. So, whereas poison becomes more effective with our algorithm, bleed becomes slightly less. Something like slow isn't really affected - regardless of whether the slow spell is concentrated or stretched out, the enemy will reach the exit at the same time. Given that the algorithm has a mix of positive, negative, and neutral effects on the various status effects, it seems like a good general approach. Negatively impacted effects like bleed might need a slight balancing buff to make up the difference.
Does it lead to weird edge-case exploits? The only exploit I can think of is this: grouping lower-level defenders with a single high-level one, who "primes" the enemy with a high-potency effect, thus allowing all the subsequent defender's effects to match that potency and add time to it.
Honestly, that sounds like a perfectly legit strategy to me, and I don't see it unbalancing the game. Compared to the alternatives, this approach still seems the best.
Final Thoughts
There's one final approach we briefly considered and dismissed which I want to touch on again - allowing the potency to rise when mixing effects. I decided against this one for several reasons: first, there's no natural, obvious formula for how much to raise the potency by. Second, raising potency shortens the lifespan of the effect, which makes for less interesting gameplay. Third, it's much harder to predict what would happen in game if effects can rise in power just by stacking, so it seems ripe for exploitation / wrecking balance.
I hope you've enjoyed this little exercise. I found it interesting because it's a good example of a problem where technical and design needs overlap, and I hope it's useful to somebody in their next game.
If you have any ideas of your own, or can think of a better algorithm than the one I laid out here, do share your thoughts in the comments!
This is a companion discussion topic for the original entry at http://www.fortressofdoors.com/a-status-effect-stacking-algorithm/