213 lines
8.2 KiB
TypeScript
213 lines
8.2 KiB
TypeScript
import * as fs from 'fs';
|
|
import Pokemon from 'pokemongo-json-pokedex/output/pokemon.json';
|
|
|
|
import { LevelMultipliers } from 'app/models/LevelMultipliers';
|
|
import { Grade, ILeaguePokemon, IPokemon, IStats, League } from 'app/models/Pokemon';
|
|
|
|
type ICpAndTotalFound = Record<number, Array<IStats>>;
|
|
interface IStatsDistribution {
|
|
great : ICpAndTotalFound;
|
|
ultra : ICpAndTotalFound;
|
|
}
|
|
interface IMaxCpByLeague {
|
|
great : number;
|
|
ultra : number;
|
|
}
|
|
|
|
const outPath = './dist/db/';
|
|
|
|
const maxCpByLeague : IMaxCpByLeague = {
|
|
great: 1500,
|
|
ultra: 2500
|
|
};
|
|
|
|
const getClosestCpMultiplierIndex = (value : number) => {
|
|
let i;
|
|
for (i = 0; i < LevelMultipliers.length; i++) {
|
|
if (value < LevelMultipliers[i]) {
|
|
break;
|
|
}
|
|
}
|
|
return Math.max(i - 1, 0);
|
|
};
|
|
|
|
const familyOrder : Array<string> = [];
|
|
const familyEvolutionOrder : Record<string, Array<string>> = {};
|
|
const familyEncountered : Record<string, Array<ILeaguePokemon>> = {};
|
|
Pokemon.forEach((mon) => {
|
|
const baseAtk = mon.stats.baseAttack;
|
|
const baseDef = mon.stats.baseDefense;
|
|
const baseHp = mon.stats.baseStamina;
|
|
const pokemon : ILeaguePokemon = {
|
|
id: mon.id,
|
|
name: mon.name,
|
|
dex: mon.dex,
|
|
stats: mon.stats,
|
|
family: mon.family.id,
|
|
pvp: {
|
|
great: [],
|
|
ultra: [],
|
|
},
|
|
};
|
|
|
|
// keep track of family order and membership
|
|
if (typeof familyEncountered[pokemon.family] === 'undefined') {
|
|
familyOrder.push(pokemon.family);
|
|
familyEncountered[pokemon.family] = [];
|
|
}
|
|
familyEncountered[pokemon.family].push(pokemon);
|
|
|
|
if (typeof mon.evolution.pastBranch === 'undefined' &&
|
|
typeof familyEvolutionOrder[pokemon.family] === 'undefined' &&
|
|
pokemon.id.indexOf('_ALOLA') === -1 // because RAICHU_ALOLA shows up before PICHU, but PICHU is the family origin
|
|
) {
|
|
familyEvolutionOrder[pokemon.family] = [];
|
|
if (mon.forms.length > 0) {
|
|
mon.forms.forEach((form) => {
|
|
familyEvolutionOrder[pokemon.family].push(form.id);
|
|
});
|
|
} else {
|
|
familyEvolutionOrder[pokemon.family].push(pokemon.id);
|
|
}
|
|
|
|
// TODO: if `mon.forms.length > 0`, there's a chance the order will get weird by doing this:
|
|
if (typeof mon.evolution.futureBranches !== 'undefined') {
|
|
(function traverseEvolutionBranches(root) {
|
|
root.forEach((evolution) => {
|
|
familyEvolutionOrder[pokemon.family].push(evolution.id);
|
|
// unfortunate workaround for typescript limitation in JSON parsing being TOO good...
|
|
// if (typeof evolution !== 'undefined') {
|
|
if ('futureBranches' in evolution) {
|
|
// traverseEvolutionBranches(evolution.futureBranches);
|
|
traverseEvolutionBranches(evolution['futureBranches']);
|
|
}
|
|
});
|
|
})(mon.evolution.futureBranches);
|
|
}
|
|
}
|
|
|
|
// calculate stats for all possible IVs
|
|
const combinedStatsDistribution : IStatsDistribution = {
|
|
great: {},
|
|
ultra: {},
|
|
};
|
|
for (let ivHp = 15; ivHp >= 0; ivHp--) {
|
|
for (let ivAtk = 15; ivAtk >= 0; ivAtk--) {
|
|
for (let ivDef = 15; ivDef >= 0; ivDef--) {
|
|
let pokemonWithIvs : IStats;
|
|
const cpMultiplier = (baseAtk + ivAtk) * Math.sqrt(baseDef + ivDef) * Math.sqrt(baseHp + ivHp);
|
|
|
|
Object.keys(maxCpByLeague).forEach((key) => {
|
|
const league = key as League;
|
|
const maxCp = maxCpByLeague[league];
|
|
const maxLeagueLevelMultiplierIndex = getClosestCpMultiplierIndex(Math.sqrt((maxCp * 10) / cpMultiplier));
|
|
const maxLeagueLevelMultiplier = LevelMultipliers[maxLeagueLevelMultiplierIndex];
|
|
const maxLeagueCp = ~~((cpMultiplier * Math.pow(maxLeagueLevelMultiplier, 2)) / 10);
|
|
const maxLeagueLevel = (maxLeagueLevelMultiplierIndex + 2) / 2;
|
|
pokemonWithIvs = {
|
|
cp: maxLeagueCp,
|
|
level: maxLeagueLevel,
|
|
ivHp: ivHp,
|
|
ivAtk: ivAtk,
|
|
ivDef: ivDef,
|
|
hp: ~~((baseHp + ivHp) * maxLeagueLevelMultiplier),
|
|
atk: ~~((baseAtk + ivAtk) * maxLeagueLevelMultiplier),
|
|
def: ~~((baseDef + ivDef) * maxLeagueLevelMultiplier),
|
|
total: 0,
|
|
speciesGrade: Grade.F,
|
|
metaGrade: Grade.F,
|
|
};
|
|
pokemonWithIvs.total = pokemonWithIvs.hp + pokemonWithIvs.atk + pokemonWithIvs.def;
|
|
|
|
const combinedStats = maxLeagueCp + pokemonWithIvs.total;
|
|
combinedStatsDistribution[league][combinedStats] = combinedStatsDistribution[league][combinedStats] || [];
|
|
combinedStatsDistribution[league][combinedStats].push(pokemonWithIvs);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// process the pokemon stats for league-worthiness
|
|
Object.keys(pokemon.pvp).forEach((key) => {
|
|
const league = key as League;
|
|
|
|
const orderedCombinedStats = Object.keys(combinedStatsDistribution[league]).map((cpTotal) => parseInt(cpTotal, 10)).sort();
|
|
const len = orderedCombinedStats.length - 1;
|
|
const offset = orderedCombinedStats[1];
|
|
const max = orderedCombinedStats[len] - offset; // index 0 is always `Grade.S`
|
|
for (let index = len; index >= 0; index--) {
|
|
const combinedStats = orderedCombinedStats[index];
|
|
const percent = (combinedStats - offset) / max;
|
|
|
|
// remove all `Grade.F` stats (to save space in the DB)
|
|
if (percent < 0.6) {
|
|
delete combinedStatsDistribution[league][combinedStats];
|
|
continue;
|
|
}
|
|
|
|
combinedStatsDistribution[league][combinedStats].forEach((pokemonStats) => {
|
|
if (index === len) {
|
|
pokemonStats.speciesGrade = Grade.S;
|
|
} else {
|
|
if (percent >= 0.9) {
|
|
pokemonStats.speciesGrade = Grade.A;
|
|
} else if (percent >= 0.8) {
|
|
pokemonStats.speciesGrade = Grade.B;
|
|
} else if (percent >= 0.7) {
|
|
pokemonStats.speciesGrade = Grade.C;
|
|
} else if (percent >= 0.6) {
|
|
pokemonStats.speciesGrade = Grade.D;
|
|
}
|
|
}
|
|
pokemon.pvp[league].push(pokemonStats);
|
|
});
|
|
combinedStatsDistribution[league][combinedStats].sort((a, b) => {
|
|
if (a.total !== b.total) {
|
|
return a.total < b.total ? -1 : 1;
|
|
}
|
|
if (a.level !== b.level) {
|
|
return a.level < b.level ? -1 : 1;
|
|
}
|
|
return 0;
|
|
});
|
|
pokemon.pvp[league].push(...combinedStatsDistribution[league][combinedStats]);
|
|
}
|
|
});
|
|
|
|
fs.mkdir(outPath, {recursive: true}, () => {
|
|
fs.writeFile(outPath + mon.id + '.json', JSON.stringify(pokemon), (err) => {
|
|
if (err) {
|
|
/* tslint:disable-next-line:no-console */
|
|
return console.error(mon.name, err);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
const pokemonOrder : Array<IPokemon> = [];
|
|
familyOrder.forEach((familyId) => {
|
|
familyEvolutionOrder[familyId].forEach((id, order) => {
|
|
familyEncountered[familyId].some((pokemon, index) => {
|
|
if (id === pokemon.id && order !== index) {
|
|
familyEncountered[familyId].splice(order, 0, familyEncountered[familyId].splice(index, 1)[0]);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
});
|
|
|
|
familyEncountered[familyId].forEach((leaguePokemon) => {
|
|
const { pvp, ...pokemon } = leaguePokemon;
|
|
pokemonOrder.push(pokemon);
|
|
});
|
|
});
|
|
|
|
fs.mkdir(outPath, {recursive: true}, () => {
|
|
fs.writeFile(outPath + 'order.json', JSON.stringify(pokemonOrder), (err) => {
|
|
if (err) {
|
|
/* tslint:disable-next-line:no-console */
|
|
return console.error('order', err);
|
|
}
|
|
});
|
|
});
|