From ccfd6a5e756138c5f5e67996c8ef1bc8846cb692 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Feb 2026 15:52:32 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=AD=A6=E5=99=A8?= =?UTF-8?q?=E7=BB=8F=E9=AA=8C=E8=8E=B7=E5=8F=96=E5=B9=B6=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E4=B9=89=E4=BD=93=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复战斗胜利后未获得武器技能经验的问题 (initCombat未传递skillExpReward) - 每级武器技能提供5%武器伤害加成(已实现,无需修改) - 实现义体安装/卸载功能,支持NPC对话交互 - StatusPanel添加义体装备槽显示 - MapPanel修复NPC对话import问题 - 新增成就系统框架 - 添加项目文档CLAUDE.md Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 177 ++++++++++++++++++++++++++++++ components/panels/MapPanel.vue | 19 +--- components/panels/StatusPanel.vue | 33 ++++++ config/achievements.js | 110 +++++++++++++++++++ config/npcs.js | 48 ++++++++ utils/combatSystem.js | 45 +++++--- utils/eventSystem.js | 133 +++++++++++++++++++++- utils/prostheticSystem.js | 142 ++++++++++++++++++++++++ 8 files changed, 672 insertions(+), 35 deletions(-) create mode 100644 CLAUDE.md create mode 100644 config/achievements.js create mode 100644 utils/prostheticSystem.js diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..85d4ec2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,177 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a text-based idle adventure game (文字冒险游戏) built with uni-app, Vue 3, and Pinia. The game features skill-driven progression, idle mechanics, combat, crafting, and a dark post-apocalyptic theme. + +**Tech Stack:** +- Framework: uni-app (Vue 3 with Composition API) +- State Management: Pinia +- Testing: Jest + Vitest + Playwright +- Style: SCSS with global variables + +## Build and Run Commands + +### Development +- Use HBuilderX IDE to run the project (uni-app standard) +- Run to different platforms: mp-weixin (WeChat Mini Program), h5, app + +### Testing +```bash +npm test # Run Jest tests in watch mode +npm run test:watch # Run tests in watch mode +npm run test:coverage # Generate coverage report +``` + +## Architecture + +### Directory Structure +``` +├── components/ +│ ├── common/ # Reusable UI components (ProgressBar, StatItem, etc.) +│ ├── layout/ # Layout components (TabBar, Drawer) +│ ├── panels/ # Main game panels (StatusPanel, MapPanel, LogPanel) +│ └── drawers/ # Slide-out drawers (Inventory, Event, Shop, Crafting) +├── config/ # Game configuration (skills, items, enemies, locations, etc.) +├── store/ # Pinia stores (player.js, game.js) +├── utils/ # Game systems (combat, skill, item, task, event, etc.) +├── pages/ # Uni-app pages +└── tests/ # Unit tests with fixtures and helpers +``` + +### Core Systems (utils/) +- **skillSystem.js**: Skill unlocking, XP, leveling, milestone rewards, parent skills +- **combatSystem.js**: AP/EP hit calculation, three-layer defense (evade/shield/armor), damage +- **itemSystem.js**: Item usage, equipment, quality calculation, inventory management +- **taskSystem.js**: Idle task management, mutual exclusion checks +- **eventSystem.js**: Event triggering, NPC dialogue handling +- **environmentSystem.js**: Environment penalties, passive skill XP +- **gameLoop.js**: Main game tick (1-second interval), time progression, auto-save +- **storage.js**: Save/load using uni.setStorageSync, offline earnings +- **craftingSystem.js**: Recipe-based crafting with quality calculation +- **levelingSystem.js**: Player level progression and stat scaling +- **mapLayout.js**: Visual map representation + +### State Management (Pinia Stores) +- **player.js**: Base stats, current stats (HP/stamina/sanity), level, skills, equipment, inventory, currency, location, flags +- **game.js**: Current tab, drawer states, logs, game time, combat state, active tasks, market prices, current event + +### Configuration (config/) +- **constants.js**: Game constants (quality levels, stat scaling, time scale, etc.) +- **skills.js**: All skill definitions with milestones and unlock conditions +- **items.js**: All item definitions (weapons, consumables, materials, books) +- **enemies.js**: Enemy stats, drops, spawn conditions +- **locations.js**: Area definitions, connections, activities, unlock conditions +- **npcs.js**: NPC dialogue trees and interactions +- **events.js**: Event definitions and triggers +- **recipes.js**: Crafting recipes +- **shop.js**: Shop inventory and pricing +- **formulas.js**: Mathematical formulas for stat calculations + +## Key Game Concepts + +### Quality System (品质系统) +Items have quality (0-250) that affects their power: +- 0-49: 垃圾 (Trash) +- 50-99: 普通 (Common) +- 100-139: 优秀 (Good) +- 140-169: 稀有 (Rare) +- 170-199: 史诗 (Epic) +- 200-250: 传说 (Legendary) + +Each tier has a multiplier that increases item stats. + +### AP/EP Combat System +- AP (Attack Points) = 灵巧 + 智力×0.2 + skills + equipment + stance - enemyCountPenalty +- EP (Evasion Points) = 敏捷 + 智力×0.2 + skills + equipment + stance - environmentPenalty +- Hit rate is non-linear: AP=5×EP → ~98%, AP=EP → ~50%, AP=0.5×EP → ~24% + +### Three-Layer Defense +1. Evasion (complete dodge) +2. Shield absorption +3. Defense reduction (min 10% damage) + +### Skill Milestones +Skills provide permanent global bonuses at specific levels (e.g., all weapons +crit rate at level 5). Parent skills auto-level to match highest child skill level. + +### Time System +- 1 real second = 5 game minutes +- Day/night cycle affects gameplay +- Markets refresh daily +- Offline earnings available (0.5h base, 2.5h with ad) + +### Idle Tasks +Active tasks (reading, training, working) and passive tasks (environment adaptation). Combat is mutually exclusive with most tasks. + +## Important Patterns + +### Adding New Skills +1. Add config to `config/skills.js` +2. Set unlockCondition (item, location, or skill level) +3. Add milestones with permanent bonuses +4. Link to parentSkill if applicable + +### Adding New Items +1. Add config to `config/items.js` +2. Set baseValue, baseDamage/defense, type +3. Add to shop or enemy drop tables +4. For equipment, quality affects final stats + +### Adding New Locations +1. Add to `config/locations.js` +2. Set connections, enemies, activities +3. Add unlockCondition if needed +4. Update `utils/mapLayout.js` for visual representation + +### Store Usage +```javascript +import { usePlayerStore } from '@/store/player.js' +import { useGameStore } from '@/store/game.js' + +const player = usePlayerStore() +const game = useGameStore() + +// Access state +player.baseStats.strength +game.logs + +// Call actions +game.addLog('message', 'info') +``` + +### Color Variables (uni.scss) +- $bg-primary: #0a0a0f (main background) +- $accent: #4ecdc4 (primary accent) +- $danger: #ff6b6b (combat/HP low) +- Quality colors: $quality-trash through $quality-legend + +## Development Workflow + +### Game Loop Timing +- Main loop runs every 1 second via `gameLoop.js` +- Time advances by TIME_SCALE (5) minutes per tick +- Auto-save triggers every 30 game minutes + +### Saving +- `App.vue` handles onLaunch/onShow/onHide +- Auto-saves on app hide +- Manual save via `window.manualSave()` +- Logs are NOT persisted (cleared on load) + +### Testing +- Tests in `tests/unit/` use Vitest +- Fixtures in `tests/fixtures/` provide mock data +- Helpers in `tests/helpers/` mock timers, stores, random +- See `tests/README.md` for testing guidelines + +## Known Gotchas + +1. **uni API availability**: Some uni APIs only work in specific platforms (H5 vs mini-program) +2. **Store timing**: Always use `setActivePinia(createPinia())` in tests +3. **Logs array**: Limited to 200 entries, shifts old entries +4. **Skill level cap**: Cannot exceed player level × 2 +5. **Equipment quality**: Calculated as base × quality% × tierMultiplier +6. **Combat stance**: Switching stance resets attack progress +7. **Market saturation**: Selling same item reduces price (encourages exploration) diff --git a/components/panels/MapPanel.vue b/components/panels/MapPanel.vue index fd134ce..2867cb4 100644 --- a/components/panels/MapPanel.vue +++ b/components/panels/MapPanel.vue @@ -124,7 +124,7 @@ import { NPC_CONFIG } from '@/config/npcs.js' import { ENEMY_CONFIG, getRandomEnemyForLocation } from '@/config/enemies.js' import { getShopConfig } from '@/config/shop.js' import { initCombat, getEnvironmentType } from '@/utils/combatSystem.js' -import { triggerExploreEvent, tryFlee } from '@/utils/eventSystem.js' +import { triggerExploreEvent, tryFlee, startNPCDialogue } from '@/utils/eventSystem.js' import TextButton from '@/components/common/TextButton.vue' import ProgressBar from '@/components/common/ProgressBar.vue' import FilterTabs from '@/components/common/FilterTabs.vue' @@ -252,16 +252,8 @@ const availableNPCs = computed(() => { function talkToNPC(npc) { game.addLog(`与 ${npc.name} 开始对话...`, 'info') - // 打开 NPC 对话 - if (npc.dialogue && npc.dialogue.first) { - game.drawerState.event = true - game.currentEvent = { - type: 'npc_dialogue', - npcId: npc.id, - dialogue: npc.dialogue.first, - choices: npc.dialogue.first.choices || [] - } - } + // 使用 eventSystem 的 startNPCDialogue 来处理对话 + startNPCDialogue(game, npc.id, 'first', player) } function openInventory() { @@ -360,14 +352,15 @@ function startCombat() { // 获取环境类型 const environment = getEnvironmentType(player.currentLocation) - // 使用 initCombat 初始化战斗 - game.combatState = initCombat(enemyConfig.id, enemyConfig, environment) + // 使用 initCombat 初始化战斗,传入偏好的战斗姿态 + game.combatState = initCombat(enemyConfig.id, enemyConfig, environment, game.preferredStance) game.inCombat = true } function changeStance(stance) { if (game.combatState) { game.combatState.stance = stance + game.preferredStance = stance // 记住玩家选择的姿态 game.addLog(`切换到${stanceTabs.find(t => t.id === stance)?.label}姿态`, 'combat') } } diff --git a/components/panels/StatusPanel.vue b/components/panels/StatusPanel.vue index c2f017c..9036b33 100644 --- a/components/panels/StatusPanel.vue +++ b/components/panels/StatusPanel.vue @@ -114,6 +114,19 @@ + + 义体 + + + {{ player.equipment.prosthetic.icon }} {{ player.equipment.prosthetic.name }} + + + {{ formatProstheticStats(player.equipment.prosthetic.stats) }} + + [{{ getProstheticSkillName(player.equipment.prosthetic.skill) }}] + + + @@ -575,6 +588,26 @@ function cancelActiveTask(task) { endTask(game, player, task.id, false) game.addLog(`取消了 ${task.name}`, 'info') } + +// 义体相关辅助函数 +function formatProstheticStats(stats) { + if (!stats) return '' + const parts = [] + if (stats.strength) parts.push(`力+${stats.strength}`) + if (stats.agility) parts.push(`敏+${stats.agility}`) + if (stats.dexterity) parts.push(`灵+${stats.dexterity}`) + if (stats.intuition) parts.push(`智+${stats.intuition}`) + if (stats.vitality) parts.push(`体+${stats.vitality}`) + if (stats.attack) parts.push(`攻+${stats.attack}`) + if (stats.defense) parts.push(`防+${stats.defense}`) + return parts.join(' ') +} + +function getProstheticSkillName(skillId) { + const { SKILL_CONFIG } = require('@/config/skills') + const skill = SKILL_CONFIG[skillId] + return skill ? skill.name : skillId +}