Fix incorrect "last to move" check for Analytic, Snatch, and Magic Coat (#8536)

This commit is contained in:
moostoet 2025-12-15 15:56:17 +01:00 committed by GitHub
parent bc0bbc5490
commit 755bec128b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 209 additions and 6 deletions

View File

@ -238,6 +238,7 @@ void MarkBattlerForControllerExec(u32 battler);
void MarkBattlerReceivedLinkData(u32 battler);
const u8 *CancelMultiTurnMoves(u32 battler, enum SkyDropState skyDropState);
bool32 WasUnableToUseMove(u32 battler);
bool32 IsLastMonToMove(u32 battler);
bool32 ShouldDefiantCompetitiveActivate(u32 battler, enum Ability ability);
void PrepareStringBattle(enum StringID stringId, u32 battler);
void ResetSentPokesToOpponentValue(void);

View File

@ -9711,7 +9711,7 @@ static void Cmd_setprotectlike(void)
TryResetProtectUseCounter(gBattlerAttacker);
if (gCurrentTurnActionNumber == (gBattlersCount - 1))
if (IsLastMonToMove(gBattlerAttacker))
notLastTurn = FALSE;
if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= RandomUniform(RNG_PROTECT_FAIL, 0, USHRT_MAX) && notLastTurn)
@ -13136,7 +13136,7 @@ static void Cmd_trysetmagiccoat(void)
{
CMD_ARGS(const u8 *failInstr);
if (gCurrentTurnActionNumber == gBattlersCount - 1) // moves last turn
if (IsLastMonToMove(gBattlerAttacker)) // fails if moving last
{
gBattlescriptCurrInstr = cmd->failInstr;
}
@ -13152,7 +13152,7 @@ static void Cmd_trysetsnatch(void)
{
CMD_ARGS(const u8 *failInstr);
if (gCurrentTurnActionNumber == gBattlersCount - 1) // moves last turn
if (IsLastMonToMove(gBattlerAttacker)) // fails if moving last
{
gBattlescriptCurrInstr = cmd->failInstr;
}

View File

@ -1216,6 +1216,26 @@ bool32 WasUnableToUseMove(u32 battler)
return FALSE;
}
// Returns TRUE if no other battler after this one in turn order will use a move
bool32 IsLastMonToMove(u32 battler)
{
u32 i;
u32 battlerTurnOrderNum = GetBattlerTurnOrderNum(battler);
if (battlerTurnOrderNum >= gBattlersCount - 1)
return TRUE;
for (i = battlerTurnOrderNum + 1; i < gBattlersCount; i++)
{
u32 otherBattler = gBattlerByTurnOrder[i];
if (!IsBattlerAlive(otherBattler))
continue;
if (gActionsByTurnOrder[i] == B_ACTION_USE_MOVE)
return FALSE;
}
return TRUE;
}
bool32 ShouldDefiantCompetitiveActivate(u32 battler, enum Ability ability)
{
u32 side = GetBattlerSide(battler);
@ -7358,7 +7378,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx)
modifier = uq4_12_multiply(modifier, UQ_4_12(0.75));
break;
case ABILITY_ANALYTIC:
if (GetBattlerTurnOrderNum(battlerAtk) == gBattlersCount - 1 && move != MOVE_FUTURE_SIGHT && move != MOVE_DOOM_DESIRE)
if (IsLastMonToMove(battlerAtk) && move != MOVE_FUTURE_SIGHT && move != MOVE_DOOM_DESIRE)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
break;
case ABILITY_TOUGH_CLAWS:

View File

@ -20,9 +20,59 @@ SINGLE_BATTLE_TEST("Analytic increases the power of moves by 30% if it's the las
}
}
DOUBLE_BATTLE_TEST("Analytic activates correctly with empty slot after fainting", s16 damage)
{
u32 speed;
PARAMETRIZE { speed = 50; }
PARAMETRIZE { speed = 5; }
GIVEN {
PLAYER(SPECIES_MAGNEMITE) { Ability(ABILITY_ANALYTIC); Speed(speed); }
PLAYER(SPECIES_WYNAUT) { HP(1); Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(20); }
OPPONENT(SPECIES_WYNAUT) { Speed(10); }
} WHEN {
TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); }
TURN {
MOVE(opponentRight, MOVE_CELEBRATE);
MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft);
}
} SCENE {
HP_BAR(opponentLeft, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage);
}
}
DOUBLE_BATTLE_TEST("Analytic does not activate when not moving last with empty slot", s16 damage)
{
u32 speed;
PARAMETRIZE { speed = 50; }
PARAMETRIZE { speed = 15; }
GIVEN {
PLAYER(SPECIES_MAGNEMITE) { Ability(ABILITY_ANALYTIC); Speed(speed); }
PLAYER(SPECIES_WYNAUT) { HP(1); Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(20); }
OPPONENT(SPECIES_WYNAUT) { Speed(10); }
} WHEN {
TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); }
TURN {
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(opponentRight, MOVE_CELEBRATE);
MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft);
}
} SCENE {
HP_BAR(opponentLeft, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_EQ(results[0].damage, results[1].damage);
}
}
TO_DO_BATTLE_TEST("Analytic takes into account modifications to speeed an priority (Gen 5-8)"); //Eg. Paralysis, Power Weight, Stall
TO_DO_BATTLE_TEST("Analytic does not take into account modifications to speeed an priority (Gen 8)"); //Eg. Paralysis, Power Weight, Stall
TO_DO_BATTLE_TEST("Analytic takes into account the turn order of what fainted Pokémon would've moved");
// Triple Battles needed to test
//TO_DO_BATTLE_TEST("If the Pokémon with Analytic is targeting a Pokémon in a flank position that chooses to switch with its ally in the middle, its move's power will always be normal when it attacks the Pokémon that is shifted into the flank position");

View File

@ -33,6 +33,42 @@ SINGLE_BATTLE_TEST("Magic Coat prints the correct message when bouncing back a m
}
}
SINGLE_BATTLE_TEST("Magic Coat fails if user moves last")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(5); }
OPPONENT(SPECIES_WYNAUT) { Speed(10); }
} WHEN {
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_MAGIC_COAT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, player);
}
}
DOUBLE_BATTLE_TEST("Magic Coat fails when the only slower battler is a fainted ally")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(5); }
PLAYER(SPECIES_WYNAUT) { HP(1); Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(20); }
OPPONENT(SPECIES_WYNAUT) { Speed(10); }
} WHEN {
TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); }
TURN {
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(opponentRight, MOVE_CELEBRATE);
MOVE(playerLeft, MOVE_MAGIC_COAT);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft);
MESSAGE("Wynaut fainted!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, playerLeft);
}
}
DOUBLE_BATTLE_TEST("Magic Coat reflects hazards regardless of the user's position")
{
struct BattlePokemon *coatUser = NULL;

View File

@ -356,6 +356,42 @@ SINGLE_BATTLE_TEST("Protect: Multi-hit moves don't hit a protected target and fa
}
}
SINGLE_BATTLE_TEST("Protect fails if user moves last")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(5); }
OPPONENT(SPECIES_WYNAUT) { Speed(10); }
} WHEN {
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_PROTECT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, player);
}
}
DOUBLE_BATTLE_TEST("Protect fails when the only slower battler is a fainted ally")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(5); }
PLAYER(SPECIES_WYNAUT) { HP(1); Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(20); }
OPPONENT(SPECIES_WYNAUT) { Speed(10); }
} WHEN {
TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); }
TURN {
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(opponentRight, MOVE_CELEBRATE);
MOVE(playerLeft, MOVE_PROTECT);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft);
MESSAGE("Wynaut fainted!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, playerLeft);
}
}
DOUBLE_BATTLE_TEST("Protect: Wide Guard protects self and ally from multi-target moves")
{
u16 move = MOVE_NONE;

View File

@ -1,4 +1,64 @@
#include "global.h"
#include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Snatch (Move Effect) test titles")
ASSUMPTIONS
{
ASSUME(GetMoveEffect(MOVE_SNATCH) == EFFECT_SNATCH);
ASSUME(MoveCanBeSnatched(MOVE_SWORDS_DANCE));
}
SINGLE_BATTLE_TEST("Snatch steals stat-boosting moves from the opponent")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(10); }
OPPONENT(SPECIES_WYNAUT) { Speed(5); }
} WHEN {
TURN { MOVE(player, MOVE_SNATCH); MOVE(opponent, MOVE_SWORDS_DANCE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNATCH, player);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player);
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2);
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Snatch fails if user moves last")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(5); }
OPPONENT(SPECIES_WYNAUT) { Speed(10); }
} WHEN {
TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SNATCH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SNATCH, player);
}
}
DOUBLE_BATTLE_TEST("Snatch fails when the only slower battler is a fainted ally")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(5); }
PLAYER(SPECIES_WYNAUT) { HP(1); Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(20); }
OPPONENT(SPECIES_WYNAUT) { Speed(10); }
} WHEN {
TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); }
TURN {
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(opponentRight, MOVE_CELEBRATE);
MOVE(playerLeft, MOVE_SNATCH);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft);
MESSAGE("Wynaut fainted!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SNATCH, playerLeft);
}
}
TO_DO_BATTLE_TEST("Snatch does not steal moves that cannot be snatched");
TO_DO_BATTLE_TEST("Snatch can steal healing moves");