From a57b936f9fb5a334fe008cfdfdd6386cb3942e77 Mon Sep 17 00:00:00 2001 From: Celestia74 <77862536+Celestia74@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:42:02 -0500 Subject: [PATCH] reset after EiKPP-1 removal of EiKPP-1, preparing for sync with upstream --- README.md | 11 +- build | 3 +- calc/src/data/eikpp_species.ts | 478 -------- calc/src/data/eikpp_types.ts | 478 -------- calc/src/mechanics/eikpp_gen789.ts | 1700 ---------------------------- src/base.template.html | 2 +- src/eipp-1.template.html | 1646 --------------------------- 7 files changed, 7 insertions(+), 4311 deletions(-) delete mode 100644 calc/src/data/eikpp_species.ts delete mode 100644 calc/src/data/eikpp_types.ts delete mode 100644 calc/src/mechanics/eikpp_gen789.ts delete mode 100644 src/eipp-1.template.html diff --git a/README.md b/README.md index 390c4eca2..f3d394d04 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -# Pokémon Damage Calculator +# Pokémon Damage Calculator for EiPP -![Test Status](https://github.com/smogon/damage-calc/workflows/Tests/badge.svg) [![npm version](https://img.shields.io/npm/v/@smogon/calc.svg)](https://www.npmjs.com/package/@smogon/calc)  -Damage calculator for all generations of Pokémon battling. +Damage calculator and soon-to-be multipurpose tool for Everyone is Pokémon Pokémon. -If you are currently looking at [smogon/damage-calc][0] and not -a fork, this is the official repository for the Pokémon Showdown! damage calculator: -https://calc.pokemonshowdown.com. +Fork of [smogon/damage-calc][0]. + +[the rest of this ReadMe is unchanged from the parent repo] This repository houses both the package implementing the core damage formula mechanics in each generation ([`@smogon/calc`][1]) as well as [logic and markup for the official UI][2]. diff --git a/build b/build index c1368f785..a71811393 100755 --- a/build +++ b/build @@ -63,6 +63,5 @@ if (process.argv[2] !== 'view') { cpdir('src', 'dist'); cpdir('calc/dist', 'dist/calc'); makeCachebuster('dist/honkalculate.template.html', 'dist/honkalculate.html'); -makeCachebuster('dist/base.template.html', 'dist/base.html'); -makeCachebuster('dist/eipp-1.template.html', 'dist/index.html'); +makeCachebuster('dist/base.template.html', 'dist/index.html'); makeCachebuster('dist/randoms.template.html', 'dist/randoms.html'); diff --git a/calc/src/data/eikpp_species.ts b/calc/src/data/eikpp_species.ts deleted file mode 100644 index 7d5b2e823..000000000 --- a/calc/src/data/eikpp_species.ts +++ /dev/null @@ -1,478 +0,0 @@ -import * as I from './interface'; -import {toID, extend, DeepPartial, assignWithout} from '../util'; - -export interface SpeciesData { - readonly types: [I.TypeName] | [I.TypeName, I.TypeName]; - // TODO: replace with baseStats - readonly bs: { - hp: number; - at: number; - df: number; - sa?: number; - sd?: number; - sp: number; - sl?: number; - }; - readonly weightkg: number; // weight - readonly nfe?: boolean; - readonly gender?: I.GenderName; - readonly otherFormes?: string[]; - readonly baseSpecies?: string; - readonly abilities?: {0: string}; // ability -} - -const RBY: {[name: string]: SpeciesData} = { - Zubat: { - types: ['Poison', 'Flying'], - bs: {hp: 40, at: 45, df: 35, sp: 55, sl: 40}, - weightkg: 7.5, - nfe: true, - }, -}; - -const GSC_PATCH: {[name: string]: DeepPartial} = { - // gen 1 pokemon changes - Zubat: {bs: {sa: 30, sd: 40}}, - // gen 2 pokemon - Ampharos: { - types: ['Electric'], - bs: {hp: 90, at: 75, df: 75, sa: 115, sd: 90, sp: 55}, - weightkg: 61.5, - }, -}; -const GSC: {[name: string]: SpeciesData} = extend(true, {}, RBY, GSC_PATCH); - -const ADV_PATCH: {[name: string]: DeepPartial} = { - // gen 1 pokemon changes - Zubat: {abilities: {0: 'Inner Focus'}}, - // gen 2 pokemon changes - Ampharos: {abilities: {0: 'Static'}}, - // gen 3 pokemon - Absol: { - types: ['Dark'], - bs: {hp: 65, at: 130, df: 60, sa: 75, sd: 60, sp: 75}, - weightkg: 47, - abilities: {0: 'Pressure'}, - }, -}; - -const ADV: {[name: string]: SpeciesData} = extend(true, {}, GSC, ADV_PATCH); - -const DPP_PATCH: {[name: string]: DeepPartial} = { - Giratina: { - types: ['Ghost', 'Dragon'], - bs: {hp: 150, at: 100, df: 120, sa: 100, sd: 120, sp: 90}, - weightkg: 750, - gender: 'N', - otherFormes: ['Giratina-Origin'], - abilities: {0: 'Pressure'}, - }, - 'Giratina-Origin': { - types: ['Ghost', 'Dragon'], - bs: {hp: 150, at: 120, df: 100, sa: 120, sd: 100, sp: 90}, - weightkg: 650, - gender: 'N', - abilities: {0: 'Levitate'}, - baseSpecies: 'Giratina', - }, -}; - -const DPP: {[name: string]: SpeciesData} = extend(true, {}, ADV, DPP_PATCH); - -const BW_PATCH: {[name: string]: DeepPartial} = { - Accelgor: { - types: ['Bug'], - bs: {hp: 80, at: 70, df: 40, sa: 100, sd: 60, sp: 145}, - weightkg: 25.3, - abilities: {0: 'Hydration'}, - }, -}; - -const BW: {[name: string]: SpeciesData} = extend(true, {}, DPP, BW_PATCH); - -const XY_PATCH: {[name: string]: DeepPartial} = { - Absol: {otherFormes: ['Absol-Mega']}, - 'Absol-Mega': { - types: ['Dark'], - bs: {hp: 65, at: 150, df: 60, sa: 115, sd: 60, sp: 115}, - weightkg: 49, - abilities: {0: 'Magic Bounce'}, - baseSpecies: 'Absol', - }, -}; - -const XY: {[name: string]: SpeciesData} = extend(true, {}, BW, XY_PATCH); - -const SM_PATCH: {[name: string]: DeepPartial} = { - Zeraora: { - types: ['Electric'], - bs: {hp: 88, at: 112, df: 75, sa: 102, sd: 80, sp: 143}, - weightkg: 44.5, - abilities: {0: 'Volt Absorb'}, - gender: 'N', - }, -}; - -const SM: {[name: string]: SpeciesData} = extend(true, {}, XY, SM_PATCH); - -const SS_PATCH: {[name: string]: DeepPartial} = { - Applin: { - types: ['Grass', 'Dragon'], - bs: {hp: 40, at: 40, df: 80, sa: 40, sd: 40, sp: 20}, - weightkg: 0.5, - abilities: {0: 'Ripen'}, - nfe: true, - }, -}; - -const SS: {[name: string]: SpeciesData} = extend(true, {}, SM, SS_PATCH); - -const PLA_PATCH: {[name: string]: DeepPartial} = { - 'Arcanine-Hisui': { - types: ['Fire', 'Rock'], - bs: {hp: 95, at: 115, df: 80, sa: 95, sd: 80, sp: 90}, - weightkg: 168, - abilities: {0: 'Intimidate'}, - }, -}; - -const SV_PATCH: {[name: string]: DeepPartial} = { - Aurogon: { - types: ['Rock', 'Dragon'], - bs: {hp: 108, at: 92, df: 77, sa: 92, sd: 88, sp: 86}, - weightkg: 153.5, - abilities: {0: 'Refrigerate'}, - }, - Benite: { - types: ['Normal', 'Flying'], - bs: {hp: 110, at: 131, df: 90, sa: 70, sd: 73, sp: 73}, - weightkg: 172.5, - abilities: {0: 'Fluffy'}, - }, - Celeroc: { - types: ['Psychic', 'Rock'], - bs: {hp: 91, at: 110, df: 76, sa: 85, sd: 88, sp: 108}, - weightkg: 15, - abilities: {0: 'Natural Cure'}, - gender: 'N', - }, - Chandeltic: { - types: ['Ghost', 'Water'], - bs: {hp: 71, at: 58, df: 82, sa: 130, sd: 101, sp: 80}, - weightkg: 98.2, - abilities: {0: 'Flash Fire'}, - }, - Cofaslash: { - types: ['Ghost', 'Steel'], - bs: {hp: 58, at: 50, df: 141, sa: 80, sd: 116, sp: 50}, - weightkg: 64.8, - abilities: {0: 'Mummy'}, - }, - Darktres: { - types: ['Dark', 'Flying'], - bs: {hp: 76, at: 96, df: 90, sa: 131, sd: 88, sp: 101}, - weightkg: 55.3, - abilities: {0: 'Bad Dreams'}, - gender: 'N', - }, - Dhelmterra: { - types: ['Ghost', 'Ground'], - bs: {hp: 78, at: 116, df: 103, sa: 82, sd: 88, sp: 50}, - weightkg: 260, - abilities: {0: 'Steelworker'}, - gender: 'N', - }, - Dragozone: { - types: ['Dragon', 'Electric'], - bs: {hp: 84, at: 91, df: 108, sa: 110, sd: 96, sp: 66}, - weightkg: 195, - abilities: {0: 'Inner Focus'}, - }, - Exeggumence: { - types: ['Grass', 'Flying'], - bs: {hp: 95, at: 121, df: 81, sa: 120, sd: 76, sp: 85}, - weightkg: 111.3, - abilities: {0: 'Chlorophyll'}, - }, - Ferrovile: { - types: ['Grass', 'Ice'], - bs: {hp: 72, at: 111, df: 87, sa: 51, sd: 105, sp: 90}, - weightkg: 72, - abilities: {0: 'Iron Barbs'}, - }, - Gardelade: { - types: ['Psychic', 'Fighting'], - bs: {hp: 68, at: 105, df: 65, sa: 105, sd: 115, sp: 80}, - weightkg: 50.2, - abilities: {0: 'Synchronize'}, - }, - Garleon: { - types: ['Dragon', 'Steel'], - bs: {hp: 100, at: 100, df: 90, sa: 90, sd: 90, sp: 74}, - weightkg: 89.8, - abilities: {0: 'Sand Veil'}, - }, - Goocor: { - types: ['Dragon', 'Flying'], - bs: {hp: 85, at: 96, df: 106, sa: 88, sd: 125, sp: 90}, - weightkg: 96.5, - abilities: {0: 'Sap Sipper'}, - }, - Hawltei: { - types: ['Fighting', 'Fire'], - bs: {hp: 90, at: 107, df: 81, sa: 79, sd: 67, sp: 106}, - weightkg: 109.8, - abilities: {0: 'Limber'}, - }, - Haxdile: { - types: ['Dragon', 'Dark'], - bs: {hp: 82, at: 127, df: 83, sa: 61, sd: 70, sp: 93}, - weightkg: 100.9, - abilities: {0: 'Rivalry'}, - }, - Krookonite: { - types: ['Ground', 'Flying'], - bs: {hp: 93, at: 128, df: 90, sa: 76, sd: 80, sp: 84}, - weightkg: 153.2, - abilities: {0: 'Intimidate'}, - }, - Kyuzor: { - types: ['Dragon', 'Steel'], - bs: {hp: 106, at: 130, df: 96, sa: 105, sd: 86, sp: 75}, - weightkg: 221.5, - abilities: {0: 'Pressure'}, - gender: 'N', - }, - Lansteel: { - types: ['Water', 'Steel'], - bs: {hp: 110, at: 69, df: 119, sa: 75, sd: 100, sp: 55}, - weightkg: 113.8, - abilities: {0: 'Volt Absorb'}, - }, - Latiacorio: { - types: ['Dragon', 'Flying'], - bs: {hp: 78, at: 73, df: 76, sa: 106, sd: 110, sp: 98}, - weightkg: 21.7, - abilities: {0: 'Levitate'}, - }, - Latianine: { - types: ['Dragon', 'Fire'], - bs: {hp: 83, at: 100, df: 83, sa: 106, sd: 113, sp: 100}, - weightkg: 97.5, - abilities: {0: 'Levitate'}, - }, - Mimirem: { - types: ['Ghost', 'Ice'], - bs: {hp: 78, at: 116, df: 86, sa: 76, sd: 100, sp: 95}, - weightkg: 162.9, - abilities: {0: 'Disguise'}, - }, - Necroluff: { - types: ['Psychic', 'Flying'], - bs: {hp: 89, at: 72, df: 80, sa: 103, sd: 91, sp: 99}, - weightkg: 116.5, - abilities: {0: 'Prism Armor'}, - gender: 'N', - }, - Noiswine: { - types: ['Flying', 'Ground'], - bs: {hp: 93, at: 110, df: 80, sa: 88, sd: 73, sp: 94}, - weightkg: 188, - abilities: {0: 'Frisk'}, - }, - Oriselia: { - types: ['Fire', 'Psychic'], - bs: {hp: 90, at: 70, df: 96, sa: 90, sd: 86, sp: 87}, - weightkg: 44.5, - abilities: {0: 'Dancer'}, - }, - Palotrum: { - types: ['Ghost', 'Dragon'], - bs: {hp: 84, at: 105, df: 116, sa: 89, sd: 69, sp: 59}, - weightkg: 260, - abilities: {0: 'Water Compaction'}, - }, - Porystar: { - types: ['Normal', 'Water'], - bs: {hp: 80, at: 66, df: 106, sa: 128, sd: 73, sp: 66}, - weightkg: 34.5, - abilities: {0: 'Adaptability'}, - gender: 'N', - }, - Porytoise: { - types: ['Normal', 'Water'], - bs: {hp: 83, at: 82, df: 90, sa: 118, sd: 85, sp: 82}, - weightkg: 60.3, - abilities: {0: 'Adaptability'}, - gender: 'N', - }, - Proboster: { - types: ['Rock', 'Ice'], - bs: {hp: 56, at: 81, df: 168, sa: 78, sd: 115, sp: 60}, - weightkg: 236.3, - abilities: {0: 'Sturdy'}, - }, - Regivenant: { - types: ['Rock', 'Ghost'], - bs: {hp: 81, at: 106, df: 117, sa: 55, sd: 94, sp: 54}, - weightkg: 150.5, - abilities: {0: 'Clear Body'}, - gender: 'N', - }, - Regixorus: { - types: ['Ice', 'Dragon'], - bs: {hp: 78, at: 114, df: 93, sa: 86, sd: 156, sp: 81}, - weightkg: 140.3, - abilities: {0: 'Clear Body'}, - gender: 'N', - }, - Scolichomp: { - types: ['Bug', 'Ground'], - bs: {hp: 76, at: 120, df: 93, sa: 63, sd: 74, sp: 105}, - weightkg: 147.8, - abilities: {0: 'Poison Point'}, - }, - Spiridra: { - types: ['Dark', 'Dragon'], - bs: {hp: 63, at: 97, df: 82, sa: 98, sd: 122, sp: 65}, - weightkg: 129.3, - abilities: {0: 'Pressure'}, - }, - Syldily: { - types: ['Fairy', 'Grass'], - bs: {hp: 92, at: 75, df: 86, sa: 100, sd: 122, sp: 48}, - weightkg: 42, - abilities: {0: 'Cute Charm'}, - }, - Talonperior: { - types: ['Fire', 'Rock'], - bs: {hp: 90, at: 120, df: 110, sa: 67, sd: 64, sp: 68}, - weightkg: 153.7, - abilities: {0: 'Flame Body'}, - }, - 'Test-Mon': { - types: ['Normal', 'Ghost'], - bs: {hp: 60, at: 60, df: 60, sa: 60, sd: 60, sp: 60}, - weightkg: 60, - abilities: {0: 'Normalize'}, - }, - Togeki: { - types: ['Fairy', 'Steel'], - bs: {hp: 75, at: 70, df: 92, sa: 106, sd: 105, sp: 76}, - weightkg: 20.5, - abilities: {0: 'Hustle'}, - }, - Togemagius: { - types: ['Fairy', 'Ghost'], - bs: {hp: 76, at: 56, df: 71, sa: 115, sd: 111, sp: 96}, - weightkg: 21.2, - abilities: {0: 'Hustle'}, - }, - Tortgron: { - types: ['Grass', 'Rock'], - bs: {hp: 86, at: 109, df: 155, sa: 70, sd: 76, sp: 52}, - weightkg: 335, - abilities: {0: 'Overgrow'}, - }, - Volcadactyl: { - types: ['Bug', 'Flying'], - bs: {hp: 83, at: 90, df: 65, sa: 110, sd: 95, sp: 120}, - weightkg: 52.5, - abilities: {0: 'Flame Body'}, - }, - Volcantrum: { - types: ['Bug', 'Dragon'], - bs: {hp: 84, at: 100, df: 101, sa: 113, sd: 89, sp: 80}, - weightkg: 158, - abilities: {0: 'Flame Body'}, - }, -}; - - -const SV: {[name: string]: SpeciesData} = extend(true, {}, SS, SV_PATCH, PLA_PATCH); - -delete SV['Zubat']; -delete SV['Ampharos']; -delete SV['Absol']; -delete SV['Absol-Mega']; -delete SV['Giratina']; -delete SV['Giratina-Origin']; -delete SV['Accelgor']; -delete SV['Zeraora']; -delete SV['Applin']; -delete SV ['Arcanine-Hisui']; - -export const SPECIES = [{}, RBY, GSC, ADV, DPP, BW, XY, SM, SS, SV]; - -export class Species implements I.Species { - private readonly gen: I.GenerationNum; - - constructor(gen: I.GenerationNum) { - this.gen = gen; - } - - get(id: I.ID) { - return SPECIES_BY_ID[this.gen][id]; - } - - *[Symbol.iterator]() { - for (const id in SPECIES_BY_ID[this.gen]) { - yield this.get(id as I.ID)!; - } - } -} - -class Specie implements I.Specie { - readonly kind: 'Species'; - readonly id: I.ID; - readonly name: I.SpeciesName; - readonly types!: [I.TypeName] | [I.TypeName, I.TypeName]; - readonly baseStats: Readonly; - readonly weightkg!: number; // weight - readonly nfe?: boolean; - readonly gender?: I.GenderName; - readonly otherFormes?: I.SpeciesName[]; - readonly baseSpecies?: I.SpeciesName; - readonly abilities?: {0: I.AbilityName}; // ability - - private static readonly EXCLUDE = new Set(['bs', 'otherFormes']); - - constructor(name: string, data: SpeciesData) { - this.kind = 'Species'; - this.id = toID(name); - this.name = name as I.SpeciesName; - - const baseStats: Partial = {}; - baseStats.hp = data.bs.hp; - baseStats.atk = data.bs.at; - baseStats.def = data.bs.df; - baseStats.spa = gen >= 2 ? data.bs.sa : data.bs.sl; - baseStats.spd = gen >= 2 ? data.bs.sd : data.bs.sl; - baseStats.spe = data.bs.sp; - this.baseStats = baseStats as I.StatsTable; - // Hack for getting Gmax pokemon out of existence in Gen 9+ - if (data.otherFormes) { - this.otherFormes = data.otherFormes as I.SpeciesName[]; - if (gen >= 9 && !['toxtricity', 'urshifu'].includes(this.id)) { - this.otherFormes = this.otherFormes.filter(f => !f.endsWith('-Gmax')); - if (!this.otherFormes.length) this.otherFormes = undefined; - if (this.otherFormes) this.otherFormes = [...new Set(this.otherFormes)]; - } - } - - assignWithout(this, data, Specie.EXCLUDE); - } -} -const SPECIES_BY_ID: Array<{[id: string]: Specie}> = []; - -let gen = 0; -for (const species of SPECIES) { - const map: {[id: string]: Specie} = {}; - for (const specie in species) { - if (gen >= 2 && species[specie].bs.sl) delete species[specie].bs.sl; - const m = new Specie(specie, species[specie]); - map[m.id] = m; - } - SPECIES_BY_ID.push(map); - gen++; -} diff --git a/calc/src/data/eikpp_types.ts b/calc/src/data/eikpp_types.ts deleted file mode 100644 index 1ac5f5c19..000000000 --- a/calc/src/data/eikpp_types.ts +++ /dev/null @@ -1,478 +0,0 @@ -import * as I from './interface'; -import {toID, extend} from '../util'; - -export type TypeChart = { - [type in I.TypeName]?: {[type in I.TypeName]?: number}; -}; - -const RBY: TypeChart = { - '???': { - Normal: 1, - Grass: 1, - Fire: 1, - Water: 1, - Electric: 1, - Ice: 1, - Flying: 1, - Bug: 1, - Poison: 1, - Ground: 1, - Rock: 1, - Fighting: 1, - Psychic: 1, - Ghost: 1, - Dragon: 1, - }, - Normal: { - '???': 1, - Normal: 0.5, - Grass: 1, - Fire: 2, - Water: 1, - Electric: 2, - Ice: 0.5, - Flying: 1, - Bug: 1, - Poison: 1, - Ground: 0.5, - Rock: 0.5, - Fighting: 1, - Psychic: 2, - Ghost: 1, - Dragon: 2, - }, - Grass: { - '???': 1, - Normal: 2, - Grass: 1, - Fire: 1, - Water: 1, - Electric: 0.5, - Ice: 1, - Flying: 1, - Bug: 1, - Poison: 2, - Ground: 2, - Rock: 0.5, - Fighting: 1, - Psychic: 1, - Ghost: 1, - Dragon: 2, - }, - Fire: { - '???': 1, - Normal: 1, - Grass: 2, - Fire: 0.5, - Water: 1, - Electric: 2, - Ice: 2, - Flying: 0.5, - Bug: 1, - Poison: 0.5, - Ground: 0.5, - Rock: 0.5, - Fighting: 1, - Psychic: 0.5, - Ghost: 1, - Dragon: 0.5, - }, - Water: { - '???': 1, - Normal: 1, - Grass: 1, - Fire: 1, - Water: 1, - Electric: 1, - Ice: 1, - Flying: 0.5, - Bug: 2, - Poison: 1, - Ground: 0.5, - Rock: 0.5, - Fighting: 1, - Psychic: 2, - Ghost: 1, - Dragon: 1, - }, - Electric: { - '???': 1, - Normal: 1, - Grass: 2, - Fire: 0.5, - Water: 1, - Electric: 1, - Ice: 1, - Flying: 2, - Bug: 1, - Poison: 0.5, - Ground: 2, - Rock: 2, - Fighting: 1, - Psychic: 1, - Ghost: 1, - Dragon: 0, - }, - Ice: { - '???': 1, - Normal: 1, - Grass: 2, - Fire: 0.5, - Water: 1, - Electric: 2, - Ice: 0.5, - Flying: 1, - Bug: 1, - Poison: 1, - Ground: 2, - Rock: 1, - Fighting: 1, - Psychic: 0.5, - Ghost: 1, - Dragon: 1, - }, - Flying: { - '???': 1, - Normal: 1, - Grass: 0.5, - Fire: 2, - Water: 2, - Electric: 0.5, - Ice: 1, - Flying: 0.5, - Bug: 1, - Poison: 1, - Ground: 1, - Rock: 0, - Fighting: 1, - Psychic: 1, - Ghost: 1, - Dragon: 1, - }, - Bug: { - '???': 1, - Normal: 1, - Grass: 1, - Fire: 1, - Water: 0.5, - Electric: 1, - Ice: 1, - Flying: 1, - Bug: 0.5, - Poison: 1, - Ground: 1, - Rock: 1, - Fighting: 1, - Psychic: 1, - Ghost: 2, - Dragon: 1, - }, - Poison: { - '???': 1, - Normal: 1, - Grass: 1, - Fire: 2, - Water: 0.5, - Electric: 1, - Ice: 1, - Flying: 0.5, - Bug: 2, - Poison: 1, - Ground: 0.5, - Rock: 0.5, - Fighting: 1, - Psychic: 1, - Ghost: 2, - Dragon: 0.5, - }, - Ground: { - '???': 1, - Normal: 2, - Grass: 0.5, - Fire: 2, - Water: 1, - Electric: 1, - Ice: 0.5, - Flying: 1, - Bug: 1, - Poison: 2, - Ground: 0.5, - Rock: 2, - Fighting: 1, - Psychic: 0.5, - Ghost: 1, - Dragon: 1, - }, - Rock: { - '???': 1, - Normal: 2, - Grass: 2, - Fire: 1, - Water: 2, - Electric: 1, - Ice: 0.5, - Flying: 1, - Bug: 1, - Poison: 1, - Ground: 0.5, - Rock: 0.5, - Fighting: 1, - Psychic: 1, - Ghost: 1, - Dragon: 1, - }, - Fighting: { - '???': 1, - Normal: 1, - Grass: 0.5, - Fire: 1, - Water: 1, - Electric: 1, - Ice: 1, - Flying: 1, - Bug: 1, - Poison: 1, - Ground: 1, - Rock: 0.5, - Fighting: 1, - Psychic: 1, - Ghost: 1, - Dragon: 1, - }, - Psychic: { - '???': 1, - Normal: 1, - Grass: 1, - Fire: 1, - Water: 0, - Electric: 1, - Ice: 1, - Flying: 1, - Bug: 1, - Poison: 1, - Ground: 1, - Rock: 0.5, - Fighting: 1, - Psychic: 2, - Ghost: 1, - Dragon: 1, - }, - Ghost: { - '???': 1, - Normal: 1, - Grass: 1, - Fire: 1, - Water: 1, - Electric: 1, - Ice: 1, - Flying: 2, - Bug: 0, - Poison: 1, - Ground: 1, - Rock: 0.5, - Fighting: 1, - Psychic: 1, - Ghost: 0.5, - Dragon: 1, - }, - Dragon: { - '???': 1, - Normal: 1, - Grass: 0.5, - Fire: 2, - Water: 1, - Electric: 1, - Ice: 1, - Flying: 1, - Bug: 1, - Poison: 2, - Ground: 1, - Rock: 0.5, - Fighting: 1, - Psychic: 1, - Ghost: 1, - Dragon: 1, - }, -}; - -const GSC: TypeChart = extend(true, {}, RBY, { - '???': {Dark: 1, Steel: 1}, - Normal: {Dark: 1, Steel: 1}, - Grass: {Dark: 0.5, Steel: 1}, - Fire: {Dark: 1, Steel: 1}, - Water: {Dark: 2, Steel: 1}, - Electric: {Dark: 1, Steel: 1}, - Ice: {Dark: 1, Steel: 1}, - Flying: {Dark: 1, Steel: 0}, - Bug: {Dark: 0.5, Steel: 2}, - Poison: {Dark: 0.5, Steel: 0}, - Ground: {Dark: 1, Steel: 1}, - Rock: {Dark: 1, Steel: 1}, - Fighting: {Dark: 1, Steel: 0}, - Psychic: {Dark: 1, Steel: 1}, - Ghost: {Dark: 2, Steel: 1}, - Dragon: {Dark: 2, Steel: 1}, - Dark: { - '???': 1, - Normal: 2, - Grass: 1, - Fire: 1, - Water: 0.5, - Electric: 1, - Ice: 1, - Flying: 0.5, - Bug: 2, - Poison: 0.5, - Ground: 1, - Rock: 2, - Fighting: 2, - Psychic: 1, - Ghost: 0.5, - Dragon: 0.5, - Dark: 1, - Steel: 0, - }, - Steel: { - '???': 1, - Normal: 1, - Grass: 1, - Fire: 1, - Water: 1, - Electric: 1, - Ice: 1, - Flying: 1, - Bug: 0.5, - Poison: 1, - Ground: 1, - Rock: 1, - Fighting: 0, - Psychic: 1, - Ghost: 2, - Dragon: 1, - Dark: 1, - Steel: 2, - }, -}); - -const ADV = GSC; - -const DPP = GSC; - -const BW = GSC; - -const XY: TypeChart = extend(true, {}, GSC, { - '???': {Fairy: 1}, - Normal: {Fairy: 1}, - Grass: {Fairy: 1}, - Fire: {Fairy: 1}, - Water: {Fairy: 1}, - Electric: {Fairy: 2}, - Ice: {Fairy: 1}, - Flying: {Fairy: 1}, - Bug: {Fairy: 1}, - Poison: {Fairy: 1}, - Ground: {Fairy: 1}, - Rock: {Fairy: 0.5}, - Fighting: {Fairy: 1}, - Psychic: {Fairy: 1}, - Ghost: {Fairy: 1}, - Dragon: {Fairy: 0.5}, - Dark: {Fairy: 1}, - Steel: {Fairy: 1}, - Fairy: { - '???': 1, - Normal: 1, - Grass: 1, - Fire: 0.5, - Water: 1, - Electric: 1, - Ice: 2, - Flying: 1, - Bug: 1, - Poison: 1, - Ground: 1, - Rock: 1, - Fighting: 1, - Psychic: 0.5, - Ghost: 1, - Dragon: 2, - Dark: 1, - Steel: 1, - Fairy: 0.5, - }, -}); - -const SM = XY; - -const SS = SM; - -const SV: TypeChart = extend(true, {}, SS, { - '???': {Stellar: 1}, - Normal: {Stellar: 1}, - Grass: {Stellar: 1}, - Fire: {Stellar: 1}, - Water: {Stellar: 1}, - Electric: {Stellar: 1}, - Ice: {Stellar: 1}, - Flying: {Stellar: 1}, - Bug: {Stellar: 1}, - Poison: {Stellar: 1}, - Ground: {Stellar: 1}, - Rock: {Stellar: 1}, - Fighting: {Stellar: 1}, - Psychic: {Stellar: 1}, - Ghost: {Stellar: 1}, - Dragon: {Stellar: 1}, - Dark: {Stellar: 1}, - Steel: {Stellar: 1}, - Fairy: {Stellar: 1}, - Stellar: { - '???': 1, - }, -}); - -export const TYPE_CHART = [{}, RBY, GSC, ADV, DPP, BW, XY, SM, SS, SV]; - -export class Types implements I.Types { - private readonly gen: I.GenerationNum; - - constructor(gen: I.GenerationNum) { - this.gen = gen; - } - - get(id: I.ID) { - // toID('???') => '', as do many other things, but returning the '???' type seems appropriate :) - return TYPES_BY_ID[this.gen][id]; - } - - *[Symbol.iterator]() { - for (const id in TYPES_BY_ID[this.gen]) { - yield this.get(id as I.ID)!; - } - } -} - -class Type implements I.Type { - readonly kind: 'Type'; - readonly id: I.ID; - readonly name: I.TypeName; - readonly effectiveness: Readonly<{[type in I.TypeName]?: I.TypeEffectiveness}>; - - constructor(name: string, effectiveness: TypeChart[I.TypeName]) { - this.kind = 'Type'; - this.id = toID(name); - this.name = name as I.TypeName; - this.effectiveness = effectiveness! as {[type in I.TypeName]?: I.TypeEffectiveness}; - } -} - -const TYPES_BY_ID: Array<{[id: string]: Type}> = []; - -for (const typeChart of TYPE_CHART) { - const map: {[id: string]: Type} = {}; - for (const type in typeChart) { - const t = new Type(type, {...typeChart[type as I.TypeName]!}); - map[t.id] = t; - } - TYPES_BY_ID.push(map); -} diff --git a/calc/src/mechanics/eikpp_gen789.ts b/calc/src/mechanics/eikpp_gen789.ts deleted file mode 100644 index cbdae9f89..000000000 --- a/calc/src/mechanics/eikpp_gen789.ts +++ /dev/null @@ -1,1700 +0,0 @@ -import {Generation, AbilityName, StatID, Terrain} from '../data/interface'; -import {toID} from '../util'; -import { - getBerryResistType, - getFlingPower, - getItemBoostType, - getMultiAttack, - getNaturalGift, - getTechnoBlast, - SEED_BOOSTED_STAT, -} from '../items'; -import {RawDesc} from '../desc'; -import {Field} from '../field'; -import {Move} from '../move'; -import {Pokemon} from '../pokemon'; -import {Result} from '../result'; -import { - chainMods, - checkAirLock, - checkDauntlessShield, - checkDownload, - checkEmbody, - checkForecast, - checkInfiltrator, - checkIntimidate, - checkIntrepidSword, - checkItem, - checkMultihitBoost, - checkSeedBoost, - checkTeraformZero, - checkWonderRoom, - computeFinalStats, - countBoosts, - getBaseDamage, - getEVDescriptionText, - getFinalDamage, - getModifiedStat, - getQPBoostedStat, - getMoveEffectiveness, - getShellSideArmCategory, - getWeightFactor, - handleFixedDamageMoves, - isGrounded, - OF16, OF32, - pokeRound, - isQPActive, -} from './util'; - -export function calculateSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - move: Move, - field: Field -) { - // #region Initial - - checkAirLock(attacker, field); - checkAirLock(defender, field); - checkTeraformZero(attacker, field); - checkTeraformZero(defender, field); - checkForecast(attacker, field.weather); - checkForecast(defender, field.weather); - checkItem(attacker, field.isMagicRoom); - checkItem(defender, field.isMagicRoom); - checkWonderRoom(attacker, field.isWonderRoom); - checkWonderRoom(defender, field.isWonderRoom); - checkSeedBoost(attacker, field); - checkSeedBoost(defender, field); - checkDauntlessShield(attacker, gen); - checkDauntlessShield(defender, gen); - checkEmbody(attacker, gen); - checkEmbody(defender, gen); - - computeFinalStats(gen, attacker, defender, field, 'def', 'spd', 'spe'); - - checkIntimidate(gen, attacker, defender); - checkIntimidate(gen, defender, attacker); - checkDownload(attacker, defender, field.isWonderRoom); - checkDownload(defender, attacker, field.isWonderRoom); - checkIntrepidSword(attacker, gen); - checkIntrepidSword(defender, gen); - - computeFinalStats(gen, attacker, defender, field, 'atk', 'spa'); - - checkInfiltrator(attacker, field.defenderSide); - checkInfiltrator(defender, field.attackerSide); - - const desc: RawDesc = { - attackerName: attacker.name, - attackerTera: attacker.teraType, - moveName: move.name, - defenderName: defender.name, - defenderTera: defender.teraType, - isDefenderDynamaxed: defender.isDynamaxed, - isWonderRoom: field.isWonderRoom, - }; - - const result = new Result(gen, attacker, defender, move, field, 0, desc); - - if (move.category === 'Status' && !move.named('Nature Power')) { - return result; - } - - const breaksProtect = move.breaksProtect || move.isZ || attacker.isDynamaxed || - (attacker.hasAbility('Unseen Fist') && move.flags.contact); - - if (field.defenderSide.isProtected && !breaksProtect) { - desc.isProtected = true; - return result; - } - - const defenderIgnoresAbility = defender.hasAbility( - 'Full Metal Body', - 'Neutralizing Gas', - 'Prism Armor', - 'Shadow Shield' - ); - - const attackerIgnoresAbility = attacker.hasAbility('Mold Breaker', 'Teravolt', 'Turboblaze'); - const moveIgnoresAbility = move.named( - 'G-Max Drum Solo', - 'G-Max Fire Ball', - 'G-Max Hydrosnipe', - 'Light That Burns the Sky', - 'Menacing Moonraze Maelstrom', - 'Moongeist Beam', - 'Photon Geyser', - 'Searing Sunraze Smash', - 'Sunsteel Strike' - ); - if (!defenderIgnoresAbility && !defender.hasAbility('Poison Heal') && - (attackerIgnoresAbility || moveIgnoresAbility)) { - if (attackerIgnoresAbility) desc.attackerAbility = attacker.ability; - if (defender.hasItem('Ability Shield')) { - desc.defenderItem = defender.item; - } else { - defender.ability = '' as AbilityName; - } - } - - // Merciless does not ignore Shell Armor, damage dealt to a poisoned Pokemon with Shell Armor - // will not be a critical hit (UltiMario) - const isCritical = !defender.hasAbility('Battle Armor', 'Shell Armor') && - (move.isCrit || (attacker.hasAbility('Merciless') && defender.hasStatus('psn', 'tox'))) && - move.timesUsed === 1; - - let type = move.type; - if (move.named('Weather Ball')) { - const holdingUmbrella = attacker.hasItem('Utility Umbrella'); - type = - field.hasWeather('Sun', 'Harsh Sunshine') && !holdingUmbrella ? 'Fire' - : field.hasWeather('Rain', 'Heavy Rain') && !holdingUmbrella ? 'Water' - : field.hasWeather('Sand') ? 'Rock' - : field.hasWeather('Hail', 'Snow') ? 'Ice' - : 'Normal'; - desc.weather = field.weather; - desc.moveType = type; - } else if (move.named('Judgment') && attacker.item && attacker.item.includes('Plate')) { - type = getItemBoostType(attacker.item)!; - } else if (move.named('Techno Blast') && attacker.item && attacker.item.includes('Drive')) { - type = getTechnoBlast(attacker.item)!; - } else if (move.named('Multi-Attack') && attacker.item && attacker.item.includes('Memory')) { - type = getMultiAttack(attacker.item)!; - } else if (move.named('Natural Gift') && attacker.item && attacker.item.includes('Berry')) { - const gift = getNaturalGift(gen, attacker.item)!; - type = gift.t; - desc.moveType = type; - desc.attackerItem = attacker.item; - } else if ( - move.named('Nature Power') || - (move.named('Terrain Pulse') && isGrounded(attacker, field)) - ) { - type = - field.hasTerrain('Electric') ? 'Electric' - : field.hasTerrain('Grassy') ? 'Grass' - : field.hasTerrain('Misty') ? 'Fairy' - : field.hasTerrain('Psychic') ? 'Psychic' - : 'Normal'; - desc.terrain = field.terrain; - desc.moveType = type; - } else if (move.named('Revelation Dance')) { - if (attacker.teraType) { - type = attacker.teraType; - } else { - type = attacker.types[0]; - } - } else if (move.named('Aura Wheel')) { - if (attacker.named('Morpeko')) { - type = 'Electric'; - } else if (attacker.named('Morpeko-Hangry')) { - type = 'Dark'; - } - } else if (move.named('Raging Bull')) { - if (attacker.named('Tauros-Paldea-Combat')) { - type = 'Fighting'; - } else if (attacker.named('Tauros-Paldea-Blaze')) { - type = 'Fire'; - } else if (attacker.named('Tauros-Paldea-Aqua')) { - type = 'Water'; - } - } else if (move.named('Ivy Cudgel')) { - if (attacker.name.includes('Ogerpon-Cornerstone')) { - type = 'Rock'; - } else if (attacker.name.includes('Ogerpon-Hearthflame')) { - type = 'Fire'; - } else if (attacker.name.includes('Ogerpon-Wellspring')) { - type = 'Water'; - } - } - - let hasAteAbilityTypeChange = false; - let isAerilate = false; - let isPixilate = false; - let isRefrigerate = false; - let isGalvanize = false; - let isLiquidVoice = false; - let isNormalize = false; - const noTypeChange = move.named( - 'Revelation Dance', - 'Judgment', - 'Nature Power', - 'Techno Blast', - 'Multi Attack', - 'Natural Gift', - 'Weather Ball', - 'Terrain Pulse', - 'Struggle', - ) || (move.named('Tera Blast') && attacker.teraType); - - if (!move.isZ && !noTypeChange) { - const normal = move.hasType('Normal'); - if ((isAerilate = attacker.hasAbility('Aerilate') && normal)) { - type = 'Flying'; - } else if ((isGalvanize = attacker.hasAbility('Galvanize') && normal)) { - type = 'Electric'; - } else if ((isLiquidVoice = attacker.hasAbility('Liquid Voice') && !!move.flags.sound)) { - type = 'Water'; - } else if ((isPixilate = attacker.hasAbility('Pixilate') && normal)) { - type = 'Fairy'; - } else if ((isRefrigerate = attacker.hasAbility('Refrigerate') && normal)) { - type = 'Ice'; - } else if ((isNormalize = attacker.hasAbility('Normalize'))) { // Boosts any type - type = 'Normal'; - } - if (isGalvanize || isPixilate || isRefrigerate || isAerilate || isNormalize) { - desc.attackerAbility = attacker.ability; - hasAteAbilityTypeChange = true; - } else if (isLiquidVoice) { - desc.attackerAbility = attacker.ability; - } - } - - if (move.named('Tera Blast') && attacker.teraType) { - type = attacker.teraType; - } - - move.type = type; - - // FIXME: this is incorrect, should be move.flags.heal, not move.drain - if ((attacker.hasAbility('Triage') && move.drain) || - (attacker.hasAbility('Gale Wings') && - move.hasType('Flying') && - attacker.curHP() === attacker.maxHP())) { - move.priority = 1; - desc.attackerAbility = attacker.ability; - } - - const isGhostRevealed = - attacker.hasAbility('Scrappy') || attacker.hasAbility('Mind\'s Eye') || - field.defenderSide.isForesight; - const isRingTarget = - defender.hasItem('Ring Target') && !defender.hasAbility('Klutz'); - const type1Effectiveness = getMoveEffectiveness( - gen, - move, - defender.types[0], - isGhostRevealed, - field.isGravity, - isRingTarget - ); - const type2Effectiveness = defender.types[1] - ? getMoveEffectiveness( - gen, - move, - defender.types[1], - isGhostRevealed, - field.isGravity, - isRingTarget - ) - : 1; - let typeEffectiveness = type1Effectiveness * type2Effectiveness; - - if (defender.teraType && defender.teraType !== 'Stellar') { - typeEffectiveness = getMoveEffectiveness( - gen, - move, - defender.teraType, - isGhostRevealed, - field.isGravity, - isRingTarget - ); - } - - if (typeEffectiveness === 0 && move.hasType('Ground') && - defender.hasItem('Iron Ball') && !defender.hasAbility('Klutz')) { - typeEffectiveness = 1; - } - - if (typeEffectiveness === 0 && move.named('Thousand Arrows')) { - typeEffectiveness = 1; - } - - if (typeEffectiveness === 0) { - return result; - } - - if ((move.named('Sky Drop') && - (defender.hasType('Flying') || defender.weightkg >= 200 || field.isGravity)) || - (move.named('Synchronoise') && !defender.hasType(attacker.types[0]) && - (!attacker.types[1] || !defender.hasType(attacker.types[1]))) || - (move.named('Dream Eater') && - (!(defender.hasStatus('slp') || defender.hasAbility('Comatose')))) || - (move.named('Steel Roller') && !field.terrain) || - (move.named('Poltergeist') && (!defender.item || isQPActive(defender, field))) - ) { - return result; - } - - if ( - (field.hasWeather('Harsh Sunshine') && move.hasType('Water')) || - (field.hasWeather('Heavy Rain') && move.hasType('Fire')) - ) { - desc.weather = field.weather; - return result; - } - - if (field.hasWeather('Strong Winds') && defender.hasType('Flying') && - gen.types.get(toID(move.type))!.effectiveness['Flying']! > 1) { - typeEffectiveness /= 2; - desc.weather = field.weather; - } - - if (move.type === 'Stellar') { - typeEffectiveness = !defender.teraType ? 1 : 2; - } - - // Tera Shell works only at full HP, but for all hits of multi-hit moves - if (defender.hasAbility('Tera Shell') && - defender.curHP() === defender.maxHP() && - (!field.defenderSide.isSR && (!field.defenderSide.spikes || defender.hasType('Flying')) || - defender.hasItem('Heavy-Duty Boots')) - ) { - typeEffectiveness = 0.5; - desc.defenderAbility = defender.ability; - } - - if ((defender.hasAbility('Wonder Guard') && typeEffectiveness <= 1) || - (move.hasType('Fire') && defender.hasAbility('Sap Sipper')) || - (move.hasType('Ground') && defender.hasAbility('Flash Fire', 'Well-Baked Body')) || - (move.hasType('Ice') && defender.hasAbility('Dry Skin', 'Storm Drain', 'Water Absorb')) || - (move.hasType('Fairy') && - defender.hasAbility('Lightning Rod', 'Motor Drive', 'Volt Absorb')) || - (move.hasType('Electric') && - !field.isGravity && !move.named('Thousand Arrows') && - !defender.hasItem('Iron Ball') && defender.hasAbility('Levitate')) || - (move.flags.bullet && defender.hasAbility('Bulletproof')) || - (move.flags.sound && !move.named('Clangorous Soul') && defender.hasAbility('Soundproof')) || - (move.priority > 0 && defender.hasAbility('Queenly Majesty', 'Dazzling', 'Armor Tail')) || - (move.hasType('Electric') && defender.hasAbility('Earth Eater')) || - (move.flags.wind && defender.hasAbility('Wind Rider')) - ) { - desc.defenderAbility = defender.ability; - return result; - } - if (typeEffectiveness === 4) { - desc.alliesFainted = 3; - } - - if (move.hasType('Ground') && !move.named('Thousand Arrows') && - !field.isGravity && defender.hasItem('Air Balloon')) { - desc.defenderItem = defender.item; - return result; - } - - if (move.priority > 0 && field.hasTerrain('Psychic') && isGrounded(defender, field)) { - desc.terrain = field.terrain; - return result; - } - - const weightBasedMove = move.named('Heat Crash', 'Heavy Slam', 'Low Kick', 'Grass Knot'); - if (defender.isDynamaxed && weightBasedMove) { - return result; - } - - desc.HPEVs = `${defender.evs.hp} HP`; - - const fixedDamage = handleFixedDamageMoves(attacker, move); - if (fixedDamage) { - if (attacker.hasAbility('Parental Bond')) { - result.damage = [fixedDamage, fixedDamage]; - desc.attackerAbility = attacker.ability; - } else { - result.damage = fixedDamage; - } - return result; - } - - if (move.named('Final Gambit')) { - result.damage = attacker.curHP(); - return result; - } - - if (move.named('Guardian of Alola')) { - let zLostHP = Math.floor((defender.curHP() * 3) / 4); - if (field.defenderSide.isProtected && attacker.item && attacker.item.includes(' Z')) { - zLostHP = Math.ceil(zLostHP / 4 - 0.5); - } - result.damage = zLostHP; - return result; - } - - if (move.named('Nature\'s Madness')) { - const lostHP = field.defenderSide.isProtected ? 0 : Math.floor(defender.curHP() / 2); - result.damage = lostHP; - return result; - } - - if (move.named('Spectral Thief')) { - let stat: StatID; - for (stat in defender.boosts) { - if (defender.boosts[stat] > 0) { - attacker.boosts[stat] += - attacker.hasAbility('Contrary') ? -defender.boosts[stat]! : defender.boosts[stat]!; - if (attacker.boosts[stat] > 6) attacker.boosts[stat] = 6; - if (attacker.boosts[stat] < -6) attacker.boosts[stat] = -6; - attacker.stats[stat] = getModifiedStat(attacker.rawStats[stat]!, attacker.boosts[stat]!); - defender.boosts[stat] = 0; - defender.stats[stat] = defender.rawStats[stat]; - } - } - } - - if (move.hits > 1) { - desc.hits = move.hits; - } - - const turnOrder = attacker.stats.spe > defender.stats.spe ? 'first' : 'last'; - - // #endregion - // #region Base Power - - const basePower = calculateBasePowerSMSSSV( - gen, - attacker, - defender, - move, - field, - hasAteAbilityTypeChange, - desc - ); - if (basePower === 0) { - return result; - } - - // #endregion - // #region (Special) Attack - const attack = calculateAttackSMSSSV(gen, attacker, defender, move, field, desc, isCritical); - const attackSource = move.named('Foul Play') ? defender : attacker; - if (move.named('Photon Geyser', 'Light That Burns The Sky') || - (move.named('Tera Blast') && attackSource.teraType)) { - move.category = attackSource.stats.atk > attackSource.stats.spa ? 'Physical' : 'Special'; - } - const attackStat = - move.named('Shell Side Arm') && - getShellSideArmCategory(attacker, defender) === 'Physical' - ? 'atk' - : move.named('Body Press') - ? 'def' - : move.category === 'Special' - ? 'spa' - : 'atk'; - // #endregion - // #region (Special) Defense - - const defense = calculateDefenseSMSSSV(gen, attacker, defender, move, field, desc, isCritical); - const hitsPhysical = move.overrideDefensiveStat === 'def' || move.category === 'Physical' || - (move.named('Shell Side Arm') && getShellSideArmCategory(attacker, defender) === 'Physical'); - const defenseStat = hitsPhysical ? 'def' : 'spd'; - - // #endregion - // #region Damage - - const baseDamage = calculateBaseDamageSMSSSV( - gen, - attacker, - defender, - basePower, - attack, - defense, - move, - field, - desc, - isCritical - ); - - if (hasTerrainSeed(defender) && - field.hasTerrain(defender.item!.substring(0, defender.item!.indexOf(' ')) as Terrain) && - SEED_BOOSTED_STAT[defender.item!] === defenseStat) { - // Last condition applies so the calc doesn't show a seed where it wouldn't affect the outcome - // (like Grassy Seed when being hit by a special move) - desc.defenderItem = defender.item; - } - - // the random factor is applied between the crit mod and the stab mod, so don't apply anything - // below this until we're inside the loop - let stabMod = 4096; - if (attacker.hasOriginalType(move.type)) { - stabMod += 2048; - } else if (attacker.hasAbility('Protean', 'Libero') && !attacker.teraType) { - stabMod += 2048; - desc.attackerAbility = attacker.ability; - } - const teraType = attacker.teraType; - if (teraType === move.type && teraType !== 'Stellar') { - stabMod += 2048; - desc.attackerTera = teraType; - } - if (attacker.hasAbility('Adaptability') && attacker.hasType(move.type)) { - stabMod += teraType && attacker.hasOriginalType(teraType) ? 1024 : 2048; - desc.attackerAbility = attacker.ability; - } - - // TODO: For now all moves are always boosted - const isStellarBoosted = - attacker.teraType === 'Stellar' && - (move.isStellarFirstUse || attacker.named('Terapagos-Stellar')); - if (isStellarBoosted) { - if (attacker.hasOriginalType(move.type)) { - stabMod += 2048; - } else { - stabMod = 4915; - } - } - - const applyBurn = - attacker.hasStatus('brn') && - move.category === 'Physical' && - !attacker.hasAbility('Guts') && - !move.named('Facade'); - desc.isBurned = applyBurn; - const finalMods = calculateFinalModsSMSSSV( - gen, - attacker, - defender, - move, - field, - desc, - isCritical, - typeEffectiveness - ); - - let protect = false; - if (field.defenderSide.isProtected && - (attacker.isDynamaxed || (move.isZ && attacker.item && attacker.item.includes(' Z')))) { - protect = true; - desc.isProtected = true; - } - - const finalMod = chainMods(finalMods, 41, 131072); - - const isSpread = field.gameType !== 'Singles' && - ['allAdjacent', 'allAdjacentFoes'].includes(move.target); - - let childDamage: number[] | undefined; - if (attacker.hasAbility('Parental Bond') && move.hits === 1 && !isSpread) { - const child = attacker.clone(); - child.ability = 'Parental Bond (Child)' as AbilityName; - checkMultihitBoost(gen, child, defender, move, field, desc); - childDamage = calculateSMSSSV(gen, child, defender, move, field).damage as number[]; - desc.attackerAbility = attacker.ability; - } - - let damage = []; - for (let i = 0; i < 16; i++) { - damage[i] = - getFinalDamage(baseDamage, i, typeEffectiveness, applyBurn, stabMod, finalMod, protect); - } - - if (move.dropsStats && move.timesUsed! > 1) { - const simpleMultiplier = attacker.hasAbility('Simple') ? 2 : 1; - - desc.moveTurns = `over ${move.timesUsed} turns`; - const hasWhiteHerb = attacker.hasItem('White Herb'); - let usedWhiteHerb = false; - let dropCount = 0; - for (let times = 0; times < move.timesUsed!; times++) { - const newAttack = getModifiedStat(attack, dropCount); - let damageMultiplier = 0; - damage = damage.map(affectedAmount => { - if (times) { - const newBaseDamage = getBaseDamage(attacker.level, basePower, newAttack, defense); - const newFinalDamage = getFinalDamage( - newBaseDamage, - damageMultiplier, - typeEffectiveness, - applyBurn, - stabMod, - finalMod, - protect - ); - damageMultiplier++; - return affectedAmount + newFinalDamage; - } - return affectedAmount; - }); - - if (attacker.hasAbility('Contrary')) { - dropCount = Math.min(6, dropCount + move.dropsStats); - desc.attackerAbility = attacker.ability; - } else { - dropCount = Math.max(-6, dropCount - move.dropsStats * simpleMultiplier); - if (attacker.hasAbility('Simple')) { - desc.attackerAbility = attacker.ability; - } - } - - // the Pokémon hits THEN the stat rises / lowers - if (hasWhiteHerb && attacker.boosts[attackStat] < 0 && !usedWhiteHerb) { - dropCount += move.dropsStats * simpleMultiplier; - usedWhiteHerb = true; - desc.attackerItem = attacker.item; - } - } - } - - if (move.hits > 1) { - let defenderDefBoost = 0; - for (let times = 0; times < move.hits; times++) { - const newDefense = getModifiedStat(defense, defenderDefBoost); - let damageMultiplier = 0; - damage = damage.map(affectedAmount => { - if (times) { - const newFinalMods = calculateFinalModsSMSSSV( - gen, - attacker, - defender, - move, - field, - desc, - isCritical, - typeEffectiveness, - times - ); - const newFinalMod = chainMods(newFinalMods, 41, 131072); - const newBaseDamage = calculateBaseDamageSMSSSV( - gen, - attacker, - defender, - basePower, - attack, - newDefense, - move, - field, - desc, - isCritical - ); - const newFinalDamage = getFinalDamage( - newBaseDamage, - damageMultiplier, - typeEffectiveness, - applyBurn, - stabMod, - newFinalMod, - protect - ); - damageMultiplier++; - return affectedAmount + newFinalDamage; - } - return affectedAmount; - }); - if (hitsPhysical && defender.ability === 'Stamina') { - defenderDefBoost = Math.min(6, defenderDefBoost + 1); - desc.defenderAbility = 'Stamina'; - } else if (hitsPhysical && defender.ability === 'Weak Armor') { - defenderDefBoost = Math.max(-6, defenderDefBoost - 1); - desc.defenderAbility = 'Weak Armor'; - } - } - } - - desc.attackBoost = - move.named('Foul Play') ? defender.boosts[attackStat] : attacker.boosts[attackStat]; - - result.damage = childDamage ? [damage, childDamage] : damage; - - // #endregion - - return result; -} - -export function calculateBasePowerSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - move: Move, - field: Field, - hasAteAbilityTypeChange: boolean, - desc: RawDesc -) { - const turnOrder = attacker.stats.spe > defender.stats.spe ? 'first' : 'last'; - - let basePower: number; - - switch (move.name) { - case 'Payback': - basePower = move.bp * (turnOrder === 'last' ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Bolt Beak': - case 'Fishious Rend': - basePower = move.bp * (turnOrder !== 'last' ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Pursuit': - const switching = field.defenderSide.isSwitching === 'out'; - basePower = move.bp * (switching ? 2 : 1); - if (switching) desc.isSwitching = 'out'; - desc.moveBP = basePower; - break; - case 'Electro Ball': - const r = Math.floor(attacker.stats.spe / defender.stats.spe); - basePower = r >= 4 ? 150 : r >= 3 ? 120 : r >= 2 ? 80 : r >= 1 ? 60 : 40; - if (defender.stats.spe === 0) basePower = 40; - desc.moveBP = basePower; - break; - case 'Gyro Ball': - basePower = Math.min(150, Math.floor((25 * defender.stats.spe) / attacker.stats.spe) + 1); - if (attacker.stats.spe === 0) basePower = 1; - desc.moveBP = basePower; - break; - case 'Punishment': - basePower = Math.min(200, 60 + 20 * countBoosts(gen, defender.boosts)); - desc.moveBP = basePower; - break; - case 'Low Kick': - case 'Grass Knot': - const w = defender.weightkg * getWeightFactor(defender); - basePower = w >= 200 ? 120 : w >= 100 ? 100 : w >= 50 ? 80 : w >= 25 ? 60 : w >= 10 ? 40 : 20; - desc.moveBP = basePower; - break; - case 'Hex': - case 'Infernal Parade': - // Hex deals double damage to Pokemon with Comatose (ih8ih8sn0w) - basePower = move.bp * (defender.status || defender.hasAbility('Comatose') ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Barb Barrage': - basePower = move.bp * (defender.hasStatus('psn', 'tox') ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Heavy Slam': - case 'Heat Crash': - const wr = - (attacker.weightkg * getWeightFactor(attacker)) / - (defender.weightkg * getWeightFactor(defender)); - basePower = wr >= 5 ? 120 : wr >= 4 ? 100 : wr >= 3 ? 80 : wr >= 2 ? 60 : 40; - desc.moveBP = basePower; - break; - case 'Stored Power': - case 'Power Trip': - basePower = 20 + 20 * countBoosts(gen, attacker.boosts); - desc.moveBP = basePower; - break; - case 'Acrobatics': - basePower = move.bp * (attacker.hasItem('Flying Gem') || - (!attacker.item || isQPActive(attacker, field)) ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Assurance': - basePower = move.bp * (defender.hasAbility('Parental Bond (Child)') ? 2 : 1); - // NOTE: desc.attackerAbility = 'Parental Bond' will already reflect this boost - break; - case 'Wake-Up Slap': - // Wake-Up Slap deals double damage to Pokemon with Comatose (ih8ih8sn0w) - basePower = move.bp * (defender.hasStatus('slp') || defender.hasAbility('Comatose') ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Smelling Salts': - basePower = move.bp * (defender.hasStatus('par') ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Weather Ball': - basePower = move.bp * (field.weather && !field.hasWeather('Strong Winds') ? 2 : 1); - if (field.hasWeather('Sun', 'Harsh Sunshine', 'Rain', 'Heavy Rain') && - attacker.hasItem('Utility Umbrella')) basePower = move.bp; - desc.moveBP = basePower; - break; - case 'Terrain Pulse': - basePower = move.bp * (isGrounded(attacker, field) && field.terrain ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Rising Voltage': - basePower = move.bp * ((isGrounded(defender, field) && field.hasTerrain('Electric')) ? 2 : 1); - desc.moveBP = basePower; - break; - case 'Psyblade': - basePower = move.bp * (field.hasTerrain('Electric') ? 1.5 : 1); - if (field.hasTerrain('Electric')) { - desc.moveBP = basePower; - desc.terrain = field.terrain; - } - break; - case 'Fling': - basePower = getFlingPower(attacker.item); - desc.moveBP = basePower; - desc.attackerItem = attacker.item; - break; - case 'Dragon Energy': - case 'Eruption': - case 'Water Spout': - basePower = Math.max(1, Math.floor((150 * attacker.curHP()) / attacker.maxHP())); - desc.moveBP = basePower; - break; - case 'Flail': - case 'Reversal': - const p = Math.floor((48 * attacker.curHP()) / attacker.maxHP()); - basePower = p <= 1 ? 200 : p <= 4 ? 150 : p <= 9 ? 100 : p <= 16 ? 80 : p <= 32 ? 40 : 20; - desc.moveBP = basePower; - break; - case 'Natural Gift': - if (attacker.item?.includes('Berry')) { - const gift = getNaturalGift(gen, attacker.item)!; - basePower = gift.p; - desc.attackerItem = attacker.item; - desc.moveBP = move.bp; - } else { - basePower = move.bp; - } - break; - case 'Nature Power': - move.category = 'Special'; - move.secondaries = true; - switch (field.terrain) { - case 'Electric': - basePower = 90; - desc.moveName = 'Thunderbolt'; - break; - case 'Grassy': - basePower = 90; - desc.moveName = 'Energy Ball'; - break; - case 'Misty': - basePower = 95; - desc.moveName = 'Moonblast'; - break; - case 'Psychic': - basePower = 90; - desc.moveName = 'Psychic'; - break; - default: - basePower = 80; - desc.moveName = 'Tri Attack'; - } - break; - case 'Water Shuriken': - basePower = attacker.named('Greninja-Ash') && attacker.hasAbility('Battle Bond') ? 20 : 15; - desc.moveBP = basePower; - break; - // Triple Axel's damage doubles after each consecutive hit (20, 40, 60), this is a hack - case 'Triple Axel': - basePower = move.hits === 2 ? 30 : move.hits === 3 ? 40 : 20; - desc.moveBP = basePower; - break; - // Triple Kick's damage doubles after each consecutive hit (10, 20, 30), this is a hack - case 'Triple Kick': - basePower = move.hits === 2 ? 15 : move.hits === 3 ? 30 : 10; - desc.moveBP = basePower; - break; - case 'Crush Grip': - case 'Wring Out': - basePower = 100 * Math.floor((defender.curHP() * 4096) / defender.maxHP()); - basePower = Math.floor(Math.floor((120 * basePower + 2048 - 1) / 4096) / 100) || 1; - desc.moveBP = basePower; - break; - case 'Hard Press': - basePower = 100 * Math.floor((defender.curHP() * 4096) / defender.maxHP()); - basePower = Math.floor(Math.floor((100 * basePower + 2048 - 1) / 4096) / 100) || 1; - desc.moveBP = basePower; - break; - case 'Tera Blast': - basePower = attacker.teraType === 'Stellar' ? 100 : 80; - desc.moveBP = basePower; - break; - default: - basePower = move.bp; - } - if (basePower === 0) { - return 0; - } - if (move.named( - 'Breakneck Blitz', 'Bloom Doom', 'Inferno Overdrive', 'Hydro Vortex', 'Gigavolt Havoc', - 'Subzero Slammer', 'Supersonic Skystrike', 'Savage Spin-Out', 'Acid Downpour', 'Tectonic Rage', - 'Continental Crush', 'All-Out Pummeling', 'Shattered Psyche', 'Never-Ending Nightmare', - 'Devastating Drake', 'Black Hole Eclipse', 'Corkscrew Crash', 'Twinkle Tackle' - )) { - // show z-move power in description - desc.moveBP = move.bp; - } - const bpMods = calculateBPModsSMSSSV( - gen, - attacker, - defender, - move, - field, - desc, - basePower, - hasAteAbilityTypeChange, - turnOrder - ); - basePower = OF16(Math.max(1, pokeRound((basePower * chainMods(bpMods, 41, 2097152)) / 4096))); - if ( - attacker.teraType && move.type === attacker.teraType && - attacker.hasType(attacker.teraType) && move.hits === 1 && - move.priority <= 0 && move.bp > 0 && !move.named('Dragon Energy', 'Eruption', 'Water Spout') && - basePower < 60 && gen.num >= 9 - ) { - basePower = 60; - desc.moveBP = 60; - } - return basePower; -} - -export function calculateBPModsSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - move: Move, - field: Field, - desc: RawDesc, - basePower: number, - hasAteAbilityTypeChange: boolean, - turnOrder: string -) { - const bpMods = []; - - // Move effects - - let resistedKnockOffDamage = - (!defender.item || isQPActive(defender, field)) || - (defender.named('Dialga-Origin') && defender.hasItem('Adamant Crystal')) || - (defender.named('Palkia-Origin') && defender.hasItem('Lustrous Globe')) || - // Griseous Core for gen 9, Griseous Orb otherwise - (defender.name.includes('Giratina-Origin') && defender.item.includes('Griseous')) || - (defender.name.includes('Arceus') && defender.item.includes('Plate')) || - (defender.name.includes('Genesect') && defender.item.includes('Drive')) || - (defender.named('Groudon', 'Groudon-Primal') && defender.hasItem('Red Orb')) || - (defender.named('Kyogre', 'Kyogre-Primal') && defender.hasItem('Blue Orb')) || - (defender.name.includes('Silvally') && defender.item.includes('Memory')) || - defender.item.includes(' Z') || - (defender.named('Zacian') && defender.hasItem('Rusted Sword')) || - (defender.named('Zamazenta') && defender.hasItem('Rusted Shield')) || - (defender.name.includes('Ogerpon-Cornerstone') && defender.hasItem('Cornerstone Mask')) || - (defender.name.includes('Ogerpon-Hearthflame') && defender.hasItem('Hearthflame Mask')) || - (defender.name.includes('Ogerpon-Wellspring') && defender.hasItem('Wellspring Mask')) || - (defender.named('Venomicon-Epilogue') && defender.hasItem('Vile Vial')); - - // The last case only applies when the Pokemon has the Mega Stone that matches its species - // (or when it's already a Mega-Evolution) - if (!resistedKnockOffDamage && defender.item) { - const item = gen.items.get(toID(defender.item))!; - resistedKnockOffDamage = !!item.megaEvolves && defender.name.includes(item.megaEvolves); - } - - if ((move.named('Facade') && attacker.hasStatus('brn', 'par', 'psn', 'tox')) || - (move.named('Brine') && defender.curHP() <= defender.maxHP() / 2) || - (move.named('Venoshock') && defender.hasStatus('psn', 'tox')) || - (move.named('Lash Out') && (countBoosts(gen, attacker.boosts) < 0)) - ) { - bpMods.push(8192); - desc.moveBP = basePower * 2; - } else if ( - move.named('Expanding Force') && isGrounded(attacker, field) && field.hasTerrain('Psychic') - ) { - move.target = 'allAdjacentFoes'; - bpMods.push(6144); - desc.moveBP = basePower * 1.5; - } else if ( - move.named('Tera Starstorm') && attacker.name === 'Terapagos-Stellar' - ) { - move.target = 'allAdjacentFoes'; - move.type = 'Stellar'; - } else if ((move.named('Knock Off') && !resistedKnockOffDamage) || - (move.named('Misty Explosion') && isGrounded(attacker, field) && field.hasTerrain('Misty')) || - (move.named('Grav Apple') && field.isGravity) - ) { - bpMods.push(6144); - desc.moveBP = basePower * 1.5; - } else if (move.named('Solar Beam', 'Solar Blade') && - field.hasWeather('Rain', 'Heavy Rain', 'Sand', 'Hail', 'Snow')) { - bpMods.push(2048); - desc.moveBP = basePower / 2; - desc.weather = field.weather; - } else if (move.named('Collision Course', 'Electro Drift')) { - const isGhostRevealed = - attacker.hasAbility('Scrappy') || attacker.hasAbility('Mind\'s Eye') || - field.defenderSide.isForesight; - const isRingTarget = - defender.hasItem('Ring Target') && !defender.hasAbility('Klutz'); - const types = defender.teraType ? [defender.teraType] : defender.types; - const type1Effectiveness = getMoveEffectiveness( - gen, - move, - types[0], - isGhostRevealed, - field.isGravity, - isRingTarget - ); - const type2Effectiveness = types[1] ? getMoveEffectiveness( - gen, - move, - types[1], - isGhostRevealed, - field.isGravity, - isRingTarget - ) : 1; - if (type1Effectiveness * type2Effectiveness >= 2) { - bpMods.push(5461); - desc.moveBP = basePower * (5461 / 4096); - } - } - - if (field.attackerSide.isHelpingHand) { - bpMods.push(6144); - desc.isHelpingHand = true; - } - - // Field effects - - const terrainMultiplier = gen.num > 7 ? 5325 : 6144; - if (isGrounded(attacker, field)) { - if ((field.hasTerrain('Electric') && move.hasType('Electric')) || - (field.hasTerrain('Grassy') && move.hasType('Grass')) || - (field.hasTerrain('Psychic') && move.hasType('Psychic')) - ) { - bpMods.push(terrainMultiplier); - desc.terrain = field.terrain; - } - } - if (isGrounded(defender, field)) { - if ((field.hasTerrain('Misty') && move.hasType('Dragon')) || - (field.hasTerrain('Grassy') && move.named('Bulldoze', 'Earthquake')) - ) { - bpMods.push(2048); - desc.terrain = field.terrain; - } - } - - // Abilities - - // Use BasePower after moves with custom BP to determine if Technician should boost - if ((attacker.hasAbility('Technician') && basePower <= 60) || - (attacker.hasAbility('Flare Boost') && - attacker.hasStatus('brn') && move.category === 'Special') || - (attacker.hasAbility('Toxic Boost') && - attacker.hasStatus('psn', 'tox') && move.category === 'Physical') || - (attacker.hasAbility('Mega Launcher') && move.flags.pulse) || - (attacker.hasAbility('Strong Jaw') && move.flags.bite) || - (attacker.hasAbility('Steely Spirit') && move.hasType('Steel')) || - (attacker.hasAbility('Sharpness') && move.flags.slicing) - ) { - bpMods.push(6144); - desc.attackerAbility = attacker.ability; - } - - const aura = `${move.type} Aura`; - const isAttackerAura = attacker.hasAbility(aura); - const isDefenderAura = defender.hasAbility(aura); - const isUserAuraBreak = attacker.hasAbility('Aura Break') || defender.hasAbility('Aura Break'); - const isFieldAuraBreak = field.isAuraBreak; - const isFieldFairyAura = field.isFairyAura && move.type === 'Fairy'; - const isFieldDarkAura = field.isDarkAura && move.type === 'Dark'; - const auraActive = isAttackerAura || isDefenderAura || isFieldFairyAura || isFieldDarkAura; - const auraBreak = isFieldAuraBreak || isUserAuraBreak; - if (auraActive) { - if (auraBreak) { - bpMods.push(3072); - desc.attackerAbility = attacker.ability; - desc.defenderAbility = defender.ability; - } else { - bpMods.push(5448); - if (isAttackerAura) desc.attackerAbility = attacker.ability; - if (isDefenderAura) desc.defenderAbility = defender.ability; - } - } - - // Sheer Force does not power up max moves or remove the effects (SadisticMystic) - if ( - (attacker.hasAbility('Sheer Force') && - (move.secondaries || move.named('Jet Punch', 'Order Up')) && !move.isMax) || - (attacker.hasAbility('Sand Force') && - field.hasWeather('Sand') && move.hasType('Rock', 'Ground', 'Steel')) || - (attacker.hasAbility('Analytic') && - (turnOrder !== 'first' || field.defenderSide.isSwitching === 'out')) || - (attacker.hasAbility('Tough Claws') && move.flags.contact) || - (attacker.hasAbility('Punk Rock') && move.flags.sound) - ) { - bpMods.push(5325); - desc.attackerAbility = attacker.ability; - } - - if (field.attackerSide.isBattery && move.category === 'Special') { - bpMods.push(5325); - desc.isBattery = true; - } - - if (field.attackerSide.isPowerSpot) { - bpMods.push(5325); - desc.isPowerSpot = true; - } - - if (attacker.hasAbility('Rivalry') && ![attacker.gender, defender.gender].includes('N')) { - if (attacker.gender === defender.gender) { - bpMods.push(5120); - desc.rivalry = 'buffed'; - } else { - bpMods.push(3072); - desc.rivalry = 'nerfed'; - } - desc.attackerAbility = attacker.ability; - } - - // The -ate abilities already changed move typing earlier, so most checks are done and desc is set - // However, Max Moves also don't boost -ate Abilities - if (!move.isMax && hasAteAbilityTypeChange) { - bpMods.push(4915); - } - - if ((attacker.hasAbility('Reckless') && (move.recoil || move.hasCrashDamage)) || - (attacker.hasAbility('Iron Fist') && move.flags.punch) - ) { - bpMods.push(4915); - desc.attackerAbility = attacker.ability; - } - - if (attacker.hasItem('Punching Glove') && move.flags.punch) { - bpMods.push(4506); - desc.attackerItem = attacker.item; - } - - if (gen.num <= 8 && defender.hasAbility('Heatproof') && move.hasType('Fire')) { - bpMods.push(2048); - desc.defenderAbility = defender.ability; - } else if (defender.hasAbility('Dry Skin') && move.hasType('Fire')) { - bpMods.push(5120); - desc.defenderAbility = defender.ability; - } - - if (attacker.hasAbility('Supreme Overlord') && attacker.alliesFainted) { - const powMod = [4096, 4506, 4915, 5325, 5734, 6144]; - bpMods.push(powMod[Math.min(5, attacker.alliesFainted)]); - desc.attackerAbility = attacker.ability; - desc.alliesFainted = attacker.alliesFainted; - } - - // Items - - if (attacker.hasItem(`${move.type} Gem`)) { - bpMods.push(5325); - desc.attackerItem = attacker.item; - } else if ( - (((attacker.hasItem('Adamant Crystal') && attacker.named('Dialga-Origin')) || - (attacker.hasItem('Adamant Orb') && attacker.named('Dialga'))) && - move.hasType('Steel', 'Dragon')) || - (((attacker.hasItem('Lustrous Orb') && - attacker.named('Palkia')) || - (attacker.hasItem('Lustrous Globe') && attacker.named('Palkia-Origin'))) && - move.hasType('Water', 'Dragon')) || - (((attacker.hasItem('Griseous Orb') || attacker.hasItem('Griseous Core')) && - (attacker.named('Giratina-Origin') || attacker.named('Giratina'))) && - move.hasType('Ghost', 'Dragon')) || - (attacker.hasItem('Vile Vial') && - attacker.named('Venomicon-Epilogue') && - move.hasType('Poison', 'Flying')) || - (attacker.hasItem('Soul Dew') && - attacker.named('Latios', 'Latias', 'Latios-Mega', 'Latias-Mega') && - move.hasType('Psychic', 'Dragon')) || - attacker.item && move.hasType(getItemBoostType(attacker.item)) || - (attacker.name.includes('Ogerpon-Cornerstone') && attacker.hasItem('Cornerstone Mask')) || - (attacker.name.includes('Ogerpon-Hearthflame') && attacker.hasItem('Hearthflame Mask')) || - (attacker.name.includes('Ogerpon-Wellspring') && attacker.hasItem('Wellspring Mask')) - ) { - bpMods.push(4915); - desc.attackerItem = attacker.item; - } else if ( - (attacker.hasItem('Muscle Band') && move.category === 'Physical') || - (attacker.hasItem('Wise Glasses') && move.category === 'Special') - ) { - bpMods.push(4505); - desc.attackerItem = attacker.item; - } - return bpMods; -} - -export function calculateAttackSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - move: Move, - field: Field, - desc: RawDesc, - isCritical = false -) { - let attack: number; - const attackSource = move.named('Foul Play') ? defender : attacker; - if (move.named('Photon Geyser', 'Light That Burns The Sky') || - (move.named('Tera Blast') && attackSource.teraType)) { - move.category = attackSource.stats.atk > attackSource.stats.spa ? 'Physical' : 'Special'; - } - const attackStat = - move.named('Shell Side Arm') && - getShellSideArmCategory(attacker, defender) === 'Physical' - ? 'atk' - : move.named('Body Press') - ? 'def' - : move.category === 'Special' - ? 'spa' - : 'atk'; - desc.attackEVs = - move.named('Foul Play') - ? getEVDescriptionText(gen, defender, attackStat, defender.nature) - : getEVDescriptionText(gen, attacker, attackStat, attacker.nature); - - if (attackSource.boosts[attackStat] === 0 || - (isCritical && attackSource.boosts[attackStat] < 0)) { - attack = attackSource.rawStats[attackStat]; - } else if (defender.hasAbility('Unaware')) { - attack = attackSource.rawStats[attackStat]; - desc.defenderAbility = defender.ability; - } else { - attack = attackSource.stats[attackStat]; - desc.attackBoost = attackSource.boosts[attackStat]; - } - - // unlike all other attack modifiers, Hustle gets applied directly - if (attacker.hasAbility('Hustle') && move.category === 'Physical') { - attack = pokeRound((attack * 3) / 2); - desc.attackerAbility = attacker.ability; - } - const atMods = calculateAtModsSMSSSV(gen, attacker, defender, move, field, desc); - attack = OF16(Math.max(1, pokeRound((attack * chainMods(atMods, 410, 131072)) / 4096))); - return attack; -} - -export function calculateAtModsSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - move: Move, - field: Field, - desc: RawDesc -) { - const atMods = []; - - // Slow Start also halves damage with special Z-moves - if ((attacker.hasAbility('Slow Start') && attacker.abilityOn && - (move.category === 'Physical' || (move.category === 'Special' && move.isZ))) || - (attacker.hasAbility('Defeatist') && attacker.curHP() <= attacker.maxHP() / 2) - ) { - atMods.push(2048); - desc.attackerAbility = attacker.ability; - } else if ( - (attacker.hasAbility('Solar Power') && - field.hasWeather('Sun', 'Harsh Sunshine') && - move.category === 'Special') || - (attacker.named('Cherrim') && - attacker.hasAbility('Flower Gift') && - field.hasWeather('Sun', 'Harsh Sunshine') && - move.category === 'Physical')) { - atMods.push(6144); - desc.attackerAbility = attacker.ability; - desc.weather = field.weather; - } else if ( - // Gorilla Tactics has no effect during Dynamax (Anubis) - (attacker.hasAbility('Gorilla Tactics') && move.category === 'Physical' && - !attacker.isDynamaxed)) { - atMods.push(6144); - desc.attackerAbility = attacker.ability; - } else if ( - field.attackerSide.isFlowerGift && - field.hasWeather('Sun', 'Harsh Sunshine') && - move.category === 'Physical') { - atMods.push(6144); - desc.weather = field.weather; - desc.isFlowerGiftAttacker = true; - } else if ( - (attacker.hasAbility('Guts') && attacker.status && move.category === 'Physical') || - (attacker.curHP() <= attacker.maxHP() / 3 && - ((attacker.hasAbility('Overgrow') && move.hasType('Grass')) || - (attacker.hasAbility('Blaze') && move.hasType('Fire')) || - (attacker.hasAbility('Torrent') && move.hasType('Water')) || - (attacker.hasAbility('Swarm') && move.hasType('Bug')))) || - (move.category === 'Special' && attacker.abilityOn && attacker.hasAbility('Plus', 'Minus')) - ) { - atMods.push(6144); - desc.attackerAbility = attacker.ability; - } else if (attacker.hasAbility('Flash Fire') && attacker.abilityOn && move.hasType('Fire')) { - atMods.push(6144); - desc.attackerAbility = 'Flash Fire'; - } else if ( - (attacker.hasAbility('Steelworker') && move.hasType('Steel')) || - (attacker.hasAbility('Dragon\'s Maw') && move.hasType('Dragon')) || - (attacker.hasAbility('Rocky Payload') && move.hasType('Rock')) - ) { - atMods.push(6144); - desc.attackerAbility = attacker.ability; - } else if (attacker.hasAbility('Transistor') && move.hasType('Electric')) { - atMods.push(gen.num >= 9 ? 5325 : 6144); - desc.attackerAbility = attacker.ability; - } else if (attacker.hasAbility('Stakeout') && attacker.abilityOn) { - atMods.push(8192); - desc.attackerAbility = attacker.ability; - } else if ( - (attacker.hasAbility('Water Bubble') && move.hasType('Water')) || - (attacker.hasAbility('Huge Power', 'Pure Power') && move.category === 'Physical') - ) { - atMods.push(8192); - desc.attackerAbility = attacker.ability; - } - - if ((defender.hasAbility('Thick Fat') && move.hasType('Fire', 'Ice')) || - (defender.hasAbility('Water Bubble') && move.hasType('Fire')) || - (defender.hasAbility('Purifying Salt') && move.hasType('Ghost'))) { - atMods.push(2048); - desc.defenderAbility = defender.ability; - } - - if (gen.num >= 9 && defender.hasAbility('Heatproof') && move.hasType('Fire')) { - atMods.push(2048); - desc.defenderAbility = defender.ability; - } - // Pokemon with "-of Ruin" Ability are immune to the opposing "-of Ruin" ability - const isTabletsOfRuinActive = (defender.hasAbility('Tablets of Ruin') || field.isTabletsOfRuin) && - !attacker.hasAbility('Tablets of Ruin'); - const isVesselOfRuinActive = (defender.hasAbility('Vessel of Ruin') || field.isVesselOfRuin) && - !attacker.hasAbility('Vessel of Ruin'); - if ( - (isTabletsOfRuinActive && move.category === 'Physical') || - (isVesselOfRuinActive && move.category === 'Special') - ) { - if (defender.hasAbility('Tablets of Ruin') || defender.hasAbility('Vessel of Ruin')) { - desc.defenderAbility = defender.ability; - } else { - desc[move.category === 'Special' ? 'isVesselOfRuin' : 'isTabletsOfRuin'] = true; - } - atMods.push(3072); - } - - if (isQPActive(attacker, field)) { - if ( - (move.category === 'Physical' && getQPBoostedStat(attacker) === 'atk') || - (move.category === 'Special' && getQPBoostedStat(attacker) === 'spa') - ) { - atMods.push(5325); - desc.attackerAbility = attacker.ability; - } - } - - if ( - (attacker.hasAbility('Hadron Engine') && move.category === 'Special' && - field.hasTerrain('Electric') && isGrounded(attacker, field)) || - (attacker.hasAbility('Orichalcum Pulse') && move.category === 'Physical' && - field.hasWeather('Sun', 'Harsh Sunshine') && !attacker.hasItem('Utility Umbrella')) - ) { - atMods.push(5461); - desc.attackerAbility = attacker.ability; - } - - if ((attacker.hasItem('Thick Club') && - attacker.named('Cubone', 'Marowak', 'Marowak-Alola', 'Marowak-Alola-Totem') && - move.category === 'Physical') || - (attacker.hasItem('Deep Sea Tooth') && - attacker.named('Clamperl') && - move.category === 'Special') || - (attacker.hasItem('Light Ball') && attacker.name.includes('Pikachu') && !move.isZ) - ) { - atMods.push(8192); - desc.attackerItem = attacker.item; - // Choice Band/Scarf/Specs move lock and stat boosts are ignored during Dynamax (Anubis) - } else if (!move.isZ && !move.isMax && - ((attacker.hasItem('Choice Band') && move.category === 'Physical') || - (attacker.hasItem('Choice Specs') && move.category === 'Special')) - ) { - atMods.push(6144); - desc.attackerItem = attacker.item; - } - return atMods; -} - -export function calculateDefenseSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - move: Move, - field: Field, - desc: RawDesc, - isCritical = false -) { - let defense: number; - const hitsPhysical = move.overrideDefensiveStat === 'def' || move.category === 'Physical' || - (move.named('Shell Side Arm') && getShellSideArmCategory(attacker, defender) === 'Physical'); - const defenseStat = hitsPhysical ? 'def' : 'spd'; - desc.defenseEVs = getEVDescriptionText(gen, defender, defenseStat, defender.nature); - if (defender.boosts[defenseStat] === 0 || - (isCritical && defender.boosts[defenseStat] > 0) || - move.ignoreDefensive) { - defense = defender.rawStats[defenseStat]; - } else if (attacker.hasAbility('Unaware')) { - defense = defender.rawStats[defenseStat]; - desc.attackerAbility = attacker.ability; - } else { - defense = defender.stats[defenseStat]; - desc.defenseBoost = defender.boosts[defenseStat]; - } - - // unlike all other defense modifiers, Sandstorm SpD boost gets applied directly - if (field.hasWeather('Sand') && defender.hasType('Rock') && !hitsPhysical) { - defense = pokeRound((defense * 3) / 2); - desc.weather = field.weather; - } - if (field.hasWeather('Snow') && defender.hasType('Ice') && hitsPhysical) { - defense = pokeRound((defense * 3) / 2); - desc.weather = field.weather; - } - - const dfMods = calculateDfModsSMSSSV( - gen, - attacker, - defender, - move, - field, - desc, - isCritical, - hitsPhysical - ); - - return OF16(Math.max(1, pokeRound((defense * chainMods(dfMods, 410, 131072)) / 4096))); -} - -export function calculateDfModsSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - move: Move, - field: Field, - desc: RawDesc, - isCritical = false, - hitsPhysical = false -) { - const dfMods = []; - if (defender.hasAbility('Marvel Scale') && defender.status && hitsPhysical) { - dfMods.push(6144); - desc.defenderAbility = defender.ability; - } else if ( - defender.named('Cherrim') && - defender.hasAbility('Flower Gift') && - field.hasWeather('Sun', 'Harsh Sunshine') && - !hitsPhysical - ) { - dfMods.push(6144); - desc.defenderAbility = defender.ability; - desc.weather = field.weather; - } else if ( - field.defenderSide.isFlowerGift && - field.hasWeather('Sun', 'Harsh Sunshine') && - !hitsPhysical) { - dfMods.push(6144); - desc.weather = field.weather; - desc.isFlowerGiftDefender = true; - } else if ( - defender.hasAbility('Grass Pelt') && - field.hasTerrain('Grassy') && - hitsPhysical - ) { - dfMods.push(6144); - desc.defenderAbility = defender.ability; - } else if (defender.hasAbility('Fur Coat') && hitsPhysical) { - dfMods.push(8192); - desc.defenderAbility = defender.ability; - } - // Pokemon with "-of Ruin" Ability are immune to the opposing "-of Ruin" ability - const isSwordOfRuinActive = (attacker.hasAbility('Sword of Ruin') || field.isSwordOfRuin) && - !defender.hasAbility('Sword of Ruin'); - const isBeadsOfRuinActive = (attacker.hasAbility('Beads of Ruin') || field.isBeadsOfRuin) && - !defender.hasAbility('Beads of Ruin'); - if ( - (isSwordOfRuinActive && hitsPhysical) || - (isBeadsOfRuinActive && !hitsPhysical) - ) { - if (attacker.hasAbility('Sword of Ruin') || attacker.hasAbility('Beads of Ruin')) { - desc.attackerAbility = attacker.ability; - } else { - desc[hitsPhysical ? 'isSwordOfRuin' : 'isBeadsOfRuin'] = true; - } - dfMods.push(3072); - } - - if (isQPActive(defender, field)) { - if ( - (hitsPhysical && getQPBoostedStat(defender) === 'def') || - (!hitsPhysical && getQPBoostedStat(defender) === 'spd') - ) { - desc.defenderAbility = defender.ability; - dfMods.push(5324); - } - } - - if ((defender.hasItem('Eviolite') && - (defender.name === 'Dipplin' || gen.species.get(toID(defender.name))?.nfe)) || - (!hitsPhysical && defender.hasItem('Assault Vest'))) { - dfMods.push(6144); - desc.defenderItem = defender.item; - } else if ( - (defender.hasItem('Metal Powder') && defender.named('Ditto') && hitsPhysical) || - (defender.hasItem('Deep Sea Scale') && defender.named('Clamperl') && !hitsPhysical) - ) { - dfMods.push(8192); - desc.defenderItem = defender.item; - } - return dfMods; -} - -function calculateBaseDamageSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - basePower: number, - attack: number, - defense: number, - move: Move, - field: Field, - desc: RawDesc, - isCritical = false, -) { - let baseDamage = getBaseDamage(attacker.level, basePower, attack, defense); - const isSpread = field.gameType !== 'Singles' && - ['allAdjacent', 'allAdjacentFoes'].includes(move.target); - if (isSpread) { - baseDamage = pokeRound(OF32(baseDamage * 3072) / 4096); - } - - if (attacker.hasAbility('Parental Bond (Child)')) { - baseDamage = pokeRound(OF32(baseDamage * 1024) / 4096); - } - - if ( - field.hasWeather('Sun') && move.named('Hydro Steam') && !attacker.hasItem('Utility Umbrella') - ) { - baseDamage = pokeRound(OF32(baseDamage * 6144) / 4096); - desc.weather = field.weather; - } else if (!defender.hasItem('Utility Umbrella')) { - if ( - (field.hasWeather('Sun', 'Harsh Sunshine') && move.hasType('Fire')) || - (field.hasWeather('Rain', 'Heavy Rain') && move.hasType('Water')) - ) { - baseDamage = pokeRound(OF32(baseDamage * 6144) / 4096); - desc.weather = field.weather; - } else if ( - (field.hasWeather('Sun') && move.hasType('Water')) || - (field.hasWeather('Rain') && move.hasType('Fire')) - ) { - baseDamage = pokeRound(OF32(baseDamage * 2048) / 4096); - desc.weather = field.weather; - } - } - - if (isCritical) { - baseDamage = Math.floor(OF32(baseDamage * 1.5)); - desc.isCritical = isCritical; - } - - return baseDamage; -} - -export function calculateFinalModsSMSSSV( - gen: Generation, - attacker: Pokemon, - defender: Pokemon, - move: Move, - field: Field, - desc: RawDesc, - isCritical = false, - typeEffectiveness: number, - hitCount = 0 -) { - const finalMods = []; - - if (field.defenderSide.isReflect && move.category === 'Physical' && - !isCritical && !field.defenderSide.isAuroraVeil) { - // doesn't stack with Aurora Veil - finalMods.push(field.gameType !== 'Singles' ? 2732 : 2048); - desc.isReflect = true; - } else if ( - field.defenderSide.isLightScreen && move.category === 'Special' && - !isCritical && !field.defenderSide.isAuroraVeil - ) { - // doesn't stack with Aurora Veil - finalMods.push(field.gameType !== 'Singles' ? 2732 : 2048); - desc.isLightScreen = true; - } - if (field.defenderSide.isAuroraVeil && !isCritical) { - finalMods.push(field.gameType !== 'Singles' ? 2732 : 2048); - desc.isAuroraVeil = true; - } - - if (attacker.hasAbility('Neuroforce') && typeEffectiveness > 1) { - finalMods.push(5120); - desc.attackerAbility = attacker.ability; - } else if (attacker.hasAbility('Sniper') && isCritical) { - finalMods.push(6144); - desc.attackerAbility = attacker.ability; - } else if (attacker.hasAbility('Tinted Lens') && typeEffectiveness < 1) { - finalMods.push(8192); - desc.attackerAbility = attacker.ability; - } - - if (defender.isDynamaxed && move.named('Dynamax Cannon', 'Behemoth Blade', 'Behemoth Bash')) { - finalMods.push(8192); - } - - if (defender.hasAbility('Multiscale', 'Shadow Shield') && - defender.curHP() === defender.maxHP() && - hitCount === 0 && - (!field.defenderSide.isSR && (!field.defenderSide.spikes || defender.hasType('Flying')) || - defender.hasItem('Heavy-Duty Boots')) && !attacker.hasAbility('Parental Bond (Child)') - ) { - finalMods.push(2048); - desc.defenderAbility = defender.ability; - } - - if (defender.hasAbility('Fluffy') && move.flags.contact && !attacker.hasAbility('Long Reach')) { - finalMods.push(2048); - desc.defenderAbility = defender.ability; - } else if ( - (defender.hasAbility('Punk Rock') && move.flags.sound) || - (defender.hasAbility('Ice Scales') && move.category === 'Special') - ) { - finalMods.push(2048); - desc.defenderAbility = defender.ability; - } - - if (defender.hasAbility('Solid Rock', 'Filter', 'Prism Armor') && typeEffectiveness > 1) { - finalMods.push(3072); - desc.defenderAbility = defender.ability; - } - - if (field.defenderSide.isFriendGuard) { - finalMods.push(3072); - desc.isFriendGuard = true; - } - - if (defender.hasAbility('Fluffy') && move.hasType('Fire')) { - finalMods.push(8192); - desc.defenderAbility = defender.ability; - } - - if (attacker.hasItem('Expert Belt') && typeEffectiveness > 1 && !move.isZ) { - finalMods.push(4915); - desc.attackerItem = attacker.item; - } else if (attacker.hasItem('Life Orb')) { - finalMods.push(5324); - desc.attackerItem = attacker.item; - } else if (attacker.hasItem('Metronome') && move.timesUsedWithMetronome! >= 1) { - const timesUsedWithMetronome = Math.floor(move.timesUsedWithMetronome!); - if (timesUsedWithMetronome <= 4) { - finalMods.push(4096 + timesUsedWithMetronome * 819); - } else { - finalMods.push(8192); - } - desc.attackerItem = attacker.item; - } - - if (move.hasType(getBerryResistType(defender.item)) && - (typeEffectiveness > 1 || move.hasType('Normal')) && - hitCount === 0 && - !attacker.hasAbility('Unnerve', 'As One (Glastrier)', 'As One (Spectrier)')) { - if (defender.hasAbility('Ripen')) { - finalMods.push(1024); - } else { - finalMods.push(2048); - } - desc.defenderItem = defender.item; - } - - return finalMods; -} - -function hasTerrainSeed(pokemon: Pokemon) { - return pokemon.hasItem('Electric Seed', 'Misty Seed', 'Grassy Seed', 'Psychic Seed'); -} diff --git a/src/base.template.html b/src/base.template.html index aedb31881..22b8dac78 100644 --- a/src/base.template.html +++ b/src/base.template.html @@ -54,7 +54,7 @@
Select the calculator's mode of function. - + diff --git a/src/eipp-1.template.html b/src/eipp-1.template.html deleted file mode 100644 index 129fbbfdd..000000000 --- a/src/eipp-1.template.html +++ /dev/null @@ -1,1646 +0,0 @@ - - - - - - EiKPP Damage Calculator - - - - - - - - - - - - - - -
- - EiKPP Pokémon Damage Calculator -
- - Select the calculator's mode of function. - - - - - - Select the generation. - - - - - Select the output notation. - - - -
-
-
-
-
Pokémon 1's Moves (select one to show detailed results) -
-
- - - ???% -
-
- - - ???% -
-
- - - ???% -
-
- - - ???% -
-
-
-
Pokémon 2's Moves (select one to show detailed results) -
-
- - - ???% -
-
- - - ???% -
-
- - - ???% -
-
- - - ???% -
-
-
-
-
- Loading... (If you see this message for more than a few seconds, try enabling JavaScript.) -
Copied
-
-
- -
-
- Pokémon 1 - - -
-
- - - -
-
- - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
BaseIVsEVsDVs
- - - - - - - - - - 341 -
- - - - - - - - - - 236 - - -
- - - - - - - - - - 236 - - -
- - - - - - - - - - 236 - - -
- - - - - - - - - - 236 - - -
- - - - - - 236 - - -
- - - - - - - - - - 236 - - - --- -
-
-
-
- - -
-
- - - - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
-
-
- - /341 ( - %) - - -
-
- Health
-
- - -
- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - -
-
-
- -
-
-
- Field -
- Select the battle format. - - - - -
-
- Select the default level. - - - - - - -
-
- Select the current terrain. - - - - -
-
- Select the active Ruin abilities from other Pokeémon on the field. - - - - - - - - -
-
-
- Select the current weather condition. -
- - - - - - - - - - - - - - -
-
- - - - - - -
-
-
- Select the current weather condition. - - - - - - - - -
-
- - - - - - -
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pokémon 1's side
Pokémon 2's side
- - -
- - -
- - -
- - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - - - - - -
- - - - - - - - -
- - -
- - -
- - - - - -
- - - - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- -
- -
-
-
-
-
- Import / Export - -
-
-
-
-
- Pokémon 2 - - -
-
- - - -
-
- - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
BaseIVsEVsDVs
- - - - - - - - - - 341 -
- - - - - - - - - - 236 - - -
- - - - - - - - - - 236 - - -
- - - - - - - - - - 236 - - -
- - - - - - - - - - 236 - - -
- - - - - - 236 - - -
- - - - - - - - - - 236 - - - --- -
-
-
-
- - -
-
- - - - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
-
-
- - /341 ( - %) - - -
-
- Health
-
-
- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
Created by Honko, maintained by Austin and Kris
EiPP Mod by Celestia - - - - -
- - - -