From 7c64199fc58faa3a162f024e66175a8423ddfdf2 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Wed, 22 Apr 2026 04:12:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=88=98=E6=96=97?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=20bug=EF=BC=88switch=20=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E3=80=81async=20learnableMoves=E3=80=81=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AE=89=E5=85=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - engine.ts: switch 动作改为映射 creatureId 到 party slot index - settlement.ts: 改用 for-of 循环支持 async learnableMoves 检测 - types.ts: miss 事件增加 side 字段,消除 as any - BattleFlow.tsx: handleAction 改为 async 支持 await settleBattle - battle.test.ts: 补充缺失的 async 标记 Co-Authored-By: Claude Opus 4.6 --- packages/pokemon/src/__tests__/battle.test.ts | 20 ++++---- packages/pokemon/src/battle/engine.ts | 11 +++-- packages/pokemon/src/battle/settlement.ts | 46 ++++++++++++++----- packages/pokemon/src/battle/types.ts | 2 +- packages/pokemon/src/ui/BattleFlow.tsx | 4 +- 5 files changed, 56 insertions(+), 27 deletions(-) diff --git a/packages/pokemon/src/__tests__/battle.test.ts b/packages/pokemon/src/__tests__/battle.test.ts index 80fc9d191..241a04026 100644 --- a/packages/pokemon/src/__tests__/battle.test.ts +++ b/packages/pokemon/src/__tests__/battle.test.ts @@ -99,7 +99,7 @@ describe('executeTurn', () => { }) describe('settleBattle', () => { - test('player win increments battlesWon', () => { + test('player win increments battlesWon', async () => { const creature = makeTestCreature() const data: BuddyData = { version: 2, @@ -127,11 +127,11 @@ describe('settleBattle', () => { participantIds: [creature.id], } - const settlement = settleBattle(data, result, 'squirtle', 20) + const settlement = await settleBattle(data, result, 'squirtle', 20) expect(settlement.data.stats.battlesWon).toBe(1) }) - test('player loss returns unchanged data', () => { + test('player loss returns unchanged data', async () => { const creature = makeTestCreature() const data: BuddyData = { version: 2, @@ -159,7 +159,7 @@ describe('settleBattle', () => { participantIds: [creature.id], } - const settlement = settleBattle(data, result, 'squirtle', 20) + const settlement = await settleBattle(data, result, 'squirtle', 20) // Loss early-returns unchanged data expect(settlement.data.creatures[0]!.totalXp).toBe(creature.totalXp) expect(settlement.learnableMoves).toEqual([]) @@ -255,7 +255,7 @@ describe('chooseAIMove', () => { }) describe('settleBattle - advanced', () => { - test('player win awards XP to creature', () => { + test('player win awards XP to creature', async () => { const creature = makeTestCreature({ level: 5 }) const data = makeTestBuddyData([creature]) const result = { @@ -265,11 +265,11 @@ describe('settleBattle - advanced', () => { evGained: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 }, participantIds: [creature.id], } - const settlement = settleBattle(data, result, 'squirtle', 20) + const settlement = await settleBattle(data, result, 'squirtle', 20) expect(settlement.data.creatures[0]!.totalXp).toBeGreaterThan(0) }) - test('player win awards EVs (capped at 252 per stat)', () => { + test('player win awards EVs (capped at 252 per stat)', async () => { const creature = makeTestCreature({ level: 5, ev: { hp: 250, attack: 250, defense: 250, spAtk: 250, spDef: 250, speed: 250 }, @@ -282,13 +282,13 @@ describe('settleBattle - advanced', () => { evGained: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 }, participantIds: [creature.id], } - const settlement = settleBattle(data, result, 'squirtle', 20) + const settlement = await settleBattle(data, result, 'squirtle', 20) for (const stat of ['hp', 'attack', 'defense', 'spAtk', 'spDef', 'speed'] as const) { expect(settlement.data.creatures[0]!.ev[stat]).toBeLessThanOrEqual(252) } }) - test('player loss does not increment battlesWon', () => { + test('player loss does not increment battlesWon', async () => { const creature = makeTestCreature() const data = makeTestBuddyData([creature]) const result = { @@ -298,7 +298,7 @@ describe('settleBattle - advanced', () => { evGained: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 }, participantIds: [creature.id], } - const settlement = settleBattle(data, result, 'squirtle', 20) + const settlement = await settleBattle(data, result, 'squirtle', 20) expect(settlement.data.stats.battlesWon).toBe(0) }) }) diff --git a/packages/pokemon/src/battle/engine.ts b/packages/pokemon/src/battle/engine.ts index c16e57510..15ba5f260 100644 --- a/packages/pokemon/src/battle/engine.ts +++ b/packages/pokemon/src/battle/engine.ts @@ -164,7 +164,7 @@ function parseLogToEvents(log: string[]): BattleEvent[] { } else if (line.startsWith('|-crit|')) { events.push({ type: 'crit' }) } else if (line.startsWith('|-miss|')) { - events.push({ type: 'miss', side } as any) + events.push({ type: 'miss', side }) } else if (line.startsWith('|-status|')) { events.push({ type: 'status', side, status: mapStatus(parts[3]) }) } else if (line.startsWith('|-boost|') || line.startsWith('|-unboost|')) { @@ -235,8 +235,13 @@ export function executeTurn( case 'move': p1Choice = `move ${action.moveIndex + 1}` break - case 'switch': - p1Choice = `switch ${action.creatureId}` + case 'switch': { + // Find the party slot number for this creature (sim uses 1-based index) + const p1Pokemon: any[] = battle.p1.pokemon + const switchIdx = p1Pokemon.findIndex((p: any) => toID(p.name) === action.creatureId || p.name === action.creatureId) + p1Choice = switchIdx >= 0 ? `switch ${switchIdx + 1}` : 'move 1' + break + } break case 'item': p1Choice = 'move 1' // Items handled via settlement diff --git a/packages/pokemon/src/battle/settlement.ts b/packages/pokemon/src/battle/settlement.ts index 9449a6e96..63c578a99 100644 --- a/packages/pokemon/src/battle/settlement.ts +++ b/packages/pokemon/src/battle/settlement.ts @@ -11,16 +11,16 @@ import { Dex } from '@pkmn/sim' /** * Settle battle results: XP, EV, level ups, move learning, evolution detection. */ -export function settleBattle( +export async function settleBattle( data: BuddyData, result: BattleResult, opponentSpeciesId: SpeciesId, opponentLevel: number, -): { +): Promise<{ data: BuddyData learnableMoves: { creatureId: string; moveId: string; moveName: string }[] pendingEvolutions: { creatureId: string; from: SpeciesId; to: SpeciesId }[] -} { +}> { if (result.winner !== 'player') { return { data, learnableMoves: [], pendingEvolutions: [] } } @@ -40,10 +40,14 @@ export function settleBattle( // Award XP/EV to participant creatures const learnableMoves: { creatureId: string; moveId: string; moveName: string }[] = [] const pendingEvolutions: { creatureId: string; from: SpeciesId; to: SpeciesId }[] = [] - const participantIds = new Set(result.participantIds.length > 0 ? result.participantIds : data.party.filter(Boolean)) + const participantIds = new Set(result.participantIds.length > 0 ? result.participantIds : data.party.filter((id): id is string => id !== null)) - const updatedCreatures = data.creatures.map(creature => { - if (!participantIds.has(creature.id)) return creature + const updatedCreatures: typeof data.creatures = [] + for (const creature of data.creatures) { + if (!participantIds.has(creature.id)) { + updatedCreatures.push(creature) + continue + } // Award EVs (capped) const newEv = { ...creature.ev } @@ -61,8 +65,28 @@ export function settleBattle( const species = getSpeciesData(creature.speciesId) const newLevel = Math.min(100, levelFromXp(newTotalXp, species.growthRate)) - // Detect new learnable moves (async in real code, but for settlement we check synchronously) - // This will be handled at the UI level with getNewLearnableMoves + // Detect new learnable moves on level up + if (newLevel > oldLevel) { + const learnset = await Dex.learnsets.get(creature.speciesId) + if (learnset?.learnset) { + for (const [moveId, sources] of Object.entries(learnset.learnset)) { + for (const src of sources as string[]) { + if (src.startsWith('9L')) { + const moveLevel = parseInt(src.slice(2)) + if (moveLevel > oldLevel && moveLevel <= newLevel) { + const dexMove = Dex.moves.get(moveId) + learnableMoves.push({ + creatureId: creature.id, + moveId, + moveName: dexMove?.name ?? moveId, + }) + } + break + } + } + } + } + } // Detect evolution if (newLevel > oldLevel) { @@ -80,13 +104,13 @@ export function settleBattle( } } - return { + updatedCreatures.push({ ...creature, level: newLevel, totalXp: newTotalXp, ev: newEv, - } - }) + }) + } // Update data const updatedData: BuddyData = { diff --git a/packages/pokemon/src/battle/types.ts b/packages/pokemon/src/battle/types.ts index 557a0a395..1ed735e14 100644 --- a/packages/pokemon/src/battle/types.ts +++ b/packages/pokemon/src/battle/types.ts @@ -39,7 +39,7 @@ export type BattleEvent = | { type: 'switch'; side: 'player' | 'opponent'; speciesId: string; name: string } | { type: 'effectiveness'; multiplier: number } | { type: 'crit' } - | { type: 'miss' } + | { type: 'miss'; side: 'player' | 'opponent' } | { type: 'status'; side: 'player' | 'opponent'; status: StatusCondition } | { type: 'statChange'; side: 'player' | 'opponent'; stat: string; stages: number } | { type: 'ability'; side: 'player' | 'opponent'; ability: string } diff --git a/packages/pokemon/src/ui/BattleFlow.tsx b/packages/pokemon/src/ui/BattleFlow.tsx index f6a0f5f69..04cb20d6e 100644 --- a/packages/pokemon/src/ui/BattleFlow.tsx +++ b/packages/pokemon/src/ui/BattleFlow.tsx @@ -65,7 +65,7 @@ export function BattleFlow({ buddyData: initialData, onClose }: BattleFlowProps) }, [buddyData]) // Battle phase: handle action - const handleAction = useCallback((action: PlayerAction) => { + const handleAction = useCallback(async (action: PlayerAction) => { if (!battleInit) return const state = executeTurn(battleInit, action) setBattleState(state) @@ -73,7 +73,7 @@ export function BattleFlow({ buddyData: initialData, onClose }: BattleFlowProps) if (state.finished && state.result) { const participants = buddyData.party.filter((id): id is string => id !== null) const result = { ...state.result, participantIds: participants } - const settled = settleBattle(buddyData, result, opponentSpeciesId, opponentLevel) + const settled = await settleBattle(buddyData, result, opponentSpeciesId, opponentLevel) setBuddyData(settled.data) setPendingMoves(settled.learnableMoves)