Refactor protect failure to match vanilla (#8832)

This commit is contained in:
Alex 2026-01-10 18:23:01 +01:00 committed by GitHub
parent ad31eb7149
commit c76c3b37bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 97 additions and 66 deletions

View File

@ -468,5 +468,7 @@ bool32 IsUsableWhileAsleepEffect(enum BattleMoveEffects effect);
void SetWrapTurns(u32 battler, enum HoldEffect holdEffect);
bool32 ChangeOrderTargetAfterAttacker(void);
void TryUpdateEvolutionTracker(u32 evolutionCondition, u32 upAmount, enum Move usedMove);
bool32 CanUseMoveConsecutively(u32 battler);
void TryResetConsecutiveUseCounter(u32 battler);
#endif // GUARD_BATTLE_UTIL_H

View File

@ -74,6 +74,7 @@
#define B_EXTRAPOLATED_MOVE_FLAGS TRUE // Adds move flags to moves that they don't officially have but would likely have if they were in the latest core series game.
#define B_HIDDEN_POWER_COUNTER GEN_LATEST // Prior to Gen4, Counter and Mirror Coat treat Hidden Power as Physical regardless of type.
#define B_MODERN_TRICK_CHOICE_LOCK GEN_LATEST // In Gen5+, if a Choice Item is swapped for a Choice Item, the Trick/Switcheroo user can pick another move, and then they'll be locked into it.
#define B_PROTECT_FAILURE_RATE GEN_LATEST // In Gen5+, protect moves fails 1/3 of the time instead of 1/2
// Ability data settings
#define B_UPDATED_ABILITY_DATA GEN_LATEST // Affects flags

View File

@ -224,7 +224,7 @@ enum VolatileFlags
F(VOLATILE_TRANSFORMED_MON_PID, transformedMonPID, (u32, UINT32_MAX)) \
F(VOLATILE_DISABLED_MOVE, disabledMove, (u32, MOVES_COUNT_ALL)) \
F(VOLATILE_ENCORED_MOVE, encoredMove, (u32, MOVES_COUNT_ALL)) \
F(VOLATILE_PROTECT_USES, protectUses, (u32, UINT8_MAX)) \
F(VOLATILE_PROTECT_USES, consecutiveMoveUses, (u32, UINT8_MAX)) \
F(VOLATILE_STOCKPILE_COUNTER, stockpileCounter, (u32, MAX_STAT_STAGE)) \
F(VOLATILE_STOCKPILE_DEF, stockpileDef, (u32, MAX_STAT_STAGE)) \
F(VOLATILE_STOCKPILE_SP_DEF, stockpileSpDef, (u32, MAX_STAT_STAGE)) \

View File

@ -835,7 +835,6 @@ enum ProtectLikeUsedStringID
{
B_MSG_PROTECTED_ITSELF,
B_MSG_BRACED_ITSELF,
B_MSG_PROTECT_FAILED,
B_MSG_PROTECTED_TEAM,
};

View File

@ -2399,14 +2399,14 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, enum Move move, s32 s
{
ADJUST_SCORE(-10); //Don't protect if you're going to faint after protecting
}
else if (gBattleMons[battlerAtk].volatiles.protectUses == 1 && Random() % 100 < 50)
else if (gBattleMons[battlerAtk].volatiles.consecutiveMoveUses == 1 && Random() % 100 < 50)
{
if (isBattle1v1)
ADJUST_SCORE(-6);
else
ADJUST_SCORE(-10); //Don't try double protecting in doubles
}
else if (gBattleMons[battlerAtk].volatiles.protectUses >= 2)
else if (gBattleMons[battlerAtk].volatiles.consecutiveMoveUses >= 2)
{
ADJUST_SCORE(-10);
}

View File

@ -2250,7 +2250,7 @@ s32 ProtectChecks(u32 battlerAtk, u32 battlerDef, enum Move move, enum Move pred
s32 score = 0;
// TODO more sophisticated logic
u32 uses = gBattleMons[battlerAtk].volatiles.protectUses;
u32 uses = gBattleMons[battlerAtk].volatiles.consecutiveMoveUses;
/*if (GetMoveResultFlags(predictedMove) & (MOVE_RESULT_NO_EFFECT | MOVE_RESULT_MISSED))
{

View File

@ -1065,7 +1065,6 @@ const u16 gProtectLikeUsedStringIds[] =
{
[B_MSG_PROTECTED_ITSELF] = STRINGID_PKMNPROTECTEDITSELF2,
[B_MSG_BRACED_ITSELF] = STRINGID_PKMNBRACEDITSELF,
[B_MSG_PROTECT_FAILED] = STRINGID_BUTITFAILED,
[B_MSG_PROTECTED_TEAM] = STRINGID_PROTECTEDTEAM,
};

View File

@ -907,8 +907,6 @@ static const struct SpriteTemplate sSpriteTemplate_MonIconOnLvlUpBanner =
.callback = SpriteCB_MonIconOnLvlUpBanner
};
static const u16 sProtectSuccessRates[] = {USHRT_MAX, USHRT_MAX / 2, USHRT_MAX / 4, USHRT_MAX / 8};
#define _ 0
static const struct PickupItem sPickupTable[] =
@ -7425,65 +7423,28 @@ static void Cmd_unused_0x78(void)
{
}
static void TryResetProtectUseCounter(u32 battler)
{
u32 lastMove = gLastResultingMoves[battler];
if (lastMove == MOVE_UNAVAILABLE)
{
gBattleMons[battler].volatiles.protectUses = 0;
return;
}
enum BattleMoveEffects lastEffect = GetMoveEffect(lastMove);
if (!gBattleMoveEffects[lastEffect].usesProtectCounter)
{
if (GetConfig(CONFIG_ALLY_SWITCH_FAIL_CHANCE) < GEN_9 || lastEffect != EFFECT_ALLY_SWITCH)
gBattleMons[battler].volatiles.protectUses = 0;
}
}
static void Cmd_setprotectlike(void)
{
CMD_ARGS();
bool32 notLastTurn = TRUE;
u32 protectMethod = GetMoveProtectMethod(gCurrentMove);
TryResetProtectUseCounter(gBattlerAttacker);
if (IsLastMonToMove(gBattlerAttacker))
notLastTurn = FALSE;
if ((sProtectSuccessRates[gBattleMons[gBattlerAttacker].volatiles.protectUses] >= RandomUniform(RNG_PROTECT_FAIL, 0, USHRT_MAX) && notLastTurn)
|| (protectMethod == PROTECT_WIDE_GUARD && GetConfig(CONFIG_WIDE_GUARD) >= GEN_6)
|| (protectMethod == PROTECT_QUICK_GUARD && GetConfig(CONFIG_QUICK_GUARD) >= GEN_6))
if (GetMoveEffect(gCurrentMove) == EFFECT_ENDURE)
{
if (GetMoveEffect(gCurrentMove) == EFFECT_ENDURE)
{
gBattleMons[gBattlerAttacker].volatiles.endured = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BRACED_ITSELF;
}
else if (GetProtectType(protectMethod) == PROTECT_TYPE_SIDE)
{
gProtectStructs[gBattlerAttacker].protected = protectMethod;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECTED_TEAM;
}
else
{
gProtectStructs[gBattlerAttacker].protected = protectMethod;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECTED_ITSELF;
}
gBattleMons[gBattlerAttacker].volatiles.protectUses++;
gBattleMons[gBattlerAttacker].volatiles.endured = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BRACED_ITSELF;
}
else // Protect failed
else if (GetProtectType(protectMethod) == PROTECT_TYPE_SIDE)
{
gBattleMons[gBattlerAttacker].volatiles.protectUses = 0;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECT_FAILED;
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2;
gProtectStructs[gBattlerAttacker].protected = protectMethod;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECTED_TEAM;
}
else
{
gProtectStructs[gBattlerAttacker].protected = protectMethod;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECTED_ITSELF;
}
gBattleMons[gBattlerAttacker].volatiles.consecutiveMoveUses++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
@ -13058,16 +13019,17 @@ void BS_TryAllySwitch(void)
}
else if (GetConfig(CONFIG_ALLY_SWITCH_FAIL_CHANCE) >= GEN_9)
{
TryResetProtectUseCounter(gBattlerAttacker);
if (sProtectSuccessRates[gBattleMons[gBattlerAttacker].volatiles.protectUses] < Random())
TryResetConsecutiveUseCounter(gBattlerAttacker);
if (CanUseMoveConsecutively(gBattlerAttacker))
{
gBattleMons[gBattlerAttacker].volatiles.protectUses = 0;
gBattlescriptCurrInstr = cmd->failInstr;
gBattleMons[gBattlerAttacker].volatiles.consecutiveMoveUses++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattleMons[gBattlerAttacker].volatiles.protectUses++;
gBattlescriptCurrInstr = cmd->nextInstr;
gBattleMons[gBattlerAttacker].volatiles.consecutiveMoveUses = 0;
gBattlescriptCurrInstr = cmd->failInstr;
}
}
else

View File

@ -2814,7 +2814,27 @@ static enum MoveCanceler CancelerMoveFailure(struct BattleContext *ctx)
battleScript = BattleScript_ButItFailed;
break;
case EFFECT_PROTECT:
// TODO
case EFFECT_ENDURE:
TryResetConsecutiveUseCounter(gBattlerAttacker);
if (IsLastMonToMove(ctx->battlerAtk))
{
battleScript = BattleScript_ButItFailed;
}
else
{
u32 protectMethod = GetMoveProtectMethod(ctx->move);
bool32 canUseProtectSecondTime = CanUseMoveConsecutively(ctx->battlerAtk);
bool32 canUseWideGuard = (GetConfig(CONFIG_WIDE_GUARD) >= GEN_6 && protectMethod == PROTECT_WIDE_GUARD);
bool32 canUseQuickGuard = (GetConfig(CONFIG_QUICK_GUARD) >= GEN_6 && protectMethod == PROTECT_QUICK_GUARD);
if (!canUseProtectSecondTime && !canUseWideGuard && !canUseQuickGuard)
battleScript = BattleScript_ButItFailed;
}
if (battleScript != NULL)
{
gBattleMons[gBattlerAttacker].volatiles.consecutiveMoveUses = 0;
gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2;
}
break;
case EFFECT_REST:
if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP
@ -12246,3 +12266,51 @@ void TryUpdateEvolutionTracker(u32 evolutionCondition, u32 upAmount, enum Move u
}
}
}
static const u16 sProtectSuccessRates[] =
{
USHRT_MAX,
USHRT_MAX / 2,
USHRT_MAX / 4,
USHRT_MAX / 8
};
static const u16 sGen5ProtectSuccessRates[] =
{
USHRT_MAX,
USHRT_MAX / 3,
USHRT_MAX / 9,
USHRT_MAX / 27
};
bool32 CanUseMoveConsecutively(u32 battler)
{
u32 moveUses = gBattleMons[battler].volatiles.consecutiveMoveUses;
if (moveUses >= ARRAY_COUNT(sProtectSuccessRates))
moveUses = ARRAY_COUNT(sProtectSuccessRates) - 1;
u32 successRate = sGen5ProtectSuccessRates[moveUses];
if (B_PROTECT_FAILURE_RATE < GEN_5)
successRate = sProtectSuccessRates[moveUses];
return successRate >= RandomUniform(RNG_PROTECT_FAIL, 0, USHRT_MAX);
}
// Used for Protect, Endure and Ally switch
void TryResetConsecutiveUseCounter(u32 battler)
{
u32 lastMove = gLastResultingMoves[battler];
if (lastMove == MOVE_UNAVAILABLE)
{
gBattleMons[battler].volatiles.consecutiveMoveUses = 0;
return;
}
enum BattleMoveEffects lastEffect = GetMoveEffect(lastMove);
if (!gBattleMoveEffects[lastEffect].usesProtectCounter)
{
if (GetConfig(CONFIG_ALLY_SWITCH_FAIL_CHANCE) < GEN_9 || lastEffect != EFFECT_ALLY_SWITCH)
gBattleMons[battler].volatiles.consecutiveMoveUses = 0;
}
}

View File

@ -196,7 +196,7 @@ DOUBLE_BATTLE_TEST("Ally Switch doesn't increase the Protect-like moves counter
} WHEN {
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); }
} THEN {
EXPECT(gBattleMons[B_POSITION_PLAYER_RIGHT].volatiles.protectUses == 0);
EXPECT(gBattleMons[B_POSITION_PLAYER_RIGHT].volatiles.consecutiveMoveUses == 0);
}
}
@ -211,7 +211,7 @@ DOUBLE_BATTLE_TEST("Ally Switch increases the Protect-like moves counter (Gen9+)
} WHEN {
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); }
} THEN {
EXPECT(gBattleMons[B_POSITION_PLAYER_RIGHT].volatiles.protectUses == 1);
EXPECT(gBattleMons[B_POSITION_PLAYER_RIGHT].volatiles.consecutiveMoveUses == 1);
}
}