mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-25 15:40:31 -05:00
TypeScript 4.8+ supports Lowercase for lowercase strings, which isn't exactly what ID is, but can be used to type IDs in object keys and data entries that previously required string. I'm calling it IDEntry in places where it should be an ID but TypeScript doesn't support that. Very conveniently, no additional casts will be needed when using ID where IDEntry is expected. It's caught at least a few bugs, which is also why I'm PRing: I didn't write the code for the bugs it found, and don't know if it's the right way to fix them. This ballooned into several other type refactors.
128 lines
3.9 KiB
TypeScript
128 lines
3.9 KiB
TypeScript
import type {PokemonEventMethods, ConditionData} from './dex-conditions';
|
|
import {BasicEffect, toID} from './dex-data';
|
|
|
|
interface AbilityEventMethods {
|
|
onCheckShow?: (this: Battle, pokemon: Pokemon) => void;
|
|
onEnd?: (this: Battle, target: Pokemon & Side & Field) => void;
|
|
onPreStart?: (this: Battle, pokemon: Pokemon) => void;
|
|
onStart?: (this: Battle, target: Pokemon) => void;
|
|
}
|
|
|
|
/* Possible Ability flags */
|
|
interface AbilityFlags {
|
|
breakable?: 1; // Can be suppressed by Mold Breaker and related effects
|
|
cantsuppress?: 1; // Ability can't be suppressed by e.g. Gastro Acid or Neutralizing Gas
|
|
failroleplay?: 1; // Role Play fails if target has this Ability
|
|
failskillswap?: 1; // Skill Swap fails if either the user or target has this Ability
|
|
noentrain?: 1; // Entrainment fails if user has this Ability
|
|
noreceiver?: 1; // Receiver and Power of Alchemy will not activate if an ally faints with this Ability
|
|
notrace?: 1; // Trace cannot copy this Ability
|
|
notransform?: 1; // Disables the Ability if the user is Transformed
|
|
}
|
|
|
|
export interface AbilityData extends Partial<Ability>, AbilityEventMethods, PokemonEventMethods {
|
|
name: string;
|
|
}
|
|
|
|
export type ModdedAbilityData = AbilityData | Partial<AbilityData> & {inherit: true};
|
|
export interface AbilityDataTable {[abilityid: IDEntry]: AbilityData}
|
|
export interface ModdedAbilityDataTable {[abilityid: IDEntry]: ModdedAbilityData}
|
|
|
|
export class Ability extends BasicEffect implements Readonly<BasicEffect> {
|
|
declare readonly effectType: 'Ability';
|
|
|
|
/** Rating from -1 Detrimental to +5 Essential; see `data/abilities.ts` for details. */
|
|
readonly rating: number;
|
|
readonly suppressWeather: boolean;
|
|
readonly flags: AbilityFlags;
|
|
declare readonly condition?: ConditionData;
|
|
|
|
constructor(data: AnyObject) {
|
|
super(data);
|
|
|
|
this.fullname = `ability: ${this.name}`;
|
|
this.effectType = 'Ability';
|
|
this.suppressWeather = !!data.suppressWeather;
|
|
this.flags = data.flags || {};
|
|
this.rating = data.rating || 0;
|
|
|
|
if (!this.gen) {
|
|
if (this.num >= 268) {
|
|
this.gen = 9;
|
|
} else if (this.num >= 234) {
|
|
this.gen = 8;
|
|
} else if (this.num >= 192) {
|
|
this.gen = 7;
|
|
} else if (this.num >= 165) {
|
|
this.gen = 6;
|
|
} else if (this.num >= 124) {
|
|
this.gen = 5;
|
|
} else if (this.num >= 77) {
|
|
this.gen = 4;
|
|
} else if (this.num >= 1) {
|
|
this.gen = 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export class DexAbilities {
|
|
readonly dex: ModdedDex;
|
|
readonly abilityCache = new Map<ID, Ability>();
|
|
allCache: readonly Ability[] | null = null;
|
|
|
|
constructor(dex: ModdedDex) {
|
|
this.dex = dex;
|
|
}
|
|
|
|
get(name: string | Ability = ''): Ability {
|
|
if (name && typeof name !== 'string') return name;
|
|
|
|
const id = toID(name);
|
|
return this.getByID(id);
|
|
}
|
|
|
|
getByID(id: ID): Ability {
|
|
let ability = this.abilityCache.get(id);
|
|
if (ability) return ability;
|
|
|
|
if (this.dex.data.Aliases.hasOwnProperty(id)) {
|
|
ability = this.get(this.dex.data.Aliases[id]);
|
|
} else if (id && this.dex.data.Abilities.hasOwnProperty(id)) {
|
|
const abilityData = this.dex.data.Abilities[id] as any;
|
|
const abilityTextData = this.dex.getDescs('Abilities', id, abilityData);
|
|
ability = new Ability({
|
|
name: id,
|
|
...abilityData,
|
|
...abilityTextData,
|
|
});
|
|
if (ability.gen > this.dex.gen) {
|
|
(ability as any).isNonstandard = 'Future';
|
|
}
|
|
if (this.dex.currentMod === 'gen7letsgo' && ability.id !== 'noability') {
|
|
(ability as any).isNonstandard = 'Past';
|
|
}
|
|
if ((this.dex.currentMod === 'gen7letsgo' || this.dex.gen <= 2) && ability.id === 'noability') {
|
|
(ability as any).isNonstandard = null;
|
|
}
|
|
} else {
|
|
ability = new Ability({
|
|
id, name: id, exists: false,
|
|
});
|
|
}
|
|
|
|
if (ability.exists) this.abilityCache.set(id, this.dex.deepFreeze(ability));
|
|
return ability;
|
|
}
|
|
|
|
all(): readonly Ability[] {
|
|
if (this.allCache) return this.allCache;
|
|
const abilities = [];
|
|
for (const id in this.dex.data.Abilities) {
|
|
abilities.push(this.getByID(id as ID));
|
|
}
|
|
this.allCache = abilities;
|
|
return this.allCache;
|
|
}
|
|
}
|