Adds some Snatch interactions, fixes for Dragon Darts, Trace, Primal Reversion, Protosynthesis/Quark Drive (#5430)

* Fixes Electrified Dragon Darts sometimes targeting battlers with absorbing abilities (Volt Absorb, Motor Drive)

* Add Snatch interactions with Dancer, Swallow

* Trace fix + cleanup

* Simplify Quash

* Fixes multiple mons with Primal Reversion causing only one Primal Reversion, add tests

* Fix Booster Energy Ability Popup

* Accidentally removed healing from Swallow

* More Trace cleanup
This commit is contained in:
PhallenTree 2024-09-24 17:40:44 +01:00 committed by GitHub
parent 55086586c5
commit e67d5a23ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 313 additions and 55 deletions

View file

@ -6961,12 +6961,12 @@ BattleScript_WishMegaEvolution::
BattleScript_PrimalReversion:: BattleScript_PrimalReversion::
call BattleScript_PrimalReversionRet call BattleScript_PrimalReversionRet
end2 end3
BattleScript_PrimalReversionRestoreAttacker:: BattleScript_PrimalReversionRestoreAttacker::
call BattleScript_PrimalReversionRet call BattleScript_PrimalReversionRet
copybyte gBattlerAttacker, sSAVED_BATTLER copybyte gBattlerAttacker, sSAVED_BATTLER
end2 end3
BattleScript_PrimalReversionRet:: BattleScript_PrimalReversionRet::
flushtextbox flushtextbox
@ -7675,15 +7675,11 @@ BattleScript_EmergencyExitWildNoPopUp::
BattleScript_TraceActivates:: BattleScript_TraceActivates::
pause B_WAIT_TIME_SHORT pause B_WAIT_TIME_SHORT
call BattleScript_AbilityPopUp call BattleScript_AbilityPopUpScripting
printstring STRINGID_PKMNTRACED printstring STRINGID_PKMNTRACED
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
settracedability BS_SCRIPTING settracedability BS_SCRIPTING
switchinabilities BS_SCRIPTING switchinabilities BS_SCRIPTING
return
BattleScript_TraceActivatesEnd3::
call BattleScript_TraceActivates
end3 end3
BattleScript_ReceiverActivates:: BattleScript_ReceiverActivates::
@ -10011,7 +10007,7 @@ BattleScript_BerserkGeneRet_End:
BattleScript_BoosterEnergyEnd2:: BattleScript_BoosterEnergyEnd2::
playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, sB_ANIM_ARG1 playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, sB_ANIM_ARG1
call BattleScript_AbilityPopUpTarget call BattleScript_AbilityPopUpScripting
printstring STRINGID_BOOSTERENERGYACTIVATES printstring STRINGID_BOOSTERENERGYACTIVATES
waitmessage B_WAIT_TIME_MED waitmessage B_WAIT_TIME_MED
printstring STRINGID_STATWASHEIGHTENED printstring STRINGID_STATWASHEIGHTENED

View file

@ -93,7 +93,7 @@ struct ResourceFlags
#define RESOURCE_FLAG_ROOST 0x2 #define RESOURCE_FLAG_ROOST 0x2
#define RESOURCE_FLAG_UNBURDEN 0x4 #define RESOURCE_FLAG_UNBURDEN 0x4
#define RESOURCE_FLAG_UNUSED 0x8 #define RESOURCE_FLAG_UNUSED 0x8
#define RESOURCE_FLAG_TRACED 0x10 #define RESOURCE_FLAG_UNUSED_2 0x10
#define RESOURCE_FLAG_EMERGENCY_EXIT 0x20 #define RESOURCE_FLAG_EMERGENCY_EXIT 0x20
#define RESOURCE_FLAG_NEUTRALIZING_GAS 0x40 #define RESOURCE_FLAG_NEUTRALIZING_GAS 0x40
#define RESOURCE_FLAG_ICE_FACE 0x80 #define RESOURCE_FLAG_ICE_FACE 0x80
@ -751,6 +751,7 @@ struct BattleStruct
u8 blunderPolicy:1; // should blunder policy activate u8 blunderPolicy:1; // should blunder policy activate
u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky
u8 bouncedMoveIsUsed:1; u8 bouncedMoveIsUsed:1;
u8 snatchedMoveIsUsed:1;
u8 descriptionSubmenu:1; // For Move Description window in move selection screen u8 descriptionSubmenu:1; // For Move Description window in move selection screen
u8 ackBallUseBtn:1; // Used for the last used ball feature u8 ackBallUseBtn:1; // Used for the last used ball feature
u8 ballSwapped:1; // Used for the last used ball feature u8 ballSwapped:1; // Used for the last used ball feature

View file

@ -166,7 +166,6 @@ extern const u8 BattleScript_ItemSteal[];
extern const u8 BattleScript_DrizzleActivates[]; extern const u8 BattleScript_DrizzleActivates[];
extern const u8 BattleScript_SpeedBoostActivates[]; extern const u8 BattleScript_SpeedBoostActivates[];
extern const u8 BattleScript_TraceActivates[]; extern const u8 BattleScript_TraceActivates[];
extern const u8 BattleScript_TraceActivatesEnd3[];
extern const u8 BattleScript_RainDishActivates[]; extern const u8 BattleScript_RainDishActivates[];
extern const u8 BattleScript_SandstreamActivates[]; extern const u8 BattleScript_SandstreamActivates[];
extern const u8 BattleScript_ShedSkinActivates[]; extern const u8 BattleScript_ShedSkinActivates[];

View file

@ -256,7 +256,7 @@ bool32 MoveHasAdditionalEffectSelf(u32 move, u32 moveEffect);
bool32 MoveHasAdditionalEffectSelfArg(u32 move, u32 moveEffect, u32 argument); bool32 MoveHasAdditionalEffectSelfArg(u32 move, u32 moveEffect, u32 argument);
bool32 MoveHasChargeTurnAdditionalEffect(u32 move); bool32 MoveHasChargeTurnAdditionalEffect(u32 move);
bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef); bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef);
bool32 TargetFullyImmuneToCurrMove(u32 BattlerAtk, u32 battlerDef); bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef);
bool32 CanBeSlept(u32 battler, u32 ability); bool32 CanBeSlept(u32 battler, u32 ability);
bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 defAbility); bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 defAbility);

View file

@ -1401,6 +1401,7 @@ static void Cmd_attackcanceler(void)
if ((gProtectStructs[gBattlerByTurnOrder[i]].stealMove) && gMovesInfo[gCurrentMove].snatchAffected) if ((gProtectStructs[gBattlerByTurnOrder[i]].stealMove) && gMovesInfo[gCurrentMove].snatchAffected)
{ {
gProtectStructs[gBattlerByTurnOrder[i]].stealMove = FALSE; gProtectStructs[gBattlerByTurnOrder[i]].stealMove = FALSE;
gBattleStruct->snatchedMoveIsUsed = TRUE;
gBattleScripting.battler = gBattlerByTurnOrder[i]; gBattleScripting.battler = gBattlerByTurnOrder[i];
BattleScriptPushCursor(); BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_SnatchedMove; gBattlescriptCurrInstr = BattleScript_SnatchedMove;
@ -6287,7 +6288,7 @@ static void Cmd_moveend(void)
gBattleScripting.moveendState++; gBattleScripting.moveendState++;
break; break;
case MOVEEND_DANCER: // Special case because it's so annoying case MOVEEND_DANCER: // Special case because it's so annoying
if (gMovesInfo[gCurrentMove].danceMove) if (gMovesInfo[gCurrentMove].danceMove && !gBattleStruct->snatchedMoveIsUsed)
{ {
u32 battler, nextDancer = 0; u32 battler, nextDancer = 0;
bool32 hasDancerTriggered = FALSE; bool32 hasDancerTriggered = FALSE;
@ -6431,6 +6432,7 @@ static void Cmd_moveend(void)
gBattleStruct->swapDamageCategory = FALSE; gBattleStruct->swapDamageCategory = FALSE;
gBattleStruct->categoryOverride = FALSE; gBattleStruct->categoryOverride = FALSE;
gBattleStruct->bouncedMoveIsUsed = FALSE; gBattleStruct->bouncedMoveIsUsed = FALSE;
gBattleStruct->snatchedMoveIsUsed = FALSE;
gBattleStruct->enduredDamage = 0; gBattleStruct->enduredDamage = 0;
gBattleStruct->additionalEffectsCounter = 0; gBattleStruct->additionalEffectsCounter = 0;
gBattleStruct->poisonPuppeteerConfusion = FALSE; gBattleStruct->poisonPuppeteerConfusion = FALSE;
@ -11537,7 +11539,7 @@ static void Cmd_stockpiletohpheal(void)
const u8 *failInstr = cmd->failInstr; const u8 *failInstr = cmd->failInstr;
if (gDisableStructs[gBattlerAttacker].stockpileCounter == 0) if (gDisableStructs[gBattlerAttacker].stockpileCounter == 0 && !gBattleStruct->snatchedMoveIsUsed)
{ {
gBattlescriptCurrInstr = failInstr; gBattlescriptCurrInstr = failInstr;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWALLOW_FAILED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWALLOW_FAILED;
@ -11553,14 +11555,22 @@ static void Cmd_stockpiletohpheal(void)
} }
else else
{ {
gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / (1 << (3 - gDisableStructs[gBattlerAttacker].stockpileCounter)); if (gDisableStructs[gBattlerAttacker].stockpileCounter > 0)
{
gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / (1 << (3 - gDisableStructs[gBattlerAttacker].stockpileCounter));
gBattleScripting.animTurn = gDisableStructs[gBattlerAttacker].stockpileCounter;
gBattleStruct->moveEffect2 = MOVE_EFFECT_STOCKPILE_WORE_OFF;
}
else // Snatched move
{
gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4;
gBattleScripting.animTurn = 1;
}
if (gBattleMoveDamage == 0) if (gBattleMoveDamage == 0)
gBattleMoveDamage = 1; gBattleMoveDamage = 1;
gBattleMoveDamage *= -1; gBattleMoveDamage *= -1;
gBattleScripting.animTurn = gDisableStructs[gBattlerAttacker].stockpileCounter;
gBattleStruct->moveEffect2 = MOVE_EFFECT_STOCKPILE_WORE_OFF;
gBattlescriptCurrInstr = cmd->nextInstr; gBattlescriptCurrInstr = cmd->nextInstr;
gBattlerTarget = gBattlerAttacker; gBattlerTarget = gBattlerAttacker;
} }
@ -17122,30 +17132,18 @@ void BS_TryQuash(void)
// If the above condition is not true, it means we are faster than the foe, so we can set the quash bit // If the above condition is not true, it means we are faster than the foe, so we can set the quash bit
gProtectStructs[gBattlerTarget].quash = TRUE; gProtectStructs[gBattlerTarget].quash = TRUE;
if (B_QUASH_TURN_ORDER < GEN_8) // this implementation assumes turn order is correct when using Quash
i = GetBattlerTurnOrderNum(gBattlerTarget);
for (j = i + 1; j < gBattlersCount; j++)
{ {
// Gen 7- config makes target go last so that the order of quash targets is kept for the correct turn order // Gen 7- config makes target go last so that the order of quash targets is kept for the correct turn order
j = GetBattlerTurnOrderNum(gBattlerTarget); // Gen 8+ config alters Turn Order of the target according to speed, dynamic speed should handle the rest
for (i = j + 1; i < gBattlersCount; i++) if (B_QUASH_TURN_ORDER < GEN_8 || GetWhichBattlerFaster(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], FALSE) == -1)
{
SwapTurnOrder(i, j); SwapTurnOrder(i, j);
j++; else
} break;
} i++;
else
{
// Gen 8+ config only alters Turn Order of battlers affected by Quash, dynamic speed should handle the rest
for (i = gCurrentTurnActionNumber + 1; i < gBattlersCount - 1; i++)
{
for (j = i + 1; j < gBattlersCount; j++)
{
u32 battler1 = gBattlerByTurnOrder[i], battler2 = gBattlerByTurnOrder[j];
if ((gProtectStructs[battler1].quash || gProtectStructs[battler2].quash)
&& GetWhichBattlerFaster(battler1, battler2, FALSE) == -1)
SwapTurnOrder(i, j);
}
}
} }
gBattlescriptCurrInstr = cmd->nextInstr; gBattlescriptCurrInstr = cmd->nextInstr;
} }

View file

@ -4368,13 +4368,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
if (gSpecialStatuses[battler].switchInAbilityDone) if (gSpecialStatuses[battler].switchInAbilityDone)
break; break;
if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_TRACED)
break;
side = (BATTLE_OPPOSITE(GetBattlerPosition(battler))) & BIT_SIDE; side = (BATTLE_OPPOSITE(GetBattlerPosition(battler))) & BIT_SIDE;
target1 = GetBattlerAtPosition(side); target1 = GetBattlerAtPosition(side);
target2 = GetBattlerAtPosition(side + BIT_FLANK); target2 = GetBattlerAtPosition(side + BIT_FLANK);
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
{ {
if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0 if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0
@ -4393,11 +4390,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
if (effect != 0) if (effect != 0)
{ {
BattleScriptPushCursorAndCallback(BattleScript_TraceActivatesEnd3); BattleScriptPushCursorAndCallback(BattleScript_TraceActivates);
gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_TRACED;
gBattleStruct->tracedAbility[battler] = gLastUsedAbility = gBattleMons[chosenTarget].ability; gBattleStruct->tracedAbility[battler] = gLastUsedAbility = gBattleMons[chosenTarget].ability;
RecordAbilityBattle(chosenTarget, gLastUsedAbility); // Record the opposing battler has this ability RecordAbilityBattle(chosenTarget, gLastUsedAbility); // Record the opposing battler has this ability
battler = gBattlerAbility = gBattleScripting.battler = battler; gBattlerAbility = battler;
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, chosenTarget, gBattlerPartyIndexes[chosenTarget]) PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, chosenTarget, gBattlerPartyIndexes[chosenTarget])
PREPARE_ABILITY_BUFFER(gBattleTextBuff2, gLastUsedAbility) PREPARE_ABILITY_BUFFER(gBattleTextBuff2, gLastUsedAbility)
@ -5214,7 +5210,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
break; break;
case ABILITY_GOOD_AS_GOLD: case ABILITY_GOOD_AS_GOLD:
if (IS_MOVE_STATUS(gCurrentMove) if (IS_MOVE_STATUS(gCurrentMove)
&& !(moveTarget & MOVE_TARGET_USER)
&& !(moveTarget & MOVE_TARGET_OPPONENTS_FIELD) && !(moveTarget & MOVE_TARGET_OPPONENTS_FIELD)
&& !(moveTarget & MOVE_TARGET_ALL_BATTLERS)) && !(moveTarget & MOVE_TARGET_ALL_BATTLERS))
effect = 3; effect = 3;
@ -6388,14 +6383,14 @@ bool32 TryPrimalReversion(u32 battler)
{ {
if (gBattlerAttacker == battler) if (gBattlerAttacker == battler)
{ {
BattleScriptExecute(BattleScript_PrimalReversion); BattleScriptPushCursorAndCallback(BattleScript_PrimalReversion);
} }
else else
{ {
// edge case for scenarios like a switch-in after activated eject button // edge case for scenarios like a switch-in after activated eject button
gBattleScripting.savedBattler = gBattlerAttacker; gBattleScripting.savedBattler = gBattlerAttacker;
gBattlerAttacker = battler; gBattlerAttacker = battler;
BattleScriptExecute(BattleScript_PrimalReversionRestoreAttacker); BattleScriptPushCursorAndCallback(BattleScript_PrimalReversionRestoreAttacker);
} }
return TRUE; return TRUE;
} }
@ -8706,7 +8701,7 @@ u32 CountBattlerStatIncreases(u32 battler, bool32 countEvasionAcc)
u32 GetMoveTargetCount(u32 move, u32 battlerAtk, u32 battlerDef) u32 GetMoveTargetCount(u32 move, u32 battlerAtk, u32 battlerDef)
{ {
switch (GetBattlerMoveTargetType(gBattlerAttacker, move)) switch (GetBattlerMoveTargetType(battlerAtk, move))
{ {
case MOVE_TARGET_BOTH: case MOVE_TARGET_BOTH:
return !(gAbsentBattlerFlags & gBitTable[battlerDef]) return !(gAbsentBattlerFlags & gBitTable[battlerDef])
@ -11816,19 +11811,19 @@ bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef)
&& battlerDef != BATTLE_PARTNER(battlerAtk)); && battlerDef != BATTLE_PARTNER(battlerAtk));
} }
static inline bool32 DoesCurrentTargetHaveAbilityImmunity(void) static inline bool32 DoesBattlerHaveAbilityImmunity(u32 battlerDef)
{ {
return (AbilityBattleEffects(ABILITYEFFECT_WOULD_BLOCK, gBattlerTarget, 0, 0, 0) return (AbilityBattleEffects(ABILITYEFFECT_WOULD_BLOCK, battlerDef, 0, 0, 0)
|| AbilityBattleEffects(ABILITYEFFECT_WOULD_ABSORB, gBattlerTarget, 0, 0, 0)); || AbilityBattleEffects(ABILITYEFFECT_WOULD_ABSORB, battlerDef, 0, 0, 0));
} }
bool32 TargetFullyImmuneToCurrMove(u32 BattlerAtk, u32 battlerDef) bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef)
{ {
u32 moveType = 0; u32 moveType = 0;
GET_MOVE_TYPE(gCurrentMove, moveType); GET_MOVE_TYPE(gCurrentMove, moveType);
return ((CalcTypeEffectivenessMultiplier(gCurrentMove, moveType, BattlerAtk, battlerDef, GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0)) return ((CalcTypeEffectivenessMultiplier(gCurrentMove, moveType, battlerAtk, battlerDef, GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0))
|| IsBattlerProtected(BattlerAtk, battlerDef, gCurrentMove) || IsBattlerProtected(battlerAtk, battlerDef, gCurrentMove)
|| IsSemiInvulnerable(battlerDef, gCurrentMove) || IsSemiInvulnerable(battlerDef, gCurrentMove)
|| DoesCurrentTargetHaveAbilityImmunity()); || DoesBattlerHaveAbilityImmunity(battlerDef));
} }

View file

@ -146,6 +146,85 @@ SINGLE_BATTLE_TEST("Dancer-called attacks have their type updated")
} }
} }
DOUBLE_BATTLE_TEST("Dancer doesn't trigger on a snatched move")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].danceMove == TRUE);
ASSUME(gMovesInfo[MOVE_SNATCH].effect == EFFECT_SNATCH);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_ORICORIO);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentRight, MOVE_SNATCH); MOVE(playerRight, MOVE_DRAGON_DANCE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNATCH, opponentRight);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
NONE_OF {
ABILITY_POPUP(opponentLeft, ABILITY_DANCER);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
}
}
}
DOUBLE_BATTLE_TEST("Dancer triggers on Instructed dance moves")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].danceMove == TRUE);
ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].instructBanned == FALSE);
ASSUME(gMovesInfo[MOVE_INSTRUCT].effect == EFFECT_INSTRUCT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_ORICORIO);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_DRAGON_DANCE); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
ABILITY_POPUP(opponentLeft, ABILITY_DANCER);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
ABILITY_POPUP(opponentLeft, ABILITY_DANCER);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
}
}
DOUBLE_BATTLE_TEST("Dancer-called move doesn't update move to be Instructed")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].danceMove == TRUE);
ASSUME(gMovesInfo[MOVE_TACKLE].instructBanned == FALSE);
ASSUME(gMovesInfo[MOVE_INSTRUCT].effect == EFFECT_INSTRUCT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_ORICORIO);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); MOVE(playerRight, MOVE_DRAGON_DANCE); MOVE(opponentRight, MOVE_INSTRUCT, target: opponentLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
ABILITY_POPUP(opponentLeft, ABILITY_DANCER);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, opponentRight);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft);
}
}
DOUBLE_BATTLE_TEST("Dancer doesn't call a move that didn't execute due to Powder") DOUBLE_BATTLE_TEST("Dancer doesn't call a move that didn't execute due to Powder")
{ {
GIVEN { GIVEN {

View file

@ -80,6 +80,21 @@ SINGLE_BATTLE_TEST("Trace will copy an opponent's ability whenever it has the ch
} }
} }
SINGLE_BATTLE_TEST("Trace copies opponent's Intimidate and triggers it immediately")
{
GIVEN {
PLAYER(SPECIES_RALTS) { Ability(ABILITY_TRACE); }
OPPONENT(SPECIES_MASQUERAIN) { Ability(ABILITY_INTIMIDATE); }
} WHEN {
TURN { }
} SCENE {
ABILITY_POPUP(player, ABILITY_TRACE);
ABILITY_POPUP(player, ABILITY_INTIMIDATE);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
}
}
DOUBLE_BATTLE_TEST("Trace respects the turn order") DOUBLE_BATTLE_TEST("Trace respects the turn order")
{ {
GIVEN { GIVEN {

View file

@ -234,3 +234,101 @@ SINGLE_BATTLE_TEST("Primal reversion happens immediately if it was brought in by
EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL);
} }
} }
DOUBLE_BATTLE_TEST("Primal reversion triggers for multiple battlers if multiple fainted the previous turn")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_CATERPIE) { HP(1); }
PLAYER(SPECIES_RESHIRAM);
OPPONENT(SPECIES_CATERPIE) { HP(1); }
OPPONENT(SPECIES_CATERPIE) { HP(1); }
OPPONENT(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); }
OPPONENT(SPECIES_GROUDON) { Item(ITEM_RED_ORB); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_EARTHQUAKE);
SEND_OUT(opponentRight, 3);
SEND_OUT(opponentLeft, 2);
SEND_OUT(playerRight, 2); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft);
ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA);
ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND);
}
}
DOUBLE_BATTLE_TEST("Primal reversion triggers for all battlers if multiple fainted the previous turn")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION);
ASSUME(gMovesInfo[MOVE_EXPLOSION].target == MOVE_TARGET_FOES_AND_ALLY);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_CATERPIE) { HP(1); }
PLAYER(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); }
PLAYER(SPECIES_GROUDON) { Item(ITEM_RED_ORB); }
OPPONENT(SPECIES_CATERPIE) { HP(1); }
OPPONENT(SPECIES_CATERPIE) { HP(1); }
OPPONENT(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); }
OPPONENT(SPECIES_GROUDON) { Item(ITEM_RED_ORB); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_EXPLOSION);
SEND_OUT(opponentRight, 3);
SEND_OUT(opponentLeft, 2);
SEND_OUT(playerRight, 3);
SEND_OUT(playerLeft, 2); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft);
ABILITY_POPUP(playerLeft, ABILITY_PRIMORDIAL_SEA);
ABILITY_POPUP(playerRight, ABILITY_DESOLATE_LAND);
ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA);
ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND);
}
}
DOUBLE_BATTLE_TEST("Primal reversion and other switch-in effects trigger for all battlers if multiple fainted the previous turn")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION);
ASSUME(gMovesInfo[MOVE_EXPLOSION].target == MOVE_TARGET_FOES_AND_ALLY);
ASSUME(gMovesInfo[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB);
ASSUME(gMovesInfo[MOVE_SPIKES].effect == EFFECT_SPIKES);
ASSUME(gMovesInfo[MOVE_TOXIC_SPIKES].effect == EFFECT_TOXIC_SPIKES);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_CATERPIE) { HP(1); }
PLAYER(SPECIES_SCRAFTY) { Ability(ABILITY_INTIMIDATE); }
PLAYER(SPECIES_RESHIRAM);
OPPONENT(SPECIES_CATERPIE) { HP(1); }
OPPONENT(SPECIES_CATERPIE) { HP(1); }
OPPONENT(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); }
OPPONENT(SPECIES_GROUDON) { Item(ITEM_RED_ORB); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_STICKY_WEB);
MOVE(opponentLeft, MOVE_SPIKES);
MOVE(playerRight, MOVE_TOXIC_SPIKES); }
TURN { MOVE(playerLeft, MOVE_EXPLOSION);
SEND_OUT(opponentRight, 3);
SEND_OUT(opponentLeft, 2);
SEND_OUT(playerRight, 3);
SEND_OUT(playerLeft, 2); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft);
ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE);
ABILITY_POPUP(playerRight, ABILITY_TURBOBLAZE);
ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA);
ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND);
} THEN {
EXPECT_NE(playerLeft->hp, playerLeft->maxHP);
EXPECT_NE(playerRight->hp, playerRight->maxHP);
EXPECT_EQ(opponentLeft->status1, STATUS1_POISON);
EXPECT_EQ(opponentRight->status1, STATUS1_POISON);
EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1);
EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1);
EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1);
EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1);
}
}

View file

@ -94,6 +94,83 @@ DOUBLE_BATTLE_TEST("Dragon Darts strikes the left ally twice if the target is a
} }
} }
DOUBLE_BATTLE_TEST("Dragon Darts strikes left ally twice if electrified and right ally has Volt Absorb")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_ELECTRIFY].effect == EFFECT_ELECTRIFY);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_VOLT_ABSORB); };
} WHEN {
TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft);
HP_BAR(opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft);
HP_BAR(opponentLeft);
MESSAGE("Hit 2 time(s)!");
}
}
DOUBLE_BATTLE_TEST("Dragon Darts strikes right ally twice if electrified and left ally has Volt Absorb")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_ELECTRIFY].effect == EFFECT_ELECTRIFY);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_VOLT_ABSORB); };
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft);
HP_BAR(opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft);
HP_BAR(opponentRight);
MESSAGE("Hit 2 time(s)!");
}
}
DOUBLE_BATTLE_TEST("Dragon Darts strikes left ally twice if electrified and right ally has Motor Drive")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_ELECTRIFY].effect == EFFECT_ELECTRIFY);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ELECTIVIRE) { Ability(ABILITY_MOTOR_DRIVE); };
} WHEN {
TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft);
HP_BAR(opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft);
HP_BAR(opponentLeft);
MESSAGE("Hit 2 time(s)!");
}
}
DOUBLE_BATTLE_TEST("Dragon Darts strikes right ally twice if electrified and left ally has Motor Drive")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_ELECTRIFY].effect == EFFECT_ELECTRIFY);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ELECTIVIRE) { Ability(ABILITY_MOTOR_DRIVE); };
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft);
HP_BAR(opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft);
HP_BAR(opponentRight);
MESSAGE("Hit 2 time(s)!");
}
}
DOUBLE_BATTLE_TEST("Dragon Darts strikes the ally twice if the target is in a semi-invulnerable turn") DOUBLE_BATTLE_TEST("Dragon Darts strikes the ally twice if the target is in a semi-invulnerable turn")
{ {
GIVEN { GIVEN {