import fs from 'fs'; import POGOProtos from 'pogo-protos'; import { parseGameMaster } from './parseGameMaster'; import { ILeaguePokemon, League, MaxCpByLeague } from 'app/models/League'; import { LevelMultipliers } from 'app/models/LevelMultipliers'; import { Grade, IMaxStats, IStats } from 'app/models/Pokemon'; type ICpAndTotalFound = Record>; interface IStatsDistribution { great : ICpAndTotalFound; ultra : ICpAndTotalFound; master : ICpAndTotalFound; custom : ICpAndTotalFound; } const outPath = './dist/db/'; const maxPossibleStats : IMaxStats = { baseStamina: 0, baseAttack: 0, baseDefense: 0, level: 40, }; const getClosestCpMultiplierIndex = (value : number) => { let i; for (i = 0; i < LevelMultipliers.length; i++) { if (value < LevelMultipliers[i]) { break; } } return Math.max(i - 1, 0); }; fs.mkdirSync(outPath, { recursive: true }); (async () => { const Pokemon = await parseGameMaster(); Pokemon.forEach((mon) => { const baseHp = mon.stats.baseStamina; const baseAtk = mon.stats.baseAttack; const baseDef = mon.stats.baseDefense; const pokemon : ILeaguePokemon = { ...mon, statMax: { great: { stamina: { best: -Infinity, worst: Infinity, }, attack: { best: -Infinity, worst: Infinity, }, defense: { best: -Infinity, worst: Infinity, }, }, ultra: { stamina: { best: -Infinity, worst: Infinity, }, attack: { best: -Infinity, worst: Infinity, }, defense: { best: -Infinity, worst: Infinity, }, }, master: { stamina: { best: -Infinity, worst: Infinity, }, attack: { best: -Infinity, worst: Infinity, }, defense: { best: -Infinity, worst: Infinity, }, }, custom: { stamina: { best: -Infinity, worst: Infinity, }, attack: { best: -Infinity, worst: Infinity, }, defense: { best: -Infinity, worst: Infinity, }, }, }, pvp: { great: [], ultra: [], master: [], custom: [], }, }; maxPossibleStats.baseStamina = Math.max(baseHp, maxPossibleStats.baseStamina); maxPossibleStats.baseAttack = Math.max(baseAtk, maxPossibleStats.baseAttack); maxPossibleStats.baseDefense = Math.max(baseDef, maxPossibleStats.baseDefense); // calculate stats for all possible IVs const combinedStatsDistribution : IStatsDistribution = { great: {}, ultra: {}, master: {}, custom: {}, }; 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 = Math.floor((cpMultiplier * Math.pow(maxLeagueLevelMultiplier, 2)) / 10); const maxLeagueLevel = (maxLeagueLevelMultiplierIndex + 2) / 2; pokemonWithIvs = { cp: maxLeagueCp, level: maxLeagueLevel, ivHp, ivAtk, ivDef, hp: Math.floor((baseHp + ivHp) * maxLeagueLevelMultiplier), atk: Math.floor((baseAtk + ivAtk) * maxLeagueLevelMultiplier), def: Math.floor((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); // console.log(pokemonWithIvs, key); pokemon.statMax[league].stamina.best = Math.max(pokemon.statMax[league].stamina.best, pokemonWithIvs.hp); pokemon.statMax[league].stamina.worst = Math.min(pokemon.statMax[league].stamina.worst, pokemonWithIvs.hp); pokemon.statMax[league].attack.best = Math.max(pokemon.statMax[league].attack.best, pokemonWithIvs.atk); pokemon.statMax[league].attack.worst = Math.min(pokemon.statMax[league].attack.worst, pokemonWithIvs.atk); pokemon.statMax[league].defense.best = Math.max(pokemon.statMax[league].defense.best, pokemonWithIvs.def); pokemon.statMax[league].defense.worst = Math.min(pokemon.statMax[league].defense.worst, pokemonWithIvs.def); }); } } } // 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)); orderedCombinedStats.sort((a, b) => a - b); 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), except // for the very worst one if (percent < 0.6 && index !== 0) { 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; }); } }); try { const filename = mon.form === POGOProtos.Enums.Form.FORM_UNSET ? POGOProtos.Enums.PokemonId[mon.id] : POGOProtos.Enums.Form[mon.form]; fs.writeFileSync(outPath + filename + '.json', JSON.stringify(pokemon)); } catch (error) { if (error) { /* tslint:disable-next-line:no-console */ return console.error(pokemon.name, error); } } }); try { fs.writeFileSync(outPath + 'order.json', JSON.stringify(Pokemon)); } catch (error) { if (error) { /* tslint:disable-next-line:no-console */ return console.error('order', error); } } try { // TODO: add moves fs.writeFileSync(outPath + 'config.json', JSON.stringify({ maxPossibleStats })); } catch (error) { if (error) { /* tslint:disable-next-line:no-console */ return console.error('config', error); } } })();