From 991f11bec1c642a4b6d84d07dce3c50b2cea3295 Mon Sep 17 00:00:00 2001 From: AlexOn1ine Date: Sat, 21 Mar 2026 10:28:21 +0100 Subject: [PATCH 1/2] Fix frame counter in multi battles --- include/battle.h | 2 +- include/battle_util.h | 2 -- src/battle_ai_main.c | 62 ++++++++++++++++++++++++++++--------------- src/battle_ai_util.c | 24 +---------------- src/battle_util.c | 18 ++++++------- 5 files changed, 51 insertions(+), 57 deletions(-) diff --git a/include/battle.h b/include/battle.h index d111551fed..8f0f5736a0 100644 --- a/include/battle.h +++ b/include/battle.h @@ -210,7 +210,6 @@ struct AiLogicData enum Ability abilities[MAX_BATTLERS_COUNT]; enum Item items[MAX_BATTLERS_COUNT]; enum HoldEffect holdEffects[MAX_BATTLERS_COUNT]; - u8 holdEffectParams[MAX_BATTLERS_COUNT]; enum Move lastUsedMove[MAX_BATTLERS_COUNT]; u8 hpPercents[MAX_BATTLERS_COUNT]; enum Move partnerMove; @@ -223,6 +222,7 @@ struct AiLogicData u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. enum Move predictedMove[MAX_BATTLERS_COUNT]; u8 resistBerryAffected[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // Tracks whether currently calc'd move is affected by a resist berry into given target + u8 turnOrder[MAX_BATTLERS_COUNT]; // Flags u32 weatherHasEffect:1; // The same as HasWeatherEffect(). Stored here, so it's called only once. diff --git a/include/battle_util.h b/include/battle_util.h index 97d5623470..836854a8dd 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -121,8 +121,6 @@ struct BattleContext enum HoldEffect holdEffectAtk; enum HoldEffect holdEffectDef; - u8 aiTurnOrder[MAX_BATTLERS_COUNT]; - // Flags u32 isCrit:1; u32 randomFactor:1; diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 19d1792ac6..38a65b64ad 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -643,7 +643,6 @@ void SetBattlerAiData(enum BattlerId battler, struct AiLogicData *aiData) ability = aiData->abilities[battler] = AI_DecideKnownAbilityForTurn(battler); aiData->items[battler] = gBattleMons[battler].item; holdEffect = aiData->holdEffects[battler] = AI_DecideHoldEffectForTurn(battler); - aiData->holdEffectParams[battler] = GetBattlerHoldEffectParam(battler); aiData->lastUsedMove[battler] = (gLastMoves[battler] == MOVE_UNAVAILABLE) ? MOVE_NONE : gLastMoves[battler]; aiData->hpPercents[battler] = GetHealthPercentage(battler); aiData->moveLimitations[battler] = CheckMoveLimitations(battler, 0, MOVE_LIMITATIONS_ALL); @@ -678,6 +677,22 @@ static u32 Ai_SetMoveAccuracy(struct AiLogicData *aiData, enum BattlerId battler } #undef BYPASSES_ACCURACY_CALC +static void SetBattlerTurnOrder(u8 *aiTurnOrder) +{ + for (u32 i = 0; i < gBattlersCount; i++) + { + for (u32 j = 0; j < gBattlersCount; j++) + { + if (AI_WhoStrikesFirst(aiTurnOrder[i], aiTurnOrder[j], MOVE_NONE, MOVE_NONE, DONT_CONSIDER_PRIORITY) == AI_IS_FASTER) + { + u32 temp = aiTurnOrder[i]; + aiTurnOrder[i] = aiTurnOrder[j]; + aiTurnOrder[j] = temp; + } + } + } +} + void CalcBattlerAiMovesData(struct AiLogicData *aiData, enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 weather, u32 fieldStatus) { enum Move move; @@ -723,56 +738,59 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, enum BattlerId bat void SetAiLogicDataForTurn(struct AiLogicData *aiData) { - u32 battlersCount, weather; - memset(aiData, 0, sizeof(struct AiLogicData)); gAiBattleData->aiUsingGimmick = 0; if (!(gBattleTypeFlags & BATTLE_TYPE_HAS_AI) && !IsWildMonSmart()) return; gAiLogicData->aiCalcInProgress = TRUE; - + if (DEBUG_AI_DELAY_TIMER) AIDebugTimerStart(); aiData->weatherHasEffect = HasWeatherEffect(); - weather = AI_GetWeather(); + u32 weather = AI_GetWeather(); // get/assume all battler data and simulate AI damage - battlersCount = gBattlersCount; + u32 battlersCount = gBattlersCount; - for (enum BattlerId battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++) + for (enum BattlerId battler = 0; battler < battlersCount; battler++) { - if (!IsBattlerAlive(battlerAtk)) + if (!IsBattlerAlive(battler)) continue; - SetBattlerAiData(battlerAtk, aiData); + SetBattlerAiData(battler, aiData); } - for (enum BattlerId battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++) + for (enum BattlerId battler = 0; battler < battlersCount; battler++) + aiData->turnOrder[battler] = battler; + SetBattlerTurnOrder(aiData->turnOrder); + + for (enum BattlerId battler = 0; battler < battlersCount; battler++) { - if (!IsBattlerAlive(battlerAtk)) + if (!IsBattlerAlive(battler)) continue; - SetBattlerAiMovesData(aiData, battlerAtk, battlersCount, weather); + SetBattlerAiMovesData(aiData, battler, battlersCount, weather); + aiData->turnOrder[battler] = battler; } - for (enum BattlerId battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++) + for (enum BattlerId battler = 0; battler < battlersCount; battler++) { // Prediction limited to player side but can be expanded to read partners move in the future - if (!IsOnPlayerSide(battlerAtk) || !CanAiPredictMove(battlerAtk)) + if (!IsOnPlayerSide(battler) || !CanAiPredictMove(battler)) continue; // This can potentially be cleaned up more - BattleAI_SetupAIData(0xF, battlerAtk); - u32 chosenMoveIndex = ChooseMoveOrAction(battlerAtk); - gAiLogicData->predictedMove[battlerAtk] = gBattleMons[battlerAtk].moves[chosenMoveIndex]; + BattleAI_SetupAIData(0xF, battler); + u32 chosenMoveIndex = ChooseMoveOrAction(battler); + gAiLogicData->predictedMove[battler] = gBattleMons[battler].moves[chosenMoveIndex]; aiData->predictingMove = RandomPercentage(RNG_AI_PREDICT_MOVE, PREDICT_MOVE_CHANCE); } if (DEBUG_AI_DELAY_TIMER) AIDebugTimerEnd(); - + gAiLogicData->aiCalcInProgress = FALSE; } @@ -1272,7 +1290,7 @@ static s32 AI_CheckBadMove(enum BattlerId battlerAtk, enum BattlerId battlerDef, { ADJUST_SCORE(-5); } - + // check non-user target if (moveTarget != TARGET_USER) { @@ -5068,7 +5086,7 @@ static s32 AI_CalcMoveEffectScore(enum BattlerId battlerAtk, enum BattlerId batt && AI_WhoStrikesFirst(battlerAtk, battlerDef, MOVE_FAKE_OUT, MOVE_FAKE_OUT, CONSIDER_PRIORITY) == AI_IS_FASTER) || (HasMove(battlerDefPartner, MOVE_FAKE_OUT) && gBattleStruct->battlerState[battlerDefPartner].isFirstTurn && AI_WhoStrikesFirst(battlerAtk, battlerDefPartner, MOVE_FAKE_OUT, MOVE_FAKE_OUT, CONSIDER_PRIORITY) == AI_IS_FASTER)) - { + { ADJUST_SCORE(FAST_KILL + 2); } // If ally has KO on target's partner, but target can fast KO ally (checking move and priority combinations for everything likely gets a bit complicated) @@ -5091,7 +5109,7 @@ static s32 AI_CalcMoveEffectScore(enum BattlerId battlerAtk, enum BattlerId batt } } // If ally has slow KO with their chosen move, user sees no KOs while outspeeding (checking move and priority combinations for everything likely gets a bit complicated) - else if (hasPartner + else if (hasPartner && (atkPartnerKoDef && AI_WhoStrikesFirst(battlerAtkPartner, battlerDef, gBattleMons[battlerAtkPartner].moves[gAiBattleData->chosenMoveIndex[battlerAtkPartner]], predictedMoveSpeedCheck, CONSIDER_PRIORITY) == AI_IS_SLOWER) && !(atkKoDef && AI_WhoStrikesFirst(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, DONT_CONSIDER_PRIORITY) == AI_IS_FASTER) && !(atkKoDefPartner && AI_WhoStrikesFirst(battlerAtk, battlerDefPartner, move, MOVE_SCRATCH, DONT_CONSIDER_PRIORITY) == AI_IS_FASTER)) @@ -5099,7 +5117,7 @@ static s32 AI_CalcMoveEffectScore(enum BattlerId battlerAtk, enum BattlerId batt ADJUST_SCORE(GOOD_EFFECT); } else - { + { ADJUST_SCORE(DECENT_EFFECT); } } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 1e67d3a0c1..c15cf60f4b 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -23,7 +23,6 @@ #include "constants/moves.h" #include "constants/items.h" -static void AI_SetBattlerTurnOrder(u8 *aiTurnOrder); static u32 GetAIEffectGroup(enum BattleMoveEffects effect); static u32 GetAIEffectGroupFromMove(enum BattlerId battler, enum Move move); @@ -959,7 +958,6 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk, ctx.abilityDef = AI_GetMoldBreakerSanitizedAbility(battlerAtk, ctx.abilityAtk, aiData->abilities[battlerDef], ctx.holdEffectDef, move); ctx.isCrit = ShouldCalcCritDamage(&ctx); ctx.typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(&ctx); - AI_SetBattlerTurnOrder(ctx.aiTurnOrder); u32 movePower = GetMovePower(move); @@ -2188,7 +2186,7 @@ bool32 ShouldTryOHKO(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum u32 accuracy = gAiLogicData->moveAccuracy[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex]; gPotentialItemEffectBattler = battlerDef; - if (holdEffect == HOLD_EFFECT_FOCUS_BAND && (Random() % 100) < gAiLogicData->holdEffectParams[battlerDef]) + if (holdEffect == HOLD_EFFECT_FOCUS_BAND && (Random() % 100) < GetBattlerHoldEffectParam(battlerDef)) return FALSE; //probabilistically speaking, focus band should activate so dont OHKO else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && AI_BattlerAtMaxHp(battlerDef)) return FALSE; @@ -6501,23 +6499,3 @@ bool32 IsPartyMonPlannedToBeSwitchedInByPartner(u32 partyIndex, enum BattlerId b return TRUE; return FALSE; } - -static void AI_SetBattlerTurnOrder(u8 *aiTurnOrder) -{ - for (u32 battler = 0; battler < gBattlersCount; battler++) - aiTurnOrder[battler] = battler; - - for (u32 i = 0; i < gBattlersCount; i++) - { - for (u32 j = 0; j < gBattlersCount; j++) - { - if (AI_WhoStrikesFirst(aiTurnOrder[i], aiTurnOrder[j], MOVE_NONE, MOVE_NONE, DONT_CONSIDER_PRIORITY) == AI_IS_FASTER) - { - u32 temp = aiTurnOrder[i]; - aiTurnOrder[i] = aiTurnOrder[j]; - aiTurnOrder[j] = temp; - } - } - } -} - diff --git a/src/battle_util.c b/src/battle_util.c index e058e351be..06430c7d72 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1113,25 +1113,25 @@ bool32 IsLastMonToMove(enum BattlerId battler) return TRUE; } -static u32 GetAiTurnOrder(u8 *aiTurnOrder, enum BattlerId battler) +static u32 GetAiTurnOrder(enum BattlerId battler) { for (u32 i = 0; i < gBattlersCount; i++) { - if (aiTurnOrder[i] == battler) + if (gAiLogicData->turnOrder[i] == battler) return i; } return 0; } -static bool32 Ai_AttackerMovesAfterTarget(struct BattleContext *ctx) +static bool32 Ai_AttackerMovesAfterTarget(enum BattlerId battlerAtk, enum BattlerId battlerDef) { - return GetAiTurnOrder(ctx->aiTurnOrder, ctx->battlerAtk) > GetAiTurnOrder(ctx->aiTurnOrder, ctx->battlerDef); + return GetAiTurnOrder(battlerAtk) > GetAiTurnOrder(battlerDef); } -static bool32 Ai_AttackerMovesLast(struct BattleContext *ctx) +static bool32 Ai_AttackerMovesLast(enum BattlerId battlerAtk) { u32 numAliveBattlers = 0; - u32 battlerTurnOrder = GetAiTurnOrder(ctx->aiTurnOrder, ctx->battlerAtk); + u32 battlerTurnOrder = GetAiTurnOrder(battlerAtk); for (enum BattlerId battler = B_BATTLER_0; battler < gBattlersCount; battler++) { @@ -6344,7 +6344,7 @@ static inline u32 CalcMoveBasePower(struct BattleContext *ctx) case EFFECT_PAYBACK: if (ctx->aiCalc) { - if (Ai_AttackerMovesAfterTarget(ctx)) + if (Ai_AttackerMovesAfterTarget(battlerAtk, battlerDef)) basePower *= 2; } else if (HasBattlerActedThisTurn(battlerDef) @@ -6356,7 +6356,7 @@ static inline u32 CalcMoveBasePower(struct BattleContext *ctx) case EFFECT_BOLT_BEAK: if (ctx->aiCalc) { - if (!Ai_AttackerMovesAfterTarget(ctx)) + if (!Ai_AttackerMovesAfterTarget(battlerAtk, battlerDef)) basePower *= 2; } else if (!HasBattlerActedThisTurn(battlerDef) @@ -6567,7 +6567,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct BattleContext *ctx) if (ctx->aiCalc) { - if (Ai_AttackerMovesLast(ctx)) + if (Ai_AttackerMovesLast(battlerAtk)) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); } else if (IsLastMonToMove(battlerAtk)) From 6f894ded4d902c3d83077e9bb05a47af84b61a8f Mon Sep 17 00:00:00 2001 From: AlexOn1ine Date: Sat, 21 Mar 2026 13:16:26 +0100 Subject: [PATCH 2/2] oops --- src/battle_ai_main.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 38a65b64ad..4d469bfd80 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -766,13 +766,12 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) aiData->turnOrder[battler] = battler; SetBattlerTurnOrder(aiData->turnOrder); - for (enum BattlerId battler = 0; battler < battlersCount; battler++) + for (enum BattlerId battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++) { - if (!IsBattlerAlive(battler)) + if (!IsBattlerAlive(battlerAtk)) continue; - SetBattlerAiMovesData(aiData, battler, battlersCount, weather); - aiData->turnOrder[battler] = battler; + SetBattlerAiMovesData(aiData, battlerAtk, battlersCount, weather); } for (enum BattlerId battler = 0; battler < battlersCount; battler++)