mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
1169 lines
40 KiB
TypeScript
1169 lines
40 KiB
TypeScript
/**
|
||
* Simulator Battle
|
||
* Pokemon Showdown - http://pokemonshowdown.com/
|
||
*
|
||
* @license MIT license
|
||
*/
|
||
|
||
export class Tools {
|
||
/**
|
||
* Safely converts the passed variable into a string. Unlike '' + str,
|
||
* String(str), or str.toString(), Dex.getString is guaranteed not to
|
||
* crash.
|
||
*
|
||
* Specifically, the fear with untrusted JSON is an object like:
|
||
*
|
||
* let a = {"toString": "this is not a function"};
|
||
* console.log(`a is ${a}`);
|
||
*
|
||
* This will crash (because a.toString() is not a function). Instead,
|
||
* Dex.getString simply returns '' if the passed variable isn't a
|
||
* string or a number.
|
||
*/
|
||
static getString(str: any): string {
|
||
return (typeof str === 'string' || typeof str === 'number') ? '' + str : '';
|
||
}
|
||
|
||
/**
|
||
* 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.
|
||
*
|
||
* Dex.getId is generally assigned to the global toID, because of how
|
||
* commonly it's used.
|
||
*/
|
||
/* 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 */
|
||
static getId(text: any): ID {
|
||
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 */
|
||
}
|
||
const toID = Tools.getId;
|
||
|
||
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;
|
||
/**
|
||
* Is this item/move/ability/pokemon unreleased? True if there's
|
||
* no known way to get access to it without cheating.
|
||
*/
|
||
isUnreleased: boolean | 'Past';
|
||
/**
|
||
* 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 effect. */
|
||
duration?: number;
|
||
/** Whether or not the effect is ignored by Baton Pass. */
|
||
noCopy: boolean;
|
||
/** Whether or not the effect affects fainted Pokemon. */
|
||
affectsFainted: boolean;
|
||
/** The status that the effect may cause. */
|
||
status?: ID;
|
||
/** The weather that the effect may cause. */
|
||
weather?: ID;
|
||
sourceEffect: string;
|
||
|
||
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
|
||
this.exists = true;
|
||
data = combine(this, data, ...moreData);
|
||
|
||
this.name = Tools.getString(data.name).trim();
|
||
this.id = data.id as ID || toID(this.name); // Hidden Power hack
|
||
this.fullname = Tools.getString(data.fullname) || this.name;
|
||
this.effectType = Tools.getString(data.effectType) as EffectType || 'Effect';
|
||
this.exists = !!(this.exists && this.id);
|
||
this.num = data.num || 0;
|
||
this.gen = data.gen || 0;
|
||
this.isUnreleased = data.isUnreleased || false;
|
||
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;
|
||
}
|
||
}
|
||
|
||
/** rule, source, limit, bans */
|
||
export type ComplexBan = [string, string, number, string[]];
|
||
export type ComplexTeamBan = ComplexBan;
|
||
|
||
/**
|
||
* A RuleTable keeps track of the rules that a format has. The key can be:
|
||
* - '[ruleid]' the ID of a rule in effect
|
||
* - '-[thing]' or '-[category]:[thing]' ban a thing
|
||
* - '+[thing]' or '+[category]:[thing]' allow a thing (override a ban)
|
||
* [category] is one of: item, move, ability, species, basespecies
|
||
*
|
||
* The value is the name of the parent rule (blank for the active format).
|
||
*/
|
||
export class RuleTable extends Map<string, string> {
|
||
complexBans: ComplexBan[];
|
||
complexTeamBans: ComplexTeamBan[];
|
||
// tslint:disable-next-line:ban-types
|
||
checkLearnset: [Function, string] | null;
|
||
timer: [Partial<GameTimerSettings>, string] | null;
|
||
minSourceGen: [number, string] | null;
|
||
|
||
constructor() {
|
||
super();
|
||
this.complexBans = [];
|
||
this.complexTeamBans = [];
|
||
this.checkLearnset = null;
|
||
this.timer = null;
|
||
this.minSourceGen = null;
|
||
}
|
||
|
||
isBanned(thing: string) {
|
||
if (this.has(`+${thing}`)) return false;
|
||
return this.has(`-${thing}`);
|
||
}
|
||
|
||
check(thing: string, setHas: {[id: string]: true} | null = null) {
|
||
if (this.has(`+${thing}`)) return '';
|
||
if (setHas) setHas[thing] = true;
|
||
return this.getReason(`-${thing}`);
|
||
}
|
||
|
||
getReason(key: string): string | null {
|
||
const source = this.get(key);
|
||
if (source === undefined) return null;
|
||
if (key === '-nonexistent' || key.startsWith('obtainable')) {
|
||
return 'not obtainable';
|
||
}
|
||
return source ? `banned by ${source}` : `banned`;
|
||
}
|
||
|
||
getComplexBanIndex(complexBans: ComplexBan[], rule: string): number {
|
||
const ruleId = toID(rule);
|
||
let complexBanIndex = -1;
|
||
for (let i = 0; i < complexBans.length; i++) {
|
||
if (toID(complexBans[i][0]) === ruleId) {
|
||
complexBanIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
return complexBanIndex;
|
||
}
|
||
|
||
addComplexBan(rule: string, source: string, limit: number, bans: string[]) {
|
||
const complexBanIndex = this.getComplexBanIndex(this.complexBans, rule);
|
||
if (complexBanIndex !== -1) {
|
||
if (this.complexBans[complexBanIndex][2] === Infinity) return;
|
||
this.complexBans[complexBanIndex] = [rule, source, limit, bans];
|
||
} else {
|
||
this.complexBans.push([rule, source, limit, bans]);
|
||
}
|
||
}
|
||
|
||
addComplexTeamBan(rule: string, source: string, limit: number, bans: string[]) {
|
||
const complexBanTeamIndex = this.getComplexBanIndex(this.complexTeamBans, rule);
|
||
if (complexBanTeamIndex !== -1) {
|
||
if (this.complexTeamBans[complexBanTeamIndex][2] === Infinity) return;
|
||
this.complexTeamBans[complexBanTeamIndex] = [rule, source, limit, bans];
|
||
} else {
|
||
this.complexTeamBans.push([rule, source, limit, bans]);
|
||
}
|
||
}
|
||
}
|
||
|
||
type FormatEffectType = 'Format' | 'Ruleset' | 'Rule' | 'ValidatorRule';
|
||
|
||
export class Format extends BasicEffect implements Readonly<BasicEffect & FormatsData> {
|
||
readonly mod: string;
|
||
/**
|
||
* Name of the team generator algorithm, if this format uses
|
||
* random/fixed teams. null if players can bring teams.
|
||
*/
|
||
readonly team?: string;
|
||
readonly effectType: FormatEffectType;
|
||
readonly debug: boolean;
|
||
/**
|
||
* Whether or not a format will update ladder points if searched
|
||
* for using the "Battle!" button.
|
||
* (Challenge and tournament games will never update ladder points.)
|
||
* (Defaults to `true`.)
|
||
*/
|
||
readonly rated: boolean;
|
||
/** Game type. */
|
||
readonly gameType: GameType;
|
||
/** List of rule names. */
|
||
readonly ruleset: string[];
|
||
/**
|
||
* Base list of rule names as specified in "./config/formats.js".
|
||
* Used in a custom format to correctly display the altered ruleset.
|
||
*/
|
||
readonly baseRuleset: string[];
|
||
/** List of banned effects. */
|
||
readonly banlist: string[];
|
||
/** List of inherited banned effects to override. */
|
||
readonly unbanlist: string[];
|
||
/** List of ruleset and banlist changes in a custom format. */
|
||
readonly customRules: string[] | null;
|
||
/** Table of rule names and banned effects. */
|
||
ruleTable: RuleTable | null;
|
||
/**
|
||
* The number of Pokemon players can bring to battle and
|
||
* the number that can actually be used.
|
||
*/
|
||
readonly teamLength?: {battle?: number, validate?: [number, number]};
|
||
/** An optional function that runs at the start of a battle. */
|
||
readonly onBegin?: (this: Battle) => void;
|
||
/** Pokemon must be obtained from this generation or later. */
|
||
readonly minSourceGen?: number;
|
||
/**
|
||
* Maximum possible level pokemon you can bring. Note that this is
|
||
* still 100 in VGC, because you can bring level 100 pokemon,
|
||
* they'll just be set to level 50. Can be above 100 in special
|
||
* formats.
|
||
*/
|
||
readonly maxLevel: number;
|
||
/**
|
||
* Default level of a pokemon without level specified. Mainly
|
||
* relevant to Custom Game where the default level is still 100
|
||
* even though higher level pokemon can be brought.
|
||
*/
|
||
readonly defaultLevel: number;
|
||
/**
|
||
* Forces all pokemon brought in to this level. Certain Game Freak
|
||
* formats will change level 1 and level 100 pokemon to level 50,
|
||
* which is what this does.
|
||
*
|
||
* You usually want maxForcedLevel instead, which will bring level
|
||
* 100 pokemon down, but not level 1 pokemon up.
|
||
*/
|
||
readonly forcedLevel?: number;
|
||
/**
|
||
* Forces all pokemon above this level down to this level. This
|
||
* will allow e.g. level 50 Hydreigon in Gen 5, which is not
|
||
* normally legal because Hydreigon doesn't evolve until level
|
||
* 64.
|
||
*/
|
||
readonly maxForcedLevel?: number;
|
||
readonly noLog: boolean;
|
||
|
||
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
|
||
super(data, ...moreData);
|
||
data = this;
|
||
|
||
this.mod = Tools.getString(data.mod) || 'gen8';
|
||
this.effectType = Tools.getString(data.effectType) as FormatEffectType || 'Format';
|
||
this.debug = !!data.debug;
|
||
this.rated = (data.rated !== false);
|
||
this.gameType = data.gameType || 'singles';
|
||
this.ruleset = data.ruleset || [];
|
||
this.baseRuleset = data.baseRuleset || [];
|
||
this.banlist = data.banlist || [];
|
||
this.unbanlist = data.unbanlist || [];
|
||
this.customRules = data.customRules || null;
|
||
this.ruleTable = null;
|
||
this.teamLength = data.teamLength || undefined;
|
||
this.onBegin = data.onBegin || undefined;
|
||
this.minSourceGen = data.minSourceGen || undefined;
|
||
this.maxLevel = data.maxLevel || 100;
|
||
this.defaultLevel = data.defaultLevel || this.maxLevel;
|
||
this.forcedLevel = data.forcedLevel || undefined;
|
||
this.maxForcedLevel = data.maxForcedLevel || undefined;
|
||
this.noLog = !!data.noLog;
|
||
}
|
||
}
|
||
|
||
export class PureEffect extends BasicEffect implements Readonly<BasicEffect & PureEffectData> {
|
||
readonly effectType: 'Effect' | 'Weather' | 'Status';
|
||
|
||
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
|
||
super(data, ...moreData);
|
||
data = this;
|
||
this.effectType = (['Weather', 'Status'].includes(data.effectType) ? data.effectType : 'Effect');
|
||
}
|
||
}
|
||
|
||
export class Item extends BasicEffect implements Readonly<BasicEffect & ItemData> {
|
||
readonly effectType: 'Item';
|
||
/**
|
||
* A Move-like object depicting what happens when Fling is used on
|
||
* this item.
|
||
*/
|
||
readonly fling?: FlingData;
|
||
/**
|
||
* If this is a Drive: The type it turns Techno Blast into.
|
||
* undefined, if not a Drive.
|
||
*/
|
||
readonly onDrive?: string;
|
||
/**
|
||
* If this is a Memory: The type it turns Multi-Attack into.
|
||
* undefined, if not a Memory.
|
||
*/
|
||
readonly onMemory?: string;
|
||
/**
|
||
* If this is a mega stone: The name (e.g. Charizard-Mega-X) of the
|
||
* forme this allows transformation into.
|
||
* undefined, if not a mega stone.
|
||
*/
|
||
readonly megaStone?: string;
|
||
/**
|
||
* If this is a mega stone: The name (e.g. Charizard) of the
|
||
* forme this allows transformation from.
|
||
* undefined, if not a mega stone.
|
||
*/
|
||
readonly megaEvolves?: string;
|
||
/**
|
||
* If this is a Z crystal: true if the Z Crystal is generic
|
||
* (e.g. Firium Z). If species-specific, the name
|
||
* (e.g. Inferno Overdrive) of the Z Move this crystal allows
|
||
* the use of.
|
||
* undefined, if not a Z crystal.
|
||
*/
|
||
readonly zMove?: true | string;
|
||
/**
|
||
* If this is a generic Z crystal: The type (e.g. Fire) of the
|
||
* Z Move this crystal allows the use of (e.g. Fire)
|
||
* undefined, if not a generic Z crystal
|
||
*/
|
||
readonly zMoveType?: string;
|
||
/**
|
||
* If this is a species-specific Z crystal: The name
|
||
* (e.g. Play Rough) of the move this crystal requires its
|
||
* holder to know to use its Z move.
|
||
* undefined, if not a species-specific Z crystal
|
||
*/
|
||
readonly zMoveFrom?: string;
|
||
/**
|
||
* If this is a species-specific Z crystal: An array of the
|
||
* species of Pokemon that can use this crystal's Z move.
|
||
* Note that these are the full names, e.g. 'Mimikyu-Busted'
|
||
* undefined, if not a species-specific Z crystal
|
||
*/
|
||
readonly itemUser?: string[];
|
||
/** Is this item a Berry? */
|
||
readonly isBerry: boolean;
|
||
/** Whether or not this item ignores the Klutz ability. */
|
||
readonly ignoreKlutz: boolean;
|
||
/** The type the holder will change into if it is an Arceus. */
|
||
readonly onPlate?: string;
|
||
/** Is this item a Gem? */
|
||
readonly isGem: boolean;
|
||
/** Is this item a Pokeball? */
|
||
readonly isPokeball: boolean;
|
||
|
||
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
|
||
super(data, ...moreData);
|
||
data = this;
|
||
|
||
this.fullname = `item: ${this.name}`;
|
||
this.effectType = 'Item';
|
||
this.fling = data.fling || undefined;
|
||
this.onDrive = data.onDrive || undefined;
|
||
this.onMemory = data.onMemory || undefined;
|
||
this.megaStone = data.megaStone || undefined;
|
||
this.megaEvolves = data.megaEvolves || undefined;
|
||
this.zMove = data.zMove || undefined;
|
||
this.zMoveType = data.zMoveType || undefined;
|
||
this.zMoveFrom = data.zMoveFrom || undefined;
|
||
this.itemUser = data.itemUser || undefined;
|
||
this.isBerry = !!data.isBerry;
|
||
this.ignoreKlutz = !!data.ignoreKlutz;
|
||
this.onPlate = data.onPlate || undefined;
|
||
this.isGem = !!data.isGem;
|
||
this.isPokeball = !!data.isPokeball;
|
||
|
||
if (!this.gen) {
|
||
if (this.num >= 689) {
|
||
this.gen = 7;
|
||
} else if (this.num >= 577) {
|
||
this.gen = 6;
|
||
} else if (this.num >= 537) {
|
||
this.gen = 5;
|
||
} else if (this.num >= 377) {
|
||
this.gen = 4;
|
||
} else {
|
||
this.gen = 3;
|
||
}
|
||
// Due to difference in gen 2 item numbering, gen 2 items must be
|
||
// specified manually
|
||
}
|
||
|
||
if (this.isBerry) this.fling = {basePower: 10};
|
||
if (this.id.endsWith('plate')) this.fling = {basePower: 90};
|
||
if (this.onDrive) this.fling = {basePower: 70};
|
||
if (this.megaStone) this.fling = {basePower: 80};
|
||
if (this.onMemory) this.fling = {basePower: 50};
|
||
}
|
||
}
|
||
|
||
export class Ability extends BasicEffect implements Readonly<BasicEffect & AbilityData> {
|
||
readonly effectType: 'Ability';
|
||
/** Represents how useful or detrimental this ability is. */
|
||
readonly rating: number;
|
||
/** Whether or not this ability suppresses weather. */
|
||
readonly suppressWeather: boolean;
|
||
|
||
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
|
||
super(data, ...moreData);
|
||
data = this;
|
||
|
||
this.fullname = `ability: ${this.name}`;
|
||
this.effectType = 'Ability';
|
||
this.suppressWeather = !!data.suppressWeather;
|
||
this.rating = data.rating!;
|
||
|
||
if (!this.gen) {
|
||
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 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 Species extends BasicEffect implements Readonly<BasicEffect & SpeciesData & SpeciesFormatsData> {
|
||
readonly effectType: 'Pokemon';
|
||
/**
|
||
* Species ID. Identical to ID. Note that this is the full ID, e.g.
|
||
* 'basculinbluestriped'. To get the base species ID, you need to
|
||
* manually read toID(species.baseSpecies).
|
||
*/
|
||
readonly id: ID;
|
||
/**
|
||
* Name. Note that this is the full name with forme,
|
||
* e.g. 'Basculin-Blue-Striped'. To get the name without forme, see
|
||
* `species.baseSpecies`.
|
||
*/
|
||
readonly name: string;
|
||
/**
|
||
* Base species. Species, but without the forme name.
|
||
*
|
||
* DO NOT ASSUME A POKEMON CAN TRANSFORM FROM `baseSpecies` TO
|
||
* `species`. USE `inheritsFrom` FOR THAT.
|
||
*/
|
||
readonly baseSpecies: string;
|
||
/**
|
||
* Forme name. If the forme exists,
|
||
* `species.name === species.baseSpecies + '-' + species.forme`
|
||
*
|
||
* The games make a distinction between Forme (foorumu) (legendary Pokémon)
|
||
* and Form (sugata) (non-legendary Pokémon). PS does not use the same
|
||
* distinction – they're all "Forme" to PS, reflecting current community
|
||
* use of the term.
|
||
*
|
||
* This property only tracks non-cosmetic formes, and will be `''` for
|
||
* cosmetic formes.
|
||
*/
|
||
readonly forme: string;
|
||
/**
|
||
* Base forme name (e.g. 'Altered' for Giratina).
|
||
*/
|
||
readonly baseForme: string;
|
||
/**
|
||
* Other forms. List of names of cosmetic forms. These should have
|
||
* `aliases.js` aliases to this entry, but not have their own
|
||
* entry in `pokedex.js`.
|
||
*/
|
||
readonly cosmeticFormes?: string[];
|
||
/**
|
||
* Other formes. List of names of formes, appears only on the base
|
||
* forme. Unlike forms, these have their own entry in `pokedex.js`.
|
||
*/
|
||
readonly otherFormes?: string[];
|
||
/**
|
||
* Sprite ID. Basically the same as ID, but with a dash between
|
||
* species and forme.
|
||
*/
|
||
readonly spriteid: string;
|
||
/** Abilities. */
|
||
readonly abilities: SpeciesAbility;
|
||
/** Types. */
|
||
readonly types: string[];
|
||
/** Added type (used in OMs). */
|
||
readonly addedType?: string;
|
||
/** Pre-evolution. '' if nothing evolves into this Pokemon. */
|
||
readonly prevo: ID;
|
||
/** Evolutions. Array because many Pokemon have multiple evolutions. */
|
||
readonly evos: ID[];
|
||
readonly evoType?: 'trade' | 'useItem' | 'levelMove' | 'levelExtra' | 'levelFriendship' | 'levelHold' | 'other';
|
||
readonly evoMove?: string;
|
||
/** Evolution level. falsy if doesn't evolve. */
|
||
readonly evoLevel?: number;
|
||
/** Is NFE? True if this Pokemon can evolve (Mega evolution doesn't count). */
|
||
readonly nfe: boolean;
|
||
/** Egg groups. */
|
||
readonly eggGroups: string[];
|
||
/**
|
||
* Gender. M = always male, F = always female, N = always
|
||
* genderless, '' = sometimes male sometimes female.
|
||
*/
|
||
readonly gender: GenderName;
|
||
/** Gender ratio. Should add up to 1 unless genderless. */
|
||
readonly genderRatio: {M: number, F: number};
|
||
/** Base stats. */
|
||
readonly baseStats: StatsTable;
|
||
/** Max HP. Overrides usual HP calculations (for Shedinja). */
|
||
readonly maxHP?: number;
|
||
/** Weight (in kg). Not valid for OMs; use weighthg / 10 instead. */
|
||
readonly weightkg: number;
|
||
/** Weight (in integer multiples of 0.1kg). */
|
||
readonly weighthg: number;
|
||
/** Height (in m). */
|
||
readonly heightm: number;
|
||
/** Color. */
|
||
readonly color: string;
|
||
/** Does this Pokemon have an unreleased hidden ability? */
|
||
readonly unreleasedHidden: boolean | 'Past';
|
||
/**
|
||
* Is it only possible to get the hidden ability on a male pokemon?
|
||
* This is mainly relevant to Gen 5.
|
||
*/
|
||
readonly maleOnlyHidden: boolean;
|
||
/** True if a pokemon is mega. */
|
||
readonly isMega?: boolean;
|
||
/** True if a pokemon is primal. */
|
||
readonly isPrimal?: boolean;
|
||
/** Name of its Gigantamax move, if a pokemon is gigantamax. */
|
||
readonly isGigantamax?: string;
|
||
/** True if a pokemon is a forme that is only accessible in battle. */
|
||
readonly battleOnly?: string | string[];
|
||
/** Required item. Do not use this directly; see requiredItems. */
|
||
readonly requiredItem?: string;
|
||
/** Required move. Move required to use this forme in-battle. */
|
||
readonly requiredMove?: string;
|
||
/** Required ability. Ability required to use this forme in-battle. */
|
||
readonly requiredAbility?: string;
|
||
/**
|
||
* Required items. Items required to be in this forme, e.g. a mega
|
||
* stone, or Griseous Orb. Array because Arceus formes can hold
|
||
* either a Plate or a Z-Crystal.
|
||
*/
|
||
readonly requiredItems?: string[];
|
||
|
||
/**
|
||
* Formes that can transform into this Pokemon, to inherit learnsets
|
||
* from. (Like `prevo`, but for transformations that aren't
|
||
* technically evolution. Includes in-battle transformations like
|
||
* Zen Mode and out-of-battle transformations like Rotom.)
|
||
*
|
||
* Not filled out for megas/primals - fall back to baseSpecies
|
||
* for in-battle formes.
|
||
*/
|
||
readonly inheritsFrom: ID;
|
||
|
||
/**
|
||
* Singles Tier. The Pokemon's location in the Smogon tier system.
|
||
* Do not use for LC bans (usage tier will override LC Uber).
|
||
*/
|
||
readonly tier: string;
|
||
/**
|
||
* Doubles Tier. The Pokemon's location in the Smogon doubles tier system.
|
||
* Do not use for LC bans (usage tier will override LC Uber).
|
||
*/
|
||
readonly doublesTier: string;
|
||
readonly randomBattleMoves?: readonly ID[];
|
||
readonly randomDoubleBattleMoves?: readonly ID[];
|
||
readonly exclusiveMoves?: readonly ID[];
|
||
readonly comboMoves?: readonly ID[];
|
||
readonly essentialMove?: ID;
|
||
readonly randomSets?: readonly RandomTeamsTypes.Gen2RandomSet[];
|
||
|
||
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
|
||
super(data, ...moreData);
|
||
data = this;
|
||
|
||
this.fullname = `pokemon: ${data.name}`;
|
||
this.effectType = 'Pokemon';
|
||
this.id = data.id as ID;
|
||
this.name = data.name;
|
||
this.baseSpecies = data.baseSpecies || this.name;
|
||
this.forme = data.forme || '';
|
||
this.baseForme = data.baseForme || '';
|
||
this.cosmeticFormes = data.cosmeticFormes || undefined;
|
||
this.otherFormes = data.otherFormes || undefined;
|
||
this.spriteid = data.spriteid ||
|
||
(toID(this.baseSpecies) + (this.baseSpecies !== this.name ? `-${toID(this.forme)}` : ''));
|
||
this.abilities = data.abilities || {0: ""};
|
||
this.types = data.types || ['???'];
|
||
this.addedType = data.addedType || undefined;
|
||
this.prevo = data.prevo || '';
|
||
this.tier = data.tier || '';
|
||
this.doublesTier = data.doublesTier || '';
|
||
this.evos = data.evos || [];
|
||
this.evoType = data.evoType || undefined;
|
||
this.evoMove = data.evoMove || undefined;
|
||
this.evoLevel = data.evoLevel || undefined;
|
||
this.nfe = !!this.evos.length;
|
||
this.eggGroups = data.eggGroups || [];
|
||
this.gender = data.gender || '';
|
||
this.genderRatio = data.genderRatio || (this.gender === 'M' ? {M: 1, F: 0} :
|
||
this.gender === 'F' ? {M: 0, F: 1} :
|
||
this.gender === 'N' ? {M: 0, F: 0} :
|
||
{M: 0.5, F: 0.5});
|
||
this.requiredItem = data.requiredItem || undefined;
|
||
this.requiredItems = this.requiredItems || (this.requiredItem ? [this.requiredItem] : undefined);
|
||
this.baseStats = data.baseStats || {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||
this.weightkg = data.weightkg || 0;
|
||
this.weighthg = this.weightkg * 10;
|
||
this.heightm = data.heightm || 0;
|
||
this.color = data.color || '';
|
||
this.unreleasedHidden = data.unreleasedHidden || false;
|
||
this.maleOnlyHidden = !!data.maleOnlyHidden;
|
||
this.maxHP = data.maxHP || undefined;
|
||
this.isMega = !!(this.forme && ['Mega', 'Mega-X', 'Mega-Y'].includes(this.forme)) || undefined;
|
||
this.isGigantamax = data.isGigantamax || undefined;
|
||
this.battleOnly = data.battleOnly || (this.isMega || this.isGigantamax ? this.baseSpecies : undefined);
|
||
this.inheritsFrom = data.inheritsFrom || (this.isGigantamax ? toID(this.baseSpecies) : undefined);
|
||
|
||
if (!this.gen && this.num >= 1) {
|
||
if (this.num >= 810 || ['Gmax', 'Galar', 'Galar-Zen'].includes(this.forme)) {
|
||
this.gen = 8;
|
||
} else if (this.num >= 722 || this.forme.startsWith('Alola') || this.forme === 'Starter') {
|
||
this.gen = 7;
|
||
} else if (this.forme === 'Primal') {
|
||
this.gen = 6;
|
||
this.isPrimal = true;
|
||
this.battleOnly = this.baseSpecies;
|
||
} else if (this.num >= 650 || this.isMega) {
|
||
this.gen = 6;
|
||
} else if (this.num >= 494) {
|
||
this.gen = 5;
|
||
} else if (this.num >= 387) {
|
||
this.gen = 4;
|
||
} else if (this.num >= 252) {
|
||
this.gen = 3;
|
||
} else if (this.num >= 152) {
|
||
this.gen = 2;
|
||
} else {
|
||
this.gen = 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/** Possible move flags. */
|
||
interface MoveFlags {
|
||
authentic?: 1; // Ignores a target's substitute.
|
||
bite?: 1; // Power is multiplied by 1.5 when used by a Pokemon with the Ability Strong Jaw.
|
||
bullet?: 1; // Has no effect on Pokemon with the Ability Bulletproof.
|
||
charge?: 1; // The user is unable to make a move between turns.
|
||
contact?: 1; // Makes contact.
|
||
dance?: 1; // When used by a Pokemon, other Pokemon with the Ability Dancer can attempt to execute the same move.
|
||
defrost?: 1; // Thaws the user if executed successfully while the user is frozen.
|
||
distance?: 1; // Can target a Pokemon positioned anywhere in a Triple Battle.
|
||
gravity?: 1; // Prevented from being executed or selected during Gravity's effect.
|
||
heal?: 1; // Prevented from being executed or selected during Heal Block's effect.
|
||
mirror?: 1; // Can be copied by Mirror Move.
|
||
mystery?: 1; // Unknown effect.
|
||
nonsky?: 1; // Prevented from being executed or selected in a Sky Battle.
|
||
powder?: 1; // Has no effect on Pokemon which are Grass-type, have the Ability Overcoat, or hold Safety Goggles.
|
||
protect?: 1; // Blocked by Detect, Protect, Spiky Shield, and if not a Status move, King's Shield.
|
||
pulse?: 1; // Power is multiplied by 1.5 when used by a Pokemon with the Ability Mega Launcher.
|
||
punch?: 1; // Power is multiplied by 1.2 when used by a Pokemon with the Ability Iron Fist.
|
||
recharge?: 1; // If this move is successful, the user must recharge on the following turn and cannot make a move.
|
||
reflectable?: 1; // Bounced back to the original user by Magic Coat or the Ability Magic Bounce.
|
||
snatch?: 1; // Can be stolen from the original user and instead used by another Pokemon using Snatch.
|
||
sound?: 1; // Has no effect on Pokemon with the Ability Soundproof.
|
||
}
|
||
|
||
type MoveCategory = 'Physical' | 'Special' | 'Status';
|
||
|
||
export class Move extends BasicEffect implements Readonly<BasicEffect & MoveData> {
|
||
readonly effectType: 'Move';
|
||
/** Move type. */
|
||
readonly type: string;
|
||
/** Move target. */
|
||
readonly target: MoveTarget;
|
||
/** Move base power. */
|
||
readonly basePower: number;
|
||
/** Move base accuracy. True denotes a move that always hits. */
|
||
readonly accuracy: true | number;
|
||
/** Critical hit ratio. Defaults to 1. */
|
||
readonly critRatio: number;
|
||
/** Will this move always or never be a critical hit? */
|
||
readonly willCrit?: boolean;
|
||
/** Can this move OHKO foes? */
|
||
readonly ohko?: boolean | string;
|
||
/**
|
||
* Base move type. This is the move type as specified by the games,
|
||
* tracked because it often differs from the real move type.
|
||
*/
|
||
readonly baseMoveType: string;
|
||
/**
|
||
* Secondary effect. You usually don't want to access this
|
||
* directly; but through the secondaries array.
|
||
*/
|
||
readonly secondary: SecondaryEffect | null;
|
||
/**
|
||
* Secondary effects. An array because there can be more than one
|
||
* (for instance, Fire Fang has both a burn and a flinch
|
||
* secondary).
|
||
*/
|
||
readonly secondaries: SecondaryEffect[] | null;
|
||
/**
|
||
* Move priority. Higher priorities go before lower priorities,
|
||
* trumping the Speed stat.
|
||
*/
|
||
readonly priority: number;
|
||
/** Move category. */
|
||
readonly category: MoveCategory;
|
||
/**
|
||
* Category that changes which defense to use when calculating
|
||
* move damage.
|
||
*/
|
||
readonly defensiveCategory?: MoveCategory;
|
||
/** Uses the target's Atk/SpA as the attacking stat, instead of the user's. */
|
||
readonly useTargetOffensive: boolean;
|
||
/** Use the user's Def/SpD as the attacking stat, instead of Atk/SpA. */
|
||
readonly useSourceDefensiveAsOffensive: boolean;
|
||
/** Whether or not this move ignores negative attack boosts. */
|
||
readonly ignoreNegativeOffensive: boolean;
|
||
/** Whether or not this move ignores positive defense boosts. */
|
||
readonly ignorePositiveDefensive: boolean;
|
||
/** Whether or not this move ignores attack boosts. */
|
||
readonly ignoreOffensive: boolean;
|
||
/** Whether or not this move ignores defense boosts. */
|
||
readonly ignoreDefensive: boolean;
|
||
/**
|
||
* Whether or not this move ignores type immunities. Defaults to
|
||
* true for Status moves and false for Physical/Special moves.
|
||
*/
|
||
readonly ignoreImmunity: AnyObject | boolean;
|
||
/** Base move PP. */
|
||
readonly pp: number;
|
||
/** Whether or not this move can receive PP boosts. */
|
||
readonly noPPBoosts: boolean;
|
||
/** Is this move a Z-Move? */
|
||
readonly isZ: boolean | string;
|
||
/** How many times does this move hit? */
|
||
readonly multihit?: number | number[];
|
||
/** Max/G-Max move power */
|
||
readonly gmaxPower?: number;
|
||
/** Z-move power */
|
||
readonly zMovePower?: number;
|
||
readonly flags: MoveFlags;
|
||
/** Whether or not the user must switch after using this move. */
|
||
readonly selfSwitch?: ID | boolean;
|
||
/** Move target only used by Pressure. */
|
||
readonly pressureTarget: string;
|
||
/** Move target used if the user is not a Ghost type (for Curse). */
|
||
readonly nonGhostTarget: string;
|
||
/** Whether or not the move ignores abilities. */
|
||
readonly ignoreAbility: boolean;
|
||
/**
|
||
* Move damage against the current target
|
||
* false = move will always fail with "But it failed!"
|
||
* null = move will always silently fail
|
||
* undefined = move does not deal fixed damage
|
||
*/
|
||
readonly damage: number | 'level' | false | null;
|
||
/** Whether or not this move hit multiple targets. */
|
||
readonly spreadHit: boolean;
|
||
/** Modifier that affects damage when multiple targets are hit. */
|
||
readonly spreadModifier?: number;
|
||
/** Modifier that affects damage when this move is a critical hit. */
|
||
readonly critModifier?: number;
|
||
/** Forces the move to get STAB even if the type doesn't match. */
|
||
readonly forceSTAB: boolean;
|
||
/** True if it can't be copied with Sketch. */
|
||
readonly noSketch: boolean;
|
||
/** STAB multiplier (can be modified by other effects) (default 1.5). */
|
||
readonly stab?: number;
|
||
|
||
readonly volatileStatus?: ID;
|
||
|
||
constructor(data: AnyObject, ...moreData: (AnyObject | null)[]) {
|
||
super(data, ...moreData);
|
||
data = this;
|
||
|
||
this.fullname = `move: ${this.name}`;
|
||
this.effectType = 'Move';
|
||
this.type = Tools.getString(data.type);
|
||
this.target = data.target;
|
||
this.basePower = Number(data.basePower!);
|
||
this.accuracy = data.accuracy!;
|
||
this.critRatio = Number(data.critRatio) || 1;
|
||
this.baseMoveType = Tools.getString(data.baseMoveType) || this.type;
|
||
this.secondary = data.secondary || null;
|
||
this.secondaries = data.secondaries || (this.secondary && [this.secondary]) || null;
|
||
this.priority = Number(data.priority) || 0;
|
||
this.category = data.category!;
|
||
this.defensiveCategory = data.defensiveCategory || undefined;
|
||
this.useTargetOffensive = !!data.useTargetOffensive;
|
||
this.useSourceDefensiveAsOffensive = !!data.useSourceDefensiveAsOffensive;
|
||
this.ignoreNegativeOffensive = !!data.ignoreNegativeOffensive;
|
||
this.ignorePositiveDefensive = !!data.ignorePositiveDefensive;
|
||
this.ignoreOffensive = !!data.ignoreOffensive;
|
||
this.ignoreDefensive = !!data.ignoreDefensive;
|
||
this.ignoreImmunity = (data.ignoreImmunity !== undefined ? data.ignoreImmunity : this.category === 'Status');
|
||
this.pp = Number(data.pp!);
|
||
this.noPPBoosts = !!data.noPPBoosts;
|
||
this.isZ = data.isZ || false;
|
||
this.flags = data.flags || {};
|
||
this.selfSwitch = (typeof data.selfSwitch === 'string' ? (data.selfSwitch as ID) : data.selfSwitch) || undefined;
|
||
this.pressureTarget = data.pressureTarget || '';
|
||
this.nonGhostTarget = data.nonGhostTarget || '';
|
||
this.ignoreAbility = data.ignoreAbility || false;
|
||
this.damage = data.damage!;
|
||
this.spreadHit = data.spreadHit || false;
|
||
this.forceSTAB = !!data.forceSTAB;
|
||
this.noSketch = !!data.noSketch;
|
||
this.stab = data.stab || undefined;
|
||
this.volatileStatus = typeof data.volatileStatus === 'string' ? (data.volatileStatus as ID) : undefined;
|
||
|
||
if (this.category !== 'Status' && !this.gmaxPower) {
|
||
if (!this.basePower) {
|
||
this.gmaxPower = 100;
|
||
} else if (['Fighting', 'Poison'].includes(this.type)) {
|
||
if (this.basePower >= 150) {
|
||
this.gmaxPower = 100;
|
||
} else if (this.basePower >= 110) {
|
||
this.gmaxPower = 95;
|
||
} else if (this.basePower >= 75) {
|
||
this.gmaxPower = 90;
|
||
} else if (this.basePower >= 65) {
|
||
this.gmaxPower = 85;
|
||
} else if (this.basePower >= 55) {
|
||
this.gmaxPower = 80;
|
||
} else if (this.basePower >= 45) {
|
||
this.gmaxPower = 75;
|
||
} else {
|
||
this.gmaxPower = 70;
|
||
}
|
||
} else {
|
||
if (this.basePower >= 150) {
|
||
this.gmaxPower = 150;
|
||
} else if (this.basePower >= 110) {
|
||
this.gmaxPower = 140;
|
||
} else if (this.basePower >= 75) {
|
||
this.gmaxPower = 130;
|
||
} else if (this.basePower >= 65) {
|
||
this.gmaxPower = 120;
|
||
} else if (this.basePower >= 55) {
|
||
this.gmaxPower = 110;
|
||
} else if (this.basePower >= 45) {
|
||
this.gmaxPower = 100;
|
||
} else {
|
||
this.gmaxPower = 90;
|
||
}
|
||
}
|
||
}
|
||
if (this.category !== 'Status' && !this.zMovePower) {
|
||
let basePower = this.basePower;
|
||
if (Array.isArray(this.multihit)) basePower *= 3;
|
||
if (!basePower) {
|
||
this.zMovePower = 100;
|
||
} else if (basePower >= 140) {
|
||
this.zMovePower = 200;
|
||
} else if (basePower >= 130) {
|
||
this.zMovePower = 195;
|
||
} else if (basePower >= 120) {
|
||
this.zMovePower = 190;
|
||
} else if (basePower >= 110) {
|
||
this.zMovePower = 185;
|
||
} else if (basePower >= 100) {
|
||
this.zMovePower = 180;
|
||
} else if (basePower >= 90) {
|
||
this.zMovePower = 175;
|
||
} else if (basePower >= 80) {
|
||
this.zMovePower = 160;
|
||
} else if (basePower >= 70) {
|
||
this.zMovePower = 140;
|
||
} else if (basePower >= 60) {
|
||
this.zMovePower = 120;
|
||
} else {
|
||
this.zMovePower = 100;
|
||
}
|
||
}
|
||
|
||
if (!this.gen) {
|
||
if (this.num >= 743) {
|
||
this.gen = 8;
|
||
} else if (this.num >= 622) {
|
||
this.gen = 7;
|
||
} else if (this.num >= 560) {
|
||
this.gen = 6;
|
||
} else if (this.num >= 468) {
|
||
this.gen = 5;
|
||
} else if (this.num >= 355) {
|
||
this.gen = 4;
|
||
} else if (this.num >= 252) {
|
||
this.gen = 3;
|
||
} else if (this.num >= 166) {
|
||
this.gen = 2;
|
||
} else if (this.num >= 1) {
|
||
this.gen = 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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 = Tools.getString(data.name).trim();
|
||
this.effectType = Tools.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;
|
||
}
|
||
}
|
||
|
||
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);
|
||
// }
|
||
// }
|