|
| 1 | +/** |
| 2 | + * Fetch base_experience, EV yield, and growth_rate for all species from PokeAPI. |
| 3 | + * Generates src/dex/pokedex-data.ts |
| 4 | + * |
| 5 | + * Usage: bun run scripts/fetch-pokedex-data.ts |
| 6 | + */ |
| 7 | +import { Dex } from '@pkmn/sim' |
| 8 | + |
| 9 | +const GROWTH_RATE_MAP: Record<string, string> = { |
| 10 | + 'slow-then-very-fast': 'erratic', |
| 11 | + 'fast-then-very-slow': 'fluctuating', |
| 12 | + 'medium': 'medium-fast', |
| 13 | + 'medium-slow': 'medium-slow', |
| 14 | + 'slow': 'slow', |
| 15 | + 'fast': 'fast', |
| 16 | +} |
| 17 | + |
| 18 | +const STAT_MAP: Record<string, string> = { |
| 19 | + 'hp': 'hp', |
| 20 | + 'attack': 'atk', |
| 21 | + 'defense': 'def', |
| 22 | + 'special-attack': 'spa', |
| 23 | + 'special-defense': 'spd', |
| 24 | + 'speed': 'spe', |
| 25 | +} |
| 26 | + |
| 27 | +interface SpeciesPokedex { |
| 28 | + baseExperience: number |
| 29 | + evs: Record<string, number> |
| 30 | + growthRate: string |
| 31 | + captureRate: number |
| 32 | + baseHappiness: number |
| 33 | + hatchCounter: number |
| 34 | +} |
| 35 | + |
| 36 | +async function fetchSpeciesData(id: number): Promise<SpeciesPokedex | null> { |
| 37 | + try { |
| 38 | + const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`) |
| 39 | + if (!res.ok) return null |
| 40 | + const data = await res.json() as any |
| 41 | + |
| 42 | + // Get growth rate from species endpoint |
| 43 | + const speciesRes = await fetch(`https://pokeapi.co/api/v2/pokemon-species/${id}`) |
| 44 | + if (!speciesRes.ok) return null |
| 45 | + const speciesData = await speciesRes.json() as any |
| 46 | + |
| 47 | + const evs: Record<string, number> = {} |
| 48 | + for (const stat of data.stats || []) { |
| 49 | + if (stat.effort > 0) { |
| 50 | + const statName = STAT_MAP[stat.stat.name] |
| 51 | + if (statName) evs[statName] = stat.effort |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + const growthRateName = GROWTH_RATE_MAP[speciesData.growth_rate?.name] ?? 'medium-slow' |
| 56 | + |
| 57 | + return { |
| 58 | + baseExperience: data.base_experience ?? 50, |
| 59 | + evs, |
| 60 | + growthRate: growthRateName, |
| 61 | + captureRate: speciesData.capture_rate ?? 45, |
| 62 | + baseHappiness: speciesData.base_happiness ?? 70, |
| 63 | + hatchCounter: speciesData.hatch_counter ?? 20, |
| 64 | + } |
| 65 | + } catch { |
| 66 | + return null |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +async function main() { |
| 71 | + // Get all base species IDs from Dex |
| 72 | + const rawSpecies = Dex.data.Species as Record<string, { num: number; forme?: string }> |
| 73 | + const species: { id: string; num: number }[] = [] |
| 74 | + for (const [id, s] of Object.entries(rawSpecies)) { |
| 75 | + if (s.num > 0 && Number.isInteger(s.num) && !s.forme) { |
| 76 | + species.push({ id, num: s.num }) |
| 77 | + } |
| 78 | + } |
| 79 | + species.sort((a, b) => a.num - b.num) |
| 80 | + |
| 81 | + console.log(`Fetching data for ${species.length} species from PokeAPI...`) |
| 82 | + |
| 83 | + const results: Record<string, SpeciesPokedex> = {} |
| 84 | + let fetched = 0 |
| 85 | + const BATCH_SIZE = 20 |
| 86 | + |
| 87 | + for (let i = 0; i < species.length; i += BATCH_SIZE) { |
| 88 | + const batch = species.slice(i, i + BATCH_SIZE) |
| 89 | + const promises = batch.map(async (s) => { |
| 90 | + const data = await fetchSpeciesData(s.num) |
| 91 | + if (data) results[s.id] = data |
| 92 | + fetched++ |
| 93 | + }) |
| 94 | + await Promise.all(promises) |
| 95 | + process.stdout.write(`\rFetched ${fetched}/${species.length}...`) |
| 96 | + // Small delay to avoid rate limiting |
| 97 | + await new Promise(r => setTimeout(r, 200)) |
| 98 | + } |
| 99 | + |
| 100 | + console.log(`\nFetched ${Object.keys(results).length} species.`) |
| 101 | + |
| 102 | + // Generate TypeScript file |
| 103 | + const lines: string[] = [ |
| 104 | + '// Auto-generated from PokeAPI. Run: bun run scripts/fetch-pokedex-data.ts', |
| 105 | + '// eslint-disable-next-line @typescript-eslint/no-extraneous-class', |
| 106 | + 'export interface PokedexEntry {', |
| 107 | + ' baseExperience: number', |
| 108 | + ' evs: Record<string, number>', |
| 109 | + ' growthRate: string', |
| 110 | + ' captureRate: number', |
| 111 | + ' baseHappiness: number', |
| 112 | + ' hatchCounter?: number', |
| 113 | + '}', |
| 114 | + '', |
| 115 | + 'export const POKEDEX_DATA: Record<string, PokedexEntry> = {', |
| 116 | + ] |
| 117 | + |
| 118 | + for (const [id, data] of Object.entries(results)) { |
| 119 | + const evsStr = Object.keys(data.evs).length > 0 |
| 120 | + ? `{ ${Object.entries(data.evs).map(([k, v]) => `${k}: ${v}`).join(', ')} }` |
| 121 | + : '{}' |
| 122 | + lines.push(` '${id}': { baseExperience: ${data.baseExperience}, evs: ${evsStr}, growthRate: '${data.growthRate}', captureRate: ${data.captureRate}, baseHappiness: ${data.baseHappiness}, hatchCounter: ${data.hatchCounter} },`) |
| 123 | + } |
| 124 | + |
| 125 | + lines.push('}') |
| 126 | + lines.push('') |
| 127 | + |
| 128 | + const outputPath = new URL('../src/dex/pokedex-data.ts', import.meta.url) |
| 129 | + await Bun.write(outputPath, lines.join('\n')) |
| 130 | + console.log(`Written to ${outputPath.pathname}`) |
| 131 | +} |
| 132 | + |
| 133 | +main().catch(console.error) |
0 commit comments