diff --git a/build b/build index 8ff9f9c78..c1368f785 100755 --- a/build +++ b/build @@ -63,7 +63,6 @@ if (process.argv[2] !== 'view') { cpdir('src', 'dist'); cpdir('calc/dist', 'dist/calc'); makeCachebuster('dist/honkalculate.template.html', 'dist/honkalculate.html'); -makeCachebuster('dist/index.template.html', 'dist/index.html'); makeCachebuster('dist/base.template.html', 'dist/base.html'); -makeCachebuster('dist/eikpp2.template.html', 'dist/eikpp2.html'); +makeCachebuster('dist/eipp-1.template.html', 'dist/index.html'); makeCachebuster('dist/randoms.template.html', 'dist/randoms.html'); diff --git a/calc/src/data/eipp-1_items.ts b/calc/src/data/eipp-1_items.ts new file mode 100644 index 000000000..41ede2e5e --- /dev/null +++ b/calc/src/data/eipp-1_items.ts @@ -0,0 +1,647 @@ +import * as I from './interface'; +import {toID} from '../util'; + +const RBY: string[] = []; + +const GSC = [ + 'Berry Juice', + 'Berry', + 'Berserk Gene', + 'Bitter Berry', + 'Black Belt', + 'Black Glasses', + 'Bright Powder', + 'Burnt Berry', + 'Charcoal', + 'Dragon Fang', + 'Dragon Scale', + 'Fast Ball', + 'Fire Stone', + 'Focus Band', + 'Friend Ball', + 'Gold Berry', + 'Great Ball', + 'Hard Stone', + 'Heavy Ball', + 'Ice Berry', + 'King\'s Rock', + 'Leaf Stone', + 'Leftovers', + 'Level Ball', + 'Light Ball', + 'Love Ball', + 'Lucky Punch', + 'Lure Ball', + 'Magnet', + 'Mail', + 'Master Ball', + 'Metal Coat', + 'Metal Powder', + 'Mint Berry', + 'Miracle Berry', + 'Miracle Seed', + 'Moon Ball', + 'Moon Stone', + 'Mystery Berry', + 'Mystic Water', + 'Never-Melt Ice', + 'Pink Bow', + 'Poison Barb', + 'Poke Ball', + 'Polkadot Bow', + 'PRZ Cure Berry', + 'PSN Cure Berry', + 'Quick Claw', + 'Safari Ball', + 'Scope Lens', + 'Sharp Beak', + 'Silver Powder', + 'Soft Sand', + 'Spell Tag', + 'Sport Ball', + 'Stick', + 'Sun Stone', + 'Thick Club', + 'Thunder Stone', + 'Twisted Spoon', + 'Ultra Ball', + 'Up-Grade', + 'Water Stone', +]; + +const GSC_ONLY = [ + 'Berry', + 'Berserk Gene', + 'Bitter Berry', + 'Burnt Berry', + 'Ice Berry', + 'Mint Berry', + 'Miracle Berry', + 'Mystery Berry', + 'PRZ Cure Berry', + 'Gold Berry', + 'Pink Bow', + 'Polkadot Bow', + 'PSN Cure Berry', +]; + +const ADV = GSC.filter(i => !GSC_ONLY.includes(i)).concat([ + 'Aguav Berry', + 'Apicot Berry', + 'Aspear Berry', + 'Belue Berry', + 'Bluk Berry', + 'Cheri Berry', + 'Chesto Berry', + 'Choice Band', + 'Claw Fossil', + 'Cornn Berry', + 'Deep Sea Scale', + 'Deep Sea Tooth', + 'Dive Ball', + 'Dome Fossil', + 'Durin Berry', + 'Enigma Berry', + 'Figy Berry', + 'Ganlon Berry', + 'Grepa Berry', + 'Helix Fossil', + 'Hondew Berry', + 'Iapapa Berry', + 'Kelpsy Berry', + 'Lansat Berry', + 'Lax Incense', + 'Leppa Berry', + 'Liechi Berry', + 'Lum Berry', + 'Luxury Ball', + 'Macho Brace', + 'Mago Berry', + 'Magost Berry', + 'Mental Herb', + 'Nanab Berry', + 'Nest Ball', + 'Net Ball', + 'Nomel Berry', + 'Old Amber', + 'Oran Berry', + 'Pamtre Berry', + 'Pecha Berry', + 'Persim Berry', + 'Petaya Berry', + 'Pinap Berry', + 'Pomeg Berry', + 'Premier Ball', + 'Qualot Berry', + 'Rabuta Berry', + 'Rawst Berry', + 'Razz Berry', + 'Repeat Ball', + 'Root Fossil', + 'Salac Berry', + 'Sea Incense', + 'Shell Bell', + 'Silk Scarf', + 'Sitrus Berry', + 'Soul Dew', + 'Spelon Berry', + 'Starf Berry', + 'Tamato Berry', + 'Timer Ball', + 'Watmel Berry', + 'Wepear Berry', + 'White Herb', + 'Wiki Berry', +]); + +const DPP = ADV.concat([ + 'Adamant Orb', + 'Armor Fossil', + 'Babiri Berry', + 'Big Root', + 'Black Sludge', + 'Charti Berry', + 'Cherish Ball', + 'Chilan Berry', + 'Choice Scarf', + 'Choice Specs', + 'Chople Berry', + 'Coba Berry', + 'Colbur Berry', + 'Custap Berry', + 'Damp Rock', + 'Dawn Stone', + 'Destiny Knot', + 'Draco Plate', + 'Dread Plate', + 'Dubious Disc', + 'Dusk Ball', + 'Dusk Stone', + 'Earth Plate', + 'Electirizer', + 'Expert Belt', + 'Fist Plate', + 'Flame Orb', + 'Flame Plate', + 'Focus Sash', + 'Full Incense', + 'Grip Claw', + 'Griseous Orb', + 'Haban Berry', + 'Heal Ball', + 'Heat Rock', + 'Icicle Plate', + 'Icy Rock', + 'Insect Plate', + 'Iron Ball', + 'Iron Plate', + 'Jaboca Berry', + 'Kasib Berry', + 'Kebia Berry', + 'Lagging Tail', + 'Life Orb', + 'Light Clay', + 'Lustrous Orb', + 'Magmarizer', + 'Meadow Plate', + 'Metronome', + 'Micle Berry', + 'Mind Plate', + 'Muscle Band', + 'Occa Berry', + 'Odd Incense', + 'Oval Stone', + 'Park Ball', + 'Passho Berry', + 'Payapa Berry', + 'Power Anklet', + 'Power Band', + 'Power Belt', + 'Power Bracer', + 'Power Herb', + 'Power Lens', + 'Power Weight', + 'Protector', + 'Quick Ball', + 'Quick Powder', + 'Rare Bone', + 'Razor Claw', + 'Razor Fang', + 'Reaper Cloth', + 'Rindo Berry', + 'Rock Incense', + 'Rose Incense', + 'Rowap Berry', + 'Shed Shell', + 'Shiny Stone', + 'Shuca Berry', + 'Skull Fossil', + 'Sky Plate', + 'Smooth Rock', + 'Splash Plate', + 'Spooky Plate', + 'Sticky Barb', + 'Stone Plate', + 'Tanga Berry', + 'Toxic Orb', + 'Toxic Plate', + 'Wacan Berry', + 'Wave Incense', + 'Wide Lens', + 'Wise Glasses', + 'Yache Berry', + 'Zap Plate', + 'Zoom Lens', +]); + +const BW = DPP.concat([ + 'Absorb Bulb', + 'Air Balloon', + 'Big Nugget', + 'Binding Band', + 'Bug Gem', + 'Burn Drive', + 'Cell Battery', + 'Chill Drive', + 'Cover Fossil', + 'Dark Gem', + 'Douse Drive', + 'Dragon Gem', + 'Dream Ball', + 'Eject Button', + 'Electric Gem', + 'Eviolite', + 'Fighting Gem', + 'Fire Gem', + 'Float Stone', + 'Flying Gem', + 'Ghost Gem', + 'Grass Gem', + 'Ground Gem', + 'Ice Gem', + 'Normal Gem', + 'Plume Fossil', + 'Poison Gem', + 'Prism Scale', + 'Psychic Gem', + 'Red Card', + 'Ring Target', + 'Rock Gem', + 'Rocky Helmet', + 'Shock Drive', + 'Steel Gem', + 'Water Gem', +]); + +export const MEGA_STONES: {[species: string]: string} = { + Absolite: 'Absol', + Abomasite: 'Abomasnow', + Aerodactylite: 'Aerodactyl', + Aggronite: 'Aggron', + Alakazite: 'Alakazam', + Altarianite: 'Altaria', + Ampharosite: 'Ampharos', + Audinite: 'Audino', + Banettite: 'Banette', + Beedrillite: 'Beedrill', + Blastoisinite: 'Blastoise', + Blazikenite: 'Blaziken', + Cameruptite: 'Camerupt', + 'Charizardite X': 'Charizard', + 'Charizardite Y': 'Charizard', + Crucibellite: 'Crucibelle', + Diancite: 'Diancie', + Galladite: 'Gallade', + Garchompite: 'Garchomp', + Gardevoirite: 'Gardevoir', + Gengarite: 'Gengar', + Glalitite: 'Glalie', + Gyaradosite: 'Gyarados', + Heracronite: 'Heracross', + Houndoominite: 'Houndoom', + Kangaskhanite: 'Kangaskhan', + Latiasite: 'Latias', + Latiosite: 'Latios', + Lopunnite: 'Lopunny', + Lucarionite: 'Lucario', + Manectite: 'Manectric', + Mawilite: 'Mawile', + Medichamite: 'Medicham', + Metagrossite: 'Metagross', + 'Mewtwonite X': 'Mewtwo', + 'Mewtwonite Y': 'Mewtwo', + Pidgeotite: 'Pidgeot', + Pinsirite: 'Pinsir', + Sablenite: 'Sableye', + Salamencite: 'Salamence', + Sceptilite: 'Sceptile', + Scizorite: 'Scizor', + Sharpedonite: 'Sharpedo', + Slowbronite: 'Slowbro', + Steelixite: 'Steelix', + Swampertite: 'Swampert', + Tyranitarite: 'Tyranitar', + Venusaurite: 'Venusaur', +}; + +const XY = BW.concat( + [ + ...Object.keys(MEGA_STONES), + 'Assault Vest', + 'Blue Orb', + 'Fairy Gem', + 'Jaw Fossil', + 'Kee Berry', + 'Luminous Moss', + 'Maranga Berry', + 'Pixie Plate', + 'Red Orb', + 'Roseli Berry', + 'Sachet', + 'Safety Goggles', + 'Sail Fossil', + 'Snowball', + 'Weakness Policy', + 'Whipped Dream', + ].sort() +); + +const SM = XY.filter(i => i !== 'Old Amber').concat([ + 'Adrenaline Orb', + 'Aloraichium Z', + 'Beast Ball', + 'Bottle Cap', + 'Bug Memory', + 'Buginium Z', + 'Dark Memory', + 'Darkinium Z', + 'Decidium Z', + 'Dragon Memory', + 'Dragonium Z', + 'Eevium Z', + 'Electric Memory', + 'Electric Seed', + 'Electrium Z', + 'Fairium Z', + 'Fairy Memory', + 'Fighting Memory', + 'Fightinium Z', + 'Fire Memory', + 'Firium Z', + 'Flying Memory', + 'Flyinium Z', + 'Ghost Memory', + 'Ghostium Z', + 'Gold Bottle Cap', + 'Grass Memory', + 'Grassium Z', + 'Grassy Seed', + 'Ground Memory', + 'Groundium Z', + 'Ice Memory', + 'Ice Stone', + 'Icium Z', + 'Incinium Z', + 'Kommonium Z', + 'Lunalium Z', + 'Lycanium Z', + 'Marshadium Z', + 'Mewnium Z', + 'Mimikium Z', + 'Misty Seed', + 'Normalium Z', + 'Pikanium Z', + 'Pikashunium Z', + 'Poison Memory', + 'Poisonium Z', + 'Primarium Z', + 'Protective Pads', + 'Psychic Memory', + 'Psychic Seed', + 'Psychium Z', + 'Rock Memory', + 'Rockium Z', + 'Snorlium Z', + 'Solganium Z', + 'Steel Memory', + 'Steelium Z', + 'Tapunium Z', + 'Terrain Extender', + 'Ultranecrozium Z', + 'Water Memory', + 'Waterium Z', +]); + +const SS = SM.concat([ + 'Berry Sweet', + 'Blunder Policy', + 'Chipped Pot', + 'Clover Sweet', + 'Cracked Pot', + 'Eject Pack', + 'Flower Sweet', + 'Fossilized Bird', + 'Fossilized Dino', + 'Fossilized Drake', + 'Fossilized Fish', + 'Galarica Cuff', + 'Galarica Wreath', + 'Heavy-Duty Boots', + 'Leek', + 'Love Sweet', + 'Ribbon Sweet', + 'Room Service', + 'Rusted Shield', + 'Rusted Sword', + 'Star Sweet', + 'Strawberry Sweet', + 'Sweet Apple', + 'Tart Apple', + 'Throat Spray', +]); + +for (let i = 0; i < 100; i++) { + SS.push(`TR${i < 10 ? `0${i}` : i}`); +} + +// Added after the TRs to maintain sort order +SS.push('Utility Umbrella', 'Vile Vial'); + +// Because we support National Dex all Past items are added back in +SS.push(...GSC_ONLY, 'Old Amber'); + +const SV = SS.concat([ + 'Ability Shield', + 'Adamant Crystal', + 'Assist Shovel', + 'Assurance Policy', + 'Auspicious Armor', + 'Bare Spool', + 'Boomerang', + 'Booster Energy', + 'Clear Amulet', + 'Cornerstone Mask', + 'Covert Cloak', + 'Drain Shield', + 'EiPP Crown', + 'Fairy Feather', + 'Griseous Core', + 'Hearthflame Mask', + 'Loaded Dice', + 'Lustrous Globe', + 'Malicious Armor', + 'Masterpiece Teacup', + 'Metal Alloy', + 'Mirror Herb', + 'Nurse Hat', + 'Overlord Crown', + 'Punching Glove', + 'Queen\'s Gland', + 'Room Service 2', + 'Sharpshooter\'s Lens', + 'Sledgehammer', + 'Slime', + 'Spit Card', + 'Strange Ball', + 'Syrupy Apple', + 'Unremarkable Teacup', + 'Wellspring Mask', + 'Wide Decoy', +]); + +const BERRIES: {[berry: string]: {t: I.TypeName; p: number}} = { + 'Aguav Berry': {t: 'Dragon', p: 80}, + 'Apicot Berry': {t: 'Ground', p: 100}, + 'Aspear Berry': {t: 'Ice', p: 80}, + 'Babiri Berry': {t: 'Steel', p: 80}, + 'Belue Berry': {t: 'Electric', p: 100}, + Berry: {t: 'Poison', p: 80}, + 'Bitter Berry': {t: 'Ground', p: 80}, + 'Bluk Berry': {t: 'Fire', p: 90}, + 'Burnt Berry': {t: 'Ice', p: 80}, + 'Charti Berry': {t: 'Rock', p: 80}, + 'Cheri Berry': {t: 'Fire', p: 80}, + 'Chesto Berry': {t: 'Water', p: 80}, + 'Chilan Berry': {t: 'Normal', p: 80}, + 'Chople Berry': {t: 'Fighting', p: 80}, + 'Coba Berry': {t: 'Flying', p: 80}, + 'Colbur Berry': {t: 'Dark', p: 80}, + 'Cornn Berry': {t: 'Bug', p: 90}, + 'Custap Berry': {t: 'Ghost', p: 100}, + 'Durin Berry': {t: 'Water', p: 100}, + 'Enigma Berry': {t: 'Bug', p: 100}, + 'Figy Berry': {t: 'Bug', p: 80}, + 'Ganlon Berry': {t: 'Ice', p: 100}, + 'Gold Berry': {t: 'Psychic', p: 80}, + 'Grepa Berry': {t: 'Flying', p: 90}, + 'Haban Berry': {t: 'Dragon', p: 80}, + 'Hondew Berry': {t: 'Ground', p: 90}, + 'Iapapa Berry': {t: 'Dark', p: 80}, + 'Ice Berry': {t: 'Grass', p: 80}, + 'Jaboca Berry': {t: 'Dragon', p: 100}, + 'Kasib Berry': {t: 'Ghost', p: 80}, + 'Kebia Berry': {t: 'Poison', p: 80}, + 'Kee Berry': {t: 'Fairy', p: 100}, + 'Kelpsy Berry': {t: 'Fighting', p: 90}, + 'Lansat Berry': {t: 'Flying', p: 100}, + 'Leppa Berry': {t: 'Fighting', p: 80}, + 'Liechi Berry': {t: 'Grass', p: 100}, + 'Lum Berry': {t: 'Flying', p: 80}, + 'Mago Berry': {t: 'Ghost', p: 80}, + 'Magost Berry': {t: 'Rock', p: 90}, + 'Maranga Berry': {t: 'Dark', p: 100}, + 'Micle Berry': {t: 'Rock', p: 100}, + 'Mint Berry': {t: 'Water', p: 80}, + 'Miracle Berry': {t: 'Flying', p: 80}, + 'Mystery Berry': {t: 'Fighting', p: 80}, + 'Nanab Berry': {t: 'Water', p: 90}, + 'Nomel Berry': {t: 'Dragon', p: 90}, + 'Occa Berry': {t: 'Fire', p: 80}, + 'Oran Berry': {t: 'Poison', p: 80}, + 'Pamtre Berry': {t: 'Steel', p: 90}, + 'Passho Berry': {t: 'Water', p: 80}, + 'Payapa Berry': {t: 'Psychic', p: 80}, + 'Pecha Berry': {t: 'Electric', p: 80}, + 'Persim Berry': {t: 'Ground', p: 80}, + 'Petaya Berry': {t: 'Poison', p: 100}, + 'Pinap Berry': {t: 'Grass', p: 90}, + 'Pomeg Berry': {t: 'Ice', p: 90}, + 'PRZ Cure Berry': {t: 'Fire', p: 80}, + 'PSN Cure Berry': {t: 'Electric', p: 80}, + 'Qualot Berry': {t: 'Poison', p: 90}, + 'Rabuta Berry': {t: 'Ghost', p: 90}, + 'Rawst Berry': {t: 'Grass', p: 80}, + 'Razz Berry': {t: 'Steel', p: 80}, + 'Rindo Berry': {t: 'Grass', p: 80}, + 'Roseli Berry': {t: 'Fairy', p: 80}, + 'Rowap Berry': {t: 'Dark', p: 100}, + 'Salac Berry': {t: 'Fighting', p: 100}, + 'Shuca Berry': {t: 'Ground', p: 80}, + 'Sitrus Berry': {t: 'Psychic', p: 80}, + 'Spelon Berry': {t: 'Dark', p: 90}, + 'Starf Berry': {t: 'Psychic', p: 100}, + 'Tamato Berry': {t: 'Psychic', p: 90}, + 'Tanga Berry': {t: 'Bug', p: 80}, + 'Wacan Berry': {t: 'Electric', p: 80}, + 'Watmel Berry': {t: 'Fire', p: 100}, + 'Wepear Berry': {t: 'Electric', p: 90}, + 'Wiki Berry': {t: 'Rock', p: 80}, + 'Yache Berry': {t: 'Ice', p: 80}, +}; + +export const ITEMS = [[], RBY, GSC, ADV, DPP, BW, XY, SM, SS, SV]; + +export class Items implements I.Items { + private readonly gen: I.GenerationNum; + + constructor(gen: I.GenerationNum) { + this.gen = gen; + } + + get(id: I.ID) { + return ITEMS_BY_ID[this.gen][id]; + } + + *[Symbol.iterator]() { + for (const id in ITEMS_BY_ID[this.gen]) { + yield this.get(id as I.ID)!; + } + } +} + +class Item implements I.Item { + readonly kind: 'Item'; + readonly id: I.ID; + readonly name: I.ItemName; + readonly megaEvolves?: I.SpeciesName; + readonly isBerry?: boolean; + readonly naturalGift?: Readonly<{basePower: number; type: I.TypeName}>; + + constructor(name: string, gen: number) { + this.kind = 'Item'; + this.id = toID(name); + this.name = name as I.ItemName; + this.megaEvolves = MEGA_STONES[name] as I.SpeciesName; + const berry = BERRIES[name]; + if (berry) { + this.isBerry = true; + this.naturalGift = { + basePower: gen < 6 ? berry.p - 20 : berry.p, + type: berry.t, + }; + } + } +} + +const ITEMS_BY_ID: Array<{[id: string]: Item}> = []; + +let gen = 0; +for (const items of ITEMS) { + const map: {[id: string]: Item} = {}; + for (const item of items) { + const i = new Item(item, gen); + map[i.id] = i; + } + ITEMS_BY_ID.push(map); + gen++; +} diff --git a/calc/src/data/eipp-1_species.ts b/calc/src/data/eipp-1_species.ts new file mode 100644 index 000000000..a891d743b --- /dev/null +++ b/calc/src/data/eipp-1_species.ts @@ -0,0 +1,687 @@ +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} = { + Dragonite: { + types: ['Dragon', 'Flying'], + bs: {hp: 91, at: 134, df: 95, sp: 80, sl: 100}, + weightkg: 210, + }, + Gyarados: { + types: ['Water', 'Flying'], + bs: {hp: 95, at: 125, df: 79, sp: 81, sl: 100}, + weightkg: 235, + }, + Mew: { + types: ['Psychic'], + bs: {hp: 100, at: 100, df: 100, sp: 100, sl: 100}, + weightkg: 4, + }, + Pinsir: {types: ['Bug'], bs: {hp: 65, at: 125, df: 100, sp: 85, sl: 55}, weightkg: 55}, + Slowbro: { + types: ['Water', 'Psychic'], + bs: {hp: 95, at: 75, df: 110, sp: 30, sl: 80}, + weightkg: 78.5, + }, + Snorlax: { + types: ['Normal'], + bs: {hp: 160, at: 110, df: 65, sp: 30, sl: 65}, + weightkg: 460, + }, + Vaporeon: { + types: ['Water'], + bs: {hp: 130, at: 65, df: 60, sp: 65, sl: 110}, + weightkg: 29, + }, + Venusaur: { + types: ['Grass', 'Poison'], + bs: {hp: 80, at: 82, df: 83, sp: 80, sl: 100}, + weightkg: 100, + }, + Zapdos: { + types: ['Electric', 'Flying'], + bs: {hp: 90, at: 90, df: 85, sp: 100, sl: 125}, + weightkg: 52.6, + }, +}; + +const GSC_PATCH: {[name: string]: DeepPartial} = { + // gen 1 pokemon changes + Dragonite: {bs: {sa: 100, sd: 100}}, + Gyarados: {bs: {sa: 60, sd: 100}}, + Mew: {bs: {sa: 100, sd: 100}, gender: 'N'}, + Pinsir: {bs: {sa: 55, sd: 70}}, + Slowbro: {bs: {sa: 100, sd: 80}}, + Snorlax: {bs: {sa: 65, sd: 110}}, + Vaporeon: {bs: {sa: 110, sd: 95}}, + Venusaur: {bs: {sa: 100, sd: 100}}, + Zapdos: {bs: {sa: 125, sd: 90}, gender: 'N'}, + // gen 2 pokemon + Heracross: { + types: ['Bug', 'Fighting'], + bs: {hp: 80, at: 125, df: 75, sa: 40, sd: 95, sp: 85}, + weightkg: 54, + }, + Houndoom: { + types: ['Dark', 'Fire'], + bs: {hp: 75, at: 90, df: 50, sa: 110, sd: 80, sp: 95}, + weightkg: 35, + }, + Octillery: { + types: ['Water'], + bs: {hp: 75, at: 105, df: 75, sa: 105, sd: 75, sp: 45}, + weightkg: 28.5, + }, + Porygon2: { + types: ['Normal'], + bs: {hp: 85, at: 80, df: 90, sa: 105, sd: 95, sp: 60}, + weightkg: 32.5, + gender: 'N', + }, + Scizor: { + types: ['Bug', 'Steel'], + bs: {hp: 70, at: 130, df: 100, sa: 55, sd: 80, sp: 65}, + weightkg: 118, + }, + Shuckle: { + types: ['Bug', 'Rock'], + bs: {hp: 20, at: 10, df: 230, sa: 10, sd: 230, sp: 5}, + weightkg: 20.5, + }, + Togetic: { + types: ['Normal', 'Flying'], + bs: {hp: 55, at: 40, df: 85, sa: 80, sd: 105, sp: 40}, + weightkg: 3.2, + }, +}; +const GSC: {[name: string]: SpeciesData} = extend(true, {}, RBY, GSC_PATCH); + +const ADV_PATCH: {[name: string]: DeepPartial} = { + // gen 1 pokemon changes + Dragonite: {abilities: {0: 'Inner Focus'}}, + Gyarados: {abilities: {0: 'Intimidate'}}, + Mew: {abilities: {0: 'Synchronize'}}, + Pinsir: {abilities: {0: 'Hyper Cutter'}}, + Slowbro: {abilities: {0: 'Oblivious'}}, + Snorlax: {abilities: {0: 'Immunity'}}, + Vaporeon: {abilities: {0: 'Water Absorb'}}, + Venusaur: {abilities: {0: 'Overgrow'}}, + Zapdos: {abilities: {0: 'Pressure'}}, + // gen 2 pokemon changes + Heracross: {abilities: {0: 'Swarm'}}, + Houndoom: {abilities: {0: 'Early Bird'}}, + Octillery: {abilities: {0: 'Suction Cups'}}, + Porygon2: {abilities: {0: 'Trace'}}, + Scizor: {abilities: {0: 'Swarm'}}, + Shuckle: {abilities: {0: 'Sturdy'}}, + Togetic: {abilities: {0: 'Hustle'}}, + // gen 3 pokemon + Altaria: { + types: ['Dragon', 'Flying'], + bs: {hp: 75, at: 70, df: 90, sa: 70, sd: 105, sp: 80}, + weightkg: 20.6, + abilities: {0: 'Natural Cure'}, + }, + Armaldo: { + types: ['Rock', 'Bug'], + bs: {hp: 75, at: 125, df: 100, sa: 70, sd: 80, sp: 45}, + weightkg: 68.2, + abilities: {0: 'Battle Armor'}, + }, + Banette: { + types: ['Ghost'], + bs: {hp: 64, at: 115, df: 65, sa: 83, sd: 63, sp: 65}, + weightkg: 12.5, + abilities: {0: 'Insomnia'}, + }, + Blaziken: { + types: ['Fire', 'Fighting'], + bs: {hp: 80, at: 120, df: 70, sa: 110, sd: 70, sp: 80}, + weightkg: 52, + abilities: {0: 'Blaze'}, + }, + 'Deoxys-Defense': { + types: ['Psychic'], + bs: {hp: 50, at: 70, df: 160, sa: 70, sd: 160, sp: 90}, + weightkg: 60.8, + abilities: {0: 'Pressure'}, + gender: 'N', + }, + Gardevoir: { + types: ['Psychic'], + bs: {hp: 68, at: 65, df: 65, sa: 125, sd: 115, sp: 80}, + weightkg: 48.4, + abilities: {0: 'Synchronize'}, + }, + Glalie: { + types: ['Ice'], + bs: {hp: 80, at: 80, df: 80, sa: 80, sd: 80, sp: 80}, + weightkg: 256.5, + abilities: {0: 'Inner Focus'}, + }, + Jirachi: { + types: ['Steel', 'Psychic'], + bs: {hp: 100, at: 100, df: 100, sa: 100, sd: 100, sp: 100}, + weightkg: 1.1, + abilities: {0: 'Serene Grace'}, + gender: 'N', + }, + Mawile: { + types: ['Steel'], + bs: {hp: 50, at: 85, df: 85, sa: 55, sd: 55, sp: 50}, + weightkg: 11.5, + abilities: {0: 'Hyper Cutter'}, + }, + Metagross: { + types: ['Steel', 'Psychic'], + bs: {hp: 80, at: 135, df: 130, sa: 95, sd: 90, sp: 70}, + weightkg: 550, + gender: 'N', + abilities: {0: 'Clear Body'}, + }, + Sableye: { + types: ['Dark', 'Ghost'], + bs: {hp: 50, at: 75, df: 75, sa: 65, sd: 65, sp: 50}, + weightkg: 11, + abilities: {0: 'Keen Eye'}, + }, + Sceptile: { + types: ['Grass'], + bs: {hp: 70, at: 85, df: 65, sa: 105, sd: 85, sp: 120}, + weightkg: 52.2, + abilities: {0: 'Overgrow'}, + }, +}; + +const ADV: {[name: string]: SpeciesData} = extend(true, {}, GSC, ADV_PATCH); + +const DPP_PATCH: {[name: string]: DeepPartial} = { + + Porygon2: {nfe: true}, + Togetic: {nfe: true}, + Cresselia: { + types: ['Psychic'], + bs: {hp: 120, at: 70, df: 120, sa: 75, sd: 130, sp: 85}, + weightkg: 85.6, + abilities: {0: 'Levitate'}, + }, + Heatran: { + types: ['Fire', 'Steel'], + bs: {hp: 91, at: 90, df: 106, sa: 130, sd: 106, sp: 77}, + weightkg: 430, + abilities: {0: 'Flash Fire'}, + }, + Lucario: { + types: ['Fighting', 'Steel'], + bs: {hp: 70, at: 110, df: 70, sa: 115, sd: 70, sp: 90}, + weightkg: 54, + abilities: {0: 'Steadfast'}, + }, + Manaphy: { + types: ['Water'], + bs: {hp: 100, at: 100, df: 100, sa: 100, sd: 100, sp: 100}, + weightkg: 1.4, + abilities: {0: 'Hydration'}, + gender: 'N', + }, + 'Rotom-Wash': { + types: ['Electric', 'Ghost'], + bs: {hp: 50, at: 65, df: 107, sa: 105, sd: 107, sp: 86}, + weightkg: 0.3, + abilities: {0: 'Levitate'}, + gender: 'N', + }, + Vespiquen: { + types: ['Bug', 'Flying'], + bs: {hp: 70, at: 80, df: 102, sa: 80, sd: 102, sp: 40}, + weightkg: 38.5, + abilities: {0: 'Pressure'}, + }, +}; + +const DPP: {[name: string]: SpeciesData} = extend(true, {}, ADV, DPP_PATCH); + +const BW_PATCH: {[name: string]: DeepPartial} = { + 'Rotom-Wash': {types: ['Electric', 'Water']}, + Chandelure: { + types: ['Ghost', 'Fire'], + bs: {hp: 60, at: 55, df: 90, sa: 145, sd: 90, sp: 80}, + weightkg: 34.3, + abilities: {0: 'Flash Fire'}, + }, + Cofagrigus: { + types: ['Ghost'], + bs: {hp: 58, at: 50, df: 145, sa: 95, sd: 105, sp: 30}, + weightkg: 76.5, + abilities: {0: 'Mummy'}, + }, + Landorus: { + types: ['Ground', 'Flying'], + bs: {hp: 89, at: 125, df: 90, sa: 115, sd: 80, sp: 101}, + weightkg: 68, + abilities: {0: 'Sand Force'}, + }, + Scolipede: { + types: ['Bug', 'Poison'], + bs: {hp: 60, at: 90, df: 89, sa: 55, sd: 69, sp: 112}, + weightkg: 200.5, + abilities: {0: 'Poison Point'}, + }, + Thundurus: { + types: ['Electric', 'Flying'], + bs: {hp: 79, at: 115, df: 70, sa: 125, sd: 80, sp: 111}, + weightkg: 61, + abilities: {0: 'Prankster'}, + }, + 'Tornadus-Therian': { + types: ['Flying'], + bs: {hp: 79, at: 100, df: 80, sa: 110, sd: 90, sp: 121}, + weightkg: 63, + abilities: {0: 'Regenerator'}, + }, + Victini: { + types: ['Psychic', 'Fire'], + bs: {hp: 100, at: 100, df: 100, sa: 100, sd: 100, sp: 100}, + weightkg: 4, + abilities: {0: 'Victory Star'}, + gender: 'N', + }, + Volcarona: { + types: ['Bug', 'Fire'], + bs: {hp: 85, at: 60, df: 65, sa: 135, sd: 105, sp: 100}, + weightkg: 46, + abilities: {0: 'Flame Body'}, + }, +}; + +const BW: {[name: string]: SpeciesData} = extend(true, {}, DPP, BW_PATCH); + +// @ts-ignore readonly + +const XY_PATCH: {[name: string]: DeepPartial} = { + Altaria: {otherFormes: ['Altaria-Mega']}, + Banette: {otherFormes: ['Banette-Mega']}, + Blaziken: {otherFormes: ['Blaziken-Mega']}, + Gardevoir: {types: ['Psychic', 'Fairy'], otherFormes: ['Gardevoir-Mega']}, + Glalie: {otherFormes: ['Glalie-Mega']}, + Houndoom: {otherFormes: ['Houndoom-Mega']}, + Lucario: {otherFormes: ['Lucario-Mega']}, + Mawile: {types: ['Steel', 'Fairy'], otherFormes: ['Mawile-Mega']}, + Pinsir: {otherFormes: ['Pinsir-Mega']}, + Sceptile: {otherFormes: ['Sceptile-Mega']}, + Scolipede: {bs: {at: 100}}, + Slowbro: {otherFormes: ['Slowbro-Mega']}, + Togetic: {types: ['Fairy', 'Flying']}, + Venusaur: {otherFormes: ['Venusaur-Mega']}, + Avalugg: { + types: ['Ice'], + bs: {hp: 95, at: 117, df: 184, sa: 44, sd: 46, sp: 28}, + weightkg: 505, + abilities: {0: 'Own Tempo'}, + }, + Dragalge: { + types: ['Poison', 'Dragon'], + bs: {hp: 65, at: 75, df: 90, sa: 97, sd: 123, sp: 44}, + weightkg: 81.5, + abilities: {0: 'Poison Point'}, + }, + 'Altaria-Mega': { + types: ['Dragon', 'Fairy'], + bs: {hp: 75, at: 110, df: 110, sa: 110, sd: 105, sp: 80}, + weightkg: 20.6, + abilities: {0: 'Pixilate'}, + baseSpecies: 'Altaria', + }, + 'Banette-Mega': { + types: ['Ghost'], + bs: {hp: 64, at: 165, df: 75, sa: 93, sd: 83, sp: 75}, + weightkg: 13, + abilities: {0: 'Prankster'}, + baseSpecies: 'Banette', + }, + 'Blaziken-Mega': { + types: ['Fire', 'Fighting'], + bs: {hp: 80, at: 160, df: 80, sa: 130, sd: 80, sp: 100}, + weightkg: 52, + abilities: {0: 'Speed Boost'}, + baseSpecies: 'Blaziken', + }, + 'Gardevoir-Mega': { + types: ['Psychic', 'Fairy'], + bs: {hp: 68, at: 85, df: 65, sa: 165, sd: 135, sp: 100}, + weightkg: 48.4, + abilities: {0: 'Pixilate'}, + baseSpecies: 'Gardevoir', + }, + 'Glalie-Mega': { + types: ['Ice'], + bs: {hp: 80, at: 120, df: 80, sa: 120, sd: 80, sp: 100}, + weightkg: 350.2, + abilities: {0: 'Refrigerate'}, + baseSpecies: 'Glalie', + }, + 'Houndoom-Mega': { + types: ['Dark', 'Fire'], + bs: {hp: 75, at: 90, df: 90, sa: 140, sd: 90, sp: 115}, + weightkg: 49.5, + abilities: {0: 'Solar Power'}, + baseSpecies: 'Houndoom', + }, + 'Lucario-Mega': { + types: ['Fighting', 'Steel'], + bs: {hp: 70, at: 145, df: 88, sa: 140, sd: 70, sp: 112}, + weightkg: 57.5, + abilities: {0: 'Adaptability'}, + baseSpecies: 'Lucario', + }, + 'Mawile-Mega': { + types: ['Steel', 'Fairy'], + bs: {hp: 50, at: 105, df: 125, sa: 55, sd: 95, sp: 50}, + weightkg: 23.5, + abilities: {0: 'Huge Power'}, + baseSpecies: 'Mawile', + }, + 'Pinsir-Mega': { + types: ['Bug', 'Flying'], + bs: {hp: 65, at: 155, df: 120, sa: 65, sd: 90, sp: 105}, + weightkg: 59, + abilities: {0: 'Aerilate'}, + baseSpecies: 'Pinsir', + }, + 'Sceptile-Mega': { + types: ['Grass', 'Dragon'], + bs: {hp: 70, at: 110, df: 75, sa: 145, sd: 85, sp: 145}, + weightkg: 55.2, + abilities: {0: 'Lightning Rod'}, + baseSpecies: 'Sceptile', + }, + 'Slowbro-Mega': { + types: ['Water', 'Psychic'], + bs: {hp: 95, at: 75, df: 180, sa: 130, sd: 80, sp: 30}, + weightkg: 120, + abilities: {0: 'Shell Armor'}, + baseSpecies: 'Slowbro', + }, + 'Venusaur-Mega': { + types: ['Grass', 'Poison'], + bs: {hp: 80, at: 100, df: 123, sa: 122, sd: 120, sp: 80}, + weightkg: 155.5, + abilities: {0: 'Thick Fat'}, + baseSpecies: 'Venusaur', + }, + Sylveon: { + types: ['Fairy'], + bs: {hp: 95, at: 65, df: 65, sa: 110, sd: 130, sp: 60}, + weightkg: 23.5, + abilities: {0: 'Cute Charm'}, + }, +}; + +const XY: {[name: string]: SpeciesData} = extend(true, {}, BW, XY_PATCH); + +const SM_PATCH: {[name: string]: DeepPartial} = { + Comfey: { + types: ['Fairy'], + bs: {hp: 51, at: 52, df: 90, sa: 82, sd: 110, sp: 100}, + weightkg: 0.3, + abilities: {0: 'Flower Veil'}, + }, + Golisopod: { + types: ['Bug', 'Water'], + bs: {hp: 75, at: 125, df: 140, sa: 60, sd: 90, sp: 40}, + weightkg: 108, + abilities: {0: 'Emergency Exit'}, + }, + Necrozma: { + types: ['Psychic'], + bs: {hp: 97, at: 107, df: 101, sa: 127, sd: 89, sp: 79}, + weightkg: 230, + abilities: {0: 'Prism Armor'}, + gender: 'N', + }, + Nihilego: { + types: ['Rock', 'Poison'], + bs: {hp: 109, at: 53, df: 47, sa: 127, sd: 131, sp: 103}, + weightkg: 55.5, + abilities: {0: 'Beast Boost'}, + gender: 'N', + }, + Oranguru: { + types: ['Normal', 'Psychic'], + bs: {hp: 90, at: 60, df: 80, sa: 90, sd: 110, sp: 60}, + weightkg: 76, + abilities: {0: 'Inner Focus'}, + }, + 'Tapu Fini': { + types: ['Water', 'Fairy'], + bs: {hp: 70, at: 75, df: 115, sa: 95, sd: 130, sp: 85}, + weightkg: 21.2, + abilities: {0: 'Misty Surge'}, + gender: 'N', + }, + Turtonator: { + types: ['Fire', 'Dragon'], + bs: {hp: 60, at: 78, df: 135, sa: 91, sd: 85, sp: 36}, + weightkg: 212, + abilities: {0: 'Shell Armor'}, + }, + 'Type: Null': { + types: ['Normal'], + bs: {hp: 95, at: 95, df: 95, sa: 95, sd: 95, sp: 59}, + weightkg: 120.5, + abilities: {0: 'Battle Armor'}, + nfe: true, + gender: 'N', + }, + 'Zygarde-10%': { + types: ['Dragon', 'Ground'], + bs: {hp: 54, at: 100, df: 71, sa: 61, sd: 85, sp: 115}, + weightkg: 33.5, + abilities: {0: 'Aura Break'}, + baseSpecies: 'Zygarde', + gender: 'N', + }, +}; + +const SM: {[name: string]: SpeciesData} = extend(true, {}, XY, SM_PATCH); + +const SS_PATCH: {[name: string]: DeepPartial} = { + Slowbro: {otherFormes: ['Slowbro-Galar', 'Slowbro-Mega']}, + Alcremie: { + types: ['Fairy'], + bs: {hp: 65, at: 60, df: 75, sa: 110, sd: 121, sp: 64}, + weightkg: 0.5, + abilities: {0: 'Sweet Veil'}, + otherFormes: ['Alcremie-Gmax'], + }, + Corviknight: { + types: ['Flying', 'Steel'], + bs: {hp: 98, at: 87, df: 105, sa: 53, sd: 85, sp: 67}, + weightkg: 75, + abilities: {0: 'Pressure'}, + }, + Dragapult: { + types: ['Dragon', 'Ghost'], + bs: {hp: 88, at: 120, df: 75, sa: 100, sd: 75, sp: 142}, + weightkg: 50, + abilities: {0: 'Clear Body'}, + }, + Inteleon: { + types: ['Water'], + bs: {hp: 70, at: 85, df: 65, sa: 125, sd: 65, sp: 120}, + weightkg: 45.2, + abilities: {0: 'Torrent'}, + }, + Regidrago: { + types: ['Dragon'], + bs: {hp: 200, at: 100, df: 50, sa: 100, sd: 50, sp: 80}, + weightkg: 200, + abilities: {0: 'Dragon\'s Maw'}, + gender: 'N', + }, + 'Slowbro-Galar': { + types: ['Poison', 'Psychic'], + bs: {hp: 95, at: 100, df: 95, sa: 100, sd: 70, sp: 30}, + weightkg: 70.5, + abilities: {0: 'Quick Draw'}, + baseSpecies: 'Slowbro', + }, +}; + +const SS: {[name: string]: SpeciesData} = extend(true, {}, SM, SS_PATCH); + +const PLA_PATCH: {[name: string]: DeepPartial} = { + 'Enamorus-Therian': { + types: ['Fairy', 'Flying'], + bs: {hp: 74, at: 115, df: 110, sa: 135, sd: 100, sp: 46}, + weightkg: 48, + abilities: {0: 'Overcoat'}, + }, + 'Goodra-Hisui': { + types: ['Steel', 'Dragon'], + bs: {hp: 80, at: 100, df: 100, sa: 110, sd: 150, sp: 60}, + weightkg: 334.1, + abilities: {0: 'Sap Sipper'}, + }, +}; + +const SV_PATCH: {[name: string]: DeepPartial} = { + Cresselia: {bs: {df: 110, sd: 120}}, + Archaludon: { + types: ['Steel', 'Dragon'], + bs: {hp: 90, at: 105, df: 130, sa: 125, sd: 65, sp: 85}, + weightkg: 60, + abilities: {0: 'Stamina'}, + }, + 'Chi-Yu': { + types: ['Dark', 'Fire'], + bs: {hp: 55, at: 80, df: 80, sa: 135, sd: 120, sp: 100}, + weightkg: 4.9, + gender: 'N', + abilities: {0: 'Beads of Ruin'}, + }, + Gholdengo: { + types: ['Steel', 'Ghost'], + bs: {hp: 87, at: 60, df: 95, sa: 133, sd: 91, sp: 84}, + weightkg: 30, + gender: 'N', + abilities: {0: 'Good as Gold'}, + }, + 'Gouging Fire': { + types: ['Fire', 'Dragon'], + bs: {hp: 105, at: 115, df: 121, sa: 65, sd: 93, sp: 91}, + weightkg: 590, + gender: 'N', + abilities: {0: 'Protosynthesis'}, + }, + 'Sinistcha': { + types: ['Grass', 'Ghost'], + bs: {hp: 71, at: 60, df: 106, sa: 121, sd: 80, sp: 70}, + weightkg: 2.2, + abilities: {0: 'Hospitality'}, + gender: 'N', + }, + 'Ursaluna-Bloodmoon': { + types: ['Ground', 'Normal'], + bs: {hp: 113, at: 70, df: 120, sa: 135, sd: 65, sp: 52}, + weightkg: 333, + abilities: {0: 'Mind\'s Eye'}, + }, +}; + +const SV: {[name: string]: SpeciesData} = extend(true, {}, SS, SV_PATCH, PLA_PATCH); + +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/eipp-1_items.ts b/calc/src/eipp-1_items.ts new file mode 100644 index 000000000..b41f09a4a --- /dev/null +++ b/calc/src/eipp-1_items.ts @@ -0,0 +1,438 @@ +import {Generation, TypeName, StatID} from './data/interface'; +import {toID} from './util'; + +export const SEED_BOOSTED_STAT: {[item: string]: StatID} = { + 'Electric Seed': 'def', + 'Grassy Seed': 'def', + 'Misty Seed': 'spd', + 'Psychic Seed': 'spd', +}; + +export function getItemBoostType(item: string | undefined) { + switch (item) { + case 'Draco Plate': + case 'Dragon Fang': + return 'Dragon'; + case 'Dread Plate': + case 'Black Glasses': + return 'Dark'; + case 'Earth Plate': + case 'Soft Sand': + return 'Ground'; + case 'Fist Plate': + case 'Black Belt': + return 'Fighting'; + case 'Flame Plate': + case 'Charcoal': + return 'Fire'; + case 'Icicle Plate': + case 'Never-Melt Ice': + return 'Ice'; + case 'Insect Plate': + case 'Silver Powder': + return 'Bug'; + case 'Iron Plate': + case 'Metal Coat': + return 'Steel'; + case 'Meadow Plate': + case 'Rose Incense': + case 'Miracle Seed': + return 'Grass'; + case 'Mind Plate': + case 'Odd Incense': + case 'Twisted Spoon': + return 'Psychic'; + case 'Fairy Feather': + case 'Pixie Plate': + return 'Fairy'; + case 'Sky Plate': + case 'Sharp Beak': + return 'Flying'; + case 'Splash Plate': + case 'Sea Incense': + case 'Wave Incense': + case 'Mystic Water': + return 'Water'; + case 'Spooky Plate': + case 'Spell Tag': + return 'Ghost'; + case 'Stone Plate': + case 'Rock Incense': + case 'Hard Stone': + return 'Rock'; + case 'Toxic Plate': + case 'Poison Barb': + return 'Poison'; + case 'Zap Plate': + case 'Magnet': + return 'Electric'; + case 'Silk Scarf': + case 'Pink Bow': + case 'Polkadot Bow': + return 'Normal'; + default: + return undefined; + } +} + +export function getBerryResistType(berry: string | undefined) { + switch (berry) { + case 'Chilan Berry': + return 'Normal'; + case 'Occa Berry': + return 'Fire'; + case 'Passho Berry': + return 'Water'; + case 'Wacan Berry': + return 'Electric'; + case 'Rindo Berry': + return 'Grass'; + case 'Yache Berry': + return 'Ice'; + case 'Chople Berry': + return 'Fighting'; + case 'Kebia Berry': + return 'Poison'; + case 'Shuca Berry': + return 'Ground'; + case 'Coba Berry': + return 'Flying'; + case 'Payapa Berry': + return 'Psychic'; + case 'Tanga Berry': + return 'Bug'; + case 'Charti Berry': + return 'Rock'; + case 'Kasib Berry': + return 'Ghost'; + case 'Haban Berry': + return 'Dragon'; + case 'Colbur Berry': + return 'Dark'; + case 'Babiri Berry': + return 'Steel'; + case 'Roseli Berry': + return 'Fairy'; + default: + return undefined; + } +} + +const FLING_120 = new Set([ + 'TR24', + 'TR28', + 'TR34', + 'TR39', + 'TR53', + 'TR55', + 'TR64', + 'TR66', + 'TR72', + 'TR73', +]); + +const FLING_100 = new Set([ + 'Hard Stone', + 'Room Service', + 'Claw Fossil', + 'Dome Fossil', + 'Helix Fossil', + 'Old Amber', + 'Root Fossil', + 'Armor Fossil', + 'Old Amber', + 'Fossilized Bird', + 'Fossilized Dino', + 'Fossilized Drake', + 'Fossilized Fish', + 'Plume Fossil', + 'Jaw Fossil', + 'Cover Fossil', + 'Sail Fossil', + 'Rare Bone', + 'Skull Fossil', + 'TR10', + 'TR31', + 'TR75', +]); + +const FLING_90 = new Set([ + 'Deep Sea Tooth', + 'Thick Club', + 'TR02', + 'TR04', + 'TR05', + 'TR08', + 'TR11', + 'TR22', + 'TR35', + 'TR42', + 'TR45', + 'TR50', + 'TR61', + 'TR65', + 'TR67', + 'TR86', + 'TR90', + 'TR96', +]); + +const FLING_85 = new Set(['TR01', 'TR41', 'TR62', 'TR93', 'TR97', 'TR98']); + +const FLING_80 = new Set([ + 'Assault Vest', + 'Blunder Policy', + 'Boomerang', +// test until better setup made ig + 'Chipped Pot', + 'Cracked Pot', + 'Heavy-Duty Boots', + 'Weakness Policy', + 'Quick Claw', + 'Dawn Stone', + 'Dusk Stone', + 'Electirizer', + 'Magmarizer', + 'Oval Stone', + 'Protector', + 'Sachet', + 'Whipped Dream', + 'Razor Claw', + 'Shiny Stone', + 'TR16', + 'TR18', + 'TR19', + 'TR25', + 'TR32', + 'TR33', + 'TR47', + 'TR56', + 'TR57', + 'TR58', + 'TR59', + 'TR60', + 'TR63', + 'TR69', + 'TR70', + 'TR74', + 'TR84', + 'TR87', + 'TR92', + 'TR95', + 'TR99', +]); + +const FLING_70 = new Set([ + 'Poison Barb', + 'Dragon Fang', + 'Power Anklet', + 'Power Band', + 'Power Belt', + 'Power Bracer', + 'Power Lens', + 'Power Weight', +]); + +const FLING_60 = new Set([ + 'Adamant Orb', + 'Damp Rock', + 'Heat Rock', + 'Leek', + 'Lustrous Orb', + 'Macho Brace', + 'Rocky Helmet', + 'Stick', + 'Utility Umbrella', + 'Terrain Extender', +]); +const FLING_30 = new Set([ + 'Absorb Bulb', + 'Assist Shovel', + 'Assurance Policy', + 'Bare Spool', + 'Berry Juice', + 'Black Belt', + 'Black Glasses', + 'Black Sludge', + 'Black Sludge', + 'Bottle Cap', + 'Cell Battery', + 'Charcoal', + 'Deep Sea Scale', + 'Dragon Scale', + 'Drain Shield', + 'EiPP Crown', + 'Eject Button', + 'Energy Powder', + 'Fire Stone', + 'Flame Orb', + 'Gold Bottle Cap', + 'Ice Stone', + 'King\'s Rock', + 'Leaf Stone', + 'Life Orb', + 'Light Ball', + 'Light Clay', + 'Luminous Moss', + 'Magnet', + 'Metal Coat', + 'Miracle Seed', + 'Moon Stone', + 'Mystic Water', + 'Never-Melt Ice', + 'Nurse Hat', + 'Overlord Crown', + 'Prism Scale', + 'Queen\'s Gland', + 'Razor Fang', + 'Room Service 2', + 'Scope Lens', + 'Sharpshooter\'s Lens', + 'Slime', + 'Snowball', + 'Soul Dew', + 'Spell Tag', + 'Spit Card', + 'Sun Stone', + 'Sweet Apple', + 'Tart Apple', + 'Throat Spray', + 'Thunder Stone', + 'Toxic Orb', + 'Twisted Spoon', + 'Up-Grade', + 'Water Stone', + 'Wide Decoy', +]); +const FLING_10 = new Set([ + 'Air Balloon', + 'Berry Sweet', + 'Choice Band', + 'Choice Scarf', + 'Choice Specs', + 'Clover Sweet', + 'Destiny Knot', + 'Electric Seed', + 'Expert Belt', + 'Flower Sweet', + 'Focus Band', + 'Focus Sash', + 'Full Incense', + 'Grassy Seed', + 'Lagging Tail', + 'Lax Incense', + 'Leftovers', + 'Love Sweet', + 'Mental Herb', + 'Metal Powder', + 'Mint Berry', + 'Miracle Berry', + 'Misty Seed', + 'Muscle Band', + 'Power Herb', + 'Psychic Seed', + 'Odd Incense', + 'Quick Powder', + 'Reaper Cloth', + 'Red Card', + 'Ribbon Sweet', + 'Ring Target', + 'Rock Incense', + 'Rose Incense', + 'Sea Incense', + 'Shed Shell', + 'Silk Scarf', + 'Silver Powder', + 'Smooth Rock', + 'Soft Sand', + 'Soothe Bell', + 'Star Sweet', + 'Strawberry Sweet', + 'Wave Incense', + 'White Herb', + 'Wide Lens', + 'Wise Glasses', + 'Zoom Lens', + 'Silver Powder', + 'Power Herb', + 'TR00', + 'TR07', + 'TR12', + 'TR13', + 'TR14', + 'TR17', + 'TR20', + 'TR21', + 'TR23', + 'TR26', + 'TR27', + 'TR29', + 'TR30', + 'TR37', + 'TR38', + 'TR40', + 'TR44', + 'TR46', + 'TR48', + 'TR49', + 'TR51', + 'TR52', + 'TR54', + 'TR68', + 'TR76', + 'TR77', + 'TR79', + 'TR80', + 'TR83', + 'TR85', + 'TR88', + 'TR91', +]); + +// TODO: move this data to the data files instead. +export function getFlingPower(item?: string) { + if (!item) return 0; + if (['Big Nugget', 'Iron Ball', 'Sledgehammer', 'TR43', 'TR71'].includes(item)) return 130; + if (FLING_120.has(item)) return 85; + if (['TR03', 'TR06', 'TR09', 'TR15', 'TR89'].includes(item)) return 110; + if (FLING_100.has(item)) return 100; + if (['TR36', 'TR78', 'TR81', 'TR94'].includes(item)) return 95; + if (item.includes('Plate') || FLING_90.has(item)) return 90; + if (FLING_85.has(item)) return 85; + if (FLING_80.has(item)) return 80; + if (FLING_70.has(item)) return 70; + if (FLING_60.has(item)) return 60; + if (['Eject Pack', 'Sharp Beak', 'Dubious Disc'].includes(item)) return 50; + if (['Icy Rock', 'Eviolite', 'Lucky Punch'].includes(item)) return 40; + if (FLING_30.has(item)) return 30; + if (item === 'TR82') return 20; + if (item.includes('Berry') || FLING_10.has(item)) return 10; + return 0; +} + +export function getNaturalGift(gen: Generation, item: string) { + const gift = gen.items.get(toID(item))?.naturalGift; + return gift ? {t: gift.type, p: gift.basePower} : {t: 'Normal' as TypeName, p: 1}; +} + +export function getTechnoBlast(item: string) { + switch (item) { + case 'Burn Drive': + return 'Fire'; + case 'Chill Drive': + return 'Ice'; + case 'Douse Drive': + return 'Water'; + case 'Shock Drive': + return 'Electric'; + default: + return undefined; + } +} + +export function getMultiAttack(item: string) { + if (item.includes('Memory')) { + return item.substring(0, item.indexOf(' ')) as TypeName; + } + return undefined; +} diff --git a/calc/src/mechanics/eipp-1_gen789.ts b/calc/src/mechanics/eipp-1_gen789.ts new file mode 100644 index 000000000..d3e786ea1 --- /dev/null +++ b/calc/src/mechanics/eipp-1_gen789.ts @@ -0,0 +1,1714 @@ +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) || attacker.hasItem('Sledgehammer'); + + 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 (attacker.hasItem('Nurse Hat') && move.bp <= 50 && typeEffectiveness <= 0.5 && typeEffectiveness > 0) { + typeEffectiveness = 0; + desc.attackerItem = attacker.item; + desc.moveName = 'Heal Pulse'; + 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('Grass') && defender.hasAbility('Sap Sipper')) || + (move.hasType('Fire') && defender.hasAbility('Flash Fire', 'Well-Baked Body')) || + (move.hasType('Water') && defender.hasAbility('Dry Skin', 'Storm Drain', 'Water Absorb')) || + (move.hasType('Electric') && + defender.hasAbility('Lightning Rod', 'Motor Drive', 'Volt Absorb')) || + (move.hasType('Ground') && + !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('Ground') && defender.hasAbility('Earth Eater')) || + (move.flags.wind && defender.hasAbility('Wind Rider')) + ) { + desc.defenderAbility = defender.ability; + return result; + } + + 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') || (attacker.hasItem('Queen\'s Gland') && attacker.hasType('Bug') && attacker.gender === 'F')) { + 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')) || (attacker.hasItem('Sledgehammer') && !move.named('Fling')))) { + 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; + } else if (attacker.hasType('Bug') && attacker.hasItem('Queen\'s Gland') && attacker.gender === 'F' && move.hits === 1 && !isSpread) { + const child = attacker.clone(); + child.ability = 'Parental Bond (Child)' as AbilityName; + child.item = undefined; + checkMultihitBoost(gen, child, defender, move, field, desc); + childDamage = calculateSMSSSV(gen, child, defender, move, field).damage as number[]; + desc.attackerAbility = 'Parental Bond'; + } + + 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('Sharpshooter\'s Lens') && isCritical) { + finalMods.push(6144); + 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 6357148e7..41c34c7ae 100644 --- a/src/base.template.html +++ b/src/base.template.html @@ -54,8 +54,7 @@
Select the calculator's mode of function. - - + diff --git a/src/eipp-1.template.html b/src/eipp-1.template.html new file mode 100644 index 000000000..365c49093 --- /dev/null +++ b/src/eipp-1.template.html @@ -0,0 +1,1632 @@ + + + + + + EiPP Damage Calculator + + + + + + + + + + + + + + +
+ + EiPP 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 + + + + +
+ + + +