Emergency Exit on hazards activation + fix end of turn activation (#8075)
This commit is contained in:
parent
44dc720260
commit
9d06a4fb55
@ -583,8 +583,9 @@ struct BattlerState
|
||||
u32 stompingTantrumTimer:2;
|
||||
u32 canPickupItem:1;
|
||||
u32 ateBoost:1;
|
||||
u32 wasAboveHalfHp:1; // For Berserk, Emergency Exit, Wimp Out and Anger Shell.
|
||||
u32 commanderSpecies:11;
|
||||
u32 padding:5;
|
||||
u32 padding:4;
|
||||
// End of Word
|
||||
};
|
||||
|
||||
@ -714,7 +715,6 @@ struct BattleStruct
|
||||
u8 stolenStats[NUM_BATTLE_STATS]; // hp byte is used for which stats to raise, other inform about by how many stages
|
||||
u8 lastMoveTarget[MAX_BATTLERS_COUNT]; // The last target on which each mon used a move, for the sake of Instruct
|
||||
enum Ability tracedAbility[MAX_BATTLERS_COUNT];
|
||||
u16 hpBefore[MAX_BATTLERS_COUNT]; // Hp of battlers before using a move. For Berserk and Anger Shell.
|
||||
struct Illusion illusion[MAX_BATTLERS_COUNT];
|
||||
u8 soulheartBattlerId;
|
||||
u8 friskedBattler; // Frisk needs to identify 2 battlers in double battles.
|
||||
|
||||
@ -75,6 +75,7 @@ bool32 IsMoveAffectedByParentalBond(u32 move, u32 battler);
|
||||
void SaveBattlerTarget(u32 battler);
|
||||
void SaveBattlerAttacker(u32 battler);
|
||||
bool32 CanBurnHitThaw(u16 move);
|
||||
bool32 EmergencyExitCanBeTriggered(u32 battler);
|
||||
|
||||
extern void (*const gBattleScriptingCommandsTable[])(void);
|
||||
extern const struct StatFractions gAccuracyStageRatios[];
|
||||
|
||||
@ -170,7 +170,7 @@ static bool32 HandleEndTurnVarious(u32 battler)
|
||||
if (gBattleMons[i].volatiles.laserFocus && gDisableStructs[i].laserFocusTimer == gBattleTurnCounter)
|
||||
gBattleMons[i].volatiles.laserFocus = FALSE;
|
||||
|
||||
gBattleStruct->hpBefore[i] = gBattleMons[i].hp;
|
||||
gBattleStruct->battlerState[i].wasAboveHalfHp = gBattleMons[i].hp > gBattleMons[i].maxHP / 2;
|
||||
}
|
||||
|
||||
if (gBattleStruct->incrementEchoedVoice)
|
||||
@ -289,27 +289,17 @@ static bool32 HandleEndTurnEmergencyExit(u32 battler)
|
||||
|
||||
gBattleStruct->turnEffectsBattlerId++;
|
||||
|
||||
if (ability == ABILITY_EMERGENCY_EXIT || ability == ABILITY_WIMP_OUT)
|
||||
if (EmergencyExitCanBeTriggered(battler))
|
||||
{
|
||||
u32 cutoff = gBattleMons[battler].maxHP / 2;
|
||||
bool32 HadMoreThanHalfHpNowDoesnt = gBattleStruct->hpBefore[battler] > cutoff && gBattleMons[battler].hp <= cutoff;
|
||||
gBattlerAbility = battler;
|
||||
gLastUsedAbility = ability;
|
||||
|
||||
if (HadMoreThanHalfHpNowDoesnt
|
||||
&& IsBattlerAlive(battler)
|
||||
&& (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER))
|
||||
&& !(gBattleTypeFlags & BATTLE_TYPE_ARENA)
|
||||
&& gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP) // Not currently held by Sky Drop
|
||||
{
|
||||
gBattlerAbility = battler;
|
||||
gLastUsedAbility = ability;
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
|
||||
BattleScriptExecute(BattleScript_EmergencyExitEnd2);
|
||||
else
|
||||
BattleScriptExecute(BattleScript_EmergencyExitWildEnd2);
|
||||
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
|
||||
BattleScriptExecute(BattleScript_EmergencyExitEnd2);
|
||||
else
|
||||
BattleScriptExecute(BattleScript_EmergencyExitWildEnd2);
|
||||
|
||||
effect = TRUE;
|
||||
}
|
||||
effect = TRUE;
|
||||
}
|
||||
|
||||
return effect;
|
||||
|
||||
@ -3237,6 +3237,7 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy)
|
||||
gBattleStruct->battlerState[battler].stompingTantrumTimer = 0;
|
||||
gBattleStruct->palaceFlags &= ~(1u << battler);
|
||||
gBattleStruct->battlerState[battler].canPickupItem = FALSE;
|
||||
gBattleStruct->battlerState[battler].wasAboveHalfHp = gBattleMons[battler].hp > gBattleMons[battler].maxHP / 2;
|
||||
gBattleStruct->hazardsCounter = 0;
|
||||
gDisableStructs[battler].hazardsDone = FALSE;
|
||||
gSpecialStatuses[battler].switchInItemDone = FALSE;
|
||||
@ -5144,6 +5145,7 @@ static void TurnValuesCleanUp(bool8 var0)
|
||||
gBattleMons[i].volatiles.recharge = FALSE;
|
||||
}
|
||||
gBattleStruct->battlerState[i].canPickupItem = FALSE;
|
||||
gBattleStruct->battlerState[i].wasAboveHalfHp = FALSE;
|
||||
}
|
||||
|
||||
if (gDisableStructs[i].substituteHP == 0)
|
||||
|
||||
@ -1093,8 +1093,7 @@ bool32 EmergencyExitCanBeTriggered(u32 battler)
|
||||
if (ability != ABILITY_EMERGENCY_EXIT && ability != ABILITY_WIMP_OUT)
|
||||
return FALSE;
|
||||
|
||||
if (IsBattlerTurnDamaged(battler)
|
||||
&& IsBattlerAlive(battler)
|
||||
if (IsBattlerAlive(battler)
|
||||
&& HadMoreThanHalfHpNowDoesnt(battler)
|
||||
&& (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER))
|
||||
&& !(gBattleTypeFlags & BATTLE_TYPE_ARENA)
|
||||
@ -2412,6 +2411,8 @@ static void Cmd_datahpupdate(void)
|
||||
MoveDamageDataHpUpdate(battler, cmd->battler, cmd->nextInstr);
|
||||
break;
|
||||
}
|
||||
if (gBattleMons[battler].hp > gBattleMons[battler].maxHP / 2)
|
||||
gBattleStruct->battlerState[battler].wasAboveHalfHp = TRUE;
|
||||
|
||||
}
|
||||
|
||||
@ -6659,13 +6660,13 @@ static void Cmd_moveend(void)
|
||||
case MOVEEND_EMERGENCY_EXIT: // Special case, because moves hitting multiple opponents stop after switching out
|
||||
{
|
||||
// Because sorting the battlers by speed takes lots of cycles,
|
||||
// we check if EE can be activated and cound how many.
|
||||
// we check if EE can be activated and count how many.
|
||||
u32 numEmergencyExitBattlers = 0;
|
||||
u32 emergencyExitBattlers = 0;
|
||||
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
{
|
||||
if (EmergencyExitCanBeTriggered(i))
|
||||
if (IsBattlerTurnDamaged(i) && EmergencyExitCanBeTriggered(i))
|
||||
{
|
||||
emergencyExitBattlers |= 1u << i;
|
||||
numEmergencyExitBattlers++;
|
||||
@ -7106,7 +7107,7 @@ static void Cmd_getswitchedmondata(void)
|
||||
|
||||
if (TESTING
|
||||
&& gBattlerPartyIndexes[battler] == gBattleStruct->monToSwitchIntoId[battler]
|
||||
&& gBattleStruct->hpBefore[battler] != 0) // battler is alive
|
||||
&& IsBattlerAlive(battler))
|
||||
Test_ExitWithResult(TEST_RESULT_ERROR, 0, ":L:%s:%d: battler is trying to switch to themself", __FILE__, __LINE__);
|
||||
|
||||
gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler];
|
||||
@ -7826,6 +7827,16 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler)
|
||||
gBattleStruct->battlerState[battler].storedLunarDance = FALSE;
|
||||
}
|
||||
}
|
||||
else if (EmergencyExitCanBeTriggered(battler))
|
||||
{
|
||||
gBattleScripting.battler = gBattlerAbility = battler;
|
||||
gSpecialStatuses[battler].switchInItemDone = FALSE;
|
||||
gBattleStruct->battlerState[battler].forcedSwitch = FALSE;
|
||||
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
|
||||
BattleScriptCall(BattleScript_EmergencyExit);
|
||||
else
|
||||
BattleScriptCall(BattleScript_EmergencyExitWild);
|
||||
}
|
||||
else if (!gDisableStructs[battler].hazardsDone)
|
||||
{
|
||||
TryHazardsOnSwitchIn(battler, side, gBattleStruct->hazardsQueue[side][gBattleStruct->hazardsCounter]);
|
||||
@ -7845,7 +7856,6 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler)
|
||||
gBattleScripting.battler = battler;
|
||||
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_Z_HP_TRAP;
|
||||
BattleScriptCall(BattleScript_HealReplacementZMove);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -7916,8 +7926,8 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler)
|
||||
}
|
||||
|
||||
gSpecialStatuses[battler].switchInItemDone = FALSE;
|
||||
gDisableStructs[battler].hazardsDone = FALSE;
|
||||
gBattleStruct->battlerState[battler].forcedSwitch = FALSE;
|
||||
gBattleStruct->battlerState[battler].wasAboveHalfHp = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
@ -589,7 +589,7 @@ void HandleAction_UseMove(void)
|
||||
BattleArena_AddMindPoints(gBattlerAttacker);
|
||||
|
||||
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
||||
gBattleStruct->hpBefore[i] = gBattleMons[i].hp;
|
||||
gBattleStruct->battlerState[i].wasAboveHalfHp = gBattleMons[i].hp > gBattleMons[i].maxHP / 2;
|
||||
|
||||
gCurrentActionFuncId = B_ACTION_EXEC_SCRIPT;
|
||||
}
|
||||
@ -3322,10 +3322,9 @@ static inline uq4_12_t GetSupremeOverlordModifier(u32 battler)
|
||||
|
||||
bool32 HadMoreThanHalfHpNowDoesnt(u32 battler)
|
||||
{
|
||||
u32 cutoff = gBattleMons[battler].maxHP / 2;
|
||||
// Had more than half of hp before, now has less
|
||||
return gBattleStruct->hpBefore[battler] > cutoff
|
||||
&& gBattleMons[battler].hp <= cutoff;
|
||||
return gBattleStruct->battlerState[battler].wasAboveHalfHp
|
||||
&& gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 2;
|
||||
}
|
||||
|
||||
#define ANIM_STAT_HP 0
|
||||
|
||||
@ -81,6 +81,23 @@ SINGLE_BATTLE_TEST("Emergency Exit activates when taking residual damage and fal
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Emergency Exit activates when healing from under 50% max-hp and taking residual damage to under 50% max-hp - Burn")
|
||||
{
|
||||
// Might fail if users set healing higher than burn damage
|
||||
GIVEN {
|
||||
ASSUME(GetMoveEffect(MOVE_AQUA_RING) == EFFECT_AQUA_RING);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(130); Status1(STATUS1_BURN); };
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(opponent, MOVE_AQUA_RING); SEND_OUT(opponent, 1); }
|
||||
} SCENE {
|
||||
HP_BAR(opponent);
|
||||
HP_BAR(opponent);
|
||||
ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp - Weather")
|
||||
{
|
||||
GIVEN {
|
||||
@ -95,6 +112,24 @@ SINGLE_BATTLE_TEST("Emergency Exit activates when taking residual damage and fal
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Emergency Exit activates when healing from under 50% max-hp and taking residual damage to under 50% max-hp - Sticky Barb")
|
||||
{
|
||||
// Might fail if users set healing higher than sticky barb damage
|
||||
GIVEN {
|
||||
ASSUME(GetMoveEffect(MOVE_AQUA_RING) == EFFECT_AQUA_RING);
|
||||
ASSUME(GetItemHoldEffect(ITEM_STICKY_BARB) == HOLD_EFFECT_STICKY_BARB);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(130); Item(ITEM_STICKY_BARB); };
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(opponent, MOVE_AQUA_RING); SEND_OUT(opponent, 1); }
|
||||
} SCENE {
|
||||
HP_BAR(opponent);
|
||||
HP_BAR(opponent);
|
||||
ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp - Salt Cure")
|
||||
{
|
||||
GIVEN {
|
||||
|
||||
@ -61,3 +61,113 @@ SINGLE_BATTLE_TEST("Hazards are applied correctly after a battler faints")
|
||||
MESSAGE("Pointed stones dug into Wynaut!");
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Toxic Spikes can be removed after fainting to other hazards")
|
||||
{
|
||||
KNOWN_FAILING; // tryfaintmon changes something that doesn't allow other switch-in effects on the battler
|
||||
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WYNAUT);
|
||||
PLAYER(SPECIES_GRIMER) { HP(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); }
|
||||
TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); }
|
||||
TURN { MOVE(opponent, MOVE_STICKY_WEB); }
|
||||
TURN { MOVE(opponent, MOVE_SPIKES); }
|
||||
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); SWITCH(player, 1); SEND_OUT(player, 0); }
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponent);
|
||||
MESSAGE("Pointed stones dug into Grimer!");
|
||||
MESSAGE("Grimer fainted!");
|
||||
MESSAGE("The poison spikes disappeared from the ground around your team!");
|
||||
NONE_OF {
|
||||
MESSAGE("Grimer was caught in a sticky web!");
|
||||
MESSAGE("Grimer was hurt by the spikes!");
|
||||
}
|
||||
} THEN {
|
||||
EXPECT_EQ(gBattleStruct->hazardsQueue[0][0], HAZARDS_STEALTH_ROCK);
|
||||
EXPECT_EQ(gBattleStruct->hazardsQueue[0][1], HAZARDS_STICKY_WEB);
|
||||
EXPECT_EQ(gBattleStruct->hazardsQueue[0][2], HAZARDS_SPIKES);
|
||||
EXPECT_EQ(gBattleStruct->hazardsQueue[0][3], HAZARDS_NONE);
|
||||
EXPECT_EQ(gBattleStruct->hazardsQueue[0][4], HAZARDS_NONE);
|
||||
EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Hazards can trigger Emergency Exit and other hazards don't activate")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
PLAYER(SPECIES_GOLISOPOD) { HP(105); MaxHP(200); Ability(ABILITY_EMERGENCY_EXIT); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); }
|
||||
TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); }
|
||||
TURN { MOVE(opponent, MOVE_STICKY_WEB); }
|
||||
TURN { MOVE(opponent, MOVE_SPIKES); }
|
||||
TURN { MOVE(opponent, MOVE_STEALTH_ROCK); SWITCH(player, 1); SEND_OUT(player, 0); }
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponent);
|
||||
MESSAGE("Pointed stones dug into Golisopod!");
|
||||
ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT);
|
||||
NONE_OF {
|
||||
MESSAGE("Golisopod was poisoned!");
|
||||
MESSAGE("Golisopod was caught in a sticky web!");
|
||||
MESSAGE("Golisopod was hurt by the spikes!");
|
||||
}
|
||||
MESSAGE("Pointed stones dug into Wobbuffet!");
|
||||
MESSAGE("Wobbuffet was poisoned!");
|
||||
MESSAGE("Wobbuffet was caught in a sticky web!");
|
||||
MESSAGE("Wobbuffet was hurt by the spikes!");
|
||||
NOT MESSAGE("Pointed stones dug into Wobbuffet!"); // Because the previous switch in effects instruction is still kept
|
||||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Hazards can trigger Emergency Exit and hazards still activate for other battlers")
|
||||
{
|
||||
GIVEN {
|
||||
ASSUME(GetMoveEffect(MOVE_FINAL_GAMBIT) == EFFECT_FINAL_GAMBIT);
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
|
||||
PLAYER(SPECIES_GOLISOPOD) { HP(105); MaxHP(200); Ability(ABILITY_EMERGENCY_EXIT); }
|
||||
PLAYER(SPECIES_WYNAUT);
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WYNAUT);
|
||||
} WHEN {
|
||||
TURN { MOVE(opponentLeft, MOVE_STEALTH_ROCK); MOVE(opponentRight, MOVE_TOXIC_SPIKES); }
|
||||
TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(opponentRight, MOVE_SPIKES); }
|
||||
TURN { MOVE(playerLeft, MOVE_FINAL_GAMBIT, target: opponentRight);
|
||||
MOVE(playerRight, MOVE_FINAL_GAMBIT, target: opponentRight);
|
||||
SEND_OUT(playerLeft, 2);
|
||||
SEND_OUT(playerRight, 3);
|
||||
SEND_OUT(playerLeft, 4); }
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponentLeft);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponentRight);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponentRight);
|
||||
MESSAGE("Pointed stones dug into Golisopod!");
|
||||
ABILITY_POPUP(playerLeft, ABILITY_EMERGENCY_EXIT);
|
||||
NONE_OF {
|
||||
MESSAGE("Golisopod was poisoned!");
|
||||
MESSAGE("Golisopod was caught in a sticky web!");
|
||||
MESSAGE("Golisopod was hurt by the spikes!");
|
||||
}
|
||||
MESSAGE("Pointed stones dug into Wobbuffet!");
|
||||
MESSAGE("Wobbuffet was poisoned!");
|
||||
MESSAGE("Wobbuffet was caught in a sticky web!");
|
||||
MESSAGE("Wobbuffet was hurt by the spikes!");
|
||||
MESSAGE("Pointed stones dug into Wynaut!");
|
||||
MESSAGE("Wynaut was poisoned!");
|
||||
MESSAGE("Wynaut was caught in a sticky web!");
|
||||
MESSAGE("Wynaut was hurt by the spikes!");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user