Grudge, Destiny Bond and FaintBattler refactor (#8072)

Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
This commit is contained in:
Alex 2025-10-30 23:14:02 +01:00 committed by GitHub
parent ff557e3d0c
commit 1c1a98ee45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 183 additions and 160 deletions

View File

@ -936,7 +936,7 @@
.4byte \failInstr .4byte \failInstr
.endm .endm
.macro trysetdestinybondtohappen .macro unused_0xab
.byte 0xab .byte 0xab
.endm .endm
@ -1617,14 +1617,6 @@
.4byte \failInstr .4byte \failInstr
.endm .endm
.macro tryupdaterecoiltracker
callnative BS_TryUpdateRecoilTracker
.endm
.macro tryupdateleaderscresttracker
callnative BS_TryUpdateLeadersCrestTracker
.endm
.macro trytidyup clear:req, jumpInstr:req .macro trytidyup clear:req, jumpInstr:req
callnative BS_TryTidyUp callnative BS_TryTidyUp
.byte \clear .byte \clear

View File

@ -2306,6 +2306,7 @@ BattleScript_EffectMiracleEye::
accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
setvolatile BS_TARGET, VOLATILE_MIRACLE_EYE setvolatile BS_TARGET, VOLATILE_MIRACLE_EYE
goto BattleScript_IdentifiedFoe goto BattleScript_IdentifiedFoe
BattleScript_EffectGravity:: BattleScript_EffectGravity::
call BattleScript_EffectGravityInternal call BattleScript_EffectGravityInternal
goto BattleScript_MoveEnd goto BattleScript_MoveEnd
@ -2838,7 +2839,6 @@ BattleScript_EffectOHKO::
typecalc typecalc
jumpifmovehadnoeffect BattleScript_HitFromAtkAnimation jumpifmovehadnoeffect BattleScript_HitFromAtkAnimation
tryKO BattleScript_KOFail tryKO BattleScript_KOFail
trysetdestinybondtohappen
goto BattleScript_HitFromAtkAnimation goto BattleScript_HitFromAtkAnimation
BattleScript_KOFail:: BattleScript_KOFail::
pause B_WAIT_TIME_LONG pause B_WAIT_TIME_LONG
@ -2853,6 +2853,7 @@ BattleScript_RecoilIfMiss::
healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE
datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE
tryfaintmon BS_ATTACKER tryfaintmon BS_ATTACKER
BattleScript_RecoilEnd:
return return
BattleScript_EffectMist:: BattleScript_EffectMist::
@ -4393,40 +4394,23 @@ BattleScript_EffectCamouflage::
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd goto BattleScript_MoveEnd
BattleScript_FaintAttacker:: BattleScript_FaintBattler::
tryillusionoff BS_ATTACKER tryillusionoff BS_SCRIPTING
tryactivategulpmissile tryactivategulpmissile
playfaintcry BS_ATTACKER playfaintcry BS_SCRIPTING
pause B_WAIT_TIME_LONG pause B_WAIT_TIME_LONG
dofaintanimation BS_ATTACKER dofaintanimation BS_SCRIPTING
printstring STRINGID_ATTACKERFAINTED printstring STRINGID_BATTLERFAINTED
cleareffectsonfaint BS_ATTACKER cleareffectsonfaint BS_SCRIPTING
trytoclearprimalweather trytoclearprimalweather
tryrevertweatherform tryrevertweatherform
flushtextbox flushtextbox
waitanimation waitanimation
tryactivatesoulheart tryactivatesoulheart
tryactivatereceiver BS_ATTACKER tryactivatereceiver BS_SCRIPTING
trytrainerslidemsgfirstoff BS_ATTACKER trytrainerslidemsgfirstoff BS_SCRIPTING
return return
BattleScript_FaintTarget::
tryillusionoff BS_TARGET
tryactivategulpmissile
tryupdateleaderscresttracker
playfaintcry BS_TARGET
pause B_WAIT_TIME_LONG
dofaintanimation BS_TARGET
printstring STRINGID_TARGETFAINTED
cleareffectsonfaint BS_TARGET
trytoclearprimalweather
tryrevertweatherform
flushtextbox
waitanimation
tryactivatesoulheart
tryactivatereceiver BS_TARGET
trytrainerslidemsgfirstoff BS_TARGET
return
BattleScript_GiveExp:: BattleScript_GiveExp::
setbyte sGIVEEXP_STATE, 0 setbyte sGIVEEXP_STATE, 0
@ -6391,17 +6375,11 @@ BattleScript_MoveEffectConfusion::
return return
BattleScript_MoveEffectRecoil:: BattleScript_MoveEffectRecoil::
jumpifmove MOVE_STRUGGLE, BattleScript_DoRecoil
jumpifability BS_ATTACKER, ABILITY_ROCK_HEAD, BattleScript_RecoilEnd
jumpifability BS_ATTACKER, ABILITY_MAGIC_GUARD, BattleScript_RecoilEnd
BattleScript_DoRecoil::
healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE
datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE
printstring STRINGID_PKMNHITWITHRECOIL printstring STRINGID_PKMNHITWITHRECOIL
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
tryupdaterecoiltracker
tryfaintmon BS_ATTACKER tryfaintmon BS_ATTACKER
BattleScript_RecoilEnd::
return return
BattleScript_ItemSteal:: BattleScript_ItemSteal::

View File

@ -777,11 +777,12 @@ struct BattleStruct
u8 numHazards[NUM_BATTLE_SIDES]; u8 numHazards[NUM_BATTLE_SIDES];
u8 hazardsCounter:4; // Counter for applying hazard on switch in u8 hazardsCounter:4; // Counter for applying hazard on switch in
enum SubmoveState submoveAnnouncement:2; enum SubmoveState submoveAnnouncement:2;
u8 padding3:2; u8 tryDestinyBond:1;
u8 tryGrudge:1;
u16 flingItem; u16 flingItem;
u8 incrementEchoedVoice:1; u8 incrementEchoedVoice:1;
u8 echoedVoiceCounter:3; u8 echoedVoiceCounter:3;
u8 padding4:4; u8 padding3:4;
}; };
struct AiBattleData struct AiBattleData

View File

@ -22,8 +22,7 @@ extern const u8 BattleScript_StatDown[];
extern const u8 BattleScript_AlreadyAtFullHp[]; extern const u8 BattleScript_AlreadyAtFullHp[];
extern const u8 BattleScript_PresentHealTarget[]; extern const u8 BattleScript_PresentHealTarget[];
extern const u8 BattleScript_MoveUsedMustRecharge[]; extern const u8 BattleScript_MoveUsedMustRecharge[];
extern const u8 BattleScript_FaintAttacker[]; extern const u8 BattleScript_FaintBattler[];
extern const u8 BattleScript_FaintTarget[];
extern const u8 BattleScript_GiveExp[]; extern const u8 BattleScript_GiveExp[];
extern const u8 BattleScript_HandleFaintedMon[]; extern const u8 BattleScript_HandleFaintedMon[];
extern const u8 BattleScript_LocalTrainerBattleWon[]; extern const u8 BattleScript_LocalTrainerBattleWon[];

View File

@ -264,7 +264,7 @@ enum SemiInvulnerableExclusion
#define HITMARKER_UNUSED_21 (1 << 21) #define HITMARKER_UNUSED_21 (1 << 21)
#define HITMARKER_PLAYER_FAINTED (1 << 22) #define HITMARKER_PLAYER_FAINTED (1 << 22)
#define HITMARKER_UNUSED_23 (1 << 23) #define HITMARKER_UNUSED_23 (1 << 23)
#define HITMARKER_GRUDGE (1 << 24) #define HITMARKER_UNUSED_24 (1 << 24)
#define HITMARKER_OBEYS (1 << 25) #define HITMARKER_OBEYS (1 << 25)
#define HITMARKER_UNUSED_26 (1 << 26) #define HITMARKER_UNUSED_26 (1 << 26)
#define HITMARKER_UNUSED_27 (1 << 27) #define HITMARKER_UNUSED_27 (1 << 27)

View File

@ -130,6 +130,8 @@ enum MoveEndEffects
{ {
MOVEEND_SET_VALUES, MOVEEND_SET_VALUES,
MOVEEND_PROTECT_LIKE_EFFECT, MOVEEND_PROTECT_LIKE_EFFECT,
MOVEEND_GRUDGE,
MOVEEND_DESTINY_BOND,
MOVEEND_ABSORB, MOVEEND_ABSORB,
MOVEEND_RAGE, MOVEEND_RAGE,
MOVEEND_SYNCHRONIZE_TARGET, MOVEEND_SYNCHRONIZE_TARGET,

View File

@ -28,8 +28,7 @@ enum StringID
STRINGID_STATSWONTINCREASE2, STRINGID_STATSWONTINCREASE2,
STRINGID_AVOIDEDDAMAGE, STRINGID_AVOIDEDDAMAGE,
STRINGID_ITDOESNTAFFECT, STRINGID_ITDOESNTAFFECT,
STRINGID_ATTACKERFAINTED, STRINGID_BATTLERFAINTED,
STRINGID_TARGETFAINTED,
STRINGID_PLAYERGOTMONEY, STRINGID_PLAYERGOTMONEY,
STRINGID_PLAYERWHITEOUT, STRINGID_PLAYERWHITEOUT,
STRINGID_PLAYERWHITEOUT2_WILD, STRINGID_PLAYERWHITEOUT2_WILD,

View File

@ -2250,7 +2250,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_DESTINY_BOND: case EFFECT_DESTINY_BOND:
if (DoesDestinyBondFail(battlerAtk)) if (DoesDestinyBondFail(battlerAtk))
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
if (gBattleMons[battlerDef].volatiles.destinyBond) if (gBattleMons[battlerAtk].volatiles.destinyBond)
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
ADJUST_SCORE(-10); ADJUST_SCORE(-10);

View File

@ -1540,7 +1540,6 @@ static bool32 (*const sEndTurnEffectHandlers[])(u32 battler) =
u32 DoEndTurnEffects(void) u32 DoEndTurnEffects(void)
{ {
u32 battler = MAX_BATTLERS_COUNT; u32 battler = MAX_BATTLERS_COUNT;
gHitMarker |= HITMARKER_GRUDGE;
for (;;) for (;;)
{ {
@ -1555,10 +1554,7 @@ u32 DoEndTurnEffects(void)
// Jump out if possible after endTurnEventsCounter was increased in the above code block // Jump out if possible after endTurnEventsCounter was increased in the above code block
if (gBattleStruct->endTurnEventsCounter == ENDTURN_COUNT) if (gBattleStruct->endTurnEventsCounter == ENDTURN_COUNT)
{
gHitMarker &= ~HITMARKER_GRUDGE;
return FALSE; return FALSE;
}
battler = gBattlerAttacker = gBattlerByTurnOrder[gBattleStruct->turnEffectsBattlerId]; battler = gBattlerAttacker = gBattlerByTurnOrder[gBattleStruct->turnEffectsBattlerId];

View File

@ -549,7 +549,7 @@ static void CB2_InitBattleInternal(void)
gReservedSpritePaletteCount = MAX_BATTLERS_COUNT; gReservedSpritePaletteCount = MAX_BATTLERS_COUNT;
SetVBlankCallback(VBlankCB_Battle); SetVBlankCallback(VBlankCB_Battle);
SetUpBattleVarsAndBirchZigzagoon(); SetUpBattleVarsAndBirchZigzagoon();
if ((IsMultibattleTest() && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) if ((IsMultibattleTest() && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
|| (gBattleTypeFlags & BATTLE_TYPE_MULTI && gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER) || (gBattleTypeFlags & BATTLE_TYPE_MULTI && gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER)
|| (gBattleTypeFlags & BATTLE_TYPE_MULTI && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)) || (gBattleTypeFlags & BATTLE_TYPE_MULTI && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER))
@ -5164,6 +5164,8 @@ static void TurnValuesCleanUp(bool8 var0)
gSideTimers[B_SIDE_OPPONENT].followmeTimer = 0; gSideTimers[B_SIDE_OPPONENT].followmeTimer = 0;
gBattleStruct->pledgeMove = FALSE; // combined pledge move may not have been used due to a canceller gBattleStruct->pledgeMove = FALSE; // combined pledge move may not have been used due to a canceller
gBattleStruct->tryDestinyBond = FALSE;
gBattleStruct->tryGrudge = FALSE;
ClearPursuitValues(); ClearPursuitValues();
ClearDamageCalcResults(); ClearDamageCalcResults();
} }

View File

@ -188,8 +188,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] =
[STRINGID_STATSWONTINCREASE2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s stats won't go any higher!"), [STRINGID_STATSWONTINCREASE2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s stats won't go any higher!"),
[STRINGID_AVOIDEDDAMAGE] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} avoided damage with {B_DEF_ABILITY}!"), //not in gen 5+, ability popup [STRINGID_AVOIDEDDAMAGE] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} avoided damage with {B_DEF_ABILITY}!"), //not in gen 5+, ability popup
[STRINGID_ITDOESNTAFFECT] = COMPOUND_STRING("It doesn't affect {B_DEF_NAME_WITH_PREFIX2}…"), [STRINGID_ITDOESNTAFFECT] = COMPOUND_STRING("It doesn't affect {B_DEF_NAME_WITH_PREFIX2}…"),
[STRINGID_ATTACKERFAINTED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} fainted!\p"), [STRINGID_BATTLERFAINTED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} fainted!\p"),
[STRINGID_TARGETFAINTED] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} fainted!\p"),
[STRINGID_PLAYERGOTMONEY] = COMPOUND_STRING("You got ¥{B_BUFF1} for winning!\p"), [STRINGID_PLAYERGOTMONEY] = COMPOUND_STRING("You got ¥{B_BUFF1} for winning!\p"),
[STRINGID_PLAYERWHITEOUT] = COMPOUND_STRING("You have no more Pokémon that can fight!\p"), [STRINGID_PLAYERWHITEOUT] = COMPOUND_STRING("You have no more Pokémon that can fight!\p"),
[STRINGID_PLAYERWHITEOUT2_WILD] = COMPOUND_STRING("You panicked and dropped ¥{B_BUFF1}…"), [STRINGID_PLAYERWHITEOUT2_WILD] = COMPOUND_STRING("You panicked and dropped ¥{B_BUFF1}…"),

View File

@ -310,7 +310,6 @@ enum GiveCaughtMonStates
#define TAG_LVLUP_BANNER_MON_ICON 55130 #define TAG_LVLUP_BANNER_MON_ICON 55130
static void TrySetDestinyBondToHappen(void);
static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union StatChangeFlags flags, u32 stats, const u8 *BS_ptr); static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union StatChangeFlags flags, u32 stats, const u8 *BS_ptr);
static bool32 IsMonGettingExpSentOut(void); static bool32 IsMonGettingExpSentOut(void);
static void InitLevelUpBanner(void); static void InitLevelUpBanner(void);
@ -509,7 +508,7 @@ static void Cmd_setalwayshitflag(void);
static void Cmd_copymovepermanently(void); static void Cmd_copymovepermanently(void);
static void Cmd_unused_0xA9(void); static void Cmd_unused_0xA9(void);
static void Cmd_unused_AA(void); static void Cmd_unused_AA(void);
static void Cmd_trysetdestinybondtohappen(void); static void Cmd_unused_0xab(void);
static void Cmd_settailwind(void); static void Cmd_settailwind(void);
static void Cmd_tryspiteppreduce(void); static void Cmd_tryspiteppreduce(void);
static void Cmd_healpartystatus(void); static void Cmd_healpartystatus(void);
@ -768,7 +767,7 @@ void (*const gBattleScriptingCommandsTable[])(void) =
Cmd_copymovepermanently, //0xA8 Cmd_copymovepermanently, //0xA8
Cmd_unused_0xA9, //0xA9 Cmd_unused_0xA9, //0xA9
Cmd_unused_AA, //0xAA Cmd_unused_AA, //0xAA
Cmd_trysetdestinybondtohappen, //0xAB Cmd_unused_0xab, //0xAB
Cmd_settailwind, //0xAC Cmd_settailwind, //0xAC
Cmd_tryspiteppreduce, //0xAD Cmd_tryspiteppreduce, //0xAD
Cmd_healpartystatus, //0xAE Cmd_healpartystatus, //0xAE
@ -1317,8 +1316,6 @@ static void JumpIfMoveFailed(u32 adder, u32 move, u32 moveType, const u8 *failIn
} }
else else
{ {
TrySetDestinyBondToHappen();
if (CanAbilityAbsorbMove(gBattlerAttacker, if (CanAbilityAbsorbMove(gBattlerAttacker,
gBattlerTarget, gBattlerTarget,
GetBattlerAbility(gBattlerTarget), GetBattlerAbility(gBattlerTarget),
@ -3323,6 +3320,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai
if (GetBattlerAbility(gEffectBattler) == ABILITY_PARENTAL_BOND) if (GetBattlerAbility(gEffectBattler) == ABILITY_PARENTAL_BOND)
recoil *= 2; recoil *= 2;
SetPassiveDamageAmount(gEffectBattler, recoil); SetPassiveDamageAmount(gEffectBattler, recoil);
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE);
BattleScriptPush(gBattlescriptCurrInstr + 1); BattleScriptPush(gBattlescriptCurrInstr + 1);
gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil; gBattlescriptCurrInstr = BattleScript_MoveEffectRecoil;
break; break;
@ -4175,7 +4173,7 @@ static void Cmd_clearvolatile(void)
static void Cmd_tryfaintmon(void) static void Cmd_tryfaintmon(void)
{ {
CMD_ARGS(u8 battler, bool8 isSpikes, const u8 *instr); CMD_ARGS(u8 battler, bool8 isSpikes, const u8 *instr);
u32 battler, destinyBondBattler; u32 battler;
const u8 *faintScript; const u8 *faintScript;
battler = GetBattlerForBattleScript(cmd->battler); battler = GetBattlerForBattleScript(cmd->battler);
@ -4202,16 +4200,12 @@ static void Cmd_tryfaintmon(void)
gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits;
return; return;
} }
if (cmd->battler == BS_ATTACKER) if (cmd->battler == BS_ATTACKER)
{ TryUpdateEvolutionTracker(IF_DEFEAT_X_WITH_ITEMS, 1, MOVE_NONE);
destinyBondBattler = gBattlerTarget;
faintScript = BattleScript_FaintAttacker; gBattleScripting.battler = battler;
} faintScript = BattleScript_FaintBattler;
else
{
destinyBondBattler = gBattlerAttacker;
faintScript = BattleScript_FaintTarget;
}
if (!(gAbsentBattlerFlags & (1u << battler)) if (!(gAbsentBattlerFlags & (1u << battler))
&& !IsBattlerAlive(battler)) && !IsBattlerAlive(battler))
{ {
@ -4233,30 +4227,11 @@ static void Cmd_tryfaintmon(void)
gBattleResults.lastOpponentSpecies = GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES, NULL); gBattleResults.lastOpponentSpecies = GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES, NULL);
gSideTimers[B_SIDE_OPPONENT].retaliateTimer = 2; gSideTimers[B_SIDE_OPPONENT].retaliateTimer = 2;
} }
if ((gHitMarker & HITMARKER_DESTINYBOND) && IsBattlerAlive(gBattlerAttacker)
&& !(GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX))
{
gHitMarker &= ~HITMARKER_DESTINYBOND;
BattleScriptPush(gBattlescriptCurrInstr);
gBattleStruct->passiveHpUpdate[destinyBondBattler] = gBattleMons[destinyBondBattler].hp;
gBattlescriptCurrInstr = BattleScript_DestinyBondTakesLife;
}
if (gBattleMons[gBattlerTarget].volatiles.grudge
&& !(gHitMarker & HITMARKER_GRUDGE)
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker)
&& gCurrentMove != MOVE_STRUGGLE)
{
u8 moveIndex = gBattleStruct->chosenMovePositions[gBattlerAttacker];
gBattleMons[gBattlerAttacker].pp[moveIndex] = 0; if (gBattleMons[gBattlerTarget].volatiles.destinyBond)
BattleScriptPush(gBattlescriptCurrInstr); gBattleStruct->tryDestinyBond = TRUE;
gBattlescriptCurrInstr = BattleScript_GrudgeTakesPp; if (gBattleMons[gBattlerTarget].volatiles.grudge)
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, moveIndex + REQUEST_PPMOVE1_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].pp[moveIndex]), &gBattleMons[gBattlerAttacker].pp[moveIndex]); gBattleStruct->tryGrudge = TRUE;
MarkBattlerForControllerExec(gBattlerAttacker);
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerAttacker].moves[moveIndex])
}
TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]);
} }
@ -4957,8 +4932,6 @@ static void MoveValuesCleanUp(void)
gBattleScripting.moveEffect = MOVE_EFFECT_NONE; gBattleScripting.moveEffect = MOVE_EFFECT_NONE;
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE; gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE;
gBattleCommunication[MISS_TYPE] = 0; gBattleCommunication[MISS_TYPE] = 0;
if (!gMultiHitCounter)
gHitMarker &= ~HITMARKER_DESTINYBOND;
} }
static void Cmd_movevaluescleanup(void) static void Cmd_movevaluescleanup(void)
@ -5819,7 +5792,13 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
case EFFECT_RECOIL: case EFFECT_RECOIL:
if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker))
{ {
enum Ability ability = GetBattlerAbility(gBattlerAttacker);
if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD)
|| IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_MAGIC_GUARD))
break;
SetPassiveDamageAmount(gBattlerAttacker, gBattleScripting.savedDmg * max(1, GetMoveRecoil(gCurrentMove)) / 100); SetPassiveDamageAmount(gBattlerAttacker, gBattleScripting.savedDmg * max(1, GetMoveRecoil(gCurrentMove)) / 100);
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE);
BattleScriptCall(BattleScript_MoveEffectRecoil); BattleScriptCall(BattleScript_MoveEffectRecoil);
effect = TRUE; effect = TRUE;
} }
@ -5836,10 +5815,11 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
case EFFECT_MAX_HP_50_RECOIL: case EFFECT_MAX_HP_50_RECOIL:
if (IsBattlerAlive(gBattlerAttacker) if (IsBattlerAlive(gBattlerAttacker)
&& !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED) && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED)
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) && !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD))
{ {
s32 recoil = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP s32 recoil = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP
SetPassiveDamageAmount(gBattlerAttacker, recoil); SetPassiveDamageAmount(gBattlerAttacker, recoil);
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE);
BattleScriptCall(BattleScript_MaxHp50Recoil); BattleScriptCall(BattleScript_MaxHp50Recoil);
effect = TRUE; effect = TRUE;
} }
@ -5847,8 +5827,14 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
case EFFECT_CHLOROBLAST: case EFFECT_CHLOROBLAST:
if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker))
{ {
enum Ability ability = GetBattlerAbility(gBattlerAttacker);
if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD)
|| IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_MAGIC_GUARD))
break;
s32 recoil = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP s32 recoil = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP
SetPassiveDamageAmount(gBattlerAttacker, recoil); SetPassiveDamageAmount(gBattlerAttacker, recoil);
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE);
BattleScriptCall(BattleScript_MoveEffectRecoil); BattleScriptCall(BattleScript_MoveEffectRecoil);
effect = TRUE; effect = TRUE;
} }
@ -6044,6 +6030,43 @@ static void Cmd_moveend(void)
} }
gBattleScripting.moveendState++; gBattleScripting.moveendState++;
break; break;
case MOVEEND_GRUDGE:
if (gBattleStruct->tryGrudge
&& IsBattlerTurnDamaged(gBattlerTarget)
&& !IsBattlerAlive(gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker)
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget)
&& !IsZMove(gCurrentMove)
&& gCurrentMove != MOVE_STRUGGLE)
{
u32 moveIndex = gBattleStruct->chosenMovePositions[gBattlerAttacker];
gBattleStruct->tryGrudge = FALSE;
gBattleMons[gBattlerAttacker].pp[moveIndex] = 0;
BattleScriptCall(BattleScript_GrudgeTakesPp);
BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, moveIndex + REQUEST_PPMOVE1_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].pp[moveIndex]), &gBattleMons[gBattlerAttacker].pp[moveIndex]);
MarkBattlerForControllerExec(gBattlerAttacker);
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerAttacker].moves[moveIndex])
}
gBattleScripting.moveendState++;
break;
case MOVEEND_DESTINY_BOND:
if (gBattleStruct->tryDestinyBond
&& IsBattlerTurnDamaged(gBattlerTarget)
&& !IsBattlerAlive(gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker)
&& !(GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget))
{
gBattleStruct->tryDestinyBond = FALSE;
gBattleStruct->passiveHpUpdate[gBattlerAttacker] = gBattleMons[gBattlerAttacker].hp;
BattleScriptCall(BattleScript_DestinyBondTakesLife);
effect = TRUE;
}
gBattleScripting.moveendState++;
break;
case MOVEEND_ABSORB: case MOVEEND_ABSORB:
if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE
|| !IsBattlerTurnDamaged(gBattlerTarget)) || !IsBattlerTurnDamaged(gBattlerTarget))
@ -7705,8 +7728,6 @@ static void UpdateSentMonFlags(u32 battler)
static void SetDmgHazardsBattlescript(u8 battler, u8 multistringId) static void SetDmgHazardsBattlescript(u8 battler, u8 multistringId)
{ {
gBattleMons[battler].volatiles.destinyBond = 0;
gHitMarker &= ~HITMARKER_DESTINYBOND;
gBattleScripting.battler = battler; gBattleScripting.battler = battler;
gBattleCommunication[MULTISTRING_CHOOSER] = multistringId; gBattleCommunication[MULTISTRING_CHOOSER] = multistringId;
@ -11596,22 +11617,8 @@ static void Cmd_unused_AA(void)
{ {
} }
static void TrySetDestinyBondToHappen(void) static void Cmd_unused_0xab(void)
{ {
if (gBattleMons[gBattlerTarget].volatiles.destinyBond
&& !IsBattlerAlly(gBattlerAttacker, gBattlerTarget)
&& !(gHitMarker & HITMARKER_GRUDGE))
{
gHitMarker |= HITMARKER_DESTINYBOND;
}
}
static void Cmd_trysetdestinybondtohappen(void)
{
CMD_ARGS();
TrySetDestinyBondToHappen();
gBattlescriptCurrInstr = cmd->nextInstr;
} }
static void Cmd_settailwind(void) static void Cmd_settailwind(void)
@ -15475,20 +15482,6 @@ static void TryUpdateEvolutionTracker(u32 evolutionCondition, u32 upAmount, u16
} }
} }
void BS_TryUpdateRecoilTracker(void)
{
NATIVE_ARGS();
TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->moveDamage[gBattlerAttacker], MOVE_NONE);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryUpdateLeadersCrestTracker(void)
{
NATIVE_ARGS();
TryUpdateEvolutionTracker(IF_DEFEAT_X_WITH_ITEMS, 1, MOVE_NONE);
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryTidyUp(void) void BS_TryTidyUp(void)
{ {
NATIVE_ARGS(u8 clear, const u8 *jumpInstr); NATIVE_ARGS(u8 clear, const u8 *jumpInstr);

View File

@ -14,7 +14,7 @@
static bool8 IsNotSpecialBattleString(enum StringID stringId); static bool8 IsNotSpecialBattleString(enum StringID stringId);
static void AddMovePoints(u8 caseId, u16 arg1, u8 arg2, u8 arg3); static void AddMovePoints(u8 caseId, u16 arg1, u8 arg2, u8 arg3);
static void TrySetBattleSeminarShow(void); static void TrySetBattleSeminarShow(void);
static void AddPointsOnFainting(bool8 targetFainted); static void AddPointsOnFainting(void);
static void AddPointsBasedOnWeather(u16 weatherFlags, u16 move, u8 moveSlot); static void AddPointsBasedOnWeather(u16 weatherFlags, u16 move, u8 moveSlot);
static bool8 ShouldCalculateDamage(u16 move, s32 *dmg, u16 *powerOverride); static bool8 ShouldCalculateDamage(u16 move, s32 *dmg, u16 *powerOverride);
@ -124,12 +124,23 @@ static const u16 *const sPointsArray[] =
// even if current Pokémon does not have corresponding move // even if current Pokémon does not have corresponding move
static const u16 sSpecialBattleStrings[] = static const u16 sSpecialBattleStrings[] =
{ {
STRINGID_PKMNPERISHCOUNTFELL, STRINGID_PKMNWISHCAMETRUE, STRINGID_PKMNLOSTPPGRUDGE, STRINGID_PKMNPERISHCOUNTFELL,
STRINGID_PKMNTOOKFOE, STRINGID_PKMNABSORBEDNUTRIENTS, STRINGID_PKMNANCHOREDITSELF, STRINGID_PKMNWISHCAMETRUE,
STRINGID_PKMNAFFLICTEDBYCURSE, STRINGID_PKMNSAPPEDBYLEECHSEED, STRINGID_PKMNLOCKEDINNIGHTMARE, STRINGID_PKMNLOSTPPGRUDGE,
STRINGID_PKMNHURTBY, STRINGID_PKMNHURTBYBURN, STRINGID_PKMNHURTBYPOISON, STRINGID_PKMNTOOKFOE,
STRINGID_PKMNHURTBYSPIKES, STRINGID_ATTACKERFAINTED, STRINGID_TARGETFAINTED, STRINGID_PKMNABSORBEDNUTRIENTS,
STRINGID_PKMNHITWITHRECOIL, STRINGID_PKMNCRASHED, TABLE_END STRINGID_PKMNANCHOREDITSELF,
STRINGID_PKMNAFFLICTEDBYCURSE,
STRINGID_PKMNSAPPEDBYLEECHSEED,
STRINGID_PKMNLOCKEDINNIGHTMARE,
STRINGID_PKMNHURTBY,
STRINGID_PKMNHURTBYBURN,
STRINGID_PKMNHURTBYPOISON,
STRINGID_PKMNHURTBYSPIKES,
STRINGID_BATTLERFAINTED,
STRINGID_PKMNHITWITHRECOIL,
STRINGID_PKMNCRASHED,
TABLE_END
}; };
// code // code
@ -139,7 +150,7 @@ void BattleTv_SetDataBasedOnString(enum StringID stringId)
u32 atkSide, defSide, effSide, scriptingSide; u32 atkSide, defSide, effSide, scriptingSide;
struct Pokemon *atkMon, *defMon; struct Pokemon *atkMon, *defMon;
u8 moveSlot; u8 moveSlot;
u32 atkFlank, defFlank, effFlank; u32 atkFlank, defFlank, effFlank, flank;
u8 *perishCount; u8 *perishCount;
u16 *statStringId, *finishedMoveId; u16 *statStringId, *finishedMoveId;
@ -452,8 +463,6 @@ void BattleTv_SetDataBasedOnString(enum StringID stringId)
tvPtr->pos[atkSide][atkFlank].mudSportMonId = gBattlerPartyIndexes[gBattlerAttacker] + 1; tvPtr->pos[atkSide][atkFlank].mudSportMonId = gBattlerPartyIndexes[gBattlerAttacker] + 1;
tvPtr->pos[atkSide][atkFlank].mudSportMoveSlot = moveSlot; tvPtr->pos[atkSide][atkFlank].mudSportMoveSlot = moveSlot;
break; break;
case STRINGID_ATTACKERFAINTED:
AddPointsOnFainting(FALSE);
case STRINGID_RETURNMON: case STRINGID_RETURNMON:
if (tvPtr->pos[atkSide][atkFlank].waterSportMonId != 0) if (tvPtr->pos[atkSide][atkFlank].waterSportMonId != 0)
{ {
@ -466,17 +475,22 @@ void BattleTv_SetDataBasedOnString(enum StringID stringId)
tvPtr->pos[atkSide][atkFlank].mudSportMoveSlot = 0; tvPtr->pos[atkSide][atkFlank].mudSportMoveSlot = 0;
} }
break; break;
case STRINGID_TARGETFAINTED: case STRINGID_BATTLERFAINTED:
AddPointsOnFainting(TRUE); AddPointsOnFainting();
if (tvPtr->pos[atkSide][defFlank].waterSportMonId != 0) if (gBattlerAttacker == gBattleScripting.battler)
flank = atkFlank;
else
flank = defFlank;
if (tvPtr->pos[atkSide][flank].waterSportMonId != 0)
{ {
tvPtr->pos[atkSide][defFlank].waterSportMonId = 0; tvPtr->pos[atkSide][flank].waterSportMonId = 0;
tvPtr->pos[atkSide][defFlank].waterSportMoveSlot = 0; tvPtr->pos[atkSide][flank].waterSportMoveSlot = 0;
} }
if (tvPtr->pos[atkSide][defFlank].mudSportMonId != 0) if (tvPtr->pos[atkSide][flank].mudSportMonId != 0)
{ {
tvPtr->pos[atkSide][defFlank].mudSportMonId = 0; tvPtr->pos[atkSide][flank].mudSportMonId = 0;
tvPtr->pos[atkSide][defFlank].mudSportMoveSlot = 0; tvPtr->pos[atkSide][flank].mudSportMoveSlot = 0;
} }
break; break;
case STRINGID_PKMNRAISEDDEF: case STRINGID_PKMNRAISEDDEF:
@ -1064,7 +1078,7 @@ static void AddMovePoints(u8 caseId, u16 arg1, u8 arg2, u8 arg3)
} }
} }
static void AddPointsOnFainting(bool8 targetFainted) static void AddPointsOnFainting(void)
{ {
struct BattleTv *tvPtr = &gBattleStruct->tv; struct BattleTv *tvPtr = &gBattleStruct->tv;
u32 atkSide = GetBattlerSide(gBattlerAttacker); u32 atkSide = GetBattlerSide(gBattlerAttacker);
@ -1187,7 +1201,7 @@ static void AddPointsOnFainting(bool8 targetFainted)
} }
break; break;
case FNT_RECOIL: case FNT_RECOIL:
if (targetFainted == TRUE) if (gBattlerAttacker == gBattleScripting.battler)
{ {
AddMovePoints(PTS_FAINT_SET_UP, 0, atkSide, AddMovePoints(PTS_FAINT_SET_UP, 0, atkSide,
(gBattlerPartyIndexes[gBattlerAttacker]) * 4 + tvPtr->side[atkSide].usedMoveSlot); (gBattlerPartyIndexes[gBattlerAttacker]) * 4 + tvPtr->side[atkSide].usedMoveSlot);

View File

@ -912,8 +912,7 @@ void HandleAction_NothingIsFainted(void)
gCurrentTurnActionNumber++; gCurrentTurnActionNumber++;
gCurrentActionFuncId = gActionsByTurnOrder[gCurrentTurnActionNumber]; gCurrentActionFuncId = gActionsByTurnOrder[gCurrentTurnActionNumber];
gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE; gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE;
gHitMarker &= ~(HITMARKER_DESTINYBOND gHitMarker &= ~(HITMARKER_ATTACKSTRING_PRINTED
| HITMARKER_ATTACKSTRING_PRINTED
| HITMARKER_STATUS_ABILITY_EFFECT | HITMARKER_STATUS_ABILITY_EFFECT
| HITMARKER_OBEYS); | HITMARKER_OBEYS);
} }
@ -926,8 +925,7 @@ void HandleAction_ActionFinished(void)
gCurrentTurnActionNumber++; gCurrentTurnActionNumber++;
gCurrentActionFuncId = gActionsByTurnOrder[gCurrentTurnActionNumber]; gCurrentActionFuncId = gActionsByTurnOrder[gCurrentTurnActionNumber];
memset(&gSpecialStatuses, 0, sizeof(gSpecialStatuses)); memset(&gSpecialStatuses, 0, sizeof(gSpecialStatuses));
gHitMarker &= ~(HITMARKER_DESTINYBOND gHitMarker &= ~(HITMARKER_ATTACKSTRING_PRINTED
| HITMARKER_ATTACKSTRING_PRINTED
| HITMARKER_STATUS_ABILITY_EFFECT | HITMARKER_STATUS_ABILITY_EFFECT
| HITMARKER_OBEYS); | HITMARKER_OBEYS);
@ -1786,8 +1784,6 @@ void TryToRevertMimicryAndFlags(void)
bool32 BattleArenaTurnEnd(void) bool32 BattleArenaTurnEnd(void)
{ {
gHitMarker |= HITMARKER_GRUDGE;
if ((gBattleTypeFlags & BATTLE_TYPE_ARENA) if ((gBattleTypeFlags & BATTLE_TYPE_ARENA)
&& gBattleStruct->arenaTurnCounter == 2 && gBattleStruct->arenaTurnCounter == 2
&& IsBattlerAlive(B_POSITION_PLAYER_LEFT) && IsBattlerAlive(B_POSITION_OPPONENT_LEFT)) && IsBattlerAlive(B_POSITION_PLAYER_LEFT) && IsBattlerAlive(B_POSITION_OPPONENT_LEFT))
@ -1799,9 +1795,6 @@ bool32 BattleArenaTurnEnd(void)
BattleScriptExecute(BattleScript_ArenaDoJudgment); BattleScriptExecute(BattleScript_ArenaDoJudgment);
return TRUE; return TRUE;
} }
gHitMarker &= ~HITMARKER_GRUDGE;
return FALSE; return FALSE;
} }

View File

@ -239,8 +239,8 @@ SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon are affected by Grudge")
} SCENE { } SCENE {
MESSAGE("The opposing Wobbuffet used Grudge!"); MESSAGE("The opposing Wobbuffet used Grudge!");
MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Wobbuffet used Max Strike!");
MESSAGE("Wobbuffet's Scratch lost all its PP due to the grudge!");
MESSAGE("The opposing Wobbuffet fainted!"); MESSAGE("The opposing Wobbuffet fainted!");
MESSAGE("Wobbuffet's Scratch lost all its PP due to the grudge!");
} }
} }
@ -860,7 +860,7 @@ SINGLE_BATTLE_TEST("Dynamax: Max Hailstorm sets up hail")
MESSAGE("It started to hail!"); MESSAGE("It started to hail!");
MESSAGE("The opposing Wobbuffet used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HAIL_CONTINUES); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HAIL_CONTINUES);
#endif #endif
} }
} }

View File

@ -10,12 +10,18 @@ SINGLE_BATTLE_TEST("Destiny Bond faints the opposing mon if it fainted from the
{ {
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); } PLAYER(SPECIES_WOBBUFFET) { HP(1); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_DESTINY_BOND); MOVE(opponent, MOVE_SCRATCH); } TURN {
MOVE(player, MOVE_DESTINY_BOND);
MOVE(opponent, MOVE_SCRATCH);
SEND_OUT(player, 1);
}
} SCENE { } SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DESTINY_BOND, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_DESTINY_BOND, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent);
MESSAGE("Wobbuffet fainted!");
MESSAGE("Wobbuffet took its attacker down with it!"); MESSAGE("Wobbuffet took its attacker down with it!");
MESSAGE("The opposing Wobbuffet fainted!"); MESSAGE("The opposing Wobbuffet fainted!");
} }

View File

@ -2,3 +2,52 @@
#include "test/battle.h" #include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Grudge (Move Effect) test titles") TO_DO_BATTLE_TEST("TODO: Write Grudge (Move Effect) test titles")
SINGLE_BATTLE_TEST("Grudge depletes all pp of the move that fainted the target")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_SCRATCH, MOVE_POUND, MOVE_SURF); };
} WHEN {
TURN {
MOVE(player, MOVE_GRUDGE);
MOVE(opponent, MOVE_SCRATCH);
SEND_OUT(player, 1);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_GRUDGE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent);
MESSAGE("Wobbuffet fainted!");
} THEN {
EXPECT_GT(opponent->pp[0], 0);
EXPECT_EQ(opponent->pp[1], 0);
EXPECT_GT(opponent->pp[2], 0);
EXPECT_GT(opponent->pp[3], 0);
}
}
SINGLE_BATTLE_TEST("Grudge does not depletes pp of a z-move")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Moves(MOVE_CELEBRATE, MOVE_SCRATCH, MOVE_POUND, MOVE_SURF); };
} WHEN {
TURN {
MOVE(player, MOVE_GRUDGE);
MOVE(opponent, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE);
SEND_OUT(player, 1);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_GRUDGE, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, opponent);
MESSAGE("Wobbuffet fainted!");
} THEN {
EXPECT_GT(opponent->pp[0], 0);
EXPECT_GT(opponent->pp[1], 0);
EXPECT_GT(opponent->pp[2], 0);
EXPECT_GT(opponent->pp[3], 0);
}
}