From 4610b04b8223a54a2f5d0ff2ef0efa98cf1e1ce8 Mon Sep 17 00:00:00 2001 From: AlexOn1ine Date: Wed, 1 Jan 2025 16:21:14 +0100 Subject: [PATCH 1/4] Shell Bell Clean up / Simplification --- include/battle.h | 12 +-- src/battle_script_commands.c | 29 +----- src/battle_util.c | 19 ++-- test/battle/hold_effect/life_orb.c | 0 test/battle/hold_effect/shell_bell.c | 144 +++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 42 deletions(-) create mode 100644 test/battle/hold_effect/life_orb.c create mode 100644 test/battle/hold_effect/shell_bell.c diff --git a/include/battle.h b/include/battle.h index 0f798ea4819b..26577e95d0c3 100644 --- a/include/battle.h +++ b/include/battle.h @@ -67,9 +67,6 @@ #define BATTLE_BUFFER_LINK_SIZE 0x1000 -// Special indicator value for shellBellDmg in SpecialStatus -#define IGNORE_SHELL_BELL 0xFFFF - // For defining EFFECT_HIT etc. with battle TV scores and flags etc. struct __attribute__((packed, aligned(2))) BattleMoveEffect { @@ -212,7 +209,6 @@ struct ProtectStruct struct SpecialStatus { - s32 shellBellDmg; s32 physicalDmg; s32 specialDmg; u8 physicalBattlerId; @@ -224,7 +220,8 @@ struct SpecialStatus u8 faintedHasReplacement:1; u8 focusBanded:1; u8 focusSashed:1; - u8 unused:2; + u8 emergencyExited:1; + u8 afterYou:1; // End of byte u8 sturdied:1; u8 stormDrainRedirected:1; @@ -243,16 +240,13 @@ struct SpecialStatus u8 neutralizingGasRemoved:1; // See VARIOUS_TRY_END_NEUTRALIZING_GAS u8 affectionEndured:1; // End of byte - u8 damagedMons:4; // Mons that have been damaged directly by using a move, includes substitute. u8 dancerUsedMove:1; u8 dancerOriginalTarget:3; - // End of byte - u8 emergencyExited:1; - u8 afterYou:1; u8 preventLifeOrbDamage:1; // So that Life Orb doesn't activate various effects. u8 distortedTypeMatchups:1; u8 teraShellAbilityDone:1; u8 criticalHit:1; + // End of byte }; struct SideTimer diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 5a24ead2fe40..4cba84396716 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2196,12 +2196,9 @@ static void Cmd_adjustdamage(void) { gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FOE_ENDURED_AFFECTION; } - - END: - if (!(gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT) && gBattleStruct->moveDamage[battlerDef] >= 1) - gSpecialStatuses[gBattlerAttacker].damagedMons |= 1u << battlerDef; } + END: if (calcSpreadMoveDamage) gBattleStruct->calculatedDamageDone = TRUE; gBattlescriptCurrInstr = cmd->nextInstr; @@ -2572,16 +2569,12 @@ static void Cmd_datahpupdate(void) { if (gDisableStructs[battler].substituteHP >= gBattleStruct->moveDamage[battler]) { - if (gSpecialStatuses[battler].shellBellDmg == 0) - gSpecialStatuses[battler].shellBellDmg = gBattleStruct->moveDamage[battler]; gDisableStructs[battler].substituteHP -= gBattleStruct->moveDamage[battler]; gHpDealt = gBattleStruct->moveDamage[battler]; } else { - if (gSpecialStatuses[battler].shellBellDmg == 0) - gSpecialStatuses[battler].shellBellDmg = gDisableStructs[battler].substituteHP; - gHpDealt = gDisableStructs[battler].substituteHP; + gHpDealt = gBattleStruct->moveDamage[battler] = gDisableStructs[battler].substituteHP; gDisableStructs[battler].substituteHP = 0; } // check substitute fading @@ -2643,14 +2636,10 @@ static void Cmd_datahpupdate(void) } else { - gHpDealt = gBattleMons[battler].hp; + gHpDealt = gBattleStruct->moveDamage[battler] = gBattleMons[battler].hp; gBattleMons[battler].hp = 0; } - // Record damage for Shell Bell - if (gSpecialStatuses[battler].shellBellDmg == 0 && !(gHitMarker & HITMARKER_PASSIVE_DAMAGE)) - gSpecialStatuses[battler].shellBellDmg = 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. @@ -2692,13 +2681,7 @@ static void Cmd_datahpupdate(void) MarkBattlerForControllerExec(battler); } } - else - { - // MOVE_RESULT_NO_EFFECT was set - battler = GetBattlerForBattleScript(cmd->battler); - if (gSpecialStatuses[battler].shellBellDmg == 0) - gSpecialStatuses[battler].shellBellDmg = IGNORE_SHELL_BELL; - } + gBattlescriptCurrInstr = cmd->nextInstr; } @@ -5931,7 +5914,7 @@ static void Cmd_moveend(void) switch (gBattleScripting.moveendState) { case MOVEEND_SUM_DAMAGE: // Sum and store damage dealt for multi strike recoil - gBattleScripting.savedDmg += gHpDealt; + gBattleScripting.savedDmg += gBattleStruct->moveDamage[gBattlerTarget];; gBattleScripting.moveendState++; break; case MOVEEND_PROTECT_LIKE_EFFECT: @@ -6979,7 +6962,6 @@ static void Cmd_moveend(void) gBattleStruct->ateBoost[gBattlerAttacker] = FALSE; gStatuses3[gBattlerAttacker] &= ~STATUS3_ME_FIRST; gSpecialStatuses[gBattlerAttacker].gemBoost = FALSE; - gSpecialStatuses[gBattlerAttacker].damagedMons = 0; gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = 0; gSpecialStatuses[gBattlerTarget].berryReduced = FALSE; gSpecialStatuses[gBattlerTarget].distortedTypeMatchups = FALSE; @@ -13260,7 +13242,6 @@ static void Cmd_painsplitdmgcalc(void) gBattleStruct->moveDamage[gBattlerTarget] = GetNonDynamaxHP(gBattlerTarget) - hpDiff; gBattleStruct->moveDamage[gBattlerAttacker] = gBattleMons[gBattlerAttacker].hp - hpDiff; - gSpecialStatuses[gBattlerTarget].shellBellDmg = IGNORE_SHELL_BELL; gBattlescriptCurrInstr = cmd->nextInstr; } else diff --git a/src/battle_util.c b/src/battle_util.c index debd3b198192..3b13092e2195 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2940,7 +2940,6 @@ bool32 HandleWishPerishSongOnTurnEnd(void) gBattlerTarget = battler; gBattlerAttacker = gWishFutureKnock.futureSightBattlerIndex[battler]; - gSpecialStatuses[gBattlerTarget].shellBellDmg = IGNORE_SHELL_BELL; gCurrentMove = gWishFutureKnock.futureSightMove[battler]; party = GetSideParty(GetBattlerSide(gBattlerAttacker)); @@ -5877,7 +5876,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !IsBattlerAlive(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) { - gBattleStruct->moveDamage[gBattlerAttacker] = gSpecialStatuses[gBattlerTarget].shellBellDmg; + gBattleStruct->moveDamage[gBattlerAttacker] = gBattleStruct->moveDamage[gBattlerTarget]; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_AftermathDmg; effect++; @@ -8159,19 +8158,21 @@ u32 ItemBattleEffects(enum ItemEffect caseID, u32 battler, bool32 moveTurn) switch (atkHoldEffect) { case HOLD_EFFECT_SHELL_BELL: - if (gSpecialStatuses[gBattlerAttacker].damagedMons // Need to have done damage + if (gBattleScripting.savedDmg > 0 + && MoveResultHasEffect(battler) && gBattlerAttacker != gBattlerTarget - && gBattleMons[gBattlerAttacker].hp != gBattleMons[gBattlerAttacker].maxHP + && !IsBattlerAtMaxHp(gBattlerAttacker) && IsBattlerAlive(gBattlerAttacker) + && gMovesInfo[gCurrentMove].effect != EFFECT_FUTURE_SIGHT + && gMovesInfo[gCurrentMove].effect != EFFECT_PAIN_SPLIT && (B_HEAL_BLOCKING < GEN_5 || !(gStatuses3[battler] & STATUS3_HEAL_BLOCK))) { gLastUsedItem = atkItem; gPotentialItemEffectBattler = gBattlerAttacker; gBattleScripting.battler = gBattlerAttacker; - gBattleStruct->moveDamage[gBattlerAttacker] = (gSpecialStatuses[gBattlerTarget].shellBellDmg / atkHoldEffectParam) * -1; + gBattleStruct->moveDamage[gBattlerAttacker] = (gBattleScripting.savedDmg / atkHoldEffectParam) * -1; if (gBattleStruct->moveDamage[gBattlerAttacker] == 0) gBattleStruct->moveDamage[gBattlerAttacker] = -1; - gSpecialStatuses[gBattlerTarget].shellBellDmg = 0; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_ItemHealHP_Ret; effect = ITEM_HP_CHANGE; @@ -8179,11 +8180,11 @@ u32 ItemBattleEffects(enum ItemEffect caseID, u32 battler, bool32 moveTurn) break; case HOLD_EFFECT_LIFE_ORB: if (IsBattlerAlive(gBattlerAttacker) + && (IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveDamage[gBattlerTarget]) // Needs the second check in case of Substitute && !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && !gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage - && gSpecialStatuses[gBattlerAttacker].damagedMons) + && !gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage) { gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 10; if (gBattleStruct->moveDamage[gBattlerAttacker] == 0) @@ -8191,7 +8192,7 @@ u32 ItemBattleEffects(enum ItemEffect caseID, u32 battler, bool32 moveTurn) effect = ITEM_HP_CHANGE; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_ItemHurtRet; - gLastUsedItem = gBattleMons[gBattlerAttacker].item; + gLastUsedItem = atkItem; } break; case HOLD_EFFECT_THROAT_SPRAY: // Does NOT need to be a damaging move diff --git a/test/battle/hold_effect/life_orb.c b/test/battle/hold_effect/life_orb.c new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/battle/hold_effect/shell_bell.c b/test/battle/hold_effect/shell_bell.c new file mode 100644 index 000000000000..2f5ebfea48f1 --- /dev/null +++ b/test/battle/hold_effect/shell_bell.c @@ -0,0 +1,144 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_SHELL_BELL].holdEffect == HOLD_EFFECT_SHELL_BELL); +} + +#define HITS 5 +SINGLE_BATTLE_TEST("Shell Bell recovers 1/8 of HP from after the last hit from all hits of a multi hit move") +{ + s16 multiHitDamage[HITS]; + s16 totalDamage = 0; + s16 shellBellRecovery = 0; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BULLET_SEED); } + } SCENE { + for (u32 i = 0; i < HITS; i++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + HP_BAR(opponent, captureDamage: &multiHitDamage[i]); + totalDamage += multiHitDamage[i]; + } + HP_BAR(player, captureDamage: &shellBellRecovery); + } THEN { + EXPECT_EQ(totalDamage / 8, -1 * shellBellRecovery); + } +} +#undef HITS + +SINGLE_BATTLE_TEST("Shell Bell recovers no HP if the move did no damage") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; + } WHEN { + TURN { MOVE(player, MOVE_FALSE_SWIPE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FALSE_SWIPE, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } + + } +} + +SINGLE_BATTLE_TEST("Shell Bell activates if it hits a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + // HP_BAR(opponent); // When you hit a sub the hp bar check doesn't work. Not sure if this is a bug + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Bell activates after Absorb") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Bell activates after Rough Skin") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHELL_BELL); } + OPPONENT(SPECIES_GIBLE) { Ability(ABILITY_ROUGH_SKIN); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + HP_BAR(opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Bell does not activate on Future Sight if the original user is on the field") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN {} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Wynaut took the Future Sight attack!"); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } + } +} + +SINGLE_BATTLE_TEST("Shell Bell does not activate on Future Sight if the original user is not on the field") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); Item(ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { SWITCH(player, 1); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Wynaut took the Future Sight attack!"); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } + } +} + +// HOLD_EFFECT_LIFE_ORB +// !gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage +TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Thief or Covet, it will recover HP for the use of that move that stole the Shell Bell") +TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Magician, it will recover HP for the use of that move that stole the Shell Bell") \ No newline at end of file From d3b52a7f560dce50845c0e4766fa599424190102 Mon Sep 17 00:00:00 2001 From: AlexOn1ine Date: Wed, 1 Jan 2025 17:51:05 +0100 Subject: [PATCH 2/4] remove not needed goto --- src/battle_script_commands.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 4cba84396716..434d1db25788 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2116,12 +2116,12 @@ static void Cmd_adjustdamage(void) continue; if (DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove)) - goto END; + continue; if (DoesDisguiseBlockMove(battlerDef, gCurrentMove)) { gBattleStruct->enduredDamage |= 1u << battlerDef; - goto END; + continue; } if (GetBattlerAbility(battlerDef) == ABILITY_ICE_FACE && IS_MOVE_PHYSICAL(gCurrentMove) && gBattleMons[battlerDef].species == SPECIES_EISCUE) { @@ -2131,10 +2131,10 @@ static void Cmd_adjustdamage(void) RecordAbilityBattle(gBattlerTarget, ABILITY_ICE_FACE); gBattleResources->flags->flags[battlerDef] |= RESOURCE_FLAG_ICE_FACE; // Form change will be done after attack animation in Cmd_resultmessage. - goto END; + continue; } if (gBattleMons[gBattlerTarget].hp > gBattleStruct->moveDamage[battlerDef]) - goto END; + continue; holdEffect = GetBattlerHoldEffect(battlerDef, TRUE); param = GetBattlerHoldEffectParam(battlerDef); @@ -2170,7 +2170,7 @@ static void Cmd_adjustdamage(void) && !gSpecialStatuses[battlerDef].focusSashed && (B_AFFECTION_MECHANICS == FALSE || !gSpecialStatuses[battlerDef].affectionEndured) && !gSpecialStatuses[battlerDef].sturdied) - goto END; + continue; // Handle reducing the dmg to 1 hp. gBattleStruct->moveDamage[battlerDef] = gBattleMons[battlerDef].hp - 1; @@ -2198,7 +2198,6 @@ static void Cmd_adjustdamage(void) } } - END: if (calcSpreadMoveDamage) gBattleStruct->calculatedDamageDone = TRUE; gBattlescriptCurrInstr = cmd->nextInstr; From 285de6a76a268aa017e028fd9ecdcb69e270df72 Mon Sep 17 00:00:00 2001 From: AlexOn1ine Date: Thu, 2 Jan 2025 19:36:23 +0100 Subject: [PATCH 3/4] review comments --- test/battle/hold_effect/shell_bell.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/battle/hold_effect/shell_bell.c b/test/battle/hold_effect/shell_bell.c index 2f5ebfea48f1..8f03ca4a0f4f 100644 --- a/test/battle/hold_effect/shell_bell.c +++ b/test/battle/hold_effect/shell_bell.c @@ -45,7 +45,6 @@ SINGLE_BATTLE_TEST("Shell Bell recovers no HP if the move did no damage") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); HP_BAR(player); } - } } @@ -138,7 +137,5 @@ SINGLE_BATTLE_TEST("Shell Bell does not activate on Future Sight if the original } } -// HOLD_EFFECT_LIFE_ORB -// !gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Thief or Covet, it will recover HP for the use of that move that stole the Shell Bell") -TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Magician, it will recover HP for the use of that move that stole the Shell Bell") \ No newline at end of file +TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Magician, it will recover HP for the use of that move that stole the Shell Bell") From 84140e67e9b472f7950990b6aa20430fad38e028 Mon Sep 17 00:00:00 2001 From: AlexOn1ine Date: Fri, 3 Jan 2025 17:34:46 +0100 Subject: [PATCH 4/4] fix compile --- src/battle_script_commands.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e575fcfaa946..65033dc61f34 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2647,6 +2647,8 @@ static void Cmd_datahpupdate(void) gBattleMons[battler].hp = 0; } + u32 effect = GetMoveEffect(gCurrentMove); + // 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.