claude-code-best/packages/pokemon/src/core/creature.ts
2026-04-21 21:38:13 +08:00

120 lines
3.3 KiB
TypeScript

import { randomUUID } from 'node:crypto'
import type { Creature, SpeciesId, StatName, StatsResult } from '../types'
import { STAT_NAMES } from '../types'
import { getSpeciesData } from '../data/species'
import { determineGender } from './gender'
import { levelFromXp } from '../data/xpTable'
/**
* Generate a new creature of the given species.
*/
export function generateCreature(speciesId: SpeciesId, seed?: number): Creature {
const species = getSpeciesData(speciesId)
const actualSeed = seed ?? Math.floor(Math.random() * 0xffffffff)
// Generate IVs (0-31) using simple hash from seed
const iv = generateIVs(actualSeed)
// Determine gender
const gender = determineGender(species, actualSeed & 0xff)
// Determine shiny status
const isShiny = Math.random() < species.shinyChance
return {
id: randomUUID(),
speciesId,
gender,
level: 1,
xp: 0,
totalXp: 0,
ev: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 },
iv,
friendship: species.baseHappiness,
isShiny,
hatchedAt: Date.now(),
}
}
/**
* Calculate actual stats for a creature using Pokémon stat formulas.
* HP: floor((2 * base + iv + floor(ev/4)) * level / 100) + level + 10
* Other: floor((2 * base + iv + floor(ev/4)) * level / 100) + 5
*/
export function calculateStats(creature: Creature): StatsResult {
const species = getSpeciesData(creature.speciesId)
const level = creature.level
const result: StatsResult = {} as StatsResult
for (const stat of STAT_NAMES) {
const base = species.baseStats[stat]
const iv = creature.iv[stat]
const ev = creature.ev[stat]
const raw = Math.floor((2 * base + iv + Math.floor(ev / 4)) * level / 100)
if (stat === 'hp') {
result[stat] = raw + level + 10
} else {
result[stat] = raw + 5
}
}
return result
}
/**
* Get display name for a creature (nickname or species name).
*/
export function getCreatureName(creature: Creature): string {
if (creature.nickname) return creature.nickname
return getSpeciesData(creature.speciesId).name
}
/**
* Recalculate level from total XP (e.g. after XP gain).
*/
export function recalculateLevel(creature: Creature): Creature {
const species = getSpeciesData(creature.speciesId)
const newLevel = levelFromXp(creature.totalXp, species.growthRate)
if (newLevel !== creature.level) {
return { ...creature, level: newLevel }
}
return creature
}
/**
* Get the active creature from buddy data.
* Reads from party[0] (new) with fallback to activeCreatureId (legacy).
*/
export function getActiveCreature(buddyData: { party?: (string | null)[]; activeCreatureId?: string | null; creatures: Creature[] }): Creature | null {
const activeId = buddyData.party?.[0] ?? buddyData.activeCreatureId ?? null
if (!activeId) return null
return buddyData.creatures.find((c) => c.id === activeId) ?? null
}
/**
* Generate IVs from a seed value. Each stat gets 0-31.
*/
function generateIVs(seed: number): Record<StatName, number> {
let s = seed
const nextRand = () => {
s = (s * 1103515245 + 12345) & 0x7fffffff
return s
}
return {
hp: nextRand() % 32,
attack: nextRand() % 32,
defense: nextRand() % 32,
spAtk: nextRand() % 32,
spDef: nextRand() % 32,
speed: nextRand() % 32,
}
}
/**
* Get total EV across all stats.
*/
export function getTotalEV(creature: Creature): number {
return STAT_NAMES.reduce((sum, stat) => sum + creature.ev[stat], 0)
}