Let's say instance A is attacking instance B for N damage. I want to calculate the ideally expected damage if those instances have damage mitigation/amplification depending on target's current HP.

Instance B has an ability that it will take less damage as its HP gets lower. It could be like a (half of) sin curve, or maybe some complex function. So the formula look something like this:

$$damage = damage_{raw} \cdot ({1 \over 4} + {3 \over 4} \cdot \sin ({\pi \over 2} \cdot {hp \over hp_{max}}))$$

B will have 75% damage mitigation at 0% HP and 0% at 100% HP.

What I want to here is to chop the damage into infinite pieces and apply each to B so B can fully utilize its ability, and get the value after the process.

Also I'd like know how I'll calculate it when A has a similar ability that also changes the damage, like, increasing damage if the target has lower HP. Also there can be a case both of the instances have a variety of damage changers.

I think it's related to calculus but I have no knowledge about it.


1 Answer 1


I think your idea of chopping the damage up into many pieces might actually be the best way to go, treating current health as a floating point value so that it can track "loose change" when subtracting fractional damage.

The advantage is that we could then sub-in any damage resistance/vulnerability function we want, and the same code "Just Works" - it might looks a bit like this in C#:

public delegate float Vulnerability(float currentHealth, float maxHealth);

private float currentHealth;
private float maxHealth;

public void TakeDamage(float incomingDamage, Vulnerability vulnerability) {
    for(int i = 0; i < incomingDamage; i++) {
        currentHealth -= vulnerability(currentHealth, maxHealth);

    // Handle any fractional bit left at the end.
    float leftover = incomingDamage % 1.0f;
    currentHealth -= leftover * vulnerability(currentHealth, maxHealth);

    if(currentHealth <= 0f)

Note how this code doesn't care what math the vulnerability function is doing internally. It just provides an arbitrary mapping between each point of damage and the amount we should actually take, based on our current health level. The result is approximate, and will tend to slightly overestimate damage dealt, but you can compensate by slightly increasing your resistance values.

In contrast, let's look at what a continuous/analytical solution might look like. Here I'm going to simplify our vulnerability function to make it more tractable. Instead of the sinusoid curve, let's do something basic like...

$$V(T) = 1 - r\cdot T$$

Where \$V(T)\$ is the amount of damage we should take per point of incoming damage, at the current value of \$T\$, which is the total amount of damage we've taken so far. \$r\$ is a real number coefficient that controls how quickly the damage resistance ramps up as we take more damage. For instance, if we want to imitate your example of 75% damage reduction near 0 HP, it would look like this:

$$r = \frac 3 4 \cdot \frac 1 {\text{max health}}$$

Now we need a function \$T(i)\$ that tells us how our total damage taken increases as a function of total incoming damage received, \$i\$. We want the derivative of this function, its rate of change with respect to \$i\$, to match our vulnerability function \$V(T)\$, ie...

$$\begin{align} \frac {d T(i)} {d i} &= V(T(i))\\ \frac {d T(i)} {d i} &= 1 - r \cdot T(i) \end{align}$$

So now we have a linear equation that relates the function to its own derivative - a differential equation. Fortunately, this one is reasonably easy to solve with the integrating factor \$U(i) = e^{r\cdot i}\$, whose derivative is \$\frac {dU(i)} {di} = r\cdot e^{r\cdot i}\$.

Since all our remaining upper-case letters are functions of \$i\$ now, from here on I'm going to abbreviate \$T(i) = T\$, \$\frac {d T(i)} {d i} = T'\$, and likewise for \$U\$.

Rearranging and multiplying everything by our integrating factor gives us...

$$\begin{align} T' + r \cdot T &= 1\\ e^{r\cdot i}T' + r e^{r\cdot i} \cdot T &= e^{r\cdot i} U \cdot T' + U' \cdot T = e^{r\cdot i}\\ \int{U \cdot T' + U' \cdot T} \, di &= \int{e^{r\cdot i}} \, di\\ U \cdot T &= \frac 1 r e^{r\cdot i} + c\\ e^{r\cdot i} \cdot T & = \frac 1 r e^{r\cdot i} + c\\ T &= \frac 1 r + c\cdot e^{-r\cdot i} \end{align}$$

Now we just need to work out the constant of integration, \$c\$. We know that at \$i = 0\$, \$T(i) = 0\$ (if we've received no incoming damage, then we've taken no damage). That lets us solve...

$$\begin{align} T(i) &= \frac 1 r + c\cdot e^{-r\cdot i}\\ T(0) &= \frac 1 r + c\cdot e^{-r\cdot 0}\\ 0 &= \frac 1 r + c\\ c &= \frac {-1} r \end{align}$$

So we've finally arrived at our continuous function for total damage taken, given total incoming damage received:

$$T(i) = \frac {1 - e^{-r\cdot i}} r$$

For such a simple starting point, that was a fair bit of work to get to, and the result doesn't have a super obvious relationship to the vulnerability function we started with.

If we want to change the shape of our vulnerability function, we have to do a whole new mathematical derivation, and we're not guaranteed that a closed-form solution exists. For instance, if we want to try...

$$V(T) = \frac 1 4 + \frac 3 4 \cos \left( \frac \pi 2 \frac T {\text{max health}} \right) \;\text{ ...or... }\; V(T) = 1 - \frac 3 4 \left( \frac T {\text{max health}} \right)^2$$

...to get the slow start & accelerating resistance you had in your original example, we end up with non-linear differential equations, which can be much more difficult to solve.

These might be a solvable case - I'm not sure. It's been quite a while since I studied differential equations so I'm rusty on all but the simplest of them.

Overall though, I'd say doing a discrete approximation - chopping the damage into "small enough" slices and applying a constant resistance to each slice - is likely to give you better results for a game:

  • simpler to get up & running
  • clear and easy to debug
  • scalable to arbitrary vulnerability functions

You must log in to answer this question.

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