From 593219226b7a739b3290a26d73140e7798b8c05d Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 16 Oct 2025 12:06:54 +0200 Subject: [PATCH 01/49] Fix dns color transition not applying weather blending (#7883) Co-authored-by: Bassoonian --- src/overworld.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/overworld.c b/src/overworld.c index 8c2eaf5d88..e8be437519 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -1716,8 +1716,7 @@ static void OverworldBasic(void) || bld0[1] != bld1[1] || bld0[2] != bld1[2]) { - UpdateAltBgPalettes(PALETTES_BG); - UpdatePalettesWithTime(PALETTES_ALL); + ApplyWeatherColorMapIfIdle(gWeatherPtr->colorMapIndex); } } } From f8eed27ed02a9cb7fbf918a46264890403b374aa Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Thu, 16 Oct 2025 07:09:28 -0400 Subject: [PATCH 02/49] Add missing break to Power Split AI case (#7959) Co-authored-by: ghoulslash --- src/battle_ai_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 1960088640..2cc7cfe62c 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5116,6 +5116,7 @@ case EFFECT_GUARD_SPLIT: ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); ADJUST_SCORE(WORST_EFFECT); + break; } case EFFECT_ELECTRIC_TERRAIN: if (ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_ELECTRIC_TERRAIN)) From f6c6ed6956c74fcdf22931ed1f20b16731315d68 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 16 Oct 2025 13:37:36 +0200 Subject: [PATCH 03/49] Fix follower NPC sidewaystair movement (#7909) --- src/event_object_movement.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 84f810e95a..6d0cf86cf6 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -6591,7 +6591,7 @@ bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *objectEvent) static u8 TryUpdateMovementActionOnStairs(struct ObjectEvent *objectEvent, u8 movementActionId) { - if (objectEvent->isPlayer || objectEvent->localId == OBJ_EVENT_ID_FOLLOWER) + if (objectEvent->isPlayer || objectEvent->localId == OBJ_EVENT_ID_FOLLOWER || objectEvent->localId == OBJ_EVENT_ID_NPC_FOLLOWER) return movementActionId; // handled separately if (!ObjectMovingOnRockStairs(objectEvent, objectEvent->movementDirection)) From de3c031fc5d0c01913d52ddfaed540b6a36e31fb Mon Sep 17 00:00:00 2001 From: spindrift64 <102487911+spindrift64@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:36:07 +0200 Subject: [PATCH 04/49] Fix Anticipation type effectiveness check (#7840) --- include/battle_util.h | 3 ++- src/battle_util.c | 31 +++++++++++++++++------------- test/battle/ability/anticipation.c | 29 ++++++++++++++++------------ 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index f22fd8b0ac..d30caadd47 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -165,7 +165,8 @@ struct DamageContext u32 isCrit:1; u32 randomFactor:1; u32 updateFlags:1; - u32 padding1:2; + u32 isAnticipation:1; + u32 padding1:1; u32 weather:16; u32 fixedBasePower:8; u32 padding2:8; diff --git a/src/battle_util.c b/src/battle_util.c index 58349507a7..abf61c1d58 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3690,8 +3690,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_ANTICIPATION: if (!gSpecialStatuses[battler].switchInAbilityDone) { - u32 types[3]; - GetBattlerTypes(battler, FALSE, types); + struct DamageContext ctx = {0}; + uq4_12_t modifier = UQ_4_12(1.0); for (i = 0; i < MAX_BATTLERS_COUNT; i++) { if (IsBattlerAlive(i) && !IsBattlerAlly(i, battler)) @@ -3702,9 +3702,14 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 enum BattleMoveEffects moveEffect = GetMoveEffect(move); moveType = GetBattleMoveType(move); - if (GetTypeModifier(moveType, types[0]) >= UQ_4_12(2.0) - || (types[0] != types[1] && GetTypeModifier(moveType, types[1]) >= UQ_4_12(2.0)) - || (types[2] != TYPE_MYSTERY && GetTypeModifier(moveType, types[2]) >= UQ_4_12(2.0)) + ctx.battlerAtk = i; + ctx.battlerDef = battler; + ctx.move = move; + ctx.moveType = moveType; + ctx.isAnticipation = TRUE; + modifier = CalcTypeEffectivenessMultiplier(&ctx); + + if (modifier >= UQ_4_12(2.0) || moveEffect == EFFECT_OHKO || moveEffect == EFFECT_SHEER_COLD) { @@ -7639,13 +7644,13 @@ enum IronBallCheck }; // Only called directly when calculating damage type effectiveness, and Iron Ball's type effectiveness mechanics -static bool32 IsBattlerGroundedInverseCheck(u32 battler, u32 ability, enum InverseBattleCheck checkInverse, enum IronBallCheck checkIronBall) +static bool32 IsBattlerGroundedInverseCheck(u32 battler, u32 ability, enum InverseBattleCheck checkInverse, enum IronBallCheck checkIronBall, bool32 isAnticipation) { enum ItemHoldEffect holdEffect = GetBattlerHoldEffect(battler, TRUE); if (!(checkIronBall == IGNORE_IRON_BALL) && holdEffect == HOLD_EFFECT_IRON_BALL) return TRUE; - if (gFieldStatuses & STATUS_FIELD_GRAVITY) + if (gFieldStatuses & STATUS_FIELD_GRAVITY && isAnticipation == FALSE) return TRUE; if (B_ROOTED_GROUNDING >= GEN_4 && gBattleMons[battler].volatiles.root) return TRUE; @@ -7666,7 +7671,7 @@ static bool32 IsBattlerGroundedInverseCheck(u32 battler, u32 ability, enum Inver bool32 IsBattlerGrounded(u32 battler) { - return IsBattlerGroundedInverseCheck(battler, GetBattlerAbility(battler), NOT_INVERSE_BATTLE, CHECK_IRON_BALL); + return IsBattlerGroundedInverseCheck(battler, GetBattlerAbility(battler), NOT_INVERSE_BATTLE, CHECK_IRON_BALL, FALSE); } u32 GetMoveSlot(u16 *moves, u32 move) @@ -9522,7 +9527,7 @@ static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *m if (ctx->moveType == TYPE_PSYCHIC && defType == TYPE_DARK && gBattleMons[ctx->battlerDef].volatiles.miracleEye && mod == UQ_4_12(0.0)) mod = UQ_4_12(1.0); - if (GetMoveEffect(ctx->move) == EFFECT_SUPER_EFFECTIVE_ON_ARG && defType == GetMoveArgType(ctx->move)) + if (GetMoveEffect(ctx->move) == EFFECT_SUPER_EFFECTIVE_ON_ARG && defType == GetMoveArgType(ctx->move) && !ctx->isAnticipation) mod = UQ_4_12(2.0); if (ctx->moveType == TYPE_GROUND && defType == TYPE_FLYING && IsBattlerGrounded(ctx->battlerDef) && mod == UQ_4_12(0.0)) mod = UQ_4_12(1.0); @@ -9530,7 +9535,7 @@ static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *m mod = UQ_4_12(2.0); // B_WEATHER_STRONG_WINDS weakens Super Effective moves against Flying-type Pokémon - if (gBattleWeather & B_WEATHER_STRONG_WINDS && HasWeatherEffect()) + if (gBattleWeather & B_WEATHER_STRONG_WINDS && HasWeatherEffect() && !ctx->isAnticipation) { if (defType == TYPE_FLYING && mod >= UQ_4_12(2.0)) mod = UQ_4_12(1.0); @@ -9621,7 +9626,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont if (B_GLARE_GHOST < GEN_4 && ctx->move == MOVE_GLARE && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_GHOST)) modifier = UQ_4_12(0.0); } - else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, INVERSE_BATTLE, CHECK_IRON_BALL) && !(MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move))) + else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, INVERSE_BATTLE, CHECK_IRON_BALL, ctx->isAnticipation) && !(MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move))) { modifier = UQ_4_12(0.0); if (ctx->updateFlags && ctx->abilityDef == ABILITY_LEVITATE) @@ -9651,7 +9656,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont && ctx->moveType == TYPE_GROUND && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_FLYING) && GetBattlerHoldEffect(ctx->battlerDef, TRUE) == HOLD_EFFECT_IRON_BALL - && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, NOT_INVERSE_BATTLE, IGNORE_IRON_BALL) + && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, NOT_INVERSE_BATTLE, IGNORE_IRON_BALL, FALSE) && !FlagGet(B_FLAG_INVERSE_BATTLE)) { modifier = UQ_4_12(1.0); @@ -9685,7 +9690,7 @@ uq4_12_t CalcTypeEffectivenessMultiplier(struct DamageContext *ctx) if (ctx->move != MOVE_STRUGGLE && ctx->moveType != TYPE_MYSTERY) { modifier = CalcTypeEffectivenessMultiplierInternal(ctx, modifier); - if (GetMoveEffect(ctx->move) == EFFECT_TWO_TYPED_MOVE) + if (GetMoveEffect(ctx->move) == EFFECT_TWO_TYPED_MOVE && !ctx->isAnticipation) { ctx->moveType = GetMoveArgType(ctx->move); modifier = CalcTypeEffectivenessMultiplierInternal(ctx, modifier); diff --git a/test/battle/ability/anticipation.c b/test/battle/ability/anticipation.c index 223512710f..a4a3bef77f 100644 --- a/test/battle/ability/anticipation.c +++ b/test/battle/ability/anticipation.c @@ -16,6 +16,18 @@ SINGLE_BATTLE_TEST("Anticipation causes notifies if an opponent has a super-effe } } +SINGLE_BATTLE_TEST("Anticipation does not trigger even when a move is super effective on only 1 type") +{ + GIVEN { + PLAYER(SPECIES_WHISCASH) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_PIKACHU) { Moves(MOVE_CELEBRATE, MOVE_THUNDERBOLT); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + SINGLE_BATTLE_TEST("Anticipation causes notifies if an opponent has a One-hit KO move") { GIVEN { @@ -59,28 +71,21 @@ SINGLE_BATTLE_TEST("Anticipation doesn't consider Normalize into their effective SINGLE_BATTLE_TEST("Anticipation doesn't consider Scrappy into their effectiveness (Gen5+)") { - KNOWN_FAILING; GIVEN { ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); - ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); - ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); - PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } - OPPONENT(SPECIES_KANGASKHAN) { Ability(ABILITY_SCRAPPY); Moves(MOVE_CLOSE_COMBAT, MOVE_TRICK_OR_TREAT, MOVE_SKILL_SWAP, MOVE_CELEBRATE); } + ASSUME(GetSpeciesType(SPECIES_DOUBLADE, 0) == TYPE_STEEL); + ASSUME(GetSpeciesType(SPECIES_DOUBLADE, 1) == TYPE_GHOST); + PLAYER(SPECIES_DOUBLADE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_KANGASKHAN) { Ability(ABILITY_SCRAPPY); Moves(MOVE_CLOSE_COMBAT, MOVE_CELEBRATE); } } WHEN { - TURN { MOVE(opponent, MOVE_TRICK_OR_TREAT); MOVE(player, MOVE_SKILL_SWAP); } - TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + TURN { } } SCENE { - ABILITY_POPUP(player, ABILITY_ANTICIPATION); - ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_OR_TREAT, opponent); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, player); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); } } SINGLE_BATTLE_TEST("Anticipation doesn't consider Gravity into their effectiveness (Gen5+)") { - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_SKARMORY); OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Moves(MOVE_EARTHQUAKE, MOVE_GRAVITY, MOVE_SCRATCH, MOVE_POUND); } From 864377f99a0f3ffb63b53d10e257ec2e820f0ba4 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 16 Oct 2025 19:19:31 +0200 Subject: [PATCH 05/49] Fix Cherim and Castfrom not reverting to baseform when Teraform Zero is triggered (#7961) --- data/battle_scripts_1.s | 18 ++++++++++++++++-- test/battle/ability/flower_gift.c | 19 +++++++++++++++++++ test/battle/ability/forecast.c | 19 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 004594753f..3cf1f53fec 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8883,14 +8883,28 @@ BattleScript_ActivateTeraformZero_RemoveWeather: removeweather printfromtable gWeatherEndsStringIds waitmessage B_WAIT_TIME_LONG - jumpifhalfword CMP_NO_COMMON_BITS, gFieldStatuses, STATUS_FIELD_TERRAIN_ANY, BattleScript_ActivateTeraformZero_End + call BattleScript_ActivateWeatherAbilities + jumpifhalfword CMP_NO_COMMON_BITS, gFieldStatuses, STATUS_FIELD_TERRAIN_ANY, BattleScript_ActivateTeraformZeroEffects BattleScript_ActivateTeraformZero_RemoveTerrain: removeterrain playanimation BS_ATTACKER, B_ANIM_RESTORE_BG printfromtable gTerrainStringIds waitmessage B_WAIT_TIME_LONG -BattleScript_ActivateTeraformZero_End: +BattleScript_ActivateTeraformZeroEffects: + saveattacker + savetarget tryboosterenergy ON_ANY + resetterrainabilityflags + setbyte gBattlerAttacker, 0 +BattleScript_ActivateTeraformZeroLoop: + copyarraywithindex gBattlerTarget, gBattlerByTurnOrder, gBattlerAttacker, 1 + activateterrainchangeabilities BS_TARGET + activateweatherchangeabilities BS_TARGET + addbyte gBattlerAttacker, 1 + jumpifbytenotequal gBattlerAttacker, gBattlersCount, BattleScript_ActivateTeraformZeroLoop + restoreattacker + restoretarget +BattleScript_ActivateTeraformZero_End: end3 BattleScript_QuickClawActivation:: diff --git a/test/battle/ability/flower_gift.c b/test/battle/ability/flower_gift.c index 8f897e4668..29f5820191 100644 --- a/test/battle/ability/flower_gift.c +++ b/test/battle/ability/flower_gift.c @@ -199,4 +199,23 @@ SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim back when it uses a move that } } +DOUBLE_BATTLE_TEST("Flower Gift reverts Cherrim back after Teraform Zero clears weather") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_CHERRIM) { Ability(ABILITY_FLOWER_GIFT); } + OPPONENT(SPECIES_GROUDON) { Ability(ABILITY_DROUGHT); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_DROUGHT); + ABILITY_POPUP(playerRight, ABILITY_FLOWER_GIFT); + ABILITY_POPUP(playerLeft, ABILITY_TERAFORM_ZERO); + ABILITY_POPUP(playerRight, ABILITY_FLOWER_GIFT); + } THEN { + EXPECT_EQ(playerRight->species, SPECIES_CHERRIM); + } +} + TO_DO_BATTLE_TEST("Flower Gift does not transform Cherrim back to normal when suppressed if Cherrim is Dynamaxed"); diff --git a/test/battle/ability/forecast.c b/test/battle/ability/forecast.c index c462a3a634..418c8058d2 100644 --- a/test/battle/ability/forecast.c +++ b/test/battle/ability/forecast.c @@ -419,3 +419,22 @@ SINGLE_BATTLE_TEST("Forecast transforms Castform when Cloud Nine ability user le MESSAGE("Castform transformed!"); } } + +DOUBLE_BATTLE_TEST("Forecast reverts Castform back after Teraform Zero clears weather") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_CASTFORM) { Ability(ABILITY_FORECAST); } + OPPONENT(SPECIES_KYOGRE) { Ability(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_FORECAST); + ABILITY_POPUP(playerLeft, ABILITY_TERAFORM_ZERO); + ABILITY_POPUP(playerRight, ABILITY_FORECAST); + } THEN { + EXPECT_EQ(playerRight->species, SPECIES_CASTFORM_NORMAL); + } +} From 1946d18ba52dda2f54a3394afb7b07477305b700 Mon Sep 17 00:00:00 2001 From: Raymond Dodge Date: Thu, 16 Oct 2025 15:31:51 -0400 Subject: [PATCH 06/49] Add all pages in `docs` to doc website (#7907) --- docs/SUMMARY.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index c1e6a78752..7f20718f30 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -3,21 +3,40 @@ - [README](./README.md) - [Installation](./INSTALL.md) - [Setting up WSL1 (Legacy Portion)](./legacy_WSL1_INSTALL.md) + - [ChromeOS](./install/chromeos/CHROME_OS.md) + - [Linux]() + - [ARCH_LINUX](./install/linux/ARCH_LINUX.md) + - [DEBIAN](./install/linux/DEBIAN.md) + - [NIXOS](./install/linux/NIXOS.md) + - [OTHERS](./install/linux/OTHERS.md) + - [UBUNTU](./install/linux/UBUNTU.md) + - [macOS](./install/mac/MAC_OS.md) + - [Windows]() + - [CYGWIN](./install/windows/CYGWIN.md) + - [MSYS2](./install/windows/MSYS2.md) + - [WSL](./install/windows/WSL.md) - [Run documentation site locally](local_mdbook/index.md) - [Ubuntu WSL1/WSL2](local_mdbook/ubuntu_WSL.md) - [Contributing](./CONTRIBUTING.md) +- [Styleguide and Principles](./STYLEGUIDE.md) - [Credits](./CREDITS.md) - [Tutorials]() - [What are AI Flags?](tutorials/ai_flags.md) - [How to add new AI Flags](tutorials/ai_logic.md) - [How to add new battle script commands/macros](tutorials/how_to_battle_script_command_macro.md) - [How to add a new move](tutorials/how_to_new_move.md) - - [How to add a new trainer class](tutorials/how_to_trainer_class.md) + - [How to add a new trainer class]() + - [How to add a new trainer front pic](tutorials/how_to_trainer_front_pic.md) + - [How to add a new trainer back pic](tutorials/how_to_trainer_back_pic.md) - [How to add a new Pokémon](tutorials/how_to_new_pokemon.md) - [v1.6.x and earlier](tutorials/how_to_new_pokemon_1_6_0.md) - [How to use the Testing System](tutorials/how_to_testing_system.md) - [How to add new Trainer Slides](tutorials/how_to_new_trainer_slide.md) - [Day/Night System FAQ](tutorials/dns.md) + - [How to use the code entry system](tutorials/how_to_code_entry.md) + - [How to use Follower NPCs](tutorials/how_to_follower_npc.md) + - [Time-Based Encounters](tutorials/how_to_time_of_day_encounters.md) + - [How to use Trainer Party Pools](tutorials/how_to_trainer_party_pool.md) - [Changelog](./CHANGELOG.md) - [1.13.x]() - [Version 1.13.2](changelogs/1.13.x/1.13.2.md) @@ -39,6 +58,7 @@ - [Version 1.10.2](changelogs/1.10.x/1.10.2.md) - [Version 1.10.1](changelogs/1.10.x/1.10.1.md) - [Version 1.10.0](changelogs/1.10.x/1.10.0.md) + - [Megaman Battle Network Style Names](./mmbn_style_names.md) - [1.9.x]() - [Version 1.9.4](changelogs/1.9.x/1.9.4.md) - [Version 1.9.3](changelogs/1.9.x/1.9.3.md) @@ -87,4 +107,5 @@ - [Team Procedures]() - [How to make an Expansion version](team_procedures/expansion_versions.md) - [Release Schedule and Process](team_procedures/schedule.md) + - [Merge Checklist](team_procedures/merge_checklist.md) - [Scope Guidelines](team_procedures/scope.md) From bb4a87676846948d2605940830db8d11c4479dce Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 16 Oct 2025 21:34:40 +0200 Subject: [PATCH 07/49] Fix Focus Energy boosting crit by the wrong amount with gen1 crit chance (#7956) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_script_commands.c | 3 ++- test/battle/move_effect/focus_energy.c | 29 +++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 5280797a41..b09ac176a9 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -11161,7 +11161,8 @@ static void Cmd_setfocusenergy(void) } else { - if (GetGenConfig(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO) >= GEN_3) + if (GetGenConfig(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO) >= GEN_3 + || GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1) gBattleMons[battler].volatiles.focusEnergy = TRUE; else gBattleMons[battler].volatiles.dragonCheer = TRUE; diff --git a/test/battle/move_effect/focus_energy.c b/test/battle/move_effect/focus_energy.c index 48924132ef..17a08ba7f3 100644 --- a/test/battle/move_effect/focus_energy.c +++ b/test/battle/move_effect/focus_energy.c @@ -23,7 +23,34 @@ SINGLE_BATTLE_TEST("Focus Energy increases the user's critical hit ratio by 1 st } PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); GIVEN { - WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, genConfig); + WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, (genConfig == GEN_1)? GEN_2 : genConfig); + WITH_CONFIG(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO, genConfig); + ASSUME(GetSpeciesBaseSpeed(SPECIES_WOBBUFFET) == 33); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (useFocusEnergy) + TURN { MOVE(player, MOVE_FOCUS_ENERGY); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + if (useFocusEnergy) + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_ENERGY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("Focus Energy multiplies crit chance by 4 with gen 1 crit chance") +{ + bool32 useFocusEnergy = 0; + u32 genConfig = 0, chance = 0; + for (u32 j = GEN_1; j <= GEN_9; j++) { + PARAMETRIZE { genConfig = j; useFocusEnergy = FALSE; chance = 16;} + PARAMETRIZE { genConfig = j; useFocusEnergy = TRUE; chance = 4;} + } + PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, GEN_1); WITH_CONFIG(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO, genConfig); ASSUME(GetSpeciesBaseSpeed(SPECIES_WOBBUFFET) == 33); PLAYER(SPECIES_WOBBUFFET); From 358f3211ca1c3497c110f9440077b254292b38ec Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Fri, 17 Oct 2025 20:17:35 +0200 Subject: [PATCH 08/49] Fix nicknames containing many / overflowing the party screen textbox (#7970) Co-authored-by: Hedara --- graphics/fonts/latin_small_narrower.png | Bin 4371 -> 4359 bytes src/fonts.c | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/fonts/latin_small_narrower.png b/graphics/fonts/latin_small_narrower.png index 69bca4645ac66404905d8275a827ffea46f6c93b..ebb7c70d27257fcd07f6ee5e10b475576e4662a4 100644 GIT binary patch literal 4359 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 diff --git a/src/fonts.c b/src/fonts.c index 46f3a335c4..3e69537113 100644 --- a/src/fonts.c +++ b/src/fonts.c @@ -229,7 +229,7 @@ ALIGNED(4) const u8 gFontSmallNarrowerLatinGlyphWidths[] = { 4, 5, 6, 7, 4, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 2, 4, 2, - 4, 4, 4, 2, 2, 4, 4, 8, 2, 8, 5, 4, 4, 4, 4, 4, + 4, 4, 4, 2, 2, 4, 4, 8, 2, 8, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 3, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7, From dd2eab0c410cdb42ebfbddd189bf6efa2540d2fb Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Fri, 17 Oct 2025 20:27:54 +0200 Subject: [PATCH 09/49] Fix bug where transformed pokemon lose copied stats on levelup (#7969) --- src/battle_script_commands.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b09ac176a9..e6c4a4adb4 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4773,7 +4773,15 @@ static void Cmd_getexp(void) if (battler != 0xFF) { - CopyMonLevelAndBaseStatsToBattleMon(battler, &gPlayerParty[*expMonId]); + if (gBattleMons[battler].volatiles.transformed) + { + gBattleMons[battler].level = GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL); + gBattleMons[battler].hp = GetMonData(&gPlayerParty[*expMonId], MON_DATA_HP); + } + else + { + CopyMonLevelAndBaseStatsToBattleMon(battler, &gPlayerParty[*expMonId]); + } if (gBattleMons[battler].volatiles.powerTrick) SWAP(gBattleMons[battler].attack, gBattleMons[battler].defense, temp); } From 38221b9de512f29cfe6b1c70e73e90265573d724 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:40:37 +0100 Subject: [PATCH 10/49] Fixes Shields Down incorrectly preventing status on Minior Core form (#7968) --- asm/macros/battle_script.inc | 4 +- data/battle_scripts_1.s | 6 +- src/battle_script_commands.c | 12 ++- src/battle_util.c | 6 +- test/battle/ability/flower_veil.c | 4 +- test/battle/ability/shields_down.c | 26 +++++++ test/battle/move_effect/rest.c | 117 +++++++++++++++++++++++++++++ 7 files changed, 159 insertions(+), 16 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 547e4d6e3f..5ccaf2ea50 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -2405,8 +2405,8 @@ .4byte \jumpInstr .endm - .macro jumpifleafguardprotected battler:req, jumpInstr:req - callnative BS_JumpIfLeafGuardProtected + .macro jumpifabilitypreventsrest battler:req, jumpInstr:req + callnative BS_JumpIfAbilityPreventsRest .byte \battler .4byte \jumpInstr .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 3cf1f53fec..db4f7e65e3 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -3186,9 +3186,7 @@ BattleScript_EffectRest:: jumpifability BS_TARGET, ABILITY_INSOMNIA, BattleScript_InsomniaProtects jumpifability BS_TARGET, ABILITY_VITAL_SPIRIT, BattleScript_InsomniaProtects jumpifability BS_ATTACKER, ABILITY_PURIFYING_SALT, BattleScript_InsomniaProtects -.if B_LEAF_GUARD_PREVENTS_REST >= GEN_5 - jumpifleafguardprotected BS_TARGET, BattleScript_LeafGuardPreventsRest -.endif + jumpifabilitypreventsrest BS_TARGET, BattleScript_AbilityPreventsRest trysetrest BattleScript_AlreadyAtFullHp pause B_WAIT_TIME_SHORT printfromtable gRestUsedStringIds @@ -3210,7 +3208,7 @@ BattleScript_RestIsAlreadyAsleep:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd -BattleScript_LeafGuardPreventsRest:: +BattleScript_AbilityPreventsRest:: pause B_WAIT_TIME_SHORT printstring STRINGID_BUTITFAILED waitmessage B_WAIT_TIME_LONG diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e6c4a4adb4..c9b64ffea2 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -18243,19 +18243,17 @@ void BS_JumpIfSpecies(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_JumpIfLeafGuardProtected(void) +void BS_JumpIfAbilityPreventsRest(void) { NATIVE_ARGS(u8 battler, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); - if (IsLeafGuardProtected(battler, GetBattlerAbility(battler))) - { - gBattlerAbility = battler; + u32 ability = GetBattlerAbility(battler); + if (B_LEAF_GUARD_PREVENTS_REST >= GEN_5 && IsLeafGuardProtected(battler, ability)) + gBattlescriptCurrInstr = cmd->jumpInstr; + else if (IsShieldsDownProtected(battler, ability)) gBattlescriptCurrInstr = cmd->jumpInstr; - } else - { gBattlescriptCurrInstr = cmd->nextInstr; - } } void BS_SetAttackerToStickyWebUser(void) diff --git a/src/battle_util.c b/src/battle_util.c index abf61c1d58..42329b5495 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5713,7 +5713,6 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u // Checks that apply to all non volatile statuses if (abilityDef == ABILITY_COMATOSE - || abilityDef == ABILITY_SHIELDS_DOWN || abilityDef == ABILITY_PURIFYING_SALT) { abilityAffected = TRUE; @@ -5728,6 +5727,11 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u abilityAffected = TRUE; battleScript = BattleScript_AbilityProtectsDoesntAffect; } + else if (IsShieldsDownProtected(battlerDef, abilityDef)) + { + abilityAffected = TRUE; + battleScript = BattleScript_AbilityProtectsDoesntAffect; + } else if ((sideBattler = IsFlowerVeilProtected(battlerDef))) { abilityAffected = TRUE; diff --git a/test/battle/ability/flower_veil.c b/test/battle/ability/flower_veil.c index cb72cd33ae..78d15df1bd 100644 --- a/test/battle/ability/flower_veil.c +++ b/test/battle/ability/flower_veil.c @@ -15,7 +15,7 @@ ASSUMPTIONS ASSUME(GetMoveNonVolatileStatus(MOVE_HYPNOSIS) == MOVE_EFFECT_SLEEP); } -DOUBLE_BATTLE_TEST("Flower Veil prevents Toxic bad poison on partner - right target") +DOUBLE_BATTLE_TEST("Flower Veil prevents status on allied Grass-types - right target") { u32 move; @@ -39,7 +39,7 @@ DOUBLE_BATTLE_TEST("Flower Veil prevents Toxic bad poison on partner - right tar } } -DOUBLE_BATTLE_TEST("Flower Veil prevents Toxic bad poison on partner - left target") +DOUBLE_BATTLE_TEST("Flower Veil prevents status on allied Grass-types - left target") { u32 move; diff --git a/test/battle/ability/shields_down.c b/test/battle/ability/shields_down.c index f9def2991d..5c46a8d589 100644 --- a/test/battle/ability/shields_down.c +++ b/test/battle/ability/shields_down.c @@ -32,3 +32,29 @@ SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on switch-in if it EXPECT_EQ(opponent->species, SPECIES_MINIOR_METEOR); } } + +SINGLE_BATTLE_TEST("Shields Down protects Minior Meteor from status conditions") +{ + u32 species, hp; + PARAMETRIZE { species = SPECIES_MINIOR_METEOR; hp = 300; } + PARAMETRIZE { species = SPECIES_MINIOR_CORE; hp = 100; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + PLAYER(SPECIES_WYNAUT); + OPPONENT(species) { Ability(ABILITY_SHIELDS_DOWN); HP(hp); MaxHP(300); } + } WHEN { + TURN { MOVE(player, MOVE_WILL_O_WISP); } + } SCENE { + if (species == SPECIES_MINIOR_METEOR) + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + } THEN { + if (species == SPECIES_MINIOR_METEOR) + EXPECT_EQ(opponent->status1, STATUS1_NONE); + else + EXPECT(opponent->status1 & STATUS1_BURN); + } +} diff --git a/test/battle/move_effect/rest.c b/test/battle/move_effect/rest.c index 4d0d85b560..fcf2b15567 100644 --- a/test/battle/move_effect/rest.c +++ b/test/battle/move_effect/rest.c @@ -1,4 +1,121 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_REST) == EFFECT_REST); +} + +SINGLE_BATTLE_TEST("Rest causes the user to fall asleep and restores HP to full") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(player->status1 & STATUS1_SLEEP); + EXPECT_EQ(player->hp, player->maxHP); + } +} + +SINGLE_BATTLE_TEST("Rest fails if the user is at full HP") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(300); MaxHP(300); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +SINGLE_BATTLE_TEST("Rest fails if the user is protected by Leaf Guard") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUNNY_DAY) == EFFECT_SUNNY_DAY); + ASSUME(B_LEAF_GUARD_PREVENTS_REST >= GEN_5); + PLAYER(SPECIES_CHIKORITA) { Ability(ABILITY_LEAF_GUARD); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +SINGLE_BATTLE_TEST("Rest fails if the user is protected by Shields Down") +{ + GIVEN { + PLAYER(SPECIES_MINIOR_METEOR) { Ability(ABILITY_SHIELDS_DOWN); HP(299); MaxHP(300); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +SINGLE_BATTLE_TEST("Rest fails if the user is protected by Electric/Misty Terrain") +{ + u32 move; + PARAMETRIZE { move = MOVE_ELECTRIC_TERRAIN; } + PARAMETRIZE { move = MOVE_MISTY_TERRAIN; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIC_TERRAIN) == EFFECT_ELECTRIC_TERRAIN); + ASSUME(GetMoveEffect(MOVE_MISTY_TERRAIN) == EFFECT_MISTY_TERRAIN); + ASSUME(GetSpeciesType(SPECIES_WYNAUT, 0) != TYPE_FLYING && GetSpeciesType(SPECIES_WYNAUT, 1) != TYPE_FLYING); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +SINGLE_BATTLE_TEST("Rest doesn't fail if the user is protected by Safeguard") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SAFEGUARD) == EFFECT_SAFEGUARD); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SAFEGUARD); } + TURN { MOVE(player, MOVE_REST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(player->status1 & STATUS1_SLEEP); + } +} + +DOUBLE_BATTLE_TEST("Rest doesn't fail if the user is protected by Flower Veil") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_CHIKORITA, 0) == TYPE_GRASS || GetSpeciesType(SPECIES_CHIKORITA, 1) == TYPE_GRASS); + PLAYER(SPECIES_CHIKORITA) { HP(1); } + PLAYER(SPECIES_FLORGES) { Ability(ABILITY_FLOWER_VEIL); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_REST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, playerLeft); + } THEN { + EXPECT(playerLeft->status1 & STATUS1_SLEEP); + } +} + TO_DO_BATTLE_TEST("TODO: Write Rest (Move Effect) test titles") From b03e58e2a1224011767364e472770fc640365e68 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Fri, 17 Oct 2025 22:57:18 +0200 Subject: [PATCH 11/49] Fix Persim Berry battle usage (#7963) Co-authored-by: Hedara --- src/data/items.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/items.h b/src/data/items.h index 9927191d64..6f19ad6d0b 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -9826,7 +9826,7 @@ const struct Item gItemsInfo[] = "heals confusion\n" "in battle."), .pocket = POCKET_BERRIES, - .type = ITEM_USE_BAG_MENU, + .type = ITEM_USE_PARTY_MENU, .fieldUseFunc = ItemUseOutOfBattle_CannotUse, .battleUsage = EFFECT_ITEM_CURE_STATUS, .effect = gItemEffect_PersimBerry, From e0fec934bc8d9feb1ed44b1037498ea640848b9b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 22:58:09 +0200 Subject: [PATCH 12/49] add Syreldar as a contributor for doc, and userTesting (#7888) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 10 ++++++++++ CREDITS.md | 3 +++ 2 files changed, 13 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 7d2e4d4bbf..79c5e9bb26 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -357,6 +357,16 @@ "contributions": [ "design" ] + }, + { + "login": "Syreldar", + "name": "Enrico Drago", + "avatar_url": "https://avatars.githubusercontent.com/u/42327659?v=4", + "profile": "https://metin2.dev/index.php", + "contributions": [ + "doc", + "userTesting" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 4c0b6a3d3e..fef2b2eb63 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -62,6 +62,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d tustin2121
tustin2121

📖 💻 Phantonomy
Phantonomy

🎨 + + Enrico Drago
Enrico Drago

📖 📓 + From f4a4164c067980da469229b72964c6416d9be79d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 23:22:58 +0200 Subject: [PATCH 13/49] add Pyredrid as a contributor for userTesting, and maintenance (#7889) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- .all-contributorsrc | 10 ++++++++++ CREDITS.md | 1 + 2 files changed, 11 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 79c5e9bb26..0d95328420 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -367,6 +367,16 @@ "doc", "userTesting" ] + }, + { + "login": "Pyredrid", + "name": "Pyredrid", + "avatar_url": "https://avatars.githubusercontent.com/u/8324784?v=4", + "profile": "https://github.com/Pyredrid", + "contributions": [ + "userTesting", + "maintenance" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index fef2b2eb63..36e45717f2 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -64,6 +64,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Enrico Drago
Enrico Drago

📖 📓 + Pyredrid
Pyredrid

📓 🚧 From db3d39d071cc880ea75a2b361b46d1f2b1ba9368 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 23:48:56 +0200 Subject: [PATCH 14/49] add mvit as a contributor for code, and design (#7890) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- .all-contributorsrc | 10 ++++++++++ CREDITS.md | 1 + 2 files changed, 11 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 0d95328420..f0349e5445 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -377,6 +377,16 @@ "userTesting", "maintenance" ] + }, + { + "login": "mvit", + "name": "mv", + "avatar_url": "https://avatars.githubusercontent.com/u/128863?v=4", + "profile": "https://github.com/mvit", + "contributions": [ + "code", + "design" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 36e45717f2..0798af22e9 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -65,6 +65,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Enrico Drago
Enrico Drago

📖 📓 Pyredrid
Pyredrid

📓 🚧 + mv
mv

💻 🎨 From 721f385e1d4056ccd1e3896caae889cbbebbbbb4 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 18 Oct 2025 00:18:26 +0200 Subject: [PATCH 15/49] add Mother-Of-Dragons as a contributor for data (#7891) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CREDITS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index f0349e5445..ae837d03d9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -387,6 +387,15 @@ "code", "design" ] + }, + { + "login": "Mother-Of-Dragons", + "name": "Avara", + "avatar_url": "https://avatars.githubusercontent.com/u/31101124?v=4", + "profile": "https://github.com/Mother-Of-Dragons", + "contributions": [ + "data" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 0798af22e9..c1cb8f87b8 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -66,6 +66,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Enrico Drago
Enrico Drago

📖 📓 Pyredrid
Pyredrid

📓 🚧 mv
mv

💻 🎨 + Avara
Avara

🔣 From eeceff27e7570e085a08f69a461072087438afe4 Mon Sep 17 00:00:00 2001 From: Raymond Dodge Date: Sat, 18 Oct 2025 04:56:40 -0400 Subject: [PATCH 16/49] Relativize doc links, to fix links in docs site (#7964) --- docs/fix_links.py | 1 - docs/local_mdbook/index.md | 2 +- docs/team_procedures/merge_checklist.md | 10 +++++----- docs/team_procedures/schedule.md | 2 +- docs/tutorials/dns.md | 6 +++--- docs/tutorials/how_to_follower_npc.md | 2 +- docs/tutorials/how_to_new_pokemon.md | 16 ++++++++-------- 7 files changed, 19 insertions(+), 20 deletions(-) diff --git a/docs/fix_links.py b/docs/fix_links.py index 7b6b01b995..6e2eaec485 100644 --- a/docs/fix_links.py +++ b/docs/fix_links.py @@ -36,7 +36,6 @@ def proc_items(items): s = s.replace('](README.md)', '](./)') s = s.replace('](/INSTALL.md', '](INSTALL.md') s = s.replace('](docs/', '](') - s = s.replace('](/docs/', '](/') s = URL_RE.sub(handle_url, s) item['Chapter']['content'] = ANCHOR_RE.sub(handle_anchor, s) proc_items(item['Chapter']['sub_items']) diff --git a/docs/local_mdbook/index.md b/docs/local_mdbook/index.md index ef362dc45f..fa4b7f1e70 100644 --- a/docs/local_mdbook/index.md +++ b/docs/local_mdbook/index.md @@ -1,2 +1,2 @@ ## Running documentation website locally -- [Ubuntu WSL1/WSL2](/docs/local_mdbook/ubuntu_WSL.md) +- [Ubuntu WSL1/WSL2](ubuntu_WSL.md) diff --git a/docs/team_procedures/merge_checklist.md b/docs/team_procedures/merge_checklist.md index ae3ff60b19..53e8d7683f 100644 --- a/docs/team_procedures/merge_checklist.md +++ b/docs/team_procedures/merge_checklist.md @@ -7,7 +7,7 @@ This document is a guide for maintainers to account for all the reccomended step # Checklist ## Is the branch's theoretical functionality in scope? -If you're not sure if a branch's functionality is [in scope](docs/team_procedures/scope.md), start a conversation on Discord to resolve. +If you're not sure if a branch's functionality is [in scope](scope.md), start a conversation on Discord to resolve. ## Does the branch successfully compile? From `make clean`, the branch should locally compile. @@ -37,18 +37,18 @@ If you're not sure if something CAN be tested, start a discussion. Some contribu If any new tests are `KNOWN_FAILING`, issues should be opened describing each of the `KNOWN_FAILING` tests and our understanding of why they fail. -## Does the branch meet our [config philosophy](/docs/STYLEGUIDE.md#config-philosophy)? +## Does the branch meet our [config philosophy](../STYLEGUIDE.md#config-philosophy)? -## Does the branch meet our [saves philosophy](/docs/STYLEGUIDE.md#saves-philosophy)? +## Does the branch meet our [saves philosophy](../STYLEGUIDE.md#saves-philosophy)? -## Does the submitted code follow the [styleguide](/docs/STYLEGUIDE.md)? +## Does the submitted code follow the [styleguide](../STYLEGUIDE.md)? This applies to code that comes from other branches or games. ## Is the pull request appropriately labeled? Without labels, the CHANGELOG will not be properly formatted. For specifically the `bugfix` label, an additional label, detailing what area the bug exists in is required. ## Is `pokeemerald-expansion` free from a merge freeze? -Our [release schedule](/docs/team_procedures/schedule.md) prevents us from merging Big Features and non-bugfixes within certain dates close to a release. Please use `/release` in the RHH Discord to clarify when these are occuring. +Our [release schedule](schedule.md) prevents us from merging Big Features and non-bugfixes within certain dates close to a release. Please use `/release` in the RHH Discord to clarify when these are occuring. # Merging diff --git a/docs/team_procedures/schedule.md b/docs/team_procedures/schedule.md index c689e5b454..520c4af000 100644 --- a/docs/team_procedures/schedule.md +++ b/docs/team_procedures/schedule.md @@ -53,4 +53,4 @@ This designation should be reserved for instances where an existing feature on ` Blocking issues or PRs can be deferred to future releases but should be discussed with the Maintainers that assigned the designation in the first place. -If a version's milestone does not have any issues or PRs assigned to it, that version should be [released](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/docs/team_procedures/expansion_versions.md) as close to the goal date as possible. +If a version's milestone does not have any issues or PRs assigned to it, that version should be [released](expansion_versions.md) as close to the goal date as possible. diff --git a/docs/tutorials/dns.md b/docs/tutorials/dns.md index 908329761e..6fdd1c1d77 100644 --- a/docs/tutorials/dns.md +++ b/docs/tutorials/dns.md @@ -23,8 +23,8 @@ When writing map scripts, `fadescreenswapbuffers` should be preferred over `fade ### Q: How do I make lightbulbs glow? -![Rustboro before adding lamp object events](/docs/tutorials/img/dns/without_lamp.png) -![Rustboro after adding lamp object events](/docs/tutorials/img/dns/with_lamp.png) +![Rustboro before adding lamp object events](img/dns/without_lamp.png) +![Rustboro after adding lamp object events](img/dns/with_lamp.png) A: Making lamps glow is not part of the tileset itself. Instead, place certain object events on top of where you desire a glowing effect. @@ -48,7 +48,7 @@ on separate lines to mark those colors as being light-blended, i.e: During the day time, these color indices appear as normal, but will be blended with either yellow or the 0 index at night. These indices should only be used for things you expect to light up. If you are using [porytiles](https://github.com/grunt-lucas/porytiles/wiki), palette overrides and using slight alterations to a color will aid you in avoiding color conflicts where the wrong index is assigned. -![Rustboro gym after light-blending the windows](/docs/tutorials/img/dns/window_lights.png) +![Rustboro gym after light-blending the windows](img/dns/window_lights.png) The windows appear as normal during the day time (blue) and light up in the night. These use the default color. diff --git a/docs/tutorials/how_to_follower_npc.md b/docs/tutorials/how_to_follower_npc.md index 2e449adbc0..fbd0087859 100644 --- a/docs/tutorials/how_to_follower_npc.md +++ b/docs/tutorials/how_to_follower_npc.md @@ -2,7 +2,7 @@ *Written by Bivurnum* *gif by ghoulslash* -![follower-npc](/docs/tutorials/img/follower_npc/follower-npc.gif) +![follower-npc](img/follower_npc/follower-npc.gif) ## Configs The configs for follower NPCs can be found in [include/config/follower_npc.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/upcoming/include/config/follower_npc.h). diff --git a/docs/tutorials/how_to_new_pokemon.md b/docs/tutorials/how_to_new_pokemon.md index da6add17bc..79f01acbd2 100644 --- a/docs/tutorials/how_to_new_pokemon.md +++ b/docs/tutorials/how_to_new_pokemon.md @@ -42,7 +42,7 @@ The main things that the Expansion changes are listed here. # Useful resources You can open a sprite debug menu by pressing `Select` in a Pokémon's summary screen outside of battle. -![visualizer1](/docs/tutorials/img/add_pokemon/visualizer1.gif) +![visualizer1](img/add_pokemon/visualizer1.gif) # The Data - Part 1 @@ -73,7 +73,7 @@ We add this at the end so that no existing species change Id and so that we don' Now, let's see how it looks in-game! -![visualizer2](/docs/tutorials/img/add_pokemon/visualizer2.png) +![visualizer2](img/add_pokemon/visualizer2.png) Hmmm, something's not right... @@ -446,7 +446,7 @@ Now we can add the number and entry to our Mewthree: }, }; ``` -![image](/docs/tutorials/img/add_pokemon/dex1.png) +![image](img/add_pokemon/dex1.png) The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex. @@ -497,7 +497,7 @@ Edit [src/data/pokemon/pokedex_orders.h](https://github.com/rh-hideout/pokeemera ... }; ``` -![mGBA_lUBfmFEKUx](/docs/tutorials/img/add_pokemon/dex2.gif) +![mGBA_lUBfmFEKUx](img/add_pokemon/dex2.gif) # The Graphics @@ -1058,7 +1058,7 @@ What this allows us to do is to be able to get all forms of a Pokémon in our co For example, in the HGSS dex, it lets us browse between the entries of every form available.: -![hgssdex1](/docs/tutorials/img/add_pokemon/hgssdex1.png) ![image](/docs/tutorials/img/add_pokemon/hgssdex2.png) +![hgssdex1](img/add_pokemon/hgssdex1.png) ![image](img/add_pokemon/hgssdex2.png) In addition, we have the `GET_BASE_SPECIES_ID` macro, which returns the first entry of the table (or return the species itself if it doesn't have a table registered). With this, you can check if a Pokémon is any form of a species. For example, making it so that the Light Ball affects all Pikachu forms: ```c @@ -1089,7 +1089,7 @@ The second value is the target form, to which the Pokémon will change into. Values after that are referred as arguments, and needs to be put there depends on the type of form change, detailed in `include/constants/form_change_types.h`. ## 3. Gender differences -![gender_diffs](/docs/tutorials/img/add_pokemon/gender_diffs.gif) +![gender_diffs](img/add_pokemon/gender_diffs.gif) You may have seen that there's a couple of duplicate fields with a "Female" suffix. ```diff @@ -1262,8 +1262,8 @@ Either way, you may also create custom animation tables and use them here approp ### How to add the Pokémon Object Events to map In Porymap, select the object you want to set the sprite to. Then, change the field "Sprite" to use `OBJ_EVENT_GFX_SPECIES(SPECIES)`, replacing SPECIES with the name of the species you want to use. If you get a compiler error, it's because it used the species define as part of the macro, so it needs to match how you defined it all the way back in [Declare a species constant](#1-Declare-a-species-constant). -![charizard](/docs/tutorials/img/add_pokemon/charizard.png) -![overworld_data](/docs/tutorials/img/add_pokemon/overworld_data.gif) +![charizard](img/add_pokemon/charizard.png) +![overworld_data](img/add_pokemon/overworld_data.gif) If you want to use their shiny and/or female versions, use one of the following macros: - `OBJ_EVENT_GFX_SPECIES_SHINY(name)` From ade1f26d4176810a74dd984ff31cc750905faeff Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 18 Oct 2025 11:09:35 +0200 Subject: [PATCH 17/49] add Doesnty as a contributor for design (#7892) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CREDITS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index ae837d03d9..2be39e2539 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -396,6 +396,15 @@ "contributions": [ "data" ] + }, + { + "login": "Doesnty", + "name": "Doesnty", + "avatar_url": "https://avatars.githubusercontent.com/u/6163136?v=4", + "profile": "https://github.com/Doesnty", + "contributions": [ + "design" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index c1cb8f87b8..60350703e5 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -67,6 +67,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Pyredrid
Pyredrid

📓 🚧 mv
mv

💻 🎨 Avara
Avara

🔣 + Doesnty
Doesnty

🎨 From cff5c8aa060e4312db7a60a3e31e57c935239c5a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 18 Oct 2025 11:42:59 +0200 Subject: [PATCH 18/49] add FosterProgramming as a contributor for code (#7893) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CREDITS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 2be39e2539..7f904db31b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -405,6 +405,15 @@ "contributions": [ "design" ] + }, + { + "login": "FosterProgramming", + "name": "FosterProgramming", + "avatar_url": "https://avatars.githubusercontent.com/u/178871164?v=4", + "profile": "https://github.com/FosterProgramming", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 60350703e5..476b0f8eee 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -68,6 +68,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d mv
mv

💻 🎨 Avara
Avara

🔣 Doesnty
Doesnty

🎨 + FosterProgramming
FosterProgramming

💻 From 55f8e401c9cca3fdbc00fa830cff349b5a30920a Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 18 Oct 2025 12:40:05 +0200 Subject: [PATCH 19/49] add Squeetz as a contributor for maintenance (#7894) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CREDITS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 7f904db31b..241a2d088f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -414,6 +414,15 @@ "contributions": [ "code" ] + }, + { + "login": "Squeetz", + "name": "Squeetz", + "avatar_url": "https://avatars.githubusercontent.com/u/21145213?v=4", + "profile": "https://github.com/Squeetz", + "contributions": [ + "maintenance" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 476b0f8eee..8b4271a701 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -69,6 +69,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Avara
Avara

🔣 Doesnty
Doesnty

🎨 FosterProgramming
FosterProgramming

💻 + Squeetz
Squeetz

🚧 From ffe08b52e3e0bce0b4deb54fb2514acc822ecf36 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 18 Oct 2025 14:12:25 +0200 Subject: [PATCH 20/49] add ghostyboyy97 as a contributor for code (#7901) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CREDITS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 241a2d088f..a04141fcf7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -423,6 +423,15 @@ "contributions": [ "maintenance" ] + }, + { + "login": "ghostyboyy97", + "name": "ghostyboyy97", + "avatar_url": "https://avatars.githubusercontent.com/u/106448956?v=4", + "profile": "https://github.com/ghostyboyy97", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 8b4271a701..1cd4611d69 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -70,6 +70,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Doesnty
Doesnty

🎨 FosterProgramming
FosterProgramming

💻 Squeetz
Squeetz

🚧 + ghostyboyy97
ghostyboyy97

💻 From 90c3a8cb2c48142d88f40a35c19517629bea29c9 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sat, 18 Oct 2025 18:52:57 +0200 Subject: [PATCH 21/49] Fix battle dome pre round 1 waiting room (#7976) --- data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc b/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc index e06284c2ca..8e01660a31 100644 --- a/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc +++ b/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc @@ -18,6 +18,7 @@ BattleFrontier_BattleDomePreBattleRoom_OnFrame: BattleFrontier_BattleDomePreBattleRoom_EventScript_EnterRoom:: goto_if_eq VAR_0x8006, 1, BattleFrontier_BattleDomePreBattleRoom_EventScript_ReturnFromBattle + delay 0 frontier_set FRONTIER_DATA_RECORD_DISABLED, TRUE setvar VAR_TEMP_0, 1 applymovement LOCALID_PLAYER, BattleFrontier_BattleDomePreBattleRoom_Movement_PlayerEnter From ddddca68e997fcc4b7568f46ab2f42228cfacef2 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 19 Oct 2025 07:42:59 +0200 Subject: [PATCH 22/49] Fix no_effect script command overwriting trainer data in trainer script (#7978) --- src/trainer_see.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/trainer_see.c b/src/trainer_see.c index 71237b4e29..0433e56071 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -429,7 +429,7 @@ bool8 CheckForTrainersWantingBattle(void) static u8 CheckTrainer(u8 objectEventId) { - const u8 *scriptPtr, *trainerBattlePtr; + const u8 *trainerBattlePtr; u8 numTrainers = 1; u8 approachDistance = GetTrainerApproachDistance(&gObjectEvents[objectEventId]); @@ -438,13 +438,13 @@ static u8 CheckTrainer(u8 objectEventId) if (InTrainerHill() == TRUE) { - trainerBattlePtr = scriptPtr = GetTrainerHillTrainerScript(); + trainerBattlePtr = GetTrainerHillTrainerScript(); } else { - trainerBattlePtr = scriptPtr = GetObjectEventScriptPointerByObjectEventId(objectEventId); + trainerBattlePtr = GetObjectEventScriptPointerByObjectEventId(objectEventId); struct ScriptContext ctx; - if (RunScriptImmediatelyUntilEffect(SCREFF_V1 | SCREFF_SAVE | SCREFF_HARDWARE | SCREFF_TRAINERBATTLE, scriptPtr, &ctx)) + if (RunScriptImmediatelyUntilEffect(SCREFF_V1 | SCREFF_SAVE | SCREFF_HARDWARE | SCREFF_TRAINERBATTLE, trainerBattlePtr, &ctx)) { if (*ctx.scriptPtr == 0x5c) // trainerbattle trainerBattlePtr = ctx.scriptPtr; @@ -492,7 +492,7 @@ static u8 CheckTrainer(u8 objectEventId) } gApproachingTrainers[gNoOfApproachingTrainers].objectEventId = objectEventId; - gApproachingTrainers[gNoOfApproachingTrainers].trainerScriptPtr = scriptPtr; + gApproachingTrainers[gNoOfApproachingTrainers].trainerScriptPtr = trainerBattlePtr; gApproachingTrainers[gNoOfApproachingTrainers].radius = approachDistance; InitTrainerApproachTask(&gObjectEvents[objectEventId], approachDistance - 1); gNoOfApproachingTrainers++; From 5b2ecfe4d4ecad99e6da73ac72e479e22efab678 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 19 Oct 2025 08:45:21 +0200 Subject: [PATCH 23/49] Setting wallclock time now properly sets fakeRTC (#7860) Co-authored-by: Bassoonian --- src/overworld.c | 4 ++++ src/rtc.c | 1 + src/wallclock.c | 10 +++++----- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/overworld.c b/src/overworld.c index e8be437519..d9d5543798 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -1794,6 +1794,10 @@ void CB2_NewGame(void) SetFieldVBlankCallback(); SetMainCallback1(CB1_Overworld); SetMainCallback2(CB2_Overworld); +#if OW_USE_FAKE_RTC + // Wall clock now track local time so we set it to 10AM to match initial wall clock time + RtcCalcLocalTimeOffset(0, 10, 0, 0); +#endif } void CB2_WhiteOut(void) diff --git a/src/rtc.c b/src/rtc.c index 34f84183cf..fb2518f210 100644 --- a/src/rtc.c +++ b/src/rtc.c @@ -348,6 +348,7 @@ void RtcCalcLocalTimeOffset(s32 days, s32 hours, s32 minutes, s32 seconds) gLocalTime.hours = hours; gLocalTime.minutes = minutes; gLocalTime.seconds = seconds; + FakeRtc_ManuallySetTime(gLocalTime.days, gLocalTime.hours, gLocalTime.minutes, seconds); RtcGetInfo(&sRtc); RtcCalcTimeDifference(&sRtc, &gSaveBlock2Ptr->localTimeOffset, &gLocalTime); } diff --git a/src/wallclock.c b/src/wallclock.c index 28c96fc972..bca008ac24 100644 --- a/src/wallclock.c +++ b/src/wallclock.c @@ -692,13 +692,13 @@ void CB2_StartWallClock(void) DecompressDataWithHeaderVram(gWallClockStart_Tilemap, (u16 *)BG_SCREEN_ADDR(7)); taskId = CreateTask(Task_SetClock_WaitFadeIn, 0); - gTasks[taskId].tHours = 10; - gTasks[taskId].tMinutes = 0; + gTasks[taskId].tHours = gLocalTime.hours; + gTasks[taskId].tMinutes = gLocalTime.minutes; gTasks[taskId].tMoveDir = 0; - gTasks[taskId].tPeriod = 0; + gTasks[taskId].tPeriod = gTasks[taskId].tHours / 12; gTasks[taskId].tMoveSpeed = 0; - gTasks[taskId].tMinuteHandAngle = 0; - gTasks[taskId].tHourHandAngle = 300; + gTasks[taskId].tMinuteHandAngle = gTasks[taskId].tMinutes * 6; + gTasks[taskId].tHourHandAngle = (gTasks[taskId].tHours % 12) * 30 + (gTasks[taskId].tMinutes / 10) * 5; spriteId = CreateSprite(&sSpriteTemplate_MinuteHand, 120, 80, 1); gSprites[spriteId].sTaskId = taskId; From 4816fe02c243e6192fbd12350c8c5977bedd3b31 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 19 Oct 2025 16:39:07 +0200 Subject: [PATCH 24/49] Bugfix hidefollower not waiting properly (#7768) --- asm/macros/event.inc | 2 +- data/script_cmd_table.inc | 1 + src/scrcmd.c | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 43b9417860..f8578a4a9c 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -2482,7 +2482,7 @@ @ Hides any follower Pokémon if present, putting them into their Poké Ball; by default waits for their movement to finish. .macro hidefollower wait=1 - callnative ScrFunc_hidefollower + .byte SCR_OP_HIDEFOLLOWER .2byte \wait .endm diff --git a/data/script_cmd_table.inc b/data/script_cmd_table.inc index e05260d3d4..a3defef83e 100644 --- a/data/script_cmd_table.inc +++ b/data/script_cmd_table.inc @@ -251,6 +251,7 @@ gScriptCmdTable:: script_cmd_table_entry SCR_OP_BUFFERITEMNAMEPLURAL ScrCmd_bufferitemnameplural, requests_effects=1 @ 0xe2 script_cmd_table_entry SCR_OP_DYNMULTICHOICE ScrCmd_dynmultichoice, requests_effects=1 @ 0xe3 script_cmd_table_entry SCR_OP_DYNMULTIPUSH ScrCmd_dynmultipush, requests_effects=1 @ 0xe4 + script_cmd_table_entry SCR_OP_HIDEFOLLOWER ScrCmd_hidefollower, requests_effects=1 @ 0xe5 .if ALLOCATE_SCRIPT_CMD_TABLE gScriptCmdTableEnd:: diff --git a/src/scrcmd.c b/src/scrcmd.c index 767f41c6d5..54edd76c61 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -3154,11 +3154,13 @@ bool8 Scrcmd_getobjectfacingdirection(struct ScriptContext *ctx) return FALSE; } -bool8 ScrFunc_hidefollower(struct ScriptContext *ctx) +bool8 ScrCmd_hidefollower(struct ScriptContext *ctx) { bool16 wait = VarGet(ScriptReadHalfword(ctx)); struct ObjectEvent *obj; + Script_RequestEffects(SCREFF_V1 | SCREFF_HARDWARE); + if ((obj = ScriptHideFollower()) != NULL && wait) { sMovingNpcId = obj->localId; From fb5f5b8f576a66cc365632d7dbcf6fa1e86685cf Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 19 Oct 2025 20:37:43 +0200 Subject: [PATCH 25/49] Fix ruination and nature's madness damage percentage (#7983) --- src/data/moves_info.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 35fed13f71..d521622efb 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -17103,6 +17103,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_SPECIAL, + .argument = { .damagePercentage = 50 }, .metronomeBanned = B_UPDATED_MOVE_FLAGS >= GEN_8, .contestEffect = CONTEST_EFFECT_BADLY_STARTLE_MONS_WITH_GOOD_APPEALS, .contestCategory = CONTEST_CATEGORY_CUTE, @@ -20171,6 +20172,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_SPECIAL, + .argument = { .damagePercentage = 50 }, .metronomeBanned = TRUE, .battleAnimScript = gBattleAnimMove_Ruination, }, From 51de48fc3f88edd44c2598bfe30613e797f2bad8 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Sun, 19 Oct 2025 20:40:34 +0200 Subject: [PATCH 26/49] SetShellSideArmCategory avoid div by zero (#7980) --- src/battle_util.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/battle_util.c b/src/battle_util.c index 42329b5495..617bb8b2e5 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -11248,6 +11248,8 @@ void SetShellSideArmCategory(void) statStage = gBattleMons[battlerDef].statStages[STAT_DEF]; targetDefStat *= gStatStageRatios[statStage][0]; targetDefStat /= gStatStageRatios[statStage][1]; + if (targetDefStat == 0) + targetDefStat = 1; physical = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * power * attackerAtkStat) / targetDefStat) / 50); @@ -11255,6 +11257,8 @@ void SetShellSideArmCategory(void) statStage = gBattleMons[battlerDef].statStages[STAT_SPDEF]; targetSpDefStat *= gStatStageRatios[statStage][0]; targetSpDefStat /= gStatStageRatios[statStage][1]; + if (targetSpDefStat == 0) + targetSpDefStat = 1; special = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * power * attackerSpAtkStat) / targetSpDefStat) / 50); From c16e2ded6062555d2e0c474d63ec8afb7a441ff3 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Sun, 19 Oct 2025 22:36:59 +0200 Subject: [PATCH 27/49] Fix ribbon colours (#7971) Co-authored-by: Hedara --- src/pokenav_ribbons_summary.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pokenav_ribbons_summary.c b/src/pokenav_ribbons_summary.c index 4b08b05cac..1e3d88b2dd 100644 --- a/src/pokenav_ribbons_summary.c +++ b/src/pokenav_ribbons_summary.c @@ -585,7 +585,11 @@ static u32 LoopedTask_OpenRibbonsSummaryMenu(s32 state) DecompressAndCopyTileDataToVram(1, sRibbonIconsSmall_Gfx, 0, 1, 0); SetBgTilemapBuffer(1, menu->tilemapBuffers[1]); FillBgTilemapBufferRect_Palette0(1, 0, 0, 0, 32, 20); - CopyPaletteIntoBufferUnfaded(sRibbonIcons1_Pal, BG_PLTT_ID(2), 5 * PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons1_Pal, BG_PLTT_ID(2), PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons2_Pal, BG_PLTT_ID(3), PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons3_Pal, BG_PLTT_ID(4), PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons4_Pal, BG_PLTT_ID(5), PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons5_Pal, BG_PLTT_ID(6), PLTT_SIZE_4BPP); CopyPaletteIntoBufferUnfaded(sMonInfo_Pal, BG_PLTT_ID(10), sizeof(sMonInfo_Pal)); CopyBgTilemapBufferToVram(1); return LT_INC_AND_PAUSE; From 1dd7a5731c6507025d914c550792550e7e5768b5 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Sun, 19 Oct 2025 22:37:23 +0200 Subject: [PATCH 28/49] CalcBarFilledPixels Safe Div (#7979) --- src/battle_interface.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/battle_interface.c b/src/battle_interface.c index 2264d94ce4..29a2cf0436 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -2258,10 +2258,11 @@ static u8 CalcBarFilledPixels(s32 maxValue, s32 oldValue, s32 receivedValue, s32 for (i = 0; i < scale; i++) pixelsArray[i] = 0; + // Safe Div, because 2vs1 battles can have maxValue 0. if (maxValue < totalPixels) - pixels = (*currValue * totalPixels / maxValue) >> 8; + pixels = SAFE_DIV(*currValue * totalPixels, maxValue) >> 8; else - pixels = *currValue * totalPixels / maxValue; + pixels = SAFE_DIV(*currValue * totalPixels, maxValue); filledPixels = pixels; From e5df8f0212edd481122408322acf00cdc3f2e861 Mon Sep 17 00:00:00 2001 From: Estellar <137097857+estellarc@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:50:54 -0300 Subject: [PATCH 29/49] Bugfix Emotes not loading their palette (#7843) --- src/trainer_see.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/trainer_see.c b/src/trainer_see.c index 0433e56071..4439ce6977 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -951,13 +951,17 @@ u8 FldEff_HeartIcon(void) return 0; } - u8 FldEff_DoubleExclMarkIcon(void) { u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_ExclamationQuestionMark, 0, 0, 0x53); if (spriteId != MAX_SPRITES) - SetIconSpriteData(&gSprites[spriteId], FLDEFF_EXCLAMATION_MARK_ICON, 2); + { + struct Sprite *sprite = &gSprites[spriteId]; + + SetIconSpriteData(sprite, FLDEFF_DOUBLE_EXCL_MARK_ICON, 2); + UpdateSpritePaletteByTemplate(&sSpriteTemplate_ExclamationQuestionMark, sprite); + } return 0; } @@ -967,7 +971,12 @@ u8 FldEff_XIcon(void) u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_ExclamationQuestionMark, 0, 0, 0x53); if (spriteId != MAX_SPRITES) - SetIconSpriteData(&gSprites[spriteId], FLDEFF_EXCLAMATION_MARK_ICON, 3); + { + struct Sprite *sprite = &gSprites[spriteId]; + + SetIconSpriteData(sprite, FLDEFF_X_ICON, 3); + UpdateSpritePaletteByTemplate(&sSpriteTemplate_ExclamationQuestionMark, sprite); + } return 0; } From 24c595bfec5f8467916a49a138c1ce2cf4689c99 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Mon, 20 Oct 2025 13:38:01 +0200 Subject: [PATCH 30/49] Fix psychic terrain affecting semi-invulnerable mons (#7986) --- src/battle_util.c | 3 +-- test/battle/move_effect/psychic_terrain.c | 32 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index 617bb8b2e5..b0206f98bd 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2403,8 +2403,7 @@ static enum MoveCanceller CancellerProtean(void) static enum MoveCanceller CancellerPsychicTerrain(void) { - if (gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN - && IsBattlerGrounded(gBattlerTarget) + if (IsBattlerTerrainAffected(gBattlerTarget, STATUS_FIELD_PSYCHIC_TERRAIN) && GetChosenMovePriority(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) > 0 && GetMoveTarget(gCurrentMove) != MOVE_TARGET_ALL_BATTLERS && GetMoveTarget(gCurrentMove) != MOVE_TARGET_OPPONENTS_FIELD diff --git a/test/battle/move_effect/psychic_terrain.c b/test/battle/move_effect/psychic_terrain.c index b85653a0be..2c5089c704 100644 --- a/test/battle/move_effect/psychic_terrain.c +++ b/test/battle/move_effect/psychic_terrain.c @@ -114,6 +114,38 @@ SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority field moves") } } +SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves against semi-invulnerable targets") +{ + u32 move = 0, shouldWork = 0; + PARAMETRIZE { move = MOVE_SOLAR_BEAM; shouldWork = FALSE;} + PARAMETRIZE { move = MOVE_FLY; shouldWork = TRUE;} + GIVEN { + PLAYER(SPECIES_SHROODLE) { Ability(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); MOVE(opponent,move);} + TURN { MOVE(player, MOVE_TOXIC); SKIP_TURN(opponent);} + } SCENE { + if (shouldWork) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + } + else + { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + } + } + } THEN { + if (shouldWork) + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + else + EXPECT(!(opponent->status1 & STATUS1_TOXIC_POISON)); + } +} + SINGLE_BATTLE_TEST("Psychic Terrain lasts for 5 turns") { GIVEN { From ac321bf581147fa83d63b59c930ce1d8984839c1 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Mon, 20 Oct 2025 15:56:55 +0200 Subject: [PATCH 31/49] Fix spritesheet rules for rayquaza sprite in cutscene (#7985) --- graphics_file_rules.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics_file_rules.mk b/graphics_file_rules.mk index 2b9cb9df8b..0b8a91415d 100644 --- a/graphics_file_rules.mk +++ b/graphics_file_rules.mk @@ -405,7 +405,7 @@ $(RAYQUAZAGFXDIR)/scene_2/bg.4bpp: %.4bpp: %.png $(GFX) $< $@ -num_tiles 313 -Wnum_tiles $(RAYQUAZAGFXDIR)/scene_3/rayquaza.4bpp: %.4bpp: %.png - $(GFX) $< $@ -num_tiles 124 -Wnum_tiles + $(GFX) $< $@ -num_tiles 128 -Wnum_tiles $(RAYQUAZAGFXDIR)/scene_4/streaks.4bpp: %.4bpp: %.png $(GFX) $< $@ -num_tiles 19 -Wnum_tiles From c3735c5bd0efac67d198ccc9f7b7116a9bac7506 Mon Sep 17 00:00:00 2001 From: Marky <143505183+HashtagMarky@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:23:05 +0100 Subject: [PATCH 32/49] =?UTF-8?q?Fix=20OW=20Pok=C3=A9mon=20VObjects=20(#79?= =?UTF-8?q?91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/event_object_movement.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 6d0cf86cf6..0c17f5c445 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -1927,8 +1927,15 @@ u8 CreateVirtualObject(u16 graphicsId, u8 virtualObjId, s16 x, s16 y, u8 elevati x += MAP_OFFSET; y += MAP_OFFSET; SetSpritePosToOffsetMapCoords(&x, &y, 8, 16); - if (spriteTemplate.paletteTag != TAG_NONE) + if (spriteTemplate.paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) + { + u32 paletteNum = LoadDynamicFollowerPaletteFromGraphicsId(graphicsId, &spriteTemplate); + spriteTemplate.paletteTag = GetSpritePaletteTagByPaletteNum(paletteNum); + } + else if (spriteTemplate.paletteTag != TAG_NONE) + { LoadObjectEventPalette(spriteTemplate.paletteTag); + } spriteId = CreateSpriteAtEnd(&spriteTemplate, x, y, 0); if (spriteId != MAX_SPRITES) @@ -1942,6 +1949,9 @@ u8 CreateVirtualObject(u16 graphicsId, u8 virtualObjId, s16 x, s16 y, u8 elevati sprite->sVirtualObjId = virtualObjId; sprite->sVirtualObjElev = elevation; + if (OW_GFX_COMPRESS && graphicsInfo->compressed) + spriteTemplate.tileTag = LoadSheetGraphicsInfo(graphicsInfo, graphicsId, sprite); + if (subspriteTables != NULL) { SetSubspriteTables(sprite, subspriteTables); From bc6ba15702dda7a50ead49f8deea5bb3cfac66e5 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:50:55 +0200 Subject: [PATCH 33/49] Fixes Terrain Extender timer (#7995) --- src/battle_script_commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index c9b64ffea2..d64a2d953f 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -15339,7 +15339,7 @@ void BS_SetTerrain(void) enum ItemHoldEffect atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; gFieldStatuses |= statusFlag; - gFieldTimers.terrainTimer = gBattleTurnCounter + (atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5; + gFieldTimers.terrainTimer = gBattleTurnCounter + ((atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5); gBattlescriptCurrInstr = cmd->nextInstr; } else From 41fa1ad2b90de712c3d8e705113afda4c9928cba Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:08:31 +0200 Subject: [PATCH 34/49] add HashtagMarky as a contributor for code (#7998) Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 11 ++++++++++- CREDITS.md | 5 ++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index a04141fcf7..aa13a3864d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -367,7 +367,7 @@ "doc", "userTesting" ] - }, + }, { "login": "Pyredrid", "name": "Pyredrid", @@ -432,6 +432,15 @@ "contributions": [ "code" ] + }, + { + "login": "HashtagMarky", + "name": "Marky", + "avatar_url": "https://avatars.githubusercontent.com/u/143505183?v=4", + "profile": "http://hashtagmarky.github.io", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 1cd4611d69..d3d46d0361 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -64,13 +64,16 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Enrico Drago
Enrico Drago

📖 📓 - Pyredrid
Pyredrid

📓 🚧 + Pyredrid
Pyredrid

📓 🚧 mv
mv

💻 🎨 Avara
Avara

🔣 Doesnty
Doesnty

🎨 FosterProgramming
FosterProgramming

💻 Squeetz
Squeetz

🚧 + + ghostyboyy97
ghostyboyy97

💻 + Marky
Marky

💻 From 3bb6cddffb7432b064939de9271444b37d03a6d0 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Tue, 21 Oct 2025 16:39:02 +0200 Subject: [PATCH 35/49] Fix Minior start of battle form (#7972) Co-authored-by: Hedara Co-authored-by: Bassoonian --- src/data/pokemon/form_change_tables.h | 7 ++++++ test/battle/ability/shields_down.c | 31 +++++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index ad8a7e90ca..fd443458c7 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -925,6 +925,7 @@ static const struct FormChange sSilvallyFormChangeTable[] = { #if P_FAMILY_MINIOR static const struct FormChange sMiniorRedFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_RED}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_RED, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_RED, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_RED}, @@ -933,6 +934,7 @@ static const struct FormChange sMiniorRedFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorBlueFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_BLUE}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_BLUE, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_BLUE, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_BLUE}, @@ -941,6 +943,7 @@ static const struct FormChange sMiniorBlueFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorGreenFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_GREEN}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_GREEN, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_GREEN, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_GREEN}, @@ -949,6 +952,7 @@ static const struct FormChange sMiniorGreenFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorIndigoFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_INDIGO}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_INDIGO, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_INDIGO, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_INDIGO}, @@ -957,6 +961,7 @@ static const struct FormChange sMiniorIndigoFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorOrangeFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_ORANGE}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_ORANGE, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_ORANGE, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_ORANGE}, @@ -965,6 +970,7 @@ static const struct FormChange sMiniorOrangeFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorVioletFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_VIOLET}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_VIOLET, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_VIOLET, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_VIOLET}, @@ -973,6 +979,7 @@ static const struct FormChange sMiniorVioletFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorYellowFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_YELLOW}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_YELLOW, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_YELLOW, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_YELLOW}, diff --git a/test/battle/ability/shields_down.c b/test/battle/ability/shields_down.c index 5c46a8d589..554e67b0d8 100644 --- a/test/battle/ability/shields_down.c +++ b/test/battle/ability/shields_down.c @@ -1,30 +1,47 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Minior Meteor transforms into Minior Core on switch-in if it has 1/2 or less health") +SINGLE_BATTLE_TEST("Minior Core doesn't transform into Minior Meteor on switch-in if it has 1/2 or less health") { GIVEN { PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_WOBBUFFET) { HP(1); } - OPPONENT(SPECIES_MINIOR_METEOR) { Ability(ABILITY_SHIELDS_DOWN); HP(1); } + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); HP(50); MaxHP(100); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } + } THEN { + EXPECT_EQ(opponent->species, SPECIES_MINIOR_CORE); + } +} + +SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on switch-in if it has more than 1/2 health") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); HP(51); MaxHP(101); } } WHEN { TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } } SCENE { ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); } THEN { - EXPECT_EQ(opponent->species, SPECIES_MINIOR_CORE); + EXPECT_EQ(opponent->species, SPECIES_MINIOR_METEOR); } } -SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on switch-in if it more then 1/2 health") +SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on battle start if it has more than 1/2 health") { GIVEN { PLAYER(SPECIES_WYNAUT); - OPPONENT(SPECIES_WOBBUFFET) { HP(1); } - OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); } + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); HP(51); MaxHP(101); } } WHEN { - TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + TURN { } } SCENE { ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); From 5276f2bc6b20917880cb4127f6b57155f900245c Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Tue, 21 Oct 2025 15:49:48 -0300 Subject: [PATCH 36/49] Fixed Max Move in-battle descriptions (#8004) --- src/battle_controller_player.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index c4ede0eab4..3361ce2f65 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -1725,6 +1725,14 @@ static void MoveSelectionDisplayMoveDescription(u32 battler) u16 move = moveInfo->moves[gMoveSelectionCursor[battler]]; u16 pwr = GetMovePower(move); u16 acc = GetMoveAccuracy(move); + enum DamageCategory cat = GetBattleMoveCategory(move); + + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX || IsGimmickSelected(battler, GIMMICK_DYNAMAX)) + { + pwr = GetMaxMovePower(move); + move = GetMaxMove(battler, move); + acc = 0; + } u8 pwr_num[3], acc_num[3]; u8 cat_desc[7] = _("CAT: "); @@ -1758,7 +1766,7 @@ static void MoveSelectionDisplayMoveDescription(u32 battler) if (gCategoryIconSpriteId == 0xFF) gCategoryIconSpriteId = CreateSprite(&gSpriteTemplate_CategoryIcons, 38, 64, 1); - StartSpriteAnim(&gSprites[gCategoryIconSpriteId], GetBattleMoveCategory(move)); + StartSpriteAnim(&gSprites[gCategoryIconSpriteId], cat); CopyWindowToVram(B_WIN_MOVE_DESCRIPTION, COPYWIN_FULL); } From 2d7328677747f796442304be697fceb92a6728d5 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Wed, 22 Oct 2025 10:09:58 +0200 Subject: [PATCH 37/49] Fix long pokemon name in partner party not appearing properly (#8009) --- src/party_menu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/party_menu.c b/src/party_menu.c index 3b05551cb2..04bf04a9df 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -273,6 +273,7 @@ static bool8 IsMonAllowedInMinigame(u8); static void DisplayPartyPokemonDataToTeachMove(u8, u16); static u8 CanTeachMove(struct Pokemon *, u16); static void DisplayPartyPokemonBarDetail(u8, const u8 *, u8, const u8 *); +static void DisplayPartyPokemonBarDetailToFit(u8 windowId, const u8 *str, u8 color, const u8 *align, u32 width); static void DisplayPartyPokemonLevel(u8, struct PartyMenuBox *); static void DisplayPartyPokemonGender(u8, u16, u8 *, struct PartyMenuBox *); static void DisplayPartyPokemonHP(u16 hp, u16 maxHp, struct PartyMenuBox *menuBox); @@ -1167,7 +1168,7 @@ static void DisplayPartyPokemonDataForMultiBattle(u8 slot) StringCopy(gStringVar1, gMultiPartnerParty[actualSlot].nickname); StringGet_Nickname(gStringVar1); ConvertInternationalPlayerName(gStringVar1); - DisplayPartyPokemonBarDetail(menuBox->windowId, gStringVar1, 0, menuBox->infoRects->dimensions); + DisplayPartyPokemonBarDetailToFit(menuBox->windowId, gStringVar1, 0, menuBox->infoRects->dimensions, 50); DisplayPartyPokemonLevel(gMultiPartnerParty[actualSlot].level, menuBox); DisplayPartyPokemonGender(gMultiPartnerParty[actualSlot].gender, gMultiPartnerParty[actualSlot].species, gMultiPartnerParty[actualSlot].nickname, menuBox); DisplayPartyPokemonHP(gMultiPartnerParty[actualSlot].hp, gMultiPartnerParty[actualSlot].maxhp, menuBox); From 2416bfb53b594f336943b19f638deebbb27f75ea Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:39:24 +0100 Subject: [PATCH 38/49] Fixes Echoed Voice base power increase depending on attacker's use of the move (#7997) --- include/battle.h | 5 +- src/battle_end_turn.c | 11 ++ src/battle_main.c | 4 +- src/battle_script_commands.c | 8 +- src/battle_util.c | 8 +- test/battle/move_effect/echoed_voice.c | 170 ++++++++++++++++++++++++- 6 files changed, 191 insertions(+), 15 deletions(-) diff --git a/include/battle.h b/include/battle.h index 8fe4fa8cce..ba3752717a 100644 --- a/include/battle.h +++ b/include/battle.h @@ -722,7 +722,7 @@ struct BattleStruct struct Illusion illusion[MAX_BATTLERS_COUNT]; u8 soulheartBattlerId; u8 friskedBattler; // Frisk needs to identify 2 battlers in double battles. - u8 sameMoveTurns[MAX_BATTLERS_COUNT]; // For Metronome, number of times the same moves has been SUCCESFULLY used. + u8 metronomeItemCounter[MAX_BATTLERS_COUNT]; // For Metronome, number of times the same moves has been SUCCESFULLY used. u8 quickClawBattlerId; struct LostItem itemLost[NUM_BATTLE_SIDES][PARTY_SIZE]; // Pokemon that had items consumed or stolen (two bytes per party member per side) u8 blunderPolicy:1; // should blunder policy activate @@ -782,7 +782,8 @@ struct BattleStruct u8 hazardsQueue[NUM_BATTLE_SIDES][HAZARDS_MAX_COUNT]; u8 numHazards[NUM_BATTLE_SIDES]; u8 hazardsCounter:4; // Counter for applying hazard on switch in - u8 padding2:4; + u8 incrementEchoedVoice:1; + u8 echoedVoiceCounter:3; }; struct AiBattleData diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index 73ffa12023..da7607d9c4 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -168,6 +168,17 @@ static bool32 HandleEndTurnVarious(u32 battler) gBattleStruct->hpBefore[i] = gBattleMons[i].hp; } + if (gBattleStruct->incrementEchoedVoice) + { + if (gBattleStruct->echoedVoiceCounter < 4) + gBattleStruct->echoedVoiceCounter++; + gBattleStruct->incrementEchoedVoice = FALSE; + } + else + { + gBattleStruct->echoedVoiceCounter = 0; + } + return effect; } diff --git a/src/battle_main.c b/src/battle_main.c index 26755eda97..ee593da2f1 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3227,7 +3227,7 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy) gLastHitBy[battler] = 0xFF; gBattleStruct->lastTakenMove[battler] = 0; - gBattleStruct->sameMoveTurns[battler] = 0; + gBattleStruct->metronomeItemCounter[battler] = 0; gBattleStruct->lastTakenMoveFrom[battler][0] = 0; gBattleStruct->lastTakenMoveFrom[battler][1] = 0; gBattleStruct->lastTakenMoveFrom[battler][2] = 0; @@ -3348,7 +3348,7 @@ const u8* FaintClearSetData(u32 battler) gLastHitBy[battler] = 0xFF; gBattleStruct->choicedMove[battler] = MOVE_NONE; - gBattleStruct->sameMoveTurns[battler] = 0; + gBattleStruct->metronomeItemCounter[battler] = 0; gBattleStruct->lastTakenMove[battler] = MOVE_NONE; gBattleStruct->lastTakenMoveFrom[battler][0] = 0; gBattleStruct->lastTakenMoveFrom[battler][1] = 0; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d64a2d953f..e5bd66a0ac 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1541,7 +1541,7 @@ static void Cmd_ppreduce(void) // For item Metronome, echoed voice if (gCurrentMove != gLastResultingMoves[gBattlerAttacker] || WasUnableToUseMove(gBattlerAttacker)) - gBattleStruct->sameMoveTurns[gBattlerAttacker] = 0; + gBattleStruct->metronomeItemCounter[gBattlerAttacker] = 0; if (gBattleMons[gBattlerAttacker].pp[gCurrMovePos] > ppToDeduct) gBattleMons[gBattlerAttacker].pp[gCurrMovePos] -= ppToDeduct; @@ -6893,9 +6893,9 @@ static void Cmd_moveend(void) if (gCurrentMove != gLastResultingMoves[gBattlerAttacker] || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT || gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - gBattleStruct->sameMoveTurns[gBattlerAttacker] = 0; + gBattleStruct->metronomeItemCounter[gBattlerAttacker] = 0; else if (gCurrentMove == gLastResultingMoves[gBattlerAttacker] && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT) - gBattleStruct->sameMoveTurns[gBattlerAttacker]++; + gBattleStruct->metronomeItemCounter[gBattlerAttacker]++; gBattleScripting.moveendState++; break; case MOVEEND_CLEAR_BITS: // Clear/Set bits for things like using a move for all targets and all hits. @@ -6949,6 +6949,8 @@ static void Cmd_moveend(void) SetActiveGimmick(gBattlerAttacker, GIMMICK_NONE); if (B_CHARGE >= GEN_9 && moveType == TYPE_ELECTRIC && (IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) gBattleMons[gBattlerAttacker].volatiles.charge = FALSE; + if (moveEffect == EFFECT_ECHOED_VOICE && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)) + gBattleStruct->incrementEchoedVoice = TRUE; // check if Stellar type boost should be used up if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA && GetBattlerTeraType(gBattlerAttacker) == TYPE_STELLAR diff --git a/src/battle_util.c b/src/battle_util.c index b0206f98bd..cd1c435474 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8074,10 +8074,10 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) break; } case EFFECT_ECHOED_VOICE: - // gBattleStruct->sameMoveTurns incremented in ppreduce - if (gBattleStruct->sameMoveTurns[battlerAtk] != 0 && GetMoveEffect(gLastResultingMoves[battlerAtk]) == EFFECT_ECHOED_VOICE) + // gBattleStruct->echoedVoiceCounter incremented in EndTurnVarious called by DoEndTurnEffects + if (gBattleStruct->echoedVoiceCounter != 0) { - basePower += (basePower * gBattleStruct->sameMoveTurns[battlerAtk]); + basePower += (basePower * gBattleStruct->echoedVoiceCounter); if (basePower > 200) basePower = 200; } @@ -9207,7 +9207,7 @@ static inline uq4_12_t GetAttackerItemsModifier(u32 battlerAtk, uq4_12_t typeEff { case HOLD_EFFECT_METRONOME: metronomeBoostBase = PercentToUQ4_12(GetBattlerHoldEffectParam(battlerAtk)); - metronomeTurns = min(gBattleStruct->sameMoveTurns[battlerAtk], 5); + metronomeTurns = min(gBattleStruct->metronomeItemCounter[battlerAtk], 5); // according to bulbapedia this is the "correct" way to calculate the metronome boost // due to the limited domain of damage numbers it will never really matter whether this is off by one return uq4_12_add(UQ_4_12(1.0), metronomeBoostBase * metronomeTurns); diff --git a/test/battle/move_effect/echoed_voice.c b/test/battle/move_effect/echoed_voice.c index 3c36270454..29a2b6e487 100644 --- a/test/battle/move_effect/echoed_voice.c +++ b/test/battle/move_effect/echoed_voice.c @@ -1,7 +1,169 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Echoed Voice's power is multiplied for every consecutive turn used, capped at 5"); -TO_DO_BATTLE_TEST("Echoed Voice's power is reset when using a different move"); -TO_DO_BATTLE_TEST("Echoed Voice's power is increased even if it misses"); -TO_DO_BATTLE_TEST("Echoed Voice's power is increased even if it's blocked by Protect"); +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_ECHOED_VOICE) == EFFECT_ECHOED_VOICE); +} + +SINGLE_BATTLE_TEST("Echoed Voice's power is multiplied for every consecutive turn used, capped at 5") +{ + s16 damage[6]; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SOFT_BOILED) == EFFECT_SOFTBOILED); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BLISSEY); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_SOFT_BOILED); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_SOFT_BOILED); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[3]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[4]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[5]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(3.0), damage[2]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(4.0), damage[3]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(5.0), damage[4]); + EXPECT_EQ(damage[4], damage[5]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power increases even if used by another battler") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[1]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power does not change until the end of the turn") +{ + s16 damage[3]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, opponent); + HP_BAR(player, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[2]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power increase is reset when no battler uses it successfully during a turn") +{ + s16 damage[3]; + + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BITE, MOVE_EFFECT_FLINCH)); + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(opponent, MOVE_BITE); MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponent); + MESSAGE("Wobbuffet flinched and couldn't move!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_EQ(damage[0], damage[2]); + EXPECT_NE(damage[1], damage[2]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power is increased even if it misses") +{ + s16 damage[3]; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_SAND_ATTACK); } + TURN { MOVE(player, MOVE_ECHOED_VOICE, hit: FALSE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + MESSAGE("Wobbuffet's attack missed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(4.0), damage[2]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power is increased even if it's blocked by Protect") +{ + s16 damage[3]; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_PROTECT) == EFFECT_PROTECT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_PROTECT); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(4.0), damage[2]); + } +} From dc0d9101820bc5187db8a577e2529019a6346cb2 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Wed, 22 Oct 2025 10:20:44 -0300 Subject: [PATCH 39/49] Fixed Stomping Tantrum not doubling in damage if the user failed Protect (#8008) --- include/random.h | 1 + src/battle_script_commands.c | 3 ++- test/battle/move_effect/stomping_tantrum.c | 25 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/include/random.h b/include/random.h index df3e1579ad..816cf9b40a 100644 --- a/include/random.h +++ b/include/random.h @@ -217,6 +217,7 @@ enum RandomTag RNG_WRAP, RNG_BALLTHROW_CRITICAL, RNG_BALLTHROW_SHAKE, + RNG_PROTECT_FAIL, }; #define RandomWeighted(tag, ...) \ diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e5bd66a0ac..7cd573f5ae 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9575,7 +9575,7 @@ static void Cmd_setprotectlike(void) if (gCurrentTurnActionNumber == (gBattlersCount - 1)) notLastTurn = FALSE; - if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= Random() && notLastTurn) + if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= RandomUniform(RNG_PROTECT_FAIL, 0, USHRT_MAX) && notLastTurn) || (protectMethod == PROTECT_WIDE_GUARD && B_WIDE_GUARD != GEN_5) || (protectMethod == PROTECT_QUICK_GUARD && B_QUICK_GUARD != GEN_5)) { @@ -9604,6 +9604,7 @@ static void Cmd_setprotectlike(void) gDisableStructs[gBattlerAttacker].protectUses = 0; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECT_FAILED; gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; + gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2; } gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/test/battle/move_effect/stomping_tantrum.c b/test/battle/move_effect/stomping_tantrum.c index 95d93e07e6..06b29334d7 100644 --- a/test/battle/move_effect/stomping_tantrum.c +++ b/test/battle/move_effect/stomping_tantrum.c @@ -90,6 +90,31 @@ SINGLE_BATTLE_TEST("Stomping Tantrum will not deal double damage if target prote } } +SINGLE_BATTLE_TEST("Stomping Tantrum will deal double damage if user failed a Protect") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + TURN { MOVE(player, MOVE_PROTECT); } + TURN { MOVE(player, MOVE_PROTECT, WITH_RNG(RNG_PROTECT_FAIL, USHRT_MAX)); } + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, player); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); + } +} + SINGLE_BATTLE_TEST("Stomping Tantrum will not deal double if it missed") { s16 damage[2]; From 1343ddf7c30f043b9726ebfc464a07bdf8a310a4 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Wed, 22 Oct 2025 22:57:04 +0200 Subject: [PATCH 40/49] Fix badge boost not applying in gen1 and 2 (#8013) --- include/battle_util.h | 1 + include/config/battle.h | 2 +- include/constants/generational_changes.h | 1 + include/generational_changes.h | 1 + src/battle_main.c | 2 +- src/battle_util.c | 20 ++- test/battle/badge_boost.c | 166 +++++++++++++++++++++++ 7 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 test/battle/badge_boost.c diff --git a/include/battle_util.h b/include/battle_util.h index d30caadd47..961ae450f4 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -311,6 +311,7 @@ u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pok bool32 SetIllusionMon(struct Pokemon *mon, u32 battler); u32 TryImmunityAbilityHealStatus(u32 battler, u32 caseID); bool32 ShouldGetStatBadgeBoost(u16 flagId, u32 battler); +uq4_12_t GetBadgeBoostModifier(void); enum DamageCategory GetBattleMoveCategory(u32 move); void SetDynamicMoveCategory(u32 battlerAtk, u32 battlerDef, u32 move); bool32 CanFling(u32 battler); diff --git a/include/config/battle.h b/include/config/battle.h index 61603ba5b8..2b9e9fdecc 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -19,7 +19,7 @@ #define B_LEVEL_UP_NOTIFICATION GEN_LATEST // In Gen9+, if the Pokémon gets enough experience to level up multiple times, the message is only displayed once. // Stat settings -#define B_BADGE_BOOST GEN_LATEST // In Gen4+, Gym Badges no longer boost a Pokémon's stats. +#define B_BADGE_BOOST GEN_LATEST // In Gen4+, Gym Badges no longer boost a Pokémon's stats. (Gen2 does not include the additional boost to the type matching the gym the badge is from) #define B_FRIENDSHIP_BOOST FALSE // In LGPE only, all stats except HP are boosted up to 10% based on Friendship. Unlike B_BADGE_BOOST, these boosts are accounted when calculating base stats. #define B_MAX_LEVEL_EV_GAINS GEN_LATEST // In Gen5+, Lv100 Pokémon can obtain Effort Values normally. #define B_RECALCULATE_STATS GEN_LATEST // In Gen5+, the stats of the Pokémon who participate in battle are recalculated at the end of each battle. diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 7a9aed85c3..7be7998d9e 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -45,6 +45,7 @@ enum GenConfigTag GEN_CONFIG_OBLIVIOUS_TAUNT, GEN_CONFIG_TOXIC_NEVER_MISS, GEN_CONFIG_PARALYZE_ELECTRIC, + GEN_CONFIG_BADGE_BOOST, GEN_CONFIG_COUNT }; diff --git a/include/generational_changes.h b/include/generational_changes.h index 30739a14f5..9b6b385c7e 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -48,6 +48,7 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] = [GEN_CONFIG_OBLIVIOUS_TAUNT] = B_OBLIVIOUS_TAUNT, [GEN_CONFIG_TOXIC_NEVER_MISS] = B_TOXIC_NEVER_MISS, [GEN_CONFIG_PARALYZE_ELECTRIC] = B_PARALYZE_ELECTRIC, + [GEN_CONFIG_BADGE_BOOST] = B_BADGE_BOOST }; #if TESTING diff --git a/src/battle_main.c b/src/battle_main.c index ee593da2f1..6aa4c7212d 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4770,7 +4770,7 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, enum ItemHoldEffect h && ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_SPEED, battler) && IsOnPlayerSide(battler)) { - speed = (speed * 110) / 100; + speed = uq4_12_multiply_by_int_half_down(GetBadgeBoostModifier(), speed); } // item effects diff --git a/src/battle_util.c b/src/battle_util.c index cd1c435474..d5cad634d9 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8766,9 +8766,9 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) // The offensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the corresponding flags set (eg. Badges) if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_ATTACK, battlerAtk) && IsBattleMovePhysical(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier()); if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_SPATK, battlerAtk) && IsBattleMoveSpecial(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier()); return uq4_12_multiply_by_int_half_down(modifier, atkStat); } @@ -8942,11 +8942,11 @@ static inline u32 CalcDefenseStat(struct DamageContext *ctx) if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE) && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SNOW) && usesDefStat) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - // The offensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the corresponding flags set (eg. Badges) + // The defensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the corresponding flags set (eg. Badges) if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_DEFENSE, battlerDef) && IsBattleMovePhysical(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier()); if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_SPDEF, battlerDef) && IsBattleMoveSpecial(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier()); return uq4_12_multiply_by_int_half_down(modifier, defStat); } @@ -10432,9 +10432,17 @@ u32 TryImmunityAbilityHealStatus(u32 battler, u32 caseID) return 0; } +uq4_12_t GetBadgeBoostModifier(void) +{ + if (GetGenConfig(GEN_CONFIG_BADGE_BOOST) < GEN_3) + return UQ_4_12(1.125); + else + return UQ_4_12(1.1); +} + bool32 ShouldGetStatBadgeBoost(u16 badgeFlag, u32 battler) { - if (B_BADGE_BOOST == GEN_3 && badgeFlag != 0) + if (GetGenConfig(GEN_CONFIG_BADGE_BOOST) <= GEN_3 && badgeFlag != 0) { if (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_FRONTIER)) return FALSE; diff --git a/test/battle/badge_boost.c b/test/battle/badge_boost.c new file mode 100644 index 0000000000..eb8cecb240 --- /dev/null +++ b/test/battle/badge_boost.c @@ -0,0 +1,166 @@ +#include "global.h" +#include "event_data.h" +#include "test/battle.h" + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_ATTACK boost Attack", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_ATTACK); + else + FlagClear(B_FLAG_BADGE_BOOST_ATTACK); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].dmg); + } FINALLY { + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + if (gen <= GEN_3) + EXPECT_GT(results[2 * gen + 1].dmg, results[2 * gen].dmg); + else + EXPECT_EQ(results[2 * gen + 1].dmg, results[2 * gen].dmg); + } + } +} + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_SPATK boost Special Attack", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_SPATK); + else + FlagClear(B_FLAG_BADGE_BOOST_SPATK); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_SHOCK); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].dmg); + } FINALLY { + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + if (gen <= GEN_3) + EXPECT_GT(results[2 * gen + 1].dmg, results[2 * gen].dmg); + else + EXPECT_EQ(results[2 * gen + 1].dmg, results[2 * gen].dmg); + } + } +} + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_DEFENSE boost Defense", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_DEFENSE); + else + FlagClear(B_FLAG_BADGE_BOOST_DEFENSE); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].dmg); + } FINALLY { + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + if (gen <= GEN_3) + EXPECT_LT(results[2 * gen + 1].dmg, results[2 * gen].dmg); + else + EXPECT_EQ(results[2 * gen + 1].dmg, results[2 * gen].dmg); + } + } +} + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_SPDEF boost Special Defense", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_SPDEF); + else + FlagClear(B_FLAG_BADGE_BOOST_SPDEF); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_SHOCK); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].dmg); + } FINALLY { + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + if (gen <= GEN_3) + EXPECT_LT(results[2 * gen + 1].dmg, results[2 * gen].dmg); + else + EXPECT_EQ(results[2 * gen + 1].dmg, results[2 * gen].dmg); + } + } +} + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_SPEED boost Speed", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_SPEED); + else + FlagClear(B_FLAG_BADGE_BOOST_SPEED); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) { Speed(100); HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(101); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SCRATCH);} + } THEN { + if (badge && genConfig <= GEN_3) + { + EXPECT_EQ(opponent->hp, 0); + EXPECT_EQ(player->hp, 1); + } + else + { + EXPECT_EQ(opponent->hp, 1); + EXPECT_EQ(player->hp, 0); + } + } +} From 5b4403ddfe9c2a386c55533585e27bd0fc24a300 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Thu, 23 Oct 2025 18:59:15 +0200 Subject: [PATCH 41/49] Fix stats defined in tests being overwritteng by stat change (#8018) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/pokemon.h | 1 + src/pokemon.c | 87 +++++++++++++++--------- test/battle/form_change/mega_evolution.c | 14 ++-- test/battle/form_change/ultra_burst.c | 6 +- test/battle/test_runner_features.c | 55 +++++++++++++++ test/pokemon.c | 18 +++++ test/test_runner_battle.c | 14 ++++ 7 files changed, 154 insertions(+), 41 deletions(-) diff --git a/include/pokemon.h b/include/pokemon.h index f58ebbba1a..296713e59d 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -789,6 +789,7 @@ u32 GetSpeciesBaseDefense(u16 species); u32 GetSpeciesBaseSpAttack(u16 species); u32 GetSpeciesBaseSpDefense(u16 species); u32 GetSpeciesBaseSpeed(u16 species); +u32 GetSpeciesBaseStat(u16 species, u32 statIndex); const struct LevelUpMove *GetSpeciesLevelUpLearnset(u16 species); const u16 *GetSpeciesTeachableLearnset(u16 species); const u16 *GetSpeciesEggMoves(u16 species); diff --git a/src/pokemon.c b/src/pokemon.c index fdc49c7eda..dcf61b99b3 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -1691,32 +1691,10 @@ static u16 CalculateBoxMonChecksumReencrypt(struct BoxPokemon *boxMon) return checksum; } -#define CALC_STAT(base, iv, ev, statIndex, field) \ -{ \ - u8 baseStat = gSpeciesInfo[species].base; \ - s32 n = (((2 * baseStat + iv + ev / 4) * level) / 100) + 5; \ - n = ModifyStatByNature(nature, n, statIndex); \ - if (B_FRIENDSHIP_BOOST == TRUE) \ - n = n + ((n * 10 * friendship) / (MAX_FRIENDSHIP * 100));\ - SetMonData(mon, field, &n); \ -} - void CalculateMonStats(struct Pokemon *mon) { s32 oldMaxHP = GetMonData(mon, MON_DATA_MAX_HP, NULL); s32 currentHP = GetMonData(mon, MON_DATA_HP, NULL); - s32 hpIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_HP) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_HP_IV, NULL); - s32 hpEV = GetMonData(mon, MON_DATA_HP_EV, NULL); - s32 attackIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_ATK) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_ATK_IV, NULL); - s32 attackEV = GetMonData(mon, MON_DATA_ATK_EV, NULL); - s32 defenseIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_DEF) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_DEF_IV, NULL); - s32 defenseEV = GetMonData(mon, MON_DATA_DEF_EV, NULL); - s32 speedIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPEED) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPEED_IV, NULL); - s32 speedEV = GetMonData(mon, MON_DATA_SPEED_EV, NULL); - s32 spAttackIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPATK) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPATK_IV, NULL); - s32 spAttackEV = GetMonData(mon, MON_DATA_SPATK_EV, NULL); - s32 spDefenseIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPDEF) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPDEF_IV, NULL); - s32 spDefenseEV = GetMonData(mon, MON_DATA_SPDEF_EV, NULL); u16 species = GetMonData(mon, MON_DATA_SPECIES, NULL); u8 friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, NULL); s32 level = GetLevelFromMonExp(mon); @@ -1726,28 +1704,55 @@ void CalculateMonStats(struct Pokemon *mon) SetMonData(mon, MON_DATA_LEVEL, &level); + bool32 hyperTrained[NUM_STATS]; //In a battle test, hyper training flag indicates a fixed stat + s32 iv[NUM_STATS]; + s32 ev[NUM_STATS]; + for (u32 i = 0; i < NUM_STATS; i++) + { + hyperTrained[i] = GetMonData(mon, MON_DATA_HYPER_TRAINED_HP + i); + iv[i] = GetMonData(mon, MON_DATA_HP_IV + i); + ev[i] = GetMonData(mon, MON_DATA_HP_EV + i); + + if (hyperTrained[i]) + { + #if TESTING + if (gMain.inBattle) + continue; + #endif + iv[i] = MAX_PER_STAT_IVS; + } + + if (i == STAT_HP) + continue; + + u8 baseStat = GetSpeciesBaseStat(species, i); + s32 n = (((2 * baseStat + iv[i] + ev[i] / 4) * level) / 100) + 5; + n = ModifyStatByNature(nature, n, i); + if (B_FRIENDSHIP_BOOST == TRUE) + n = n + ((n * 10 * friendship) / (MAX_FRIENDSHIP * 100)); + SetMonData(mon, MON_DATA_MAX_HP + i, &n); + } + +#if TESTING + if (hyperTrained[STAT_HP] && gMain.inBattle) + return; +#endif + if (species == SPECIES_SHEDINJA) { newMaxHP = 1; } else { - s32 n = 2 * GetSpeciesBaseHP(species) + hpIV; - newMaxHP = (((n + hpEV / 4) * level) / 100) + level + 10; + s32 n = 2 * GetSpeciesBaseHP(species) + iv[STAT_HP]; + newMaxHP = (((n + ev[STAT_HP] / 4) * level) / 100) + level + 10; } gBattleScripting.levelUpHP = newMaxHP - oldMaxHP; if (gBattleScripting.levelUpHP == 0) gBattleScripting.levelUpHP = 1; - SetMonData(mon, MON_DATA_MAX_HP, &newMaxHP); - CALC_STAT(baseAttack, attackIV, attackEV, STAT_ATK, MON_DATA_ATK) - CALC_STAT(baseDefense, defenseIV, defenseEV, STAT_DEF, MON_DATA_DEF) - CALC_STAT(baseSpeed, speedIV, speedEV, STAT_SPEED, MON_DATA_SPEED) - CALC_STAT(baseSpAttack, spAttackIV, spAttackEV, STAT_SPATK, MON_DATA_SPATK) - CALC_STAT(baseSpDefense, spDefenseIV, spDefenseEV, STAT_SPDEF, MON_DATA_SPDEF) - // Since a pokemon's maxHP data could either not have // been initialized at this point or this pokemon is // just fainted, the check for oldMaxHP is important. @@ -3567,6 +3572,26 @@ u32 GetSpeciesBaseSpeed(u16 species) return gSpeciesInfo[SanitizeSpeciesId(species)].baseSpeed; } +u32 GetSpeciesBaseStat(u16 species, u32 statIndex) +{ + switch (statIndex) + { + case STAT_HP: + return GetSpeciesBaseHP(species); + case STAT_ATK: + return GetSpeciesBaseAttack(species); + case STAT_DEF: + return GetSpeciesBaseDefense(species); + case STAT_SPEED: + return GetSpeciesBaseSpeed(species); + case STAT_SPATK: + return GetSpeciesBaseSpAttack(species); + case STAT_SPDEF: + return GetSpeciesBaseSpDefense(species); + } + return 0; +} + const struct LevelUpMove *GetSpeciesLevelUpLearnset(u16 species) { const struct LevelUpMove *learnset = gSpeciesInfo[SanitizeSpeciesId(species)].levelUpLearnset; diff --git a/test/battle/form_change/mega_evolution.c b/test/battle/form_change/mega_evolution.c index 8cce1a084d..3e27a84608 100644 --- a/test/battle/form_change/mega_evolution.c +++ b/test/battle/form_change/mega_evolution.c @@ -75,10 +75,10 @@ SINGLE_BATTLE_TEST("Mega Evolution doesn't affect turn order (Gen6)") { GIVEN { WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_6); - PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(105); } - OPPONENT(SPECIES_WOBBUFFET) { Speed(106); } + PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); } + OPPONENT(SPECIES_WOBBUFFET) {} } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("The opposing Wobbuffet used Celebrate!"); MESSAGE("Gardevoir used Celebrate!"); @@ -91,10 +91,10 @@ SINGLE_BATTLE_TEST("Mega Evolution affects turn order (Gen7+)") { GIVEN { WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_7); - PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(105); } - OPPONENT(SPECIES_WOBBUFFET) { Speed(106); } + PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE);} + OPPONENT(SPECIES_WOBBUFFET) {} } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Gardevoir used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); @@ -117,7 +117,7 @@ SINGLE_BATTLE_TEST("Abilities replaced by Mega Evolution do not affect turn orde MESSAGE("Sableye used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); } THEN { - ASSUME(player->speed == 45); + ASSUME(player->speed == 105); } } diff --git a/test/battle/form_change/ultra_burst.c b/test/battle/form_change/ultra_burst.c index 9d7ec1c396..4b1bf6a8e3 100644 --- a/test/battle/form_change/ultra_burst.c +++ b/test/battle/form_change/ultra_burst.c @@ -59,10 +59,10 @@ SINGLE_BATTLE_TEST("Ultra Burst affects turn order") { GIVEN { WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_7); - PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(105); } - OPPONENT(SPECIES_WOBBUFFET) { Speed(106); } + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z);} + OPPONENT(SPECIES_WOBBUFFET) {} } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } } SCENE { MESSAGE("Necrozma used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); diff --git a/test/battle/test_runner_features.c b/test/battle/test_runner_features.c index bb4840799f..2c6d73abcc 100644 --- a/test/battle/test_runner_features.c +++ b/test/battle/test_runner_features.c @@ -25,3 +25,58 @@ SINGLE_BATTLE_TEST("Forced abilities activate on switch-in") MESSAGE("Kadabra's Sp. Atk was heightened!"); } } + +SINGLE_BATTLE_TEST("Setting level doesn't overwrite set stats") +{ + u32 level = 0; + + PARAMETRIZE{level = 1;} + PARAMETRIZE{level = 10;} + PARAMETRIZE{level = 50;} + PARAMETRIZE{level = 99;} + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {HP(5); MaxHP(10); Attack(10); Defense(10); Speed(10); SpAttack(10); SpDefense(10); Level(level); }; + OPPONENT(SPECIES_WOBBUFFET) {Speed(1);} + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE);} + } THEN { + EXPECT_EQ(player->hp, 5); + EXPECT_EQ(player->maxHP, 10); + EXPECT_EQ(player->attack, 10); + EXPECT_EQ(player->defense, 10); + EXPECT_EQ(player->speed, 10); + EXPECT_EQ(player->spAttack, 10); + EXPECT_EQ(player->spDefense, 10); + } +} + +SINGLE_BATTLE_TEST("Changing forms doesn't overwrite set stats (not HP)") +{ + GIVEN { + PLAYER(SPECIES_DIANCIE) {Attack(10); Defense(10); Speed(10); SpAttack(10); SpDefense(10); Item(ITEM_DIANCITE);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(1);} + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); + } THEN { + EXPECT_EQ(player->attack, 10); + EXPECT_EQ(player->defense, 10); + EXPECT_EQ(player->speed, 10); + EXPECT_EQ(player->spAttack, 10); + EXPECT_EQ(player->spDefense, 10); + } +} + +SINGLE_BATTLE_TEST("Changing forms doesn't overwrite set stats (HP)") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS) {HP(5); MaxHP(10); TeraType(TYPE_STELLAR);} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_CELEBRATE);} + } THEN { + EXPECT_EQ(player->hp, 5); + EXPECT_EQ(player->maxHP, 10); + } +} diff --git a/test/pokemon.c b/test/pokemon.c index 6079bd28bd..d6c0cb4d42 100644 --- a/test/pokemon.c +++ b/test/pokemon.c @@ -477,6 +477,24 @@ TEST("Optimised SetMonData") EXPECT_FASTER(optimised, vanilla); } +//Sanity check for a CalculateMonStats refactor (could be deleted or improved) +TEST("CalculateMonStats") +{ + 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, shinyMode=SHINY_MODE_ALWAYS, gmaxFactor=TRUE, teraType=TYPE_FIRE, dmaxLevel=7; + ); + + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_MAX_HP), 497); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_ATK), 71); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_DEF), 143); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPEED), 82); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPATK), 83); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPDEF), 134); + +} + TEST("BoxPokemon encryption works") { u32 raw[20] = diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 2f9dd037fb..157cec9018 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -1681,7 +1681,9 @@ void Level_(u32 sourceLine, u32 level) INVALID_IF(level == 0 || level > MAX_LEVEL, "Illegal level: %d", level); SetMonData(DATA.currentMon, MON_DATA_LEVEL, &level); SetMonData(DATA.currentMon, MON_DATA_EXP, &gExperienceTables[gSpeciesInfo[species].growthRate][level]); + gMain.inBattle = TRUE; CalculateMonStats(DATA.currentMon); + gMain.inBattle = FALSE; } void MaxHP_(u32 sourceLine, u32 maxHP) @@ -1689,6 +1691,8 @@ void MaxHP_(u32 sourceLine, u32 maxHP) INVALID_IF(!DATA.currentMon, "MaxHP outside of PLAYER/OPPONENT"); INVALID_IF(maxHP == 0, "Illegal max HP: %d", maxHP); SetMonData(DATA.currentMon, MON_DATA_MAX_HP, &maxHP); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_HP, &hyperTrainingFlag); } void HP_(u32 sourceLine, u32 hp) @@ -1704,6 +1708,8 @@ void Attack_(u32 sourceLine, u32 attack) INVALID_IF(!DATA.currentMon, "Attack outside of PLAYER/OPPONENT"); INVALID_IF(attack == 0, "Illegal attack: %d", attack); SetMonData(DATA.currentMon, MON_DATA_ATK, &attack); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_ATK, &hyperTrainingFlag); } void Defense_(u32 sourceLine, u32 defense) @@ -1711,6 +1717,8 @@ void Defense_(u32 sourceLine, u32 defense) INVALID_IF(!DATA.currentMon, "Defense outside of PLAYER/OPPONENT"); INVALID_IF(defense == 0, "Illegal defense: %d", defense); SetMonData(DATA.currentMon, MON_DATA_DEF, &defense); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_DEF, &hyperTrainingFlag); } void SpAttack_(u32 sourceLine, u32 spAttack) @@ -1718,6 +1726,8 @@ void SpAttack_(u32 sourceLine, u32 spAttack) INVALID_IF(!DATA.currentMon, "SpAttack outside of PLAYER/OPPONENT"); INVALID_IF(spAttack == 0, "Illegal special attack: %d", spAttack); SetMonData(DATA.currentMon, MON_DATA_SPATK, &spAttack); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPATK, &hyperTrainingFlag); } void SpDefense_(u32 sourceLine, u32 spDefense) @@ -1725,6 +1735,8 @@ void SpDefense_(u32 sourceLine, u32 spDefense) INVALID_IF(!DATA.currentMon, "SpDefense outside of PLAYER/OPPONENT"); INVALID_IF(spDefense == 0, "Illegal special defense: %d", spDefense); SetMonData(DATA.currentMon, MON_DATA_SPDEF, &spDefense); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPDEF, &hyperTrainingFlag); } void Speed_(u32 sourceLine, u32 speed) @@ -1732,6 +1744,8 @@ void Speed_(u32 sourceLine, u32 speed) INVALID_IF(!DATA.currentMon, "Speed outside of PLAYER/OPPONENT"); INVALID_IF(speed == 0, "Illegal speed: %d", speed); SetMonData(DATA.currentMon, MON_DATA_SPEED, &speed); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPEED, &hyperTrainingFlag); DATA.hasExplicitSpeeds = TRUE; DATA.explicitSpeeds[DATA.currentSide] |= 1 << DATA.currentPartyIndex; } From 8c875224153a1068db01337f55979db7b1655a9a Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Fri, 24 Oct 2025 11:50:19 +0200 Subject: [PATCH 42/49] Fix toxic debris setting hazards on the wrong side when hit by an ally (#8026) --- data/battle_scripts_1.s | 5 ++--- src/battle_util.c | 5 ++++- test/battle/ability/toxic_debris.c | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index db4f7e65e3..970c16179d 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -6048,9 +6048,8 @@ BattleScript_ToxicDebrisActivates:: printstring STRINGID_POISONSPIKESSCATTERED waitmessage B_WAIT_TIME_LONG BattleScript_ToxicDebrisRet: - copybyte sBATTLER, gBattlerTarget - copybyte gBattlerTarget, gBattlerAttacker - copybyte gBattlerAttacker, sBATTLER + restoretarget + restoreattacker return BattleScript_EarthEaterActivates:: diff --git a/src/battle_util.c b/src/battle_util.c index d5cad634d9..7744cb78e3 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4959,7 +4959,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && IsBattlerTurnDamaged(gBattlerTarget) && (gSideTimers[GetBattlerSide(gBattlerAttacker)].toxicSpikesAmount != 2)) { - SWAP(gBattlerAttacker, gBattlerTarget, i); + SaveBattlerTarget(gBattlerTarget); + SaveBattlerAttacker(gBattlerAttacker); + gBattlerAttacker = gBattlerTarget; + gBattlerTarget = BATTLE_OPPOSITE(gBattlerAttacker); BattleScriptCall(BattleScript_ToxicDebrisActivates); effect++; } diff --git a/test/battle/ability/toxic_debris.c b/test/battle/ability/toxic_debris.c index 61e128e985..4661849905 100644 --- a/test/battle/ability/toxic_debris.c +++ b/test/battle/ability/toxic_debris.c @@ -120,3 +120,23 @@ SINGLE_BATTLE_TEST("Air Balloon is popped after Toxic Debris activates") MESSAGE("Glimmora's Air Balloon popped!"); } } + +DOUBLE_BATTLE_TEST("Toxic Debris sets Toxic Spikes on the opposing side even when hit by an ally") +{ + struct BattlePokemon *user = NULL; + + PARAMETRIZE{ user = opponentLeft; } + PARAMETRIZE{ user = opponentRight; } + PARAMETRIZE{ user = playerRight; } + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_TOXIC_DEBRIS); } + PLAYER(SPECIES_WYNAUT) { } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { } + } WHEN { + TURN { MOVE(user, MOVE_SCRATCH, target: playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } +} From 083add81275c0bcc868abec619cdda975e534a9d Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:50:23 +0200 Subject: [PATCH 43/49] Adds missing alive check for Rapid Spin (#8024) --- src/battle_script_commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 7cd573f5ae..a4e543bfc1 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5865,7 +5865,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) } break; case EFFECT_RAPID_SPIN: - if (IsBattlerTurnDamaged(gBattlerTarget)) + if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) { BattleScriptCall(BattleScript_RapidSpinAway); effect = TRUE; From 8e7688957990a26ef49642c67faa6c36dc9d3ec0 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:50:41 +0200 Subject: [PATCH 44/49] Fixes visual glitch after Misty Explosion (#8022) --- src/battle_script_commands.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index a4e543bfc1..91b91be301 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5839,6 +5839,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) } break; case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: if (!IsAbilityOnField(ABILITY_DAMP)) { gBattleStruct->moveDamage[gBattlerAttacker] = 0; From 8ea947d5a1612e7bcbcdb7e1290ac92f35a42c13 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:53:34 +0200 Subject: [PATCH 45/49] Fixes Protosynthesis not activating after weather was reset (#8021) --- include/battle_util.h | 3 +- src/battle_script_commands.c | 15 +++--- src/battle_util.c | 68 ++++++++++++++-------------- test/battle/ability/protosynthesis.c | 18 ++++++++ 4 files changed, 61 insertions(+), 43 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index 961ae450f4..3531f0eb6b 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -242,7 +242,8 @@ void TryClearRageAndFuryCutter(void); enum MoveCanceller AtkCanceller_MoveSuccessOrder(void); void SetAtkCancellerForCalledMove(void); bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2); -bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbility); +bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability); +bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag); bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u32 abilityDef, u32 move, enum FunctionCallOption option); bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 move, u32 moveType, enum FunctionCallOption option); u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 moveArg); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 91b91be301..dc6ce74ecd 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -3831,7 +3831,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai } break; } - if (TryChangeBattleWeather(gBattlerAttacker, weather, FALSE)) + if (TryChangeBattleWeather(gBattlerAttacker, weather, ABILITY_NONE)) { gBattleCommunication[MULTISTRING_CHOOSER] = msg; BattleScriptPush(gBattlescriptCurrInstr + 1); @@ -9744,7 +9744,7 @@ static void Cmd_setfieldweather(void) u8 battleWeatherId = cmd->weather; - if (!TryChangeBattleWeather(gBattlerAttacker, battleWeatherId, FALSE)) + if (!TryChangeBattleWeather(gBattlerAttacker, battleWeatherId, ABILITY_NONE)) { gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_FAILED; @@ -15338,12 +15338,13 @@ void BS_SetTerrain(void) default: break; } - if (statusFlag) + if (gBattleStruct->isSkyBattle) { - enum ItemHoldEffect atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); - gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; - gFieldStatuses |= statusFlag; - gFieldTimers.terrainTimer = gBattleTurnCounter + ((atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5); + gBattlescriptCurrInstr = cmd->jumpInstr; + } + else if (statusFlag) + { + TryChangeBattleTerrain(gBattlerAttacker, statusFlag); gBattlescriptCurrInstr = cmd->nextInstr; } else diff --git a/src/battle_util.c b/src/battle_util.c index 7744cb78e3..8700fe2b2e 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2791,24 +2791,24 @@ bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2 } } -bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbility) +bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability) { - u16 battlerAbility = GetBattlerAbility(battler); - if (gBattleWeather & sBattleWeatherInfo[battleWeatherId].flag) { return FALSE; } else if (gBattleWeather & B_WEATHER_PRIMAL_ANY - && battlerAbility != ABILITY_DESOLATE_LAND - && battlerAbility != ABILITY_PRIMORDIAL_SEA - && battlerAbility != ABILITY_DELTA_STREAM) + && ability != ABILITY_DESOLATE_LAND + && ability != ABILITY_PRIMORDIAL_SEA + && ability != ABILITY_DELTA_STREAM) { return FALSE; } - else if (GetGenConfig(GEN_CONFIG_ABILITY_WEATHER) < GEN_6 && viaAbility) + else if (GetGenConfig(GEN_CONFIG_ABILITY_WEATHER) < GEN_6 && ability != ABILITY_NONE) { gBattleWeather = sBattleWeatherInfo[battleWeatherId].flag; + for (u32 i = 0; i < gBattlersCount; i++) + gDisableStructs[i].weatherAbilityDone = FALSE; return TRUE; } else @@ -2821,25 +2821,29 @@ bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbilit gWishFutureKnock.weatherDuration = 8; else gWishFutureKnock.weatherDuration = 5; + for (u32 i = 0; i < gBattlersCount; i++) + gDisableStructs[i].weatherAbilityDone = FALSE; return TRUE; } return FALSE; } -static bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag, u16 *timer) +bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag) { - if ((!(gFieldStatuses & statusFlag) && (!gBattleStruct->isSkyBattle))) + if (gBattleStruct->isSkyBattle) + return FALSE; + + if (!(gFieldStatuses & statusFlag)) { gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; gFieldStatuses |= statusFlag; - gDisableStructs[battler].terrainAbilityDone = FALSE; - + for (u32 i = 0; i < gBattlersCount; i++) + gDisableStructs[i].terrainAbilityDone = FALSE; if (GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_TERRAIN_EXTENDER) - *timer = gBattleTurnCounter + 8; + gFieldTimers.terrainTimer = gBattleTurnCounter + 8; else - *timer = gBattleTurnCounter + 5; - + gFieldTimers.terrainTimer = gBattleTurnCounter + 5; gBattleScripting.battler = battler; return TRUE; } @@ -3838,7 +3842,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_DRIZZLE: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_DrizzleActivates); effect++; @@ -3851,7 +3855,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_SAND_STREAM: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_SandstreamActivates); effect++; @@ -3863,8 +3867,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; + case ABILITY_ORICHALCUM_PULSE: case ABILITY_DROUGHT: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates); effect++; @@ -3877,12 +3882,12 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_SNOW_WARNING: - if (GetGenConfig(GEN_SNOW_WARNING) >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, TRUE)) + if (GetGenConfig(GEN_SNOW_WARNING) >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesSnow); effect++; } - else if (GetGenConfig(GEN_SNOW_WARNING) < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, TRUE)) + else if (GetGenConfig(GEN_SNOW_WARNING) < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesHail); effect++; @@ -3896,28 +3901,28 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_ELECTRIC_SURGE: case ABILITY_HADRON_ENGINE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_ELECTRIC_TERRAIN, &gFieldTimers.terrainTimer)) + if (TryChangeBattleTerrain(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) { BattleScriptPushCursorAndCallback(BattleScript_ElectricSurgeActivates); effect++; } break; case ABILITY_GRASSY_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_GRASSY_TERRAIN, &gFieldTimers.terrainTimer)) + if (TryChangeBattleTerrain(battler, STATUS_FIELD_GRASSY_TERRAIN)) { BattleScriptPushCursorAndCallback(BattleScript_GrassySurgeActivates); effect++; } break; case ABILITY_MISTY_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_MISTY_TERRAIN, &gFieldTimers.terrainTimer)) + if (TryChangeBattleTerrain(battler, STATUS_FIELD_MISTY_TERRAIN)) { BattleScriptPushCursorAndCallback(BattleScript_MistySurgeActivates); effect++; } break; case ABILITY_PSYCHIC_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_PSYCHIC_TERRAIN, &gFieldTimers.terrainTimer)) + if (TryChangeBattleTerrain(battler, STATUS_FIELD_PSYCHIC_TERRAIN)) { BattleScriptPushCursorAndCallback(BattleScript_PsychicSurgeActivates); effect++; @@ -4018,21 +4023,21 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_DESOLATE_LAND: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN_PRIMAL, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN_PRIMAL, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_DesolateLandActivates); effect++; } break; case ABILITY_PRIMORDIAL_SEA: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN_PRIMAL, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN_PRIMAL, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_PrimordialSeaActivates); effect++; } break; case ABILITY_DELTA_STREAM: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_STRONG_WINDS, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_STRONG_WINDS, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_DeltaStreamActivates); effect++; @@ -4074,13 +4079,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; - case ABILITY_ORICHALCUM_PULSE: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, TRUE)) - { - BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates); - effect++; - } - break; case ABILITY_SUPREME_OVERLORD: if (!gSpecialStatuses[battler].switchInAbilityDone) { @@ -4851,7 +4849,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 BattleScriptCall(BattleScript_BlockedByPrimalWeatherRet); effect++; } - else if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, TRUE)) + else if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, gLastUsedAbility)) { gBattleScripting.battler = battler; BattleScriptCall(BattleScript_SandSpitActivates); @@ -4909,7 +4907,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerTarget) - && TryChangeBattleTerrain(gBattlerTarget, STATUS_FIELD_GRASSY_TERRAIN, &gFieldTimers.terrainTimer)) + && TryChangeBattleTerrain(gBattlerTarget, STATUS_FIELD_GRASSY_TERRAIN)) { BattleScriptCall(BattleScript_SeedSowerActivates); effect++; diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index 9bf6d85f43..bad29b4c9a 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -200,3 +200,21 @@ SINGLE_BATTLE_TEST("Protosynthesis doesn't activate if Cloud Nine/Air Lock is on NOT ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); } } + +SINGLE_BATTLE_TEST("Protosynthesis activates after weather was reset") +{ + GIVEN { + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + TURN { MOVE(player, MOVE_RAIN_DANCE); } + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAIN_DANCE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } +} From eb1be34d4728f5a5b764758b86c9bda8a14ec8cc Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:53:53 +0200 Subject: [PATCH 46/49] Fix Salt Cure script (#8005) --- data/battle_scripts_1.s | 1 + src/battle_end_turn.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 970c16179d..36c3712854 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -408,6 +408,7 @@ BattleScript_SaltCureExtraDamage:: printstring STRINGID_TARGETISHURTBYSALTCURE waitmessage B_WAIT_TIME_LONG tryfaintmon BS_ATTACKER + tryrestorehpberry end2 BattleScript_HurtTarget_NoString: diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index da7607d9c4..0c7807bece 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -808,9 +808,9 @@ static bool32 HandleEndTurnSaltCure(u32 battler) && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) { if (IS_BATTLER_ANY_TYPE(battler, TYPE_STEEL, TYPE_WATER)) - gBattleStruct->moveDamage[battler] = gBattleMons[battler].maxHP / 4; + gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 4; else - gBattleStruct->moveDamage[battler] = gBattleMons[battler].maxHP / 8; + gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 8; if (gBattleStruct->moveDamage[battler] == 0) gBattleStruct->moveDamage[battler] = 1; PREPARE_MOVE_BUFFER(gBattleTextBuff1, MOVE_SALT_CURE); From b4041535cf78d7bd11e9981cbb665e779af9a050 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Fri, 24 Oct 2025 18:55:32 +0200 Subject: [PATCH 47/49] Fix battle dome bug (again) (#8007) --- data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc b/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc index 8e01660a31..3406d4121c 100644 --- a/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc +++ b/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc @@ -18,7 +18,7 @@ BattleFrontier_BattleDomePreBattleRoom_OnFrame: BattleFrontier_BattleDomePreBattleRoom_EventScript_EnterRoom:: goto_if_eq VAR_0x8006, 1, BattleFrontier_BattleDomePreBattleRoom_EventScript_ReturnFromBattle - delay 0 + delay 1 frontier_set FRONTIER_DATA_RECORD_DISABLED, TRUE setvar VAR_TEMP_0, 1 applymovement LOCALID_PLAYER, BattleFrontier_BattleDomePreBattleRoom_Movement_PlayerEnter From 6eaa09bf5d511810d4f806a620f4a70a55eb7670 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Sat, 25 Oct 2025 14:37:17 +0200 Subject: [PATCH 48/49] Add error messages for trying to send an illegal mon to the PC and fixes index in double wild battles (#7982) Co-authored-by: Hedara --- include/strings.h | 2 ++ src/party_menu.c | 16 +++++++++++++--- src/strings.c | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/include/strings.h b/include/strings.h index a9cddee25b..ec0072f062 100644 --- a/include/strings.h +++ b/include/strings.h @@ -2425,5 +2425,7 @@ extern const u8 gText_Rename[]; // change nickname from summary screen // Switch Caught Mon into Party extern const u8 gText_CannotSendMonToBoxHM[]; +extern const u8 gText_CannotSendMonToBoxActive[]; +extern const u8 gText_CannotSendMonToBoxPartner[]; #endif // GUARD_STRINGS_H diff --git a/src/party_menu.c b/src/party_menu.c index 04bf04a9df..8a9532eaea 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -1512,11 +1512,21 @@ static void HandleChooseMonSelection(u8 taskId, s8 *slotPtr) case PARTY_ACTION_SEND_MON_TO_BOX: { u8 partyId = GetPartyIdFromBattleSlot((u8)*slotPtr); - if (partyId == 0 || ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && partyId == 2) - || ((gBattleTypeFlags & BATTLE_TYPE_MULTI) && partyId >= (PARTY_SIZE / 2))) + if (partyId == 0 || ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && partyId == 1)) { - // Can't select if mon is currently on the field, or doesn't belong to you + // Can't select if mon is currently on the field PlaySE(SE_FAILURE); + DisplayPartyMenuMessage(gText_CannotSendMonToBoxActive, FALSE); + ScheduleBgCopyTilemapToVram(2); + gTasks[taskId].func = Task_ReturnToChooseMonAfterText; + } + else if ((gBattleTypeFlags & BATTLE_TYPE_MULTI) && partyId >= (PARTY_SIZE / 2)) + { + // Can't select if mon doesn't belong to you + PlaySE(SE_FAILURE); + DisplayPartyMenuMessage(gText_CannotSendMonToBoxPartner, FALSE); + ScheduleBgCopyTilemapToVram(2); + gTasks[taskId].func = Task_ReturnToChooseMonAfterText; } else if (DoesSelectedMonKnowHM((u8 *)slotPtr)) { diff --git a/src/strings.c b/src/strings.c index fd92b31e92..1c8f252fcf 100644 --- a/src/strings.c +++ b/src/strings.c @@ -1300,3 +1300,5 @@ const u8 gText_PM[] = _("PM"); const u8 gText_Relearn[] = _("{START_BUTTON} RELEARN"); // future note: don't decap this, because it mimics the summary screen BG graphics which will not get decapped const u8 gText_Rename[] = _("RENAME"); const u8 gText_CannotSendMonToBoxHM[] = _("Cannot send that mon to the box,\nbecause it knows a HM move.{PAUSE_UNTIL_PRESS}"); +const u8 gText_CannotSendMonToBoxActive[] = _("Cannot send an active battler\nto the box.{PAUSE_UNTIL_PRESS}"); +const u8 gText_CannotSendMonToBoxPartner[] = _("Cannot send a mon that doesn't,\nbelong to you to the box.{PAUSE_UNTIL_PRESS}"); From 9fa6cd6a4cd10fb370c489959d7fa2e3317bb755 Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Sat, 25 Oct 2025 14:38:58 +0200 Subject: [PATCH 49/49] Add test to detect save file shifting (#8030) --- test/save.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/save.c diff --git a/test/save.c b/test/save.c new file mode 100644 index 0000000000..4e3000a794 --- /dev/null +++ b/test/save.c @@ -0,0 +1,35 @@ +#include "global.h" +#include "pokemon_storage_system.h" +#include "test/test.h" + +// If you would like to ensure save compatibility, update the values below with those for your hack. You can find these through the debug menu. +// Please note that this simple check is not 100% foolproof, but should be able to catch most unintended shifts. +#define T_SAVEBLOCK1_SIZE 15568 +#define T_SAVEBLOCK2_SIZE 3884 +#define T_SAVEBLOCK3_SIZE 4 +#define T_POKEMONSTORAGE_SIZE 34144 + +TEST("SaveBlock1 is backwards compatible") +{ + EXPECT_EQ(sizeof(struct SaveBlock1), T_SAVEBLOCK1_SIZE); +} + +TEST("SaveBlock2 is backwards compatible") +{ + EXPECT_EQ(sizeof(struct SaveBlock2), T_SAVEBLOCK2_SIZE); +} + +TEST("SaveBlock3 is backwards compatible") +{ + EXPECT_EQ(sizeof(struct SaveBlock3), T_SAVEBLOCK3_SIZE); +} + +TEST("PokemonStorage is backwards compatible") +{ + EXPECT_EQ(sizeof(struct PokemonStorage), T_POKEMONSTORAGE_SIZE); +} + +#undef T_SAVEBLOCK1_SIZE +#undef T_SAVEBLOCK2_SIZE +#undef T_SAVEBLOCK3_SIZE +#undef T_POKEMONSTORAGE_SIZE