Fixes Life Orb still activating if move was absorbed (#7521)

This commit is contained in:
Alex 2025-08-10 16:58:36 +02:00 committed by GitHub
parent 80a3a3b7ef
commit 7de3663f38
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 106 additions and 6 deletions

View File

@ -169,7 +169,8 @@ struct ProtectStruct
u16 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy
u16 usedAllySwitch:1;
u16 lashOutAffected:1;
u16 padding:4;
u16 assuranceDoubled:1;
u16 padding:3;
// End of 16-bit bitfield
u16 physicalDmg;
u16 specialDmg;

View File

@ -2272,6 +2272,9 @@ static void Cmd_adjustdamage(void)
gBattleStruct->moveDamage[battlerDef] = gBattleMons[battlerDef].hp - 1;
gSpecialStatuses[battlerDef].enduredDamage = TRUE;
}
if (gSpecialStatuses[battlerDef].enduredDamage)
gProtectStructs[battlerDef].assuranceDoubled = TRUE;
}
if (calcSpreadMoveDamage)
@ -2726,6 +2729,7 @@ static void Cmd_datahpupdate(void)
gProtectStructs[battler].physicalBattlerId = gBattlerAttacker;
else
gProtectStructs[battler].physicalBattlerId = gBattlerTarget;
gProtectStructs[battler].assuranceDoubled = TRUE;
}
else if (!IsBattleMovePhysical(gCurrentMove) && !(gHitMarker & HITMARKER_PASSIVE_DAMAGE) && effect != EFFECT_PAIN_SPLIT)
{
@ -2736,6 +2740,7 @@ static void Cmd_datahpupdate(void)
gProtectStructs[battler].specialBattlerId = gBattlerAttacker;
else
gProtectStructs[battler].specialBattlerId = gBattlerTarget;
gProtectStructs[battler].assuranceDoubled = TRUE;
}
}
gHitMarker &= ~HITMARKER_PASSIVE_DAMAGE;
@ -3722,7 +3727,7 @@ void SetMoveEffect(bool32 primary, bool32 certain)
if (gBattleMons[gEffectBattler].statStages[i] != DEFAULT_STAT_STAGE)
break;
}
if ((gSpecialStatuses[gEffectBattler].physicalDmg || gSpecialStatuses[gEffectBattler].specialDmg) && i != NUM_BATTLE_STATS)
if (IsBattlerTurnDamaged(gEffectBattler) && i != NUM_BATTLE_STATS)
{
for (i = 0; i < NUM_BATTLE_STATS; i++)
gBattleMons[gEffectBattler].statStages[i] = DEFAULT_STAT_STAGE;

View File

@ -7155,10 +7155,8 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn)
case HOLD_EFFECT_LIFE_ORB:
if (IsBattlerAlive(gBattlerAttacker)
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
&& !IsBattleMoveStatus(gCurrentMove)
&& (IsBattlerTurnDamaged(gBattlerTarget) || !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) // Needs the second check in case of Substitute
&& (IsBattlerTurnDamaged(gBattlerTarget) || gBattleScripting.savedDmg > 0)
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& !IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget, gCurrentMove))
{
gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 10;
@ -8157,7 +8155,7 @@ static inline u32 CalcMoveBasePower(struct DamageCalculationData *damageCalcData
basePower = gBattleMons[battlerDef].hp * basePower / gBattleMons[battlerDef].maxHP;
break;
case EFFECT_ASSURANCE:
if (gProtectStructs[battlerDef].physicalDmg != 0 || gProtectStructs[battlerDef].specialDmg != 0 || gProtectStructs[battlerDef].confusionSelfDmg)
if (gProtectStructs[battlerDef].assuranceDoubled)
basePower *= 2;
break;
case EFFECT_TRUMP_CARD:

View File

@ -81,3 +81,53 @@ SINGLE_BATTLE_TEST("Life Orb doesn't cause any HP loss if user is unable to atta
}
}
}
SINGLE_BATTLE_TEST("Life Orb does not activate if on a confusion hit")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_POUND, WITH_RNG(RNG_CONFUSION, TRUE)); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent);
HP_BAR(player);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player);
HP_BAR(opponent);
HP_BAR(player);
MESSAGE("Wobbuffet was hurt by the Life Orb!");
}
}
}
SINGLE_BATTLE_TEST("Life Orb does not activate if move was absorbed by target")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); }
} WHEN {
TURN { MOVE(player, MOVE_SHOCK_WAVE); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, player);
HP_BAR(opponent);
HP_BAR(player);
MESSAGE("Wobbuffet was hurt by the Life Orb!");
}
}
}
SINGLE_BATTLE_TEST("Life Orb activates if move connected but no damage was dealt")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_WOBBUFFET) { HP(1); }
} WHEN {
TURN { MOVE(player, MOVE_FALSE_SWIPE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FALSE_SWIPE, player);
HP_BAR(player);
MESSAGE("Wobbuffet was hurt by the Life Orb!");
}
}

View File

@ -6,3 +6,30 @@ TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in
TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Crash");
TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Confusion");
TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Rocky Helmet");
DOUBLE_BATTLE_TEST("Assurance doubles in power if False Swipe connected but didn't do any damage")
{
s16 hits[2];
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_ASSURANCE, target: playerRight); }
TURN {
MOVE(opponentLeft, MOVE_FALSE_SWIPE, target: playerLeft);
MOVE(playerLeft, MOVE_RECOVER);
MOVE(opponentRight, MOVE_ASSURANCE, target: playerLeft);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSURANCE, opponentLeft);
HP_BAR(playerRight, captureDamage: &hits[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FALSE_SWIPE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSURANCE, opponentRight);
HP_BAR(playerLeft, captureDamage: &hits[1]);
} THEN {
EXPECT_MUL_EQ(hits[0], Q_4_12(2.0), hits[1]);
}
}

View File

@ -0,0 +1,19 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Clear Smog removes stat changes even if it did no damage")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SWORDS_DANCE); }
TURN { MOVE(player, MOVE_ENDURE); MOVE(opponent, MOVE_CLEAR_SMOG); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CLEAR_SMOG, opponent);
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
}
}