|
(Cross-posted on my personal blog, Fortress of Doors)

Status effects are a cool way to add strategic depth to both Tower Defense games and RPG's, and since our upcoming title Defender's Quest is a hybrid of both, it's chock full of 'em, and unlike some RPG's, we took special care to make sure that they're not totally useless.
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!
|

|
|
Ice Mage freezing and slowing creeps
|
The Problem
As you upgrade these skills, both the potency and the duration of the status effect goes up. Shortly after implementing this system, I ran into a dilemma - what do I do when two status effects hit the same enemy? This problem was particularly interesting because it was both a technical and a design problem.
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

Let's say an archer with poisoned arrows hits an enemy, and then hits them again, inflicting the same status effect twice at different times. Let's say the first poison effect (A) is 10 dps for 5 seconds, or (10dps | 5s) for short. This will deal 50 damage over the life of the effect. One second later, the archer hits the same enemy with an identical poison effect (B), also (10dps | 5s). In this case, the variables will be the same, so it's pretty easy to combine them, right?

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!

So, whatever we do, combining the two needs to result in 90 total damage at the end of the day. More generally, the PotencyDuration value of the combined effect must equal the sum of the PotencyDuration of each effect taken individually. This principle, "conservation of PotencyDuration," should work in all cases, no matter what values the status effects have.
Conservation of PotencyDuration
Mathematically, the principle can be stated like this:
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:
|

|
|
Archer hits a creep
|
|

|
|
Creep takes 20 damage per second
|
|

|
|
Second archer hits the same creep
|
|

|
|
Creep takes 18 damage every second
|
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).

What the player sees is:
- One mage slows the enemy down
- Another mage hits it with a second slow spell
- The enemy speeds up!
In both cases Potency-Duration is conserved, but the player will be misled because their chief visual feedback is the size of the purple numbers bouncing off of the enemy, or how fast the creep is moving. Let's try something else.

In this case, we've made the combined effect match the potency of Poison A, the strongest effect, and converted Poison B's PD value into 20 dps for 4.5 seconds. Instead of diluting Poison A, we're concentrating Poison B, squeezing it into a shorter time-frame so that the potency can match.
With this approach, the player will see no change in the amount of damage happening each turn, but the effect will last for longer thanks to the extra poisoned arrow. The player will see the same amount of damage bouncing off the poisoned enemy every second, but each extra poisoned hit will keep the effect going a little bit longer.
Sounds good to me!
Final Considerations
Before we conclude things, let's compare our solution to our original goals.
Is it mathematically sound?
Yep! We're conserving Potency-Duration, so everything's good here.
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, please share your thoughts in the comments!
EDIT: Really good ideas being thrown around in the comments. Consider the first dozen or so comments "part 2" of this article!
|
Why not just keep track of all effects currently applied, and then each ‘tick’ apply them all. As you apply them keep buckets for each effect type, such as damage, chill, etc. Then fill these buckets with whatever you find stacked. If there is five chills in a bucket at the end then take 5% off the speed. If during the process of filling the buckets you add five chill, but then a fire effect takes 2 away, the end result is 3% speed off. It’s possible the fire effect will wear off before the chill, in which case the chill will go back to the full 5% for the remainder. Unless of course another effect is stacked later, that continues to modify the total chill effect. Then balance the game around lower attacks that are expected to stack to make stronger attacks. This seems like just adding everything up, and mostly it is like that, the difference however is that you watch the timer on each effect independently and allow it to expire, and allow the effects to merge how they will. I feel sort of silly posting this because it’s so similar to some of the concepts you list above. But please set me straight if I just rehashed your work above.
We could just multiple effects of the same type separately, of course, like you say - just keep two instances of something and let them both be active rather than figuring out how to combine both.
There's a few reasons to consolidate rather than use this method - the first is performance and book-keeping - if each instance of poison gets its own entry, than we could easily have 10x as many status effect entries per enemy in the worst case, and considering we have lots of enemies on screen at once, this can start to add up. Consolidating effects helps with this.
Secondly, as I mention above, if we treat each one separately, the player doesn't see what's going on under the hood - all they know is "the enemy is poisoned," and we can still wind up with the situation where an enemy is poisoned with a strong poison first, then a weak poison. Then, the strong poison wears off and suddenly the enemy is taking less damage than before, and the player might be confused as to why this happened.
Secondly, as I mentioned in my reply to Jesse below, speed is a very important special case - we don't want you to be able to slow an enemy down to zero just because you have a slowdown spell that you can stack, because slow should be qualitatively different than slow.
Furthermore, with slow, we considered having analog fire/ice interaction, but decided it's easiest in terms of feedback and tactics to make it binary. Ie, fire cancels one "level" of ice regardless of stats, with the two levels being chill(slow) and freeze(stop).
This is for class balance reasons and to distinguish fire DPS from poison. Poison has no problem interacting with ice, but has lower DPS which only comes from dragons, the ultimate class. Dragons are awesome, but to make the player use them wisely, their fire effects will work against any slowing/stopping you've been doing with your ice mages. This small drawback keeps dragons from overshadowing the other classes late in the game, and keeps them from being a "cheeseburger" you can just plunk down anywhere.
The analog approach - 0.5 fire cancels 0.5 iciness - works from a mathematical perspective, but is a little less clear to the player when the screen is filled with enemies, bouncing numbers, and icons. When a dragon starts breathing fire on slow enemies and then they start moving faster, it's a little simpler to understand, and to plan for tactically.
That's the reasoning, at least! Clearly there's a diversity of opinions on this so I'm open to changing things, too.
I'm having difficulty seeing this as exploitative because no matter how you fiddle with the numbers, you're going to do the same damage in the end as long as you conserve total damage. In an additive stacking scenario, enemies would take more damage sooner but I'm not sure if that's a particular benefit. I don't find your "Match Highest Potency" solution to be bad at all, it just doesn't provide immediate feedback to the player what is happening unless they're paying attention to duration of effects.
For binary effects like confuse, I would definitely consider duration stacking to be the way to go.
I'm curious as to how you plan on calculating %slowdown on chill when converting it from a freeze.
The main reason not to stack potency is that an enemy is likely to get hit over and over and over again and then the qualitative aspects of the status effects can tend to get lost.
With poison, if we really compress the strength in the same short amount of time from multiple poisonings, then we start to lose the effect of "hit the guy just once, but keep doing damage after he's walked out of range." The more the effect is allowed to stack, the more it starts to behave like a straight damage buff to the main attack, and we already have a different skill for that (critical hit). Matching potency and adding time to the effect adds to the "do damage long after you first hit them" aspect of poison.
The more we can make effects behave differently, the more interesting the game choices can be because things won't just all boil down to maximizing DPS.
I think chill/slow is a better example, however. With slow, if we allow the potencies to stack, then eventually the enemy is going to tend towards 99% slowdown, in which case slow is pretty much identical to stop if you can just cast it enough times, and then there's less reason to invest in the freeze/stop skill.
So that's where I'm coming from. If I've missed anything, let me know! This is shaping up to be an interesting conversation!
EDIT:
Also, looking at things a second time, I think you might be right that there can be some feedback ambiguity in my approach. With your method, an enemy that gets pincussioned with poisoned arrows one after another would display a sequence of poison damage numbers that more or less matches a normal distribution as they stack together and then wear off, which might indeed be more intuitive!
As for how we calculate % slowdown on chill - that's a good question! Looking at my code it looks like I didn't implement that one feature the way I said in the article - fire just cancels freeze and chill outright, and vice versa, regardless of stats. This means I don't have an answer to your question! Just thinking off the top of my head right now, possible solutions are:
1) Have the "freeze" skill have a second, unused potency variable in case it is downgraded by fire. This levels up with the skill itself. When the freeze is hit by fire, the duration stays the same and this is the potency used for the speed debuff.
2) Calculate how much "freeze time" was left on the effect, multiply that by some penalty constant for melting (say, 0.5), arbitrarily set the potency to 50% speed debuff, then use the PD equation to get the duration.
3) Got a better idea?
I'm thinking that if you pincushion a single mob with a lot of poison or fire DOTs, chances are they'll drop long before they take total damage. If it's a boss, your implementation could possibly result in so much stacked duration that any more poison damage would not really matter because the boss will reach the creep goal (assuming standard mechanics of tower defense) long before the poison wears off.
Here's a weird scenario to consider: A boss creep enters, and you have a bunch of mages rapidly casting 10DPS for 10 seconds. By the time the boss is through the barrage, he has 10DPS for the next 100 seconds. That in its own right should be raising some flags. If your spell crafting system is very flexible, I might want to make some sort of poison caster that casts 100 DPS for one second so that I can load up the boss with slow poison and then unleash it all with this single caster. I'm not sure if bleed affects poison damage, but I'd probably apply some bleed right before this. This could promote interesting gameplay, but I'm afraid it might be relying on players figuring out the system a little bit too deeply.
For something like slow, a few alternatives could be
1. To have a hard cap on how slow a creep can go. If you really don't want to cheat the player, you could always use your conversion formula above to turn an additional 10% slow for 5 seconds to 85% slow for an additional .59 seconds, given an 85% cap.
2. Actually freeze the creep. If you already have freezing in the game, you'll probably need some sort of conversion for what will happen if you cast frost on a frozen guy. If a guy gets so frosted that he freezes, maybe you can just use that conversion to add additional freeze duration.
For the fire vs freeze, I would probably use fire to shorten the freeze duration by some conversion rate. People don't have any outright expectations for how much fire is required to reduce freeze by a second, so you can really use whatever you want!
Have you played Magicka? They do a pretty good job at juggling a bunch of elemental effects in that game.
The boss scenario is a pretty good thing to consider. We have two types of bosses in the game, stationary and moving. Moving bosses are your standard tower defense slow-moving super-creep, basically a huge angry bucket of hit points. Here, you need to kill the boss before they reach the exit and kill you.
Our other boss type is a semi-stationary caster, combined with infinite waves of enemies. This kind of boss generally stays in one spot, but teleports around the map. In this battle, you need to take out the boss before you're overwhelmed by infinite creeps that slowly gain more strength over time.
In either of these cases, I do see how the "match potency and add duration" algorithm doesn't do the player much good.
As for our spell-casting system, it's not that in-depth. Basically, each defender has a skill tree you can put points in, so rangers can learn poison and bleed, dragons can learn "set things on fire", ice mages can learn slow / freeze, etc. But I do see what you mean in your example.
The more I hear you guys talk, the more I think you guys might be right that DPS needs to stack potency. I was really hoping for one general-use algorithm, but I guess I might just need to handle DPS differently from slow.
I have played Magicka, it's quite cool! Generally they have a unitary system for dealing with environmental effects - 1 water + 1 fire = dry, 2 water + 1 fire = wet, 1 water + 2 fire = burned, etc.
Fire and poison are currently differentiated in this way:
1) Rangers (cheap) have the poison skill
2) Dragons (expensive) have the fire skill
3) DPS is higher across the board for fire than for poison
4) Fire is cancellable by ice
5) Poison generally lasts longer than fire
Though making fire spread is certainly an interesting idea.... especially considering how often you see rows of enemies and crossing paths in tower defense.
Defenders don't have any magic points or anything in game. These status effect skills are passive skills - "always on" abilities that are added to their attacks. Cost comes in when you summon them, or boost (upgrade) them to unlock their higher level active attack skills.
You can play the demo at www.defendersquest.com/play_demo.html to see it in action - I'll be updating that later this week, but your save file should carry over.
Will check out your game when I get a chance!
A possible alternative might be to have poison add an amount of damage per tick (or whatever unit of time you are using to determine damage done) rather than trying to add the full amount at once.
For example, assuming a one second tick, the enemy is hit by 20/2sec and 10/5sec at the same time. They would simply take 30 for the first two ticks, then 10 for the next three ticks.
The first thing coming to mind was the fact that while more poison shall mean more damage, there's also the fact that there's already the poison effect active, and different levels of poison should have different levels of effect, but still be part of the same damage type.
If you're using a table to calculate how much ever effect, and every effect level is worth in points, it shouldn't be too complicated to convert every effect done to highest one, or simply sum it's points and convert to the resulting effect.
But that would change how things actually work already is your Potency/Duration system, because a smoothed effect drain curve would be easier to treat this way.
In a very rough mathematical function (ideally it shall be smoother than that):
Potency = EffectPoints * 0.2; //watching for the minimum Potency per pulse
EffectPoints *= 0.8; //or zero if totally drain already
This would bias the effect towards the short time after each new injection, boosting any current effects of the same type, be it stronger or weaker in it's level, but losing potency over time until losing all effect.
Edit: If you put a cap on active effect points (and show effects as percentage instead of points, of course), it can make consecutive one-type strategies to only sustain the roof, but making different strategies more effective. Probably making this cap be based on the enemies' HP is the best option, if the cap is a good idea after all.
Which is to say, 20 damage/second for 10 seconds and 10 damage/second for 5 seconds applied partway through should produce a series of damages:
-20 -20 -20 -30 -30 -30 -30 -30 -20 -20
It requires an interesting design for calculating damage per second, but it's about as "fair" as I can imagine. Your requirements, which seems to be preserving total damage, can in fact allow for increasing potency. In the case of poison, freezing, or burning, these seem like reasonable models of how the effects might stack. It certainly doesn't prolong duration, but if you have something which converts duration into potency you're creating a system you can game.
Do you have other constraints that led you down this path? It's an interesting problem and I'd love to know more about your experience in case I ever run into this problem myself.
In the case of TD games where the enemy is bombarded continuously, skills like Damage over Time or variable potency status ailments make for a very interesting situation indeed. In some sense I might even say it would be more valuable to give the player the choice of which stacking effect they want to power up, so that they *could* produce a freezing passage which brings enemies to as slow as 5% of their movement speed, but to decrease the additive duration with each additional attack, which is to say that creeps "adapt" to things like cold, after which point they gradually sped up to 30% of their original speed and powered through.
Another interesting possibility might be to have the slow spell bring creeps to a complete stop, but also rely on the fact that in a TD game stopping one unit is never good enough, and each unit (unless very adeptly micromanaged) will focus on keeping their target frozen in place. Unless your TD targeting system is more advanced, this will produce a situation in which at least a few creeps get through, and the player tradeoff moves into the limitations of space and units. Should they spend all their experience and money on keeping the enemy from moving forward? Should they risk lining their fortress with too few damage dealers to really capably kill off a whole wave?
I'd be very interested to hear what else you tried, or what other ideas you thought of that seemed promising to you. :D
Each defender has 5 targetting options:
Strong (highest hp+armor),
Weak (lowest hp+armor),
First (closest to goal),
Last (furthest from goal),
Fast (fastest moving)
For ice mages, you generally set them to target "Fast", and this will make them spread out their slow spell among an entire group of enemies, or selectively target fast-moving bolters trying to race through the pack.
You can check out the first two acts of our game in our playable demo, which should let you play with the last version of our status effects engine - you'll have to level up your rangers a bit before you start to get to those skills.
http://www.defendersquest.com/play_demo.html
You could have a bucket for each effect that tracks the 'temperature' or strength of effect. Each attack adds x units to the bucket, target dissipates y units per second. Target takes damage (or slows) each second proportional to the amount in the bucket. This implies that duration becomes a function of target dissipation and attack strength rather than a predefined parameter.
Heat and cold could work simply as signed attack values working on the same bucket, maybe freezing at a certain threshold. Poison would have low dissipation so it works for longer, and a lower damage coefficient to balance.
You could also consider adding diminishing returns (heat dissipated faster at higher temperature) if it helps balancing (effectively a soft ceiling on DPS). Skills-wise you could have the option of increasing attack (more front-loaded damage) or reducing dissipation (more damage over time).
Anyway, just a another way of looking at the problem. Don't know how easy it would be to balance attack values, monster dissipation rates and effect damage coefficients!
However, it is by far not the "right" or "best" solution based on your original guidelines (mathematically ok, fair, etc). Probably the best one for your game, though. Consider always questioning your guidelines. "Solving a problem simply means representing it so as to make the solution transparent".
On a side note, in our current game we really wanted a simple, straightforward and "non-stacking" method - that is, you would not have any benefit if you stacked the same effect (nor would it be less effective). We went with the following solution:
"Put all durations in a pile and run all effects parallel to each other, but only the strongest effect is applied at any time."
This means that if you get: [20% Slow, 10s] and [10% Slow, 15s] at the same time, you would be slowed by 20% for 10 seconds and then by 10% for 5 seconds.