pokemon-showdown/sim/dex-data.ts

342 lines
11 KiB
TypeScript

/**
* Simulator Battle
* Pokemon Showdown - http://pokemonshowdown.com/
*
* @license MIT license
*/
import {Utils} from '../lib';
/**
* Converts anything to an ID. An ID must have only lowercase alphanumeric
* characters.
*
* If a string is passed, it will be converted to lowercase and
* non-alphanumeric characters will be stripped.
*
* If an object with an ID is passed, its ID will be returned.
* Otherwise, an empty string will be returned.
*
* Generally assigned to the global toID, because of how
* commonly it's used.
*/
export function toID(text: any): ID {
// The sucrase transformation of optional chaining is too expensive to be used in a hot function like this.
/* eslint-disable @typescript-eslint/prefer-optional-chain */
if (text && text.id) {
text = text.id;
} else if (text && text.userid) {
text = text.userid;
} else if (text && text.roomid) {
text = text.roomid;
}
if (typeof text !== 'string' && typeof text !== 'number') return '';
return ('' + text).toLowerCase().replace(/[^a-z0-9]+/g, '') as ID;
/* eslint-enable @typescript-eslint/prefer-optional-chain */
}
export class BasicEffect implements EffectData {
/**
* ID. This will be a lowercase version of the name with all the
* non-alphanumeric characters removed. So, for instance, "Mr. Mime"
* becomes "mrmime", and "Basculin-Blue-Striped" becomes
* "basculinbluestriped".
*/
id: ID;
/**
* Name. Currently does not support Unicode letters, so "Flabébé"
* is "Flabebe" and "Nidoran♀" is "Nidoran-F".
*/
name: string;
/**
* Full name. Prefixes the name with the effect type. For instance,
* Leftovers would be "item: Leftovers", confusion the status
* condition would be "confusion", etc.
*/
fullname: string;
/** Effect type. */
effectType: EffectType;
/**
* Does it exist? For historical reasons, when you use an accessor
* for an effect that doesn't exist, you get a dummy effect that
* doesn't do anything, and this field set to false.
*/
exists: boolean;
/**
* Dex number? For a Pokemon, this is the National Dex number. For
* other effects, this is often an internal ID (e.g. a move
* number). Not all effects have numbers, this will be 0 if it
* doesn't. Nonstandard effects (e.g. CAP effects) will have
* negative numbers.
*/
num: number;
/**
* The generation of Pokemon game this was INTRODUCED (NOT
* necessarily the current gen being simulated.) Not all effects
* track generation; this will be 0 if not known.
*/
gen: number;
/**
* A shortened form of the description of this effect.
* Not all effects have this.
*/
shortDesc: string;
/** The full description for this effect. */
desc: string;
/**
* Is this item/move/ability/pokemon nonstandard? Specified for effects
* that have no use in standard formats: made-up pokemon (CAP),
* glitches (MissingNo etc), Pokestar pokemon, etc.
*/
isNonstandard: Nonstandard | null;
/** The duration of the condition - only for pure conditions. */
duration?: number;
/** Whether or not the condition is ignored by Baton Pass - only for pure conditions. */
noCopy: boolean;
/** Whether or not the condition affects fainted Pokemon. */
affectsFainted: boolean;
/** Moves only: what status does it set? */
status?: ID;
/** Moves only: what weather does it set? */
weather?: ID;
/** ??? */
sourceEffect: string;
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
this.exists = true;
data = combine(this, data, ...moreData);
this.name = Utils.getString(data.name).trim();
this.id = data.realMove ? toID(data.realMove) : toID(this.name); // Hidden Power hack
this.fullname = Utils.getString(data.fullname) || this.name;
this.effectType = Utils.getString(data.effectType) as EffectType || 'Condition';
this.exists = !!(this.exists && this.id);
this.num = data.num || 0;
this.gen = data.gen || 0;
this.shortDesc = data.shortDesc || '';
this.desc = data.desc || '';
this.isNonstandard = data.isNonstandard || null;
this.duration = data.duration;
this.noCopy = !!data.noCopy;
this.affectsFainted = !!data.affectsFainted;
this.status = data.status as ID || undefined;
this.weather = data.weather as ID || undefined;
this.sourceEffect = data.sourceEffect || '';
}
toString() {
return this.name;
}
}
export class Learnset {
readonly effectType: 'Learnset';
/**
* Keeps track of exactly how a pokemon might learn a move, in the
* form moveid:sources[].
*/
readonly learnset?: {[moveid: string]: MoveSource[]};
/** True if the only way to get this Pokemon is from events. */
readonly eventOnly: boolean;
/** List of event data for each event. */
readonly eventData?: EventInfo[];
readonly encounters?: EventInfo[];
readonly exists: boolean;
constructor(data: AnyObject) {
this.exists = true;
this.effectType = 'Learnset';
this.learnset = data.learnset || undefined;
this.eventOnly = !!data.eventOnly;
this.eventData = data.eventData || undefined;
this.encounters = data.encounters || undefined;
}
}
export class Nature extends BasicEffect implements Readonly<BasicEffect & NatureData> {
readonly effectType: 'Nature';
readonly plus?: StatNameExceptHP;
readonly minus?: StatNameExceptHP;
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
super(data, moreData);
data = this;
this.fullname = `nature: ${this.name}`;
this.effectType = 'Nature';
this.gen = 3;
this.plus = data.plus || undefined;
this.minus = data.minus || undefined;
}
}
type TypeInfoEffectType = 'Type' | 'EffectType';
export class TypeInfo implements Readonly<TypeData> {
/**
* ID. This will be a lowercase version of the name with all the
* non-alphanumeric characters removed. e.g. 'flying'
*/
readonly id: ID;
/** Name. e.g. 'Flying' */
readonly name: string;
/** Effect type. */
readonly effectType: TypeInfoEffectType;
/**
* Does it exist? For historical reasons, when you use an accessor
* for an effect that doesn't exist, you get a dummy effect that
* doesn't do anything, and this field set to false.
*/
readonly exists: boolean;
/**
* The generation of Pokemon game this was INTRODUCED (NOT
* necessarily the current gen being simulated.) Not all effects
* track generation; this will be 0 if not known.
*/
readonly gen: number;
/**
* Type chart, attackingTypeName:result, effectid:result
* result is: 0 = normal, 1 = weakness, 2 = resistance, 3 = immunity
*/
readonly damageTaken: {[attackingTypeNameOrEffectid: string]: number};
/** The IVs to get this Type Hidden Power (in gen 3 and later) */
readonly HPivs: SparseStatsTable;
/** The DVs to get this Type Hidden Power (in gen 2). */
readonly HPdvs: SparseStatsTable;
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
this.exists = true;
data = combine(this, data, ...moreData);
this.id = data.id || '';
this.name = Utils.getString(data.name).trim();
this.effectType = Utils.getString(data.effectType) as TypeInfoEffectType || 'Type';
this.exists = !!(this.exists && this.id);
this.gen = data.gen || 0;
this.damageTaken = data.damageTaken || {};
this.HPivs = data.HPivs || {};
this.HPdvs = data.HPdvs || {};
}
toString() {
return this.name;
}
}
export function combine(obj: AnyObject, ...data: (AnyObject | null)[]): AnyObject {
for (const d of data) {
if (d) Object.assign(obj, d);
}
return obj;
}
// export class PokemonSet {
// /**
// * The Pokemon's set's nickname, which is identical to its base
// * species if not specified by the player, e.g. "Minior".
// */
// name: string;
// /**
// * The Pokemon's species, e.g. "Minior-Red".
// * This should always be converted to an id before use.
// */
// species: string;
// /**
// * The Pokemon's set's item. This can be an id, e.g. "whiteherb"
// * or a full name, e.g. "White Herb".
// * This should always be converted to an id before use.
// */
// item: string;
// /**
// * The Pokemon's set's ability. This can be an id, e.g. "shieldsdown"
// * or a full name, e.g. "Shields Down".
// * This should always be converted to an id before use.
// */
// ability: string;
// /**
// * An array of the Pokemon's set's moves. Each move can be an id,
// * e.g. "shellsmash" or a full name, e.g. "Shell Smash"
// * These should always be converted to ids before use.
// */
// moves: string[];
// /**
// * The Pokemon's set's nature. This can be an id, e.g. "adamant"
// * or a full name, e.g. "Adamant".
// * This should always be converted to an id before use.
// */
// nature: string;
// /**
// * The Pokemon's set's gender.
// */
// gender: GenderName;
// /**
// * The Pokemon's set's effort values, used in stat calculation.
// * These must be between 0 and 255, inclusive.
// */
// evs: StatsTable;
// /**
// * The Pokemon's individual values, used in stat calculation.
// * These must be between 0 and 31, inclusive.
// * These are also used as DVs, or determinant values, in Gens
// * 1 and 2, which are represented as even numbers from 0 to 30.
// * From Gen 2 and on, IVs/DVs are used to determine Hidden Power's
// * type, although in Gen 7 a Pokemon may be legally altered such
// * that its stats are calculated as if these values were 31 via
// * Bottlecaps. Currently, PS handles this by considering any
// * IV of 31 in Gen 7 to count as either even or odd for the purpose
// * of validating a Hidden Power type, though restrictions on
// * possible IVs for event-only Pokemon are still considered.
// */
// ivs: StatsTable;
// /**
// * The Pokemon's level. This is usually between 1 and 100, inclusive,
// * but the simulator supports levels up to 9999 for testing purposes.
// */
// level: number;
// /**
// * Whether the Pokemon is shiny or not. While having no direct
// * competitive effect except in a few OMs, certain Pokemon cannot
// * be legally obtained as shiny, either as a whole or with certain
// * event-only abilities or moves.
// */
// shiny?: boolean;
// /**
// * The Pokemon's set's happiness value. This is used only for
// * calculating the base power of the moves Return and Frustration.
// * This value must be between 0 and 255, inclusive.
// */
// happiness: number;
// /**
// * The Pokemon's set's Hidden Power type. This value is intended
// * to be used to manually set a set's HP type in Gen 7 where
// * its IVs do not necessarily reflect the user's intended type.
// * TODO: actually support this in the teambuilder.
// */
// hpType?: string;
// /**
// * The pokeball this Pokemon is in. Like shininess, this property
// * has no direct competitive effects, but has implications for
// * event legality. For example, any Rayquaza that knows V-Create
// * must be sent out from a Cherish Ball.
// * TODO: actually support this in the validator, switching animations,
// * and the teambuilder.
// */
// pokeball?: string;
//
// constructor(data: Partial<PokemonSet>) {
// this.name = '';
// this.species = '';
// this.item = '';
// this.ability = 'noability';
// this.moves = [];
// this.nature = '';
// this.gender = '';
// this.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
// this.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
// this.level = 100;
// this.shiny = undefined;
// this.happiness = 255; // :)
// this.hpType = undefined;
// this.pokeball = undefined;
// Object.assign(this, data);
// }
// }