diff --git a/packages/pokemon/src/battle/engine.ts b/packages/pokemon/src/battle/engine.ts index f1c6b9109..c16e57510 100644 --- a/packages/pokemon/src/battle/engine.ts +++ b/packages/pokemon/src/battle/engine.ts @@ -19,8 +19,11 @@ function creatureToSetString(creature: Creature): string { .filter(m => m.id) .map(m => Dex.moves.get(m.id)?.name ?? m.id) - const ivs = STAT_NAMES.map(s => `${creature.iv[s]} ${TO_DEX_STAT[s].toUpperCase().replace('SPA', 'SpA').replace('SPD', 'SpD')}`).join(' / ') - const evs = STAT_NAMES.map(s => `${creature.ev[s]} ${TO_DEX_STAT[s].toUpperCase().replace('SPA', 'SpA').replace('SPD', 'SpD')}`).join(' / ') + const DEX_DISPLAY: Record = { hp: 'HP', atk: 'Atk', def: 'Def', spa: 'SpA', spd: 'SpD', spe: 'Spe' } + const formatStatLine = (vals: Record) => + STAT_NAMES.map(s => `${vals[s]} ${DEX_DISPLAY[TO_DEX_STAT[s]]}`).join(' / ') + const ivs = formatStatLine(creature.iv) + const evs = formatStatLine(creature.ev) const lines = [ species.name, @@ -60,6 +63,18 @@ function getSpeciesMoves(speciesId: string, _level: number): string[] { grass: ['VineWhip', 'RazorLeaf'], electric: ['ThunderShock', 'Spark'], poison: ['PoisonSting', 'Smog'], + ice: ['IceShard', 'PowderSnow'], + fighting: ['KarateChop', 'LowKick'], + ground: ['MudSlap', 'SandAttack'], + flying: ['Gust', 'WingAttack'], + psychic: ['Confusion', 'Psybeam'], + bug: ['BugBite', 'StringShot'], + rock: ['RockThrow', 'SandAttack'], + ghost: ['Lick', 'ShadowSneak'], + dragon: ['DragonRage', 'Twister'], + dark: ['Bite', 'Pursuit'], + steel: ['MetalClaw', 'IronTail'], + fairy: ['FairyWind', 'DisarmingVoice'], } return basicMoves[type] ?? ['Tackle', 'Scratch'] } @@ -122,31 +137,24 @@ function projectBoosts(boosts: Record | undefined): Record + s?.startsWith('p1a') ? 'player' : 'opponent' + for (const line of log) { + const parts = line.split('|') + const side = parseSide(parts[2]) + if (line.startsWith('|move|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' - const moveName = parts[3] - events.push({ type: 'move', side, move: moveName, user: parts[2] }) + events.push({ type: 'move', side, move: parts[3], user: parts[2] }) } else if (line.startsWith('|-damage|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' - const hpStr = parts[3] // e.g. "16/20" or "16/20[1]" - const [cur, max] = parseHpString(hpStr) + const [cur, max] = parseHpString(parts[3]) events.push({ type: 'damage', side, amount: 0, percentage: Math.round((1 - cur / max) * 100) }) } else if (line.startsWith('|-heal|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' - const hpStr = parts[3] - const [cur, max] = parseHpString(hpStr) + const [cur, max] = parseHpString(parts[3]) events.push({ type: 'heal', side, amount: 0, percentage: Math.round(cur / max * 100) }) } else if (line.startsWith('|faint|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' events.push({ type: 'faint', side, speciesId: toID(parts[2]?.split(': ')?.[1] ?? '') }) } else if (line.startsWith('|switch|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' const speciesPart = parts[3]?.split(',')[0]?.split(': ') events.push({ type: 'switch', side, speciesId: toID(speciesPart?.[1] ?? ''), name: speciesPart?.[1] ?? '' }) } else if (line.startsWith('|-supereffective|')) { @@ -156,24 +164,16 @@ function parseLogToEvents(log: string[]): BattleEvent[] { } else if (line.startsWith('|-crit|')) { events.push({ type: 'crit' }) } else if (line.startsWith('|-miss|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' events.push({ type: 'miss', side } as any) } else if (line.startsWith('|-status|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' events.push({ type: 'status', side, status: mapStatus(parts[3]) }) } else if (line.startsWith('|-boost|') || line.startsWith('|-unboost|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' const stages = line.startsWith('|-boost|') ? parseInt(parts[4]) : -parseInt(parts[4]) events.push({ type: 'statChange', side, stat: parts[3], stages }) } else if (line.startsWith('|-ability|')) { - const parts = line.split('|') - const side = parts[2]?.startsWith('p1a') ? 'player' : 'opponent' events.push({ type: 'ability', side, ability: parts[3] }) } else if (line.startsWith('|turn|')) { - events.push({ type: 'turn', number: parseInt(line.split('|')[2]) }) + events.push({ type: 'turn', number: parseInt(parts[2]) }) } } return events diff --git a/packages/pokemon/src/battle/settlement.ts b/packages/pokemon/src/battle/settlement.ts index 63cf22517..9449a6e96 100644 --- a/packages/pokemon/src/battle/settlement.ts +++ b/packages/pokemon/src/battle/settlement.ts @@ -26,8 +26,8 @@ export function settleBattle( } // Calculate XP reward (simplified: base XP from species) - const species = Dex.species.get(opponentSpeciesId) - const baseXp = (species?.baseStats?.hp ?? 50) * opponentLevel / 7 + const oppSpecies = Dex.species.get(opponentSpeciesId) + const baseXp = (oppSpecies?.baseStats?.hp ?? 50) * opponentLevel / 7 const xpGained = Math.max(1, Math.floor(baseXp)) // Calculate EV reward diff --git a/packages/pokemon/src/core/storage.ts b/packages/pokemon/src/core/storage.ts index 3c03ff82a..78574ff64 100644 --- a/packages/pokemon/src/core/storage.ts +++ b/packages/pokemon/src/core/storage.ts @@ -382,7 +382,7 @@ export function getAllCreatureIds(data: BuddyData): string[] { // ─── Bag operations ─── export function addItemToBag(data: BuddyData, itemId: string, count = 1): BuddyData { - const items = [...data.bag.items] + const items = data.bag.items.map(e => ({ ...e })) const existing = items.find(e => e.id === itemId) if (existing) { existing.count += count @@ -393,7 +393,7 @@ export function addItemToBag(data: BuddyData, itemId: string, count = 1): BuddyD } export function removeItemFromBag(data: BuddyData, itemId: string, count = 1): { data: BuddyData; removed: boolean } { - const items = [...data.bag.items] + const items = data.bag.items.map(e => ({ ...e })) const existing = items.find(e => e.id === itemId) if (!existing || existing.count < count) return { data, removed: false } diff --git a/packages/pokemon/src/ui/BattleFlow.tsx b/packages/pokemon/src/ui/BattleFlow.tsx index 8ad2154cf..f6a0f5f69 100644 --- a/packages/pokemon/src/ui/BattleFlow.tsx +++ b/packages/pokemon/src/ui/BattleFlow.tsx @@ -1,7 +1,6 @@ import React, { useState, useCallback } from 'react' import { Box, Text, useInput } from '@anthropic/ink' import type { BuddyData, Creature, SpeciesId } from '../types' -import { getActiveCreature, getCreatureName } from '../core/creature' import { saveBuddyData } from '../core/storage' import { createBattle, executeTurn, type BattleInit } from '../battle/engine' import { settleBattle, applyMoveLearn, applyEvolution } from '../battle/settlement' @@ -232,9 +231,6 @@ export function BattleFlow({ buddyData: initialData, onClose }: BattleFlowProps) case 'evolution': { if (pendingEvos.length === 0) return null const evo = pendingEvos[0]! - useInput(() => { - handleEvolutionConfirm() - }) return ( 进化! diff --git a/packages/pokemon/src/ui/BattleView.tsx b/packages/pokemon/src/ui/BattleView.tsx index a27b3e4c6..7428edfe9 100644 --- a/packages/pokemon/src/ui/BattleView.tsx +++ b/packages/pokemon/src/ui/BattleView.tsx @@ -1,8 +1,6 @@ import React from 'react' import { Box, Text, type Color } from '@anthropic/ink' -import type { BattleState, BattleEvent, BattlePokemon, MoveOption } from '../battle/types' -import { getSpeciesData } from '../data/species' -import { Dex } from '@pkmn/sim' +import type { BattleState, BattleEvent } from '../battle/types' const CYAN = 'ansi:cyan' const GREEN = 'ansi:green'