[battle, math] refactor damage calculation to use proper fp type and inlined multiplication

This commit is contained in:
sbird 2023-07-07 14:11:49 +02:00
parent 404b18564f
commit 6482279fa3
10 changed files with 279 additions and 229 deletions

View file

@ -656,7 +656,7 @@ struct BattleStruct
u8 battleBondTransformed[NUM_BATTLE_SIDES]; // Bitfield for each party. u8 battleBondTransformed[NUM_BATTLE_SIDES]; // Bitfield for each party.
u8 storedHealingWish:4; // Each battler as a bit. u8 storedHealingWish:4; // Each battler as a bit.
u8 storedLunarDance:4; // Each battler as a bit. u8 storedLunarDance:4; // Each battler as a bit.
u16 supremeOverlordModifier[MAX_BATTLERS_COUNT]; uq4_12_t supremeOverlordModifier[MAX_BATTLERS_COUNT];
u8 itemPartyIndex[MAX_BATTLERS_COUNT]; u8 itemPartyIndex[MAX_BATTLERS_COUNT];
u8 itemMoveIndex[MAX_BATTLERS_COUNT]; u8 itemMoveIndex[MAX_BATTLERS_COUNT];
bool8 trainerSlideHalfHpMsgDone; bool8 trainerSlideHalfHpMsgDone;

View file

@ -87,7 +87,7 @@ bool32 MovesWithSplitUnusable(u32 attacker, u32 target, u32 split);
s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 *effectiveness, bool32 considerZPower); s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 *effectiveness, bool32 considerZPower);
u8 GetMoveDamageResult(u16 move); u8 GetMoveDamageResult(u16 move);
u32 GetCurrDamageHpPercent(u8 battlerAtk, u8 battlerDef); u32 GetCurrDamageHpPercent(u8 battlerAtk, u8 battlerDef);
u16 AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef); uq4_12_t AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef);
u32 AI_GetMoveEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef); u32 AI_GetMoveEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef);
u16 *GetMovesArray(u32 battler); u16 *GetMovesArray(u32 battler);
bool32 IsConfusionMoveEffect(u16 moveEffect); bool32 IsConfusionMoveEffect(u16 moveEffect);

View file

@ -166,10 +166,10 @@ bool32 IsBattlerAlive(u8 battlerId);
u8 GetBattleMonMoveSlot(struct BattlePokemon *battleMon, u16 move); u8 GetBattleMonMoveSlot(struct BattlePokemon *battleMon, u16 move);
u32 GetBattlerWeight(u8 battlerId); u32 GetBattlerWeight(u8 battlerId);
s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags); s32 CalculateMoveDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags);
s32 CalculateMoveDamageAndEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, u16 *typeEffectivenessModifier); s32 CalculateMoveDamageAndEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, uq4_12_t *typeEffectivenessModifier);
u16 CalcTypeEffectivenessMultiplier(u16 move, u8 moveType, u8 battlerAtk, u8 battlerDef, bool32 recordAbilities); uq4_12_t CalcTypeEffectivenessMultiplier(u16 move, u8 moveType, u8 battlerAtk, u8 battlerDef, bool32 recordAbilities);
u16 CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, u16 abilityDef); uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, u16 abilityDef);
u16 GetTypeModifier(u8 atkType, u8 defType); uq4_12_t GetTypeModifier(u8 atkType, u8 defType);
s32 GetStealthHazardDamage(u8 hazardType, u8 battlerId); s32 GetStealthHazardDamage(u8 hazardType, u8 battlerId);
s32 GetStealthHazardDamageByTypesAndHP(u8 hazardType, u8 type1, u8 type2, u32 maxHp); s32 GetStealthHazardDamageByTypesAndHP(u8 hazardType, u8 type1, u8 type2, u32 maxHp);
bool32 CanMegaEvolve(u8 battlerId); bool32 CanMegaEvolve(u8 battlerId);
@ -240,4 +240,9 @@ u8 GetBattlerGender(u8 battlerId);
bool8 AreBattlersOfOppositeGender(u8 battler1, u8 battler2); bool8 AreBattlersOfOppositeGender(u8 battler1, u8 battler2);
u32 CalcSecondaryEffectChance(u8 battlerId, u8 secondaryEffectChance); u32 CalcSecondaryEffectChance(u8 battlerId, u8 secondaryEffectChance);
static inline u32 ApplyModifier(uq4_12_t modifier, u32 val)
{
return UQ_4_12_TO_INT((modifier * val) + UQ_4_12_ROUND);
}
#endif // GUARD_BATTLE_UTIL_H #endif // GUARD_BATTLE_UTIL_H

61
include/fpmath.h Normal file
View file

@ -0,0 +1,61 @@
#ifndef FPMATH_H_
#define FPMATH_H_
typedef s32 q4_12_t;
typedef u32 uq4_12_t;
#define Q_4_12_SHIFT (12)
#define UQ_4_12_SHIFT (12)
// Converts a number to Q8.8 fixed-point format
#define Q_8_8(n) ((s16)((n) * 256))
// Converts a number to Q4.12 fixed-point format
#define Q_4_12(n) ((q4_12_t)((n) * 4096))
#define UQ_4_12(n) ((uq4_12_t)((n) * 4096))
// Converts a number to Q24.8 fixed-point format
#define Q_24_8(n) ((s32)((n) * 256))
// Converts a Q8.8 fixed-point format number to a regular integer
#define Q_8_8_TO_INT(n) ((s32)((n) / 256))
// Converts a Q4.12 fixed-point format number to a regular integer
#define Q_4_12_TO_INT(n) ((s32)((n) / 4096))
#define UQ_4_12_TO_INT(n) ((u32)((n) / 4096))
// Converts a Q24.8 fixed-point format number to a regular integer
#define Q_24_8_TO_INT(n) ((s32)((n) / 256))
// Rounding value for Q4.12 fixed-point format
#define Q_4_12_ROUND ((1) << (Q_4_12_SHIFT - 1))
#define UQ_4_12_ROUND ((1) << (UQ_4_12_SHIFT - 1))
// Basic arithmetic for fixed point number formats
// Consumers should use encapsulated functions where possible
// FP API does not provide sanity checks against overflows
static inline uq4_12_t uq4_12_add(uq4_12_t a, uq4_12_t b)
{
return a + b;
}
static inline uq4_12_t uq4_12_subtract(uq4_12_t a, uq4_12_t b)
{
return a - b;
}
static inline uq4_12_t uq4_12_multiply(uq4_12_t a, uq4_12_t b)
{
u32 product = (u32) a * b;
return (product + UQ_4_12_ROUND) >> UQ_4_12_SHIFT;
}
static inline uq4_12_t uq4_12_divide(uq4_12_t dividend, uq4_12_t divisor)
{
if (divisor == UQ_4_12(0.0)) return UQ_4_12(0);
return (dividend << UQ_4_12_SHIFT) / divisor;
}
#endif // FPMATH_H_

View file

@ -5,6 +5,7 @@
#include <limits.h> #include <limits.h>
#include "config.h" // we need to define config before gba headers as print stuff needs the functions nulled before defines. #include "config.h" // we need to define config before gba headers as print stuff needs the functions nulled before defines.
#include "gba/gba.h" #include "gba/gba.h"
#include "fpmath.h"
#include "constants/global.h" #include "constants/global.h"
#include "constants/flags.h" #include "constants/flags.h"
#include "constants/vars.h" #include "constants/vars.h"
@ -51,32 +52,6 @@
b = temp; \ b = temp; \
} }
// useful math macros
// Converts a number to Q8.8 fixed-point format
#define Q_8_8(n) ((s16)((n) * 256))
// Converts a number to Q4.12 fixed-point format
#define Q_4_12(n) ((s16)((n) * 4096))
#define UQ_4_12(n) ((u16)((n) * 4096))
// Converts a number to Q24.8 fixed-point format
#define Q_24_8(n) ((s32)((n) << 8))
// Converts a Q8.8 fixed-point format number to a regular integer
#define Q_8_8_TO_INT(n) ((int)((n) / 256))
// Converts a Q4.12 fixed-point format number to a regular integer
#define Q_4_12_TO_INT(n) ((int)((n) / 4096))
#define UQ_4_12_TO_INT(n) ((int)((n) / 4096))
// Converts a Q24.8 fixed-point format number to a regular integer
#define Q_24_8_TO_INT(n) ((int)((n) >> 8))
// Rounding value for Q4.12 fixed-point format
#define Q_4_12_ROUND ((1) << (12 - 1))
#define UQ_4_12_ROUND ((1) << (12 - 1))
#define min(a, b) ((a) < (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) >= (b) ? (a) : (b)) #define max(a, b) ((a) >= (b) ? (a) : (b))

View file

@ -822,7 +822,7 @@ static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId,
while (bits != 0x3F) // All mons were checked. while (bits != 0x3F) // All mons were checked.
{ {
u16 bestResist = UQ_4_12(1.0); uq4_12_t bestResist = UQ_4_12(1.0);
int bestMonId = PARTY_SIZE; int bestMonId = PARTY_SIZE;
// Find the mon whose type is the most suitable defensively. // Find the mon whose type is the most suitable defensively.
for (i = firstId; i < lastId; i++) for (i = firstId; i < lastId; i++)
@ -830,21 +830,21 @@ static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId,
if (!(gBitTable[i] & invalidMons) && !(gBitTable[i] & bits)) if (!(gBitTable[i] & invalidMons) && !(gBitTable[i] & bits))
{ {
u16 species = GetMonData(&party[i], MON_DATA_SPECIES); u16 species = GetMonData(&party[i], MON_DATA_SPECIES);
u16 typeEffectiveness = UQ_4_12(1.0); uq4_12_t typeEffectiveness = UQ_4_12(1.0);
u8 atkType1 = gBattleMons[opposingBattler].type1; u8 atkType1 = gBattleMons[opposingBattler].type1;
u8 atkType2 = gBattleMons[opposingBattler].type2; u8 atkType2 = gBattleMons[opposingBattler].type2;
u8 defType1 = gSpeciesInfo[species].types[0]; u8 defType1 = gSpeciesInfo[species].types[0];
u8 defType2 = gSpeciesInfo[species].types[1]; u8 defType2 = gSpeciesInfo[species].types[1];
MulModifier(&typeEffectiveness, (GetTypeModifier(atkType1, defType1))); typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1)));
if (atkType2 != atkType1) if (atkType2 != atkType1)
MulModifier(&typeEffectiveness, (GetTypeModifier(atkType2, defType1))); typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1)));
if (defType2 != defType1) if (defType2 != defType1)
{ {
MulModifier(&typeEffectiveness, (GetTypeModifier(atkType1, defType2))); typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType2)));
if (atkType2 != atkType1) if (atkType2 != atkType1)
MulModifier(&typeEffectiveness, (GetTypeModifier(atkType2, defType2))); typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType2)));
} }
if (typeEffectiveness < bestResist) if (typeEffectiveness < bestResist)
{ {

View file

@ -31,7 +31,7 @@
} \ } \
return FALSE return FALSE
static u32 AI_GetEffectiveness(u16 multiplier); static u32 AI_GetEffectiveness(uq4_12_t multiplier);
// Const Data // Const Data
static const s8 sAiAbilityRatings[ABILITIES_COUNT] = static const s8 sAiAbilityRatings[ABILITIES_COUNT] =
@ -768,7 +768,7 @@ s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 *typeEffectiveness,
{ {
s32 dmg, moveType, critDmg, normalDmg; s32 dmg, moveType, critDmg, normalDmg;
s8 critChance; s8 critChance;
u16 effectivenessMultiplier; uq4_12_t effectivenessMultiplier;
if (considerZPower && IsViableZMove(battlerAtk, move)) if (considerZPower && IsViableZMove(battlerAtk, move))
{ {
@ -1003,9 +1003,10 @@ u32 GetCurrDamageHpPercent(u8 battlerAtk, u8 battlerDef)
return (bestDmg * 100) / gBattleMons[battlerDef].maxHP; return (bestDmg * 100) / gBattleMons[battlerDef].maxHP;
} }
u16 AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef) uq4_12_t AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef)
{ {
u16 typeEffectiveness, moveType; uq4_12_t typeEffectiveness;
u16 moveType;
SaveBattlerData(battlerAtk); SaveBattlerData(battlerAtk);
SaveBattlerData(battlerDef); SaveBattlerData(battlerDef);
@ -1030,7 +1031,7 @@ u32 AI_GetMoveEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef)
return AI_GetEffectiveness(AI_GetTypeEffectiveness(move, battlerAtk, battlerDef)); return AI_GetEffectiveness(AI_GetTypeEffectiveness(move, battlerAtk, battlerDef));
} }
static u32 AI_GetEffectiveness(u16 multiplier) static u32 AI_GetEffectiveness(uq4_12_t multiplier)
{ {
switch (multiplier) switch (multiplier)
{ {

View file

@ -5206,7 +5206,7 @@ static u16 GetWinningMove(int winnerTournamentId, int loserTournamentId, u8 roun
u32 personality = 0; u32 personality = 0;
u32 targetSpecies = 0; u32 targetSpecies = 0;
u32 targetAbility = 0; u32 targetAbility = 0;
u32 typeMultiplier = 0; uq4_12_t typeMultiplier = 0;
do do
{ {
personality = Random32(); personality = Random32();

File diff suppressed because it is too large Load diff

18
test/fpmath.c Normal file
View file

@ -0,0 +1,18 @@
#include "global.h"
#include "test.h"
TEST("uq4_12_add adds 4.12 numbers") {
EXPECT_EQ(uq4_12_add(UQ_4_12(3.5), UQ_4_12(2.5)), UQ_4_12(6.0));
}
TEST("uq4_12_subtract subtracts 4.12 numbers") {
EXPECT_EQ(uq4_12_subtract(UQ_4_12(3.5), UQ_4_12(2.0)), UQ_4_12(1.5));
}
TEST("uq4_12_multiply multiplies 4.12 numbers") {
EXPECT_EQ(uq4_12_multiply(UQ_4_12(3.5), UQ_4_12(2.0)), UQ_4_12(7.0));
}
TEST("uq4_12_divide divides 4.12 numbers") {
EXPECT_EQ(uq4_12_divide(UQ_4_12(5.0), UQ_4_12(2.0)), UQ_4_12(2.5));
}