Table of Contents
This tutorial will provide you with a few simple ways to add damage caps to attacks that can deal a variable amount of damage. By limiting the maximum amount of damage that these types of attacks can do, you can ensure that those attacks don't end up getting out of hand.
Contents
1. Prevent Overflow in ATimes10 Function
We should definitely begin by adding a sensible cap to the ATimes10 function. Many attacks that deal variable damage will make use of this function. Because ATimes10 uses an 8-bit register (a) for its calculations, we need to make sure that the final product of the multiplication doesn't exceed 255, since that would cause an overflow which would result in the final output being incorrect. For example, 10 x 30 = 300 in decimal, but in hexadecimal, its $12c. In a computer or video game, this is represented by storing $01 in one byte and $2c in a second. Since ATimes10 uses an 8-bit (1 byte) register, it only stores the lower byte of any calculation, so multiplying 10 and 30 would cause it to output $2c, or 44, instead of 300. Fortunately, this is very simple to correct. Just open src/home/math.asm, and add the following lines to the function.
; returns a *= 10
ATimes10::
+ cp 25
+ jr nc, .reached_cap
push de
ld e, a
add a
add a
add e
add a
pop de
ret
+.reached_cap
+ ld a, 250
+ ret
2. Adding Custom Damage Caps
You can see an example of an attack capping its damage in the base game by looking at ApplyExtraWaterEnergyDamageBonus, the main effect function for the Water Gun attacks. The relevant portion is at the end of the function, which can be found in src/engine/duel/effect_functions.asm.
ApplyExtraWaterEnergyDamageBonus:
...
; a holds number of water energy not payed for energy cost
cp 3
jr c, .less_than_3
ld a, 2 ; cap this to 2 for bonus effect
.less_than_3
call ATimes10
call AddToDamage ; add 10 * a to damage
.skip_bonus
ld a, [wDamage]
ld [wAIMinDamage], a
ld [wAIMaxDamage], a
ret
Now, let's see if we can replicate that damage cap code with another attack from the base game. Still in src/engine/duel/effect_functions.asm, try to locate the primary effect function for one of the Rage attacks. This is the one used for Dodrio's Rage attack:
DodrioRage_DamageBoostEffect:
ld e, PLAY_AREA_ARENA
call GetCardDamageAndMaxHP
call AddToDamage
ret
If we were to give a copy of this attack to another Pokémon with a higher HP value, like Gyarados or Charizard, we might consider limiting the amount of damage that can be added to the default damage value. Let's edit the previous function to only add up to 60 damage. While we're at it, we'll also replace the tail call with a jump to offset one of the six bytes that we're adding to the function.
DodrioRage_DamageBoostEffect:
ld e, PLAY_AREA_ARENA
call GetCardDamageAndMaxHP
+ cp 60 + 1
+ jr c, .skip_cap ; skip cap if damage on attacking Pokémon <= 60
+ ; otherwise, limit the boost to +60 damage
+ ld a, 60
+.skip_cap
- call AddToDamage
- ret
+ jp AddToDamage
Let's try that one more time with a different attack effect. Consider the effect of "Grind", which does X damage times the amount of Energy attached to the attacking Pokémon. This attack's name and description has varied greatly over the years, but it's definitely one of the most common attacks that has variable damage. If we created an effect function for a Grind attack that did 20x damage to the Defending Pokémon, it would probably look something like this:
Grind_20TimesDamageEffect:
ld e, PLAY_AREA_ARENA
call GetPlayAreaCardAttachedEnergies
add a ; *2
call ATimes10
jp SetDefiniteDamage
If we then decided that we wanted that attack to do no more than 100 damage to the Defending Pokémon, we could accomplish that by making the following changes to the above function:
Grind_20TimesDamageEffect:
ld e, PLAY_AREA_ARENA
call GetPlayAreaCardAttachedEnergies
+ cp 5
+ jr nc, .cap_damage ; set damage to 100 if there are at least 5 Energy
+ ; otherwise, multiply the amount of Energy by 20, and set damage to that.
add a ; *2
call ATimes10
jp SetDefiniteDamage
+.cap_damage
+ ld a, 100
+ jp SetDefiniteDamage