Spaces:
Paused
Paused
| import { Utils } from '../lib/utils'; | |
| import type { ConditionData } from './dex-conditions'; | |
| import { assignMissingFields, BasicEffect, toID } from './dex-data'; | |
| /** | |
| * Describes the acceptable target(s) of a move. | |
| * adjacentAlly - Only relevant to Doubles or Triples, the move only targets an ally of the user. | |
| * adjacentAllyOrSelf - The move can target the user or its ally. | |
| * adjacentFoe - The move can target a foe, but not (in Triples) a distant foe. | |
| * all - The move targets the field or all Pokémon at once. | |
| * allAdjacent - The move is a spread move that also hits the user's ally. | |
| * allAdjacentFoes - The move is a spread move. | |
| * allies - The move affects all active Pokémon on the user's team. | |
| * allySide - The move adds a side condition on the user's side. | |
| * allyTeam - The move affects all unfainted Pokémon on the user's team. | |
| * any - The move can hit any other active Pokémon, not just those adjacent. | |
| * foeSide - The move adds a side condition on the foe's side. | |
| * normal - The move can hit one adjacent Pokémon of your choice. | |
| * randomNormal - The move targets an adjacent foe at random. | |
| * scripted - The move targets the foe that damaged the user. | |
| * self - The move affects the user of the move. | |
| */ | |
| export type MoveTarget = | |
| 'adjacentAlly' | 'adjacentAllyOrSelf' | 'adjacentFoe' | 'all' | 'allAdjacent' | 'allAdjacentFoes' | | |
| 'allies' | 'allySide' | 'allyTeam' | 'any' | 'foeSide' | 'normal' | 'randomNormal' | 'scripted' | 'self'; | |
| /** Possible move flags. */ | |
| interface MoveFlags { | |
| allyanim?: 1; // The move plays its animation when used on an ally. | |
| bypasssub?: 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. | |
| cantusetwice?: 1; // The user cannot select this move after a previous successful use. | |
| 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. | |
| failcopycat?: 1; // Cannot be selected by Copycat. | |
| failencore?: 1; // Encore fails if target used this move. | |
| failinstruct?: 1; // Cannot be repeated by Instruct. | |
| failmefirst?: 1; // Cannot be selected by Me First. | |
| failmimic?: 1; // Cannot be copied by Mimic. | |
| futuremove?: 1; // Targets a slot, and in 2 turns damages that slot. | |
| 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. | |
| metronome?: 1; // Can be selected by Metronome. | |
| mirror?: 1; // Can be copied by Mirror Move. | |
| mustpressure?: 1; // Additional PP is deducted due to Pressure when it ordinarily would not. | |
| noassist?: 1; // Cannot be selected by Assist. | |
| nonsky?: 1; // Prevented from being executed or selected in a Sky Battle. | |
| noparentalbond?: 1; // Cannot be made to hit twice via Parental Bond. | |
| nosketch?: 1; // Cannot be copied by Sketch. | |
| nosleeptalk?: 1; // Cannot be selected by Sleep Talk. | |
| pledgecombo?: 1; // Gems will not activate. Cannot be redirected by Storm Drain / Lightning Rod. | |
| 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. | |
| slicing?: 1; // Power is multiplied by 1.5 when used by a Pokemon with the Ability Sharpness. | |
| 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. | |
| wind?: 1; // Activates the Wind Power and Wind Rider Abilities. | |
| } | |
| export interface HitEffect { | |
| onHit?: MoveEventMethods['onHit']; | |
| // set pokemon conditions | |
| boosts?: SparseBoostsTable | null; | |
| status?: string; | |
| volatileStatus?: string; | |
| // set side/slot conditions | |
| sideCondition?: string; | |
| slotCondition?: string; | |
| // set field conditions | |
| pseudoWeather?: string; | |
| terrain?: string; | |
| weather?: string; | |
| } | |
| export interface SecondaryEffect extends HitEffect { | |
| chance?: number; | |
| /** Used to flag a secondary effect as added by Poison Touch */ | |
| ability?: Ability; | |
| /** | |
| * Applies to Sparkling Aria's secondary effect: Affected by | |
| * Sheer Force but not Shield Dust. | |
| */ | |
| dustproof?: boolean; | |
| /** | |
| * Gen 2 specific mechanics: Bypasses Substitute only on Twineedle, | |
| * and allows it to flinch sleeping/frozen targets | |
| */ | |
| kingsrock?: boolean; | |
| self?: HitEffect; | |
| } | |
| export interface MoveEventMethods { | |
| basePowerCallback?: (this: Battle, pokemon: Pokemon, target: Pokemon, move: ActiveMove) => number | false | null; | |
| /** Return true to stop the move from being used */ | |
| beforeMoveCallback?: (this: Battle, pokemon: Pokemon, target: Pokemon | null, move: ActiveMove) => boolean | void; | |
| beforeTurnCallback?: (this: Battle, pokemon: Pokemon, target: Pokemon) => void; | |
| damageCallback?: (this: Battle, pokemon: Pokemon, target: Pokemon) => number | false; | |
| priorityChargeCallback?: (this: Battle, pokemon: Pokemon) => void; | |
| onDisableMove?: (this: Battle, pokemon: Pokemon) => void; | |
| onAfterHit?: CommonHandlers['VoidSourceMove']; | |
| onAfterSubDamage?: (this: Battle, damage: number, target: Pokemon, source: Pokemon, move: ActiveMove) => void; | |
| onAfterMoveSecondarySelf?: CommonHandlers['VoidSourceMove']; | |
| onAfterMoveSecondary?: CommonHandlers['VoidMove']; | |
| onAfterMove?: CommonHandlers['VoidSourceMove']; | |
| onDamagePriority?: number; | |
| onDamage?: ( | |
| this: Battle, damage: number, target: Pokemon, source: Pokemon, effect: Effect | |
| ) => number | boolean | null | void; | |
| /* Invoked by the global BasePower event (onEffect = true) */ | |
| onBasePower?: CommonHandlers['ModifierSourceMove']; | |
| onEffectiveness?: ( | |
| this: Battle, typeMod: number, target: Pokemon | null, type: string, move: ActiveMove | |
| ) => number | void; | |
| onHit?: CommonHandlers['ResultMove']; | |
| onHitField?: CommonHandlers['ResultMove']; | |
| onHitSide?: (this: Battle, side: Side, source: Pokemon, move: ActiveMove) => boolean | null | "" | void; | |
| onModifyMove?: (this: Battle, move: ActiveMove, pokemon: Pokemon, target: Pokemon | null) => void; | |
| onModifyPriority?: CommonHandlers['ModifierSourceMove']; | |
| onMoveFail?: CommonHandlers['VoidMove']; | |
| onModifyType?: (this: Battle, move: ActiveMove, pokemon: Pokemon, target: Pokemon) => void; | |
| onModifyTarget?: ( | |
| this: Battle, relayVar: { target: Pokemon }, pokemon: Pokemon, target: Pokemon, move: ActiveMove | |
| ) => void; | |
| onPrepareHit?: CommonHandlers['ResultMove']; | |
| onTry?: CommonHandlers['ResultSourceMove']; | |
| onTryHit?: CommonHandlers['ExtResultSourceMove']; | |
| onTryHitField?: CommonHandlers['ResultMove']; | |
| onTryHitSide?: (this: Battle, side: Side, source: Pokemon, move: ActiveMove) => boolean | null | "" | void; | |
| onTryImmunity?: CommonHandlers['ResultMove']; | |
| onTryMove?: CommonHandlers['ResultSourceMove']; | |
| onUseMoveMessage?: CommonHandlers['VoidSourceMove']; | |
| } | |
| export interface MoveData extends EffectData, MoveEventMethods, HitEffect { | |
| name: string; | |
| /** move index number, used for Metronome rolls */ | |
| num?: number; | |
| condition?: ConditionData; | |
| basePower: number; | |
| accuracy: true | number; | |
| pp: number; | |
| category: 'Physical' | 'Special' | 'Status'; | |
| type: string; | |
| priority: number; | |
| target: MoveTarget; | |
| flags: MoveFlags; | |
| /** Hidden Power */ | |
| realMove?: string; | |
| damage?: number | 'level' | false | null; | |
| contestType?: string; | |
| noPPBoosts?: boolean; | |
| // Z-move data | |
| // ----------- | |
| /** | |
| * ID of the Z-Crystal that calls the move. | |
| * `true` for Z-Powered status moves like Z-Encore. | |
| */ | |
| isZ?: boolean | IDEntry; | |
| zMove?: { | |
| basePower?: number, | |
| effect?: IDEntry, | |
| boost?: SparseBoostsTable, | |
| }; | |
| // Max move data | |
| // ------------- | |
| /** | |
| * `true` for Max moves like Max Airstream. If its a G-Max moves, this is | |
| * the species name of the Gigantamax Pokemon that can use this G-Max move. | |
| */ | |
| isMax?: boolean | string; | |
| maxMove?: { | |
| basePower: number, | |
| }; | |
| // Hit effects | |
| // ----------- | |
| ohko?: boolean | 'Ice'; | |
| thawsTarget?: boolean; | |
| heal?: number[] | null; | |
| forceSwitch?: boolean; | |
| selfSwitch?: 'copyvolatile' | 'shedtail' | boolean; | |
| selfBoost?: { boosts?: SparseBoostsTable }; | |
| selfdestruct?: 'always' | 'ifHit' | boolean; | |
| breaksProtect?: boolean; | |
| /** | |
| * Note that this is only "true" recoil. Other self-damage, like Struggle, | |
| * crash (High Jump Kick), Mind Blown, Life Orb, and even Substitute and | |
| * Healing Wish, are sometimes called "recoil" by the community, but don't | |
| * count as "real" recoil. | |
| */ | |
| recoil?: [number, number]; | |
| drain?: [number, number]; | |
| mindBlownRecoil?: boolean; | |
| stealsBoosts?: boolean; | |
| struggleRecoil?: boolean; | |
| secondary?: SecondaryEffect | null; | |
| secondaries?: SecondaryEffect[] | null; | |
| self?: SecondaryEffect | null; | |
| hasSheerForce?: boolean; | |
| // Hit effect modifiers | |
| // -------------------- | |
| alwaysHit?: boolean; // currently unused | |
| baseMoveType?: string; | |
| basePowerModifier?: number; | |
| critModifier?: number; | |
| critRatio?: number; | |
| /** | |
| * Pokemon for the attack stat. Ability and Item damage modifiers still come from the real attacker. | |
| */ | |
| overrideOffensivePokemon?: 'target' | 'source'; | |
| /** | |
| * Physical moves use attack stat modifiers, special moves use special attack stat modifiers. | |
| */ | |
| overrideOffensiveStat?: StatIDExceptHP; | |
| /** | |
| * Pokemon for the defense stat. Ability and Item damage modifiers still come from the real defender. | |
| */ | |
| overrideDefensivePokemon?: 'target' | 'source'; | |
| /** | |
| * uses modifiers that match the new stat | |
| */ | |
| overrideDefensiveStat?: StatIDExceptHP; | |
| forceSTAB?: boolean; | |
| ignoreAbility?: boolean; | |
| ignoreAccuracy?: boolean; | |
| ignoreDefensive?: boolean; | |
| ignoreEvasion?: boolean; | |
| ignoreImmunity?: boolean | { [typeName: string]: boolean }; | |
| ignoreNegativeOffensive?: boolean; | |
| ignoreOffensive?: boolean; | |
| ignorePositiveDefensive?: boolean; | |
| ignorePositiveEvasion?: boolean; | |
| multiaccuracy?: boolean; | |
| multihit?: number | number[]; | |
| multihitType?: 'parentalbond'; | |
| noDamageVariance?: boolean; | |
| nonGhostTarget?: MoveTarget; | |
| pressureTarget?: MoveTarget; | |
| spreadModifier?: number; | |
| sleepUsable?: boolean; | |
| /** | |
| * Will change target if current target is unavailable. (Dragon Darts) | |
| */ | |
| smartTarget?: boolean; | |
| /** | |
| * Tracks the original target through Ally Switch and other switch-out-and-back-in | |
| * situations, rather than just targeting a slot. (Stalwart, Snipe Shot) | |
| */ | |
| tracksTarget?: boolean; | |
| willCrit?: boolean; | |
| callsMove?: boolean; | |
| // Mechanics flags | |
| // --------------- | |
| hasCrashDamage?: boolean; | |
| isConfusionSelfHit?: boolean; | |
| stallingMove?: boolean; | |
| baseMove?: ID; | |
| } | |
| export type ModdedMoveData = MoveData | Partial<Omit<MoveData, 'name'>> & { | |
| inherit: true, | |
| igniteBoosted?: boolean, | |
| settleBoosted?: boolean, | |
| bodyofwaterBoosted?: boolean, | |
| longWhipBoost?: boolean, | |
| gen?: number, | |
| }; | |
| export interface MoveDataTable { [moveid: IDEntry]: MoveData } | |
| export interface ModdedMoveDataTable { [moveid: IDEntry]: ModdedMoveData } | |
| export interface Move extends Readonly<BasicEffect & MoveData> { | |
| readonly effectType: 'Move'; | |
| } | |
| interface MoveHitData { | |
| [targetSlotid: string]: { | |
| /** Did this move crit against the target? */ | |
| crit: boolean, | |
| /** The type effectiveness of this move against the target */ | |
| typeMod: number, | |
| /** | |
| * Is this move a Z-Move that broke the target's protection? | |
| * (does 0.25x regular damage) | |
| */ | |
| zBrokeProtect: boolean, | |
| }; | |
| } | |
| type MutableMove = BasicEffect & MoveData; | |
| export interface ActiveMove extends MutableMove { | |
| readonly name: string; | |
| readonly effectType: 'Move'; | |
| readonly id: ID; | |
| num: number; | |
| weather?: ID; | |
| status?: ID; | |
| hit: number; | |
| moveHitData?: MoveHitData; | |
| ability?: Ability; | |
| allies?: Pokemon[]; | |
| auraBooster?: Pokemon; | |
| causedCrashDamage?: boolean; | |
| forceStatus?: ID; | |
| hasAuraBreak?: boolean; | |
| hasBounced?: boolean; | |
| hasSheerForce?: boolean; | |
| /** Is the move called by Dancer? Used to prevent infinite Dancer recursion. */ | |
| isExternal?: boolean; | |
| lastHit?: boolean; | |
| magnitude?: number; | |
| negateSecondary?: boolean; | |
| pranksterBoosted?: boolean; | |
| selfDropped?: boolean; | |
| selfSwitch?: 'copyvolatile' | 'shedtail' | boolean; | |
| spreadHit?: boolean; | |
| statusRoll?: string; | |
| /** Hardcode to make Tera Stellar STAB work with multihit moves */ | |
| stellarBoosted?: boolean; | |
| totalDamage?: number | false; | |
| typeChangerBoosted?: Effect; | |
| willChangeForme?: boolean; | |
| infiltrates?: boolean; | |
| ruinedAtk?: Pokemon; | |
| ruinedDef?: Pokemon; | |
| ruinedSpA?: Pokemon; | |
| ruinedSpD?: Pokemon; | |
| /** | |
| * Has this move been boosted by a Z-crystal or used by a Dynamax Pokemon? Usually the same as | |
| * `isZ` or `isMax`, but hacked moves will have this be `false` and `isZ` / `isMax` be truthy. | |
| */ | |
| isZOrMaxPowered?: boolean; | |
| } | |
| type MoveCategory = 'Physical' | 'Special' | 'Status'; | |
| export class DataMove extends BasicEffect implements Readonly<BasicEffect & MoveData> { | |
| declare 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? */ | |
| declare readonly willCrit?: boolean; | |
| /** Can this move OHKO foes? */ | |
| declare readonly ohko?: boolean | 'Ice'; | |
| /** | |
| * 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; | |
| /** | |
| * Moves manually boosted by Sheer Force that don't have secondary effects. | |
| * e.g. Jet Punch | |
| */ | |
| readonly hasSheerForce: boolean; | |
| /** | |
| * Move priority. Higher priorities go before lower priorities, | |
| * trumping the Speed stat. | |
| */ | |
| readonly priority: number; | |
| /** Move category. */ | |
| readonly category: MoveCategory; | |
| /** | |
| * Pokemon for the attack stat. Ability and Item damage modifiers still come from the real attacker. | |
| */ | |
| readonly overrideOffensivePokemon?: 'target' | 'source'; | |
| /** | |
| * Physical moves use attack stat modifiers, special moves use special attack stat modifiers. | |
| */ | |
| readonly overrideOffensiveStat?: StatIDExceptHP; | |
| /** | |
| * Pokemon for the defense stat. Ability and Item damage modifiers still come from the real defender. | |
| */ | |
| readonly overrideDefensivePokemon?: 'target' | 'source'; | |
| /** | |
| * uses modifiers that match the new stat | |
| */ | |
| readonly overrideDefensiveStat?: StatIDExceptHP; | |
| /** 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. | |
| * | |
| * If an Object, its keys represent the types whose immunities are | |
| * ignored, and its values should only be true. | |
| */ | |
| readonly ignoreImmunity: { [typeName: string]: boolean } | boolean; | |
| /** Base move PP. */ | |
| readonly pp: number; | |
| /** Whether or not this move can receive PP boosts. */ | |
| readonly noPPBoosts: boolean; | |
| /** How many times does this move hit? */ | |
| declare readonly multihit?: number | number[]; | |
| /** Is this move a Z-Move? */ | |
| readonly isZ: boolean | IDEntry; | |
| /* Z-Move fields */ | |
| declare readonly zMove?: { | |
| basePower?: number, | |
| effect?: IDEntry, | |
| boost?: SparseBoostsTable, | |
| }; | |
| /** Is this move a Max move? string = Gigantamax species name */ | |
| readonly isMax: boolean | string; | |
| /** Max/G-Max move fields */ | |
| declare readonly maxMove?: { | |
| basePower: number, | |
| }; | |
| readonly flags: MoveFlags; | |
| /** Whether or not the user must switch after using this move. */ | |
| readonly selfSwitch?: 'copyvolatile' | 'shedtail' | boolean; | |
| /** Move target only used by Pressure. */ | |
| readonly pressureTarget: MoveTarget; | |
| /** Move target used if the user is not a Ghost type (for Curse). */ | |
| readonly nonGhostTarget: MoveTarget; | |
| /** 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. */ | |
| declare readonly spreadModifier?: number; | |
| /** Modifier that affects damage when this move is a critical hit. */ | |
| declare readonly critModifier?: number; | |
| /** Forces the move to get STAB even if the type doesn't match. */ | |
| readonly forceSTAB: boolean; | |
| readonly volatileStatus?: ID; | |
| constructor(data: AnyObject) { | |
| super(data); | |
| this.fullname = `move: ${this.name}`; | |
| this.effectType = 'Move'; | |
| this.type = Utils.getString(data.type); | |
| this.target = data.target; | |
| this.basePower = Number(data.basePower); | |
| this.accuracy = data.accuracy!; | |
| this.critRatio = Number(data.critRatio) || 1; | |
| this.baseMoveType = Utils.getString(data.baseMoveType) || this.type; | |
| this.secondary = data.secondary || null; | |
| this.secondaries = data.secondaries || (this.secondary && [this.secondary]) || null; | |
| this.hasSheerForce = !!(data.hasSheerForce && !this.secondaries); | |
| this.priority = Number(data.priority) || 0; | |
| this.category = data.category!; | |
| this.overrideOffensiveStat = data.overrideOffensiveStat || undefined; | |
| this.overrideOffensivePokemon = data.overrideOffensivePokemon || undefined; | |
| this.overrideDefensiveStat = data.overrideDefensiveStat || undefined; | |
| this.overrideDefensivePokemon = data.overrideDefensivePokemon || undefined; | |
| 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 ?? data.isZ); | |
| this.isZ = data.isZ || false; | |
| this.isMax = data.isMax || 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.volatileStatus = typeof data.volatileStatus === 'string' ? (data.volatileStatus as ID) : undefined; | |
| if (this.category !== 'Status' && !data.maxMove && this.id !== 'struggle') { | |
| this.maxMove = { basePower: 1 }; | |
| if (this.isMax || this.isZ) { | |
| // already initialized to 1 | |
| } else if (!this.basePower) { | |
| this.maxMove.basePower = 100; | |
| } else if (['Fighting', 'Poison'].includes(this.type)) { | |
| if (this.basePower >= 150) { | |
| this.maxMove.basePower = 100; | |
| } else if (this.basePower >= 110) { | |
| this.maxMove.basePower = 95; | |
| } else if (this.basePower >= 75) { | |
| this.maxMove.basePower = 90; | |
| } else if (this.basePower >= 65) { | |
| this.maxMove.basePower = 85; | |
| } else if (this.basePower >= 55) { | |
| this.maxMove.basePower = 80; | |
| } else if (this.basePower >= 45) { | |
| this.maxMove.basePower = 75; | |
| } else { | |
| this.maxMove.basePower = 70; | |
| } | |
| } else { | |
| if (this.basePower >= 150) { | |
| this.maxMove.basePower = 150; | |
| } else if (this.basePower >= 110) { | |
| this.maxMove.basePower = 140; | |
| } else if (this.basePower >= 75) { | |
| this.maxMove.basePower = 130; | |
| } else if (this.basePower >= 65) { | |
| this.maxMove.basePower = 120; | |
| } else if (this.basePower >= 55) { | |
| this.maxMove.basePower = 110; | |
| } else if (this.basePower >= 45) { | |
| this.maxMove.basePower = 100; | |
| } else { | |
| this.maxMove.basePower = 90; | |
| } | |
| } | |
| } | |
| if (this.category !== 'Status' && !data.zMove && !this.isZ && !this.isMax && this.id !== 'struggle') { | |
| let basePower = this.basePower; | |
| this.zMove = {}; | |
| if (Array.isArray(data.multihit)) basePower *= 3; | |
| if (!basePower) { | |
| this.zMove.basePower = 100; | |
| } else if (basePower >= 140) { | |
| this.zMove.basePower = 200; | |
| } else if (basePower >= 130) { | |
| this.zMove.basePower = 195; | |
| } else if (basePower >= 120) { | |
| this.zMove.basePower = 190; | |
| } else if (basePower >= 110) { | |
| this.zMove.basePower = 185; | |
| } else if (basePower >= 100) { | |
| this.zMove.basePower = 180; | |
| } else if (basePower >= 90) { | |
| this.zMove.basePower = 175; | |
| } else if (basePower >= 80) { | |
| this.zMove.basePower = 160; | |
| } else if (basePower >= 70) { | |
| this.zMove.basePower = 140; | |
| } else if (basePower >= 60) { | |
| this.zMove.basePower = 120; | |
| } else { | |
| this.zMove.basePower = 100; | |
| } | |
| } | |
| if (!this.gen) { | |
| // special handling for gen8 gmax moves (all of them have num 1000 but they are part of gen8) | |
| if (this.num >= 827 && !this.isMax) { | |
| this.gen = 9; | |
| } else 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; | |
| } | |
| } | |
| assignMissingFields(this, data); | |
| } | |
| } | |
| const EMPTY_MOVE = Utils.deepFreeze(new DataMove({ name: '', exists: false })); | |
| export class DexMoves { | |
| readonly dex: ModdedDex; | |
| readonly moveCache = new Map<ID, Move>(); | |
| allCache: readonly Move[] | null = null; | |
| constructor(dex: ModdedDex) { | |
| this.dex = dex; | |
| } | |
| get(name?: string | Move): Move { | |
| if (name && typeof name !== 'string') return name; | |
| const id = name ? toID(name.trim()) : '' as ID; | |
| return this.getByID(id); | |
| } | |
| getByID(id: ID): Move { | |
| if (id === '') return EMPTY_MOVE; | |
| let move = this.moveCache.get(id); | |
| if (move) return move; | |
| if (this.dex.data.Aliases.hasOwnProperty(id)) { | |
| move = this.get(this.dex.data.Aliases[id]); | |
| if (move.exists) { | |
| this.moveCache.set(id, move); | |
| } | |
| return move; | |
| } | |
| if (id.startsWith('hiddenpower')) { | |
| id = /([a-z]*)([0-9]*)/.exec(id)![1] as ID; | |
| } | |
| if (id && this.dex.data.Moves.hasOwnProperty(id)) { | |
| const moveData = this.dex.data.Moves[id] as any; | |
| const moveTextData = this.dex.getDescs('Moves', id, moveData); | |
| move = new DataMove({ | |
| name: id, | |
| ...moveData, | |
| ...moveTextData, | |
| }); | |
| if (move.gen > this.dex.gen) { | |
| (move as any).isNonstandard = 'Future'; | |
| } | |
| if (this.dex.parentMod) { | |
| // If move is exactly identical to parentMod's move, reuse parentMod's copy | |
| const parentMod = this.dex.mod(this.dex.parentMod); | |
| if (moveData === parentMod.data.Moves[id]) { | |
| const parentMove = parentMod.moves.getByID(id); | |
| if ( | |
| move.isNonstandard === parentMove.isNonstandard && | |
| move.desc === parentMove.desc && move.shortDesc === parentMove.shortDesc | |
| ) { | |
| move = parentMove; | |
| } | |
| } | |
| } | |
| } else { | |
| move = new DataMove({ | |
| name: id, exists: false, | |
| }); | |
| } | |
| if (move.exists) this.moveCache.set(id, this.dex.deepFreeze(move)); | |
| return move; | |
| } | |
| all(): readonly Move[] { | |
| if (this.allCache) return this.allCache; | |
| const moves = []; | |
| for (const id in this.dex.data.Moves) { | |
| moves.push(this.getByID(id as ID)); | |
| } | |
| this.allCache = Object.freeze(moves); | |
| return this.allCache; | |
| } | |
| } | |