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.