Spaces:
Running
Running
/** | |
* Simulator Field | |
* Pokemon Showdown - http://pokemonshowdown.com/ | |
* | |
* @license MIT | |
*/ | |
import { State } from './state'; | |
import { type EffectState } from './pokemon'; | |
import { toID } from './dex'; | |
export class Field { | |
readonly battle: Battle; | |
readonly id: ID; | |
weather: ID; | |
weatherState: EffectState; | |
terrain: ID; | |
terrainState: EffectState; | |
pseudoWeather: { [id: string]: EffectState }; | |
constructor(battle: Battle) { | |
this.battle = battle; | |
const fieldScripts = this.battle.format.field || this.battle.dex.data.Scripts.field; | |
if (fieldScripts) Object.assign(this, fieldScripts); | |
this.id = ''; | |
this.weather = ''; | |
this.weatherState = this.battle.initEffectState({ id: '' }); | |
this.terrain = ''; | |
this.terrainState = this.battle.initEffectState({ id: '' }); | |
this.pseudoWeather = {}; | |
} | |
toJSON(): AnyObject { | |
return State.serializeField(this); | |
} | |
setWeather(status: string | Condition, source: Pokemon | 'debug' | null = null, sourceEffect: Effect | null = null) { | |
status = this.battle.dex.conditions.get(status); | |
if (!sourceEffect && this.battle.effect) sourceEffect = this.battle.effect; | |
if (!source && this.battle.event?.target) source = this.battle.event.target; | |
if (source === 'debug') source = this.battle.sides[0].active[0]; | |
if (this.weather === status.id) { | |
if (sourceEffect && sourceEffect.effectType === 'Ability') { | |
if (this.battle.gen > 5 || this.weatherState.duration === 0) { | |
return false; | |
} | |
} else if (this.battle.gen > 2 || status.id === 'sandstorm') { | |
return false; | |
} | |
} | |
if (source) { | |
const result = this.battle.runEvent('SetWeather', source, source, status); | |
if (!result) { | |
if (result === false) { | |
if ((sourceEffect as Move)?.weather) { | |
this.battle.add('-fail', source, sourceEffect, '[from] ' + this.weather); | |
} else if (sourceEffect && sourceEffect.effectType === 'Ability') { | |
this.battle.add('-ability', source, sourceEffect, '[from] ' + this.weather, '[fail]'); | |
} | |
} | |
return null; | |
} | |
} | |
const prevWeather = this.weather; | |
const prevWeatherState = this.weatherState; | |
this.weather = status.id; | |
this.weatherState = this.battle.initEffectState({ id: status.id }); | |
if (source) { | |
this.weatherState.source = source; | |
this.weatherState.sourceSlot = source.getSlot(); | |
} | |
if (status.duration) { | |
this.weatherState.duration = status.duration; | |
} | |
if (status.durationCallback) { | |
if (!source) throw new Error(`setting weather without a source`); | |
this.weatherState.duration = status.durationCallback.call(this.battle, source, source, sourceEffect); | |
} | |
if (!this.battle.singleEvent('FieldStart', status, this.weatherState, this, source, sourceEffect)) { | |
this.weather = prevWeather; | |
this.weatherState = prevWeatherState; | |
return false; | |
} | |
this.battle.eachEvent('WeatherChange', sourceEffect); | |
return true; | |
} | |
clearWeather() { | |
if (!this.weather) return false; | |
const prevWeather = this.getWeather(); | |
this.battle.singleEvent('FieldEnd', prevWeather, this.weatherState, this); | |
this.weather = ''; | |
this.battle.clearEffectState(this.weatherState); | |
this.battle.eachEvent('WeatherChange'); | |
return true; | |
} | |
effectiveWeather() { | |
if (this.suppressingWeather()) return ''; | |
return this.weather; | |
} | |
suppressingWeather() { | |
for (const side of this.battle.sides) { | |
for (const pokemon of side.active) { | |
if (pokemon && !pokemon.fainted && !pokemon.ignoringAbility() && | |
pokemon.getAbility().suppressWeather && !pokemon.abilityState.ending) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
isWeather(weather: string | string[]) { | |
const ourWeather = this.effectiveWeather(); | |
if (!Array.isArray(weather)) { | |
return ourWeather === toID(weather); | |
} | |
return weather.map(toID).includes(ourWeather); | |
} | |
getWeather() { | |
return this.battle.dex.conditions.getByID(this.weather); | |
} | |
setTerrain(status: string | Effect, source: Pokemon | 'debug' | null = null, sourceEffect: Effect | null = null) { | |
status = this.battle.dex.conditions.get(status); | |
if (!sourceEffect && this.battle.effect) sourceEffect = this.battle.effect; | |
if (!source && this.battle.event?.target) source = this.battle.event.target; | |
if (source === 'debug') source = this.battle.sides[0].active[0]; | |
if (!source) throw new Error(`setting terrain without a source`); | |
if (this.terrain === status.id) return false; | |
const prevTerrain = this.terrain; | |
const prevTerrainState = this.terrainState; | |
this.terrain = status.id; | |
this.terrainState = this.battle.initEffectState({ | |
id: status.id, | |
source, | |
sourceSlot: source.getSlot(), | |
duration: status.duration, | |
}); | |
if (status.durationCallback) { | |
this.terrainState.duration = status.durationCallback.call(this.battle, source, source, sourceEffect); | |
} | |
if (!this.battle.singleEvent('FieldStart', status, this.terrainState, this, source, sourceEffect)) { | |
this.terrain = prevTerrain; | |
this.terrainState = prevTerrainState; | |
return false; | |
} | |
this.battle.eachEvent('TerrainChange', sourceEffect); | |
return true; | |
} | |
clearTerrain() { | |
if (!this.terrain) return false; | |
const prevTerrain = this.getTerrain(); | |
this.battle.singleEvent('FieldEnd', prevTerrain, this.terrainState, this); | |
this.terrain = ''; | |
this.battle.clearEffectState(this.terrainState); | |
this.battle.eachEvent('TerrainChange'); | |
return true; | |
} | |
effectiveTerrain(target?: Pokemon | Side | Battle) { | |
if (this.battle.event && !target) target = this.battle.event.target; | |
return this.battle.runEvent('TryTerrain', target) ? this.terrain : ''; | |
} | |
isTerrain(terrain: string | string[], target?: Pokemon | Side | Battle) { | |
const ourTerrain = this.effectiveTerrain(target); | |
if (!Array.isArray(terrain)) { | |
return ourTerrain === toID(terrain); | |
} | |
return terrain.map(toID).includes(ourTerrain); | |
} | |
getTerrain() { | |
return this.battle.dex.conditions.getByID(this.terrain); | |
} | |
addPseudoWeather( | |
status: string | Condition, | |
source: Pokemon | 'debug' | null = null, | |
sourceEffect: Effect | null = null | |
): boolean { | |
if (!source && this.battle.event?.target) source = this.battle.event.target; | |
if (source === 'debug') source = this.battle.sides[0].active[0]; | |
status = this.battle.dex.conditions.get(status); | |
let state = this.pseudoWeather[status.id]; | |
if (state) { | |
if (!(status as any).onFieldRestart) return false; | |
return this.battle.singleEvent('FieldRestart', status, state, this, source, sourceEffect); | |
} | |
state = this.pseudoWeather[status.id] = this.battle.initEffectState({ | |
id: status.id, | |
source, | |
sourceSlot: source?.getSlot(), | |
duration: status.duration, | |
}); | |
if (status.durationCallback) { | |
if (!source) throw new Error(`setting fieldcond without a source`); | |
state.duration = status.durationCallback.call(this.battle, source, source, sourceEffect); | |
} | |
if (!this.battle.singleEvent('FieldStart', status, state, this, source, sourceEffect)) { | |
delete this.pseudoWeather[status.id]; | |
return false; | |
} | |
this.battle.runEvent('PseudoWeatherChange', source, source, status); | |
return true; | |
} | |
getPseudoWeather(status: string | Effect) { | |
status = this.battle.dex.conditions.get(status); | |
return this.pseudoWeather[status.id] ? status : null; | |
} | |
removePseudoWeather(status: string | Effect) { | |
status = this.battle.dex.conditions.get(status); | |
const state = this.pseudoWeather[status.id]; | |
if (!state) return false; | |
this.battle.singleEvent('FieldEnd', status, state, this); | |
delete this.pseudoWeather[status.id]; | |
return true; | |
} | |
destroy() { | |
// deallocate ourself | |
// get rid of some possibly-circular references | |
(this as any).battle = null!; | |
} | |
} | |