From 276275f837bf40f56ab73eed14a13d6c5619bc71 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Wed, 10 Sep 2025 07:44:14 +0200 Subject: [PATCH 01/50] Fix 3 spaces tabs in field_message_box.c (#2179) --- src/field_message_box.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/field_message_box.c b/src/field_message_box.c index b797e1d353..f59a0429eb 100755 --- a/src/field_message_box.c +++ b/src/field_message_box.c @@ -29,13 +29,13 @@ static void Task_DrawFieldMessage(u8 taskId) switch (task->tState) { case 0: - LoadMessageBoxAndBorderGfx(); - task->tState++; - break; + LoadMessageBoxAndBorderGfx(); + task->tState++; + break; case 1: - DrawDialogueFrame(0, TRUE); - task->tState++; - break; + DrawDialogueFrame(0, TRUE); + task->tState++; + break; case 2: if (RunTextPrintersAndIsPrinter0Active() != TRUE) { From 9c8cfe3b7586471958c9b90a04e0fab65f04a37a Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sat, 13 Sep 2025 18:49:36 +0200 Subject: [PATCH 02/50] Fix include order in src/clock.c to be alphabetical (#2181) --- src/clock.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/clock.c b/src/clock.c index 125d403bb3..0f46fd4ca8 100644 --- a/src/clock.c +++ b/src/clock.c @@ -1,15 +1,15 @@ #include "global.h" +#include "berry.h" +#include "dewford_trend.h" #include "event_data.h" +#include "field_specials.h" +#include "field_weather.h" +#include "main.h" +#include "lottery_corner.h" +#include "overworld.h" #include "rtc.h" #include "time_events.h" -#include "field_specials.h" -#include "lottery_corner.h" -#include "dewford_trend.h" #include "tv.h" -#include "field_weather.h" -#include "berry.h" -#include "main.h" -#include "overworld.h" #include "wallclock.h" static void UpdatePerDay(struct Time *localTime); From bd1a4db380aa72c85dc70601953e0badece2f281 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:49:09 +0200 Subject: [PATCH 03/50] Fixes EndTurn Eject Pack (#7813) --- src/battle_end_turn.c | 52 +++++----------------------- src/battle_util.c | 9 ++--- test/battle/hold_effect/eject_pack.c | 23 ++++++++++++ 3 files changed, 35 insertions(+), 49 deletions(-) diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index 85a77399c0..73ffa12023 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -61,8 +61,8 @@ enum EndTurnResolutionOrder ENDTURN_TERRAIN, ENDTURN_THIRD_EVENT_BLOCK, ENDTURN_EMERGENCY_EXIT_4, - ENDTURN_ABILITIES, - ENDTURN_FOURTH_EVENT_BLOCK, + ENDTURN_FORM_CHANGE_ABILITIES, + ENDTURN_EJECT_PACK, ENDTURN_DYNAMAX, ENDTURN_COUNT, }; @@ -101,13 +101,6 @@ enum ThirdEventBlock THIRD_EVENT_BLOCK_ITEMS, }; -// Form changing abilities and Eject Pack -enum FourthEventBlock -{ - FOURTH_EVENT_BLOCK_HUNGER_SWITCH, - FOURTH_EVENT_BLOCK_EJECT_PACK, -}; - static u32 GetBattlerSideForMessage(u32 side) { u32 battler = 0; @@ -1458,7 +1451,7 @@ static bool32 HandleEndTurnThirdEventBlock(u32 battler) return effect; } -static bool32 HandleEndTurnAbilities(u32 battler) +static bool32 HandleEndTurnFormChangeAbilities(u32 battler) { bool32 effect = FALSE; @@ -1472,6 +1465,7 @@ static bool32 HandleEndTurnAbilities(u32 battler) case ABILITY_SCHOOLING: case ABILITY_SHIELDS_DOWN: case ABILITY_ZEN_MODE: + case ABILITY_HUNGER_SWITCH: if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, ability, 0, MOVE_NONE)) effect = TRUE; } @@ -1479,38 +1473,10 @@ static bool32 HandleEndTurnAbilities(u32 battler) return effect; } -static bool32 HandleEndTurnFourthEventBlock(u32 battler) +static bool32 HandleEndTurnEjectPack(u32 battler) { - bool32 effect = FALSE; - - switch (gBattleStruct->eventBlockCounter) - { - case FOURTH_EVENT_BLOCK_HUNGER_SWITCH: - { - u32 ability = GetBattlerAbility(battler); - if (ability == ABILITY_HUNGER_SWITCH) - { - if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, ability, 0, MOVE_NONE)) - effect = TRUE; - } - gBattleStruct->eventBlockCounter++; - break; - } - case FOURTH_EVENT_BLOCK_EJECT_PACK: - { - enum ItemHoldEffect holdEffect = GetBattlerHoldEffect(battler, TRUE); - if (holdEffect == HOLD_EFFECT_EJECT_PACK) - { - if (ItemBattleEffects(ITEMEFFECT_NORMAL, battler)) - effect = TRUE; - } - gBattleStruct->eventBlockCounter = 0; - gBattleStruct->turnEffectsBattlerId++; - break; - } - } - - return effect; + gBattleStruct->turnEffectsBattlerId++; + return TrySwitchInEjectPack(ITEMEFFECT_NORMAL); } static bool32 HandleEndTurnDynamax(u32 battler) @@ -1577,8 +1543,8 @@ static bool32 (*const sEndTurnEffectHandlers[])(u32 battler) = [ENDTURN_TERRAIN] = HandleEndTurnTerrain, [ENDTURN_THIRD_EVENT_BLOCK] = HandleEndTurnThirdEventBlock, [ENDTURN_EMERGENCY_EXIT_4] = HandleEndTurnEmergencyExit, - [ENDTURN_ABILITIES] = HandleEndTurnAbilities, - [ENDTURN_FOURTH_EVENT_BLOCK] = HandleEndTurnFourthEventBlock, + [ENDTURN_FORM_CHANGE_ABILITIES] = HandleEndTurnFormChangeAbilities, + [ENDTURN_EJECT_PACK] = HandleEndTurnEjectPack, [ENDTURN_DYNAMAX] = HandleEndTurnDynamax, }; diff --git a/src/battle_util.c b/src/battle_util.c index 61d8ef0535..10d04161b0 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -11499,14 +11499,11 @@ bool32 TrySwitchInEjectPack(enum ItemCaseId caseID) gBattleScripting.battler = battler; gLastUsedItem = gBattleMons[battler].item; if (caseID == ITEMEFFECT_ON_SWITCH_IN_FIRST_TURN) - { BattleScriptPushCursorAndCallback(BattleScript_EjectPackActivate_End3); - } + else if (caseID == ITEMEFFECT_NORMAL) + BattleScriptExecute(BattleScript_EjectPackActivate_End2); else - { - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_EjectPackActivate_Ret; - } + BattleScriptCall(BattleScript_EjectPackActivate_Ret); gAiLogicData->ejectPackSwitch = TRUE; return TRUE; } diff --git a/test/battle/hold_effect/eject_pack.c b/test/battle/hold_effect/eject_pack.c index 600af04515..836f3d0fd1 100644 --- a/test/battle/hold_effect/eject_pack.c +++ b/test/battle/hold_effect/eject_pack.c @@ -338,3 +338,26 @@ SINGLE_BATTLE_TEST("Eject Pack does not activate if mon is switched in due to Ej NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); } } + +DOUBLE_BATTLE_TEST("Eject Pack will trigger on the fastest mon at the end of the turn") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_SYRUP_BOMB, MOVE_EFFECT_SYRUP_BOMB) == TRUE); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(10); Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_SYRUP_BOMB, target: playerLeft); + MOVE(opponentRight, MOVE_SYRUP_BOMB, target: playerRight); + SEND_OUT(playerRight, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SYRUP_BOMB_SPEED_DROP, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SYRUP_BOMB_SPEED_DROP, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } +} From 988a7f0420e9ccad13360f9c8613731ed2db1b5d Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Mon, 29 Sep 2025 14:30:08 +0200 Subject: [PATCH 04/50] Fix Big Root tests (#7817) Co-authored-by: Hedara --- test/battle/hold_effect/big_root.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/battle/hold_effect/big_root.c b/test/battle/hold_effect/big_root.c index 41dd09adc0..3d2256ea34 100644 --- a/test/battle/hold_effect/big_root.c +++ b/test/battle/hold_effect/big_root.c @@ -14,7 +14,7 @@ SINGLE_BATTLE_TEST("Big Root increases healing from absorbing moves", s16 damage PARAMETRIZE { item = ITEM_BIG_ROOT; } GIVEN { - PLAYER(SPECIES_WOBBUFFET) { HP(200); Item(item); } + PLAYER(SPECIES_XURKITREE) { HP(200); Item(item); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_ABSORB); } @@ -24,7 +24,7 @@ SINGLE_BATTLE_TEST("Big Root increases healing from absorbing moves", s16 damage HP_BAR(player, captureDamage: &results[i].heal); } FINALLY { EXPECT_EQ(results[0].damage, results[1].damage); // Damage is unaffected - EXPECT_MUL_EQ(results[1].heal, Q_4_12(5234 / 4096), results[0].heal); + EXPECT_MUL_EQ(results[0].heal, Q_4_12(1.3), results[1].heal); } } @@ -65,7 +65,7 @@ SINGLE_BATTLE_TEST("Big Root increases damage from absorbing Liquid Ooze", s16 d PARAMETRIZE { item = ITEM_BIG_ROOT; } GIVEN { - PLAYER(SPECIES_WOBBUFFET) { HP(200); Item(item); } + PLAYER(SPECIES_XURKITREE) { HP(200); Item(item); } OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_LIQUID_OOZE); } } WHEN { TURN { MOVE(player, MOVE_ABSORB); } @@ -73,6 +73,6 @@ SINGLE_BATTLE_TEST("Big Root increases damage from absorbing Liquid Ooze", s16 d ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); HP_BAR(player, captureDamage: &results[i].damage); } FINALLY { - EXPECT_MUL_EQ(results[1].damage, Q_4_12(5234 / 4096), results[0].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); } } From 3a018d6ecc3830c9acccd53692eed812f42065fd Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Mon, 29 Sep 2025 12:16:01 -0300 Subject: [PATCH 05/50] Fix Party Menu move select name width (#7820) --- src/party_menu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/party_menu.c b/src/party_menu.c index 63517a110d..e6568c34bf 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -5230,13 +5230,13 @@ static void ShowMoveSelectWindow(u8 slot) { u8 i; u8 moveCount = 0; - u8 fontId = FONT_NORMAL; u8 windowId = DisplaySelectionWindow(SELECTWINDOW_MOVES); u16 move; for (i = 0; i < MAX_MON_MOVES; i++) { move = GetMonData(&gPlayerParty[slot], MON_DATA_MOVE1 + i); + u8 fontId = GetFontIdToFit(GetMoveName(move), FONT_NORMAL, 0, 72); AddTextPrinterParameterized(windowId, fontId, GetMoveName(move), 8, (i * 16) + 1, TEXT_SKIP_DRAW, NULL); if (move != MOVE_NONE) moveCount++; @@ -8025,4 +8025,3 @@ static void FieldCallback_RockClimb(void) gFieldEffectArguments[0] = GetCursorSelectionMonId(); FieldEffectStart(FLDEFF_USE_ROCK_CLIMB); } - From 210aeb1d119955121ca9b2549cf34e561b357c70 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Mon, 29 Sep 2025 13:53:47 -0300 Subject: [PATCH 06/50] Fix Battle Frontier using Strange Balls (#7823) --- src/battle_tower.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/battle_tower.c b/src/battle_tower.c index 2d0cbbe11e..e215c259a4 100644 --- a/src/battle_tower.c +++ b/src/battle_tower.c @@ -1648,8 +1648,8 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 SetMonData(dst, MON_DATA_TERA_TYPE, &data); } - - SetMonData(dst, MON_DATA_POKEBALL, &ball); + if (ball != BALL_STRANGE) + SetMonData(dst, MON_DATA_POKEBALL, &ball); CalculateMonStats(dst); } From 09aa452659a73dd7b356f37f232500ce6ac59389 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:32:11 +0200 Subject: [PATCH 07/50] Fix Throat Spray activating multiply times (#7818) --- include/battle_util.h | 1 + include/constants/battle_script_commands.h | 1 + src/battle_script_commands.c | 20 ++++++++++++++++++++ src/battle_util.c | 18 +----------------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index 0c486f4a3a..fd0fbbac46 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -388,6 +388,7 @@ bool32 IsPursuitTargetSet(void); void ClearPursuitValuesIfSet(u32 battler); void ClearPursuitValues(void); bool32 HasWeatherEffect(void); +bool32 IsAnyTargetAffected(u32 battlerAtk); u32 RestoreWhiteHerbStats(u32 battler); bool32 IsFutureSightAttackerInParty(u32 battlerAtk, u32 battlerDef, u32 move); bool32 HadMoreThanHalfHpNowDoesnt(u32 battler); diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 2b4d590286..94d2a2bd48 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -153,6 +153,7 @@ enum MoveEndEffects MOVEEND_MULTIHIT_MOVE, MOVEEND_MOVE_BLOCK, MOVEEND_ITEM_EFFECTS_ATTACKER, + MOVEEND_ITEM_THROAT_SPRAY, MOVEEND_ABILITY_BLOCK, MOVEEND_SHEER_FORCE, // If move is Sheer Force affected, skip until Opportunist MOVEEND_RED_CARD, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index f2b03a382e..9e1d6cc358 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6494,10 +6494,30 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_ITEM_EFFECTS_ATTACKER: + // ITEMEFFECT_MOVE_END loops over all battlers, not just attacker. + // It will executre only the first mon with an applicable item. + // So presumably it is a bug if (ItemBattleEffects(ITEMEFFECT_MOVE_END, gBattlerAttacker)) effect = TRUE; gBattleScripting.moveendState++; break; + case MOVEEND_ITEM_THROAT_SPRAY: + if (IsSoundMove(gCurrentMove) + && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + && GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_THROAT_SPRAY + && IsBattlerAlive(gBattlerAttacker) + && IsAnyTargetAffected(gBattlerAttacker) + && CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && !NoAliveMonsForEitherParty()) // Don't activate if battle will end + { + gLastUsedItem = gBattleMons[gBattlerAttacker].item; + gBattleScripting.battler = gBattlerAttacker; + SET_STATCHANGER(STAT_SPATK, 1, FALSE); + effect = TRUE; + BattleScriptCall(BattleScript_AttackerItemStatRaise); + } + gBattleScripting.moveendState++; + break; case MOVEEND_ABILITY_BLOCK: effect = HandleMoveEndAbilityBlock(gBattlerAttacker, gBattlerTarget, gCurrentMove); gBattleScripting.moveendState++; diff --git a/src/battle_util.c b/src/battle_util.c index 10d04161b0..c2fcdc08e9 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -65,7 +65,6 @@ static u32 GetFlingPowerFromItemId(u32 itemId); static void SetRandomMultiHitCounter(); static u32 GetBattlerItemHoldEffectParam(u32 battler, u32 item); static bool32 CanBeInfinitelyConfused(u32 battler); -static bool32 IsAnyTargetAffected(u32 battlerAtk); static bool32 IsNonVolatileStatusBlocked(u32 battlerDef, u32 abilityDef, u32 abilityAffected, const u8 *battleScript, enum FunctionCallOption option); static bool32 CanSleepDueToSleepClause(u32 battlerAtk, u32 battlerDef, enum FunctionCallOption option); @@ -6501,21 +6500,6 @@ static u8 ItemEffectMoveEnd(u32 battler, enum ItemHoldEffect holdEffect) case HOLD_EFFECT_MIRROR_HERB: effect = TryConsumeMirrorHerb(battler, ITEMEFFECT_NONE); break; - case HOLD_EFFECT_THROAT_SPRAY: - if (IsSoundMove(gCurrentMove) - && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - && IsBattlerAlive(gBattlerAttacker) - && IsAnyTargetAffected(gBattlerAttacker) - && CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN) - && !NoAliveMonsForEitherParty()) // Don't activate if battle will end - { - gLastUsedItem = gBattleMons[gBattlerAttacker].item; - gBattleScripting.battler = gBattlerAttacker; - SET_STATCHANGER(STAT_SPATK, 1, FALSE); - effect = ITEM_STATS_CHANGE; - BattleScriptCall(BattleScript_AttackerItemStatRaise); - } - break; default: break; } @@ -11412,7 +11396,7 @@ bool32 HasWeatherEffect(void) return TRUE; } -static bool32 IsAnyTargetAffected(u32 battlerAtk) +bool32 IsAnyTargetAffected(u32 battlerAtk) { for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++) { From dc6178d07ae5c79e8177d0dda6959a4f4c6c4ecc Mon Sep 17 00:00:00 2001 From: ghostyboyy97 <106448956+ghostyboyy97@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:54:51 -0400 Subject: [PATCH 08/50] fix (choice lock): Gorilla Tactics interactions with choice item removal (#7824) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_script_commands.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 9e1d6cc358..6c4042b3d2 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2861,7 +2861,8 @@ void StealTargetItem(u8 battlerStealer, u8 battlerItem) BtlController_EmitSetMonData(battlerItem, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[battlerItem].item); // remove target item MarkBattlerForControllerExec(battlerItem); - gBattleStruct->choicedMove[battlerItem] = 0; + if (GetBattlerAbility(gBattlerTarget) != ABILITY_GORILLA_TACTICS) + gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE; TrySaveExchangedItem(battlerItem, gLastUsedItem); } @@ -5705,7 +5706,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) gLastUsedItem = gBattleMons[gBattlerTarget].item; gBattleMons[gBattlerTarget].item = 0; if (gBattleMons[gBattlerTarget].ability != ABILITY_GORILLA_TACTICS) - gBattleStruct->choicedMove[gBattlerTarget] = 0; + gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE; CheckSetUnburden(gBattlerTarget); // In Gen 5+, Knock Off removes the target's item rather than rendering it unusable. @@ -12885,8 +12886,10 @@ static void Cmd_tryswapitems(void) BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[gBattlerTarget].item); MarkBattlerForControllerExec(gBattlerTarget); - gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE; - gBattleStruct->choicedMove[gBattlerAttacker] = MOVE_NONE; + if (GetBattlerAbility(gBattlerTarget) != ABILITY_GORILLA_TACTICS) + gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE; + if (GetBattlerAbility(gBattlerTarget) != ABILITY_GORILLA_TACTICS) + gBattleStruct->choicedMove[gBattlerAttacker] = MOVE_NONE; gBattlescriptCurrInstr = cmd->nextInstr; From 2c80b9124b6563ab9e35a6d81bffa466b8cc484d Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Mon, 29 Sep 2025 18:05:13 -0300 Subject: [PATCH 09/50] =?UTF-8?q?Fix=20Debug=20Give=20Pok=C3=A9mon=20(Comp?= =?UTF-8?q?lex)=20with=20duplicate=20moves=20(#7821)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/debug.c | 4 ++++ test/pokemon.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/debug.c b/src/debug.c index a14b0ebe53..99e8ee85f0 100644 --- a/src/debug.c +++ b/src/debug.c @@ -2853,6 +2853,10 @@ static void DebugAction_Give_Pokemon_ComplexCreateMon(u8 taskId) //https://githu //Moves for (i = 0; i < MAX_MON_MOVES; i++) { + // Non-default moveset chosen. Reset moves before setting the chosen moves. + if (moves[0] != MOVE_NONE) + SetMonMoveSlot(&mon, MOVE_NONE, i); + if (moves[i] == MOVE_NONE || moves[i] >= MOVES_COUNT) continue; diff --git a/test/pokemon.c b/test/pokemon.c index 60058407c4..61c9e86c04 100644 --- a/test/pokemon.c +++ b/test/pokemon.c @@ -288,7 +288,7 @@ TEST("givemon [moves]") ZeroPlayerPartyMons(); RUN_OVERWORLD_SCRIPT( - givemon SPECIES_WOBBUFFET, 100, move1=MOVE_SCRATCH, move2=MOVE_SPLASH, move3=MOVE_NONE, move4=MOVE_NONE; + givemon SPECIES_WOBBUFFET, 100, move1=MOVE_SCRATCH, move2=MOVE_SPLASH, move3=MOVE_NONE; ); EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_WOBBUFFET); From affd4f5bdd9aa637edf16bab14fced70c16d7ad2 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:48:23 +0200 Subject: [PATCH 10/50] Adds conversion script for trainers.h (#7663) --- migration_scripts/1.13/convert_trainers.py | 401 +++++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 migration_scripts/1.13/convert_trainers.py diff --git a/migration_scripts/1.13/convert_trainers.py b/migration_scripts/1.13/convert_trainers.py new file mode 100644 index 0000000000..ba48f5465e --- /dev/null +++ b/migration_scripts/1.13/convert_trainers.py @@ -0,0 +1,401 @@ +import re +import sys +import os + +is_blank = re.compile(r'^[ \t]*(//.*)?$') + +begin_party_definition = re.compile(r'struct TrainerMon (\w+)\[\] =') +end_party_definition = re.compile(r'^ },') +begin_pokemon_definition = re.compile(r'^ { *$') +end_pokemon_definition = re.compile(r'^ },? *$') +level_definition = re.compile(r'\.lvl = (\d+)') +species_definition = re.compile(r'\.species = SPECIES_(\w+)') +gender_definition = re.compile(r'\.gender = TRAINER_MON_(\w+)') +nickname_definition = re.compile(r'\.nickname = COMPOUND_STRING\("([^"]+)"\)') +item_definition = re.compile(r'\.heldItem = ITEM_(\w+)') +ball_definition = re.compile(r'\.ball = ITEM_(\w+)') +ability_definition = re.compile(r'\.ability = ABILITY_(\w+)') +friendship_definition = re.compile(r'\.friendship = (\d+)') +shiny_definition = re.compile(r'\.isShiny = (\w+)') +ivs_definition = re.compile(r'\.iv = TRAINER_PARTY_IVS\(([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+)\)') +evs_definition = re.compile(r'\.ev = TRAINER_PARTY_EVS\(([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+)\)') +moves_definition = re.compile(r'\.moves = \{([^}]+)\}') +move_definition = re.compile(r'MOVE_(\w+)') +nature_definition = re.compile(r'NATURE_(\w+)') + +is_trainer_skip = re.compile(r'(const struct Trainer gTrainers\[\] = \{)|(^ \{$)|(\.partySize =)|(\.party = NULL)|(\.mugshotEnabled = TRUE)|(\};)') + +trainer_normal_definition = re.compile(r' \[DIFFICULTY_NORMAL\]\[(TRAINER_\w+)\] =') +trainer_easy_definition = re.compile(r' \[DIFFICULTY_EASY\]\[(TRAINER_\w+)\] =') +trainer_hard_definition = re.compile(r' \[DIFFICULTY_HARD\]\[(TRAINER_\w+)\] =') +end_pokemon_definition = re.compile(r' },') +trainer_class_definition = re.compile(r'\.trainerClass = TRAINER_CLASS_(\w+)') +encounter_music_gender_definition = re.compile(r'\.encounterMusic_gender = (F_TRAINER_FEMALE \| )?TRAINER_ENCOUNTER_MUSIC_(\w+)') +encounter_music_definition = re.compile(r'TRAINER_ENCOUNTER_MUSIC_(\w+)') +trainer_pic_definition = re.compile(r'\.trainerPic = TRAINER_PIC_(\w+)') +trainer_name_definition = re.compile(r'\.trainerName = _\("([^"]*)"\)') +trainer_items_definition = re.compile(r'\.items = \{([^}]*)\}') +trainer_item_definition = re.compile(r'ITEM_(\w+)') +trainer_double_battle_definition = re.compile(r'\.battleType = (\w+)') +trainer_ai_flags_definition = re.compile(r'\.aiFlags = (.*)') +trainer_ai_flag_definition = re.compile(r'AI_FLAG_(\w+)') +trainer_party_definition = re.compile(r'\.party = ') +trainer_mugshot_definition = re.compile(r'\.mugshotColor = MUGSHOT_COLOR_(\w+)') +trainer_starting_status_definition = re.compile(r'\.startingStatus = STARTING_STATUS_(\w+)') + +# NOTE: These are just for aesthetics, the Pokemon would still compile +# without them. +species_replacements = { + "CHIEN_PAO": "Chien-Pao", + "CHI_YU": "Chi-Yu", + "HAKAMO_O": "Hakamo-o", + "HO_OH": "Ho-Oh", + "JANGMO_O": "Jangmo-o", + "KOMMO_O": "Kommo-o", + "PORYGON_Z": "Porygon-Z", + "ROTOM_": "Rotom-", + "TING_LU": "Ting-Lu", + "TYPE_NULL": "Type: Null", + "WO_CHIEN": "Wo-Chien", + + "_ALOLAN": "-Alola", + "_AQUA_BREED": "-Aqua", + "_BATTLE_BOND": "-Bond", + "_BLAZE_BREED": "-Blaze", + "_CAP": "", + "_CLOAK": "", + "_COMBAT_BREED": "-Combat", + "_CROWED_SHIELD": "-Crowned", + "_CROWED_SWORD": "-Crowned", + "_DRIVE": "", + "_EAST_SEA": "-East", + "_FAMILY_OF_FOUR": "-Four", + "_FEMALE": "-F", + "_FLOWER": "", + "_GALARIAN": "-Galar", + "_GIGANTAMAX": "-Gmax", + "_HISUIAN": "-Hisui", + "_ICE_RIDER": "-Ice", + "_NOICE_FACE": "-Noice", + "_ORIGIN": "-Origin", + "_ORIGINAL_COLOR": "-Original", + "_PALDEAN": "-Paldea", + "_PLUMAGE": "", + "_POKE_BALL": "-Pokeball", + "_SHADOW_RIDER": "-Shadow", + "_STRIKE_STYLE": "-Style", + "_TOTEM": "-Totem", + "_ZEN_MODE": "-Zen", +} + +class_fixups = { + "Rs": "RS", +} + +pic_fixups = { + "Rs": "RS", +} + +pokemon_attribute_order = ['Level', 'Ability', 'IVs', 'EVs', 'Happiness', 'Shiny', 'Ball'] + +class Pokemon: + def __init__(self): + self.nickname = None + self.species = None + self.gender = None + self.item = None + self.nature = None + self.attributes = {} + self.attributes['IVs'] = "0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe" + self.moves = [] + + +class Trainer: + def __init__(self, id_): + self.id = id_ + self.class_ = None + self.encounter_music = None + self.gender = None + self.pic = None + self.name = None + self.items = [] + self.double_battle = None + self.ai_flags = None + self.mugshot = None + self.starting_status = None + self.party = None + self.difficulty = None + + +def write_tutorial(output): + output.write('/*\n') + output.write('Trainers and their parties defined with Competetive Syntax.\n') + output.write('Compatible with Pokemon Showdown exports.\n') + output.write('https://github.com/smogon/pokemon-showdown/blob/master/sim/TEAMS.md\n') + output.write('\n') + output.write('A trainer specification starts with "=== TRAINER_XXXX ==="\n') + output.write('and includes everything until the next line that starts with "==="\n') + output.write('or the file ends.\n') + output.write('\n') + output.write('A blank line is required between the trainer and their Pokemon\n') + output.write('and between their Pokemon.\n') + output.write('TRAINER_XXXX is how the trainer is referred to within code.\n') + output.write('Fields with description and/or example of usage\n') + output.write('Required fields for trainers:\n') + output.write(' - Name\n') + output.write(' - Pic\n') + output.write('Optional (but still recommended) fields for trainers:\n') + output.write(' - Class (if not specified, PkMn Trainer will be used)\n') + output.write(' - Gender (Male/Female, affects random gender weights of party if not specified)\n') + output.write(' - Music\n') + output.write(' - Items (Some Item / Another Item / Third Item)\n') + output.write(' (Can also be specified with ITEM_SOME_ITEM)\n') + output.write(' - Battle Type (Singles / Doubles, defaults to Singles)\n') + output.write(' - AI (Ai Flag / Another Flag / Third Flag / ...\n') + output.write(' see "constants/battle_ai.h" for all flags)\n') + output.write(' - Mugshot (enable Mugshots during battle transition\n') + output.write(' set to one of Purple, Green, Pink, Blue or Yellow)\n') + output.write(' - Starting Status (see include/constants/battle.h for values)\n') + output.write('\n') + output.write('Pokemon are then specified using the Showdown Export format.\n') + output.write("If a field is not specified, it will use it's default value.\n") + output.write("\n") + output.write('Required fields for Pokemon:\n') + output.write(' - Species (Either as SPECIES_ABRA or Abra)\n') + output.write(' This line also specifies Gender, Nickname and Held item.\n') + output.write(' Alfred (Abra) (M) @ Eviolite\n') + output.write(' Roberta (SPECIES_ABRA) (F) @ ITEM_CHOICE_SPECS\n') + output.write(' Both lines are valid. Gender (M) or (F) must use a capital letter.\n') + output.write(' Nickname length is limited to 10 characters using standard letters.\n') + output.write(" With narrow font it's increased to 12. Longer strings will be silently shortened.\n") + output.write('\n') + output.write('Optional fields for Pokemon:\n') + output.write(' - Level (Number between 1 and 100, defaults to 100)\n') + output.write(' - Ability (Ability Name or ABILITY_ABILITY_NAME)\n') + output.write(' - IVs (0 HP / 1 Atk / 2 Def / 3 SpA / 4 SpD / 5 Spe, defaults to all 31)\n') + output.write(' (Order does not matter)\n') + output.write(' - EVs (252 HP / 128 Spe / 48 Def, defaults to all 0, is not capped at 512 total)\n') + output.write(' (Order does not matter)\n') + output.write(' - Ball (Poke Ball or ITEM_POKE_BALL, defaults to Poke Ball)\n') + output.write(' - Happiness (Number between 1 and 255)\n') + output.write(' - Nature (Rash or NATURE_RASH, defaults to Hardy)\n') + output.write(' - Shiny (Yes/No, defaults to No)\n') + output.write(' - Dynamax Level (Number between 0 and 10, default 10, also sets "shouldDynamax" to True)\n') + output.write(' - Gigantamax (Yes/No, sets to Gigantamax factor)\n') + output.write(' (doesn\'t do anything to Pokemon without a Gigantamax form, also sets "shouldDynamax" to True)\n') + output.write(' - Tera Type (Set to a Type, either Fire or TYPE_FIRE, also sets "shouldTerastal" to True)\n') + output.write('Moves are defined with a - (dash) followed by a single space, then the move name.\n') + output.write('Either "- Tackle" or "- MOVE_TACKLE" works. One move per line.\n') + output.write('Moves have to be the last lines of a Pokemon.\n') + output.write('If no moves are specified, the Pokemon will use the last 4 moves it learns\n') + output.write('through levelup at its level.\n') + output.write("\n") + output.write('Default IVs and Level can be changed in the "main" function of tools/trainerproc/main.c\n') + output.write("\n") + output.write('This file is processed with a custom preprocessor.\n') + output.write('*/\n') + output.write("\n") + output.write('/*\n') + output.write('Comments can be added as C comment blocks\n') + output.write('// cannot be used as comments\n') + output.write('*/\n') + output.write("\n") + output.write('/*Comments can also be on a single line*/\n') + output.write("\n") + output.write("\n") + + + +def write_to_file(trainer, output): + output.write(f'=== {trainer.id} ===\n') + output.write(f'Name: {trainer.name}\n') + output.write(f'Class: {trainer.class_}\n') + output.write(f'Pic: {trainer.pic}\n') + output.write(f'Gender: {trainer.gender}\n') + output.write(f'Music: {trainer.encounter_music}\n') + if len(trainer.items) > 0: + output.write(f'Items: {trainer.items}\n') + output.write(f'Battle Type: {trainer.double_battle}\n') + if trainer.ai_flags is not None: + output.write(f'AI: {trainer.ai_flags}\n') + if trainer.mugshot: + output.write(f'Mugshot: {trainer.mugshot}\n') + if trainer.difficulty is not None: + output.write(f'Difficulty: {trainer.difficulty}\n') + + output.write(f'\n') + + for pokemon in trainer.party: + if pokemon.species is None: + continue + if pokemon.item is not None: + output.write(f'{pokemon.species} @ {pokemon.item}\n') + else: + output.write(f'{pokemon.species}\n') + # for key in pokemon.attributes: + for key in pokemon_attribute_order: + if key in pokemon.attributes: + output.write(f'{key}: {pokemon.attributes[key]}\n') + if pokemon.nature: + output.write(f'Nature: {pokemon.nature}\n') + for move in pokemon.moves: + output.write(f'- {move}\n') + output.write(f'\n') + + +def parse_trainers(content, output): + newlines = 0 + trainer = None + pokemon = None + party = [] + moves = [] + + write_tutorial(output) + + for line_no, line in enumerate(content, 1): + try: + line = line[:-1] + + # Trainer defition + if m := trainer_normal_definition.search(line): + if trainer is not None: + trainer.party = party + write_to_file(trainer, output) + trainer = None + party = [] + [id_] = m.groups() + trainer = Trainer(id_) + trainer.difficulty = None + trainer.gender = 'Male' + elif m := trainer_easy_definition.search(line): + if trainer is not None: + trainer.party = party + write_to_file(trainer, output) + trainer = None + party = [] + [id_] = m.groups() + trainer = Trainer(id_) + trainer.difficulty = "Easy" + trainer.gender = 'Male' + elif m := trainer_hard_definition.search(line): + if trainer is not None: + trainer.party = party + write_to_file(trainer, output) + trainer = None + party = [] + [id_] = m.groups() + trainer = Trainer(id_) + trainer.difficulty = "Hard" + trainer.gender = 'Male' + elif m := trainer_class_definition.search(line): + [class_] = m.groups() + class_ = class_.replace("_", " ").title() + for match, replacement in class_fixups.items(): + class_ = class_.replace(match, replacement) + trainer.class_ = class_ + elif m := encounter_music_gender_definition.search(line): + [is_female, music] = m.groups() + trainer.gender = 'Female' if is_female else 'Male' + trainer.encounter_music = music.replace("_", " ").title() + elif m := encounter_music_definition.search(line): + [music] = m.groups() + trainer.encounter_music = music.replace("_", " ").title() + elif "F_TRAINER_FEMALE" in line: + trainer.gender = 'Female' + elif m := trainer_pic_definition.search(line): + [pic] = m.groups() + pic = pic.replace("_", " ").title() + for match, replacement in pic_fixups.items(): + pic = pic.replace(match, replacement) + trainer.pic = pic + elif m := trainer_name_definition.search(line): + [name] = m.groups() + trainer.name = name + elif m := trainer_items_definition.search(line): + [items] = m.groups() + trainer.items = " / ".join(item.replace("_", " ").title() for item in trainer_item_definition.findall(items) if item != "NONE") + elif m := trainer_double_battle_definition.search(line): + [double_battle] = m.groups() + if double_battle == 'TRAINER_BATTLE_TYPE_DOUBLES': + trainer.double_battle = "Doubles" + elif double_battle == 'TRAINER_BATTLE_TYPE_SINGLES': + trainer.double_battle = "Singles" + elif m := trainer_ai_flags_definition.search(line): + [ai_flags] = m.groups() + trainer.ai_flags = " / ".join(ai_flag.replace("_", " ").title() for ai_flag in trainer_ai_flag_definition.findall(ai_flags)) + elif m := trainer_mugshot_definition.search(line): + [color] = m.groups() + trainer.mugshot = color.title() + elif m := trainer_starting_status_definition.search(line): + [starting_status] = m.groups() + trainer.starting_status = starting_status.replace("_", " ").title() + elif m := trainer_party_definition.search(line): + pokemon = Pokemon() + + # Party mons + elif end_pokemon_definition.search(line): + party.append(pokemon) + pokemon = Pokemon() + elif m := level_definition.search(line): + [level] = m.groups() + pokemon.attributes['Level'] = level + elif m := species_definition.search(line): + [species_] = m.groups() + for match, replacement in species_replacements.items(): + species_ = species_.replace(match, replacement) + pokemon.species = species_.replace("_", " ").title() + elif m := gender_definition.search(line): + [gender_] = m.groups() + if gender_ == 'MALE': + pokemon.gender = 'M' + elif gender_ == 'FEMALE': + pokemon.gender = 'F' + elif m := nickname_definition.search(line): + [nickname] = m.groups() + pokemon.nickname = nickname + elif m := item_definition.search(line): + [item_] = m.groups() + pokemon.item = item_.replace("_", " ").title() + elif m := ball_definition.search(line): + [ball] = m.groups() + pokemon.attributes['Ball'] = ball.replace("_", " ").title() + elif m := ability_definition.search(line): + [ability] = m.groups() + pokemon.attributes['Ability'] = ability.replace("_", " ").title() + elif m := friendship_definition.search(line): + [friendship] = m.groups() + pokemon.attributes['Happiness'] = friendship + elif m := shiny_definition.search(line): + [shiny] = m.groups() + if shiny == 'TRUE': + pokemon.attributes['Shiny'] = 'Yes' + elif shiny == 'FALSE': + pokemon.attributes['Shiny'] = 'No' + elif m := ivs_definition.search(line): + [hp, attack, defense, speed, special_attack, special_defense] = [stat.strip() for stat in m.groups()] + stats = {"HP": hp, "Atk": attack, "Def": defense, "SpA": special_attack, "SpD": special_defense, "Spe": speed} + pokemon.attributes['IVs'] = ' / '.join(f"{value} {key}" for key, value in stats.items()) + elif m := evs_definition.search(line): + [hp, attack, defense, speed, special_attack, special_defense] = [stat.strip() for stat in m.groups()] + stats = {"HP": hp, "Atk": attack, "Def": defense, "SpA": special_attack, "SpD": special_defense, "Spe": speed} + pokemon.attributes['EVs'] = ' / '.join(f"{value} {key}" for key, value in stats.items() if value != '0') + elif m := move_definition.search(line): + [move] = m.groups() + pokemon.moves.append(move.replace("_", " ").title()) + elif m := nature_definition.search(line): + [nature] = m.groups() + pokemon.nature = nature.replace("_", " ").title() + + except Exception as e: + print(f"{line_no}: {e}") + +if __name__ == '__main__': + try: + [argv0, trainers_in_path, out_path] = sys.argv + except: + print(f"usage: python3 {sys.argv[0]} ") + print("trainers.h path: src/data/trainers.h") + print("trainers.party output path: src/data/trainers.party") + else: + with open(trainers_in_path, "r") as source, open(out_path, 'w') as output: + parse_trainers(source, output) From 42c990ed291b1bdcc1661678fb9728f7f6118621 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:50:22 +0200 Subject: [PATCH 11/50] Fixes encore random target for gen5+ (#7800) --- src/battle_util.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/battle_util.c b/src/battle_util.c index c2fcdc08e9..77bed472d1 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -415,7 +415,8 @@ void HandleAction_UseMove(void) { gCurrentMove = gChosenMove = gDisableStructs[gBattlerAttacker].encoredMove; gCurrMovePos = gChosenMovePos = gDisableStructs[gBattlerAttacker].encoredMovePos; - gBattleStruct->moveTarget[gBattlerAttacker] = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); + if (GetGenConfig(GEN_CONFIG_ENCORE_TARGET) < GEN_5) + gBattleStruct->moveTarget[gBattlerAttacker] = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); } // check if the encored move wasn't overwritten else if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].encoredMove != MOVE_NONE From 2231b2fdcaef0f20131b90ee7f52a7fab78973f5 Mon Sep 17 00:00:00 2001 From: spindrift64 <102487911+spindrift64@users.noreply.github.com> Date: Tue, 30 Sep 2025 19:07:23 +0200 Subject: [PATCH 12/50] Immunity abilities trigger on turn 0 (leads) (#7814) --- data/battle_scripts_1.s | 7 ++ include/battle_scripts.h | 1 + include/battle_util.h | 2 + src/battle_main.c | 2 + src/battle_script_commands.c | 10 +- src/battle_util.c | 198 ++++++++++++++++++--------------- test/battle/ability/immunity.c | 16 +++ test/battle/sleep_clause.c | 1 + 8 files changed, 146 insertions(+), 91 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 4248807b01..9d75ac304e 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8285,6 +8285,13 @@ BattleScript_AbilityCuredStatus:: updatestatusicon BS_SCRIPTING return +BattleScript_AbilityCuredStatusEnd3:: + call BattleScript_AbilityPopUp + printstring STRINGID_PKMNSXCUREDITSYPROBLEM + waitmessage B_WAIT_TIME_LONG + updatestatusicon BS_SCRIPTING + end3 + BattleScript_BattlerShookOffTaunt:: call BattleScript_AbilityPopUp printstring STRINGID_PKMNSHOOKOFFTHETAUNT diff --git a/include/battle_scripts.h b/include/battle_scripts.h index d9bb476af7..16a07e971a 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -202,6 +202,7 @@ extern const u8 BattleScript_AbilityStatusEffect[]; extern const u8 BattleScript_SynchronizeActivates[]; extern const u8 BattleScript_NoItemSteal[]; extern const u8 BattleScript_AbilityCuredStatus[]; +extern const u8 BattleScript_AbilityCuredStatusEnd3[]; extern const u8 BattleScript_IgnoresWhileAsleep[]; extern const u8 BattleScript_IgnoresAndUsesRandomMove[]; extern const u8 BattleScript_MoveUsedLoafingAround[]; diff --git a/include/battle_util.h b/include/battle_util.h index fd0fbbac46..f22fd8b0ac 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -55,6 +55,7 @@ enum { ABILITYEFFECT_SWITCH_IN_WEATHER, ABILITYEFFECT_OPPORTUNIST, ABILITYEFFECT_SWITCH_IN_STATUSES, + ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES, }; // For the first argument of ItemBattleEffects, to deteremine which block of item effects to try @@ -307,6 +308,7 @@ struct Pokemon *GetIllusionMonPtr(u32 battler); void ClearIllusionMon(u32 battler); u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pokemon *partnerMon, u32 battler); bool32 SetIllusionMon(struct Pokemon *mon, u32 battler); +u32 TryImmunityAbilityHealStatus(u32 battler, u32 caseID); bool32 ShouldGetStatBadgeBoost(u16 flagId, u32 battler); enum DamageCategory GetBattleMoveCategory(u32 move); void SetDynamicMoveCategory(u32 battlerAtk, u32 battlerDef, u32 move); diff --git a/src/battle_main.c b/src/battle_main.c index 967520f9fb..e787366de8 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3870,6 +3870,8 @@ static void TryDoEventsBeforeFirstTurn(void) return; if (TryClearIllusion(i, ABILITYEFFECT_ON_SWITCHIN)) return; + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES, i, 0, 0, 0) != 0) + return; } gBattleStruct->switchInBattlerCounter = 0; gBattleStruct->eventsBeforeFirstTurnState++; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 6c4042b3d2..85184e803a 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6121,9 +6121,12 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_STATUS_IMMUNITY_ABILITIES: // status immunities - if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, 0, 0, 0, 0)) - effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers - else + for (u16 battler = 0; battler < gBattlersCount; battler++) + { + if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, 0, 0, 0)) + effect = TRUE; + } + if(!effect) gBattleScripting.moveendState++; break; case MOVEEND_SYNCHRONIZE_ATTACKER: // attacker synchronize @@ -17250,6 +17253,7 @@ void BS_SwitchinAbilities(void) AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0); AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0); AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, 0, 0, 0); if (gBattleWeather & B_WEATHER_ANY && HasWeatherEffect()) AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0); diff --git a/src/battle_util.c b/src/battle_util.c index 77bed472d1..ea904ee88c 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5092,94 +5092,12 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_IMMUNITY: - for (battler = 0; battler < gBattlersCount; battler++) - { - switch (GetBattlerAbilityIgnoreMoldBreaker(battler)) - { - case ABILITY_IMMUNITY: - case ABILITY_PASTEL_VEIL: - if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER)) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn); - effect = 1; - } - break; - case ABILITY_OWN_TEMPO: - if (gBattleMons[battler].volatiles.confusionTurns > 0) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_ConfusionJpn); - effect = 2; - } - break; - case ABILITY_LIMBER: - if (gBattleMons[battler].status1 & STATUS1_PARALYSIS) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_ParalysisJpn); - effect = 1; - } - break; - case ABILITY_INSOMNIA: - case ABILITY_VITAL_SPIRIT: - if (gBattleMons[battler].status1 & STATUS1_SLEEP) - { - TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); - gBattleMons[battler].volatiles.nightmare = FALSE; - StringCopy(gBattleTextBuff1, gStatusConditionString_SleepJpn); - effect = 1; - } - break; - case ABILITY_WATER_VEIL: - case ABILITY_WATER_BUBBLE: - case ABILITY_THERMAL_EXCHANGE: - if (gBattleMons[battler].status1 & STATUS1_BURN) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn); - effect = 1; - } - break; - case ABILITY_MAGMA_ARMOR: - if (gBattleMons[battler].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE)) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_IceJpn); - effect = 1; - } - break; - case ABILITY_OBLIVIOUS: - if (gBattleMons[battler].volatiles.infatuation) - effect = 3; - else if (gDisableStructs[battler].tauntTimer != 0) - effect = 4; - break; - } - - if (effect != 0) - { - switch (effect) - { - case 1: // status cleared - gBattleMons[battler].status1 = 0; - BattleScriptCall(BattleScript_AbilityCuredStatus); - break; - case 2: // get rid of confusion - RemoveConfusionStatus(battler); - BattleScriptCall(BattleScript_AbilityCuredStatus); - break; - case 3: // get rid of infatuation - gBattleMons[battler].volatiles.infatuation = 0; - BattleScriptCall(BattleScript_BattlerGotOverItsInfatuation); - break; - case 4: // get rid of taunt - gDisableStructs[battler].tauntTimer = 0; - BattleScriptCall(BattleScript_BattlerShookOffTaunt); - break; - } - - gBattleScripting.battler = gBattlerAbility = battler; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1); - MarkBattlerForControllerExec(battler); - return effect; - } - } + effect = TryImmunityAbilityHealStatus(battler, caseID); + if (effect) + return effect; + break; + case ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES: + effect = TryImmunityAbilityHealStatus(battler, caseID); break; case ABILITYEFFECT_SYNCHRONIZE: if (gLastUsedAbility == ABILITY_SYNCHRONIZE && gBattleStruct->synchronizeMoveEffect != MOVE_EFFECT_NONE) @@ -10404,6 +10322,110 @@ bool32 SetIllusionMon(struct Pokemon *mon, u32 battler) return FALSE; } +u32 TryImmunityAbilityHealStatus(u32 battler, u32 caseID) +{ + u32 effect = 0; + switch (GetBattlerAbilityIgnoreMoldBreaker(battler)) + { + case ABILITY_IMMUNITY: + case ABILITY_PASTEL_VEIL: + if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER)) + { + StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn); + effect = 1; + } + break; + case ABILITY_OWN_TEMPO: + if (gBattleMons[battler].volatiles.confusionTurns > 0) + { + StringCopy(gBattleTextBuff1, gStatusConditionString_ConfusionJpn); + effect = 2; + } + break; + case ABILITY_LIMBER: + if (gBattleMons[battler].status1 & STATUS1_PARALYSIS) + { + StringCopy(gBattleTextBuff1, gStatusConditionString_ParalysisJpn); + effect = 1; + } + break; + case ABILITY_INSOMNIA: + case ABILITY_VITAL_SPIRIT: + if (gBattleMons[battler].status1 & STATUS1_SLEEP) + { + TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); + gBattleMons[battler].volatiles.nightmare = FALSE; + StringCopy(gBattleTextBuff1, gStatusConditionString_SleepJpn); + effect = 1; + } + break; + case ABILITY_WATER_VEIL: + case ABILITY_WATER_BUBBLE: + case ABILITY_THERMAL_EXCHANGE: + if (gBattleMons[battler].status1 & STATUS1_BURN) + { + StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn); + effect = 1; + } + break; + case ABILITY_MAGMA_ARMOR: + if (gBattleMons[battler].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE)) + { + StringCopy(gBattleTextBuff1, gStatusConditionString_IceJpn); + effect = 1; + } + break; + case ABILITY_OBLIVIOUS: + if (gBattleMons[battler].volatiles.infatuation) + effect = 3; + else if (gDisableStructs[battler].tauntTimer != 0) + effect = 4; + break; + } + + if (effect != 0) + { + switch (effect) + { + case 1: // status cleared + gBattleMons[battler].status1 = 0; + if(caseID == ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES) + BattleScriptExecute(BattleScript_AbilityCuredStatusEnd3); + else + BattleScriptCall(BattleScript_AbilityCuredStatus); + break; + case 2: // get rid of confusion + RemoveConfusionStatus(battler); + if(caseID == ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES) + BattleScriptExecute(BattleScript_AbilityCuredStatusEnd3); + else + BattleScriptCall(BattleScript_AbilityCuredStatus); + break; + case 3: // get rid of infatuation + gBattleMons[battler].volatiles.infatuation = 0; + if(caseID == ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES) + BattleScriptExecute(BattleScript_AbilityCuredStatusEnd3); + else + BattleScriptCall(BattleScript_AbilityCuredStatus); + break; + case 4: // get rid of taunt + gDisableStructs[battler].tauntTimer = 0; + if(caseID == ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES) + BattleScriptExecute(BattleScript_AbilityCuredStatusEnd3); + else + BattleScriptCall(BattleScript_AbilityCuredStatus); + break; + } + + gBattleScripting.battler = gBattlerAbility = battler; + BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1); + MarkBattlerForControllerExec(battler); + return effect; + } + + return 0; +} + bool32 ShouldGetStatBadgeBoost(u16 badgeFlag, u32 battler) { if (B_BADGE_BOOST == GEN_3 && badgeFlag != 0) diff --git a/test/battle/ability/immunity.c b/test/battle/ability/immunity.c index 88ff45d2a4..e18aef667c 100644 --- a/test/battle/ability/immunity.c +++ b/test/battle/ability/immunity.c @@ -63,3 +63,19 @@ SINGLE_BATTLE_TEST("Immunity doesn't prevent Pokémon from being poisoned by Tox NOT HP_BAR(player); } } + +SINGLE_BATTLE_TEST("Immunity cures existing poison on turn 0") +{ + GIVEN { + PLAYER(SPECIES_ZANGOOSE) { + Ability(ABILITY_IMMUNITY); + Status1(STATUS1_POISON); + } + OPPONENT(SPECIES_WOBBUFFET); + } SCENE { + ABILITY_POPUP(player, ABILITY_IMMUNITY); + TURN { MOVE(player, MOVE_SPLASH); } + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} diff --git a/test/battle/sleep_clause.c b/test/battle/sleep_clause.c index 73f419289c..3b8999f668 100644 --- a/test/battle/sleep_clause.c +++ b/test/battle/sleep_clause.c @@ -1075,6 +1075,7 @@ SINGLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mo u32 ability; PARAMETRIZE { ability = ABILITY_VITAL_SPIRIT; } PARAMETRIZE { ability = ABILITY_INSOMNIA; } + GIVEN { FLAG_SET(B_FLAG_SLEEP_CLAUSE); ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); From a54b343948807923f814e703b841ba607a7c63bd Mon Sep 17 00:00:00 2001 From: Hedara Date: Wed, 1 Oct 2025 11:09:29 +0200 Subject: [PATCH 13/50] Expansion 1.13.2 release version --- .../ISSUE_TEMPLATE/01_battle_engine_bugs.yaml | 3 +- .../ISSUE_TEMPLATE/02_battle_ai_issues.yaml | 3 +- .github/ISSUE_TEMPLATE/04_other_errors.yaml | 3 +- README.md | 2 +- docs/SUMMARY.md | 1 + docs/changelogs/1.13.x/1.13.2.md | 123 ++++++++++++++++++ include/constants/expansion.h | 4 +- 7 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 docs/changelogs/1.13.x/1.13.2.md diff --git a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml index a06bfb5247..56368ef27c 100644 --- a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml +++ b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml @@ -43,9 +43,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using? options: - - 1.13.1 (Latest release) + - 1.13.2 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.13.1 - 1.13.0 - 1.12.3 - 1.12.2 diff --git a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml index 505cd5e6c4..06c55598d6 100644 --- a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml +++ b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml @@ -43,9 +43,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using? options: - - 1.13.1 (Latest release) + - 1.13.2 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.13.1 - 1.13.0 - 1.12.3 - 1.12.2 diff --git a/.github/ISSUE_TEMPLATE/04_other_errors.yaml b/.github/ISSUE_TEMPLATE/04_other_errors.yaml index 4abed5dbb5..56cfecf33a 100644 --- a/.github/ISSUE_TEMPLATE/04_other_errors.yaml +++ b/.github/ISSUE_TEMPLATE/04_other_errors.yaml @@ -43,9 +43,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using? options: - - 1.13.1 (Latest release) + - 1.13.2 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.13.1 - 1.13.0 - 1.12.3 - 1.12.2 diff --git a/README.md b/README.md index 21de8f622d..8f9088a6e8 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you use **`pokeemerald-expansion`**, please credit **RHH (Rom Hacking Hideout)**. Optionally, include the version number for clarity. ``` -Based off RHH's pokeemerald-expansion 1.13.1 https://github.com/rh-hideout/pokeemerald-expansion/ +Based off RHH's pokeemerald-expansion 1.13.2 https://github.com/rh-hideout/pokeemerald-expansion/ ``` Please consider [crediting all contributors](CREDITS.md) involved in the project! diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 8fd400844d..c1e6a78752 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -20,6 +20,7 @@ - [Day/Night System FAQ](tutorials/dns.md) - [Changelog](./CHANGELOG.md) - [1.13.x]() + - [Version 1.13.2](changelogs/1.13.x/1.13.2.md) - [Version 1.13.1](changelogs/1.13.x/1.13.1.md) - [Version 1.13.0](changelogs/1.13.x/1.13.0.md) - [1.12.x]() diff --git a/docs/changelogs/1.13.x/1.13.2.md b/docs/changelogs/1.13.x/1.13.2.md new file mode 100644 index 0000000000..6cd2249378 --- /dev/null +++ b/docs/changelogs/1.13.x/1.13.2.md @@ -0,0 +1,123 @@ +```md +## How to update +- If you haven't set up a remote, run the command `git remote add RHH https://github.com/rh-hideout/pokeemerald-expansion`. +- Once you have your remote set up, run the command `git pull RHH expansion/1.13.2 +`. +``` + + +## 🧬 General 🧬 +### Changed +* Removed superflous palette compression check by @hedara90 in [#7718](https://github.com/rh-hideout/pokeemerald-expansion/pull/7718) +* update of sv.json to consider newest DLC changes by @wiz1989 in [#7672](https://github.com/rh-hideout/pokeemerald-expansion/pull/7672) +* Adjusted line break substring breaking by @hedara90 in [#7789](https://github.com/rh-hideout/pokeemerald-expansion/pull/7789) +* Fixup add-new-trainer-front-pic tutorial. by @GraionDilach in [#7802](https://github.com/rh-hideout/pokeemerald-expansion/pull/7802) +* Adds conversion script for trainers.h by @AlexOn1ine in [#7663](https://github.com/rh-hideout/pokeemerald-expansion/pull/7663) + +### Fixed +* Fix bug with IF_GENDER evolution condition by @FosterProgramming in [#7749](https://github.com/rh-hideout/pokeemerald-expansion/pull/7749) +* Pokemon storage moving items bugfix by @FosterProgramming in [#7763](https://github.com/rh-hideout/pokeemerald-expansion/pull/7763) +* Fix catch bug introduced in #7774 by @FosterProgramming in [#7782](https://github.com/rh-hideout/pokeemerald-expansion/pull/7782) +* Fix Party Menu move select name width by @AsparagusEduardo in [#7820](https://github.com/rh-hideout/pokeemerald-expansion/pull/7820) +* Fix Debug Give Pokémon (Complex) with duplicate moves by @AsparagusEduardo in [#7821](https://github.com/rh-hideout/pokeemerald-expansion/pull/7821) + +## 🗺️ Overworld 🗺️ +### Changed +* Added missing `LOCALID_NONE` by @estellarc in [#7783](https://github.com/rh-hideout/pokeemerald-expansion/pull/7783) + +### Fixed +* Fix TRAINER_TYPE_SEE_ALL_DIRECTIONS by @DizzyEggg in [#7779](https://github.com/rh-hideout/pokeemerald-expansion/pull/7779) + +## 🐉 Pokémon 🐉 +### Fixed +* fix: seen flags for first mon in enemy party by @khbsd in [#7791](https://github.com/rh-hideout/pokeemerald-expansion/pull/7791) + +## ⚔️ Battle General ⚔️ +### Fixed +* Fix most failed tests with `GEN_LATEST` = `GEN_7` by @AsparagusEduardo in [#7688](https://github.com/rh-hideout/pokeemerald-expansion/pull/7688) +* Fixes Sweet Veil not protecting sleep from Yawn status by @AlexOn1ine in [#7704](https://github.com/rh-hideout/pokeemerald-expansion/pull/7704) +* Fix incorrect wrap turn amount by @AlexOn1ine in [#7667](https://github.com/rh-hideout/pokeemerald-expansion/pull/7667) +* Fixes Rage Fist for gen7 Disguise by @AlexOn1ine in [#7692](https://github.com/rh-hideout/pokeemerald-expansion/pull/7692) +* Fixes Intrepid Sword/Dauntless Shield boosting after entering while at max stats by @PhallenTree in [#7716](https://github.com/rh-hideout/pokeemerald-expansion/pull/7716) +* Fixes incorrect ending for some scripts by @AlexOn1ine in [#7691](https://github.com/rh-hideout/pokeemerald-expansion/pull/7691) +* Fixes Uproar not waking up mons by @AlexOn1ine in [#7714](https://github.com/rh-hideout/pokeemerald-expansion/pull/7714) +* Fixes Endure and Eject Pack issues by @AlexOn1ine in [#7687](https://github.com/rh-hideout/pokeemerald-expansion/pull/7687) +* Fix Beak Blast burning fire types by @hedara90 in [#7740](https://github.com/rh-hideout/pokeemerald-expansion/pull/7740) +* Fixes Recharge not actually being removed when recharge turn occurs by @PhallenTree in [#7744](https://github.com/rh-hideout/pokeemerald-expansion/pull/7744) +* Bugfixes Batch by @AlexOn1ine in [#7739](https://github.com/rh-hideout/pokeemerald-expansion/pull/7739) +* Fixes Beat Up incorrect slots usage by @AlexOn1ine in [#7741](https://github.com/rh-hideout/pokeemerald-expansion/pull/7741) +* Fixes Mycelium Might and Lagging Tail adjusting prio incorrectly by @AlexOn1ine in [#7742](https://github.com/rh-hideout/pokeemerald-expansion/pull/7742) +* Wrong argument passed down by @AlexOn1ine in [#7751](https://github.com/rh-hideout/pokeemerald-expansion/pull/7751) +* Fixed Ball Fetch Ability by @bassforte123 in [#7764](https://github.com/rh-hideout/pokeemerald-expansion/pull/7764) +* Fixes Flower Shield affecting semi-invulnerable mons by @AlexOn1ine in [#7766](https://github.com/rh-hideout/pokeemerald-expansion/pull/7766) +* Fixes Helping Hand boosts not stacking with each other by @PhallenTree in [#7775](https://github.com/rh-hideout/pokeemerald-expansion/pull/7775) +* Fixes OHKO moves calculating accuracy twice by @AlexOn1ine in [#7785](https://github.com/rh-hideout/pokeemerald-expansion/pull/7785) +* Fixes Instructed moves looking at the wrong turn order number by @PhallenTree in [#7788](https://github.com/rh-hideout/pokeemerald-expansion/pull/7788) +* Fix Flame Burst timeout if primary target is fainted by @hedara90 in [#7793](https://github.com/rh-hideout/pokeemerald-expansion/pull/7793) +* Fixes Leppa Berry timings by @AlexOn1ine in [#7787](https://github.com/rh-hideout/pokeemerald-expansion/pull/7787) +* Fixes Effects activating when move wasn't successful by @AlexOn1ine in [#7803](https://github.com/rh-hideout/pokeemerald-expansion/pull/7803) +* Fixes Throat Spray being blocked by Sheer Force by @AlexOn1ine in [#7808](https://github.com/rh-hideout/pokeemerald-expansion/pull/7808) +* Fixes inaccurate save / restore in Fling script by @AlexOn1ine in [#7811](https://github.com/rh-hideout/pokeemerald-expansion/pull/7811) +* Fix test exit prints for stored battlers by @AlexOn1ine in [#7807](https://github.com/rh-hideout/pokeemerald-expansion/pull/7807) +* Fixes EndTurn Eject Pack by @AlexOn1ine in [#7813](https://github.com/rh-hideout/pokeemerald-expansion/pull/7813) +* Fix Battle Frontier using Strange Balls by @AsparagusEduardo in [#7823](https://github.com/rh-hideout/pokeemerald-expansion/pull/7823) +* Fix Throat Spray activating multiply times by @AlexOn1ine in [#7818](https://github.com/rh-hideout/pokeemerald-expansion/pull/7818) +* fix (choice lock): Gorilla Tactics interactions with choice item removal by @ghostyboyy97 in [#7824](https://github.com/rh-hideout/pokeemerald-expansion/pull/7824) + - Fixed interactions with choice items and Gorilla Tactics both present when choice item is removed by a thief effect or item swap effect. +* Fixes encore random target for gen5+ by @AlexOn1ine in [#7800](https://github.com/rh-hideout/pokeemerald-expansion/pull/7800) + +## 🤹 Moves 🤹 +### Changed +* Initial Lash Out tests by @grintoul1 in [#7769](https://github.com/rh-hideout/pokeemerald-expansion/pull/7769) + +### Fixed +* Fix Salt Cure in double battles by @Bassoonian in [#7797](https://github.com/rh-hideout/pokeemerald-expansion/pull/7797) + +## 🎭 Abilities 🎭 +### Fixed +* Fix for Levitate and Mold Breaker being seen correctly by switch AI, with Levitate tests by @grintoul1 in [#7748](https://github.com/rh-hideout/pokeemerald-expansion/pull/7748) +* Fix Forecast and Flower Gift corruption by @Bassoonian in [#7796](https://github.com/rh-hideout/pokeemerald-expansion/pull/7796) +* Immunity abilities trigger on turn 0 (leads) by @spindrift64 in [#7814](https://github.com/rh-hideout/pokeemerald-expansion/pull/7814) + +## 🤖 Battle AI 🤖 +### Changed +* Tidy up CanTargetFaintAiWithMod and CanTargetMoveFaintAi by @grintoul1 in [#7693](https://github.com/rh-hideout/pokeemerald-expansion/pull/7693) +* Doubles AI: Trick Room timer fix and test for DOUBLE_TRICK_ROOM_ON_LAST_TURN_CHANCE by @grintoul1 in [#7622](https://github.com/rh-hideout/pokeemerald-expansion/pull/7622) + +### Fixed +* Toxic thread uses light screen's scoring.... by @surskitty in [#7674](https://github.com/rh-hideout/pokeemerald-expansion/pull/7674) +* Fix most failed and assume fail tests with `GEN_LATEST` = `GEN_6` by @AsparagusEduardo in [#7696](https://github.com/rh-hideout/pokeemerald-expansion/pull/7696) +* Fix for Levitate and Mold Breaker being seen correctly by switch AI, with Levitate tests by @grintoul1 in [#7748](https://github.com/rh-hideout/pokeemerald-expansion/pull/7748) + +## 🧹 Other Cleanup 🧹 +* Tidy up CanTargetFaintAiWithMod and CanTargetMoveFaintAi by @grintoul1 in [#7693](https://github.com/rh-hideout/pokeemerald-expansion/pull/7693) +* Removed superflous palette compression check by @hedara90 in [#7718](https://github.com/rh-hideout/pokeemerald-expansion/pull/7718) +* Fix failing test for B_PREFERRED_ICE_WEATHER = B_ICE_WEATHER_SNOW by @phexmiau in [#7755](https://github.com/rh-hideout/pokeemerald-expansion/pull/7755) +* Adjusted line break substring breaking by @hedara90 in [#7789](https://github.com/rh-hideout/pokeemerald-expansion/pull/7789) +* Added missing `LOCALID_NONE` by @estellarc in [#7783](https://github.com/rh-hideout/pokeemerald-expansion/pull/7783) + +## 🧪 Test Runner 🧪 +### Changed +* Add tests for Filter, Solid Rock and Prism Armor by @hedara90 in [#7734](https://github.com/rh-hideout/pokeemerald-expansion/pull/7734) +* Fix failing test for B_PREFERRED_ICE_WEATHER = B_ICE_WEATHER_SNOW by @phexmiau in [#7755](https://github.com/rh-hideout/pokeemerald-expansion/pull/7755) +* Initial Lash Out tests by @grintoul1 in [#7769](https://github.com/rh-hideout/pokeemerald-expansion/pull/7769) +* Improve how test involving ball throw work by @FosterProgramming in [#7774](https://github.com/rh-hideout/pokeemerald-expansion/pull/7774) + +### Fixed +* Fix EWRAM_INIT in tests and add a default state to test runner main loop by @hedara90 in [#7699](https://github.com/rh-hideout/pokeemerald-expansion/pull/7699) +* Fix most failed and assume fail tests with `GEN_LATEST` = `GEN_6` by @AsparagusEduardo in [#7696](https://github.com/rh-hideout/pokeemerald-expansion/pull/7696) +* Fix for Levitate and Mold Breaker being seen correctly by switch AI, with Levitate tests by @grintoul1 in [#7748](https://github.com/rh-hideout/pokeemerald-expansion/pull/7748) +* Fix Big Root tests by @hedara90 in [#7817](https://github.com/rh-hideout/pokeemerald-expansion/pull/7817) + +## 📚 Documentation 📚 +* Fixup add-new-trainer-front-pic tutorial. by @GraionDilach in [#7802](https://github.com/rh-hideout/pokeemerald-expansion/pull/7802) + +## New Contributors +* @phexmiau made their first contribution in [#7755](https://github.com/rh-hideout/pokeemerald-expansion/pull/7755) +* @ghostyboyy97 made their first contribution in [#7824](https://github.com/rh-hideout/pokeemerald-expansion/pull/7824) + +**Full Changelog**: https://github.com/rh-hideout/pokeemerald-expansion/compare/expansion/1.13.1...expansion/1.13.2 + + + + diff --git a/include/constants/expansion.h b/include/constants/expansion.h index 02234f603c..a9f55de615 100644 --- a/include/constants/expansion.h +++ b/include/constants/expansion.h @@ -1,13 +1,13 @@ #ifndef GUARD_CONSTANTS_EXPANSION_H #define GUARD_CONSTANTS_EXPANSION_H -// Last version: 1.13.1 +// Last version: 1.13.2 #define EXPANSION_VERSION_MAJOR 1 #define EXPANSION_VERSION_MINOR 13 #define EXPANSION_VERSION_PATCH 2 // FALSE if this this version of Expansion is not a tagged commit, i.e. // it contains unreleased changes. -#define EXPANSION_TAGGED_RELEASE FALSE +#define EXPANSION_TAGGED_RELEASE TRUE #endif From d3ba08e0bd5f365738d566db6a69f05649d268a2 Mon Sep 17 00:00:00 2001 From: Hedara Date: Wed, 1 Oct 2025 11:10:00 +0200 Subject: [PATCH 14/50] Start of 1.13.3 cycle --- include/constants/expansion.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/constants/expansion.h b/include/constants/expansion.h index a9f55de615..92973512b2 100644 --- a/include/constants/expansion.h +++ b/include/constants/expansion.h @@ -4,10 +4,10 @@ // Last version: 1.13.2 #define EXPANSION_VERSION_MAJOR 1 #define EXPANSION_VERSION_MINOR 13 -#define EXPANSION_VERSION_PATCH 2 +#define EXPANSION_VERSION_PATCH 3 // FALSE if this this version of Expansion is not a tagged commit, i.e. // it contains unreleased changes. -#define EXPANSION_TAGGED_RELEASE TRUE +#define EXPANSION_TAGGED_RELEASE FALSE #endif From 13f392987a1bcd9ca366281b40ee4c4d7566d305 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 2 Oct 2025 08:27:06 -0300 Subject: [PATCH 15/50] Fix some failed and assume fail tests with `GEN_LATEST` = `GEN_5` (#7735) --- include/constants/generational_changes.h | 4 +++ include/generational_changes.h | 4 +++ src/battle_ai_util.c | 2 +- src/battle_pike.c | 2 +- src/battle_script_commands.c | 4 +-- src/battle_util.c | 8 ++--- test/battle/ability/oblivious.c | 29 ++++++++++++++----- test/battle/ability/overcoat.c | 25 +++++++++++----- test/battle/ability/parental_bond.c | 6 ++-- test/battle/ability/protosynthesis.c | 1 + test/battle/ability/supersweet_syrup.c | 10 +++++++ test/battle/ai/ai_flag_predict_switch.c | 2 +- test/battle/damage_formula.c | 21 ++++++++++++++ test/battle/gimmick/dynamax.c | 2 +- test/battle/gimmick/terastal.c | 8 ++--- test/battle/hold_effect/booster_energy.c | 1 + test/battle/hold_effect/gems.c | 6 ++-- test/battle/move_effect/acrobatics.c | 1 - test/battle/move_effect/dragon_darts.c | 19 +++++------- test/battle/move_effect/fickle_beam.c | 7 ++--- test/battle/move_effect/future_sight.c | 28 +++++++++++------- test/battle/move_effect/instruct.c | 1 + test/battle/move_effect/multi_hit.c | 6 ++-- test/battle/move_effect/powder.c | 6 ++-- test/battle/move_effect/toxic.c | 12 ++++---- test/battle/move_effect_secondary/dire_claw.c | 3 +- test/battle/move_effect_secondary/paralysis.c | 16 +++++++--- .../battle/move_effect_secondary/tri_attack.c | 3 +- test/battle/move_flags/powder.c | 15 ++++++++-- test/battle/status1/paralysis.c | 19 ++++++++++-- test/battle/status1/sleep.c | 18 ++++++++++-- 31 files changed, 203 insertions(+), 86 deletions(-) diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index b365a6ea8f..7a9aed85c3 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -41,6 +41,10 @@ enum GenConfigTag GEN_CONFIG_PRANKSTER_DARK_TYPES, GEN_CONFIG_DESTINY_BOND_FAIL, GEN_CONFIG_POWDER_RAIN, + GEN_CONFIG_POWDER_GRASS, + GEN_CONFIG_OBLIVIOUS_TAUNT, + GEN_CONFIG_TOXIC_NEVER_MISS, + GEN_CONFIG_PARALYZE_ELECTRIC, GEN_CONFIG_COUNT }; diff --git a/include/generational_changes.h b/include/generational_changes.h index a45325caab..30739a14f5 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -44,6 +44,10 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] = [GEN_CONFIG_PRANKSTER_DARK_TYPES] = B_PRANKSTER_DARK_TYPES, [GEN_CONFIG_DESTINY_BOND_FAIL] = B_DESTINY_BOND_FAIL, [GEN_CONFIG_POWDER_RAIN] = B_POWDER_RAIN, + [GEN_CONFIG_POWDER_GRASS] = B_POWDER_GRASS, + [GEN_CONFIG_OBLIVIOUS_TAUNT] = B_OBLIVIOUS_TAUNT, + [GEN_CONFIG_TOXIC_NEVER_MISS] = B_TOXIC_NEVER_MISS, + [GEN_CONFIG_PARALYZE_ELECTRIC] = B_PARALYZE_ELECTRIC, }; #if TESTING diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index bf06a8e6a1..c402ce3d1b 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -534,7 +534,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) bool32 IsAffectedByPowder(u32 battler, u32 ability, enum ItemHoldEffect holdEffect) { if (ability == ABILITY_OVERCOAT - || (B_POWDER_GRASS >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) + || (GetGenConfig(GEN_CONFIG_POWDER_GRASS) >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) || holdEffect == HOLD_EFFECT_SAFETY_GOGGLES) return FALSE; return TRUE; diff --git a/src/battle_pike.c b/src/battle_pike.c index bdc2d1fd1c..8f669ca06f 100644 --- a/src/battle_pike.c +++ b/src/battle_pike.c @@ -862,7 +862,7 @@ static bool8 DoesTypePreventStatus(u16 species, u32 status) break; case STATUS1_PARALYSIS: if (GetSpeciesType(species, 0) == TYPE_GROUND || GetSpeciesType(species, 1) == TYPE_GROUND - || (B_PARALYZE_ELECTRIC >= GEN_6 && (GetSpeciesType(species, 0) == TYPE_ELECTRIC || GetSpeciesType(species, 1) == TYPE_ELECTRIC))) + || (GetGenConfig(GEN_CONFIG_PARALYZE_ELECTRIC) >= GEN_6 && (GetSpeciesType(species, 0) == TYPE_ELECTRIC || GetSpeciesType(species, 1) == TYPE_ELECTRIC))) ret = TRUE; break; case STATUS1_BURN: diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 85184e803a..b6b6305fb8 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1046,7 +1046,7 @@ bool32 IsMovePowderBlocked(u32 battlerAtk, u32 battlerDef, u32 move) if (IsPowderMove(move) && (battlerAtk != battlerDef)) { - if (B_POWDER_GRASS >= GEN_6 + if (GetGenConfig(GEN_CONFIG_POWDER_GRASS) >= GEN_6 && (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || GetBattlerAbility(battlerDef) == ABILITY_OVERCOAT)) { gBattlerAbility = battlerDef; @@ -12765,7 +12765,7 @@ static void Cmd_settaunt(void) { CMD_ARGS(const u8 *failInstr); - if (B_OBLIVIOUS_TAUNT >= GEN_6 && GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS) + if (GetGenConfig(GEN_CONFIG_OBLIVIOUS_TAUNT) >= GEN_6 && GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS) { gBattlescriptCurrInstr = BattleScript_NotAffectedAbilityPopUp; gLastUsedAbility = ABILITY_OBLIVIOUS; diff --git a/src/battle_util.c b/src/battle_util.c index ea904ee88c..07af6d1789 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4689,7 +4689,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_EFFECT_SPORE: { u32 abilityAtk = GetBattlerAbility(gBattlerAttacker); - if ((!IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GRASS) || B_POWDER_GRASS < GEN_6) + if ((GetGenConfig(GEN_CONFIG_POWDER_GRASS) < GEN_6 || !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GRASS)) && abilityAtk != ABILITY_OVERCOAT && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_SAFETY_GOGGLES) { @@ -5616,7 +5616,7 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u { battleScript = BattleScript_AlreadyParalyzed; } - else if (B_PARALYZE_ELECTRIC >= GEN_6 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ELECTRIC)) + else if (GetGenConfig(GEN_CONFIG_PARALYZE_ELECTRIC) >= GEN_6 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ELECTRIC)) { battleScript = BattleScript_NotAffected; } @@ -10378,7 +10378,7 @@ u32 TryImmunityAbilityHealStatus(u32 battler, u32 caseID) case ABILITY_OBLIVIOUS: if (gBattleMons[battler].volatiles.infatuation) effect = 3; - else if (gDisableStructs[battler].tauntTimer != 0) + else if (GetGenConfig(GEN_CONFIG_OBLIVIOUS_TAUNT) >= GEN_6 && gDisableStructs[battler].tauntTimer != 0) effect = 4; break; } @@ -11663,7 +11663,7 @@ bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u u32 nonVolatileStatus = GetMoveNonVolatileStatus(move); if ((gBattleMons[battlerDef].volatiles.lockOn && gDisableStructs[battlerDef].battlerWithSureHit == battlerAtk) - || (B_TOXIC_NEVER_MISS >= GEN_6 && nonVolatileStatus == MOVE_EFFECT_TOXIC && IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) + || (GetGenConfig(GEN_CONFIG_TOXIC_NEVER_MISS) >= GEN_6 && nonVolatileStatus == MOVE_EFFECT_TOXIC && IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) || gBattleMons[battlerDef].volatiles.glaiveRush) { effect = TRUE; diff --git a/test/battle/ability/oblivious.c b/test/battle/ability/oblivious.c index 40506e1967..c160ab423e 100644 --- a/test/battle/ability/oblivious.c +++ b/test/battle/ability/oblivious.c @@ -31,22 +31,35 @@ SINGLE_BATTLE_TEST("Oblivious prevents Captivate") } } -SINGLE_BATTLE_TEST("Oblivious prevents Taunt") +SINGLE_BATTLE_TEST("Oblivious prevents Taunt (Gen6+)") { + u32 gen = 0; + PARAMETRIZE { gen = GEN_5; } + PARAMETRIZE { gen = GEN_6; } GIVEN { + WITH_CONFIG(GEN_CONFIG_OBLIVIOUS_TAUNT, gen); ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT); - ASSUME(B_OBLIVIOUS_TAUNT >= GEN_6); PLAYER(SPECIES_SLOWPOKE) { Ability(ABILITY_OBLIVIOUS); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(opponent, MOVE_TAUNT); } - TURN { MOVE(player, MOVE_SPORE); } + TURN { MOVE(player, MOVE_SPORE, allowed: gen == GEN_6); } } SCENE { - ABILITY_POPUP(player, ABILITY_OBLIVIOUS); - NONE_OF { ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent); } - MESSAGE("It doesn't affect Slowpoke…"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); - ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + if (gen == GEN_6) { + NONE_OF { ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent); } + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + MESSAGE("It doesn't affect Slowpoke…"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + MESSAGE("It doesn't affect Slowpoke…"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + } + } } } diff --git a/test/battle/ability/overcoat.c b/test/battle/ability/overcoat.c index c722d7ac55..6d7b5f2a8d 100644 --- a/test/battle/ability/overcoat.c +++ b/test/battle/ability/overcoat.c @@ -1,25 +1,37 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Overcoat blocks powder and spore moves") +SINGLE_BATTLE_TEST("Overcoat blocks powder and spore moves (Gen6+)") { + u32 gen = 0; + PARAMETRIZE { gen = GEN_5; } + PARAMETRIZE { gen = GEN_6; } GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, gen); ASSUME(IsPowderMove(MOVE_STUN_SPORE)); PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_PINECO) { Ability(ABILITY_OVERCOAT); } } WHEN { TURN { MOVE(player, MOVE_STUN_SPORE); } } SCENE { - ABILITY_POPUP(opponent, ABILITY_OVERCOAT); - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); - MESSAGE("It doesn't affect the opposing Pineco…"); + if (gen == GEN_6) { + ABILITY_POPUP(opponent, ABILITY_OVERCOAT); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + MESSAGE("It doesn't affect the opposing Pineco…"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_OVERCOAT); + MESSAGE("It doesn't affect the opposing Pineco…"); + } + } } } DOUBLE_BATTLE_TEST("Overcoat blocks damage from sandstorm") { GIVEN { - PLAYER(SPECIES_WYNAUT) { Speed(50); } + PLAYER(SPECIES_WYNAUT) { Speed(50); } PLAYER(SPECIES_HELIOLISK) { Speed(40); Ability(ABILITY_SAND_VEIL); } OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_OVERCOAT); } OPPONENT(SPECIES_STARLY) { Speed(20); } @@ -41,7 +53,7 @@ DOUBLE_BATTLE_TEST("Overcoat blocks damage from hail") { GIVEN { ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); - PLAYER(SPECIES_WYNAUT) { Speed(50); Ability(ABILITY_SNOW_CLOAK); } + PLAYER(SPECIES_WYNAUT) { Speed(50); Ability(ABILITY_SNOW_CLOAK); } PLAYER(SPECIES_SOLOSIS) { Speed(40); Ability(ABILITY_RUN_AWAY); } OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_OVERCOAT); } OPPONENT(SPECIES_SNORUNT) { Speed(20); } @@ -73,4 +85,3 @@ SINGLE_BATTLE_TEST("Overcoat blocks Effect Spore's effect") EXPECT_EQ(player->status1, 0); } } - diff --git a/test/battle/ability/parental_bond.c b/test/battle/ability/parental_bond.c index 6c516c7ffc..b82f16cc26 100644 --- a/test/battle/ability/parental_bond.c +++ b/test/battle/ability/parental_bond.c @@ -291,10 +291,10 @@ SINGLE_BATTLE_TEST("Parental Bond Snore strikes twice while asleep") HP_BAR(opponent, captureDamage: &damage[1]); MESSAGE("The Pokémon was hit 2 time(s)!"); } THEN { - if (B_PARENTAL_BOND_DMG == GEN_6) - EXPECT_MUL_EQ(damage[0], Q_4_12(0.5), damage[1]); - else + if (B_PARENTAL_BOND_DMG >= GEN_7) EXPECT_MUL_EQ(damage[0], Q_4_12(0.25), damage[1]); + else + EXPECT_MUL_EQ(damage[0], Q_4_12(0.5), damage[1]); } } diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index 6fd271acb2..9bf6d85f43 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -59,6 +59,7 @@ SINGLE_BATTLE_TEST("Protosynthesis ability pop up activates only once during the u16 turns; GIVEN { + WITH_CONFIG(GEN_CONFIG_ABILITY_WEATHER, GEN_6); PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_PROTOSYNTHESIS); } OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); }; } WHEN { diff --git a/test/battle/ability/supersweet_syrup.c b/test/battle/ability/supersweet_syrup.c index 4ff8c462ba..cd819eda72 100644 --- a/test/battle/ability/supersweet_syrup.c +++ b/test/battle/ability/supersweet_syrup.c @@ -56,11 +56,21 @@ SINGLE_BATTLE_TEST("Supersweet Syrup can not further lower opponents evasion if TURN { MOVE(opponent, MOVE_SWEET_SCENT); } TURN { MOVE(opponent, MOVE_SWEET_SCENT); } TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + if (GetMoveEffect(MOVE_SWEET_SCENT) == EFFECT_EVASION_DOWN) { + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + } TURN { SWITCH(opponent, 1); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + if (GetMoveEffect(MOVE_SWEET_SCENT) == EFFECT_EVASION_DOWN) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + } ABILITY_POPUP(opponent, ABILITY_SUPERSWEET_SYRUP); NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); diff --git a/test/battle/ai/ai_flag_predict_switch.c b/test/battle/ai/ai_flag_predict_switch.c index b64dea488c..8530c49675 100644 --- a/test/battle/ai/ai_flag_predict_switch.c +++ b/test/battle/ai/ai_flag_predict_switch.c @@ -59,7 +59,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_PREDICT_SWITCH: Considers ShouldSwitch and GetMos // Switching in trapper is an advanced feature of ShouldSwitch that requires GetMostSuitableMonToSwitchInto to also return a specific mon; this passing means the AI can use both in prediction PASSES_RANDOMLY(5, 10, RNG_AI_PREDICT_SWITCH); GIVEN { - ASSUME(B_POWDER_GRASS >= GEN_6); + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_6); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PREDICT_SWITCH | AI_FLAG_PREDICT_INCOMING_MON); PLAYER(SPECIES_SKARMORY) { Moves(MOVE_SCRATCH); } PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_ARENA_TRAP); Moves(MOVE_ACROBATICS); } diff --git a/test/battle/damage_formula.c b/test/battle/damage_formula.c index f41f6a2145..4a7e4ec1ef 100644 --- a/test/battle/damage_formula.c +++ b/test/battle/damage_formula.c @@ -101,6 +101,7 @@ SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Marshadow vs Mawile)") PARAMETRIZE { expectedDamage = 123; } GIVEN { ASSUME(GetMoveCategory(MOVE_SPECTRAL_THIEF) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(B_UPDATED_TYPE_MATCHUPS >= GEN_6); // Steel resists Ghost in Gen2-5 PLAYER(SPECIES_MARSHADOW) { Level(100); Attack(286); } OPPONENT(SPECIES_MAWILE) { Level(100); Defense(226); HP(241); } } WHEN { @@ -196,6 +197,7 @@ SINGLE_BATTLE_TEST("Gem boosted Damage calculation") { s16 dmg; s16 expectedDamage; +#if I_GEM_BOOST_POWER >= GEN_6 PARAMETRIZE { expectedDamage = 240; } PARAMETRIZE { expectedDamage = 237; } PARAMETRIZE { expectedDamage = 234; } @@ -212,6 +214,25 @@ SINGLE_BATTLE_TEST("Gem boosted Damage calculation") PARAMETRIZE { expectedDamage = 208; } PARAMETRIZE { expectedDamage = 205; } PARAMETRIZE { expectedDamage = 204; } +#else + KNOWN_FAILING; + PARAMETRIZE { expectedDamage = 273; } + PARAMETRIZE { expectedDamage = 270; } + PARAMETRIZE { expectedDamage = 267; } + PARAMETRIZE { expectedDamage = 264; } + PARAMETRIZE { expectedDamage = 261; } + PARAMETRIZE { expectedDamage = 258; } + PARAMETRIZE { expectedDamage = 256; } + PARAMETRIZE { expectedDamage = 253; } + PARAMETRIZE { expectedDamage = 250; } + PARAMETRIZE { expectedDamage = 247; } + PARAMETRIZE { expectedDamage = 244; } + PARAMETRIZE { expectedDamage = 241; } + PARAMETRIZE { expectedDamage = 240; } + PARAMETRIZE { expectedDamage = 237; } + PARAMETRIZE { expectedDamage = 234; } + PARAMETRIZE { expectedDamage = 231; } +#endif GIVEN { PLAYER(SPECIES_MAKUHITA) { Item(ITEM_FIGHTING_GEM); } OPPONENT(SPECIES_MAKUHITA); diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index 8dfedad83f..bb8814fa42 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -1464,7 +1464,7 @@ DOUBLE_BATTLE_TEST("Dynamax: G-Max Chi Strike boosts allies' crit chance by 1 st { u32 j; GIVEN { - ASSUME(B_CRIT_CHANCE >= GEN_6); + WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, GEN_6); ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_CHI_STRIKE, MOVE_EFFECT_CRIT_PLUS_SIDE)); PLAYER(SPECIES_MACHAMP) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_MACHOP); diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index 56416663ff..37a6d515b3 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -606,14 +606,14 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the Stellar type boosts all moves s16 damage[4]; GIVEN { ASSUME(GetMovePower(MOVE_MEGA_DRAIN) == 40); - ASSUME(GetMovePower(MOVE_BUBBLE) == 40); + ASSUME(GetMovePower(MOVE_WATER_GUN) == 40); PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_MEGA_DRAIN); } TURN { MOVE(player, MOVE_MEGA_DRAIN, gimmick: GIMMICK_TERA); } TURN { MOVE(player, MOVE_MEGA_DRAIN); } - TURN { MOVE(player, MOVE_BUBBLE); } + TURN { MOVE(player, MOVE_WATER_GUN); } } SCENE { // turn 1 MESSAGE("Wobbuffet used Mega Drain!"); @@ -628,8 +628,8 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the Stellar type boosts all moves ANIMATION(ANIM_TYPE_MOVE, MOVE_MEGA_DRAIN, player); HP_BAR(opponent, captureDamage: &damage[2]); // turn 4 - MESSAGE("Wobbuffet used Bubble!"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_BUBBLE, player); + MESSAGE("Wobbuffet used Water Gun!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); HP_BAR(opponent, captureDamage: &damage[3]); } THEN { // The jump from 40 BP to 72 BP (60 * 1.2x) is a 1.8x boost. diff --git a/test/battle/hold_effect/booster_energy.c b/test/battle/hold_effect/booster_energy.c index 41e5fe3b47..a5113ef245 100644 --- a/test/battle/hold_effect/booster_energy.c +++ b/test/battle/hold_effect/booster_energy.c @@ -38,6 +38,7 @@ SINGLE_BATTLE_TEST("Booster Energy will activate Quark Drive after Electric Terr SINGLE_BATTLE_TEST("Booster Energy will activate Protosynthesis after harsh sunlight ends") { GIVEN { + WITH_CONFIG(GEN_CONFIG_ABILITY_WEATHER, GEN_6); PLAYER(SPECIES_RAGING_BOLT) { Attack(100); Defense(100); Speed(100); SpAttack(110); SpDefense(100); Ability(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } OPPONENT(SPECIES_TORKOAL) { Speed(100); Ability(ABILITY_DROUGHT); }; } WHEN { diff --git a/test/battle/hold_effect/gems.c b/test/battle/hold_effect/gems.c index 52c85d7fb5..b2d843e1d5 100644 --- a/test/battle/hold_effect/gems.c +++ b/test/battle/hold_effect/gems.c @@ -32,7 +32,6 @@ SINGLE_BATTLE_TEST("Gem boost is only applied once") s16 normalHit; GIVEN { - ASSUME(I_GEM_BOOST_POWER >= GEN_6); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMAL_GEM); }; OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -46,7 +45,10 @@ SINGLE_BATTLE_TEST("Gem boost is only applied once") ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); HP_BAR(opponent, captureDamage: &normalHit); } THEN { - EXPECT_MUL_EQ(normalHit, Q_4_12(1.3), boostedHit); + if (I_GEM_BOOST_POWER >= GEN_6) + EXPECT_MUL_EQ(normalHit, Q_4_12(1.3), boostedHit); + else + EXPECT_MUL_EQ(normalHit, Q_4_12(1.5), boostedHit); } } diff --git a/test/battle/move_effect/acrobatics.c b/test/battle/move_effect/acrobatics.c index 809b77f948..1229d4e2c5 100644 --- a/test/battle/move_effect/acrobatics.c +++ b/test/battle/move_effect/acrobatics.c @@ -30,7 +30,6 @@ SINGLE_BATTLE_TEST("Acrobatics still doubles in power when Flying Gem is consume PARAMETRIZE { heldItem = ITEM_NONE; } PARAMETRIZE { heldItem = ITEM_FLYING_GEM; } GIVEN { - ASSUME(I_GEM_BOOST_POWER >= GEN_6); ASSUME(gItemsInfo[ITEM_FLYING_GEM].holdEffect == HOLD_EFFECT_GEMS); ASSUME(gItemsInfo[ITEM_FLYING_GEM].secondaryId == TYPE_FLYING); PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/dragon_darts.c b/test/battle/move_effect/dragon_darts.c index 13a165a459..40a9177589 100644 --- a/test/battle/move_effect/dragon_darts.c +++ b/test/battle/move_effect/dragon_darts.c @@ -4,7 +4,6 @@ ASSUMPTIONS { ASSUME(GetMoveEffect(MOVE_DRAGON_DARTS) == EFFECT_DRAGON_DARTS); - ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_CLEFAIRY, 1) == TYPE_FAIRY); } SINGLE_BATTLE_TEST("Dragon Darts strikes twice") @@ -72,13 +71,13 @@ DOUBLE_BATTLE_TEST("Dragon Darts strikes an opponent twice if the other one is F struct BattlePokemon *chosenTarget = NULL; struct BattlePokemon *finalTarget = NULL; u32 speciesLeft, speciesRight; - PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentRight; speciesLeft = SPECIES_CLEFAIRY; speciesRight = SPECIES_WOBBUFFET; } - PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentRight; speciesLeft = SPECIES_CLEFAIRY; speciesRight = SPECIES_WOBBUFFET; } - PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentLeft; speciesLeft = SPECIES_WOBBUFFET; speciesRight = SPECIES_CLEFAIRY; } - PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentLeft; speciesLeft = SPECIES_WOBBUFFET; speciesRight = SPECIES_CLEFAIRY; } + PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentRight; speciesLeft = SPECIES_FIDOUGH; speciesRight = SPECIES_WOBBUFFET; } + PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentRight; speciesLeft = SPECIES_FIDOUGH; speciesRight = SPECIES_WOBBUFFET; } + PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentLeft; speciesLeft = SPECIES_WOBBUFFET; speciesRight = SPECIES_FIDOUGH; } + PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentLeft; speciesLeft = SPECIES_WOBBUFFET; speciesRight = SPECIES_FIDOUGH; } GIVEN { - ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_CLEFAIRY, 1) == TYPE_FAIRY); + ASSUME(GetSpeciesType(SPECIES_FIDOUGH, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_FIDOUGH, 1) == TYPE_FAIRY); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); OPPONENT(speciesLeft); @@ -257,7 +256,6 @@ DOUBLE_BATTLE_TEST("Dragon Darts strikes right ally twice if one strike misses") DOUBLE_BATTLE_TEST("Dragon Darts strikes will be both redirected to Follow Me user") { GIVEN { - ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_CLEFAIRY, 1) == TYPE_FAIRY); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -273,14 +271,14 @@ DOUBLE_BATTLE_TEST("Dragon Darts strikes will be both redirected to Follow Me us } } -DOUBLE_BATTLE_TEST("Dragon Darts fails to strike any target if under a fairy type follow me user") +DOUBLE_BATTLE_TEST("Dragon Darts fails to strike any target if under a Fairy-type follow me user") { GIVEN { - ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_CLEFAIRY, 1) == TYPE_FAIRY); + ASSUME(GetSpeciesType(SPECIES_FIDOUGH, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_FIDOUGH, 1) == TYPE_FAIRY); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_CLEFAIRY); + OPPONENT(SPECIES_FIDOUGH); } WHEN { TURN { MOVE(opponentRight, MOVE_FOLLOW_ME); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentLeft); } } SCENE { @@ -295,7 +293,6 @@ DOUBLE_BATTLE_TEST("Dragon Darts fails to strike any target if under a fairy typ DOUBLE_BATTLE_TEST("Dragon Darts fails to strike the second target if first target fainted and follow me was active") { GIVEN { - ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_CLEFAIRY, 1) == TYPE_FAIRY); PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/fickle_beam.c b/test/battle/move_effect/fickle_beam.c index 0313823aa9..8554a29dac 100644 --- a/test/battle/move_effect/fickle_beam.c +++ b/test/battle/move_effect/fickle_beam.c @@ -12,15 +12,14 @@ SINGLE_BATTLE_TEST("Fickle Beam deals double damage 30% of the time") PASSES_RANDOMLY(30, 100, RNG_FICKLE_BEAM); GIVEN { - ASSUME(GetMovePower(MOVE_POWER_GEM) == 80); - ASSUME(GetMovePower(MOVE_FICKLE_BEAM) == 80); + ASSUME(GetMovePower(MOVE_DAZZLING_GLEAM) == GetMovePower(MOVE_FICKLE_BEAM)); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_POWER_GEM); } + TURN { MOVE(player, MOVE_DAZZLING_GLEAM); } TURN { MOVE(player, MOVE_FICKLE_BEAM); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_POWER_GEM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DAZZLING_GLEAM, player); HP_BAR(opponent, captureDamage: &damage[0]); ANIMATION(ANIM_TYPE_MOVE, MOVE_FICKLE_BEAM, player); HP_BAR(opponent, captureDamage: &damage[1]); diff --git a/test/battle/move_effect/future_sight.c b/test/battle/move_effect/future_sight.c index 246c384a07..ee424c9bd9 100644 --- a/test/battle/move_effect/future_sight.c +++ b/test/battle/move_effect/future_sight.c @@ -1,10 +1,18 @@ #include "global.h" #include "test/battle.h" +#if B_UPDATED_MOVE_DATA >= GEN_6 + #define FUTURE_SIGHT_EQUIVALENT MOVE_SEED_FLARE /* 120 power */ +#elif B_UPDATED_MOVE_DATA >= GEN_5 + #define FUTURE_SIGHT_EQUIVALENT MOVE_DYNAMAX_CANNON /* 100 power */ +#else + #define FUTURE_SIGHT_EQUIVALENT MOVE_EXTRASENSORY /* 80 power */ +#endif + ASSUMPTIONS { - ASSUME(GetMovePower(MOVE_SEED_FLARE) == GetMovePower(MOVE_FUTURE_SIGHT)); - ASSUME(GetMoveCategory(MOVE_SEED_FLARE) == GetMoveCategory(MOVE_FUTURE_SIGHT)); + ASSUME(GetMovePower(FUTURE_SIGHT_EQUIVALENT) == GetMovePower(MOVE_FUTURE_SIGHT)); + ASSUME(GetMoveCategory(FUTURE_SIGHT_EQUIVALENT) == GetMoveCategory(MOVE_FUTURE_SIGHT)); ASSUME(GetMoveEffect(MOVE_FUTURE_SIGHT) == EFFECT_FUTURE_SIGHT); ASSUME(GetMovePower(MOVE_FUTURE_SIGHT) > 0); } @@ -23,13 +31,13 @@ SINGLE_BATTLE_TEST("Future Sight uses Sp. Atk stat of the original user without PLAYER(SPECIES_RAICHU) { Item(item); } OPPONENT(SPECIES_REGICE); } WHEN { - TURN { MOVE(player, MOVE_SEED_FLARE, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } + TURN { MOVE(player, FUTURE_SIGHT_EQUIVALENT, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } TURN { MOVE(player, MOVE_FUTURE_SIGHT); } TURN { SWITCH(player, 1); } TURN { } TURN { } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_FLARE, player); + ANIMATION(ANIM_TYPE_MOVE, FUTURE_SIGHT_EQUIVALENT, player); HP_BAR(opponent, captureDamage: &seedFlareDmg); ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); MESSAGE("The opposing Regice took the Future Sight attack!"); @@ -49,13 +57,13 @@ SINGLE_BATTLE_TEST("Future Sight is not boosted by Life Orb is original user if PLAYER(SPECIES_RAICHU) { Item(ITEM_LIFE_ORB); } OPPONENT(SPECIES_REGICE); } WHEN { - TURN { MOVE(player, MOVE_SEED_FLARE, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } + TURN { MOVE(player, FUTURE_SIGHT_EQUIVALENT, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } TURN { MOVE(player, MOVE_FUTURE_SIGHT); } TURN { SWITCH(player, 1); } TURN { } TURN { } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_FLARE, player); + ANIMATION(ANIM_TYPE_MOVE, FUTURE_SIGHT_EQUIVALENT, player); HP_BAR(opponent, captureDamage: &seedFlareDmg); ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); MESSAGE("The opposing Regice took the Future Sight attack!"); @@ -77,13 +85,13 @@ SINGLE_BATTLE_TEST("Future Sight receives STAB from party mon (Gen 5+)") PLAYER(SPECIES_RAICHU); OPPONENT(SPECIES_REGICE); } WHEN { - TURN { MOVE(player, MOVE_SEED_FLARE, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } + TURN { MOVE(player, FUTURE_SIGHT_EQUIVALENT, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } TURN { MOVE(player, MOVE_FUTURE_SIGHT); } TURN { SWITCH(player, 1); } TURN { } TURN { } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_FLARE, player); + ANIMATION(ANIM_TYPE_MOVE, FUTURE_SIGHT_EQUIVALENT, player); HP_BAR(opponent, captureDamage: &seedFlareDmg); ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); HP_BAR(opponent, captureDamage: &futureSightDmg); @@ -100,13 +108,13 @@ SINGLE_BATTLE_TEST("Future Sight is affected by type effectiveness (Gen 5+)") PLAYER(SPECIES_RAICHU); OPPONENT(SPECIES_HOUNDOOM); } WHEN { - TURN { MOVE(player, MOVE_SEED_FLARE, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } + TURN { MOVE(player, FUTURE_SIGHT_EQUIVALENT, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } TURN { MOVE(player, MOVE_FUTURE_SIGHT); } TURN { SWITCH(player, 1); } TURN { } TURN { } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_FLARE, player); + ANIMATION(ANIM_TYPE_MOVE, FUTURE_SIGHT_EQUIVALENT, player); HP_BAR(opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); MESSAGE("The opposing Houndoom took the Future Sight attack!"); diff --git a/test/battle/move_effect/instruct.c b/test/battle/move_effect/instruct.c index 03a39fe154..070b6ba31d 100644 --- a/test/battle/move_effect/instruct.c +++ b/test/battle/move_effect/instruct.c @@ -280,6 +280,7 @@ DOUBLE_BATTLE_TEST("Instructed move will be redirected by Rage Powder after inst PARAMETRIZE { moveTarget = opponentLeft; } PARAMETRIZE { moveTarget = opponentRight; } GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_6); ASSUME(GetMoveEffect(MOVE_RAGE_POWDER) == EFFECT_FOLLOW_ME); ASSUME(IsPowderMove(MOVE_RAGE_POWDER) == TRUE); ASSUME(GetMoveEffect(MOVE_SOAK) == EFFECT_SOAK); diff --git a/test/battle/move_effect/multi_hit.c b/test/battle/move_effect/multi_hit.c index 7052a52cbf..291c78efdb 100644 --- a/test/battle/move_effect/multi_hit.c +++ b/test/battle/move_effect/multi_hit.c @@ -177,14 +177,14 @@ SINGLE_BATTLE_TEST("Scale Shot is immune to Fairy types and will end the move co GIVEN { ASSUME(GetMoveEffect(MOVE_SCALE_SHOT) == EFFECT_MULTI_HIT); ASSUME(GetMoveType(MOVE_SCALE_SHOT) == TYPE_DRAGON); - ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_CLEFAIRY, 1) == TYPE_FAIRY); + ASSUME(GetSpeciesType(SPECIES_FIDOUGH, 0) == TYPE_FAIRY || GetSpeciesType(SPECIES_FIDOUGH, 1) == TYPE_FAIRY); PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_CLEFAIRY) { HP(1); } + OPPONENT(SPECIES_FIDOUGH) { HP(1); } } WHEN { TURN { MOVE(player, MOVE_SCALE_SHOT); } } SCENE { NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); - MESSAGE("It doesn't affect the opposing Clefairy…"); + MESSAGE("It doesn't affect the opposing Fidough…"); } } diff --git a/test/battle/move_effect/powder.c b/test/battle/move_effect/powder.c index c4e053db87..da8eba4caf 100644 --- a/test/battle/move_effect/powder.c +++ b/test/battle/move_effect/powder.c @@ -149,9 +149,10 @@ DOUBLE_BATTLE_TEST("Powder fails if target is already affected by Powder") } } -SINGLE_BATTLE_TEST("Powder fails if the target is Grass type") +SINGLE_BATTLE_TEST("Powder fails if the target is Grass type (Gen6+)") { GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_6); ASSUME(GetSpeciesType(SPECIES_VENUSAUR, 0) == TYPE_GRASS || GetSpeciesType(SPECIES_VENUSAUR, 1) == TYPE_GRASS); PLAYER(SPECIES_VENUSAUR); OPPONENT(SPECIES_VIVILLON); @@ -164,9 +165,10 @@ SINGLE_BATTLE_TEST("Powder fails if the target is Grass type") } } -SINGLE_BATTLE_TEST("Powder fails if the target has Overcoat") +SINGLE_BATTLE_TEST("Powder fails if the target has Overcoat (Gen6+)") { GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_6); PLAYER(SPECIES_FORRETRESS) { Ability(ABILITY_OVERCOAT); } OPPONENT(SPECIES_VIVILLON); } WHEN { diff --git a/test/battle/move_effect/toxic.c b/test/battle/move_effect/toxic.c index ef7c8f011a..a8134f9c66 100644 --- a/test/battle/move_effect/toxic.c +++ b/test/battle/move_effect/toxic.c @@ -43,14 +43,16 @@ SINGLE_BATTLE_TEST("Toxic can't bad poison a poison or steel type") } } -SINGLE_BATTLE_TEST("Toxic cannot miss if used by a Poison-type") +SINGLE_BATTLE_TEST("Toxic cannot miss if used by a Poison-type (Gen6+)") { - u32 species; + u32 species, gen; bool32 hit; - PARAMETRIZE { species = SPECIES_WOBBUFFET; hit = FALSE; } - PARAMETRIZE { species = SPECIES_NIDORAN_M; hit = TRUE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; hit = FALSE; gen = GEN_5; } + PARAMETRIZE { species = SPECIES_NIDORAN_M; hit = FALSE; gen = GEN_5; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; hit = FALSE; gen = GEN_6; } + PARAMETRIZE { species = SPECIES_NIDORAN_M; hit = TRUE; gen = GEN_6; } GIVEN { - ASSUME(B_TOXIC_NEVER_MISS >= GEN_6); + WITH_CONFIG(GEN_CONFIG_TOXIC_NEVER_MISS, gen); ASSUME(GetSpeciesType(SPECIES_NIDORAN_M, 0) == TYPE_POISON); PLAYER(species); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect_secondary/dire_claw.c b/test/battle/move_effect_secondary/dire_claw.c index ad9ffcc4e2..abd4563132 100644 --- a/test/battle/move_effect_secondary/dire_claw.c +++ b/test/battle/move_effect_secondary/dire_claw.c @@ -38,11 +38,10 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze poison/electric types respe u8 statusAnim; u16 species; u32 rng; - #if B_PARALYZE_ELECTRIC >= GEN_6 PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; } - #endif // B_PARALYZE_ELECTRIC PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = MOVE_EFFECT_POISON; species = SPECIES_ARBOK; } GIVEN { + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, GEN_6); PLAYER(SPECIES_WOBBUFFET); OPPONENT(species); } WHEN { diff --git a/test/battle/move_effect_secondary/paralysis.c b/test/battle/move_effect_secondary/paralysis.c index baa2183064..723309f01a 100644 --- a/test/battle/move_effect_secondary/paralysis.c +++ b/test/battle/move_effect_secondary/paralysis.c @@ -21,19 +21,27 @@ SINGLE_BATTLE_TEST("Thunder Shock inflicts paralysis") } } -SINGLE_BATTLE_TEST("Thunder Shock cannot paralyze an Electric-type") +SINGLE_BATTLE_TEST("Thunder Shock cannot paralyze an Electric-type (Gen6+)") { + u32 gen = 0; + PARAMETRIZE { gen = GEN_5; } + PARAMETRIZE { gen = GEN_6; } GIVEN { - ASSUME(B_PARALYZE_ELECTRIC >= GEN_6); + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, gen); ASSUME(GetSpeciesType(SPECIES_PIKACHU, 0) == TYPE_ELECTRIC); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_PIKACHU); } WHEN { - TURN { MOVE(player, MOVE_THUNDER_SHOCK); } + TURN { MOVE(player, MOVE_THUNDER_SHOCK, secondaryEffect: TRUE); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, player); HP_BAR(opponent); - NONE_OF { + if (gen == GEN_6) { + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + } + } else { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); STATUS_ICON(opponent, paralysis: TRUE); } diff --git a/test/battle/move_effect_secondary/tri_attack.c b/test/battle/move_effect_secondary/tri_attack.c index ab33561cf0..4ec90f6f92 100644 --- a/test/battle/move_effect_secondary/tri_attack.c +++ b/test/battle/move_effect_secondary/tri_attack.c @@ -46,12 +46,11 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze electric/fire/ice typ u8 statusAnim; u16 species; u32 rng; - #if B_PARALYZE_ELECTRIC >= GEN_6 PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; } - #endif // B_PARALYZE_ELECTRIC PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_ARCANINE; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_GLALIE; } GIVEN { + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, GEN_6); PLAYER(SPECIES_WOBBUFFET); OPPONENT(species); } WHEN { diff --git a/test/battle/move_flags/powder.c b/test/battle/move_flags/powder.c index bb0b1e3e4f..989cfb6426 100644 --- a/test/battle/move_flags/powder.c +++ b/test/battle/move_flags/powder.c @@ -1,9 +1,13 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Powder moves are blocked by Grass-type Pokémon") +SINGLE_BATTLE_TEST("Powder moves are blocked by Grass-type Pokémon (Gen6+)") { + u32 gen = 0; + PARAMETRIZE { gen = GEN_5; } + PARAMETRIZE { gen = GEN_6; } GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, gen); ASSUME(IsPowderMove(MOVE_STUN_SPORE)); ASSUME(GetSpeciesType(SPECIES_ODDISH, 0) == TYPE_GRASS); PLAYER(SPECIES_WYNAUT); @@ -11,7 +15,12 @@ SINGLE_BATTLE_TEST("Powder moves are blocked by Grass-type Pokémon") } WHEN { TURN { MOVE(player, MOVE_STUN_SPORE); } } SCENE { - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); - MESSAGE("It doesn't affect the opposing Oddish…"); + if (gen == GEN_6) { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + MESSAGE("It doesn't affect the opposing Oddish…"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + NOT MESSAGE("It doesn't affect the opposing Oddish…"); + } } } diff --git a/test/battle/status1/paralysis.c b/test/battle/status1/paralysis.c index c81daa9179..1354c6490b 100644 --- a/test/battle/status1/paralysis.c +++ b/test/battle/status1/paralysis.c @@ -55,6 +55,7 @@ AI_SINGLE_BATTLE_TEST("AI avoids Thunder Wave when it can not paralyse target") PARAMETRIZE { species = SPECIES_PIKACHU; ability = ABILITY_STATIC; } GIVEN { + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, GEN_6); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); PLAYER(species) { Ability(ability); } OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_THUNDER_WAVE); } @@ -63,18 +64,30 @@ AI_SINGLE_BATTLE_TEST("AI avoids Thunder Wave when it can not paralyse target") } } -SINGLE_BATTLE_TEST("Thunder Wave doesn't affect Electric types in Gen6+") +SINGLE_BATTLE_TEST("Thunder Wave doesn't affect Electric types (Gen6+)") { + u32 gen = 0; + PARAMETRIZE { gen = GEN_5; } + PARAMETRIZE { gen = GEN_6; } GIVEN { + WITH_CONFIG(GEN_CONFIG_PARALYZE_ELECTRIC, gen); ASSUME(GetSpeciesType(SPECIES_PIKACHU, 0) == TYPE_ELECTRIC); - ASSUME(B_PARALYZE_ELECTRIC >= GEN_6); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_PIKACHU); } WHEN { TURN { MOVE(player, MOVE_THUNDER_WAVE); } } SCENE { MESSAGE("Wobbuffet used Thunder Wave!"); - MESSAGE("It doesn't affect the opposing Pikachu…"); + if (gen == GEN_6) { + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + } + MESSAGE("It doesn't affect the opposing Pikachu…"); + } else { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + } } } diff --git a/test/battle/status1/sleep.c b/test/battle/status1/sleep.c index 401c0d0bcd..7a9f60d5fe 100644 --- a/test/battle/status1/sleep.c +++ b/test/battle/status1/sleep.c @@ -22,11 +22,25 @@ SINGLE_BATTLE_TEST("Sleep prevents the battler from using a move") } } -SINGLE_BATTLE_TEST("Sleep: Spore doesn't affect grass types (Gen 6+)") +SINGLE_BATTLE_TEST("Sleep: Spore affects grass types (Gen1-5)") { GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_5); + ASSUME(IsPowderMove(MOVE_SPORE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHIKORITA); + } WHEN { + TURN { MOVE(player, MOVE_SPORE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + } +} + +SINGLE_BATTLE_TEST("Sleep: Spore doesn't affect grass types (Gen6+)") +{ + GIVEN { + WITH_CONFIG(GEN_CONFIG_POWDER_GRASS, GEN_6); ASSUME(IsPowderMove(MOVE_SPORE)); - ASSUME(B_POWDER_GRASS >= GEN_6); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_CHIKORITA); } WHEN { From d2e8afa13acb94e7826a21f2584e8c4802df71c7 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 3 Oct 2025 15:04:56 +0200 Subject: [PATCH 16/50] Fixes Endure lasting forever (#7838) --- src/battle_main.c | 1 + test/battle/move_effect/endure.c | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/battle_main.c b/src/battle_main.c index e787366de8..bd08f559c1 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5143,6 +5143,7 @@ static void TurnValuesCleanUp(bool8 var0) gSpecialStatuses[i].parentalBondState = PARENTAL_BOND_OFF; gBattleStruct->battlerState[i].usedEjectItem = FALSE; gProtectStructs[i].lashOutAffected = FALSE; + gDisableStructs[i].endured = FALSE; } gSideTimers[B_SIDE_PLAYER].followmeTimer = 0; diff --git a/test/battle/move_effect/endure.c b/test/battle/move_effect/endure.c index f332fc28de..2d66bc41ef 100644 --- a/test/battle/move_effect/endure.c +++ b/test/battle/move_effect/endure.c @@ -1,13 +1,17 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_ENDURE) == EFFECT_ENDURE); +} + TO_DO_BATTLE_TEST("Endure allows the user to survive any attack with 1 HP left"); SINGLE_BATTLE_TEST("Endure does not prevent multiple hits and stat changes occur at the end of the turn") { GIVEN { ASSUME(GetMoveEffect(MOVE_SCALE_SHOT) == EFFECT_MULTI_HIT); - ASSUME(GetMoveEffect(MOVE_ENDURE) == EFFECT_ENDURE); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { HP(1); } } WHEN { @@ -54,6 +58,23 @@ DOUBLE_BATTLE_TEST("Endure is not transferred to a mon that is switched in due t } } +SINGLE_BATTLE_TEST("Endure only lasts for one turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + } WHEN { + TURN { MOVE(opponent, MOVE_ENDURE); MOVE(player, MOVE_POUND); } + TURN { MOVE(player, MOVE_POUND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player); + MESSAGE("The opposing Wobbuffet endured the hit!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player); + NOT MESSAGE("The opposing Wobbuffet endured the hit!"); + } +} + TO_DO_BATTLE_TEST("Endure's success rate decreases for every consecutively used turn"); TO_DO_BATTLE_TEST("Endure uses the same counter as Protect"); TO_DO_BATTLE_TEST("Endure doesn't trigger effects that require damage to be done to the Pokémon (Gen 2-4)"); // Eg. Rough Skin From 1856688ea092e37cdfb568c7b30bc0127163ce9f Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Fri, 3 Oct 2025 15:43:03 +0100 Subject: [PATCH 17/50] Fixes `EVO_BATTLE_END` evolutions not removing item with additional conditions (#7841) --- src/battle_main.c | 10 +++++++--- src/pokemon.c | 13 ++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/battle_main.c b/src/battle_main.c index bd08f559c1..6a45019ff1 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5719,20 +5719,24 @@ static void TryEvolvePokemon(void) if (!(sTriedEvolving & (1u << i))) { bool32 canStopEvo = TRUE; - u32 species = GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_BATTLE_SPECIAL, i, NULL, &canStopEvo, CHECK_EVO); + enum EvolutionMode mode = EVO_MODE_BATTLE_SPECIAL; + u32 evolutionItemArg = i; + u32 species = GetEvolutionTargetSpecies(&gPlayerParty[i], mode, evolutionItemArg, NULL, &canStopEvo, CHECK_EVO); sTriedEvolving |= 1u << i; if (species == SPECIES_NONE && (gLeveledUpInBattle & (1u << i))) { gLeveledUpInBattle &= ~(1u << i); - species = GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_BATTLE_ONLY, gLeveledUpInBattle, NULL, &canStopEvo, CHECK_EVO); + mode = EVO_MODE_BATTLE_ONLY; + evolutionItemArg = gLeveledUpInBattle; + species = GetEvolutionTargetSpecies(&gPlayerParty[i], mode, evolutionItemArg, NULL, &canStopEvo, CHECK_EVO); } if (species != SPECIES_NONE) { FreeAllWindowBuffers(); gBattleMainFunc = WaitForEvoSceneToFinish; - GetEvolutionTargetSpecies(&gPlayerParty[i], EVO_MODE_BATTLE_ONLY, gLeveledUpInBattle, NULL, &canStopEvo, DO_EVO); + GetEvolutionTargetSpecies(&gPlayerParty[i], mode, evolutionItemArg, NULL, &canStopEvo, DO_EVO); EvolutionScene(&gPlayerParty[i], species, canStopEvo, i); return; } diff --git a/src/pokemon.c b/src/pokemon.c index eb2428f2fb..fdc49c7eda 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -4836,7 +4836,7 @@ u32 GetEvolutionTargetSpecies(struct Pokemon *mon, enum EvolutionMode mode, u16 case EVO_MODE_ITEM_CHECK: for (i = 0; evolutions[i].method != EVOLUTIONS_END; i++) { - bool32 conditionMet = FALSE; + bool32 conditionsMet = FALSE; if (SanitizeSpeciesId(evolutions[i].targetSpecies) == SPECIES_NONE) continue; @@ -4844,11 +4844,11 @@ u32 GetEvolutionTargetSpecies(struct Pokemon *mon, enum EvolutionMode mode, u16 { case EVO_ITEM: if (evolutions[i].param == evolutionItem) - conditionMet = TRUE; + conditionsMet = TRUE; break; } - if (conditionMet && DoesMonMeetAdditionalConditions(mon, evolutions[i].params, NULL, PARTY_SIZE, canStopEvo, evoState)) + if (conditionsMet && DoesMonMeetAdditionalConditions(mon, evolutions[i].params, NULL, PARTY_SIZE, canStopEvo, evoState)) { // All checks passed, so stop checking the rest of the evolutions. // This is different from vanilla where the loop continues. @@ -4870,9 +4870,9 @@ u32 GetEvolutionTargetSpecies(struct Pokemon *mon, enum EvolutionMode mode, u16 switch (evolutions[i].method) { - case EVO_BATTLE_END: - conditionsMet = TRUE; - break; + case EVO_BATTLE_END: + conditionsMet = TRUE; + break; } if (conditionsMet && DoesMonMeetAdditionalConditions(mon, evolutions[i].params, NULL, evolutionItem, canStopEvo, evoState)) @@ -4898,7 +4898,6 @@ u32 GetEvolutionTargetSpecies(struct Pokemon *mon, enum EvolutionMode mode, u16 case EVO_SPIN: if (gSpecialVar_0x8000 == evolutions[i].param) conditionsMet = TRUE; - break; } From 1529adba9b4bdd77f14a0e2cf137b5d3b25f0766 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Sat, 4 Oct 2025 16:00:25 +0200 Subject: [PATCH 18/50] Fix LTO breaking with FREE_MYSTERY_GIFT set to TRUE (#7844) --- src/easy_chat.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/easy_chat.c b/src/easy_chat.c index 7c0871cb78..63808597a2 100644 --- a/src/easy_chat.c +++ b/src/easy_chat.c @@ -5851,16 +5851,23 @@ static u8 IsEasyChatWordUnlocked(u16 easyChatWord) void InitializeEasyChatWordArray(u16 *words, u16 length) { u16 i; - for (i = length - 1; i != EC_EMPTY_WORD; i--) - *(words++) = EC_EMPTY_WORD; + if (words != NULL) + { + for (i = length - 1; i != EC_EMPTY_WORD; i--) + *(words++) = EC_EMPTY_WORD; + } } void InitQuestionnaireWords(void) { int i; u16 *words = GetQuestionnaireWordsPtr(); - for (i = 0; i < NUM_QUESTIONNAIRE_WORDS; i++) - words[i] = EC_EMPTY_WORD; + + if (words != NULL) + { + for (i = 0; i < NUM_QUESTIONNAIRE_WORDS; i++) + words[i] = EC_EMPTY_WORD; + } } bool32 IsEasyChatAnswerUnlocked(int easyChatWord) From 6cfdfd091f7aae60d878acd1ec797ac39d9b52c0 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 5 Oct 2025 23:15:30 +0200 Subject: [PATCH 19/50] Add failsafe to AI_DecideHoldEffectForTurn (#7849) --- src/battle_ai_util.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index c402ce3d1b..5237c4aff2 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1673,7 +1673,10 @@ s32 AI_DecideKnownAbilityForTurn(u32 battlerId) enum ItemHoldEffect AI_DecideHoldEffectForTurn(u32 battlerId) { - enum ItemHoldEffect holdEffect; + enum ItemHoldEffect holdEffect = HOLD_EFFECT_NONE; + + if (gBattleMons[battlerId].item == ITEM_NONE) // Failsafe for when user recorded an item but it was consumed + return holdEffect; if (!IsAiBattlerAware(battlerId)) holdEffect = gAiPartyData->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].heldEffect; From 2dd590d8c662415cca35a203ea6aafbb776197a9 Mon Sep 17 00:00:00 2001 From: Ruby Date: Tue, 7 Oct 2025 08:05:46 +0800 Subject: [PATCH 20/50] Update INSTALL.md (#7852) --- INSTALL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index d265d588a1..072d43a858 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -14,8 +14,8 @@ After completing the install instructions for your OS, proceed to [Building poke On Windows, the project can be built using the following systems: - WSL2, fastest - WSL1, 7 times slower than WSL2 -- Msys2, 20 times slower than WSL2 (**NOTE**: Currently broken on pret upstream) -- Cygwin, 30 timer slower than WSL2 (**NOTE**: Currently broken on pret upstream) +- Msys2, 20 times slower than WSL2 +- Cygwin, 30 timer slower than WSL2 **NOTE**: Only WSL systems are recommended. From 6445e1863af1df78b26ac7746d924b75cb48c374 Mon Sep 17 00:00:00 2001 From: Frank DeBlasio <35279583+fdeblasio@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:19:04 -0400 Subject: [PATCH 21/50] Updated Mountain Gale's PP for Gen 9 (#7856) --- src/data/moves_info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index fb5dbeb3e9..bf7d500d92 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -19331,7 +19331,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .power = 100, .type = TYPE_ICE, .accuracy = 85, - .pp = 5, + .pp = B_UPDATED_MOVE_DATA >= GEN_9 ? 10 : 5, .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, From 6b1b24a2bcd4f1c92d099086b5f9a8e3dfd4bd8e Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Tue, 7 Oct 2025 22:21:02 +0200 Subject: [PATCH 22/50] Fix for uncaught mon with terrain active (#7868) --- src/pokedex.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/pokedex.c b/src/pokedex.c index 91fe39367b..bcd6cb607d 100644 --- a/src/pokedex.c +++ b/src/pokedex.c @@ -1,5 +1,6 @@ #include "global.h" #include "battle_main.h" +#include "battle_script_commands.h" #include "bg.h" #include "data.h" #include "decompress.h" @@ -4021,8 +4022,8 @@ static void HighlightSubmenuScreenSelectBarItem(u8 a, u16 b) #define tPalTimer data[2] #define tMonSpriteId data[3] #define tIsShiny data[13] -#define tPersonalityLo data[14] -#define tPersonalityHi data[15] +#define tPersonalityLo 14 +#define tPersonalityHi 15 u8 DisplayCaughtMonDexPage(u16 species, bool32 isShiny, u32 personality) { @@ -4035,11 +4036,18 @@ u8 DisplayCaughtMonDexPage(u16 species, bool32 isShiny, u32 personality) gTasks[taskId].tState = 0; gTasks[taskId].tSpecies = species; gTasks[taskId].tIsShiny = isShiny; - gTasks[taskId].tPersonalityLo = personality; - gTasks[taskId].tPersonalityHi = personality >> 16; + gTasks[taskId].data[tPersonalityLo] = personality; + gTasks[taskId].data[tPersonalityHi] = personality >> 16; return taskId; } +static void LoadDexMonPalette(u32 taskId, bool32 isShiny) +{ + const u16 *paletteData = GetMonSpritePalFromSpeciesAndPersonality(gTasks[taskId].tSpecies, isShiny, GetWordTaskArg(taskId, tPersonalityLo)); + u32 paletteNum = gSprites[gTasks[taskId].tMonSpriteId].oam.paletteNum; + LoadPalette(paletteData, OBJ_PLTT_ID(paletteNum), PLTT_SIZE_4BPP); +} + static void Task_DisplayCaughtMonDexPage(u8 taskId) { u8 spriteId; @@ -4087,11 +4095,16 @@ static void Task_DisplayCaughtMonDexPage(u8 taskId) gTasks[taskId].tState++; break; case 4: - spriteId = CreateMonPicSprite(species, FALSE, ((u16)gTasks[taskId].tPersonalityHi << 16) | (u16)gTasks[taskId].tPersonalityLo, TRUE, MON_PAGE_X, MON_PAGE_Y, 0, TAG_NONE); + // We're using a different mon sprite creation method, because we don't have enough memory to safely use CreateMonPicSprite. + SetMultiuseSpriteTemplateToPokemon(species, GetCatchingBattler()); + spriteId = CreateSprite(&gMultiuseSpriteTemplate, MON_PAGE_X, MON_PAGE_Y, 0); + gTasks[taskId].tMonSpriteId = spriteId; + gSprites[spriteId].oam.priority = 0; + gSprites[spriteId].callback = SpriteCallbackDummy; + LoadDexMonPalette(taskId, FALSE); gSprites[spriteId].oam.priority = 0; BeginNormalPaletteFade(PALETTES_ALL, 0, 0x10, 0, RGB_BLACK); SetVBlankCallback(gPokedexVBlankCB); - gTasks[taskId].tMonSpriteId = spriteId; gTasks[taskId].tState++; break; case 5: @@ -4138,9 +4151,6 @@ static void Task_ExitCaughtMonPage(u8 taskId) if (!gPaletteFade.active) { bool32 isShiny; - u32 personality; - u8 paletteNum; - const u16 *paletteData; void *buffer; SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_1D_MAP | DISPCNT_OBJ_ON); @@ -4153,10 +4163,7 @@ static void Task_ExitCaughtMonPage(u8 taskId) Free(buffer); isShiny = (bool8)gTasks[taskId].tIsShiny; - personality = ((u16)gTasks[taskId].tPersonalityHi << 16) | (u16)gTasks[taskId].tPersonalityLo; - paletteNum = gSprites[gTasks[taskId].tMonSpriteId].oam.paletteNum; - paletteData = GetMonSpritePalFromSpeciesAndPersonality(gTasks[taskId].tSpecies, isShiny, personality); - LoadPalette(paletteData, OBJ_PLTT_ID(paletteNum), PLTT_SIZE_4BPP); + LoadDexMonPalette(taskId, isShiny); DestroyTask(taskId); } } From f5e3464e8306d6f14438d9f05ed04769f0a7429f Mon Sep 17 00:00:00 2001 From: RavePossum <145081120+ravepossum@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:50:16 -0600 Subject: [PATCH 23/50] Fix Brine move anim and document Water Spout anim (#7865) --- data/battle_anim_scripts.s | 2 +- src/battle_anim_water.c | 135 ++++++++++++++++++++++++++++++------- 2 files changed, 113 insertions(+), 24 deletions(-) diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index a741c372ea..7de4958345 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -171,7 +171,7 @@ gBattleAnimMove_Brine:: playsewithpan SE_M_DIVE, -64 waitforvisualfinish delay 16 - createvisualtask AnimTask_WaterSpoutRain, 5 + createvisualtask AnimTask_BrineRain, 5 playsewithpan SE_M_SURF, +63 clearmonbg ANIM_DEF_PARTNER blendoff diff --git a/src/battle_anim_water.c b/src/battle_anim_water.c index 490136d116..5674cd5cc7 100644 --- a/src/battle_anim_water.c +++ b/src/battle_anim_water.c @@ -45,6 +45,7 @@ static void AnimTask_RunSinAnimTimer(u8); static void AnimTask_CreateSurfWave_Step1(u8); static void AnimTask_CreateSurfWave_Step2(u8); static void AnimTask_SurfWaveScanlineEffect(u8); +static void AnimTask_BrineRain_Step(u8); static void AnimTask_WaterSpoutLaunch_Step(u8); static void AnimTask_WaterSpoutRain_Step(u8); static u8 GetWaterSpoutPowerForAnim(void); @@ -1442,24 +1443,97 @@ static void AnimSmallWaterOrb(struct Sprite *sprite) } } +#define tRainState data[0] +#define tWaterSpoutPower data[1] +#define tDropTaskDelay data[2] +#define tDropInitialXPos data[4] +#define tDropXRange data[5] +#define tDropEndYPos data[6] +#define tDropXPos data[7] +#define tSineTableIndex data[8] +#define tCurrentDropSprites data[9] +#define tDropHasHit data[10] +#define tCreatedDropSprites data[11] +#define tMaxDropSprites data[12] +#define tShakeTasksCreated data[13] +#define tDropInitialYPos data[14] + +void AnimTask_BrineRain(u8 taskId) +{ + struct Task *task = &gTasks[taskId]; + + if (IsOnPlayerSide(gBattleAnimAttacker)) + { + task->tDropEndYPos = 40; + task->tDropInitialYPos = 0; + } + else + { + task->tDropEndYPos = 90; + task->tDropInitialYPos = 40; + } + task->tDropInitialXPos = GetBattlerSpriteCoord(gBattleAnimTarget, BATTLER_COORD_X_2); + task->tDropXRange = 40; + task->tDropXPos = task->tDropInitialXPos; + task->tMaxDropSprites = 10; + task->func = AnimTask_BrineRain_Step; +} + +static void AnimTask_BrineRain_Step(u8 taskId) +{ + struct Task *task = &gTasks[taskId]; + u8 taskId2; + + switch (task->tRainState) + { + case 0: + if (++task->tDropTaskDelay > 2) + { + task->tDropTaskDelay = 0; + CreateWaterSpoutRainDroplet(task, taskId); + } + if (task->tDropHasHit != FALSE && task->tShakeTasksCreated == FALSE) + { + gBattleAnimArgs[0] = ANIM_TARGET; + gBattleAnimArgs[1] = 0; + gBattleAnimArgs[2] = 12; + taskId2 = CreateTask(AnimTask_HorizontalShake, 80); + if (taskId2 != TASK_NONE) + { + gTasks[taskId2].func(taskId2); + gAnimVisualTaskCount++; + } + task->tShakeTasksCreated = TRUE; + } + if (task->tCreatedDropSprites >= task->tMaxDropSprites) + task->tRainState++; + break; + case 1: + if (task->tCurrentDropSprites == 0) + DestroyAnimVisualTask(taskId); + break; + } +} + void AnimTask_WaterSpoutRain(u8 taskId) { struct Task *task = &gTasks[taskId]; - task->data[1] = GetWaterSpoutPowerForAnim(); + task->tWaterSpoutPower = GetWaterSpoutPowerForAnim(); if (IsOnPlayerSide(gBattleAnimAttacker)) { - task->data[4] = 136; - task->data[6] = 40; + task->tDropInitialXPos = 136; + task->tDropEndYPos = 40; } else { - task->data[4] = 16; - task->data[6] = 80; + task->tDropInitialXPos = 16; + task->tDropEndYPos = 80; } - task->data[5] = 98; - task->data[7] = task->data[4] + 49; - task->data[12] = task->data[1] * 5 + 5; + task->tDropXRange = 98; + task->tDropXPos = task->tDropInitialXPos + 49; + task->tMaxDropSprites = task->tWaterSpoutPower * 5 + 5; + task->tDropInitialYPos = 0; task->func = AnimTask_WaterSpoutRain_Step; } @@ -1468,15 +1542,15 @@ static void AnimTask_WaterSpoutRain_Step(u8 taskId) struct Task *task = &gTasks[taskId]; u8 taskId2; - switch (task->data[0]) + switch (task->tRainState) { case 0: - if (++task->data[2] > 2) + if (++task->tDropTaskDelay > 2) { - task->data[2] = 0; + task->tDropTaskDelay = 0; CreateWaterSpoutRainDroplet(task, taskId); } - if (task->data[10] != 0 && task->data[13] == 0) + if (task->tDropHasHit != FALSE && task->tShakeTasksCreated == FALSE) { gBattleAnimArgs[0] = ANIM_TARGET; gBattleAnimArgs[1] = 0; @@ -1494,13 +1568,13 @@ static void AnimTask_WaterSpoutRain_Step(u8 taskId) gTasks[taskId2].func(taskId2); gAnimVisualTaskCount++; } - task->data[13] = 1; + task->tShakeTasksCreated = TRUE; } - if (task->data[11] >= task->data[12]) - task->data[0]++; + if (task->tCreatedDropSprites >= task->tMaxDropSprites) + task->tRainState++; break; case 1: - if (task->data[9] == 0) + if (task->tCurrentDropSprites == 0) DestroyAnimVisualTask(taskId); break; } @@ -1508,8 +1582,8 @@ static void AnimTask_WaterSpoutRain_Step(u8 taskId) static void CreateWaterSpoutRainDroplet(struct Task *task, u8 taskId) { - u16 yPosArg = ((gSineTable[task->data[8]] + 3) >> 4) + task->data[6]; - u8 spriteId = CreateSprite(&gSmallWaterOrbSpriteTemplate, task->data[7], 0, 0); + u16 yPosArg = ((gSineTable[task->tSineTableIndex] + 3) >> 4) + task->tDropEndYPos; + u8 spriteId = CreateSprite(&gSmallWaterOrbSpriteTemplate, task->tDropXPos, task->tDropInitialYPos, 0); if (spriteId != MAX_SPRITES) { @@ -1517,11 +1591,11 @@ static void CreateWaterSpoutRainDroplet(struct Task *task, u8 taskId) gSprites[spriteId].data[5] = yPosArg; gSprites[spriteId].data[6] = taskId; gSprites[spriteId].data[7] = 9; - task->data[9]++; + task->tCurrentDropSprites++; } - task->data[11]++; - task->data[8] = (task->data[8] + 39) & 0xFF; - task->data[7] = (ISO_RANDOMIZE2(task->data[7]) % task->data[5]) + task->data[4]; + task->tCreatedDropSprites++; + task->tSineTableIndex = (task->tSineTableIndex + 39) & 0xFF; + task->tDropXPos = (ISO_RANDOMIZE2(task->tDropXPos) % task->tDropXRange) + task->tDropInitialXPos; } static void AnimWaterSpoutRain(struct Sprite *sprite) @@ -1531,7 +1605,7 @@ static void AnimWaterSpoutRain(struct Sprite *sprite) sprite->y += 8; if (sprite->y >= sprite->data[5]) { - gTasks[sprite->data[6]].data[10] = 1; + gTasks[sprite->data[6]].tDropHasHit = TRUE; sprite->data[1] = CreateSprite(&gWaterHitSplatSpriteTemplate, sprite->x, sprite->y, 1); if (sprite->data[1] != MAX_SPRITES) { @@ -1560,6 +1634,21 @@ static void AnimWaterSpoutRainHit(struct Sprite *sprite) } } +#undef tRainState +#undef tWaterSpoutPower +#undef tDropTaskDelay +#undef tDropInitialXPos +#undef tDropXRange +#undef tDropEndYPos +#undef tDropXPos +#undef tSineTableIndex +#undef tCurrentDropSprites +#undef tDropHasHit +#undef tCreatedDropSprites +#undef tMaxDropSprites +#undef tShakeTasksCreated +#undef tDropInitialYPos + void AnimTask_WaterSport(u8 taskId) { struct Task *task = &gTasks[taskId]; From d6efe24bbe48d942957cc4812fc480535eca79a3 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Tue, 7 Oct 2025 23:05:29 +0200 Subject: [PATCH 24/50] Fix some ai action check happening before the logic was computed (#7867) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_ai_main.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index d9e70c6b4b..1960088640 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -319,7 +319,7 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) gAiBattleData->chosenTarget[battler] = gBattlerTarget; } -bool32 BattlerChoseNonMoveAction(void) +bool32 BattlerChooseNonMoveAction(void) { if (gAiThinkingStruct->aiAction & AI_ACTION_FLEE) { @@ -361,10 +361,6 @@ void ComputeBattlerDecisions(u32 battler) bool32 isAiBattler = (gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE)); if (isAiBattler || CanAiPredictMove()) { - // If ai is about to flee or chosen to watch player, no need to calc anything - if (isAiBattler && BattlerChoseNonMoveAction()) - return; - // Risky AI switches aggressively even mid battle enum SwitchType switchType = (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_RISKY) ? SWITCH_AFTER_KO : SWITCH_MID_BATTLE; @@ -385,6 +381,8 @@ void ComputeBattlerDecisions(u32 battler) // AI's move scoring gAiBattleData->chosenMoveIndex[battler] = BattleAI_ChooseMoveIndex(battler); // Calculate score and chose move index + if (isAiBattler) + BattlerChooseNonMoveAction(); ModifySwitchAfterMoveScoring(battler); gAiLogicData->aiCalcInProgress = FALSE; From 93661eeb39075b446245764f71e08bbc9ac6a166 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Tue, 7 Oct 2025 23:16:04 +0200 Subject: [PATCH 25/50] Fix dns palette weight (#7855) --- src/overworld.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overworld.c b/src/overworld.c index f9c949a571..8c2eaf5d88 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -1558,7 +1558,7 @@ const struct BlendSettings gTimeOfDayBlend[] = }; #define DEFAULT_WEIGHT 256 -#define TIME_BLEND_WEIGHT(begin, end) (DEFAULT_WEIGHT - (DEFAULT_WEIGHT * SAFE_DIV(((hours - begin) * MINUTES_PER_HOUR + minutes), ((end - begin) * MINUTES_PER_HOUR)))) +#define TIME_BLEND_WEIGHT(begin, end) (DEFAULT_WEIGHT - SAFE_DIV((DEFAULT_WEIGHT * ((hours - begin) * MINUTES_PER_HOUR + minutes)), ((end - begin) * MINUTES_PER_HOUR))) #define MORNING_HOUR_MIDDLE (MORNING_HOUR_BEGIN + ((MORNING_HOUR_END - MORNING_HOUR_BEGIN) / 2)) From c64c276d779f75e7f6a19f948949c39e33d432c9 Mon Sep 17 00:00:00 2001 From: cawtds <38510667+cawtds@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:16:48 +0200 Subject: [PATCH 26/50] Fixes shininess for givemon (#7847) --- asm/macros/event.inc | 12 +++++------ include/constants/pokemon.h | 6 ++++++ src/script_pokemon_util.c | 42 ++++++++++++++++++++----------------- test/pokemon.c | 6 +++--- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 49e0109451..43b9417860 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -986,7 +986,7 @@ @ Gives the player a Pokémon of the specified species and level, and allows to customize extra parameters. @ VAR_RESULT will be set to MON_GIVEN_TO_PARTY, MON_GIVEN_TO_PC, or MON_CANT_GIVE depending on the outcome. - .macro givemon species:req, level:req, item, ball, nature, abilityNum, gender, hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv, hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv, move1, move2, move3, move4, isShiny, gmaxFactor, teraType, dmaxLevel + .macro givemon species:req, level:req, item, ball, nature, abilityNum, gender, hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv, hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv, move1, move2, move3, move4, shinyMode, gmaxFactor, teraType, dmaxLevel callnative ScrCmd_createmon, requests_effects=1 .byte 0 .byte PARTY_SIZE @ assign to first empty slot @@ -1014,7 +1014,7 @@ .ifnb \move2; .set givemon_flags, givemon_flags | (1 << 18); .endif .ifnb \move3; .set givemon_flags, givemon_flags | (1 << 19); .endif .ifnb \move4; .set givemon_flags, givemon_flags | (1 << 20); .endif - .ifnb \isShiny; .set givemon_flags, givemon_flags | (1 << 21); .endif + .ifnb \shinyMode; .set givemon_flags, givemon_flags | (1 << 21); .endif .ifnb \gmaxFactor; .set givemon_flags, givemon_flags | (1 << 22); .endif .ifnb \teraType; .set givemon_flags, givemon_flags | (1 << 23); .endif .ifnb \dmaxLevel; .set givemon_flags, givemon_flags | (1 << 24); .endif @@ -1040,7 +1040,7 @@ .ifnb \move2; .2byte \move2; .endif .ifnb \move3; .2byte \move3; .endif .ifnb \move4; .2byte \move4; .endif - .ifnb \isShiny; .2byte \isShiny; .endif + .ifnb \shinyMode; .2byte \shinyMode; .endif .ifnb \gmaxFactor; .2byte \gmaxFactor; .endif .ifnb \teraType; .2byte \teraType; .endif .ifnb \dmaxLevel; .2byte \dmaxLevel; .endif @@ -1048,7 +1048,7 @@ @ creates a mon for a given party and slot @ otherwise - .macro createmon side:req, slot:req, species:req, level:req, item, ball, nature, abilityNum, gender, hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv, hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv, move1, move2, move3, move4, isShiny, gmaxFactor, teraType, dmaxLevel + .macro createmon side:req, slot:req, species:req, level:req, item, ball, nature, abilityNum, gender, hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv, hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv, move1, move2, move3, move4, shinyMode, gmaxFactor, teraType, dmaxLevel callnative ScrCmd_createmon, requests_effects=1 .byte \side @ 0 - player, 1 - opponent .byte \slot @ 0-5 @@ -1076,7 +1076,7 @@ .ifnb \move2; .set givemon_flags, givemon_flags | (1 << 18); .endif .ifnb \move3; .set givemon_flags, givemon_flags | (1 << 19); .endif .ifnb \move4; .set givemon_flags, givemon_flags | (1 << 20); .endif - .ifnb \isShiny; .set givemon_flags, givemon_flags | (1 << 21); .endif + .ifnb \shinyMode; .set givemon_flags, givemon_flags | (1 << 21); .endif .ifnb \gmaxFactor; .set givemon_flags, givemon_flags | (1 << 22); .endif .ifnb \teraType; .set givemon_flags, givemon_flags | (1 << 23); .endif .ifnb \dmaxLevel; .set givemon_flags, givemon_flags | (1 << 24); .endif @@ -1102,7 +1102,7 @@ .ifnb \move2; .2byte \move2; .endif .ifnb \move3; .2byte \move3; .endif .ifnb \move4; .2byte \move4; .endif - .ifnb \isShiny; .2byte \isShiny; .endif + .ifnb \shinyMode; .2byte \shinyMode; .endif .ifnb \gmaxFactor; .2byte \gmaxFactor; .endif .ifnb \teraType; .2byte \teraType; .endif .ifnb \dmaxLevel; .2byte \dmaxLevel; .endif diff --git a/include/constants/pokemon.h b/include/constants/pokemon.h index 20cff8a30f..b2e6b23ead 100644 --- a/include/constants/pokemon.h +++ b/include/constants/pokemon.h @@ -347,6 +347,12 @@ enum EvoSpinDirections { SPIN_EITHER, // Player spins either clockwise or counter-clockwise }; +enum ShinyMode { + SHINY_MODE_ALWAYS, + SHINY_MODE_RANDOM, + SHINY_MODE_NEVER +}; + #define MON_PIC_WIDTH 64 #define MON_PIC_HEIGHT 64 #define MON_PIC_SIZE (MON_PIC_WIDTH * MON_PIC_HEIGHT / 2) diff --git a/src/script_pokemon_util.c b/src/script_pokemon_util.c index 359ab75e41..d4fba87859 100644 --- a/src/script_pokemon_util.c +++ b/src/script_pokemon_util.c @@ -332,7 +332,7 @@ void SetTeraType(struct ScriptContext *ctx) * if side/slot are assigned, it will create the mon at the assigned party location * if slot == PARTY_SIZE, it will give the mon to first available party or storage slot */ -static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u16 item, enum PokeBall ball, u8 nature, u8 abilityNum, u8 gender, u8 *evs, u8 *ivs, u16 *moves, bool8 isShiny, bool8 gmaxFactor, u8 teraType, u8 dmaxLevel) +static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u16 item, enum PokeBall ball, u8 nature, u8 abilityNum, u8 gender, u8 *evs, u8 *ivs, u16 *moves, enum ShinyMode shinyMode, bool8 gmaxFactor, u8 teraType, u8 dmaxLevel) { enum NationalDexOrder nationalDexNum; int sentToPc; @@ -340,6 +340,7 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u u32 i; u8 genderRatio = gSpeciesInfo[species].genderRatio; u16 targetSpecies; + bool32 isShiny; // check whether to use a specific nature or a random one if (nature >= NUM_NATURES) @@ -360,10 +361,13 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u CreateMonWithNature(&mon, species, level, 32, nature); // shininess - if (P_FLAG_FORCE_SHINY != 0 && FlagGet(P_FLAG_FORCE_SHINY)) + if (shinyMode == SHINY_MODE_ALWAYS || (P_FLAG_FORCE_SHINY != 0 && FlagGet(P_FLAG_FORCE_SHINY))) isShiny = TRUE; - else if (P_FLAG_FORCE_NO_SHINY != 0 && FlagGet(P_FLAG_FORCE_NO_SHINY)) + else if (shinyMode == SHINY_MODE_NEVER || (P_FLAG_FORCE_NO_SHINY != 0 && FlagGet(P_FLAG_FORCE_NO_SHINY))) isShiny = FALSE; + else + isShiny = GetMonData(&mon, MON_DATA_IS_SHINY); + SetMonData(&mon, MON_DATA_IS_SHINY, &isShiny); // gigantamax factor @@ -479,7 +483,7 @@ u32 ScriptGiveMon(u16 species, u8 level, u16 item) MAX_PER_STAT_IVS + 1, MAX_PER_STAT_IVS + 1, MAX_PER_STAT_IVS + 1}; // ScriptGiveMonParameterized won't touch the stats' IV. u16 moves[MAX_MON_MOVES] = {MOVE_NONE, MOVE_NONE, MOVE_NONE, MOVE_NONE}; - return ScriptGiveMonParameterized(0, PARTY_SIZE, species, level, item, ITEM_POKE_BALL, NUM_NATURES, NUM_ABILITY_PERSONALITY, MON_GENDERLESS, evs, ivs, moves, FALSE, FALSE, NUMBER_OF_MON_TYPES, 0); + return ScriptGiveMonParameterized(0, PARTY_SIZE, species, level, item, ITEM_POKE_BALL, NUM_NATURES, NUM_ABILITY_PERSONALITY, MON_GENDERLESS, evs, ivs, moves, SHINY_MODE_RANDOM, FALSE, NUMBER_OF_MON_TYPES, 0); } #define PARSE_FLAG(n, default_) (flags & (1 << (n))) ? VarGet(ScriptReadHalfword(ctx)) : (default_) @@ -542,20 +546,20 @@ void ScrCmd_createmon(struct ScriptContext *ctx) } } } - hpIv = PARSE_FLAG(11, hpIv); - atkIv = PARSE_FLAG(12, atkIv); - defIv = PARSE_FLAG(13, defIv); - speedIv = PARSE_FLAG(14, speedIv); - spAtkIv = PARSE_FLAG(15, spAtkIv); - spDefIv = PARSE_FLAG(16, spDefIv); - u16 move1 = PARSE_FLAG(17, MOVE_NONE); - u16 move2 = PARSE_FLAG(18, MOVE_NONE); - u16 move3 = PARSE_FLAG(19, MOVE_NONE); - u16 move4 = PARSE_FLAG(20, MOVE_NONE); - bool8 isShiny = PARSE_FLAG(21, FALSE); - bool8 gmaxFactor = PARSE_FLAG(22, FALSE); - u8 teraType = PARSE_FLAG(23, NUMBER_OF_MON_TYPES); - u8 dmaxLevel = PARSE_FLAG(24, 0); + hpIv = PARSE_FLAG(11, hpIv); + atkIv = PARSE_FLAG(12, atkIv); + defIv = PARSE_FLAG(13, defIv); + speedIv = PARSE_FLAG(14, speedIv); + spAtkIv = PARSE_FLAG(15, spAtkIv); + spDefIv = PARSE_FLAG(16, spDefIv); + u16 move1 = PARSE_FLAG(17, MOVE_NONE); + u16 move2 = PARSE_FLAG(18, MOVE_NONE); + u16 move3 = PARSE_FLAG(19, MOVE_NONE); + u16 move4 = PARSE_FLAG(20, MOVE_NONE); + enum ShinyMode shinyMode = PARSE_FLAG(21, SHINY_MODE_RANDOM); + bool8 gmaxFactor = PARSE_FLAG(22, FALSE); + u8 teraType = PARSE_FLAG(23, NUMBER_OF_MON_TYPES); + u8 dmaxLevel = PARSE_FLAG(24, 0); u8 evs[NUM_STATS] = {hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv}; u8 ivs[NUM_STATS] = {hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv}; @@ -566,7 +570,7 @@ void ScrCmd_createmon(struct ScriptContext *ctx) else Script_RequestEffects(SCREFF_V1); - gSpecialVar_Result = ScriptGiveMonParameterized(side, slot, species, level, item, ball, nature, abilityNum, gender, evs, ivs, moves, isShiny, gmaxFactor, teraType, dmaxLevel); + gSpecialVar_Result = ScriptGiveMonParameterized(side, slot, species, level, item, ball, nature, abilityNum, gender, evs, ivs, moves, shinyMode, gmaxFactor, teraType, dmaxLevel); } #undef PARSE_FLAG diff --git a/test/pokemon.c b/test/pokemon.c index 61c9e86c04..6079bd28bd 100644 --- a/test/pokemon.c +++ b/test/pokemon.c @@ -304,7 +304,7 @@ TEST("givemon [all]") ZeroPlayerPartyMons(); RUN_OVERWORLD_SCRIPT( - givemon SPECIES_WOBBUFFET, 100, item=ITEM_LEFTOVERS, ball=ITEM_MASTER_BALL, nature=NATURE_BOLD, abilityNum=2, gender=MON_MALE, hpEv=1, atkEv=2, defEv=3, speedEv=4, spAtkEv=5, spDefEv=6, hpIv=7, atkIv=8, defIv=9, speedIv=10, spAtkIv=11, spDefIv=12, move1=MOVE_SCRATCH, move2=MOVE_SPLASH, move3=MOVE_CELEBRATE, move4=MOVE_EXPLOSION, isShiny=TRUE, gmaxFactor=TRUE, teraType=TYPE_FIRE, dmaxLevel=7; + givemon SPECIES_WOBBUFFET, 100, item=ITEM_LEFTOVERS, ball=ITEM_MASTER_BALL, nature=NATURE_BOLD, abilityNum=2, gender=MON_MALE, hpEv=1, atkEv=2, defEv=3, speedEv=4, spAtkEv=5, spDefEv=6, hpIv=7, atkIv=8, defIv=9, speedIv=10, spAtkIv=11, spDefIv=12, move1=MOVE_SCRATCH, move2=MOVE_SPLASH, move3=MOVE_CELEBRATE, move4=MOVE_EXPLOSION, shinyMode=SHINY_MODE_ALWAYS, gmaxFactor=TRUE, teraType=TYPE_FIRE, dmaxLevel=7; ); EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_WOBBUFFET); @@ -363,13 +363,13 @@ TEST("givemon [vars]") VarSet(VAR_TEMP_6, MOVE_SPLASH); VarSet(VAR_TEMP_7, MOVE_CELEBRATE); VarSet(VAR_TEMP_8, MOVE_EXPLOSION); - VarSet(VAR_TEMP_9, TRUE); + VarSet(VAR_TEMP_9, SHINY_MODE_ALWAYS); VarSet(VAR_TEMP_A, TRUE); VarSet(VAR_TEMP_B, TYPE_FIRE); VarSet(VAR_TEMP_E, 7); RUN_OVERWORLD_SCRIPT( - givemon VAR_TEMP_C, VAR_TEMP_D, item=VAR_0x8000, ball=VAR_0x8001, nature=VAR_0x8002, abilityNum=VAR_0x8003, gender=VAR_0x8004, hpEv=VAR_0x8005, atkEv=VAR_0x8006, defEv=VAR_0x8007, speedEv=VAR_0x8008, spAtkEv=VAR_0x8009, spDefEv=VAR_0x800A, hpIv=VAR_0x800B, atkIv=VAR_TEMP_0, defIv=VAR_TEMP_1, speedIv=VAR_TEMP_2, spAtkIv=VAR_TEMP_3, spDefIv=VAR_TEMP_4, move1=VAR_TEMP_5, move2=VAR_TEMP_6, move3=VAR_TEMP_7, move4=VAR_TEMP_8, isShiny=VAR_TEMP_9, gmaxFactor=VAR_TEMP_A, teraType=VAR_TEMP_B, dmaxLevel=VAR_TEMP_E; + givemon VAR_TEMP_C, VAR_TEMP_D, item=VAR_0x8000, ball=VAR_0x8001, nature=VAR_0x8002, abilityNum=VAR_0x8003, gender=VAR_0x8004, hpEv=VAR_0x8005, atkEv=VAR_0x8006, defEv=VAR_0x8007, speedEv=VAR_0x8008, spAtkEv=VAR_0x8009, spDefEv=VAR_0x800A, hpIv=VAR_0x800B, atkIv=VAR_TEMP_0, defIv=VAR_TEMP_1, speedIv=VAR_TEMP_2, spAtkIv=VAR_TEMP_3, spDefIv=VAR_TEMP_4, move1=VAR_TEMP_5, move2=VAR_TEMP_6, move3=VAR_TEMP_7, move4=VAR_TEMP_8, shinyMode=VAR_TEMP_9, gmaxFactor=VAR_TEMP_A, teraType=VAR_TEMP_B, dmaxLevel=VAR_TEMP_E; ); EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_WOBBUFFET); From b0b92532ff477aae5633553c1f8cda9ade45eb05 Mon Sep 17 00:00:00 2001 From: cawtds <38510667+cawtds@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:17:44 +0200 Subject: [PATCH 27/50] Fix EV display in debug menu (#7848) --- src/debug.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/debug.c b/src/debug.c index 99e8ee85f0..8aad180ac1 100644 --- a/src/debug.c +++ b/src/debug.c @@ -55,6 +55,7 @@ #include "strings.h" #include "string_util.h" #include "task.h" +#include "tv.h" #include "pokemon_summary_screen.h" #include "wild_encounter.h" #include "constants/abilities.h" @@ -2576,11 +2577,11 @@ static void DebugAction_Give_Pokemon_SelectDynamaxLevel(u8 taskId) } } -static void Debug_Display_StatInfo(const u8* text, u32 stat, u32 value, u32 digit, u8 windowId) +static void Debug_Display_StatInfo(const u8* text, u32 stat, u32 value, u32 digit, u8 windowId, u32 maxValue) { StringCopy(gStringVar1, gStatNamesTable[stat]); StringCopy(gStringVar2, gText_DigitIndicator[digit]); - ConvertIntToDecimalStringN(gStringVar3, value, STR_CONV_MODE_LEADING_ZEROS, 2); + ConvertIntToDecimalStringN(gStringVar3, value, STR_CONV_MODE_LEADING_ZEROS, CountDigits(maxValue)); StringCopyPadded(gStringVar3, gStringVar3, CHAR_SPACE, 15); StringExpandPlaceholders(gStringVar4, text); AddTextPrinterParameterized(windowId, DEBUG_MENU_FONT, gStringVar4, 0, 0, 0, NULL); @@ -2600,7 +2601,7 @@ static void DebugAction_Give_Pokemon_SelectGigantamaxFactor(u8 taskId) sDebugMonData->gmaxFactor = gTasks[taskId].tInput; gTasks[taskId].tInput = 0; gTasks[taskId].tDigit = 0; - Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_IVS); gTasks[taskId].func = DebugAction_Give_Pokemon_SelectIVs; } else if (JOY_NEW(B_BUTTON)) @@ -2617,7 +2618,7 @@ static void DebugAction_Give_Pokemon_SelectIVs(u8 taskId) { PlaySE(SE_SELECT); Debug_HandleInput_Numeric(taskId, 0, MAX_PER_STAT_IVS, 3); - Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_IVS); } //If A or B button @@ -2633,7 +2634,7 @@ static void DebugAction_Give_Pokemon_SelectIVs(u8 taskId) gTasks[taskId].tInput = 0; gTasks[taskId].tDigit = 0; - Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + Debug_Display_StatInfo(sDebugText_IVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_IVS); gTasks[taskId].func = DebugAction_Give_Pokemon_SelectIVs; } else @@ -2642,7 +2643,7 @@ static void DebugAction_Give_Pokemon_SelectIVs(u8 taskId) gTasks[taskId].tDigit = 0; gTasks[taskId].tIterator = 0; - Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_EVS); gTasks[taskId].func = DebugAction_Give_Pokemon_SelectEVs; } } @@ -2690,7 +2691,7 @@ static void DebugAction_Give_Pokemon_SelectEVs(u8 taskId) { PlaySE(SE_SELECT); Debug_HandleInput_Numeric(taskId, 0, MAX_PER_STAT_EVS, 4); - Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_EVS); } //If A or B button @@ -2705,7 +2706,7 @@ static void DebugAction_Give_Pokemon_SelectEVs(u8 taskId) gTasks[taskId].tIterator++; gTasks[taskId].tInput = 0; gTasks[taskId].tDigit = 0; - Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_EVS); gTasks[taskId].func = DebugAction_Give_Pokemon_SelectEVs; } else @@ -2722,7 +2723,7 @@ static void DebugAction_Give_Pokemon_SelectEVs(u8 taskId) } PlaySE(SE_FAILURE); - Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId); + Debug_Display_StatInfo(sDebugText_EVs, gTasks[taskId].tIterator, gTasks[taskId].tInput, gTasks[taskId].tDigit, gTasks[taskId].tSubWindowId, MAX_PER_STAT_EVS); gTasks[taskId].func = DebugAction_Give_Pokemon_SelectEVs; } else From d84d5cb0d4b446a1a7e9f94e7a635d8bca05558c Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Wed, 8 Oct 2025 10:36:30 +0200 Subject: [PATCH 28/50] Fix pikachu starter icon (#7879) --- graphics/pokemon/pikachu/starter/icon.png | Bin 405 -> 388 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/graphics/pokemon/pikachu/starter/icon.png b/graphics/pokemon/pikachu/starter/icon.png index 737fababd7d49b68955719525024863c8f2ff490..891f0d1b3e870760b696c9a5b4cb5ba286147e4d 100644 GIT binary patch delta 361 zcmV-v0ha!i1B3&RB!2;OQb$4nuFf3k0000mP)t-sVw{6}dwadTwEzGAb92q4toF2n zTJFV*#jGjz_9^yaQj~km#iY%>DV$F>70Dn_-fGS)T5hjWb2x%~w zhaL55?Qk}i(rCv<7kSdw34VYe*CnO_SxV5K0NE1d2y=$M_jl?0$P@`Uo9Z(xF zQqMY2?C3wm#|V1wuv1Kzkf3^Ly|?C$F~&Kdd6|KuelS#j9p?dOhwA8;=puvZ1dk=i5U**H}vwU~~O`{m084N)W00000NkvXX Hu0mjfnDM8! delta 378 zcmV-=0fqjA1C;}iB!3`dNK#Dz0D2?<0Dyx40Qvp^0D$QL0Cg__0P0@=06Lcd02gnL zEp)*E001yhOjJc;oP&ddbG^O2|NsBJq(S!fDb->@_LM1$d;ele&F*uY|Foo}#s6xo zoMK{HK|w)TsgPs<008t!L_t(YiS3lp3d0}_Ma7u($>jh4c7HLtQcWrh_OL z5#glt1)jZbP|F^+yF2_Mqg$^DnM-j$-AEb;oQ$4(z{4; zJrT1VL5!^NeUDa%;`(-{-WEjNUR-ZW=zj}{5|Sc9e`}pmgYY9-g+xX~eX#`$(9hsW z?DmLu28M*~!E=Tbjvuv+;IU?$eia>Z3utx&I>h@vCFXtqkBvacMr;Ja!yp@hj1f1l`oDg7 Y0hC4(vH__#b^rhX07*qoM6N<$g3({C>;M1& From 7cf9185477a12690b4897fdc3ddfc19179dfab38 Mon Sep 17 00:00:00 2001 From: RavePossum <145081120+ravepossum@users.noreply.github.com> Date: Wed, 8 Oct 2025 02:39:06 -0600 Subject: [PATCH 29/50] Fix right player position battle partner target display (#7878) --- src/battle_controller_player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index d8d6b84ad9..c4ede0eab4 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2002,7 +2002,7 @@ static void PlayerHandleChooseAction(u32 battler) else if (gAiBattleData->chosenTarget[B_POSITION_PLAYER_RIGHT] == B_POSITION_PLAYER_LEFT) StringAppend(gStringVar1, COMPOUND_STRING(" {DOWN_ARROW}-")); else if (gAiBattleData->chosenTarget[B_POSITION_PLAYER_RIGHT] == B_POSITION_PLAYER_RIGHT) - StringAppend(gStringVar1, COMPOUND_STRING(" {DOWN_ARROW}-")); + StringAppend(gStringVar1, COMPOUND_STRING(" -{DOWN_ARROW}")); } else if (moveTarget == MOVE_TARGET_BOTH) { From 3fb9e1a11cb071c11ca18fd13141afb2c33087fc Mon Sep 17 00:00:00 2001 From: RavePossum <145081120+ravepossum@users.noreply.github.com> Date: Wed, 8 Oct 2025 02:40:40 -0600 Subject: [PATCH 30/50] Ensure last used ball and move description window sprites don't free palette too early (#7875) --- src/battle_interface.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/battle_interface.c b/src/battle_interface.c index 2aee6460cb..2264d94ce4 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -2970,7 +2970,8 @@ void TryAddLastUsedBallItemSprites(void) static void DestroyLastUsedBallWinGfx(struct Sprite *sprite) { FreeSpriteTilesByTag(TAG_LAST_BALL_WINDOW); - FreeSpritePaletteByTag(TAG_ABILITY_POP_UP); + if (GetSpriteTileStartByTag(MOVE_INFO_WINDOW_TAG) == 0xFFFF) + FreeSpritePaletteByTag(TAG_ABILITY_POP_UP); DestroySprite(sprite); gBattleStruct->ballSpriteIds[1] = MAX_SPRITES; } @@ -3007,7 +3008,8 @@ void TryToHideMoveInfoWindow(void) static void DestroyMoveInfoWinGfx(struct Sprite *sprite) { FreeSpriteTilesByTag(MOVE_INFO_WINDOW_TAG); - FreeSpritePaletteByTag(TAG_ABILITY_POP_UP); + if (GetSpriteTileStartByTag(TAG_LAST_BALL_WINDOW) == 0xFFFF) + FreeSpritePaletteByTag(TAG_ABILITY_POP_UP); DestroySprite(sprite); gBattleStruct->moveInfoSpriteId = MAX_SPRITES; } From 5ab457045e2407622a40c27fc3706ce9eff160ab Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:41:15 +0200 Subject: [PATCH 31/50] add Ddaretrogamer as a contributor for design (#7869) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CREDITS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index c4ed2ea558..7d2e4d4bbf 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -348,6 +348,15 @@ "doc", "code" ] + }, + { + "login": "Ddaretrogamer", + "name": "Phantonomy", + "avatar_url": "https://avatars.githubusercontent.com/u/131238004?v=4", + "profile": "https://github.com/Ddaretrogamer", + "contributions": [ + "design" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 17809a3c3f..4c0b6a3d3e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -60,6 +60,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d lordraindance2
lordraindance2

💻 Pablo Pena
Pablo Pena

💻 tustin2121
tustin2121

📖 💻 + Phantonomy
Phantonomy

🎨 From 4d12c35eb3156cf2a6e7e0ad9d04bcb4b39e9356 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:01:29 +0100 Subject: [PATCH 32/50] Fixes Steadfast not activating + tests (#7886) --- data/battle_scripts_1.s | 4 +-- test/battle/ability/steadfast.c | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 9d75ac304e..f1eeeccdc9 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -6843,7 +6843,7 @@ BattleScript_PowderMoveNoEffectWaitMsg: BattleScript_MoveUsedFlinched:: printstring STRINGID_PKMNFLINCHED waitmessage B_WAIT_TIME_LONG - waitmessage B_WAIT_TIME_LONG + jumpifability BS_ATTACKER, ABILITY_STEADFAST, BattleScript_TryActivateSteadFast BattleScript_MoveUsedFlinchedEnd: goto BattleScript_MoveEnd BattleScript_TryActivateSteadFast: @@ -6853,7 +6853,7 @@ BattleScript_TryActivateSteadFast: copybyte gBattlerAbility, gBattlerAttacker call BattleScript_AbilityPopUp statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR, BattleScript_MoveUsedFlinchedEnd - setbyte gBattleCommunication STAT_SPEED + setbyte gBattleCommunication, STAT_SPEED stattextbuffer printstring STRINGID_ATTACKERABILITYSTATRAISE waitmessage B_WAIT_TIME_LONG diff --git a/test/battle/ability/steadfast.c b/test/battle/ability/steadfast.c index fc35e94278..2d6fb5d901 100644 --- a/test/battle/ability/steadfast.c +++ b/test/battle/ability/steadfast.c @@ -1,4 +1,56 @@ #include "global.h" #include "test/battle.h" +SINGLE_BATTLE_TEST("Steadfast boosts Speed when the user attempts to move but is flinched") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_STEADFAST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_STEADFAST); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Steadfast doesn't activate if the user wasn't flinched") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + ASSUME(GetItemHoldEffect(ITEM_COVERT_CLOAK) == HOLD_EFFECT_COVERT_CLOAK); + PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_STEADFAST); Item(ITEM_COVERT_CLOAK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_STEADFAST); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Steadfast doesn't activate if the user has already moved") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BITE, MOVE_EFFECT_FLINCH)); + ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT); + PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_STEADFAST); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_BITE, target: playerLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft); + NOT ABILITY_POPUP(playerLeft, ABILITY_STEADFAST); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + TO_DO_BATTLE_TEST("TODO: Write Steadfast (Ability) test titles") From c45566511525d8124663a69bd8211b7f6025cf47 Mon Sep 17 00:00:00 2001 From: Raymond Dodge Date: Wed, 8 Oct 2025 10:03:20 -0400 Subject: [PATCH 33/50] Add Struggle tests, weakness berry tests and prevent Struggle from activating Silk Scarf and Chilan Berry (#7880) --- src/battle_main.c | 9 +- test/battle/ability/color_change.c | 20 ++++ test/battle/ability/protean.c | 16 +++ test/battle/hold_effect/gems.c | 28 +++++ test/battle/hold_effect/type_power.c | 24 +++++ test/battle/hold_effect/weakness_berry.c | 124 +++++++++++++++++++++++ test/battle/move_effect/struggle.c | 75 ++++++++++++++ 7 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 test/battle/hold_effect/weakness_berry.c create mode 100644 test/battle/move_effect/struggle.c diff --git a/src/battle_main.c b/src/battle_main.c index 6a45019ff1..26755eda97 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5862,11 +5862,11 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum MonState enum ItemHoldEffect holdEffect; enum Gimmick gimmick = GetActiveGimmick(battler); - if (move == MOVE_STRUGGLE) - return TYPE_NORMAL; - if (state == MON_IN_BATTLE) { + if (moveEffect == EFFECT_STRUGGLE) + return TYPE_MYSTERY; + species = gBattleMons[battler].species; heldItem = gBattleMons[battler].item; holdEffect = GetBattlerHoldEffect(battler, TRUE); @@ -6119,8 +6119,7 @@ void SetTypeBeforeUsingMove(u32 move, u32 battler) && GetBattleMoveType(move) == GetItemSecondaryId(heldItem) && effect != EFFECT_PLEDGE && effect != EFFECT_OHKO - && effect != EFFECT_SHEER_COLD - && effect != EFFECT_STRUGGLE) + && effect != EFFECT_SHEER_COLD) { gSpecialStatuses[battler].gemParam = GetBattlerHoldEffectParam(battler); gSpecialStatuses[battler].gemBoost = TRUE; diff --git a/test/battle/ability/color_change.c b/test/battle/ability/color_change.c index e097035f62..6ea5d9537b 100644 --- a/test/battle/ability/color_change.c +++ b/test/battle/ability/color_change.c @@ -153,3 +153,23 @@ SINGLE_BATTLE_TEST("Color Change changes the type to Normal when a Pokemon is hi MESSAGE("The opposing Kecleon's Color Change made it the Normal type!"); } } + +SINGLE_BATTLE_TEST("Color Change does not change the type to Normal when a Pokemon is hit by Struggle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(player, MOVE_SOAK); } + TURN { MOVE(player, MOVE_STRUGGLE); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, player); + MESSAGE("The opposing Kecleon transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Normal type!"); + } + } +} diff --git a/test/battle/ability/protean.c b/test/battle/ability/protean.c index c5d141d244..fe4ae25e60 100644 --- a/test/battle/ability/protean.c +++ b/test/battle/ability/protean.c @@ -54,3 +54,19 @@ SINGLE_BATTLE_TEST("Protean changes the type of the user only once per switch in ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); } } + +SINGLE_BATTLE_TEST("Protean does not change the user's type when using Struggle") +{ + GIVEN { + PLAYER(SPECIES_REGIROCK); + OPPONENT(SPECIES_GRENINJA) { Ability(ABILITY_PROTEAN); } + } WHEN { + TURN { MOVE(opponent, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PROTEAN); + MESSAGE("The opposing Greninja transformed into the Normal type!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, opponent); + } +} diff --git a/test/battle/hold_effect/gems.c b/test/battle/hold_effect/gems.c index b2d843e1d5..b597beeab7 100644 --- a/test/battle/hold_effect/gems.c +++ b/test/battle/hold_effect/gems.c @@ -26,6 +26,34 @@ SINGLE_BATTLE_TEST("Gem is consumed when it corresponds to the type of a move") } } +SINGLE_BATTLE_TEST("Gem is not consumed when using Struggle", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_NORMAL_GEM; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_GEMS); + ASSUME(GetItemSecondaryId(item) == GetMoveType(MOVE_STRUGGLE)); + } + PLAYER(SPECIES_WOBBUFFET) { Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + SINGLE_BATTLE_TEST("Gem boost is only applied once") { s16 boostedHit; diff --git a/test/battle/hold_effect/type_power.c b/test/battle/hold_effect/type_power.c index 5afe2a39c8..c03d2487d5 100644 --- a/test/battle/hold_effect/type_power.c +++ b/test/battle/hold_effect/type_power.c @@ -53,3 +53,27 @@ SINGLE_BATTLE_TEST("Type-enhancing items increase the base power of moves by 20% } } } + +SINGLE_BATTLE_TEST("Type-enhancing items do not increase the power of Struggle", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_SILK_SCARF; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_TYPE_POWER); + ASSUME(GetItemSecondaryId(item) == GetMoveType(MOVE_STRUGGLE)); + } + PLAYER(SPECIES_WOBBUFFET) { Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/battle/hold_effect/weakness_berry.c b/test/battle/hold_effect/weakness_berry.c new file mode 100644 index 0000000000..17adb08aa3 --- /dev/null +++ b/test/battle/hold_effect/weakness_berry.c @@ -0,0 +1,124 @@ +#include "global.h" +#include "test/battle.h" + +static const u16 sMoveItemTable[][4] = +{ + { TYPE_NORMAL, MOVE_SCRATCH, ITEM_CHILAN_BERRY, SPECIES_WOBBUFFET }, + { TYPE_FIGHTING, MOVE_KARATE_CHOP, ITEM_CHOPLE_BERRY, SPECIES_RAMPARDOS }, + { TYPE_FLYING, MOVE_WING_ATTACK, ITEM_COBA_BERRY, SPECIES_HARIYAMA }, + { TYPE_POISON, MOVE_POISON_STING, ITEM_KEBIA_BERRY, SPECIES_GOGOAT }, + { TYPE_GROUND, MOVE_MUD_SHOT, ITEM_SHUCA_BERRY, SPECIES_RAMPARDOS }, + { TYPE_ROCK, MOVE_ROCK_THROW, ITEM_CHARTI_BERRY, SPECIES_CORVISQUIRE }, + { TYPE_BUG, MOVE_BUG_BITE, ITEM_TANGA_BERRY, SPECIES_WOBBUFFET }, + { TYPE_GHOST, MOVE_SHADOW_PUNCH, ITEM_KASIB_BERRY, SPECIES_WOBBUFFET }, + { TYPE_STEEL, MOVE_METAL_CLAW, ITEM_BABIRI_BERRY, SPECIES_RAMPARDOS }, + { TYPE_FIRE, MOVE_EMBER, ITEM_OCCA_BERRY, SPECIES_GOGOAT }, + { TYPE_WATER, MOVE_WATER_GUN, ITEM_PASSHO_BERRY, SPECIES_RAMPARDOS }, + { TYPE_GRASS, MOVE_VINE_WHIP, ITEM_RINDO_BERRY, SPECIES_RAMPARDOS }, + { TYPE_ELECTRIC, MOVE_THUNDER_SHOCK, ITEM_WACAN_BERRY, SPECIES_CORVISQUIRE }, + { TYPE_PSYCHIC, MOVE_CONFUSION, ITEM_PAYAPA_BERRY, SPECIES_HARIYAMA }, + { TYPE_ICE, MOVE_AURORA_BEAM, ITEM_YACHE_BERRY, SPECIES_DRAGONAIR }, + { TYPE_DRAGON, MOVE_DRAGON_BREATH, ITEM_HABAN_BERRY, SPECIES_DRAGONAIR }, + { TYPE_DARK, MOVE_BITE, ITEM_COLBUR_BERRY, SPECIES_WOBBUFFET }, + { TYPE_FAIRY, MOVE_DISARMING_VOICE, ITEM_ROSELI_BERRY, SPECIES_DRAGONAIR }, +}; + +SINGLE_BATTLE_TEST("Weakness berries decrease the base power of moves by half", s16 damage) +{ + u32 move = 0, item = 0, type = 0, defender = 0; + + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; defender = sMoveItemTable[j][3]; item = ITEM_NONE; } + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; defender = sMoveItemTable[j][3]; item = sMoveItemTable[j][2]; } + } + + GIVEN { + ASSUME(GetMovePower(move) > 0); + ASSUME(GetMoveType(move) == type); + ASSUME(GetSpeciesType(defender, 0) == GetSpeciesType(defender, 1)); + if (type != TYPE_NORMAL) { + ASSUME(gTypeEffectivenessTable[type][GetSpeciesType(defender, 0)] > UQ_4_12(1.0)); + } + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == type); + } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(defender) { Item(item); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (1 == i % 2) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) { + EXPECT_MUL_EQ(results[j*2].damage, Q_4_12(0.5), results[(j*2)+1].damage); + } + } +} + +SINGLE_BATTLE_TEST("Weakness berries do not activate unless a move is super effective", s16 damage) +{ + u32 move = 0, item = 0, type = 0, defender = 0; + + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) + { + if (TYPE_NORMAL == type) + { + // ITEM_CHILAN_BERRY activates without a weakness + } + else if (TYPE_FAIRY == type) + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; defender = SPECIES_WOBBUFFET; } + } + else + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; defender = SPECIES_SABLEYE; } + } + } + + GIVEN { + ASSUME(GetMovePower(move) > 0); + ASSUME(uq4_12_multiply(gTypeEffectivenessTable[type][GetSpeciesType(defender, 0)], + gTypeEffectivenessTable[type][GetSpeciesType(defender, 1)]) <= UQ_4_12(1.0)); + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == type); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(defender) { Item(item); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Weakness berries do not decrease the power of Struggle", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHILAN_BERRY; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == TYPE_NORMAL); + } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The Chilan Berry weakened the damage to the opposing Wobbuffet!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} diff --git a/test/battle/move_effect/struggle.c b/test/battle/move_effect/struggle.c new file mode 100644 index 0000000000..5bf9de6207 --- /dev/null +++ b/test/battle/move_effect/struggle.c @@ -0,0 +1,75 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Struggle deals recoil 1/4 of damage dealt (Gen 2-3)") + +SINGLE_BATTLE_TEST("Struggle deals recoil 1/4 of user's hp (Gen 4+)") +{ + ASSUME(GetMoveEffect(MOVE_STRUGGLE) == EFFECT_STRUGGLE); + + s16 recoil; + u32 atkStat = 0; + u32 hpStat = 0; + + PARAMETRIZE { atkStat = 100; hpStat = 200; } + PARAMETRIZE { atkStat = 50; hpStat = 200; } + PARAMETRIZE { atkStat = 100; hpStat = 300; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(hpStat); HP(hpStat); Attack(atkStat); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(player, captureDamage: &recoil); + } THEN { + EXPECT_MUL_EQ(hpStat, Q_4_12(0.25), recoil); + } +} + +SINGLE_BATTLE_TEST("Struggle can hit ghost types") +{ + ASSUME(GetSpeciesType(SPECIES_DRIFBLIM, 0) == TYPE_GHOST); + + s16 damage; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DRIFBLIM); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &damage); + } THEN { + EXPECT_NE(0, damage); + } +} + +SINGLE_BATTLE_TEST("Struggle does not receive normal-type STAB") +{ + // Compare with Cut, which does receive normal-type STAB + ASSUME(GetSpeciesType(SPECIES_ZANGOOSE, 0) == GetMoveType(MOVE_STRUGGLE)); + ASSUME(GetMovePower(MOVE_CUT) == GetMovePower(MOVE_STRUGGLE)); + ASSUME(GetMoveCategory(MOVE_CUT) == GetMoveCategory(MOVE_STRUGGLE)); + ASSUME(GetMoveType(MOVE_CUT) == GetMoveType(MOVE_STRUGGLE)); + + s16 cutDamage; + s16 struggleDamage; + + GIVEN { + PLAYER(SPECIES_ZANGOOSE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CUT); } + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CUT, player); + HP_BAR(opponent, captureDamage: &cutDamage); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &struggleDamage); + } THEN { + EXPECT_MUL_EQ(struggleDamage, Q_4_12(1.5), cutDamage); + } +} From e44c9866e7f45422da371b0b8bcc633025b55996 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Wed, 8 Oct 2025 17:54:23 +0200 Subject: [PATCH 34/50] Fix hgss pokedex when catching mon with terrain (#7884) Co-authored-by: Bassoonian --- include/pokedex.h | 1 + src/pokedex.c | 16 ++++++++++++---- src/pokedex_plus_hgss.c | 13 +++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/include/pokedex.h b/include/pokedex.h index 0dfc802312..c558cd1b07 100644 --- a/include/pokedex.h +++ b/include/pokedex.h @@ -8,6 +8,7 @@ void ResetPokedex(void); u16 GetNationalPokedexCount(u8 caseID); u16 GetHoennPokedexCount(u8 caseID); u8 DisplayCaughtMonDexPage(u16 species, bool32 isShiny, u32 personality); +u32 Pokedex_CreateCaughtMonSprite(u32 species, s32 x, s32 y); s8 GetSetPokedexFlag(enum NationalDexOrder nationalDexNo, u8 caseID); void DrawFootprint(u8 windowId, u16 species); u16 CreateMonSpriteFromNationalDexNumber(enum NationalDexOrder nationalNum, s16 x, s16 y, u16 paletteSlot); diff --git a/src/pokedex.c b/src/pokedex.c index bcd6cb607d..7f2ebe2d9c 100644 --- a/src/pokedex.c +++ b/src/pokedex.c @@ -4048,6 +4048,17 @@ static void LoadDexMonPalette(u32 taskId, bool32 isShiny) LoadPalette(paletteData, OBJ_PLTT_ID(paletteNum), PLTT_SIZE_4BPP); } +u32 Pokedex_CreateCaughtMonSprite(u32 species, s32 x, s32 y) +{ + u32 spriteId; + + SetMultiuseSpriteTemplateToPokemon(species, GetCatchingBattler()); + spriteId = CreateSprite(&gMultiuseSpriteTemplate, x, y, 0); + gSprites[spriteId].oam.priority = 0; + gSprites[spriteId].callback = SpriteCallbackDummy; + return spriteId; +} + static void Task_DisplayCaughtMonDexPage(u8 taskId) { u8 spriteId; @@ -4096,11 +4107,8 @@ static void Task_DisplayCaughtMonDexPage(u8 taskId) break; case 4: // We're using a different mon sprite creation method, because we don't have enough memory to safely use CreateMonPicSprite. - SetMultiuseSpriteTemplateToPokemon(species, GetCatchingBattler()); - spriteId = CreateSprite(&gMultiuseSpriteTemplate, MON_PAGE_X, MON_PAGE_Y, 0); + spriteId = Pokedex_CreateCaughtMonSprite(species, MON_PAGE_X, MON_PAGE_Y); gTasks[taskId].tMonSpriteId = spriteId; - gSprites[spriteId].oam.priority = 0; - gSprites[spriteId].callback = SpriteCallbackDummy; LoadDexMonPalette(taskId, FALSE); gSprites[spriteId].oam.priority = 0; BeginNormalPaletteFade(PALETTES_ALL, 0, 0x10, 0, RGB_BLACK); diff --git a/src/pokedex_plus_hgss.c b/src/pokedex_plus_hgss.c index 49e1016d32..2e4463ac80 100644 --- a/src/pokedex_plus_hgss.c +++ b/src/pokedex_plus_hgss.c @@ -4148,13 +4148,18 @@ void Task_DisplayCaughtMonDexPageHGSS(u8 taskId) gTasks[taskId].tState++; break; case 4: - spriteId = CreateMonSpriteFromNationalDexNumberHGSS(dexNum, MON_PAGE_X, MON_PAGE_Y, 0); - gSprites[spriteId].oam.priority = 0; + { + u32 personality = ((u16)gTasks[taskId].tPersonalityHi << 16) | (u16)gTasks[taskId].tPersonalityLo; + const u16 *paletteData = GetMonSpritePalFromSpeciesAndPersonality(species, FALSE, personality); + + spriteId = Pokedex_CreateCaughtMonSprite(species, MON_PAGE_X, MON_PAGE_Y); + LoadPalette(paletteData, OBJ_PLTT_ID(gSprites[spriteId].oam.paletteNum), PLTT_SIZE_4BPP); BeginNormalPaletteFade(PALETTES_ALL, 0, 0x10, 0, RGB_BLACK); SetVBlankCallback(gPokedexVBlankCB); gTasks[taskId].tMonSpriteId = spriteId; gTasks[taskId].tState++; break; + } case 5: SetGpuReg(REG_OFFSET_BLDCNT, 0); SetGpuReg(REG_OFFSET_BLDALPHA, 0); @@ -4532,8 +4537,8 @@ static u32 GetPokedexMonPersonality(u16 species) static u16 CreateMonSpriteFromNationalDexNumberHGSS(u16 nationalNum, s16 x, s16 y, u16 paletteSlot) { - nationalNum = NationalPokedexNumToSpeciesHGSS(nationalNum); - return CreateMonPicSprite(nationalNum, FALSE, GetPokedexMonPersonality(nationalNum), TRUE, x, y, paletteSlot, TAG_NONE); + u32 species = NationalPokedexNumToSpeciesHGSS(nationalNum); + return CreateMonPicSprite(nationalNum, FALSE, GetPokedexMonPersonality(species), TRUE, x, y, paletteSlot, TAG_NONE); } static u16 GetPokemonScaleFromNationalDexNumber(u16 nationalNum) From 015a0fea0361e274849943ff40bb3205ff932df6 Mon Sep 17 00:00:00 2001 From: Bivurnum <147376167+Bivurnum@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:10:33 -0500 Subject: [PATCH 35/50] Bug Fix: NPC follower not inheriting facing direction upon creation (#7895) --- src/follower_npc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/follower_npc.c b/src/follower_npc.c index 11b066c12c..8bc3a61697 100644 --- a/src/follower_npc.c +++ b/src/follower_npc.c @@ -187,6 +187,7 @@ static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, c u32 npcY = gObjectEvents[eventObjId].currentCoords.y; const u8 *script; u32 flag; + u16 facingDirection = gObjectEvents[eventObjId].facingDirection; flag = GetObjectEventFlagIdByLocalIdAndMap(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup); // If the object does not have an event flag, don't create follower. @@ -210,7 +211,7 @@ static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, c SetFollowerNPCData(FNPC_DATA_OBJ_ID, TrySpawnObjectEventTemplate(&npc, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, npcX, npcY)); follower = &gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]; MoveObjectEventToMapCoords(follower, npcX, npcY); - ObjectEventTurn(follower, gObjectEvents[eventObjId].facingDirection); + ObjectEventTurn(follower, facingDirection); follower->movementType = MOVEMENT_TYPE_NONE; gSprites[follower->spriteId].callback = MovementType_None; From ffe0936ff160cd3ebea858030fc4c8283d6e2cd3 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Wed, 8 Oct 2025 19:44:31 +0200 Subject: [PATCH 36/50] Fix ShouldPivot overwriting random memory (#7882) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_ai_util.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 5237c4aff2..5913c0842c 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3174,14 +3174,16 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov u32 battlerToSwitch; u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); - battlerToSwitch = gBattleStruct->AI_monToSwitchIntoId[battlerAtk]; - // Palafin always wants to activate Zero to Hero if (gBattleMons[battlerAtk].species == SPECIES_PALAFIN_ZERO && gBattleMons[battlerAtk].ability == ABILITY_ZERO_TO_HERO && CountUsablePartyMons(battlerAtk) != 0) return SHOULD_PIVOT; + battlerToSwitch = gAiLogicData->mostSuitableMonId[battlerAtk]; + // This shouldn't ever happen, but it's there to make sure we don't accidentally read past the gParty array. + if (battlerToSwitch >= PARTY_SIZE) + battlerToSwitch = 0; if (PartyBattlerShouldAvoidHazards(battlerAtk, battlerToSwitch)) return DONT_PIVOT; From 5b5b813c1dacd91070571c4bcbaa7ebb6e6540c4 Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:53:25 -0700 Subject: [PATCH 37/50] Updated PR template to make existing credit policy clearer (#7864) Co-authored-by: Pawkkie <61265402+Pawkkie@users.noreply.github.com> --- .github/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dc900e224e..21785498fb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -24,6 +24,7 @@ + From 41751451f689218fc83fe2ff11e4f32d36d68934 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 9 Oct 2025 15:26:26 +0200 Subject: [PATCH 38/50] Fix fusion pokemon aquiring illegal movesets (#7896) --- include/pokemon.h | 20 ++- src/data/pokemon/form_change_table_pointers.h | 19 +++ src/data/pokemon/form_change_tables.h | 12 +- src/party_menu.c | 120 ++++++++++++++++-- 4 files changed, 151 insertions(+), 20 deletions(-) diff --git a/include/pokemon.h b/include/pokemon.h index 1e31c425b6..f58ebbba1a 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -631,6 +631,13 @@ struct FormChange u16 param3; }; +enum FusionExtraMoveHandling +{ + FORGET_EXTRA_MOVES, + SWAP_EXTRA_MOVES_KYUREM_WHITE, + SWAP_EXTRA_MOVES_KYUREM_BLACK +}; + struct Fusion { u16 fusionStorageIndex; @@ -639,11 +646,22 @@ struct Fusion u16 targetSpecies2; u16 fusingIntoMon; u16 fusionMove; - u16 unfuseForgetMove; + enum FusionExtraMoveHandling extraMoveHandling; }; extern const struct Fusion *const gFusionTablePointers[NUM_SPECIES]; +#if P_FUSION_FORMS +#if P_FAMILY_KYUREM +#if P_FAMILY_RESHIRAM +extern const u16 gKyurenWhiteSwapMoveTable[][2]; +#endif //P_FAMILY_RESHIRAM +#if P_FAMILY_ZEKROM +extern const u16 gKyurenBlackSwapMoveTable[][2]; +#endif //P_FAMILY_ZEKROM +#endif //P_FAMILY_KYUREM +#endif //P_FUSION_FORMS + #define NUM_UNOWN_FORMS 28 #define GET_UNOWN_LETTER(personality) (( \ diff --git a/src/data/pokemon/form_change_table_pointers.h b/src/data/pokemon/form_change_table_pointers.h index ea2cdfd2a2..ad2e6a040d 100644 --- a/src/data/pokemon/form_change_table_pointers.h +++ b/src/data/pokemon/form_change_table_pointers.h @@ -34,3 +34,22 @@ const struct Fusion *const gFusionTablePointers[NUM_SPECIES] = #endif //P_FAMILY_CALYREX #endif //P_FUSION_FORMS }; + +#if P_FUSION_FORMS +#if P_FAMILY_KYUREM +#if P_FAMILY_RESHIRAM +const u16 gKyurenWhiteSwapMoveTable[][2] = +{ + {MOVE_SCARY_FACE, MOVE_FUSION_FLARE}, + {MOVE_GLACIATE, MOVE_ICE_BURN}, +}; +#endif //P_FAMILY_RESHIRAM +#if P_FAMILY_ZEKROM +const u16 gKyurenBlackSwapMoveTable[][2] = +{ + {MOVE_SCARY_FACE, MOVE_FUSION_BOLT}, + {MOVE_GLACIATE, MOVE_FREEZE_SHOCK}, +}; +#endif //P_FAMILY_ZEKROM +#endif //P_FAMILY_KYUREM +#endif //P_FUSION_FORMS diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index ed80ae2178..ad8a7e90ca 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -755,8 +755,8 @@ static const struct FormChange sLandorusFormChangeTable[] = { #if P_FAMILY_KYUREM static const struct Fusion sKyuremFusionTable[] = { - {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_RESHIRAM, SPECIES_KYUREM_WHITE}, - {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_ZEKROM, SPECIES_KYUREM_BLACK}, + {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_RESHIRAM, SPECIES_KYUREM_WHITE, MOVE_NONE, SWAP_EXTRA_MOVES_KYUREM_WHITE}, + {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_ZEKROM, SPECIES_KYUREM_BLACK, MOVE_NONE, SWAP_EXTRA_MOVES_KYUREM_BLACK}, {FUSION_TERMINATOR}, }; #endif //P_FAMILY_KYUREM @@ -998,8 +998,8 @@ static const struct FormChange sMimikyuTotemFormChangeTable[] = { #if P_FAMILY_NECROZMA static const struct Fusion sNecrozmaFusionTable[] = { - {1, ITEM_N_SOLARIZER, SPECIES_NECROZMA, SPECIES_SOLGALEO, SPECIES_NECROZMA_DUSK_MANE, MOVE_SUNSTEEL_STRIKE, MOVE_CONFUSION}, - {2, ITEM_N_LUNARIZER, SPECIES_NECROZMA, SPECIES_LUNALA, SPECIES_NECROZMA_DAWN_WINGS, MOVE_MOONGEIST_BEAM, MOVE_CONFUSION}, + {1, ITEM_N_SOLARIZER, SPECIES_NECROZMA, SPECIES_SOLGALEO, SPECIES_NECROZMA_DUSK_MANE, MOVE_SUNSTEEL_STRIKE, FORGET_EXTRA_MOVES}, + {2, ITEM_N_LUNARIZER, SPECIES_NECROZMA, SPECIES_LUNALA, SPECIES_NECROZMA_DAWN_WINGS, MOVE_MOONGEIST_BEAM, FORGET_EXTRA_MOVES}, {FUSION_TERMINATOR}, }; @@ -1266,8 +1266,8 @@ static const struct FormChange sUrshifuRapidStrikeFormChangeTable[] = { #if P_FAMILY_CALYREX static const struct Fusion sCalyrexFusionTable[] = { - {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_GLASTRIER, SPECIES_CALYREX_ICE, MOVE_GLACIAL_LANCE, MOVE_CONFUSION}, - {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_SPECTRIER, SPECIES_CALYREX_SHADOW, MOVE_ASTRAL_BARRAGE, MOVE_CONFUSION}, + {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_GLASTRIER, SPECIES_CALYREX_ICE, MOVE_GLACIAL_LANCE, FORGET_EXTRA_MOVES}, + {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_SPECTRIER, SPECIES_CALYREX_SHADOW, MOVE_ASTRAL_BARRAGE, FORGET_EXTRA_MOVES}, {FUSION_TERMINATOR}, }; #endif //P_FAMILY_CALYREX diff --git a/src/party_menu.c b/src/party_menu.c index e6568c34bf..3b05551cb2 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -6113,13 +6113,14 @@ void ItemUseCB_EvolutionStone(u8 taskId, TaskFunc task) #define tAnimWait data[2] #define tNextFunc 3 -#define fusionType data[7] -#define firstFusion data[8] -#define firstFusionSlot data[9] -#define fusionResult data[10] -#define secondFusionSlot data[11] -#define unfuseSecondMon data[12] -#define moveToLearn data[13] +#define fusionType data[6] +#define firstFusion data[7] +#define firstFusionSlot data[8] +#define fusionResult data[9] +#define secondFusionSlot data[10] +#define unfuseSecondMon data[11] +#define moveToLearn data[12] +#define tExtraMoveHandling data[13] #define forgetMove data[14] #define storageIndex data[15] @@ -6254,6 +6255,75 @@ static void RestoreFusionMon(struct Pokemon *mon) } } +static void DeleteInvalidFusionMoves(struct Pokemon *mon, u32 species) +{ + for (u32 i = 0; i < MAX_MON_MOVES; i++) + { + u32 move = GetMonData(mon, MON_DATA_MOVE1 + i); + bool32 toDelete = TRUE; + const struct LevelUpMove *learnset = GetSpeciesLevelUpLearnset(species); + for (u32 j = 0; learnset[j].move != LEVEL_UP_MOVE_END;j++) + { + if (learnset[j].move == move) + { + toDelete = FALSE; + break; + } + } + if (!toDelete) + continue; + const u16 *learnset2 = GetSpeciesTeachableLearnset(species); + for (u32 j = 0; learnset2[j] != MOVE_UNAVAILABLE;j++) + { + if (learnset2[j] == move) + { + toDelete = FALSE; + break; + } + } + if (!toDelete) + continue; + const u16 *learnset3 = GetSpeciesEggMoves(species); + for (u32 j = 0; learnset3[j] != MOVE_UNAVAILABLE;j++) + { + if (learnset3[j] == move) + { + toDelete = FALSE; + break; + } + } + if (toDelete) + DeleteMove(mon, move); + } +} + +static void SwapFusionMonMoves(struct Pokemon *mon, const u16 moveTable[][2], u32 mode) +{ + u32 oldMoveIndex, newMoveIndex; + if (mode == FUSE_MON) + { + oldMoveIndex = 0; + newMoveIndex = 1; + } + else //mode == UNFUSE_MON + { + oldMoveIndex = 1; + newMoveIndex = 0; + } + for (u32 i = 0; i < MAX_MON_MOVES; i++) + { + u32 move = GetMonData(mon, MON_DATA_MOVE1 + i); + for (u32 j = 0; j < 2; j++) + { + if (move == moveTable[j][oldMoveIndex]) + { + SetMonData(mon, MON_DATA_MOVE1 + i, &moveTable[j][newMoveIndex]); + SetMonData(mon, MON_DATA_PP1 + i, &gMovesInfo[moveTable[j][newMoveIndex]].pp); + } + } + } + +} static void Task_TryItemUseFusionChange(u8 taskId) { struct Pokemon *mon = &gPlayerParty[gTasks[taskId].firstFusionSlot]; @@ -6345,15 +6415,38 @@ static void Task_TryItemUseFusionChange(u8 taskId) case 6: if (!IsPartyMenuTextPrinterActive()) { - if (gTasks[taskId].moveToLearn != 0) + if (gTasks[taskId].fusionType == FUSE_MON) { - if (gTasks[taskId].fusionType == FUSE_MON) +#if P_FAMILY_KYUREM +#if P_FAMILY_RESHIRAM + if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_WHITE) + SwapFusionMonMoves(mon, gKyurenWhiteSwapMoveTable, FUSE_MON); +#endif //P_FAMILY_RESHIRAM +#if P_FAMILY_ZEKROM + if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_BLACK) + SwapFusionMonMoves(mon, gKyurenBlackSwapMoveTable, FUSE_MON); +#endif //P_FAMILY_ZEKROM +#endif //P_FAMILY_KYUREM + if (gTasks[taskId].moveToLearn != 0) FormChangeTeachMove(taskId, gTasks[taskId].moveToLearn, gTasks[taskId].firstFusionSlot); - else + } + else //(gTasks[taskId].fusionType == UNFUSE_MON) + { +#if P_FAMILY_KYUREM +#if P_FAMILY_RESHIRAM + if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_WHITE) + SwapFusionMonMoves(mon, gKyurenWhiteSwapMoveTable, UNFUSE_MON); +#endif //P_FAMILY_RESHIRAM +#if P_FAMILY_ZEKROM + if (gTasks[taskId].tExtraMoveHandling == SWAP_EXTRA_MOVES_KYUREM_BLACK) + SwapFusionMonMoves(mon, gKyurenBlackSwapMoveTable, UNFUSE_MON); +#endif //P_FAMILY_ZEKROM +#endif //P_FAMILY_KYUREM + if ( gTasks[taskId].tExtraMoveHandling == FORGET_EXTRA_MOVES) { - DeleteMove(mon, gTasks[taskId].forgetMove); + DeleteInvalidFusionMoves(mon, gTasks[taskId].fusionResult); if (!DoesMonHaveAnyMoves(mon)) - FormChangeTeachMove(taskId, gTasks[taskId].moveToLearn, gTasks[taskId].firstFusionSlot); + FormChangeTeachMove(taskId, MOVE_CONFUSION, gTasks[taskId].firstFusionSlot); } } gTasks[taskId].tState++; @@ -6400,7 +6493,7 @@ void ItemUseCB_Fusion(u8 taskId, TaskFunc taskFunc) task->storageIndex = itemFusion[i].fusionStorageIndex; task->fusionResult = itemFusion[i].targetSpecies1; task->unfuseSecondMon = itemFusion[i].targetSpecies2; - task->moveToLearn = itemFusion[i].unfuseForgetMove; + task->tExtraMoveHandling = itemFusion[i].extraMoveHandling; task->forgetMove = itemFusion[i].fusionMove; TryItemUseFusionChange(taskId, taskFunc); return; @@ -6440,6 +6533,7 @@ void ItemUseCB_Fusion(u8 taskId, TaskFunc taskFunc) task->fusionResult = itemFusion[i].fusingIntoMon; task->secondFusionSlot = gPartyMenu.slotId; task->moveToLearn = itemFusion[i].fusionMove; + task->tExtraMoveHandling = itemFusion[i].extraMoveHandling; // Start Fusion TryItemUseFusionChange(taskId, taskFunc); return; From d646b975ae7a2786c1787fe07e2fc0fb47ac80a8 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Thu, 9 Oct 2025 18:41:32 +0200 Subject: [PATCH 39/50] Fix SmartStrike crashing the game in double battles (#7902) --- data/battle_anim_scripts.s | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 7de4958345..ca31cd100e 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -10990,7 +10990,6 @@ gBattleAnimMove_SmartStrike:: setalpha 12, 8 call SonicBoomProjectile createvisualtask AnimTask_ShakeMon, 2, ANIM_TARGET, 3, 0, 10, 1 - loadspritegfx ANIM_TAG_FLASH_CANNON_BALL createsprite gSmartStrikeImpactTemplate, ANIM_TARGET, 4, 0x0, 0x0, 0x8, 0x1, 0x0 playsewithpan SE_M_VITAL_THROW2, SOUND_PAN_TARGET createsprite gSmartStrikeGemTemplate, ANIM_TARGET, 2, 0x1, 0x1, 0x0, 0xffe8, 0xa @@ -11005,7 +11004,6 @@ gBattleAnimMove_SmartStrike:: clearmonbg ANIM_DEF_PARTNER blendoff waitforvisualfinish - clearmonbg ANIM_ATTACKER blendoff waitforvisualfinish end From daedfd5e6211b2df88567ef7d3794939e2b079ec Mon Sep 17 00:00:00 2001 From: Marky <143505183+HashtagMarky@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:09:19 +0100 Subject: [PATCH 40/50] Add gBallItemIds Array (#7905) --- include/pokeball.h | 1 + src/debug.c | 6 ++---- src/item.c | 4 ++-- src/pokeball.c | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/include/pokeball.h b/include/pokeball.h index 530e168f93..51949206e2 100644 --- a/include/pokeball.h +++ b/include/pokeball.h @@ -45,6 +45,7 @@ enum { extern const struct CompressedSpriteSheet gBallSpriteSheets[]; extern const struct SpritePalette gBallSpritePalettes[]; extern const struct SpriteTemplate gBallSpriteTemplates[]; +extern const u16 gBallItemIds[]; #define POKEBALL_PLAYER_SENDOUT 0xFF #define POKEBALL_OPPONENT_SENDOUT 0xFE diff --git a/src/debug.c b/src/debug.c index 8aad180ac1..6fbfb3777f 100644 --- a/src/debug.c +++ b/src/debug.c @@ -3156,12 +3156,10 @@ static void DebugAction_PCBag_Fill_PocketItems(u8 taskId) static void DebugAction_PCBag_Fill_PocketPokeBalls(u8 taskId) { - u16 ballId; - - for (ballId = BALL_STRANGE; ballId < POKEBALL_COUNT; ballId++) + for (enum PokeBall ballId = BALL_STRANGE; ballId < POKEBALL_COUNT; ballId++) { if (CheckBagHasSpace(ballId, MAX_BAG_ITEM_CAPACITY)) - AddBagItem(ballId, MAX_BAG_ITEM_CAPACITY); + AddBagItem(gBallItemIds[ballId], MAX_BAG_ITEM_CAPACITY); } } diff --git a/src/item.c b/src/item.c index 162798acde..7519060287 100644 --- a/src/item.c +++ b/src/item.c @@ -235,9 +235,9 @@ bool32 HasAtLeastOneBerry(void) bool32 HasAtLeastOnePokeBall(void) { - for (u32 ballId = BALL_STRANGE; ballId < POKEBALL_COUNT; ballId++) + for (enum PokeBall ballId = BALL_STRANGE; ballId < POKEBALL_COUNT; ballId++) { - if (CheckBagHasItem(ballId, 1) == TRUE) + if (CheckBagHasItem(gBallItemIds[ballId], 1) == TRUE) return TRUE; } return FALSE; diff --git a/src/pokeball.c b/src/pokeball.c index 5608de9647..5b32aa7e1d 100644 --- a/src/pokeball.c +++ b/src/pokeball.c @@ -544,6 +544,38 @@ const struct SpriteTemplate gBallSpriteTemplates[POKEBALL_COUNT] = #define tBattler data[3] #define tOpponentBattler data[4] +const u16 gBallItemIds[POKEBALL_COUNT] = +{ + [BALL_STRANGE] = ITEM_STRANGE_BALL, + [BALL_POKE] = ITEM_POKE_BALL, + [BALL_GREAT] = ITEM_GREAT_BALL, + [BALL_ULTRA] = ITEM_ULTRA_BALL, + [BALL_MASTER] = ITEM_MASTER_BALL, + [BALL_PREMIER] = ITEM_PREMIER_BALL, + [BALL_HEAL] = ITEM_HEAL_BALL, + [BALL_NET] = ITEM_NET_BALL, + [BALL_NEST] = ITEM_NEST_BALL, + [BALL_DIVE] = ITEM_DIVE_BALL, + [BALL_DUSK] = ITEM_DUSK_BALL, + [BALL_TIMER] = ITEM_TIMER_BALL, + [BALL_QUICK] = ITEM_QUICK_BALL, + [BALL_REPEAT] = ITEM_REPEAT_BALL, + [BALL_LUXURY] = ITEM_LUXURY_BALL, + [BALL_LEVEL] = ITEM_LEVEL_BALL, + [BALL_LURE] = ITEM_LURE_BALL, + [BALL_MOON] = ITEM_MOON_BALL, + [BALL_FRIEND] = ITEM_FRIEND_BALL, + [BALL_LOVE] = ITEM_LOVE_BALL, + [BALL_FAST] = ITEM_FAST_BALL, + [BALL_HEAVY] = ITEM_HEAVY_BALL, + [BALL_DREAM] = ITEM_DREAM_BALL, + [BALL_SAFARI] = ITEM_SAFARI_BALL, + [BALL_SPORT] = ITEM_SPORT_BALL, + [BALL_PARK] = ITEM_PARK_BALL, + [BALL_BEAST] = ITEM_BEAST_BALL, + [BALL_CHERISH] = ITEM_CHERISH_BALL, +}; + u8 DoPokeballSendOutAnimation(u32 battler, s16 pan, u8 kindOfThrow) { u8 taskId; From 81af2be16e8ef6ddbc64ee17ecf0b718fb15ae93 Mon Sep 17 00:00:00 2001 From: Maxime Grouazel <116717026+MaximeGr00@users.noreply.github.com> Date: Fri, 10 Oct 2025 20:06:35 +0200 Subject: [PATCH 41/50] Fix AI seeing priority wrong for players choice lock (#7899) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_ai_switch_items.c | 9 +++++++++ test/battle/ai/ai_switching.c | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index ed81c19659..96cb7e30df 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1994,7 +1994,10 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle { damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, AI_DEFENDING); if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move + { + *bestPlayerMove = playerMove; return damageTaken; + } if (damageTaken > maxDamageTaken) { maxDamageTaken = damageTaken; @@ -2014,13 +2017,19 @@ static s32 GetMaxPriorityDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposi for (i = 0; i < MAX_MON_MOVES; i++) { + // If player is choiced into a non-priority move, AI understands that it can't deal priority damage + if (gBattleStruct->choicedMove[opposingBattler] !=MOVE_NONE && GetMovePriority(gBattleStruct->choicedMove[opposingBattler]) < 1) + break; playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i]; if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], playerMove) > 0 && playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH && gBattleMons[opposingBattler].pp[i] > 0) { damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, AI_DEFENDING); if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move + { + *bestPlayerPriorityMove = playerMove; return damageTaken; + } if (damageTaken > maxDamageTaken) { maxDamageTaken = damageTaken; diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index b1aad9afb2..22245e762a 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1399,3 +1399,20 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: Fake Out style moves won't confu TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CLOSE_COMBAT); } } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked player priority when determining which mon to send out") +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_LYCANROC) { Speed(5); Moves(MOVE_ACCELEROCK, MOVE_MIGHTY_CLEAVE); Item(item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); HP(1); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_DECIDUEYE_HISUI) { Speed(4); Moves(MOVE_LEAF_BLADE); } + OPPONENT(SPECIES_PHEROMOSA) { Speed(6); HP(1); Moves(MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_MIGHTY_CLEAVE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); } + } +} From ade850b215d4a3335e6f307cd987a5d0c31d9dd0 Mon Sep 17 00:00:00 2001 From: ghostyboyy97 <106448956+ghostyboyy97@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:07:27 -0400 Subject: [PATCH 42/50] fix (post-KO switch): force AI data recalc to see abilities on field correctly when pivot moves used by player (#7900) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_controller_opponent.c | 6 ++++++ test/battle/ai/ai_switching.c | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index 8c469733c1..19aeef3f4f 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -537,6 +537,12 @@ static void OpponentHandleChoosePokemon(u32 battler) { if (IsSwitchOutEffect(GetMoveEffect(gCurrentMove)) || gAiLogicData->ejectButtonSwitch || gAiLogicData->ejectPackSwitch) switchType = SWITCH_MID_BATTLE; + + // reset the AI data to consider the correct on-field state at time of switch + SetBattlerAiData(GetBattlerAtPosition(B_POSITION_PLAYER_LEFT), gAiLogicData); + if (IsDoubleBattle()) + SetBattlerAiData(GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT), gAiLogicData); + chosenMonId = GetMostSuitableMonToSwitchInto(battler, switchType); if (chosenMonId == PARTY_SIZE) { diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 22245e762a..14ea727f85 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -40,6 +40,28 @@ AI_SINGLE_BATTLE_TEST("AI switches if Perish Song is about to kill") } } +AI_SINGLE_BATTLE_TEST("AI sees on-field player ability correctly and does not see previous Pokémon's ability after player uses a pivot move when choosing a post-KO switch") +{ + u32 testAbility; + PARAMETRIZE { testAbility = ABILITY_WATER_ABSORB; } + PARAMETRIZE { testAbility = ABILITY_VOLT_ABSORB; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_PIKACHU) {Level(100); Moves(MOVE_VOLT_SWITCH, MOVE_SPARKLY_SWIRL); Ability(ABILITY_LIGHTNING_ROD); }; + PLAYER(SPECIES_LANTURN) {Level(44); Moves(MOVE_SCALD); Ability(testAbility); }; + OPPONENT(SPECIES_SOBBLE) {Level(44); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_BOMBIRDIER) {Level(42); Moves(MOVE_ROCK_SLIDE); } + OPPONENT(SPECIES_IRON_THORNS) {Level(43); Moves(MOVE_SUPERCELL_SLAM, MOVE_ICE_PUNCH); } + } WHEN { + TURN { + MOVE(player, MOVE_VOLT_SWITCH); + SEND_OUT(player, 1); + EXPECT_MOVE(opponent, MOVE_SCRATCH); + testAbility == ABILITY_WATER_ABSORB ? EXPECT_SEND_OUT(opponent, 2) : EXPECT_SEND_OUT(opponent, 1); + } + } +} + AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same Pokémon for 2 spots in a double battle (all bad moves)") { u32 flags; From 66965e3d68a9a047df921fc22ef2c2f1309f9fa2 Mon Sep 17 00:00:00 2001 From: Estellar <137097857+estellarc@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:09:02 -0300 Subject: [PATCH 43/50] Remove unnecessary EWRAM and IWRAM variables from the Window code (#7897) --- src/window.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/window.c b/src/window.c index 65419ff77b..51b473e89c 100644 --- a/src/window.c +++ b/src/window.c @@ -5,14 +5,10 @@ #include "blit.h" #include "decompress.h" -// This global is set to 0 and never changed. -COMMON_DATA u8 gTransparentTileNumber = 0; COMMON_DATA void *gWindowBgTilemapBuffers[NUM_BACKGROUNDS] = {0}; extern u32 gWindowTileAutoAllocEnabled; EWRAM_DATA struct Window gWindows[WINDOWS_MAX] = {0}; -EWRAM_DATA static struct Window *sWindowPtr = NULL; -EWRAM_DATA static u16 sWindowSize = 0; static u32 GetNumActiveWindowsOnBg(u32 bgId); static u32 GetNumActiveWindowsOnBg8Bit(u32 bgId); @@ -103,7 +99,6 @@ bool32 InitWindows(const struct WindowTemplate *templates) } } - gTransparentTileNumber = 0; return TRUE; } @@ -375,7 +370,7 @@ void ClearWindowTilemap(u32 windowId) FillBgTilemapBufferRect( windowLocal.window.bg, - gTransparentTileNumber, + 0, windowLocal.window.tilemapLeft, windowLocal.window.tilemapTop, windowLocal.window.width, @@ -698,20 +693,20 @@ void BlitBitmapRectToWindow4BitTo8Bit(u32 windowId, const u8 *pixels, u16 srcX, void CopyWindowToVram8Bit(u32 windowId, u8 mode) { - sWindowPtr = &gWindows[windowId]; - sWindowSize = 64 * (sWindowPtr->window.width * sWindowPtr->window.height); + struct Window *window = &gWindows[windowId]; + u16 windowSize = 64 * (window->window.width * window->window.height); switch (mode) { case COPYWIN_MAP: - CopyBgTilemapBufferToVram(sWindowPtr->window.bg); + CopyBgTilemapBufferToVram(window->window.bg); break; case COPYWIN_GFX: - LoadBgTiles(sWindowPtr->window.bg, sWindowPtr->tileData, sWindowSize, sWindowPtr->window.baseBlock); + LoadBgTiles(window->window.bg, window->tileData, windowSize, window->window.baseBlock); break; case COPYWIN_FULL: - LoadBgTiles(sWindowPtr->window.bg, sWindowPtr->tileData, sWindowSize, sWindowPtr->window.baseBlock); - CopyBgTilemapBufferToVram(sWindowPtr->window.bg); + LoadBgTiles(window->window.bg, window->tileData, windowSize, window->window.baseBlock); + CopyBgTilemapBufferToVram(window->window.bg); break; } } From 77ec4cd6ebdb34e7dcb9211408f555e10b0f62e2 Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:30:49 -0400 Subject: [PATCH 44/50] Fix palaceUnableToUseMove falling through to change battle script (#7912) Co-authored-by: ghoulslash --- src/battle_util.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index 07af6d1789..ee98c8ddb1 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -529,8 +529,7 @@ void HandleAction_UseMove(void) gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround; } } - - if (IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && !IsBattlerAlive(gBattlerTarget)) + else if (IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && !IsBattlerAlive(gBattlerTarget)) { gBattlescriptCurrInstr = BattleScript_FailedFromAtkCanceler; } From 3ad23d7f24618c812114fd7f713758d91447dbeb Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Fri, 10 Oct 2025 20:31:16 +0200 Subject: [PATCH 45/50] Replace magic numbers with define'd values in field_player_avatar.c (#7910) --- src/field_player_avatar.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index 55542a3588..2d71bb5559 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -1197,17 +1197,18 @@ void PlayerSetAnimId(u8 movementActionId, u8 copyableMovement) // slow stairs (from FRLG--faster than slow) static void PlayerWalkSlowStairs(u8 direction) { - PlayerSetAnimId(GetWalkSlowStairsMovementAction(direction), 2); + PlayerSetAnimId(GetWalkSlowStairsMovementAction(direction), COPY_MOVE_WALK); } // slow static void UNUSED PlayerWalkSlow(u8 direction) { - PlayerSetAnimId(GetWalkSlowMovementAction(direction), 2); + PlayerSetAnimId(GetWalkSlowMovementAction(direction), COPY_MOVE_WALK); } + static void PlayerRunSlow(u8 direction) { - PlayerSetAnimId(GetPlayerRunSlowMovementAction(direction), 2); + PlayerSetAnimId(GetPlayerRunSlowMovementAction(direction), COPY_MOVE_WALK); } // normal speed (1 speed) From c99da830d71590398cd220db55528fec73fd155b Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:31:51 -0400 Subject: [PATCH 46/50] Add new Move target types to GetBattlePalaceMoveGroup (#7913) Co-authored-by: ghoulslash --- src/battle_gfx_sfx_util.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index e7bf06327d..d7004d7635 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -331,6 +331,7 @@ static u8 GetBattlePalaceMoveGroup(u8 battler, u16 move) case MOVE_TARGET_RANDOM: case MOVE_TARGET_BOTH: case MOVE_TARGET_FOES_AND_ALLY: + case MOVE_TARGET_ALL_BATTLERS: if (IsBattleMoveStatus(move)) return PALACE_MOVE_GROUP_SUPPORT; else @@ -338,6 +339,7 @@ static u8 GetBattlePalaceMoveGroup(u8 battler, u16 move) break; case MOVE_TARGET_DEPENDS: case MOVE_TARGET_OPPONENTS_FIELD: + case MOVE_TARGET_ALLY: return PALACE_MOVE_GROUP_SUPPORT; case MOVE_TARGET_USER: return PALACE_MOVE_GROUP_DEFENSE; From c030a1406328bd245eccb7159c3d3080c296e346 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Fri, 10 Oct 2025 20:32:33 +0200 Subject: [PATCH 47/50] Fix follower pokemon not playing animation when colliding (#7908) --- src/field_player_avatar.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index 2d71bb5559..bb9723443d 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -1240,7 +1240,7 @@ static void PlayerRun(u8 direction) void PlayerOnBikeCollide(u8 direction) { PlayCollisionSoundIfNotFacingWarp(direction); - PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_WALK); + PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_FACE); // Edge case: If the player stops at the top of a mud slide, but the NPC follower is still on a mud slide tile, // move the follower into the player and hide them. if (PlayerHasFollowerNPC()) @@ -1261,18 +1261,18 @@ void PlayerOnBikeCollide(u8 direction) void PlayerOnBikeCollideWithFarawayIslandMew(u8 direction) { - PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_WALK); + PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_FACE); } static void PlayerNotOnBikeCollide(u8 direction) { PlayCollisionSoundIfNotFacingWarp(direction); - PlayerSetAnimId(GetWalkInPlaceSlowMovementAction(direction), COPY_MOVE_WALK); + PlayerSetAnimId(GetWalkInPlaceSlowMovementAction(direction), COPY_MOVE_FACE); } static void PlayerNotOnBikeCollideWithFarawayIslandMew(u8 direction) { - PlayerSetAnimId(GetWalkInPlaceSlowMovementAction(direction), COPY_MOVE_WALK); + PlayerSetAnimId(GetWalkInPlaceSlowMovementAction(direction), COPY_MOVE_FACE); } void PlayerFaceDirection(u8 direction) From 0bc3c348b4fe8d5e752f62beb42a2e19dd73d496 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sat, 11 Oct 2025 14:49:36 +0200 Subject: [PATCH 48/50] Fixes 2 instances of global usage in the `Cmd_adjustdamage` loop (#7918) --- src/battle_script_commands.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b6b6305fb8..45fa3f3e97 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1901,13 +1901,13 @@ static void Cmd_adjustdamage(void) gBattleStruct->moveResultFlags[battlerDef] &= ~(MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE); gBattleStruct->moveDamage[battlerDef] = 0; gSpecialStatuses[battlerDef].enduredDamage = TRUE; - RecordAbilityBattle(gBattlerTarget, ABILITY_ICE_FACE); + RecordAbilityBattle(battlerDef, ABILITY_ICE_FACE); gDisableStructs[battlerDef].iceFaceActivationPrevention = TRUE; // Form change will be done after attack animation in Cmd_resultmessage. continue; } - if (gBattleMons[gBattlerTarget].hp > gBattleStruct->moveDamage[battlerDef]) + if (gBattleMons[battlerDef].hp > gBattleStruct->moveDamage[battlerDef]) continue; holdEffect = GetBattlerHoldEffect(battlerDef, TRUE); From 5b84cd14bc6a767a74ef86c7a06ef24a30fb833f Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Sat, 11 Oct 2025 14:41:13 -0400 Subject: [PATCH 49/50] Fix Battle Anim monbg calls Part 1 (#7906) Co-authored-by: ghoulslash --- data/battle_anim_scripts.s | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index ca31cd100e..4d952965e6 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -366,6 +366,7 @@ gBattleAnimMove_UTurn:: gBattleAnimMove_CloseCombat:: loadspritegfx ANIM_TAG_IMPACT loadspritegfx ANIM_TAG_HANDS_AND_FEET + monbg ANIM_DEF_PARTNER call SetHighSpeedBg createsprite gFistFootRandomPosSpriteTemplate, ANIM_TARGET, 3, 1, 10, 0 createvisualtask AnimTask_ShakeMonInPlace, 2, ANIM_TARGET, 2, 0, 7, 1 @@ -440,7 +441,7 @@ gBattleAnimMove_CloseCombat:: playsewithpan SE_M_MEGA_KICK2, +63 delay 1 call UnsetHighSpeedBg - clearmonbg ANIM_TARGET + clearmonbg ANIM_DEF_PARTNER blendoff delay 1 setarg 7, 0x1000 @@ -969,6 +970,7 @@ gBattleAnimMove_HeartSwap:: loadspritegfx ANIM_TAG_RED_HEART loadspritegfx ANIM_TAG_PINKVIO_ORB loadspritegfx ANIM_TAG_SPARKLE_2 + monbg ANIM_TARGET createvisualtask AnimTask_BlendBattleAnimPal, 10, F_PAL_BG, 3, 0, 8, RGB(31, 24, 26) createvisualtask AnimTask_HeartSwap, 3, ANIM_TARGET createvisualtask AnimTask_BlendMonInAndOut, 5, ANIM_TARGET, RGB_WHITE, 12, 3, 1 @@ -1001,7 +1003,6 @@ gBattleAnimMove_HeartSwap:: createsprite gRedHeartCharmSpriteTemplate, ANIM_ATTACKER, 3, 20, 20 playsewithpan SE_M_CHARM, SOUND_PAN_ATTACKER waitforvisualfinish - clearmonbg ANIM_ATTACKER clearmonbg ANIM_TARGET blendoff end @@ -1607,7 +1608,6 @@ gBattleAnimMove_DragonRush:: createsprite gRockFragmentSpriteTemplate, ANIM_ATTACKER, 2, 0, 0, -30, -18, 8, 2 createvisualtask AnimTask_ShakeMon, 2, ANIM_TARGET, 0, 3, 7, 1 waitforvisualfinish - clearmonbg ANIM_DEF_PARTNER blendoff end @@ -2310,7 +2310,6 @@ gBattleAnimMove_MirrorShot:: createvisualtask AnimTask_BlendBattleAnimPalExclude, 5, 5, 2, 10, 0, RGB_WHITEALPHA createvisualtask AnimTask_HorizontalShake, 5, ANIM_TARGET, 5, 14 waitforvisualfinish - clearmonbg ANIM_ATTACKER blendoff end From dd412c766acce71141555630a5cc26f0b4258f11 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sun, 12 Oct 2025 08:54:14 +0200 Subject: [PATCH 50/50] Adds missing breakable flag for Bulletproof (#7928) --- src/data/abilities.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/abilities.h b/src/data/abilities.h index f9a3adc96c..cfffb4e05d 100644 --- a/src/data/abilities.h +++ b/src/data/abilities.h @@ -1286,6 +1286,7 @@ const struct Ability gAbilitiesInfo[ABILITIES_COUNT] = { .name = _("Bulletproof"), .description = COMPOUND_STRING("Avoids some projectiles."), + .breakable = TRUE, .aiRating = 7, },