From c6d92eaf8064a434717c4d29e5960695b627359e Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Sun, 5 Jan 2025 11:32:48 -0300 Subject: [PATCH 01/16] Removed references to HANDLE_EXPANDED_MOVE_NAME in docs (#5955) --- docs/tutorials/how_to_new_move.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/how_to_new_move.md b/docs/tutorials/how_to_new_move.md index 9ddc09b904..e3a2eb0a12 100644 --- a/docs/tutorials/how_to_new_move.md +++ b/docs/tutorials/how_to_new_move.md @@ -28,7 +28,7 @@ Let's look at an example: ```c [MOVE_THUNDER_SHOCK] = { - .name = HANDLE_EXPANDED_MOVE_NAME("ThunderShock", "Thunder Shock"), + .name = COMPOUND_STRING("Thunder Shock"), .description = COMPOUND_STRING( "An electrical attack that\n" "may paralyze the foe."), @@ -51,7 +51,7 @@ Let's look at an example: .contestComboMoves = {COMBO_STARTER_CHARGE}, }, ``` -The `HANDLE_EXPANDED_MOVE_NAME` allows the usage of a name of extended character length, so long as the `B_EXPANDED_MOVE_NAMES` is set to `TRUE`, whereas by default it's limited in Gen 3 to 12 characters. Most of the fields here are obvious, but the two important ones for determining what a move actually *does* are `effect` and `additionalEffects`. +Most of the fields here are obvious, but the two important ones for determining what a move actually *does* are `effect` and `additionalEffects`. The `effect` represents how the move actually works when called in battle - it can be a two turn move, or a move that only works if the target is holding an item, for example. How each effect works is pretty much unique, but the way a move of a particular effect is executed is defined by a script [`data/battle_scripts_1.s`](#databattle_scripts_1s), and any *variable* characteristics such as typing or power are defined in either [`src/battle_script_commands.c`](#srcbattle_script_commandsc) or [`src/battle_util.c`](#srcbattle_utilc), depending on the effect. The vast majority of non-status moves are simply `EFFECT_HIT`, in that they deal damage and apply `additionalEffects` (if defined). From cbd0b3ef0cf134d6fa0495cd4e589257e136bea3 Mon Sep 17 00:00:00 2001 From: shachar700 <48739719+shachar700@users.noreply.github.com> Date: Sun, 5 Jan 2025 17:45:21 +0200 Subject: [PATCH 02/16] Field effect for cutting grass is missing setfieldeffectargument. Issue #5766 (#5952) --- data/scripts/field_move_scripts.inc | 12 ++++++++++++ include/event_scripts.h | 1 + src/fldeff_cut.c | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/data/scripts/field_move_scripts.inc b/data/scripts/field_move_scripts.inc index 0a7660b4ce..f1bd3618e1 100644 --- a/data/scripts/field_move_scripts.inc +++ b/data/scripts/field_move_scripts.inc @@ -385,6 +385,18 @@ EventScript_UseDig:: lockall goto EventScript_DigCommon + +EventScript_CutGrassCommon: + isfollowerfieldmoveuser VAR_0x8004 + setfieldeffectargument 3, VAR_0x8004 @ skip pose if true + dofieldeffect FLDEFF_USE_CUT_ON_GRASS + waitstate + +@ Use Cut grass from party menu +EventScript_UseCutGrass:: + lockall + goto EventScript_CutGrassCommon + Text_CantDive: .string "The sea is deep here. A POKéMON\n" .string "may be able to go underwater.$" diff --git a/include/event_scripts.h b/include/event_scripts.h index 60a8d6123e..04bcf32760 100644 --- a/include/event_scripts.h +++ b/include/event_scripts.h @@ -405,6 +405,7 @@ extern const u8 EventScript_UseFlash[]; extern const u8 EventScript_UseCut[]; extern const u8 EventScript_UseRockSmash[]; extern const u8 EventScript_UseDig[]; +extern const u8 EventScript_UseCutGrass[]; //player pc extern const u8 LittlerootTown_BrendansHouse_2F_EventScript_TurnOffPlayerPC[]; diff --git a/src/fldeff_cut.c b/src/fldeff_cut.c index 2304381673..a7eccf651c 100644 --- a/src/fldeff_cut.c +++ b/src/fldeff_cut.c @@ -277,8 +277,8 @@ bool8 SetUpFieldMove_Cut(void) static void FieldCallback_CutGrass(void) { - FieldEffectStart(FLDEFF_USE_CUT_ON_GRASS); gFieldEffectArguments[0] = GetCursorSelectionMonId(); + ScriptContext_SetupScript(EventScript_UseCutGrass); } bool8 FldEff_UseCutOnGrass(void) From adb4a1ff48646b553d106df7793956566950294c Mon Sep 17 00:00:00 2001 From: LOuroboros Date: Mon, 6 Jan 2025 08:03:44 -0300 Subject: [PATCH 03/16] Added the Gen. 4+ berries to gBerryCrush_BerryData (#5937) --- src/berry.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/berry.c b/src/berry.c index a287073df1..4730799e0c 100644 --- a/src/berry.c +++ b/src/berry.c @@ -1654,6 +1654,24 @@ const struct BerryCrushBerryData gBerryCrush_BerryData[] = { [ITEM_WATMEL_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 160, .powder = 250}, [ITEM_DURIN_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 160, .powder = 250}, [ITEM_BELUE_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 160, .powder = 250}, + [ITEM_CHILAN_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 80, .powder = 70}, + [ITEM_OCCA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 100, .powder = 100}, + [ITEM_PASSHO_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 60, .powder = 30}, + [ITEM_WACAN_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_RINDO_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_YACHE_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_CHOPLE_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_KEBIA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_SHUCA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 20}, + [ITEM_COBA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_PAYAPA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_TANGA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_CHARTI_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_KASIB_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_HABAN_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 50, .powder = 30}, + [ITEM_COLBUR_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 60, .powder = 50}, + [ITEM_BABIRI_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 80, .powder = 50}, + [ITEM_ROSELI_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 60, .powder = 50}, [ITEM_LIECHI_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 180, .powder = 500}, [ITEM_GANLON_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 180, .powder = 500}, [ITEM_SALAC_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 180, .powder = 500}, @@ -1661,6 +1679,13 @@ const struct BerryCrushBerryData gBerryCrush_BerryData[] = { [ITEM_APICOT_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 180, .powder = 500}, [ITEM_LANSAT_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 200, .powder = 750}, [ITEM_STARF_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 200, .powder = 750}, + [ITEM_ENIGMA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 150, .powder = 200}, + [ITEM_MICLE_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 130, .powder = 250}, + [ITEM_CUSTAP_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 200, .powder = 750}, + [ITEM_JABOCA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 130, .powder = 250}, + [ITEM_ROWAP_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 130, .powder = 250}, + [ITEM_KEE_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 160, .powder = 500}, + [ITEM_MARANGA_BERRY - FIRST_BERRY_INDEX] = {.difficulty = 160, .powder = 500}, [ITEM_ENIGMA_BERRY_E_READER - FIRST_BERRY_INDEX] = {.difficulty = 150, .powder = 200} }; From 0294beddf1e30d082316312aa13d1b3d638c4096 Mon Sep 17 00:00:00 2001 From: kittenchilly Date: Mon, 6 Jan 2025 11:23:09 -0600 Subject: [PATCH 04/16] Add manual breaking back to "What will (mon) do?" message (#5908) --- src/battle_controller_player.c | 1 - src/battle_controller_safari.c | 1 - src/battle_message.c | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 00e73735e8..982c9e4f27 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2044,7 +2044,6 @@ static void PlayerHandleChooseAction(u32 battler) ActionSelectionCreateCursorAt(gActionSelectionCursor[battler], 0); PREPARE_MON_NICK_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]); BattleStringExpandPlaceholdersToDisplayedString(gText_WhatWillPkmnDo); - BreakStringAutomatic(gDisplayedStringBattle, WindowWidthPx(B_WIN_ACTION_PROMPT), 2, FONT_NORMAL); if (B_SHOW_PARTNER_TARGET && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && IsBattlerAlive(B_POSITION_PLAYER_RIGHT)) { diff --git a/src/battle_controller_safari.c b/src/battle_controller_safari.c index 932ce47fd9..abb2aba946 100644 --- a/src/battle_controller_safari.c +++ b/src/battle_controller_safari.c @@ -299,7 +299,6 @@ static void SafariHandleChooseAction(u32 battler) ActionSelectionCreateCursorAt(gActionSelectionCursor[battler], 0); BattleStringExpandPlaceholdersToDisplayedString(gText_WhatWillPkmnDo2); - BreakStringAutomatic(gDisplayedStringBattle, WindowWidthPx(B_WIN_ACTION_PROMPT), 2, FONT_NORMAL); BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_ACTION_PROMPT); } diff --git a/src/battle_message.c b/src/battle_message.c index 7ad18214e1..32a0c017f3 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -1406,8 +1406,8 @@ const u8 gText_PkmnIsEvolving[] = _("What?\n{STR_VAR_1} is evolving!"); const u8 gText_CongratsPkmnEvolved[] = _("Congratulations! Your {STR_VAR_1}\nevolved into {STR_VAR_2}!{WAIT_SE}\p"); const u8 gText_PkmnStoppedEvolving[] = _("Huh? {STR_VAR_1}\nstopped evolving!\p"); const u8 gText_EllipsisQuestionMark[] = _("……?\p"); -const u8 gText_WhatWillPkmnDo[] = _("What will {B_BUFF1} do?"); -const u8 gText_WhatWillPkmnDo2[] = _("What will {B_PLAYER_NAME} do?"); +const u8 gText_WhatWillPkmnDo[] = _("What will\n{B_BUFF1} do?"); +const u8 gText_WhatWillPkmnDo2[] = _("What will\n{B_PLAYER_NAME} do?"); const u8 gText_WhatWillWallyDo[] = _("What will\nWALLY do?"); const u8 gText_LinkStandby[] = _("{PAUSE 16}Link standby…"); const u8 gText_BattleMenu[] = _("Battle{CLEAR_TO 56}Bag\nPokémon{CLEAR_TO 56}Run"); From 04da838d88b53b8182ef2d7c5dd325f2703a88b2 Mon Sep 17 00:00:00 2001 From: wiz1989 <80073265+wiz1989@users.noreply.github.com> Date: Tue, 7 Jan 2025 14:00:09 +0100 Subject: [PATCH 05/16] Innards Out and Future Sight interaction --- include/battle.h | 1 + include/battle_ai_util.h | 1 + src/battle_ai_util.c | 8 ++++ src/battle_script_commands.c | 6 +++ src/battle_util.c | 24 +++++++++-- test/battle/ability/innards_out.c | 63 ++++++++++++++++++++++++++++ test/battle/hold_effect/shell_bell.c | 35 ++++++++++++++++ 7 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 test/battle/hold_effect/shell_bell.c diff --git a/include/battle.h b/include/battle.h index f0571d747e..27f7be5173 100644 --- a/include/battle.h +++ b/include/battle.h @@ -310,6 +310,7 @@ struct WishFutureKnock u8 wishPartyId[MAX_BATTLERS_COUNT]; u8 weatherDuration; u8 knockedOffMons[NUM_BATTLE_SIDES]; // Each battler is represented by a bit. + u8 futureSightDmg[MAX_BATTLERS_COUNT]; }; struct AI_SavedBattleMon diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index c74cad2e78..b26b608e99 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -137,6 +137,7 @@ bool32 ShouldSetRain(u32 battlerAtk, u32 ability, u32 holdEffect); bool32 ShouldSetSun(u32 battlerAtk, u32 atkAbility, u32 holdEffect); bool32 HasSleepMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef); bool32 IsHealingMove(u32 move); +bool32 IsForeSeenMove(u32 moveId); bool32 HasHealingEffect(u32 battler); bool32 IsTrappingMove(u32 move); bool32 HasTrappingMoveEffect(u32 battler); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index b68112374d..3e841a3d69 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2188,6 +2188,14 @@ bool32 HasHealingEffect(u32 battlerId) return FALSE; } +bool32 IsForeSeenMove(u32 moveId) +{ + if (moveId == MOVE_FUTURE_SIGHT || moveId == MOVE_DOOM_DESIRE) + return TRUE; + else + return FALSE; +} + bool32 IsTrappingMove(u32 move) { switch (gMovesInfo[move].effect) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 2f31b230ae..6f3b85daa5 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2459,6 +2459,11 @@ static void Cmd_datahpupdate(void) if (gSpecialStatuses[battler].shellBellDmg == 0 && !(gHitMarker & HITMARKER_PASSIVE_DAMAGE)) gSpecialStatuses[battler].shellBellDmg = gHpDealt; + // Record damage for foreseen moves + if (gWishFutureKnock.futureSightDmg[battler] == 0 + && IsForeSeenMove(gWishFutureKnock.futureSightMove[battler])) + gWishFutureKnock.futureSightDmg[battler] = gHpDealt; + // Note: While physicalDmg/specialDmg below are only distinguished between for Counter/Mirror Coat, they are // used in combination as general damage trackers for other purposes. specialDmg is additionally used // to help determine if a fire move should defrost the target. @@ -13955,6 +13960,7 @@ static void Cmd_trysetfutureattack(void) gWishFutureKnock.futureSightBattlerIndex[gBattlerTarget] = gBattlerAttacker; gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] = gBattlerPartyIndexes[gBattlerAttacker]; gWishFutureKnock.futureSightCounter[gBattlerTarget] = 3; + gWishFutureKnock.futureSightDmg[gBattlerTarget] = 0; if (gCurrentMove == MOVE_DOOM_DESIRE) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DOOM_DESIRE; diff --git a/src/battle_util.c b/src/battle_util.c index 9245ef21fc..b3fc4042c8 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5803,10 +5803,26 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !IsBattlerAlive(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) { - gBattleMoveDamage = gSpecialStatuses[gBattlerTarget].shellBellDmg; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_AftermathDmg; - effect++; + //special Future Sight handling + if (IsForeSeenMove(gWishFutureKnock.futureSightMove[gBattlerTarget])) + { + //no Innards Out effect if Future Sight user is currently not on field + if (gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] == gBattlerPartyIndexes[gBattlerAttacker] + || gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] == BATTLE_PARTNER(gBattlerPartyIndexes[gBattlerAttacker])) + { + gBattleMoveDamage = gWishFutureKnock.futureSightDmg[gBattlerTarget]; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_AftermathDmg; + effect++; + } + } + else + { + gBattleMoveDamage = gSpecialStatuses[gBattlerTarget].shellBellDmg; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_AftermathDmg; + effect++; + } } break; case ABILITY_EFFECT_SPORE: diff --git a/test/battle/ability/innards_out.c b/test/battle/ability/innards_out.c index 5837b98d1f..e14356b07b 100644 --- a/test/battle/ability/innards_out.c +++ b/test/battle/ability/innards_out.c @@ -65,3 +65,66 @@ SINGLE_BATTLE_TEST("Innards Out does not damage Magic Guard Pokemon") NOT HP_BAR(opponent); } } + +// IO damage for Future Sight must equal the actually lost HP of the Future Sight target +SINGLE_BATTLE_TEST("Innards Out uses correct damage amount for Future Sight") +{ + GIVEN { + PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_INNARDS_OUT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_FUTURE_SIGHT); } + TURN { } + TURN { SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, opponent); + MESSAGE("Pyukumuku took the Future Sight attack!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + HP_BAR(opponent, damage: 1); + } +} + +// IO shouldn't trigger if future sight user is no longer on field +SINGLE_BATTLE_TEST("Innards Out doesn't trigger if Future Sight user is not on field") +{ + GIVEN { + PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_INNARDS_OUT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_FUTURE_SIGHT); } + TURN { SWITCH(opponent, 1); } + TURN { SEND_OUT(player, 1); } //SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, opponent); + MESSAGE("Pyukumuku took the Future Sight attack!"); + HP_BAR(player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + HP_BAR(opponent); + } + } +} + +//IO should trigger if future sight user returns on the field +SINGLE_BATTLE_TEST("Innards Out triggers if Future Sight user is back on the field") +{ + GIVEN { + PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_INNARDS_OUT); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_FUTURE_SIGHT); } + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, opponent); + MESSAGE("Pyukumuku took the Future Sight attack!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + HP_BAR(opponent); + } +} diff --git a/test/battle/hold_effect/shell_bell.c b/test/battle/hold_effect/shell_bell.c new file mode 100644 index 0000000000..dbcb593fa5 --- /dev/null +++ b/test/battle/hold_effect/shell_bell.c @@ -0,0 +1,35 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Shell Bell restores 1/8 HP of damage dealt") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Level(16); Item(ITEM_SHELL_BELL); HP(10); } + OPPONENT(SPECIES_WOBBUFFET) { Level(16); }; + } WHEN { + TURN { MOVE(player, MOVE_SEISMIC_TOSS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, player); + HP_BAR(opponent); + HP_BAR(player, damage: -2); + } +} + +SINGLE_BATTLE_TEST("Shell Bell doesn't restore HP for damage dealt by a foreseen move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Level(16); Item(ITEM_SHELL_BELL); HP(10); } + OPPONENT(SPECIES_WOBBUFFET) { Level(16); }; + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Wobbuffet took the Future Sight attack!"); + HP_BAR(opponent); + NONE_OF { + HP_BAR(player); + } + } +} From 5bfc25a4fff708af2440cd654daf355f43fffd50 Mon Sep 17 00:00:00 2001 From: wiz1989 <80073265+wiz1989@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:33:22 +0100 Subject: [PATCH 06/16] changed data according to suggestions from the PR --- include/battle_ai_util.h | 1 - src/battle_ai_util.c | 8 -------- src/battle_script_commands.c | 2 +- src/battle_util.c | 2 +- test/battle/ability/innards_out.c | 6 +++--- test/battle/hold_effect/shell_bell.c | 1 + 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index b26b608e99..c74cad2e78 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -137,7 +137,6 @@ bool32 ShouldSetRain(u32 battlerAtk, u32 ability, u32 holdEffect); bool32 ShouldSetSun(u32 battlerAtk, u32 atkAbility, u32 holdEffect); bool32 HasSleepMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef); bool32 IsHealingMove(u32 move); -bool32 IsForeSeenMove(u32 moveId); bool32 HasHealingEffect(u32 battler); bool32 IsTrappingMove(u32 move); bool32 HasTrappingMoveEffect(u32 battler); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 3e841a3d69..b68112374d 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2188,14 +2188,6 @@ bool32 HasHealingEffect(u32 battlerId) return FALSE; } -bool32 IsForeSeenMove(u32 moveId) -{ - if (moveId == MOVE_FUTURE_SIGHT || moveId == MOVE_DOOM_DESIRE) - return TRUE; - else - return FALSE; -} - bool32 IsTrappingMove(u32 move) { switch (gMovesInfo[move].effect) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 6f3b85daa5..31ae0f2646 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2461,7 +2461,7 @@ static void Cmd_datahpupdate(void) // Record damage for foreseen moves if (gWishFutureKnock.futureSightDmg[battler] == 0 - && IsForeSeenMove(gWishFutureKnock.futureSightMove[battler])) + && gMovesInfo[gWishFutureKnock.futureSightMove[battler]].effect == EFFECT_FUTURE_SIGHT) gWishFutureKnock.futureSightDmg[battler] = gHpDealt; // Note: While physicalDmg/specialDmg below are only distinguished between for Counter/Mirror Coat, they are diff --git a/src/battle_util.c b/src/battle_util.c index b3fc4042c8..54d6862c48 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5804,7 +5804,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && IsBattlerAlive(gBattlerAttacker)) { //special Future Sight handling - if (IsForeSeenMove(gWishFutureKnock.futureSightMove[gBattlerTarget])) + if (gMovesInfo[gWishFutureKnock.futureSightMove[battler]].effect == EFFECT_FUTURE_SIGHT) { //no Innards Out effect if Future Sight user is currently not on field if (gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] == gBattlerPartyIndexes[gBattlerAttacker] diff --git a/test/battle/ability/innards_out.c b/test/battle/ability/innards_out.c index e14356b07b..e4f8e58ef6 100644 --- a/test/battle/ability/innards_out.c +++ b/test/battle/ability/innards_out.c @@ -66,10 +66,10 @@ SINGLE_BATTLE_TEST("Innards Out does not damage Magic Guard Pokemon") } } -// IO damage for Future Sight must equal the actually lost HP of the Future Sight target SINGLE_BATTLE_TEST("Innards Out uses correct damage amount for Future Sight") { GIVEN { + ASSUME(gMovesInfo[MOVE_FUTURE_SIGHT].effect == EFFECT_FUTURE_SIGHT); PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_INNARDS_OUT); } PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); @@ -86,10 +86,10 @@ SINGLE_BATTLE_TEST("Innards Out uses correct damage amount for Future Sight") } } -// IO shouldn't trigger if future sight user is no longer on field SINGLE_BATTLE_TEST("Innards Out doesn't trigger if Future Sight user is not on field") { GIVEN { + ASSUME(gMovesInfo[MOVE_FUTURE_SIGHT].effect == EFFECT_FUTURE_SIGHT); PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_INNARDS_OUT); } PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -109,10 +109,10 @@ SINGLE_BATTLE_TEST("Innards Out doesn't trigger if Future Sight user is not on f } } -//IO should trigger if future sight user returns on the field SINGLE_BATTLE_TEST("Innards Out triggers if Future Sight user is back on the field") { GIVEN { + ASSUME(gMovesInfo[MOVE_FUTURE_SIGHT].effect == EFFECT_FUTURE_SIGHT); PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_INNARDS_OUT); } OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); diff --git a/test/battle/hold_effect/shell_bell.c b/test/battle/hold_effect/shell_bell.c index dbcb593fa5..9cac6831d9 100644 --- a/test/battle/hold_effect/shell_bell.c +++ b/test/battle/hold_effect/shell_bell.c @@ -18,6 +18,7 @@ SINGLE_BATTLE_TEST("Shell Bell restores 1/8 HP of damage dealt") SINGLE_BATTLE_TEST("Shell Bell doesn't restore HP for damage dealt by a foreseen move") { GIVEN { + ASSUME(gMovesInfo[MOVE_FUTURE_SIGHT].effect == EFFECT_FUTURE_SIGHT); PLAYER(SPECIES_WOBBUFFET) { Level(16); Item(ITEM_SHELL_BELL); HP(10); } OPPONENT(SPECIES_WOBBUFFET) { Level(16); }; } WHEN { From c10287b4c1c14d1857dbeb24b416990dfaf044a3 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Tue, 7 Jan 2025 19:08:31 +0000 Subject: [PATCH 07/16] Fixes various Dynamax HP conversions/calculations (#5933) --- include/battle.h | 1 - include/battle_dynamax.h | 2 +- include/battle_util.h | 2 +- include/pokemon.h | 1 + src/battle_controller_player.c | 40 +++++----- src/battle_dynamax.c | 26 ++++--- src/battle_script_commands.c | 2 +- src/battle_util.c | 25 ++++++- src/pokemon.c | 7 ++ test/battle/gimmick/dynamax.c | 133 ++++++++++++++++++++++++++++++++- 10 files changed, 196 insertions(+), 43 deletions(-) diff --git a/include/battle.h b/include/battle.h index 27f7be5173..99c86181f6 100644 --- a/include/battle.h +++ b/include/battle.h @@ -589,7 +589,6 @@ struct DynamaxData u8 dynamaxTurns[MAX_BATTLERS_COUNT]; u16 baseMoves[MAX_BATTLERS_COUNT]; // base move of Max Move u16 lastUsedBaseMove; - u16 levelUpHP; }; struct BattleGimmickData diff --git a/include/battle_dynamax.h b/include/battle_dynamax.h index 20cd4d5e79..9957c37a10 100644 --- a/include/battle_dynamax.h +++ b/include/battle_dynamax.h @@ -58,7 +58,7 @@ enum MaxMoveEffect bool32 CanDynamax(u32 battler); bool32 IsGigantamaxed(u32 battler); -void ApplyDynamaxHPMultiplier(u32 battler, struct Pokemon* mon); +void ApplyDynamaxHPMultiplier(struct Pokemon* mon); void ActivateDynamax(u32 battler); u16 GetNonDynamaxHP(u32 battler); u16 GetNonDynamaxMaxHP(u32 battler); diff --git a/include/battle_util.h b/include/battle_util.h index 5c151665b9..1eacdaa03d 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -286,7 +286,7 @@ u32 GetBattlerMoveTargetType(u32 battler, u32 move); bool32 CanTargetBattler(u32 battlerAtk, u32 battlerDef, u16 move); void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon); void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon); -void RecalcBattlerStats(u32 battler, struct Pokemon *mon); +void RecalcBattlerStats(u32 battler, struct Pokemon *mon, bool32 isDynamaxing); bool32 IsAlly(u32 battlerAtk, u32 battlerDef); bool32 IsGen6ExpShareEnabled(void); bool32 MoveHasAdditionalEffect(u32 move, u32 moveEffect); diff --git a/include/pokemon.h b/include/pokemon.h index 8aca6c58d5..f5493542ae 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -910,5 +910,6 @@ const u8 *GetMoveAnimationScript(u16 moveId); void UpdateDaysPassedSinceFormChange(u16 days); void TrySetDayLimitToFormChange(struct Pokemon *mon); u32 CheckDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler); +uq4_12_t GetDynamaxLevelHPMultiplier(u32 dynamaxLevel, bool32 inverseMultiplier); #endif // GUARD_POKEMON_H diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 982c9e4f27..059836561c 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -1410,6 +1410,14 @@ void Task_PlayerController_RestoreBgmAfterCry(u8 taskId) #define tExpTask_gainedExp_2 data[4] // Stored as two half-words containing a word. #define tExpTask_frames data[10] +static void DynamaxModifyHPLevelUp(struct Pokemon *mon, u32 battler, u32 oldMaxHP) +{ + ApplyDynamaxHPMultiplier(mon); + gBattleScripting.levelUpHP = GetMonData(mon, MON_DATA_MAX_HP) - oldMaxHP; // overwrite levelUpHP since it overflows + gBattleMons[battler].hp += gBattleScripting.levelUpHP; + SetMonData(mon, MON_DATA_HP, &gBattleMons[battler].hp); +} + static s32 GetTaskExpValue(u8 taskId) { return (u16)(gTasks[taskId].tExpTask_gainedExp_1) | (gTasks[taskId].tExpTask_gainedExp_2 << 16); @@ -1428,21 +1436,16 @@ static void Task_GiveExpToMon(u8 taskId) u8 level = GetMonData(mon, MON_DATA_LEVEL); u32 currExp = GetMonData(mon, MON_DATA_EXP); u32 nextLvlExp = gExperienceTables[gSpeciesInfo[species].growthRate][level + 1]; + u32 oldMaxHP = GetMonData(mon, MON_DATA_MAX_HP); if (currExp + gainedExp >= nextLvlExp) { SetMonData(mon, MON_DATA_EXP, &nextLvlExp); - gBattleStruct->dynamax.levelUpHP = GetMonData(mon, MON_DATA_HP) \ - + UQ_4_12_TO_INT((gBattleScripting.levelUpHP * UQ_4_12(1.5)) + UQ_4_12_ROUND); CalculateMonStats(mon); // Reapply Dynamax HP multiplier after stats are recalculated. if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && monId == gBattlerPartyIndexes[battler]) - { - ApplyDynamaxHPMultiplier(battler, mon); - gBattleMons[battler].hp = gBattleStruct->dynamax.levelUpHP; - SetMonData(mon, MON_DATA_HP, &gBattleMons[battler].hp); - } + DynamaxModifyHPLevelUp(mon, battler, oldMaxHP); gainedExp -= nextLvlExp - currExp; BtlController_EmitTwoReturnValues(battler, BUFFER_B, RET_VALUE_LEVELED_UP, gainedExp); @@ -1491,6 +1494,7 @@ static void Task_GiveExpWithExpBar(u8 taskId) { u8 level; u16 species; + u32 oldMaxHP; s32 currExp, expOnNextLvl, newExpPoints; if (gTasks[taskId].tExpTask_frames < 13) @@ -1502,31 +1506,27 @@ static void Task_GiveExpWithExpBar(u8 taskId) u8 monId = gTasks[taskId].tExpTask_monId; s32 gainedExp = GetTaskExpValue(taskId); u8 battler = gTasks[taskId].tExpTask_battler; + struct Pokemon *mon = &gPlayerParty[monId]; newExpPoints = MoveBattleBar(battler, gHealthboxSpriteIds[battler], EXP_BAR, 0); SetHealthboxSpriteVisible(gHealthboxSpriteIds[battler]); if (newExpPoints == -1) // The bar has been filled with given exp points. { m4aSongNumStop(SE_EXP); - level = GetMonData(&gPlayerParty[monId], MON_DATA_LEVEL); - currExp = GetMonData(&gPlayerParty[monId], MON_DATA_EXP); - species = GetMonData(&gPlayerParty[monId], MON_DATA_SPECIES); + level = GetMonData(mon, MON_DATA_LEVEL); + currExp = GetMonData(mon, MON_DATA_EXP); + species = GetMonData(mon, MON_DATA_SPECIES); + oldMaxHP = GetMonData(mon, MON_DATA_MAX_HP); expOnNextLvl = gExperienceTables[gSpeciesInfo[species].growthRate][level + 1]; if (currExp + gainedExp >= expOnNextLvl) { - SetMonData(&gPlayerParty[monId], MON_DATA_EXP, &expOnNextLvl); - gBattleStruct->dynamax.levelUpHP = GetMonData(&gPlayerParty[monId], MON_DATA_HP) \ - + UQ_4_12_TO_INT((gBattleScripting.levelUpHP * UQ_4_12(1.5)) + UQ_4_12_ROUND); - CalculateMonStats(&gPlayerParty[monId]); + SetMonData(mon, MON_DATA_EXP, &expOnNextLvl); + CalculateMonStats(mon); // Reapply Dynamax HP multiplier after stats are recalculated. if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && monId == gBattlerPartyIndexes[battler]) - { - ApplyDynamaxHPMultiplier(battler, &gPlayerParty[monId]); - gBattleMons[battler].hp = gBattleStruct->dynamax.levelUpHP; - SetMonData(&gPlayerParty[monId], MON_DATA_HP, &gBattleMons[battler].hp); - } + DynamaxModifyHPLevelUp(mon, battler, oldMaxHP); gainedExp -= expOnNextLvl - currExp; BtlController_EmitTwoReturnValues(battler, BUFFER_B, RET_VALUE_LEVELED_UP, gainedExp); @@ -1535,7 +1535,7 @@ static void Task_GiveExpWithExpBar(u8 taskId) else { currExp += gainedExp; - SetMonData(&gPlayerParty[monId], MON_DATA_EXP, &currExp); + SetMonData(mon, MON_DATA_EXP, &currExp); gBattlerControllerFuncs[battler] = Controller_WaitForString; DestroyTask(taskId); } diff --git a/src/battle_dynamax.c b/src/battle_dynamax.c index 17425b51d8..0f55b33dab 100644 --- a/src/battle_dynamax.c +++ b/src/battle_dynamax.c @@ -123,22 +123,22 @@ bool32 CanDynamax(u32 battler) // Returns whether a battler is transformed into a Gigantamax form. bool32 IsGigantamaxed(u32 battler) { - struct Pokemon *mon = &GetSideParty(GetBattlerSide(battler))[gBattlerPartyIndexes[battler]]; + struct Pokemon *mon = GetPartyBattlerData(battler); if ((gSpeciesInfo[gBattleMons[battler].species].isGigantamax) && GetMonData(mon, MON_DATA_GIGANTAMAX_FACTOR)) return TRUE; return FALSE; } // Applies the HP Multiplier for Dynamaxed Pokemon and Raid Bosses. -void ApplyDynamaxHPMultiplier(u32 battler, struct Pokemon* mon) +void ApplyDynamaxHPMultiplier(struct Pokemon* mon) { if (GetMonData(mon, MON_DATA_SPECIES) == SPECIES_SHEDINJA) return; else { - u32 scale = 150 + 5 * GetMonData(mon, MON_DATA_DYNAMAX_LEVEL); - u32 hp = (GetMonData(mon, MON_DATA_HP) * scale + 99) / 100; - u32 maxHP = (GetMonData(mon, MON_DATA_MAX_HP) * scale + 99) / 100; + uq4_12_t multiplier = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), FALSE); + u32 hp = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_HP) * multiplier) + UQ_4_12_ROUND); + u32 maxHP = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_MAX_HP) * multiplier) + UQ_4_12_ROUND); SetMonData(mon, MON_DATA_HP, &hp); SetMonData(mon, MON_DATA_MAX_HP, &maxHP); } @@ -151,8 +151,9 @@ u16 GetNonDynamaxHP(u32 battler) return gBattleMons[battler].hp; else { - u16 mult = UQ_4_12(1.0/1.5); // placeholder - u16 hp = UQ_4_12_TO_INT((gBattleMons[battler].hp * mult) + UQ_4_12_ROUND); + struct Pokemon *mon = GetPartyBattlerData(battler); + uq4_12_t mult = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), TRUE); + u32 hp = UQ_4_12_TO_INT((gBattleMons[battler].hp * mult) + UQ_4_12_ROUND); return hp; } } @@ -164,8 +165,9 @@ u16 GetNonDynamaxMaxHP(u32 battler) return gBattleMons[battler].maxHP; else { - u16 mult = UQ_4_12(1.0/1.5); // placeholder - u16 maxHP = UQ_4_12_TO_INT((gBattleMons[battler].maxHP * mult) + UQ_4_12_ROUND); + struct Pokemon *mon = GetPartyBattlerData(battler); + uq4_12_t mult = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), TRUE); + u32 maxHP = UQ_4_12_TO_INT((gBattleMons[battler].maxHP * mult) + UQ_4_12_ROUND); return maxHP; } } @@ -202,7 +204,7 @@ void UndoDynamax(u32 battler) if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX) { struct Pokemon *mon = (side == B_SIDE_PLAYER) ? &gPlayerParty[monId] : &gEnemyParty[monId]; - u16 mult = UQ_4_12(1.0/1.5); // placeholder + uq4_12_t mult = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), TRUE); gBattleMons[battler].hp = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_HP) * mult + 1) + UQ_4_12_ROUND); // round up SetMonData(mon, MON_DATA_HP, &gBattleMons[battler].hp); CalculateMonStats(mon); @@ -508,10 +510,10 @@ void BS_UpdateDynamax(void) { NATIVE_ARGS(); u32 battler = gBattleScripting.battler; - struct Pokemon *mon = &GetSideParty(GetBattlerSide(battler))[gBattlerPartyIndexes[battler]]; + struct Pokemon *mon = GetPartyBattlerData(battler); if (!IsGigantamaxed(battler)) // RecalcBattlerStats will get called on form change. - RecalcBattlerStats(battler, mon); + RecalcBattlerStats(battler, mon, GetActiveGimmick(battler) == GIMMICK_DYNAMAX); UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL); gBattlescriptCurrInstr = cmd->nextInstr; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 31ae0f2646..dd4e504a1d 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -10157,7 +10157,7 @@ static void Cmd_various(void) // Change stats. else if (cmd->case_ == 1) { - RecalcBattlerStats(battler, mon); + RecalcBattlerStats(battler, mon, FALSE); } // Update healthbox. else diff --git a/src/battle_util.c b/src/battle_util.c index 54d6862c48..c35c072b77 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -11083,7 +11083,7 @@ bool32 TryBattleFormChange(u32 battler, u32 method) TryToSetBattleFormChangeMoves(&party[monId], method); SetMonData(&party[monId], MON_DATA_SPECIES, &targetSpecies); gBattleMons[battler].species = targetSpecies; - RecalcBattlerStats(battler, &party[monId]); + RecalcBattlerStats(battler, &party[monId], method == FORM_CHANGE_BATTLE_GIGANTAMAX); return TRUE; } else if (gBattleStruct->changedSpecies[side][monId] != SPECIES_NONE) @@ -11108,7 +11108,7 @@ bool32 TryBattleFormChange(u32 battler, u32 method) // Reverts the original species TryToSetBattleFormChangeMoves(&party[monId], method); SetMonData(&party[monId], MON_DATA_SPECIES, &gBattleStruct->changedSpecies[side][monId]); - RecalcBattlerStats(battler, &party[monId]); + RecalcBattlerStats(battler, &party[monId], method == FORM_CHANGE_BATTLE_GIGANTAMAX); // Battler data is not updated with regular form's ability, not doing so could cause wrong ability activation. if (method == FORM_CHANGE_FAINT) gBattleMons[battler].ability = abilityForm; @@ -11696,11 +11696,28 @@ void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon) gBattleMons[battler].types[2] = TYPE_MYSTERY; } -void RecalcBattlerStats(u32 battler, struct Pokemon *mon) +void RecalcBattlerStats(u32 battler, struct Pokemon *mon, bool32 isDynamaxing) { + u32 hp = GetMonData(mon, MON_DATA_HP); + u32 oldMaxHp = GetMonData(mon, MON_DATA_MAX_HP); CalculateMonStats(mon); if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && gChosenActionByBattler[battler] != B_ACTION_SWITCH) - ApplyDynamaxHPMultiplier(battler, mon); + { + ApplyDynamaxHPMultiplier(mon); + u32 newMaxHp = GetMonData(mon, MON_DATA_MAX_HP); + if (!isDynamaxing) + { + if (newMaxHp > oldMaxHp) // restore hp gained from changing form, without this, dynamaxed form changes are calculated incorrectly + { + hp += (newMaxHp - oldMaxHp); + SetMonData(mon, MON_DATA_HP, &hp); + } + else + { + SetMonData(mon, MON_DATA_HP, &hp); + } + } + } CopyMonLevelAndBaseStatsToBattleMon(battler, mon); CopyMonAbilityAndTypesToBattleMon(battler, mon); } diff --git a/src/pokemon.c b/src/pokemon.c index c5162ecfd0..d6ed5020b8 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -6978,3 +6978,10 @@ u32 CheckDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler) return moveType; return gMovesInfo[move].type; } + +uq4_12_t GetDynamaxLevelHPMultiplier(u32 dynamaxLevel, bool32 inverseMultiplier) +{ + if (inverseMultiplier) + return UQ_4_12(1.0/(1.5 + 0.05 * dynamaxLevel)); + return UQ_4_12(1.5 + 0.05 * dynamaxLevel); +} diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index f910f551a4..0e3ae532bf 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -7,7 +7,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax increases HP and max HP by 1.5x", u16 hp) u32 dynamax; PARAMETRIZE { dynamax = GIMMICK_NONE; } PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } - GIVEN { // TODO: Dynamax level + GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -25,6 +25,49 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax increases HP and max HP by 1.5x", u16 hp) } } +SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax Level increases HP and max HP multipliers by 0.05 for each level", u16 hp) +{ + u32 dynamax, level; + PARAMETRIZE { dynamax = GIMMICK_NONE; level = 0; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 0; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 1; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 2; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 3; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 4; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 5; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 6; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 7; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 8; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 9; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; level = 10; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { DynamaxLevel(level); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE, gimmick: dynamax); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + if (dynamax) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_DYNAMAX_GROWTH, player); + MESSAGE("Wobbuffet used Max Strike!"); + } + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } THEN { + results[i].hp = player->hp; + } FINALLY { + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.5), results[1].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.55), results[2].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.6), results[3].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.65), results[4].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.7), results[5].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.75), results[6].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.8), results[7].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.85), results[8].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.9), results[9].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(1.95), results[10].hp); + EXPECT_MUL_EQ(results[0].hp, Q_4_12(2.0), results[11].hp); + } +} + SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax expires after three turns", u16 hp) { u32 dynamax; @@ -38,8 +81,8 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax expires after three turns", u16 hp) TURN { MOVE(player, MOVE_TACKLE); } // 2nd max move TURN { MOVE(player, MOVE_TACKLE); } // 3rd max move } SCENE { - int i; - for (i = 0; i < DYNAMAX_TURNS_COUNT; ++i) { + int j; + for (j = 0; j < DYNAMAX_TURNS_COUNT; ++j) { if (dynamax) MESSAGE("Wobbuffet used Max Strike!"); else @@ -55,6 +98,49 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax expires after three turns", u16 hp) } } +SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax expires after three turns and correctly converts HP according to Dynamax Level") +{ + u32 dynamaxLevel, dynamax; + u16 capturedHP, finalHP; + s16 capturedDamage; + PARAMETRIZE { dynamaxLevel = 0; dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamaxLevel = 0; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 1; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 2; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 3; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 4; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 5; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 6; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 7; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 8; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 9; dynamax = GIMMICK_DYNAMAX; } + PARAMETRIZE { dynamaxLevel = 10; dynamax = GIMMICK_DYNAMAX; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { DynamaxLevel(dynamaxLevel); HP(200); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: dynamax); } + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE); } + TURN { } + } SCENE { + if (dynamax) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_DYNAMAX_GROWTH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + if (dynamax) + HP_BAR(player, captureHP: &capturedHP); + else + HP_BAR(player, captureDamage: &capturedDamage); + if (dynamax) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } THEN { + finalHP = player->hp; + if (dynamax) + EXPECT_MUL_EQ(finalHP, GetDynamaxLevelHPMultiplier(dynamaxLevel, FALSE), capturedHP); + EXPECT_LE(finalHP, 200); + EXPECT_GE(finalHP, 200 - capturedDamage); + } +} + SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon cannot be flinched") { GIVEN { @@ -311,6 +397,47 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon lose their substitutes") } } +SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon that changes forms does not gain HP") +{ + u16 capturedHP, finalHP; + GIVEN { + PLAYER(SPECIES_GRENINJA_BATTLE_BOND) { Ability(ABILITY_BATTLE_BOND); HP(100); Speed(100); } + OPPONENT(SPECIES_CATERPIE) { HP(1); Speed(1000); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_DYNAMAX_GROWTH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + HP_BAR(player, captureHP: &capturedHP); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAX_STRIKE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } THEN { + finalHP = player->hp; + EXPECT_EQ(capturedHP, finalHP); + } +} + +SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon that changes forms does not gain HP unless the new form gains Max HP") +{ + u32 hp = 1, maxHP = 200; + u32 species; + PARAMETRIZE { species = SPECIES_ZYGARDE_10_POWER_CONSTRUCT; } + PARAMETRIZE { species = SPECIES_ZYGARDE_50_POWER_CONSTRUCT; } + GIVEN { + PLAYER(species) { Ability(ABILITY_POWER_CONSTRUCT); HP(hp); MaxHP(maxHP); DynamaxLevel(0); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_DYNAMAX_GROWTH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAX_STRIKE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } THEN { + EXPECT_MUL_EQ(maxHP - hp, GetDynamaxLevelHPMultiplier(0, FALSE), player->maxHP - player->hp); + } +} + SINGLE_BATTLE_TEST("(DYNAMAX) Max Moves deal 1/4 damage through protect", s16 damage) { bool32 protected; From 46fc560320e5595095d3219ef0acaf29143c55d0 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:17:14 -0500 Subject: [PATCH 08/16] Update scope.md (again) (#5971) --- docs/scope.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/scope.md b/docs/scope.md index 41369eda29..fe6333090f 100644 --- a/docs/scope.md +++ b/docs/scope.md @@ -42,9 +42,9 @@ A pull request meets the scope criteria if: 4. **Non-SS Items**: Adds Items that have NOT appeared in a Showdown-supported title 5. **Non-SS Gimmicks**: Adds Gimmicks that have NOT appeared in a Showdown-supported title (Showdown's Other Metagames, etc.) 6. **Non-SS Battle Types**: Adds Special Battle Types that have NOT appeared in a Showdown-supported title -7. **Non-SS or SS Overworld Maps**: Adds overworld maps from either Showdown-supported titles or non-Showdown-supported titles -7. **Duplicate Feature UI**: Adds functionality that duplicates the core functionality of an existing vanilla feature -8. **Vanilla Link Compatibility**: The ability for Base Expansion Version and Vanilla Emerald Version to connect, trade, and battle one another +7. **Overworld Maps**: Adds overworld maps from either Showdown-supported titles or non-Showdown-supported titles +8. **Duplicate UIs**: Adds additional user interface that covers the same functionality of an existing feature (HGSS Pokédex, BW Summary Screen, etc.) +9. **Vanilla Link Compatibility**: The ability for Base Expansion Version and Vanilla Emerald Version to connect, trade, and battle one another ## Discussion Required Categories From c3833fa55fcb4fd3ca11aeaede09e6306bf2bbb6 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:24:12 +0100 Subject: [PATCH 09/16] Fixes Parting Shot / Eject Pack interaction (#5963) --- src/battle_script_commands.c | 12 ++++++++---- test/battle/hold_effect/eject_pack.c | 19 +++++++++++++++++++ test/battle/move_effect/hit_escape.c | 20 ++++++++++---------- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index dd4e504a1d..19b095333c 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6466,18 +6466,22 @@ static void Cmd_moveend(void) gLastUsedItem = gBattleMons[battler].item; if (gMovesInfo[gCurrentMove].effect == EFFECT_HIT_ESCAPE) gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection - effect = TRUE; - BattleScriptPushCursor(); - gBattleStruct->usedEjectItem |= 1u << battler; if (ejectButtonBattlers & (1u << battler)) { + effect = TRUE; + gBattleStruct->usedEjectItem |= 1u << battler; + BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_EjectButtonActivates; AI_DATA->ejectButtonSwitch = TRUE; } else // Eject Pack { - if (!(gBattleResources->flags->flags[gBattlerTarget] & RESOURCE_FLAG_EMERGENCY_EXIT)) + if (!(gBattleResources->flags->flags[gBattlerTarget] & RESOURCE_FLAG_EMERGENCY_EXIT) + && !(gMovesInfo[gCurrentMove].effect == EFFECT_PARTING_SHOT && CanBattlerSwitch(gBattlerAttacker))) { + effect = TRUE; + gBattleStruct->usedEjectItem |= 1u << battler; + BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_EjectPackActivates; AI_DATA->ejectPackSwitch = TRUE; gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = TRUE; diff --git a/test/battle/hold_effect/eject_pack.c b/test/battle/hold_effect/eject_pack.c index 0d2696392f..bba874e1b3 100644 --- a/test/battle/hold_effect/eject_pack.c +++ b/test/battle/hold_effect/eject_pack.c @@ -102,3 +102,22 @@ SINGLE_BATTLE_TEST("Eject Pack activates once intimidate mon switches in") MESSAGE("Wobbuffet is switched out with the Eject Pack!"); } } + +SINGLE_BATTLE_TEST("Eject Pack will not activate if Parting Shot user can switch out") +{ + ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + } +} diff --git a/test/battle/move_effect/hit_escape.c b/test/battle/move_effect/hit_escape.c index 0a494cc667..fb69e1b2fe 100644 --- a/test/battle/move_effect/hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -6,7 +6,7 @@ ASSUMPTIONS ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE); } -SINGLE_BATTLE_TEST("U-turn switches the user out") +SINGLE_BATTLE_TEST("Hit Escape: U-turn switches the user out") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -21,7 +21,7 @@ SINGLE_BATTLE_TEST("U-turn switches the user out") } } -SINGLE_BATTLE_TEST("U-turn does not switch the user out if the battle ends") +SINGLE_BATTLE_TEST("Hit Escape: U-turn does not switch the user out if the battle ends") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -35,7 +35,7 @@ SINGLE_BATTLE_TEST("U-turn does not switch the user out if the battle ends") } } -SINGLE_BATTLE_TEST("U-turn does not switch the user out if no replacements") +SINGLE_BATTLE_TEST("Hit Escape: U-turn does not switch the user out if no replacements") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -48,7 +48,7 @@ SINGLE_BATTLE_TEST("U-turn does not switch the user out if no replacements") } } -SINGLE_BATTLE_TEST("U-turn does not switch the user out if replacements fainted") +SINGLE_BATTLE_TEST("Hit Escape: U-turn does not switch the user out if replacements fainted") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -62,7 +62,7 @@ SINGLE_BATTLE_TEST("U-turn does not switch the user out if replacements fainted" } } -SINGLE_BATTLE_TEST("U-turn does not switch the user out if Wimp Out activates") +SINGLE_BATTLE_TEST("Hit Escape: U-turn does not switch the user out if Wimp Out activates") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -79,7 +79,7 @@ SINGLE_BATTLE_TEST("U-turn does not switch the user out if Wimp Out activates") } } -SINGLE_BATTLE_TEST("U-turn switches the user out if Wimp Out fails to activate") +SINGLE_BATTLE_TEST("Hit Escape: U-turn switches the user out if Wimp Out fails to activate") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -95,7 +95,7 @@ SINGLE_BATTLE_TEST("U-turn switches the user out if Wimp Out fails to activate") } } -SINGLE_BATTLE_TEST("U-turn switches the user out after Ice Face activates") +SINGLE_BATTLE_TEST("Hit Escape: U-turn switches the user out after Ice Face activates") { GIVEN { ASSUME(gMovesInfo[MOVE_U_TURN].category == DAMAGE_CATEGORY_PHYSICAL); @@ -113,7 +113,7 @@ SINGLE_BATTLE_TEST("U-turn switches the user out after Ice Face activates") } } -SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: player side") +SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: player side") { GIVEN { PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; @@ -136,7 +136,7 @@ SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in } } -SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: opposing side") +SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: opposing side") { GIVEN { PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; @@ -159,7 +159,7 @@ SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in } } -SINGLE_BATTLE_TEST("Electric Seed boost is received by the right pokemon after U-turn and Intimidate") +SINGLE_BATTLE_TEST("Hit Escape: Electric Seed boost is received by the right pokemon after U-turn and Intimidate") { GIVEN { PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; From b5f539ed21dc5044b779e16054388dce35b77a04 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Wed, 8 Jan 2025 08:00:20 -0500 Subject: [PATCH 10/16] Rename AI_FLAG_PREFER_STRONGEST_MOVE (#5972) --- docs/tutorials/ai_flags.md | 2 +- include/constants/battle_ai.h | 2 +- src/battle_ai_main.c | 8 ++++---- src/battle_debug.c | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/ai_flags.md b/docs/tutorials/ai_flags.md index 7b77305c13..4d0b7ca6aa 100644 --- a/docs/tutorials/ai_flags.md +++ b/docs/tutorials/ai_flags.md @@ -67,7 +67,7 @@ AI will generally behave more recklessly. This AI enables the following behaviou * Switch offensively mid battle rather than defensively (if using `AI_FLAG_SMART_MON_CHOICES`) * Prioritize Explosion moves -## `AI_FLAG_PREFER_STRONGEST_MOVE` +## `AI_FLAG_TRY_TO_2HKO` Adds score bonus to any move the AI has that either OHKOs or 2HKOs the player. Keep in mind that this is a weaker form of `AI_FLAG_TRY_TO_FAINT` at scoring OHKOs as it does not take into account who is attacking first, it does however handle 2HKOs. diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index df6069ead9..5057a04152 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -31,7 +31,7 @@ #define AI_FLAG_CHECK_VIABILITY (1 << 2) // AI damaging moves and move effects to determine the best available move in the current situation. #define AI_FLAG_FORCE_SETUP_FIRST_TURN (1 << 3) // AI will prioritize using setup moves on the first turn at the expensve of all else. AI_FLAG_CHECK_VIABILITY will instead do this when the AI determines it makes sense. #define AI_FLAG_RISKY (1 << 4) // AI will generally behave more recklessly, prioritizing damage over accuracy, explosions, etc. -#define AI_FLAG_PREFER_STRONGEST_MOVE (1 << 5) // AI adds score bonus to any move the AI has that either OHKOs or 2HKOs the player. +#define AI_FLAG_TRY_TO_2HKO (1 << 5) // AI adds score bonus to any move the AI has that either OHKOs or 2HKOs the player. #define AI_FLAG_PREFER_BATON_PASS (1 << 6) // AI prefers raising its own stats and setting for / using Baton Pass. #define AI_FLAG_DOUBLE_BATTLE (1 << 7) // Automatically set for double battles, handles AI behaviour with partner. #define AI_FLAG_HP_AWARE (1 << 8) // AI will favour certain move effects based on how much remaining HP it and the player's mon have. diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 538b0e4732..ea8a4bb91f 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -48,7 +48,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); -static s32 AI_PreferStrongestMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); +static s32 AI_TryTo2HKO(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_Roaming(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); @@ -66,7 +66,7 @@ static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) = [2] = AI_CheckViability, // AI_FLAG_CHECK_VIABILITY [3] = AI_ForceSetupFirstTurn, // AI_FLAG_FORCE_SETUP_FIRST_TURN [4] = AI_Risky, // AI_FLAG_RISKY - [5] = AI_PreferStrongestMove, // AI_FLAG_PREFER_STRONGEST_MOVE + [5] = AI_TryTo2HKO, // AI_FLAG_TRY_TO_2HKO [6] = AI_PreferBatonPass, // AI_FLAG_PREFER_BATON_PASS [7] = AI_DoubleBattle, // AI_FLAG_DOUBLE_BATTLE [8] = AI_HPAware, // AI_FLAG_HP_AWARE @@ -136,7 +136,7 @@ static u32 GetWildAiFlags(void) if (avgLevel >= 20) flags |= AI_FLAG_CHECK_VIABILITY; if (avgLevel >= 60) - flags |= AI_FLAG_PREFER_STRONGEST_MOVE; + flags |= AI_FLAG_TRY_TO_2HKO; if (avgLevel >= 80) flags |= AI_FLAG_HP_AWARE; @@ -4897,7 +4897,7 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } // Adds score bonus to best powered move -static s32 AI_PreferStrongestMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) +static s32 AI_TryTo2HKO(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) return score; diff --git a/src/battle_debug.c b/src/battle_debug.c index b03ef194f0..c4fef859f4 100644 --- a/src/battle_debug.c +++ b/src/battle_debug.c @@ -220,7 +220,7 @@ enum LIST_AI_CHECK_VIABILITY, LIST_AI_SETUP_FIRST_TURN, LIST_AI_RISKY, - LIST_AI_PREFER_STRONGEST_MOVE, + LIST_AI_TRY_TO_2HKO, LIST_AI_PREFER_BATON_PASS, LIST_AI_DOUBLE_BATTLE, LIST_AI_HP_AWARE, @@ -385,7 +385,7 @@ static const u8 sText_TryToFaint[] = _("Try to Faint"); static const u8 sText_CheckViability[] = _("Check Viability"); static const u8 sText_SetUpFirstTurn[] = _("Setup First Turn"); static const u8 sText_Risky[] = _("Risky"); -static const u8 sText_PreferStrongestMove[] = _("Prefer Strongest Move"); +static const u8 sText_TryTo2HKO[] = _("Try to 2HKO"); static const u8 sText_PreferBatonPass[] = _("Prefer Baton Pass"); static const u8 sText_DoubleBattle[] = _("Double Battle"); static const u8 sText_HpAware[] = _("HP Aware"); @@ -628,7 +628,7 @@ static const struct ListMenuItem sAIListItems[] = {sText_CheckViability, LIST_AI_CHECK_VIABILITY}, {sText_SetUpFirstTurn, LIST_AI_SETUP_FIRST_TURN}, {sText_Risky, LIST_AI_RISKY}, - {sText_PreferStrongestMove, LIST_AI_PREFER_STRONGEST_MOVE}, + {sText_TryTo2HKO, LIST_AI_TRY_TO_2HKO}, {sText_PreferBatonPass, LIST_AI_PREFER_BATON_PASS}, {sText_DoubleBattle, LIST_AI_DOUBLE_BATTLE}, {sText_HpAware, LIST_AI_HP_AWARE}, From e264fa6fc087674266c890535caae5d097aca45e Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 9 Jan 2025 01:14:38 +0100 Subject: [PATCH 11/16] Removed OW_AUTO_SIGNPOST (#5978) --- include/config/overworld.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/config/overworld.h b/include/config/overworld.h index 464024025b..be56ccb26f 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -3,7 +3,6 @@ // Movement config #define OW_RUNNING_INDOORS GEN_LATEST // In Gen4+, players are allowed to run indoors. -#define OW_AUTO_SIGNPOST FALSE // When enabled, if the tile that the player is facing has MB_SIGNPOST, MB_POKEMART_SIGN, or MB_POKEMON_CENTER_SIGN, the player will automatically read the signpost, as seen in FRLG. #define SLOW_MOVEMENT_ON_STAIRS FALSE // If enabled, the player will move slower up/down stairs like in FR // Other settings From e244d942cce5018a14f3b2910cb5689432cf2666 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Thu, 9 Jan 2025 14:01:00 +0100 Subject: [PATCH 12/16] CreateFacilityMon error: 'personality' may be used uninitialized (#5981) --- src/battle_tower.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_tower.c b/src/battle_tower.c index 6f80823b98..3f614cf294 100644 --- a/src/battle_tower.c +++ b/src/battle_tower.c @@ -1567,7 +1567,7 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 { u8 ball = (fmon->ball == 0xFF) ? Random() % POKEBALL_COUNT : fmon->ball; u16 move; - u32 personality, ability, friendship, j; + u32 personality = 0, ability, friendship, j; if (fmon->gender == TRAINER_MON_MALE) { From 25d678f1e7603fbd5c0156b68f0419555f90c8b3 Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Fri, 10 Jan 2025 10:53:27 +0100 Subject: [PATCH 13/16] PlayerHandleStatusXor - change u8 to u32 (#5988) --- src/battle_controller_player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 059836561c..e56207507b 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2236,7 +2236,7 @@ void PlayerHandleExpUpdate(u32 battler) static void PlayerHandleStatusXor(u32 battler) { - u8 val = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battler]], MON_DATA_STATUS) ^ gBattleResources->bufferA[battler][1]; + u32 val = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battler]], MON_DATA_STATUS) ^ gBattleResources->bufferA[battler][1]; SetMonData(&gPlayerParty[gBattlerPartyIndexes[battler]], MON_DATA_STATUS, &val); PlayerBufferExecCompleted(battler); From 75a2d419e11e462fefbc1c048485828ece4fde0c Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:59:37 +0100 Subject: [PATCH 14/16] Follo up for Innards Out / Future Sight interaction (#5967) --- include/battle.h | 2 +- src/battle_script_commands.c | 8 +++----- src/battle_util.c | 5 +++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/battle.h b/include/battle.h index 99c86181f6..c762e7f766 100644 --- a/include/battle.h +++ b/include/battle.h @@ -310,7 +310,7 @@ struct WishFutureKnock u8 wishPartyId[MAX_BATTLERS_COUNT]; u8 weatherDuration; u8 knockedOffMons[NUM_BATTLE_SIDES]; // Each battler is represented by a bit. - u8 futureSightDmg[MAX_BATTLERS_COUNT]; + s16 futureSightDmg; }; struct AI_SavedBattleMon diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 19b095333c..81891831f4 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2460,10 +2460,8 @@ static void Cmd_datahpupdate(void) gSpecialStatuses[battler].shellBellDmg = gHpDealt; // Record damage for foreseen moves - if (gWishFutureKnock.futureSightDmg[battler] == 0 - && gMovesInfo[gWishFutureKnock.futureSightMove[battler]].effect == EFFECT_FUTURE_SIGHT) - gWishFutureKnock.futureSightDmg[battler] = gHpDealt; - + gWishFutureKnock.futureSightDmg = gHpDealt; + // Note: While physicalDmg/specialDmg below are only distinguished between for Counter/Mirror Coat, they are // used in combination as general damage trackers for other purposes. specialDmg is additionally used // to help determine if a fire move should defrost the target. @@ -13964,7 +13962,7 @@ static void Cmd_trysetfutureattack(void) gWishFutureKnock.futureSightBattlerIndex[gBattlerTarget] = gBattlerAttacker; gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] = gBattlerPartyIndexes[gBattlerAttacker]; gWishFutureKnock.futureSightCounter[gBattlerTarget] = 3; - gWishFutureKnock.futureSightDmg[gBattlerTarget] = 0; + gWishFutureKnock.futureSightDmg = 0; if (gCurrentMove == MOVE_DOOM_DESIRE) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DOOM_DESIRE; diff --git a/src/battle_util.c b/src/battle_util.c index c35c072b77..8d87bcccf5 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5808,9 +5808,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 { //no Innards Out effect if Future Sight user is currently not on field if (gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] == gBattlerPartyIndexes[gBattlerAttacker] - || gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] == BATTLE_PARTNER(gBattlerPartyIndexes[gBattlerAttacker])) + || gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] == BATTLE_PARTNER(gBattlerPartyIndexes[gBattlerAttacker])) { - gBattleMoveDamage = gWishFutureKnock.futureSightDmg[gBattlerTarget]; + gBattleMoveDamage = gWishFutureKnock.futureSightDmg; + gWishFutureKnock.futureSightDmg = 0; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_AftermathDmg; effect++; From 3c7708fae4327edac08e44b74f380ccd63fd2cbb Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Fri, 10 Jan 2025 17:00:30 +0100 Subject: [PATCH 15/16] Remove out of bounds gSprites access in move relearner (#5991) --- src/move_relearner.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/move_relearner.c b/src/move_relearner.c index 1e66702695..7930ba15b6 100644 --- a/src/move_relearner.c +++ b/src/move_relearner.c @@ -1007,7 +1007,6 @@ void MoveRelearnerShowHideCategoryIcon(s32 moveId) DestroySprite(&gSprites[sMoveRelearnerStruct->categoryIconSpriteId]); sMoveRelearnerStruct->categoryIconSpriteId = 0xFF; - gSprites[sMoveRelearnerStruct->categoryIconSpriteId].invisible = TRUE; } else { From 71cf1bfaa7f6fe43c445c730f62b82496f4cf9b7 Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Fri, 10 Jan 2025 23:25:04 -0800 Subject: [PATCH 16/16] Updated Makefile to allow for the creation of specific tests (#5993) --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 656b0847dd..0d2ef1d240 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,9 @@ endif ifeq (debug,$(MAKECMDGOALS)) DEBUG := 1 endif +ifeq ($(TESTELF),$(MAKECMDGOALS)) + TEST := 1 +endif # Default make rule all: rom