pvpokemon/generatePokemonData.ts
2019-02-10 12:00:44 -05:00

245 lines
9.4 KiB
TypeScript

import fs from 'fs';
import POGOProtos from 'pogo-protos';
import { parseGameMaster } from './parseGameMaster';
import { calculateCp, calculateMaxLevelForLeague, calculateStatAtLevel } from 'app/utils/calculator';
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<number, Array<IStats>>;
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;
Object.keys(MaxCpByLeague).forEach((key) => {
const league = key as League;
const maxLeagueLevel = calculateMaxLevelForLeague(mon.stats, ivHp, ivAtk, ivDef, league);
const maxLeagueCp = calculateCp(mon.stats, maxLeagueLevel, ivHp, ivAtk, ivDef);
pokemonWithIvs = {
cp: maxLeagueCp,
level: maxLeagueLevel,
ivHp,
ivAtk,
ivDef,
hp: calculateStatAtLevel(maxLeagueLevel, baseHp, ivHp),
atk: calculateStatAtLevel(maxLeagueLevel, baseAtk, ivAtk),
def: calculateStatAtLevel(maxLeagueLevel, baseDef, ivDef),
total: 0,
speciesGrade: Grade.F,
metaGrade: Grade.F,
};
pokemonWithIvs.total = pokemonWithIvs.hp + pokemonWithIvs.atk + pokemonWithIvs.def;
const combinedStats = pokemonWithIvs.total;
combinedStatsDistribution[league][combinedStats] = combinedStatsDistribution[league][combinedStats] || [];
combinedStatsDistribution[league][combinedStats].push(pokemonWithIvs);
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;
// NOTE: ordered low to high!
const orderedCombinedStats = Object.keys(combinedStatsDistribution[league]).map((total) => parseInt(total, 10));
orderedCombinedStats.sort((a, b) => a - b);
const len = orderedCombinedStats.length - 1;
const offset = orderedCombinedStats[0];
const max = orderedCombinedStats[len] - offset; // index `len` 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);
}
}
})();