mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
Fix Emergency Exit timing
This is a really hacky implementation of Emergency Exit, but Emergency Exit itself is a huge mess on cart, too. Our previous implementation: - activated Emergency Exit at AfterMoveSecondary timing for move damage - activated Emergency Exit immediately after dealing any other damage This new one: - activates Emergency Exit only in three situations: - right after AfterMoveSecondary timing, for move damage - right after DamagingHit timing, for DamagingHit residual damage (Rough Skin, Iron Barbs, Rocky Helmet) - right after the switch update, for switch-hazard residual damage (Stealth Rock, Spikes) - does not otherwise activate (so Substitute, Hail, Toxic, etc no longer activate Emergency Exit) This should much accurately simulate Emergency Exit behavior, including most famously timing it after healing berries after hazards, as documented in: https://www.smogon.com/forums/threads/pokemon-sun-moon-battle-mechanics-research.3586701/#post-7075354 Fixes #6309
This commit is contained in:
parent
5d44d642d7
commit
3546cde82c
|
|
@ -891,17 +891,15 @@ let BattleAbilities = {
|
|||
"emergencyexit": {
|
||||
desc: "When this Pokemon has more than 1/2 its maximum HP and takes damage bringing it to 1/2 or less of its maximum HP, it immediately switches out to a chosen ally. This effect applies after all hits from a multi-hit move; Sheer Force prevents it from activating if the move has a secondary effect. This effect applies to both direct and indirect damage, except Curse and Substitute on use, Belly Drum, Pain Split, and confusion damage.",
|
||||
shortDesc: "This Pokemon switches out when it reaches 1/2 or less of its maximum HP.",
|
||||
onAfterMoveSecondary(target, source, move) {
|
||||
if (!source || source === target || !target.hp || !move.totalDamage) return;
|
||||
const lastAttackedBy = target.getLastAttackedBy();
|
||||
if (!lastAttackedBy) return;
|
||||
const damage = move.multihit ? move.totalDamage : lastAttackedBy.damage;
|
||||
if (target.hp <= target.maxhp / 2 && target.hp + damage > target.maxhp / 2) {
|
||||
if (!this.canSwitch(target.side) || target.forceSwitchFlag || target.switchFlag) return;
|
||||
target.switchFlag = true;
|
||||
source.switchFlag = false;
|
||||
this.add('-activate', target, 'ability: Emergency Exit');
|
||||
onEmergencyExit(target) {
|
||||
if (!this.canSwitch(target.side) || target.forceSwitchFlag || target.switchFlag) return;
|
||||
for (const side of this.sides) {
|
||||
for (const active of side.active) {
|
||||
active.switchFlag = false;
|
||||
}
|
||||
}
|
||||
target.switchFlag = true;
|
||||
this.add('-activate', target, 'ability: Emergency Exit');
|
||||
},
|
||||
id: "emergencyexit",
|
||||
name: "Emergency Exit",
|
||||
|
|
@ -4589,17 +4587,15 @@ let BattleAbilities = {
|
|||
"wimpout": {
|
||||
desc: "When this Pokemon has more than 1/2 its maximum HP and takes damage bringing it to 1/2 or less of its maximum HP, it immediately switches out to a chosen ally. This effect applies after all hits from a multi-hit move; Sheer Force prevents it from activating if the move has a secondary effect. This effect applies to both direct and indirect damage, except Curse and Substitute on use, Belly Drum, Pain Split, and confusion damage.",
|
||||
shortDesc: "This Pokemon switches out when it reaches 1/2 or less of its maximum HP.",
|
||||
onAfterMoveSecondary(target, source, move) {
|
||||
if (!source || source === target || !target.hp || !move.totalDamage) return;
|
||||
const lastAttackedBy = target.getLastAttackedBy();
|
||||
if (!lastAttackedBy) return;
|
||||
const damage = move.multihit ? move.totalDamage : lastAttackedBy.damage;
|
||||
if (target.hp <= target.maxhp / 2 && target.hp + damage > target.maxhp / 2) {
|
||||
if (!this.canSwitch(target.side) || target.forceSwitchFlag || target.switchFlag) return;
|
||||
target.switchFlag = true;
|
||||
source.switchFlag = false;
|
||||
this.add('-activate', target, 'ability: Wimp Out');
|
||||
onEmergencyExit(target) {
|
||||
if (!this.canSwitch(target.side) || target.forceSwitchFlag || target.switchFlag) return;
|
||||
for (const side of this.sides) {
|
||||
for (const active of side.active) {
|
||||
active.switchFlag = false;
|
||||
}
|
||||
}
|
||||
target.switchFlag = true;
|
||||
this.add('-activate', target, 'ability: Wimp Out');
|
||||
},
|
||||
id: "wimpout",
|
||||
name: "Wimp Out",
|
||||
|
|
|
|||
|
|
@ -708,6 +708,15 @@ let BattleScripts = {
|
|||
// @ts-ignore
|
||||
this.afterMoveSecondaryEvent(targetsCopy.filter(val => !!val), pokemon, move);
|
||||
|
||||
if (!move.negateSecondary && !(move.hasSheerForce && pokemon.hasAbility('sheerforce'))) {
|
||||
for (let i = 0; i < damage.length; i++) {
|
||||
const curDamage = damage[i];
|
||||
if (typeof curDamage === 'number' && targets[i].hp <= targets[i].maxhp / 2 && targets[i].hp + curDamage > targets[i].maxhp / 2) {
|
||||
this.runEvent('EmergencyExit', targets[i], pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return damage;
|
||||
},
|
||||
spreadMoveHit(targets, pokemon, moveOrMoveName, moveData, isSecondary, isSelf) {
|
||||
|
|
@ -801,6 +810,7 @@ let BattleScripts = {
|
|||
damagedDamage.push(damage[i]);
|
||||
}
|
||||
}
|
||||
const pokemonOriginalHP = pokemon.hp;
|
||||
if (damagedDamage.length) {
|
||||
this.runEvent('DamagingHit', damagedTargets, pokemon, move, damagedDamage);
|
||||
if (moveData.onAfterHit) {
|
||||
|
|
@ -808,6 +818,9 @@ let BattleScripts = {
|
|||
this.singleEvent('AfterHit', moveData, {}, target, pokemon, move);
|
||||
}
|
||||
}
|
||||
if (pokemon.hp <= pokemon.maxhp / 2 && pokemonOriginalHP > pokemon.maxhp / 2) {
|
||||
this.runEvent('EmergencyExit', pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
return [damage, targets];
|
||||
|
|
|
|||
|
|
@ -2608,6 +2608,7 @@ export class Battle {
|
|||
}
|
||||
|
||||
runAction(action: Actions.Action) {
|
||||
const pokemonOriginalHP = action.pokemon?.hp;
|
||||
// returns whether or not we ended in a callback
|
||||
switch (action.choice) {
|
||||
case 'start': {
|
||||
|
|
@ -2835,6 +2836,13 @@ export class Battle {
|
|||
}
|
||||
|
||||
this.eachEvent('Update');
|
||||
|
||||
if (action.choice === 'runSwitch') {
|
||||
if (action.pokemon.hp <= action.pokemon.maxhp / 2 && pokemonOriginalHP! > action.pokemon.maxhp / 2) {
|
||||
this.runEvent('EmergencyExit', action.pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.gen >= 8 && this.queue.length && this.queue[0].choice === 'move') {
|
||||
// In gen 8, speed is updated dynamically so update the queue's speed properties and sort it.
|
||||
this.updateSpeed();
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ interface PureEffectEventMethods {
|
|||
|
||||
interface EventMethods {
|
||||
onDamagingHit?: (this: Battle, damage: number, target: Pokemon, source: Pokemon, move: ActiveMove) => void
|
||||
onEmergencyExit?: (this: Battle, pokemon: Pokemon) => void
|
||||
onAfterEachBoost?: (this: Battle, boost: SparseBoostsTable, target: Pokemon, source: Pokemon) => void
|
||||
onAfterHit?: MoveEventMethods['onAfterHit']
|
||||
onAfterSetStatus?: (this: Battle, status: PureEffect, target: Pokemon, source: Pokemon, effect: Effect) => void
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ describe(`Emergency Exit`, function () {
|
|||
assert.strictEqual(battle.requestState, 'switch');
|
||||
});
|
||||
|
||||
it(`should not request switch-out if first healed by berry`, function () {
|
||||
it(`should not request switch-out if attacked and healed by berry`, function () {
|
||||
battle = common.createBattle([
|
||||
[{species: "Golisopod", ability: 'emergencyexit', moves: ['sleeptalk'], item: 'sitrusberry', ivs: EMPTY_IVS}, {species: "Clefable", ability: 'Unaware', moves: ['metronome']}],
|
||||
[{species: "Raticate", ability: 'guts', moves: ['superfang']}],
|
||||
|
|
@ -32,6 +32,20 @@ describe(`Emergency Exit`, function () {
|
|||
assert.strictEqual(battle.requestState, 'move');
|
||||
});
|
||||
|
||||
it(`should not request switch-out after taking residual damage and getting healed by berry`, function () {
|
||||
battle = common.createBattle([
|
||||
[{species: "Golisopod", ability: 'emergencyexit', moves: ['uturn', 'sleeptalk'], item: 'sitrusberry'}, {species: "Magikarp", ability: 'swiftswim', moves: ['splash']}],
|
||||
[{species: "Ferrothorn", ability: 'ironbarbs', moves: ['stealthrock', 'spikes', 'protect']}],
|
||||
]);
|
||||
battle.makeChoices('move uturn', 'move stealthrock');
|
||||
battle.makeChoices('switch 2', '');
|
||||
battle.makeChoices('move splash', 'move spikes');
|
||||
battle.makeChoices('move splash', 'move spikes');
|
||||
battle.makeChoices('move splash', 'move spikes');
|
||||
battle.makeChoices('switch 2', 'move protect');
|
||||
assert.strictEqual(battle.requestState, 'move');
|
||||
});
|
||||
|
||||
it(`should not request switch-out on usage of Substitute`, function () {
|
||||
battle = common.createBattle([
|
||||
[{species: "Golisopod", ability: 'emergencyexit', moves: ['substitute'], ivs: EMPTY_IVS}, {species: "Clefable", ability: 'Unaware', moves: ['metronome']}],
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user