In this tutorial, you will learn how to make a Pokemon Damage Calculator in JavaScript. Basically, this tool is a damage calculator for all generations of Pokémon battling.
The main source code of the Pokemon Damage Calculator is given below. It was a bit difficult to add the complete source code to this article. So, I have provided a download button at the end of this tutorial from where you can download the full source code of the Pokemon Damage Calculator.
Pokemon Damage Calculator
server.js
const express = require("express"); const calc = require("calc"); const app = express(); app.listen(3000, () => { console.log("Server running on port 3000"); }); // parse application/json app.use(express.json()) app.get("/calculate",(req, res, next) => { const gen = calc.Generations.get((typeof req.body.gen === 'undefined') ? 8 : req.body.gen); let error = ""; if(typeof req.body.attackingPokemon === 'undefined') error += "attackingPokemon must exist and have a valid pokemon name\n"; if(typeof req.body.defendingPokemon === 'undefined') error += "defendingPokemon must exist and have a valid pokemon name\n"; if(error) throw new Error(error) const result = calc.calculate( gen, new calc.Pokemon(gen, req.body.attackingPokemon, req.body.attackingPokemonOptions), new calc.Pokemon(gen, req.body.defendingPokemon, req.body.defendingPokemonOptions), new calc.Move(gen, req.body.moveName), new calc.Field((typeof req.body.field === 'undefined') ? undefined : req.body.field) ); res.json(result); }) app.use(express.static('dist'))
/calc/src/calc.ts
import {Field} from './field'; import {Generation} from './data/interface'; import {Move} from './move'; import {Pokemon} from './pokemon'; import {Result} from './result'; import {calculateRBYGSC} from './mechanics/gen12'; import {calculateADV} from './mechanics/gen3'; import {calculateDPP} from './mechanics/gen4'; import {calculateBWXY} from './mechanics/gen56'; import {calculateSMSS} from './mechanics/gen78'; const MECHANICS = [ () => {}, calculateRBYGSC, calculateRBYGSC, calculateADV, calculateDPP, calculateBWXY, calculateBWXY, calculateSMSS, calculateSMSS, ]; export function calculate( gen: Generation, attacker: Pokemon, defender: Pokemon, move: Move, field?: Field, ) { return MECHANICS[gen.num]( gen, attacker.clone(), defender.clone(), move.clone(), field ? field.clone() : new Field() ) as Result; }
/calc/src/pokemon.ts
import * as I from './data/interface'; import {Stats} from './stats'; import {toID, extend, assignWithout} from './util'; import {State} from './state'; const STATS = ['hp', 'atk', 'def', 'spa', 'spd', 'spe'] as I.StatID[]; const SPC = new Set(['spc']); export class Pokemon implements State.Pokemon { gen: I.Generation; name: I.SpeciesName; species: I.Specie; types: [I.TypeName] | [I.TypeName, I.TypeName]; weightkg: number; level: number; gender?: I.GenderName; ability?: I.AbilityName; abilityOn?: boolean; isDynamaxed?: boolean; item?: I.ItemName; nature: I.NatureName; ivs: I.StatsTable; evs: I.StatsTable; boosts: I.StatsTable; rawStats: I.StatsTable; stats: I.StatsTable; originalCurHP: number; status: I.StatusName | ''; toxicCounter: number; moves: I.MoveName[]; constructor( gen: I.Generation, name: string, options: Partial<State.Pokemon> & { curHP?: number; ivs?: Partial<I.StatsTable> & {spc?: number}; evs?: Partial<I.StatsTable> & {spc?: number}; boosts?: Partial<I.StatsTable> & {spc?: number}; } = {} ) { this.species = extend(true, {}, gen.species.get(toID(name)), options.overrides); this.gen = gen; this.name = options.name || name as I.SpeciesName; this.types = this.species.types; this.isDynamaxed = !!options.isDynamaxed; this.weightkg = this.species.weightkg; // Gigantamax 'forms' inherit weight from their base species when not dynamaxed // TODO: clean this up with proper Gigantamax support if (this.weightkg === 0 && !this.isDynamaxed && this.species.baseSpecies) { this.weightkg = gen.species.get(toID(this.species.baseSpecies))!.weightkg; } this.level = options.level || 100; this.gender = options.gender || this.species.gender || 'M'; this.ability = options.ability || this.species.abilities?.[0] || undefined; this.abilityOn = !!options.abilityOn; this.item = options.item; this.nature = options.nature || ('Serious' as I.NatureName); this.ivs = Pokemon.withDefault(gen, options.ivs, 31); this.evs = Pokemon.withDefault(gen, options.evs, gen.num >= 3 ? 0 : 252); this.boosts = Pokemon.withDefault(gen, options.boosts, 0, false); if (gen.num < 3) { this.ivs.hp = Stats.DVToIV( Stats.getHPDV({ atk: this.ivs.atk, def: this.ivs.def, spe: this.ivs.spe, spc: this.ivs.spa, }) ); } this.rawStats = {} as I.StatsTable; this.stats = {} as I.StatsTable; for (const stat of STATS) { const val = this.calcStat(gen, stat); this.rawStats[stat] = val; this.stats[stat] = val; } const curHP = options.curHP || options.originalCurHP; this.originalCurHP = curHP && curHP <= this.rawStats.hp ? curHP : this.rawStats.hp; this.status = options.status || ''; this.toxicCounter = options.toxicCounter || 0; this.moves = options.moves || []; } maxHP(original = false) { // Shedinja still has 1 max HP during the effect even if its Dynamax Level is maxed (DaWoblefet) return !original && this.isDynamaxed && this.species.baseStats.hp !== 1 ? this.rawStats.hp * 2 : this.rawStats.hp; } curHP(original = false) { // Shedinja still has 1 max HP during the effect even if its Dynamax Level is maxed (DaWoblefet) return !original && this.isDynamaxed && this.species.baseStats.hp !== 1 ? this.originalCurHP * 2 : this.originalCurHP; } hasAbility(...abilities: string[]) { return !!(this.ability && abilities.includes(this.ability)); } hasItem(...items: string[]) { return !!(this.item && items.includes(this.item)); } hasStatus(...statuses: I.StatusName[]) { return !!(this.status && statuses.includes(this.status)); } hasType(...types: I.TypeName[]) { for (const type of types) { if (this.types.includes(type)) return true; } return false; } named(...names: string[]) { return names.includes(this.name); } clone() { return new Pokemon(this.gen, this.name, { level: this.level, ability: this.ability, abilityOn: this.abilityOn, isDynamaxed: this.isDynamaxed, item: this.item, gender: this.gender, nature: this.nature, ivs: extend(true, {}, this.ivs), evs: extend(true, {}, this.evs), boosts: extend(true, {}, this.boosts), originalCurHP: this.originalCurHP, status: this.status, toxicCounter: this.toxicCounter, moves: this.moves.slice(), overrides: this.species, }); } private calcStat(gen: I.Generation, stat: I.StatID) { return Stats.calcStat( gen, stat, this.species.baseStats[stat], this.ivs[stat]!, this.evs[stat]!, this.level, this.nature ); } static getForme( gen: I.Generation, speciesName: string, item?: I.ItemName, moveName?: I.MoveName ) { const species = gen.species.get(toID(speciesName)); if (!species || !species.otherFormes) { return speciesName; } let i = 0; if ( (item && ((item.includes('ite') && !item.includes('ite Y')) || (speciesName === 'Groudon' && item === 'Red Orb') || (speciesName === 'Kyogre' && item === 'Blue Orb'))) || (moveName && speciesName === 'Meloetta' && moveName === 'Relic Song') || (speciesName === 'Rayquaza' && moveName === 'Dragon Ascent') ) { i = 1; } else if (item?.includes('ite Y')) { i = 2; } return i ? species.otherFormes[i - 1] : species.name; } private static withDefault( gen: I.Generation, current: Partial<I.StatsTable> & {spc?: number} | undefined, val: number, match = true, ) { const cur: Partial<I.StatsTable> = {}; if (current) { assignWithout(cur, current, SPC); if (current.spc) { cur.spa = current.spc; cur.spd = current.spc; } if (match && gen.num <= 2 && current.spa !== current.spd) { throw new Error('Special Attack and Special Defense must match before Gen 3'); } } return {hp: val, atk: val, def: val, spa: val, spd: val, spe: val, ...cur}; } }
/calc/src/result.ts
import {RawDesc, display, displayMove, getRecovery, getRecoil, getKOChance} from './desc'; import {Generation} from './data/interface'; import {Field} from './field'; import {Move} from './move'; import {Pokemon} from './pokemon'; export type Damage = number | number[] | [number, number] | [number[], number[]]; export class Result { gen: Generation; attacker: Pokemon; defender: Pokemon; move: Move; field: Field; damage: number | number[] | [number[], number[]]; rawDesc: RawDesc; constructor( gen: Generation, attacker: Pokemon, defender: Pokemon, move: Move, field: Field, damage: Damage, rawDesc: RawDesc, ) { this.gen = gen; this.attacker = attacker; this.defender = defender; this.move = move; this.field = field; this.damage = damage; this.rawDesc = rawDesc; } /* get */ desc() { return this.fullDesc(); } range(): [number, number] { const range = damageRange(this.damage); if (typeof range[0] === 'number') return range as [number, number]; const d = range as [number[], number[]]; return [d[0][0] + d[0][1], d[1][0] + d[1][1]]; } fullDesc(notation = '%', err = true) { return display( this.gen, this.attacker, this.defender, this.move, this.field, this.damage, this.rawDesc, notation, err ); } moveDesc(notation = '%') { return displayMove(this.gen, this.attacker, this.defender, this.move, this.damage, notation); } recovery(notation = '%') { return getRecovery(this.gen, this.attacker, this.defender, this.move, this.damage, notation); } recoil(notation = '%') { return getRecoil(this.gen, this.attacker, this.defender, this.move, this.damage, notation); } kochance(err = true) { return getKOChance( this.gen, this.attacker, this.defender, this.move, this.field, this.damage, err ); } } export function damageRange( damage: Damage ): [number, number] | [[number, number], [number, number]] { // Fixed Damage if (typeof damage === 'number') return [damage, damage]; // Standard Damage if (damage.length > 2) { const d = damage as number[]; if (d[0] > d[d.length - 1]) return [Math.min(...d), Math.max(...d)]; return [d[0], d[d.length - 1]]; } // Fixed Parental Bond Damage if (typeof damage[0] === 'number' && typeof damage[1] === 'number') { return [[damage[0], damage[1]], [damage[0], damage[1]]]; } // Parental Bond Damage const d = damage as [number[], number[]]; if (d[0][0] > d[0][d[0].length - 1]) d[0] = d[0].slice().sort(); if (d[1][0] > d[1][d[1].length - 1]) d[1] = d[1].slice().sort(); return [[d[0][0], d[1][0]], [d[0][d[0].length - 1], d[1][d[1].length - 1]]]; }
Screenshot
Download Pokemon Damage Calculator
You can download the complete source code of the Pokemon Damage Calculator using the link given below.