Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added test support for 5 battle configs #5914

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions include/battle_script_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ struct PickupItem
u8 percentage[10];
};

s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk);
s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility);
s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk);
s32 GetCritHitOdds(s32 critChanceIndex);
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect);
u8 GetBattlerTurnOrderNum(u8 battlerId);
Expand Down
1 change: 1 addition & 0 deletions include/config/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
#define B_CHARGE_SPDEF_RAISE GEN_LATEST // In Gen5+, Charge raises the user's Special Defense by 1 stage.
#define B_MINIMIZE_EVASION GEN_LATEST // In Gen5+, Minimize raises evasion by 2 stages instead of 1.
#define B_GROWTH_STAT_RAISE GEN_LATEST // In Gen5+, Growth raises Attack in addition to Special Attack by 1 stage each. Under the effects of the sun, it raises them by 2 stages each instead.
#define B_FOCUS_ENERGY_CRIT_RATIO GEN_LATEST // In Gen3+, Focus Energy increases critical hit ratio by 2 instead of 1.

// Other move settings
#define B_INCINERATE_GEMS GEN_LATEST // In Gen6+, Incinerate can destroy Gems.
Expand Down
6 changes: 6 additions & 0 deletions include/constants/generational_changes.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@

enum GenConfigTag
{
GEN_CONFIG_CRIT_CHANCE,
GEN_CONFIG_CRIT_MULTIPLIER,
GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO,
GEN_CONFIG_PARALYSIS_SPEED,
GEN_CONFIG_CONFUSION_SELF_DMG_CHANCE,
GEN_CONFIG_MULTI_HIT_CHANCE,
GEN_CONFIG_GALE_WINGS,
GEN_CONFIG_COUNT
};
Expand Down
8 changes: 7 additions & 1 deletion include/generational_changes.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@

static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] =
{
[GEN_CONFIG_GALE_WINGS] = B_GALE_WINGS,
[GEN_CONFIG_CRIT_CHANCE] = B_CRIT_CHANCE,
[GEN_CONFIG_CRIT_MULTIPLIER] = B_CRIT_MULTIPLIER,
[GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO] = B_FOCUS_ENERGY_CRIT_RATIO,
[GEN_CONFIG_PARALYSIS_SPEED] = B_PARALYSIS_SPEED,
[GEN_CONFIG_CONFUSION_SELF_DMG_CHANCE] = B_CONFUSION_SELF_DMG_CHANCE,
[GEN_CONFIG_MULTI_HIT_CHANCE] = B_MULTI_HIT_CHANCE,
[GEN_CONFIG_GALE_WINGS] = B_GALE_WINGS,
};

#if TESTING
Expand Down
2 changes: 1 addition & 1 deletion src/battle_ai_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
damageCalcData.randomFactor = FALSE;
damageCalcData.updateFlags = FALSE;

critChanceIndex = CalcCritChanceStageArgs(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]);
critChanceIndex = CalcCritChanceStage(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]);
if (critChanceIndex > 1) // Consider crit damage only if a move has at least +2 crit chance
{
damageCalcData.isCrit = FALSE;
Expand Down
2 changes: 1 addition & 1 deletion src/battle_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4823,7 +4823,7 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect)

// paralysis drop
if (gBattleMons[battler].status1 & STATUS1_PARALYSIS && ability != ABILITY_QUICK_FEET)
speed /= B_PARALYSIS_SPEED >= GEN_7 ? 2 : 4;
speed /= GetGenConfig(GEN_CONFIG_PARALYSIS_SPEED) >= GEN_7 ? 2 : 4;

if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SWAMP)
speed /= 4;
Expand Down
98 changes: 40 additions & 58 deletions src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#include "battle_ai_util.h"
#include "battle_scripts.h"
#include "battle_z_move.h"
#include "constants/moves.h"
#include "constants/abilities.h"
#include "item.h"
#include "util.h"
#include "pokemon.h"
Expand Down Expand Up @@ -47,6 +45,7 @@
#include "pokenav.h"
#include "menu_specialized.h"
#include "data.h"
#include "generational_changes.h"
#include "constants/abilities.h"
#include "constants/battle_anim.h"
#include "constants/battle_move_effects.h"
Expand Down Expand Up @@ -1819,16 +1818,19 @@ static void Cmd_ppreduce(void)
}

// The chance is 1/N for each stage.
static const u32 sGen7CriticalHitOdds[] = {24, 8, 2, 1, 1};
static const u32 sGen6CriticalHitOdds[] = {16, 8, 2, 1, 1};
static const u32 sCriticalHitOdds[] = {16, 8, 4, 3, 2}; // Gens 2,3,4,5
static const u32 sGen7CriticalHitOdds[] = {24, 8, 2, 1, 1}; // 1/X
static const u32 sGen6CriticalHitOdds[] = {16, 8, 2, 1, 1}; // 1/X
static const u32 sCriticalHitOdds[] = {16, 8, 4, 3, 2}; // 1/X, Gens 3,4,5
static const u32 sGen2CriticalHitOdds[] = {17, 32, 64, 85, 128}; // X/256

static inline u32 GetCriticalHitOdds(u32 critChance)
{
if (B_CRIT_CHANCE >= GEN_7)
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) >= GEN_7)
return sGen7CriticalHitOdds[critChance];
if (B_CRIT_CHANCE == GEN_6)
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_6)
return sGen6CriticalHitOdds[critChance];
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_2)
return sGen2CriticalHitOdds[critChance];

return sCriticalHitOdds[critChance];
}
Expand Down Expand Up @@ -1870,7 +1872,7 @@ static inline u32 GetHoldEffectCritChanceIncrease(u32 battler, u32 holdEffect)

#define CRITICAL_HIT_BLOCKED -1
#define CRITICAL_HIT_ALWAYS -2
s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk)
s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk)
{
s32 critChance = 0;

Expand Down Expand Up @@ -1913,83 +1915,59 @@ s32 CalcCritChanceStageArgs(u32 battlerAtk, u32 battlerDef, u32 move, bool32 rec

return critChance;
}
#undef CRITICAL_HIT_BLOCKED
#undef CRITICAL_HIT_ALWAYS

s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility)
{
u32 abilityAtk = GetBattlerAbility(gBattlerAttacker);
u32 abilityDef = GetBattlerAbility(gBattlerTarget);
u32 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE);
return CalcCritChanceStageArgs(battlerAtk, battlerDef, move, recordAbility, abilityAtk, abilityDef, holdEffectAtk);
}

// Bulbapedia: https://bulbapedia.bulbagarden.net/wiki/Critical_hit#Generation_I
// Crit chance = Threshold / 256, Threshold maximum of 255
// Threshold = Base Speed / 2
// High crit move = 8 * (Base Speed / 2)
// Focus Energy = 4 * (Base Speed / 2)
s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility)
s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, u32 holdEffectAtk)
{
// Vanilla
u32 focusEnergyScaler = 4;
u32 highCritRatioScaler = 8;

// Not vanilla
u32 superLuckScaler = 4;
u32 scopeLensScaler = 4;
u32 luckyPunchScaler = 8;
u32 farfetchdLeekScaler = 8;

s32 critChance = 0;
s32 moveCritStage = gMovesInfo[gCurrentMove].criticalHitStage;
s32 bonusCritStage = gBattleStruct->bonusCritStages[battlerAtk]; // G-Max Chi Strike
u32 abilityAtk = GetBattlerAbility(battlerAtk);
u32 abilityDef = GetBattlerAbility(battlerDef);
u32 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE);
u32 holdEffectCritStage = GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk);
u16 baseSpeed = gSpeciesInfo[gBattleMons[battlerAtk].species].baseSpeed;

critChance = baseSpeed / 2;

// Crit scaling
if (moveCritStage > 0)
critChance = critChance * highCritRatioScaler * moveCritStage;
critChance *= 8 * moveCritStage;

if (bonusCritStage > 0)
critChance = critChance * bonusCritStage;
critChance *= bonusCritStage;

if ((gBattleMons[battlerAtk].status2 & STATUS2_FOCUS_ENERGY_ANY) != 0)
critChance = critChance * focusEnergyScaler;
if (gBattleMons[battlerAtk].status2 & STATUS2_FOCUS_ENERGY)
critChance *= 4;
else if (gBattleMons[battlerAtk].status2 & STATUS2_DRAGON_CHEER)
critChance *= 2;

if (holdEffectAtk == HOLD_EFFECT_SCOPE_LENS)
critChance = critChance * scopeLensScaler;
else if (holdEffectAtk == HOLD_EFFECT_LUCKY_PUNCH && gBattleMons[battlerAtk].species == SPECIES_CHANSEY)
critChance = critChance * luckyPunchScaler;
else if (IsBattlerLeekAffected(battlerAtk, holdEffectAtk))
critChance = critChance * farfetchdLeekScaler;
if (holdEffectCritStage > 0)
critChance *= 4 * holdEffectCritStage;

if (abilityAtk == ABILITY_SUPER_LUCK)
critChance = critChance * superLuckScaler;
critChance *= 4;

if (critChance > 255)
critChance = 255;

// Prevented crits
if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT)
critChance = -1;
critChance = CRITICAL_HIT_BLOCKED;
else if (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR)
{
if (recordAbility)
RecordAbilityBattle(battlerDef, abilityDef);
critChance = -1;
critChance = CRITICAL_HIT_BLOCKED;
}

// Guaranteed crits
else if (gStatuses3[battlerAtk] & STATUS3_LASER_FOCUS
|| gMovesInfo[move].alwaysCriticalHit == TRUE
|| (abilityAtk == ABILITY_MERCILESS && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY))
{
critChance = -2;
critChance = CRITICAL_HIT_ALWAYS;
}

return critChance;
Expand All @@ -2002,18 +1980,23 @@ s32 GetCritHitOdds(s32 critChanceIndex)
else
return GetCriticalHitOdds(critChanceIndex);
}
#undef CRITICAL_HIT_BLOCKED
#undef CRITICAL_HIT_ALWAYS

static void Cmd_critcalc(void)
{
CMD_ARGS();

u16 partySlot;
s32 critChance;
u32 abilityAtk = GetBattlerAbility(gBattlerAttacker);
u32 abilityDef = GetBattlerAbility(gBattlerTarget);
u32 holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker, TRUE);

if (B_CRIT_CHANCE == GEN_1)
critChance = CalcCritChanceStageGen1(gBattlerAttacker, gBattlerTarget, gCurrentMove, TRUE);
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1)
critChance = CalcCritChanceStageGen1(gBattlerAttacker, gBattlerTarget, gCurrentMove, TRUE, abilityAtk, abilityDef, holdEffectAtk);
else
critChance = CalcCritChanceStage(gBattlerAttacker, gBattlerTarget, gCurrentMove, TRUE);
critChance = CalcCritChanceStage(gBattlerAttacker, gBattlerTarget, gCurrentMove, TRUE, abilityAtk, abilityDef, holdEffectAtk);

gPotentialItemEffectBattler = gBattlerAttacker;

Expand All @@ -2025,14 +2008,10 @@ static void Cmd_critcalc(void)
gIsCriticalHit = TRUE;
else
{
if (B_CRIT_CHANCE == GEN_1)
{
u8 critRoll = RandomUniform(RNG_CRITICAL_HIT, 1, 256);
if (critRoll <= critChance)
gIsCriticalHit = 1;
else
gIsCriticalHit = 0;
}
if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1)
gIsCriticalHit = RandomChance(RNG_CRITICAL_HIT, critChance, 256);
else if (GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_2)
gIsCriticalHit = RandomChance(RNG_CRITICAL_HIT, GetCriticalHitOdds(critChance), 256);
else
gIsCriticalHit = RandomChance(RNG_CRITICAL_HIT, 1, GetCriticalHitOdds(critChance));
}
Expand Down Expand Up @@ -12603,7 +12582,10 @@ static void Cmd_setfocusenergy(void)
}
else
{
gBattleMons[battler].status2 |= STATUS2_FOCUS_ENERGY;
if (GetGenConfig(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO) >= GEN_3)
gBattleMons[battler].status2 |= STATUS2_FOCUS_ENERGY;
else
gBattleMons[battler].status2 |= STATUS2_DRAGON_CHEER;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_GETTING_PUMPED;
}
gBattlescriptCurrInstr = cmd->nextInstr;
Expand Down
7 changes: 4 additions & 3 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "battle_setup.h"
#include "battle_z_move.h"
#include "battle_gimmick.h"
#include "generational_changes.h"
#include "party_menu.h"
#include "pokemon.h"
#include "international_string_util.h"
Expand Down Expand Up @@ -3473,7 +3474,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType)
if (gBattleMons[gBattlerAttacker].status2 & STATUS2_CONFUSION)
{
// confusion dmg
if (RandomPercentage(RNG_CONFUSION, (B_CONFUSION_SELF_DMG_CHANCE >= GEN_7 ? 33 : 50)))
if (RandomPercentage(RNG_CONFUSION, (GetGenConfig(GEN_CONFIG_CONFUSION_SELF_DMG_CHANCE) >= GEN_7 ? 33 : 50)))
{
gBattleCommunication[MULTISTRING_CHOOSER] = TRUE;
gBattlerTarget = gBattlerAttacker;
Expand Down Expand Up @@ -10091,7 +10092,7 @@ static inline uq4_12_t GetBurnOrFrostBiteModifier(struct DamageCalculationData *
static inline uq4_12_t GetCriticalModifier(bool32 isCrit)
{
if (isCrit)
return B_CRIT_MULTIPLIER >= GEN_6 ? UQ_4_12(1.5) : UQ_4_12(2.0);
return GetGenConfig(GEN_CONFIG_CRIT_MULTIPLIER) >= GEN_6 ? UQ_4_12(1.5) : UQ_4_12(2.0);
return UQ_4_12(1.0);
}

Expand Down Expand Up @@ -11677,7 +11678,7 @@ static void SetRandomMultiHitCounter()
{
if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LOADED_DICE)
gMultiHitCounter = RandomUniform(RNG_LOADED_DICE, 4, 5);
else if (B_MULTI_HIT_CHANCE >= GEN_5)
else if (GetGenConfig(GEN_CONFIG_MULTI_HIT_CHANCE) >= GEN_5)
gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 7, 7, 3, 3); // 35%: 2 hits, 35%: 3 hits, 15% 4 hits, 15% 5 hits.
else
gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 3, 3, 1, 1); // 37.5%: 2 hits, 37.5%: 3 hits, 12.5% 4 hits, 12.5% 5 hits.
Expand Down
49 changes: 49 additions & 0 deletions test/battle/ability/battle_armor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "global.h"
#include "test/battle.h"

SINGLE_BATTLE_TEST("Battle Armor and Shell Armor block critical hits")
{
u32 species;
u32 ability;

PARAMETRIZE { species = SPECIES_KINGLER; ability = ABILITY_SHELL_ARMOR; }
PARAMETRIZE { species = SPECIES_ARMALDO; ability = ABILITY_BATTLE_ARMOR; }

GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(species) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE, criticalHit: TRUE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
NOT MESSAGE("A critical hit!");
}
}

SINGLE_BATTLE_TEST("Mold Breaker, Teravolt and Turboblaze ignore Battle Armor and Shell Armor")
{
u32 j;
u32 species1, species2, ability1, ability2;
static const u32 breakerData[][2] =
{
{SPECIES_PINSIR, ABILITY_MOLD_BREAKER},
{SPECIES_ZEKROM, ABILITY_TERAVOLT},
{SPECIES_RESHIRAM, ABILITY_TURBOBLAZE},
};

for (j = 0; j < ARRAY_COUNT(breakerData); j++)
{
PARAMETRIZE { species1 = breakerData[j][0]; ability1 = breakerData[j][1]; species2 = SPECIES_KINGLER; ability2 = ABILITY_SHELL_ARMOR; }
PARAMETRIZE { species1 = breakerData[j][0]; ability1 = breakerData[j][1]; species2 = SPECIES_ARMALDO; ability2 = ABILITY_BATTLE_ARMOR; }
}

GIVEN {
PLAYER(species1) { Ability(ability1); }
OPPONENT(species2) { Ability(ability2); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE, criticalHit: TRUE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
MESSAGE("A critical hit!");
}
}
2 changes: 1 addition & 1 deletion test/battle/ability/inner_focus.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ SINGLE_BATTLE_TEST("Inner Focus prevents flinching")
}
}

SINGLE_BATTLE_TEST("Inner Focus is ignored by Mold Breaker")
SINGLE_BATTLE_TEST("Mold Breaker ignores Inner Focus")
{
GIVEN {
PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); };
Expand Down
16 changes: 16 additions & 0 deletions test/battle/ability/merciless.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "global.h"
#include "test/battle.h"

SINGLE_BATTLE_TEST("Merciless causes a move to result in a critical hit if the target is poisoned")
{
PASSES_RANDOMLY(1, 1, RNG_CRITICAL_HIT);
GIVEN {
PLAYER(SPECIES_MAREANIE) { Ability(ABILITY_MERCILESS); }
OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
MESSAGE("A critical hit!");
}
}
4 changes: 2 additions & 2 deletions test/battle/ability/own_tempo.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ SINGLE_BATTLE_TEST("Own Tempo prevents confusion from moves by the user")
}
}

SINGLE_BATTLE_TEST("Own Tempo is ignored by Mold Breaker")
SINGLE_BATTLE_TEST("Mold Breaker ignores Own Tempo")
{
KNOWN_FAILING; // Ideally the func CanBeConfused should be split into AttackerCanBeConfused and TargetCanBeConfused or we do it in the same func but have a check for when battlerAtk == battlerDef
GIVEN {
Expand All @@ -73,7 +73,7 @@ SINGLE_BATTLE_TEST("Own Tempo is ignored by Mold Breaker")
}
}

SINGLE_BATTLE_TEST("Own Tempo cures confusion obtained from an opponent with Mold Breaker")
SINGLE_BATTLE_TEST("Mold Breaker does not prevent Own Tempo from curing confusion right after")
{
KNOWN_FAILING;
GIVEN {
Expand Down
Loading