Strength Sap fixes (#9130)

This commit is contained in:
Alex 2026-02-06 15:11:23 +01:00 committed by GitHub
parent f102d5d556
commit 63fc5bf0fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 173 additions and 91 deletions

View File

@ -914,50 +914,21 @@ BattleScript_EffectStrengthSap::
attackcanceler
jumpifsubstituteblocks BattleScript_ButItFailed
accuracycheck BattleScript_MoveMissedPause
jumpifstat BS_TARGET, CMP_NOT_EQUAL, STAT_ATK, MIN_STAT_STAGE, BattleScript_StrengthSapTryLower
jumpifstatignorecontrary BS_TARGET, CMP_EQUAL, STAT_ATK, MIN_STAT_STAGE, BattleScript_StrengthSapPrintStatMessage
attackanimation
waitanimation
getstatvalue STAT_ATK
statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_MoveEnd
jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_CHANGE_EMPTY, BattleScript_MoveEnd
printfromtable gStatDownStringIds
waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd
BattleScript_StrengthSapPrintStatMessage:
pause B_WAIT_TIME_SHORT
statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_MoveEnd
printfromtable gStatDownStringIds
waitmessage B_WAIT_TIME_LONG
setmoveresultflags MOVE_RESULT_MISSED @ TODO: Is this even necessary?
goto BattleScript_MoveEnd
BattleScript_StrengthSapTryLower:
getstatvalue STAT_ATK
jumpiffullhp BS_ATTACKER, BattleScript_StrengthSapMustLower
BattleScript_StrengthSapAnimation:
attackanimation
waitanimation
statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_StrengthSapHp
jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_CHANGE_EMPTY, BattleScript_StrengthSapHp
printfromtable gStatDownStringIds
waitmessage B_WAIT_TIME_LONG
@ Drain HP without lowering a stat
BattleScript_StrengthSapHp:
jumpifability BS_TARGET, ABILITY_LIQUID_OOZE, BattleScript_StrengthSapManipulateDmg
jumpifvolatile BS_ATTACKER, VOLATILE_HEAL_BLOCK, BattleScript_MoveEnd
jumpiffullhp BS_ATTACKER, BattleScript_MoveEnd
BattleScript_StrengthSapManipulateDmg:
manipulatedamage DMG_BIG_ROOT
jumpifability BS_TARGET, ABILITY_LIQUID_OOZE, BattleScript_StrengthSapLiquidOoze
healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE
datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE
printstring STRINGID_PKMNENERGYDRAINED
waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd
BattleScript_StrengthSapLiquidOoze:
call BattleScript_AbilityPopUpTarget
manipulatedamage DMG_CHANGE_SIGN
setbyte cMULTISTRING_CHOOSER, B_MSG_ABSORB_OOZE
healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE
datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE
printfromtable gAbsorbDrainStringIds
waitmessage B_WAIT_TIME_LONG
tryfaintmon BS_ATTACKER
goto BattleScript_MoveEnd
BattleScript_StrengthSapMustLower:
statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_ONLY_CHECKING, BattleScript_MoveEnd
jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_CHANGE_EMPTY, BattleScript_MoveEnd
goto BattleScript_StrengthSapAnimation
BattleScript_MoveEffectIncinerate::
printstring STRINGID_INCINERATEBURN

View File

@ -150,6 +150,7 @@
#define B_COUNTER_TRY_HIT_PARTNER GEN_LATEST // In Gen5+, if the user of the last attack is not on the field, it will be redirected to the partner. In Gen4-, Counter/Mirror Coat/Metal Burst would fail.
#define B_RAGE_BUILDS GEN_LATEST // In Gen4+, Rage's effect only sets in when it successfully hits. In Gen3, Rage's effect sets in regardless of whether it hits, misses or fails.
#define B_CHECK_USER_FAILURE GEN_LATEST // In Gen5+, The user no longer checks it's own failure, e.g. Soundproof will not block it's own Perish Song
#define B_ABSORB_MESSAGE GEN_LATEST // In Gen5+, No absorb message is played if user is already at full hp.
// Ability settings
#define B_GALE_WINGS GEN_LATEST // In Gen7+ requires full HP to trigger.

View File

@ -349,9 +349,7 @@ enum BattleScriptOpcode
#define CMP_NO_COMMON_BITS 5
// Cmd_manipulatedamage
#define DMG_CHANGE_SIGN 1
#define DMG_1_8_TARGET_HP 2
#define DMG_BIG_ROOT 3
#define DMG_1_8_TARGET_HP 0 // Used by bad dreams
// Cmd_jumpifcantswitch
#define SWITCH_IGNORE_ESCAPE_PREVENTION (1 << 7)

View File

@ -132,6 +132,7 @@
F(FOCUS_PUNCH_FAILURE, focusPunchFailure, (u32, GEN_COUNT - 1)) \
F(RAGE_BUILDS, rageBuilds, (u32, GEN_COUNT - 1)) \
F(CHECK_USER_FAILURE, checkUserFailure, (u32, GEN_COUNT - 1)) \
F(ABSORB_MESSAGE, absorbMessge, (u32, GEN_COUNT - 1)) \
/* Ability settings */ \
F(GALE_WINGS, galeWings, (u32, GEN_COUNT - 1)) \
F(STANCE_CHANGE_FAIL, stanceChangeFail, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \

View File

@ -1759,6 +1759,24 @@ static enum MoveEndResult MoveEndProtectLikeEffect(void)
return result;
}
static void SetHealScript(s32 healAmount)
{
healAmount = GetDrainedBigRootHp(gBattlerAttacker, healAmount);
if (GetBattlerAbility(gBattlerTarget) == ABILITY_LIQUID_OOZE
&& (GetMoveEffect(gCurrentMove) != EFFECT_DREAM_EATER || GetConfig(CONFIG_DREAM_EATER_LIQUID_OOZE) >= GEN_5))
{
SetPassiveDamageAmount(gBattlerAttacker, healAmount);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB_OOZE;
BattleScriptCall(BattleScript_EffectAbsorbLiquidOoze);
}
else if (!IsBattlerAtMaxHp(gBattlerAttacker) || GetConfig(CONFIG_ABSORB_MESSAGE) < GEN_5)
{
SetHealAmount(gBattlerAttacker, healAmount);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB;
BattleScriptCall(BattleScript_EffectAbsorb);
}
}
static enum MoveEndResult MoveEndAbsorb(void)
{
if (gBattleStruct->unableToUseMove)
@ -1783,28 +1801,22 @@ static enum MoveEndResult MoveEndAbsorb(void)
switch (moveEffect)
{
case EFFECT_STRENGTH_SAP:
if (gBattleStruct->passiveHpUpdate[gBattlerAttacker] > 0)
{
s32 healAmount = gBattleStruct->passiveHpUpdate[gBattlerAttacker];
SetHealScript(healAmount);
result = MOVEEND_RESULT_RUN_SCRIPT;
}
break;
case EFFECT_ABSORB:
case EFFECT_DREAM_EATER:
if (!gBattleMons[gBattlerAttacker].volatiles.healBlock
&& gBattleStruct->moveDamage[gBattlerTarget] > 0
if (gBattleStruct->moveDamage[gBattlerTarget] > 0
&& IsBattlerTurnDamaged(gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker))
{
s32 healAmount = (gBattleStruct->moveDamage[gBattlerTarget] * GetMoveAbsorbPercentage(gCurrentMove) / 100);
healAmount = GetDrainedBigRootHp(gBattlerAttacker, healAmount);
if ((moveEffect == EFFECT_DREAM_EATER && GetConfig(CONFIG_DREAM_EATER_LIQUID_OOZE) < GEN_5)
|| GetBattlerAbility(gBattlerTarget) != ABILITY_LIQUID_OOZE)
{
SetHealAmount(gBattlerAttacker, healAmount);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB;
BattleScriptCall(BattleScript_EffectAbsorb);
}
else // Liquid Ooze damage
{
SetPassiveDamageAmount(gBattlerAttacker, healAmount);
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB_OOZE;
BattleScriptCall(BattleScript_EffectAbsorbLiquidOoze);
}
SetHealScript(healAmount);
result = MOVEEND_RESULT_RUN_SCRIPT;
}
break;

View File

@ -7339,15 +7339,9 @@ static void Cmd_manipulatedamage(void)
switch (cmd->mode)
{
case DMG_CHANGE_SIGN:
gBattleStruct->passiveHpUpdate[gBattlerAttacker] *= -1;
break;
case DMG_1_8_TARGET_HP:
SetPassiveDamageAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerTarget) / 8);
break;
case DMG_BIG_ROOT:
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = -1 * GetDrainedBigRootHp(gBattlerAttacker, gBattleStruct->passiveHpUpdate[gBattlerAttacker]);
break;
}
gBattlescriptCurrInstr = cmd->nextInstr;
@ -13409,30 +13403,34 @@ void BS_TrySetFairyLock(void)
void BS_GetStatValue(void)
{
NATIVE_ARGS(u8 stat);
u32 stat = cmd->stat;
u32 healAmount = 0;
switch (stat)
{
case STAT_ATK:
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].attack;
healAmount = gBattleMons[gBattlerTarget].attack;
break;
case STAT_DEF:
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].defense;
healAmount = gBattleMons[gBattlerTarget].defense;
break;
case STAT_SPATK:
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].spAttack;
healAmount = gBattleMons[gBattlerTarget].spAttack;
break;
case STAT_SPDEF:
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].spDefense;
healAmount = gBattleMons[gBattlerTarget].spDefense;
break;
case STAT_SPEED:
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerTarget].speed;
healAmount = gBattleMons[gBattlerTarget].speed;
break;
default:
errorf("Illegal stat requested");
return;
}
gBattleStruct->passiveHpUpdate[gBattlerAttacker] *= gStatStageRatios[gBattleMons[gBattlerTarget].statStages[stat]][0];
gBattleStruct->passiveHpUpdate[gBattlerAttacker] /= gStatStageRatios[gBattleMons[gBattlerTarget].statStages[stat]][1];
healAmount *= gStatStageRatios[gBattleMons[gBattlerTarget].statStages[stat]][0];
healAmount /= gStatStageRatios[gBattleMons[gBattlerTarget].statStages[stat]][1];
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = healAmount;
gBattlescriptCurrInstr = cmd->nextInstr;
}

View File

@ -10066,6 +10066,7 @@ void ClearDamageCalcResults(void)
{
gBattleStruct->moveDamage[battler] = 0;
gBattleStruct->moveResultFlags[battler] = 0;
gBattleStruct->passiveHpUpdate[battler] = 0;
gSpecialStatuses[battler].criticalHit = FALSE;
gSpecialStatuses[battler].damagedByAttack = FALSE;
}

View File

@ -88,14 +88,14 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes Strength Sap users to lose HP instead of
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Attack(atkStat); Ability(ABILITY_LIQUID_OOZE); }
OPPONENT(SPECIES_TENTACOOL) { Attack(atkStat); Ability(ABILITY_LIQUID_OOZE); }
} WHEN {
TURN { MOVE(player, MOVE_STRENGTH_SAP); if (atkStat == 490) { SEND_OUT(player, 1); } }
} SCENE {
MESSAGE("Wobbuffet used Strength Sap!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STRENGTH_SAP, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Wobbuffet's Attack fell!");
MESSAGE("The opposing Tentacool's Attack fell!");
ABILITY_POPUP(opponent, ABILITY_LIQUID_OOZE);
HP_BAR(player, captureDamage: &lostHp);
MESSAGE("Wobbuffet sucked up the liquid ooze!");

View File

@ -24,6 +24,7 @@ SINGLE_BATTLE_TEST("Absorb recovers 50% of the damage dealt")
}
}
// The animation makes the recording freeze. Changing it fixes it
SINGLE_BATTLE_TEST("Absorb fails if Heal Block applies")
{
GIVEN {
@ -162,3 +163,26 @@ DOUBLE_BATTLE_TEST("Spread Move: Heals the correct amount from all Pokemon")
EXPECT_MUL_EQ(damage[2], Q_4_12(-0.5), healed[2]);
}
}
SINGLE_BATTLE_TEST("Absorb does not play the draining message at full HP in Gen5+")
{
u32 genConfig = 0;
PARAMETRIZE { genConfig = GEN_4; }
PARAMETRIZE { genConfig = GEN_5; }
GIVEN {
WITH_CONFIG(CONFIG_ABSORB_MESSAGE, genConfig);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_ABSORB); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player);
if (genConfig < GEN_5)
MESSAGE("The opposing Wobbuffet had its energy drained!");
else
NOT MESSAGE("The opposing Wobbuffet had its energy drained!");
}
}

View File

@ -141,7 +141,7 @@ SINGLE_BATTLE_TEST("Strength Sap fails if target is at -6 Atk")
}
}
TO_DO_BATTLE_TEST("Strength Sap doesn't fail if target has Contrary and is at +6 Atk, restoring HP based on +5 Atk")
TO_DO_BATTLE_TEST("Strength Sap will restore hp if target has Contrary and is at +6 Atk")
SINGLE_BATTLE_TEST("Strength Sap restores more HP if Big Root is held", s16 hp)
{
@ -167,3 +167,92 @@ SINGLE_BATTLE_TEST("Strength Sap restores more HP if Big Root is held", s16 hp)
EXPECT_GT(abs(results[1].hp), abs(results[0].hp));
}
}
SINGLE_BATTLE_TEST("Strength Sap will not drain users hp due to Liquid Ooze if user is Magic Guard protected")
{
GIVEN {
PLAYER(SPECIES_TENTACOOL) { Ability(ABILITY_LIQUID_OOZE); }
OPPONENT(SPECIES_CLEFAIRY) { HP(1); Ability(ABILITY_MAGIC_GUARD); }
} WHEN {
TURN { MOVE(opponent, MOVE_STRENGTH_SAP); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STRENGTH_SAP, opponent);
ABILITY_POPUP(player, ABILITY_LIQUID_OOZE);
NOT HP_BAR(opponent);
}
}
SINGLE_BATTLE_TEST("Strength Sap does not fail if target has Contrary and is at +6 Atk")
{
GIVEN {
PLAYER(SPECIES_SNIVY) { Ability(ABILITY_CONTRARY); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(opponent, MOVE_CHARM); }
TURN { MOVE(opponent, MOVE_CHARM); }
TURN { MOVE(opponent, MOVE_CHARM); }
TURN { MOVE(opponent, MOVE_STRENGTH_SAP); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_STRENGTH_SAP, opponent);
}
}
SINGLE_BATTLE_TEST("Strength Sap does not fail if user is at full hp and target has Clear Body")
{
GIVEN {
PLAYER(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(opponent, MOVE_STRENGTH_SAP); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STRENGTH_SAP, opponent);
}
}
SINGLE_BATTLE_TEST("Strength Sap fails if Heal Block applies")
{
GIVEN {
ASSUME(B_HEAL_BLOCKING >= GEN_6);
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_HEAL_BLOCK); MOVE(player, MOVE_STRENGTH_SAP); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BLOCK, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STRENGTH_SAP, player);
}
}
SINGLE_BATTLE_TEST("Strength Sap will drain users HP if target has Liquid Ooze")
{
s16 lostHp;
s32 atkStat;
PARAMETRIZE { atkStat = 100; }
PARAMETRIZE { atkStat = 490; } // Checks that attacker can faint with no problems.
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_TENTACOOL) { Attack(atkStat); Ability(ABILITY_LIQUID_OOZE); }
} WHEN {
TURN { MOVE(player, MOVE_STRENGTH_SAP); if (atkStat == 490) { SEND_OUT(player, 1); } }
} SCENE {
MESSAGE("Wobbuffet used Strength Sap!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STRENGTH_SAP, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Tentacool's Attack fell!");
ABILITY_POPUP(opponent, ABILITY_LIQUID_OOZE);
HP_BAR(player, captureDamage: &lostHp);
MESSAGE("Wobbuffet sucked up the liquid ooze!");
if (atkStat >= 490) {
MESSAGE("Wobbuffet fainted!");
SEND_IN_MESSAGE("Wobbuffet");
}
} THEN {
EXPECT_EQ(lostHp, atkStat);
}
}

View File

@ -36,30 +36,23 @@ SINGLE_BATTLE_TEST("Upper Hand fails if the target is using a status move")
TURN { MOVE(opponent, MOVE_BABY_DOLL_EYES); MOVE(player, MOVE_UPPER_HAND); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player);
MESSAGE("Mienshao used Upper Hand!");
MESSAGE("But it failed!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_BABY_DOLL_EYES, opponent);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Mienshao's Attack fell!");
}
}
SINGLE_BATTLE_TEST("Upper Hand fails if the target is not using a priority move")
{
GIVEN {
ASSUME(GetMoveCategory(MOVE_DRAINING_KISS) == DAMAGE_CATEGORY_SPECIAL);
ASSUME(GetMovePriority(MOVE_DRAINING_KISS) == 0);
ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL);
ASSUME(GetMovePriority(MOVE_WATER_GUN) == 0);
PLAYER(SPECIES_MIENSHAO);
OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_FLOWER_VEIL); }
} WHEN {
TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); }
TURN { MOVE(opponent, MOVE_WATER_GUN); MOVE(player, MOVE_UPPER_HAND); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player);
MESSAGE("Mienshao used Upper Hand!");
MESSAGE("But it failed!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent);
HP_BAR(player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent);
}
}
@ -75,8 +68,6 @@ SINGLE_BATTLE_TEST("Upper Hand succeeds if the target's move is boosted in prior
TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player);
HP_BAR(opponent);
MESSAGE("The opposing Comfey flinched and couldn't move!");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent);
}
}
@ -93,11 +84,7 @@ SINGLE_BATTLE_TEST("Upper Hand fails if the target moves first")
TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent);
HP_BAR(player);
HP_BAR(opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player);
MESSAGE("Mienshao used Upper Hand!");
MESSAGE("But it failed!");
}
}