mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
fix: 修复战斗系统 bug(switch 映射、async learnableMoves、类型安全)
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user