mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 00:05:51 +00:00
feat: PC Box 管理系统 + 全英文名统一 + 队伍补位机制
- 新增 PC Box tab(左侧 party + 右侧 box 网格,支持 party↔box 拾取/放置/交换) - 空格键抓取/放下,左键在 col=0 时切到 party 面板 - 使用 useTabHeaderFocus 避免左右键被 Tabs 组件拦截 - 所有 1025 只精灵统一使用 Dex 英文名,移除中英混搭 - compactParty 补位机制:不允许前置空位,队伍最少保留一只 - PC Box tab 移至第二位(Buddy → PC Box → Pokédex → Egg) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,10 +112,12 @@ describe('addToParty', () => {
|
||||
})
|
||||
|
||||
describe('removeFromParty', () => {
|
||||
test('removes creature at index', () => {
|
||||
const data = makeData(2)
|
||||
const updated = removeFromParty(data, 1)
|
||||
expect(updated.party[1]).toBeNull()
|
||||
test('removes creature and compacts party', () => {
|
||||
const data = makeData(3)
|
||||
const updated = removeFromParty(data, 0)
|
||||
expect(updated.party[0]).toBe('creature-1')
|
||||
expect(updated.party[1]).toBe('creature-2')
|
||||
expect(updated.party[2]).toBeNull()
|
||||
})
|
||||
|
||||
test('does nothing for out-of-bounds index', () => {
|
||||
@@ -123,6 +125,12 @@ describe('removeFromParty', () => {
|
||||
const updated = removeFromParty(data, 10)
|
||||
expect(updated.party).toEqual(data.party)
|
||||
})
|
||||
|
||||
test('cannot remove last party member', () => {
|
||||
const data = makeData(1)
|
||||
const updated = removeFromParty(data, 0)
|
||||
expect(updated.party[0]).toBe('creature-0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('swapPartySlots', () => {
|
||||
|
||||
@@ -266,19 +266,28 @@ export function incrementTurns(data: BuddyData): BuddyData {
|
||||
|
||||
// ─── Party operations ───
|
||||
|
||||
/** Compact party: move all non-null to front, pad with nulls to length 6 */
|
||||
export function compactParty(party: (string | null)[]): (string | null)[] {
|
||||
const filled = party.filter((id): id is string => id !== null)
|
||||
return [...filled, ...Array(6).fill(null)].slice(0, 6)
|
||||
}
|
||||
|
||||
export function addToParty(data: BuddyData, creatureId: string): { data: BuddyData; added: boolean } {
|
||||
const party = [...data.party]
|
||||
const emptyIdx = party.findIndex(p => p === null)
|
||||
if (emptyIdx === -1) return { data, added: false }
|
||||
party[emptyIdx] = creatureId
|
||||
return { data: { ...data, party }, added: true }
|
||||
return { data: { ...data, party: compactParty(party) }, added: true }
|
||||
}
|
||||
|
||||
export function removeFromParty(data: BuddyData, slotIndex: number): BuddyData {
|
||||
if (slotIndex < 0 || slotIndex >= 6) return data
|
||||
const party = [...data.party]
|
||||
// Don't remove if it would leave party empty
|
||||
const count = party.filter(Boolean).length
|
||||
if (count <= 1) return data
|
||||
party[slotIndex] = null
|
||||
return { ...data, party }
|
||||
return { ...data, party: compactParty(party) }
|
||||
}
|
||||
|
||||
export function swapPartySlots(data: BuddyData, indexA: number, indexB: number): BuddyData {
|
||||
@@ -287,7 +296,7 @@ export function swapPartySlots(data: BuddyData, indexA: number, indexB: number):
|
||||
const b = party[indexB]
|
||||
party[indexA] = b
|
||||
party[indexB] = a
|
||||
return { ...data, party }
|
||||
return { ...data, party: compactParty(party) }
|
||||
}
|
||||
|
||||
export function setActivePartyMember(data: BuddyData, creatureId: string): BuddyData {
|
||||
@@ -300,7 +309,7 @@ export function setActivePartyMember(data: BuddyData, creatureId: string): Buddy
|
||||
} else {
|
||||
party[0] = creatureId
|
||||
}
|
||||
return { ...data, party }
|
||||
return { ...data, party: compactParty(party) }
|
||||
}
|
||||
|
||||
// ─── PC Box operations ───
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Dex } from '@pkmn/sim'
|
||||
import type { SpeciesData, SpeciesId, GrowthRate } from '../types'
|
||||
import { getSpecies, mapBaseStats, mapGenderRatio } from './pkmn'
|
||||
import { getNextEvolution } from './evolution'
|
||||
import { SPECIES_I18N, SPECIES_PERSONALITY } from './names'
|
||||
import { SPECIES_PERSONALITY } from './names'
|
||||
|
||||
// ─── Dynamic species list from @pkmn/sim Dex ───
|
||||
|
||||
@@ -111,7 +111,6 @@ function buildEvolutionChain(speciesId: SpeciesId): SpeciesData['evolutionChain'
|
||||
function buildSpeciesData(id: SpeciesId): SpeciesData {
|
||||
const dex = getSpecies(id)
|
||||
const sup = SUPPLEMENT[id] ?? DEFAULT_SUPPLEMENT
|
||||
const i18n = SPECIES_I18N[id]
|
||||
const personality = SPECIES_PERSONALITY[id]
|
||||
|
||||
if (!dex) {
|
||||
@@ -121,7 +120,7 @@ function buildSpeciesData(id: SpeciesId): SpeciesData {
|
||||
return {
|
||||
id,
|
||||
name: dex.name,
|
||||
names: i18n ?? { en: dex.name },
|
||||
names: { en: dex.name },
|
||||
dexNumber: dex.num,
|
||||
genderRate: mapGenderRatio(dex.genderRatio as { M: number; F: number } | undefined),
|
||||
baseStats: mapBaseStats(dex.baseStats),
|
||||
|
||||
@@ -52,7 +52,7 @@ export { checkEggEligibility, generateEgg, advanceEggSteps, isEggReadyToHatch, h
|
||||
export {
|
||||
loadBuddyData, saveBuddyData, getDefaultBuddyData, migrateFromLegacy,
|
||||
updateDailyStats, incrementTurns,
|
||||
addToParty, removeFromParty, swapPartySlots, setActivePartyMember,
|
||||
addToParty, removeFromParty, swapPartySlots, setActivePartyMember, compactParty,
|
||||
depositToBox, withdrawFromBox, moveInBox, renameBox,
|
||||
findCreatureLocation, releaseCreature, getTotalCreatureCount, getAllCreatureIds,
|
||||
addItemToBag, removeItemFromBag, getItemCount,
|
||||
@@ -62,6 +62,7 @@ export { loadSprite, fetchAndCacheSprite, getSpeciesDisplay } from './core/sprit
|
||||
// Sprites
|
||||
export { renderAnimatedSprite, shrinkSprite, getIdleAnimMode, getPetOverlay } from './sprites/renderer'
|
||||
export { getFallbackSprite } from './sprites/fallback'
|
||||
export { SpeciesPicker } from './ui/SpeciesPicker'
|
||||
|
||||
// UI Components
|
||||
export { CompanionCard } from './ui/CompanionCard'
|
||||
|
||||
@@ -67,7 +67,7 @@ export function CompanionCard({ creature, buddyData, spriteLines }: CompanionCar
|
||||
|
||||
// Evolution hint
|
||||
const evoHint = nextEvo ? (
|
||||
<Text color={GRAY}> → <Text color={CYAN}>{getSpeciesData(nextEvo.to).names.zh ?? getSpeciesData(nextEvo.to).name}</Text> Lv.{nextEvo.minLevel}</Text>
|
||||
<Text color={GRAY}> → <Text color={CYAN}>{getSpeciesData(nextEvo.to).name}</Text> Lv.{nextEvo.minLevel}</Text>
|
||||
) : null
|
||||
|
||||
return (
|
||||
@@ -84,7 +84,7 @@ export function CompanionCard({ creature, buddyData, spriteLines }: CompanionCar
|
||||
|
||||
{/* Species + type + gender */}
|
||||
<Box>
|
||||
<Text color={GRAY}>{species.names.zh ?? species.name}</Text>
|
||||
<Text color={GRAY}>{species.name}</Text>
|
||||
<Text> </Text>
|
||||
{typeBadges}
|
||||
{genderSymbol && <Text> {genderSymbol}</Text>}
|
||||
|
||||
@@ -112,7 +112,7 @@ export function PokedexView({ buddyData }: PokedexViewProps) {
|
||||
<Text>{isActive ? <Text color={YELLOW}>▶</Text> : ' '}</Text>
|
||||
<Text color={GRAY}>#{String(species.dexNumber).padStart(3, '0')} </Text>
|
||||
<Text color={WHITE} bold={isActive}>
|
||||
{(species.names as Record<string, string>).zh ?? species.name}
|
||||
{species.name}
|
||||
</Text>
|
||||
<Text>
|
||||
{' '}
|
||||
|
||||
@@ -59,7 +59,7 @@ export function SpeciesDetail({ speciesId, caughtLevel, spriteLines }: SpeciesDe
|
||||
{/* Header */}
|
||||
<Box justifyContent="space-between">
|
||||
<Box>
|
||||
<Text bold color={CYAN}>#{String(species.dexNumber).padStart(3, '0')} {species.names.zh ?? species.name}</Text>
|
||||
<Text bold color={CYAN}>#{String(species.dexNumber).padStart(3, '0')} {species.name}</Text>
|
||||
</Box>
|
||||
{caughtLevel && <Text color={GREEN}>Best: Lv.{caughtLevel}</Text>}
|
||||
</Box>
|
||||
@@ -163,7 +163,7 @@ function EvolutionChain({ speciesId }: { speciesId: SpeciesId }) {
|
||||
<React.Fragment key={sid}>
|
||||
{i > 0 && <Text color={GRAY}> → </Text>}
|
||||
<Text color={sid === speciesId ? CYAN : GRAY} bold={sid === speciesId}>
|
||||
{getSpeciesData(sid).names.zh ?? getSpeciesData(sid).name}
|
||||
{getSpeciesData(sid).name}
|
||||
</Text>
|
||||
{i < chain.length - 1 && getNextEvolution(sid) && (
|
||||
<Text color={GRAY}> Lv.{getNextEvolution(sid)!.minLevel}</Text>
|
||||
|
||||
@@ -19,7 +19,7 @@ const ALL_ENTRIES: SpeciesEntry[] = ALL_SPECIES_IDS.map(id => {
|
||||
return {
|
||||
id,
|
||||
name: data.name,
|
||||
displayName: (data.names as Record<string, string>).zh ?? data.name,
|
||||
displayName: data.name,
|
||||
dexNumber: data.dexNumber,
|
||||
types: data.types as string[],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user