import * as fs from 'fs'; import Pokemon from 'pokemongo-json-pokedex/output/pokemon.json'; import { ILeaguePokemon, IPokemon, IStats, League, Grade } from 'app/models/Pokemon'; type ICpAndTotalFound = Record>; interface IStatsDistribution { great : ICpAndTotalFound; ultra : ICpAndTotalFound; } interface IMaxCpByLeague { great : number; ultra : number; } const outPath = './dist/db/'; const maxCpByLeague : IMaxCpByLeague = { great: 1500, ultra: 2500 }; const cpMultipliers : Array = JSON.parse(fs.readFileSync('src/db/cpMultipliers.json', 'utf8')); const getClosestCpMultiplierIndex = (value : number) => { let i; for (i = 0; i < cpMultipliers.length; i++) { if (value < cpMultipliers[i]) { break; } } return Math.max(i - 1, 0); }; const familyOrder : Array = []; const familyEvolutionOrder : Record> = {}; const familyEncountered : Record> = {}; 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 = cpMultipliers[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)).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) { return console.log(mon.name, err); } }); }); }); const pokemonOrder : Array = []; 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) { return console.log('order', err); } }); });