import { randomUUID } from 'node:crypto' import type { BuddyData, Creature, Egg, SpeciesId } from '../types' import { ALL_SPECIES_IDS } from '../types' import { getHatchCounter } from '../dex/pokedex-data' import { generateCreature } from './creature' import { addToParty, depositToBox } from './storage' /** Days of consecutive coding needed to be eligible for an egg */ export const EGG_REQUIRED_DAYS = 3 /** * Check if the player is eligible to receive an egg. * Conditions: consecutiveDays >= EGG_REQUIRED_DAYS AND totalTurns % 50 === 0 AND eggs.length < 1 */ export function checkEggEligibility(buddyData: BuddyData): boolean { if (buddyData.eggs.length >= 1) return false if (buddyData.stats.consecutiveDays < EGG_REQUIRED_DAYS) return false if (buddyData.stats.totalTurns % 50 !== 0) return false return true } /** * Generate a new egg with a species the player hasn't collected yet. * Priority: uncollected species > random from all species. */ export function generateEgg(buddyData: BuddyData): Egg { // Find uncollected species const collectedSpecies = new Set(buddyData.creatures.map((c) => c.speciesId)) const uncollected = ALL_SPECIES_IDS.filter((id) => !collectedSpecies.has(id)) // Pick species (prefer uncollected, fall back to random starter) const starters: SpeciesId[] = ['bulbasaur', 'charmander', 'squirtle', 'pikachu'] const speciesId = uncollected.length > 0 ? uncollected[Math.floor(Math.random() * uncollected.length)] : starters[Math.floor(Math.random() * starters.length)] // Steps based on real hatch_counter from PokeAPI (steps = cycles * 257) const hatchCounter = getHatchCounter(speciesId) const baseSteps = hatchCounter * 257 return { id: randomUUID(), obtainedAt: Date.now(), stepsRemaining: baseSteps, totalSteps: baseSteps, speciesId, } } /** * Advance egg steps by a given amount. * Returns updated egg or null if egg hatched. */ export function advanceEggSteps(egg: Egg, steps: number): Egg { const newSteps = Math.max(0, egg.stepsRemaining - steps) return { ...egg, stepsRemaining: newSteps } } /** * Check if an egg is ready to hatch. */ export function isEggReadyToHatch(egg: Egg): boolean { return egg.stepsRemaining <= 0 } /** * Hatch an egg, creating a new creature and updating buddy data. * Tries to add to party first, then deposits to PC box. */ export async function hatchEgg(buddyData: BuddyData, egg: Egg): Promise<{ buddyData: BuddyData; creature: Creature }> { const creature = await generateCreature(egg.speciesId) creature.hatchedAt = Date.now() // Add creature to list let updatedData: BuddyData = { ...buddyData, creatures: [...buddyData.creatures, creature], eggs: buddyData.eggs.filter((e) => e.id !== egg.id), dex: updateDexEntry(buddyData.dex, egg.speciesId, creature.level), stats: { ...buddyData.stats, totalEggsObtained: buddyData.stats.totalEggsObtained + 1, }, } // Place in party or PC box const partyResult = addToParty(updatedData, creature.id) if (partyResult.added) { updatedData = partyResult.data } else { const boxResult = depositToBox(updatedData, creature.id) if (boxResult.deposited) updatedData = boxResult.data } return { buddyData: updatedData, creature } } /** * Update or create a dex entry for a species. */ function updateDexEntry(dex: BuddyData['dex'], speciesId: SpeciesId, level: number): BuddyData['dex'] { const existing = dex.find((d) => d.speciesId === speciesId) if (existing) { return dex.map((d) => d.speciesId === speciesId ? { ...d, caughtCount: d.caughtCount + 1, bestLevel: Math.max(d.bestLevel, level) } : d, ) } return [...dex, { speciesId, discoveredAt: Date.now(), caughtCount: 1, bestLevel: level }] }