Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul 'SwitchIn' event for more accurate effect resolution order #10766

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
16 changes: 9 additions & 7 deletions config/formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ New sections will be added to the bottom of the specified column.
The column value will be ignored for repeat sections.
*/

import {EffectState} from '../sim/pokemon';

export const Formats: import('../sim/dex-formats').FormatList = [

// S/V Singles
Expand Down Expand Up @@ -632,15 +634,15 @@ export const Formats: import('../sim/dex-formats').FormatList = [
const itemTable = new Set<ID>();
for (const set of team) {
const item = this.dex.items.get(set.item);
if (!item.megaStone && !item.onPrimal && !item.forcedForme?.endsWith('Origin') &&
if (!item.megaStone && !item.isPrimalOrb && !item.forcedForme?.endsWith('Origin') &&
!item.name.startsWith('Rusted') && !item.name.endsWith('Mask')) continue;
const natdex = this.ruleTable.has('standardnatdex');
if (natdex && item.id !== 'ultranecroziumz') continue;
const species = this.dex.species.get(set.species);
if (species.isNonstandard && !this.ruleTable.has(`+pokemontag:${this.toID(species.isNonstandard)}`)) {
return [`${species.baseSpecies} does not exist in gen 9.`];
}
if ((item.itemUser?.includes(species.name) && !item.megaStone && !item.onPrimal) ||
if ((item.itemUser?.includes(species.name) && !item.megaStone && !item.isPrimalOrb) ||
(natdex && species.name.startsWith('Necrozma-') && item.id === 'ultranecroziumz')) {
continue;
}
Expand Down Expand Up @@ -722,7 +724,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
if (!format.getSharedPower) format = this.dex.formats.get('gen9sharedpower');
for (const ability of format.getSharedPower!(pokemon)) {
const effect = 'ability:' + ability;
pokemon.volatiles[effect] = {id: this.toID(effect), target: pokemon};
pokemon.volatiles[effect] = new EffectState({id: this.toID(effect), target: pokemon}, this);
if (!pokemon.m.abils) pokemon.m.abils = [];
if (!pokemon.m.abils.includes(effect)) pokemon.m.abils.push(effect);
}
Expand Down Expand Up @@ -1456,14 +1458,14 @@ export const Formats: import('../sim/dex-formats').FormatList = [
if (!pokemon.m.innate && !BAD_ABILITIES.includes(this.toID(ally.ability))) {
pokemon.m.innate = 'ability:' + ally.ability;
if (!ngas || ally.getAbility().flags['cantsuppress'] || pokemon.hasItem('Ability Shield')) {
pokemon.volatiles[pokemon.m.innate] = {id: pokemon.m.innate, target: pokemon};
pokemon.volatiles[pokemon.m.innate] = new EffectState({id: pokemon.m.innate, target: pokemon}, this);
pokemon.m.startVolatile = true;
}
}
if (!ally.m.innate && !BAD_ABILITIES.includes(this.toID(pokemon.ability))) {
ally.m.innate = 'ability:' + pokemon.ability;
if (!ngas || pokemon.getAbility().flags['cantsuppress'] || ally.hasItem('Ability Shield')) {
ally.volatiles[ally.m.innate] = {id: ally.m.innate, target: ally};
ally.volatiles[ally.m.innate] = new EffectState({id: ally.m.innate, target: ally}, this);
ally.m.startVolatile = true;
}
}
Expand Down Expand Up @@ -1761,7 +1763,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
for (const item of format.getSharedItems!(pokemon)) {
if (pokemon.m.sharedItemsUsed.includes(item)) continue;
const effect = 'item:' + item;
pokemon.volatiles[effect] = {id: this.toID(effect), target: pokemon};
pokemon.volatiles[effect] = new EffectState({id: this.toID(effect), target: pokemon}, this);
if (!pokemon.m.items) pokemon.m.items = [];
if (!pokemon.m.items.includes(effect)) pokemon.m.items.push(effect);
}
Expand Down Expand Up @@ -2560,7 +2562,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
if (!format.getSharedPower) format = this.dex.formats.get('gen9sharedpower');
for (const ability of format.getSharedPower!(pokemon)) {
const effect = 'ability:' + ability;
pokemon.volatiles[effect] = {id: this.toID(effect), target: pokemon};
pokemon.volatiles[effect] = new EffectState({id: this.toID(effect), target: pokemon}, this);
if (!pokemon.m.abils) pokemon.m.abils = [];
if (!pokemon.m.abils.includes(effect)) pokemon.m.abils.push(effect);
}
Expand Down
123 changes: 65 additions & 58 deletions data/abilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,12 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
},
airlock: {
onSwitchIn(pokemon) {
this.effectState.switchingIn = true;
// Air Lock does not activate when Skill Swapped or when Neutralizing Gas leaves the field
this.add('-ability', pokemon, 'Air Lock');
((this.effect as any).onStart as (p: Pokemon) => void).call(this, pokemon);
},
onStart(pokemon) {
// Air Lock does not activate when Skill Swapped or when Neutralizing Gas leaves the field
pokemon.abilityState.ending = false; // Clear the ending flag
if (this.effectState.switchingIn) {
this.add('-ability', pokemon, 'Air Lock');
this.effectState.switchingIn = false;
}
this.eachEvent('WeatherChange', this.effect);
},
onEnd(pokemon) {
Expand Down Expand Up @@ -255,11 +252,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 165,
},
asoneglastrier: {
onPreStart(pokemon) {
this.add('-ability', pokemon, 'As One');
this.add('-ability', pokemon, 'Unnerve');
this.effectState.unnerved = true;
},
onSwitchInPriority: 1,
onStart(pokemon) {
if (this.effectState.unnerved) return;
this.add('-ability', pokemon, 'As One');
Expand All @@ -283,11 +276,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 266,
},
asonespectrier: {
onPreStart(pokemon) {
this.add('-ability', pokemon, 'As One');
this.add('-ability', pokemon, 'Unnerve');
this.effectState.unnerved = true;
},
onSwitchInPriority: 1,
onStart(pokemon) {
if (this.effectState.unnerved) return;
this.add('-ability', pokemon, 'As One');
Expand Down Expand Up @@ -547,15 +536,12 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
},
cloudnine: {
onSwitchIn(pokemon) {
this.effectState.switchingIn = true;
// Cloud Nine does not activate when Skill Swapped or when Neutralizing Gas leaves the field
this.add('-ability', pokemon, 'Cloud Nine');
((this.effect as any).onStart as (p: Pokemon) => void).call(this, pokemon);
},
onStart(pokemon) {
// Cloud Nine does not activate when Skill Swapped or when Neutralizing Gas leaves the field
pokemon.abilityState.ending = false; // Clear the ending flag
if (this.effectState.switchingIn) {
this.add('-ability', pokemon, 'Cloud Nine');
this.effectState.switchingIn = false;
}
this.eachEvent('WeatherChange', this.effect);
},
onEnd(pokemon) {
Expand Down Expand Up @@ -610,9 +596,15 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 213,
},
commander: {
onSwitchInPriority: -2,
onStart(pokemon) {
this.effectState.started = true;
((this.effect as any).onUpdate as (p: Pokemon) => void).call(this, pokemon);
},
onUpdate(pokemon) {
if (this.gameType !== 'doubles') return;
const ally = pokemon.allies()[0];
if (this.gameType !== 'doubles' || !this.effectState.started && !pokemon.isStarted) return;
if (pokemon.switchFlag || ally?.switchFlag) return;
if (!ally || pokemon.baseSpecies.baseSpecies !== 'Tatsugiri' || ally.baseSpecies.baseSpecies !== 'Dondozo') {
// Handle any edge cases
if (pokemon.getVolatile('commanding')) pokemon.removeVolatile('commanding');
Expand Down Expand Up @@ -693,6 +685,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 212,
},
costar: {
onSwitchInPriority: -2,
onStart(pokemon) {
const ally = pokemon.allies()[0];
if (!ally) return;
Expand Down Expand Up @@ -1055,10 +1048,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
},
drizzle: {
onStart(source) {
for (const action of this.queue) {
if (action.choice === 'runPrimal' && action.pokemon === source && source.species.id === 'kyogre') return;
if (action.choice !== 'runSwitch' && action.choice !== 'runPrimal') break;
}
if (source.species.id === 'kyogre' && source.item === 'blueorb') return;
this.field.setWeather('raindance');
},
flags: {},
Expand All @@ -1068,10 +1058,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
},
drought: {
onStart(source) {
for (const action of this.queue) {
if (action.choice === 'runPrimal' && action.pokemon === source && source.species.id === 'groudon') return;
if (action.choice !== 'runSwitch' && action.choice !== 'runPrimal') break;
}
if (source.species.id === 'groudon' && source.item === 'redorb') return;
this.field.setWeather('sunnyday');
},
flags: {},
Expand Down Expand Up @@ -1337,6 +1324,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 18,
},
flowergift: {
onSwitchInPriority: -2,
onStart(pokemon) {
this.singleEvent('WeatherChange', this.effect, this.effectState, pokemon);
},
Expand Down Expand Up @@ -1424,6 +1412,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 218,
},
forecast: {
onSwitchInPriority: -2,
onStart(pokemon) {
this.singleEvent('WeatherChange', this.effect, this.effectState, pokemon);
},
Expand Down Expand Up @@ -1827,6 +1816,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 118,
},
hospitality: {
onSwitchInPriority: -2,
onStart(pokemon) {
for (const ally of pokemon.adjacentAllies()) {
this.heal(ally.baseMaxhp / 4, ally, pokemon);
Expand Down Expand Up @@ -1921,6 +1911,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 115,
},
iceface: {
onSwitchInPriority: -2,
onStart(pokemon) {
if (this.field.isWeather(['hail', 'snow']) && pokemon.species.id === 'eiscuenoice') {
this.add('-activate', pokemon, 'ability: Ice Face');
Expand Down Expand Up @@ -2068,19 +2059,14 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
},
imposter: {
onSwitchIn(pokemon) {
this.effectState.switchingIn = true;
},
onStart(pokemon) {
// Imposter does not activate when Skill Swapped or when Neutralizing Gas leaves the field
if (!this.effectState.switchingIn) return;
// copies across in doubles/triples
// Imposter copies across in doubles/triples
// (also copies across in multibattle and diagonally in free-for-all,
// but side.foe already takes care of those)
const target = pokemon.side.foe.active[pokemon.side.foe.active.length - 1 - pokemon.position];
if (target) {
pokemon.transformInto(target, this.dex.abilities.get('imposter'));
}
this.effectState.switchingIn = false;
},
flags: {failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1},
name: "Imposter",
Expand Down Expand Up @@ -2523,6 +2509,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 196,
},
mimicry: {
onSwitchInPriority: -1,
onStart(pokemon) {
this.singleEvent('TerrainChange', this.effect, this.effectState, pokemon);
},
Expand Down Expand Up @@ -2850,7 +2837,8 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
},
neutralizinggas: {
// Ability suppression implemented in sim/pokemon.ts:Pokemon#ignoringAbility
onPreStart(pokemon) {
onSwitchInPriority: 2,
onSwitchIn(pokemon) {
this.add('-ability', pokemon, 'Neutralizing Gas');
pokemon.abilityState.ending = false;
const strongWeathers = ['desolateland', 'primordialsea', 'deltastream'];
Expand Down Expand Up @@ -2986,16 +2974,34 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
opportunist: {
onFoeAfterBoost(boost, target, source, effect) {
if (effect?.name === 'Opportunist' || effect?.name === 'Mirror Herb') return;
const pokemon = this.effectState.target;
const positiveBoosts: Partial<BoostsTable> = {};
if (!this.effectState.boosts) this.effectState.boosts = {} as SparseBoostsTable;
const boostPlus = this.effectState.boosts;
let i: BoostID;
for (i in boost) {
if (boost[i]! > 0) {
positiveBoosts[i] = boost[i];
boostPlus[i] = (boostPlus[i] || 0) + boost[i];
}
}
if (Object.keys(positiveBoosts).length < 1) return;
this.boost(positiveBoosts, pokemon);
},
onAnySwitchInPriority: -3,
onAnySwitchIn() {
if (!this.effectState.boosts) return;
this.boost(this.effectState.boosts, this.effectState.target);
delete this.effectState.boosts;
},
onAnyAfterMove() {
if (!this.effectState.boosts) return;
this.boost(this.effectState.boosts, this.effectState.target);
delete this.effectState.boosts;
},
onResidualOrder: 29,
onResidual(pokemon) {
if (!this.effectState.boosts) return;
this.boost(this.effectState.boosts, this.effectState.target);
delete this.effectState.boosts;
},
onEnd() {
delete this.effectState.boosts;
},
flags: {},
name: "Opportunist",
Expand Down Expand Up @@ -3434,6 +3440,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 168,
},
protosynthesis: {
onSwitchInPriority: -2,
onStart(pokemon) {
this.singleEvent('WeatherChange', this.effect, this.effectState, pokemon);
},
Expand Down Expand Up @@ -3571,6 +3578,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 272,
},
quarkdrive: {
onSwitchInPriority: -2,
onStart(pokemon) {
this.singleEvent('TerrainChange', this.effect, this.effectState, pokemon);
},
Expand Down Expand Up @@ -3966,6 +3974,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 157,
},
schooling: {
onSwitchInPriority: -1,
onStart(pokemon) {
if (pokemon.baseSpecies.baseSpecies !== 'Wishiwashi' || pokemon.level < 20 || pokemon.transformed) return;
if (pokemon.hp > pokemon.maxhp / 4) {
Expand Down Expand Up @@ -4160,6 +4169,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 19,
},
shieldsdown: {
onSwitchInPriority: -1,
onStart(pokemon) {
if (pokemon.baseSpecies.baseSpecies !== 'Minior' || pokemon.transformed) return;
if (pokemon.hp > pokemon.maxhp / 2) {
Expand Down Expand Up @@ -4247,7 +4257,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
},
onResidual(pokemon) {
if (!pokemon.activeTurns) {
this.effectState.duration += 1;
this.effectState.duration! += 1;
}
},
onModifyAtkPriority: 5,
Expand Down Expand Up @@ -4887,7 +4897,8 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 308,
},
terashift: {
onPreStart(pokemon) {
onSwitchInPriority: 2,
onSwitchIn(pokemon) {
if (pokemon.baseSpecies.baseSpecies !== 'Terapagos') return;
if (pokemon.species.forme !== 'Terastal') {
this.add('-activate', pokemon, 'ability: Tera Shift');
Expand Down Expand Up @@ -5048,19 +5059,23 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
},
trace: {
onStart(pokemon) {
this.effectState.seek = true;
// n.b. only affects Hackmons
// interaction with No Ability is complicated: https://www.smogon.com/forums/threads/pokemon-sun-moon-battle-mechanics-research.3586701/page-76#post-7790209
if (pokemon.adjacentFoes().some(foeActive => foeActive.ability === 'noability')) {
this.effectState.gaveUp = true;
this.effectState.seek = false;
}
// interaction with Ability Shield is similar to No Ability
if (pokemon.hasItem('Ability Shield')) {
this.add('-block', pokemon, 'item: Ability Shield');
this.effectState.gaveUp = true;
this.effectState.seek = false;
}
if (this.effectState.seek) {
this.singleEvent('Update', this.effect, this.effectState, pokemon);
}
},
onUpdate(pokemon) {
if (!pokemon.isStarted || this.effectState.gaveUp) return;
if (!this.effectState.seek) return;

const possibleTargets = pokemon.adjacentFoes().filter(
target => !target.getAbility().flags['notrace'] && target.ability !== 'noability'
Expand Down Expand Up @@ -5185,10 +5200,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
num: 84,
},
unnerve: {
onPreStart(pokemon) {
this.add('-ability', pokemon, 'Unnerve');
this.effectState.unnerved = true;
},
onSwitchInPriority: 1,
onStart(pokemon) {
if (this.effectState.unnerved) return;
this.add('-ability', pokemon, 'Unnerve');
Expand Down Expand Up @@ -5574,12 +5586,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
pokemon.formeChange('Palafin-Hero', this.effect, true);
}
},
onSwitchIn() {
this.effectState.switchingIn = true;
},
onStart(pokemon) {
if (!this.effectState.switchingIn) return;
this.effectState.switchingIn = false;
onSwitchIn(pokemon) {
if (pokemon.baseSpecies.baseSpecies !== 'Palafin') return;
if (!this.effectState.heroMessageDisplayed && pokemon.species.forme === 'Hero') {
this.add('-activate', pokemon, 'ability: Zero to Hero');
Expand Down
Loading
Loading