- 孵化步数从 captureRate 反推改为 hatchCounter * 257(原版公式) - getHatchCounter 支持进化阶段/传说回退分类 - fetch-pokedex-data.ts 已更新以采集 hatchCounter 字段 - 解决 #17 蛋系统孵化步数不准确 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
112 lines
3.7 KiB
TypeScript
112 lines
3.7 KiB
TypeScript
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 }]
|
|
}
|