diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a7c3b87..8b3f890 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,8 @@ "Bash(git config:*)", "Bash(git remote add:*)", "Bash(git add:*)", - "Bash(git commit:*)" + "Bash(git commit:*)", + "Bash(git push:*)" ] } } diff --git a/App.vue b/App.vue index 346f9b9..908d84b 100644 --- a/App.vue +++ b/App.vue @@ -243,4 +243,185 @@ page { background-color: $bg-tertiary; border-radius: 4rpx; } + +/* ==================== 过渡动画 ==================== */ + +/* 淡入淡出 */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.3s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +/* 滑动进入 - 从右侧 */ +.slide-right-enter-active, +.slide-right-leave-active { + transition: all 0.3s ease; +} + +.slide-right-enter-from { + transform: translateX(100%); + opacity: 0; +} + +.slide-right-leave-to { + transform: translateX(100%); + opacity: 0; +} + +/* 滑动进入 - 从左侧 */ +.slide-left-enter-active, +.slide-left-leave-active { + transition: all 0.3s ease; +} + +.slide-left-enter-from { + transform: translateX(-100%); + opacity: 0; +} + +.slide-left-leave-to { + transform: translateX(-100%); + opacity: 0; +} + +/* 滑动进入 - 从下方 */ +.slide-up-enter-active, +.slide-up-leave-active { + transition: all 0.3s ease; +} + +.slide-up-enter-from { + transform: translateY(100%); + opacity: 0; +} + +.slide-up-leave-to { + transform: translateY(100%); + opacity: 0; +} + +/* 缩放动画 */ +.scale-enter-active, +.scale-leave-active { + transition: all 0.2s ease; +} + +.scale-enter-from, +.scale-leave-to { + transform: scale(0.9); + opacity: 0; +} + +/* 弹跳动画 */ +.bounce-enter-active { + animation: bounce-in 0.4s ease; +} + +.bounce-leave-active { + animation: bounce-in 0.3s ease reverse; +} + +@keyframes bounce-in { + 0% { + transform: scale(0); + opacity: 0; + } + 50% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +/* 闪烁动画 */ +@keyframes flash { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.flash { + animation: flash 0.5s ease; +} + +/* 脉冲动画 */ +@keyframes pulse { + 0%, 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.05); + opacity: 0.8; + } +} + +.pulse { + animation: pulse 1.5s ease infinite; +} + +/* 摇晃动画 */ +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-10rpx); } + 75% { transform: translateX(10rpx); } +} + +.shake { + animation: shake 0.3s ease; +} + +/* 旋转加载 */ +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.spin { + animation: spin 1s linear infinite; +} + +/* 打字机效果 */ +@keyframes typewriter { + from { width: 0; } + to { width: 100%; } +} + +/* ==================== 工具类 ==================== */ + +/* 过渡效果 */ +.transition-all { + transition: all 0.3s ease; +} + +.transition-fast { + transition: all 0.15s ease; +} + +.transition-slow { + transition: all 0.5s ease; +} + +/* 悬停效果 */ +.hover-scale { + transition: transform 0.2s ease; +} + +.hover-scale:active { + transform: scale(0.95); +} + +.hover-bright { + transition: filter 0.2s ease; +} + +.hover-bright:active { + filter: brightness(1.2); +} diff --git a/components/common/TextButton.vue b/components/common/TextButton.vue index 06aed80..5d7d96b 100644 --- a/components/common/TextButton.vue +++ b/components/common/TextButton.vue @@ -1,10 +1,17 @@ @@ -12,13 +19,14 @@ const props = defineProps({ text: { type: String, required: true }, type: { type: String, default: 'default' }, - disabled: { type: Boolean, default: false } + disabled: { type: Boolean, default: false }, + loading: { type: Boolean, default: false } }) const emit = defineEmits(['click']) function handleClick() { - if (!props.disabled) { + if (!props.disabled && !props.loading) { emit('click') } } @@ -26,43 +34,133 @@ function handleClick() { diff --git a/components/drawers/InventoryDrawer.vue b/components/drawers/InventoryDrawer.vue index 299bbeb..09ad88e 100644 --- a/components/drawers/InventoryDrawer.vue +++ b/components/drawers/InventoryDrawer.vue @@ -21,8 +21,9 @@ > {{ item.icon || '📦' }} - + {{ item.name }} + [{{ item.qualityName }}] x{{ item.count }} @@ -73,6 +74,7 @@ import { ref, computed } from 'vue' import { usePlayerStore } from '@/store/player' import { useGameStore } from '@/store/game' +import { equipItem as equipItemUtil, unequipItemBySlot, useItem as useItemUtil } from '@/utils/itemSystem.js' import FilterTabs from '@/components/common/FilterTabs.vue' import TextButton from '@/components/common/TextButton.vue' @@ -128,16 +130,6 @@ function isEquipped(item) { return equipped && (equipped.uniqueId === item.uniqueId || equipped.id === item.id) } -function qualityColor(quality) { - if (!quality) return '#ffffff' - if (quality >= 200) return '#f97316' // 传说 - if (quality >= 160) return '#a855f7' // 史诗 - if (quality >= 130) return '#60a5fa' // 稀有 - if (quality >= 100) return '#4ade80' // 优秀 - if (quality >= 50) return '#ffffff' // 普通 - return '#808080' // 垃圾 -} - function close() { emit('close') } @@ -148,30 +140,10 @@ function selectItem(item) { function equipItem() { if (!selectedItem.value) return - const item = selectedItem.value - const slot = slotMap[item.type] - - if (!slot) return - - // 如果该槽位已有装备,先卸下旧装备放回背包 - const oldEquip = player.equipment[slot] - if (oldEquip) { - player.inventory.push(oldEquip) + const result = equipItemUtil(player, game, selectedItem.value.uniqueId) + if (result.success) { + selectedItem.value = null } - - // 从背包中移除该物品 - const index = player.inventory.findIndex(i => - (i.uniqueId === item.uniqueId) || (i.id === item.id && i.uniqueId === undefined) - ) - if (index > -1) { - player.inventory.splice(index, 1) - } - - // 装备该物品 - player.equipment[slot] = item - - game.addLog(`装备了 ${item.name}`, 'info') - selectedItem.value = null } function unequipItem() { @@ -181,57 +153,19 @@ function unequipItem() { if (!slot) return - // 从装备槽移除 - player.equipment[slot] = null - - // 放回背包 - player.inventory.push(item) - - game.addLog(`卸下了 ${item.name}`, 'info') - selectedItem.value = null + const result = unequipItemBySlot(player, game, slot) + if (result.success) { + selectedItem.value = null + } } function useItem() { if (!selectedItem.value) return - const item = selectedItem.value - - // 检查是否是消耗品 - if (item.type !== 'consumable') return - - // 应用效果 - if (item.effect) { - if (item.effect.health) { - player.currentStats.health = Math.min( - player.currentStats.maxHealth, - player.currentStats.health + item.effect.health - ) - } - if (item.effect.stamina) { - player.currentStats.stamina = Math.min( - player.currentStats.maxStamina, - player.currentStats.stamina + item.effect.stamina - ) - } - if (item.effect.sanity) { - player.currentStats.sanity = Math.min( - player.currentStats.maxSanity, - player.currentStats.sanity + item.effect.sanity - ) - } + const result = useItemUtil(player, game, selectedItem.value.id, 1) + if (result.success) { + // 如果阅读任务,可以在这里处理 + selectedItem.value = null } - - // 从背包中移除(如果是堆叠物品,减少数量) - if (item.count > 1) { - item.count-- - } else { - const index = player.inventory.findIndex(i => i.id === item.id) - if (index > -1) { - player.inventory.splice(index, 1) - } - } - - game.addLog(`使用了 ${item.name}`, 'info') - selectedItem.value = null } function sellItem() { @@ -322,6 +256,13 @@ function sellItem() { font-size: 22rpx; margin-top: 4rpx; } +} + +.quality-badge { + font-size: 18rpx; + opacity: 0.8; + margin-left: 8rpx; +} &__equipped-badge { width: 32rpx; diff --git a/components/layout/Drawer.vue b/components/layout/Drawer.vue index 0b59987..2698109 100644 --- a/components/layout/Drawer.vue +++ b/components/layout/Drawer.vue @@ -1,5 +1,10 @@ diff --git a/config/enemies.js b/config/enemies.js index 70a867c..5944c95 100644 --- a/config/enemies.js +++ b/config/enemies.js @@ -1,45 +1,246 @@ +/** + * 敌人配置 + * Phase 4 数值调整 - 战斗数值平衡 + */ + // 敌人配置 export const ENEMY_CONFIG = { + // ===== Lv.1 敌人 ===== wild_dog: { id: 'wild_dog', name: '野狗', level: 1, baseStats: { - health: 30, + health: 35, attack: 8, defense: 2, - speed: 1.0 + speed: 1.2 }, derivedStats: { - ap: 10, // 攻击点数 - ep: 8 // 闪避点数 + ap: 8, + ep: 10 }, - expReward: 15, - skillExpReward: 10, + expReward: 20, + skillExpReward: 12, drops: [ - { itemId: 'dog_skin', chance: 0.8, count: { min: 1, max: 1 } } + { itemId: 'dog_skin', chance: 0.7, count: { min: 1, max: 1 } }, + { itemId: 'healing_herb', chance: 0.15, count: { min: 1, max: 2 } } ] }, - test_boss: { - id: 'test_boss', - name: '测试Boss', - level: 5, + rat: { + id: 'rat', + name: '老鼠', + level: 1, baseStats: { - health: 200, - attack: 25, - defense: 10, - speed: 0.8 + health: 20, + attack: 5, + defense: 1, + speed: 1.5 }, derivedStats: { - ap: 25, - ep: 15 + ap: 6, + ep: 12 }, - expReward: 150, - skillExpReward: 50, + expReward: 10, + skillExpReward: 6, drops: [ - { itemId: 'basement_key', chance: 1.0, count: { min: 1, max: 1 } } + { itemId: 'healing_herb', chance: 0.2, count: { min: 1, max: 1 } } + ] + }, + + // ===== Lv.3 敌人 ===== + wolf: { + id: 'wolf', + name: '灰狼', + level: 3, + baseStats: { + health: 50, + attack: 15, + defense: 5, + speed: 1.3 + }, + derivedStats: { + ap: 14, + ep: 13 + }, + expReward: 40, + skillExpReward: 20, + drops: [ + { itemId: 'wolf_fang', chance: 0.6, count: { min: 1, max: 2 } }, + { itemId: 'dog_skin', chance: 0.3, count: { min: 1, max: 1 } } + ] + }, + + bandit: { + id: 'bandit', + name: '流浪者', + level: 3, + baseStats: { + health: 60, + attack: 18, + defense: 8, + speed: 1.0 + }, + derivedStats: { + ap: 16, + ep: 10 + }, + expReward: 50, + skillExpReward: 25, + drops: [ + { itemId: 'copper_coin', chance: 0.8, count: { min: 3, max: 8 } }, + { itemId: 'bread', chance: 0.3, count: { min: 1, max: 2 } } + ] + }, + + // ===== Lv.5 Boss ===== + test_boss: { + id: 'test_boss', + name: '流浪狗王', + level: 5, + baseStats: { + health: 250, + attack: 30, + defense: 15, + speed: 1.1 + }, + derivedStats: { + ap: 30, + ep: 18 + }, + expReward: 200, + skillExpReward: 80, + drops: [ + { itemId: 'basement_key', chance: 1.0, count: { min: 1, max: 1 } }, + { itemId: 'dog_skin', chance: 0.5, count: { min: 2, max: 4 } } + ], + isBoss: true + }, + + // ===== Lv.7 敌人 ===== + cave_bat: { + id: 'cave_bat', + name: '洞穴蝙蝠', + level: 7, + baseStats: { + health: 40, + attack: 25, + defense: 3, + speed: 1.8 + }, + derivedStats: { + ap: 22, + ep: 24 + }, + expReward: 70, + skillExpReward: 35, + drops: [ + { itemId: 'bat_wing', chance: 0.5, count: { min: 1, max: 2 } } + ] + }, + + // ===== Lv.10 Boss ===== + cave_boss: { + id: 'cave_boss', + name: '洞穴领主', + level: 10, + baseStats: { + health: 500, + attack: 50, + defense: 25, + speed: 0.9 + }, + derivedStats: { + ap: 50, + ep: 25 + }, + expReward: 500, + skillExpReward: 200, + drops: [ + { itemId: 'rare_gem', chance: 0.8, count: { min: 1, max: 1 } }, + { itemId: 'iron_sword', chance: 0.3, count: { min: 1, max: 1 } } ], isBoss: true } } + +/** + * 根据区域获取可用的敌人列表 + * @param {String} locationId - 位置ID + * @returns {Array} 敌人ID列表 + */ +export function getEnemiesForLocation(locationId) { + const locationEnemies = { + 'wild1': ['wild_dog', 'rat'], + 'wild2': ['wolf', 'bandit'], + 'wild3': ['cave_bat', 'wolf'], + 'boss_lair': ['test_boss'], + 'cave_depth': ['cave_bat', 'cave_boss'] + } + + return locationEnemies[locationId] || [] +} + +/** + * 获取位置的敌人完整配置 + * @param {String} locationId - 位置ID + * @returns {Array} 敌人配置对象列表 + */ +export function getEnemyConfigsForLocation(locationId) { + const enemyIds = getEnemiesForLocation(locationId) + return enemyIds.map(id => ENEMY_CONFIG[id]).filter(Boolean) +} + +/** + * 根据等级获取推荐的敌人 + * @param {Number} playerLevel - 玩家等级 + * @returns {Array} 敌人ID列表 + */ +export function getRecommendedEnemies(playerLevel) { + const enemies = [] + + for (const [id, enemy] of Object.entries(ENEMY_CONFIG)) { + const levelDiff = Math.abs(enemy.level - playerLevel) + // 推荐等级差在3以内的敌人 + if (levelDiff <= 3 && !enemy.isBoss) { + enemies.push({ id, levelDiff, enemy }) + } + } + + // 按等级差排序 + enemies.sort((a, b) => a.levelDiff - b.levelDiff) + return enemies.map(e => e.id) +} + +/** + * 随机获取位置的一个敌人 + * @param {String} locationId - 位置ID + * @returns {Object|null} 敌人配置 + */ +export function getRandomEnemyForLocation(locationId) { + const enemyIds = getEnemiesForLocation(locationId) + if (enemyIds.length === 0) return null + + const randomIndex = Math.floor(Math.random() * enemyIds.length) + const enemyId = enemyIds[randomIndex] + return ENEMY_CONFIG[enemyId] || null +} + +/** + * 获取所有敌人列表 + * @returns {Array} 所有敌人ID + */ +export function getAllEnemyIds() { + return Object.keys(ENEMY_CONFIG) +} + +/** + * 获取Boss列表 + * @returns {Array} Boss敌人ID + */ +export function getBossIds() { + return Object.entries(ENEMY_CONFIG) + .filter(([_, config]) => config.isBoss) + .map(([id, _]) => id) +} diff --git a/config/events.js b/config/events.js index 47d940c..cec0b23 100644 --- a/config/events.js +++ b/config/events.js @@ -3,6 +3,8 @@ * Phase 6 核心系统实现 */ +import { LOCATION_CONFIG } from './locations.js' + /** * 事件配置 * 每个事件包含: @@ -236,9 +238,56 @@ export const EVENT_CONFIG = { title: '新区域', textTemplate: '解锁了新区域:{areaName}\n\n{description}', choices: [ - { text: '前往探索', next: null, action: 'teleport', actionData: { locationId: '{areaId}' } }, { text: '稍后再说', next: null } ] + }, + + // 探索事件 + explore_nothing: { + id: 'explore_nothing', + type: 'info', + title: '探索结果', + text: '你仔细搜索了这片区域,但没有发现任何有用的东西。', + choices: [ + { text: '继续', next: null } + ] + }, + explore_find_herb: { + id: 'explore_find_herb', + type: 'reward', + title: '探索发现', + text: '你在草丛中发现了一些草药!', + choices: [ + { text: '收下', next: null, action: 'give_item', actionData: { itemId: 'healing_herb', count: { min: 1, max: 3 } } } + ] + }, + explore_find_coin: { + id: 'explore_find_coin', + type: 'reward', + title: '探索发现', + textTemplate: '你在角落里发现了一些铜币!\n\n获得了 {amount} 铜币', + choices: [ + { text: '太好了', next: null } + ] + }, + explore_find_trash: { + id: 'explore_find_trash', + type: 'info', + title: '探索发现', + text: '你只找到了一些垃圾。', + choices: [ + { text: '离开', next: null } + ] + }, + explore_encounter: { + id: 'explore_encounter', + type: 'combat', + title: '遭遇', + textTemplate: '探索时突然遇到了{enemyName}!', + choices: [ + { text: '迎战', next: null }, + { text: '逃跑', next: null, action: 'flee' } + ] } } diff --git a/config/items.js b/config/items.js index 0517182..1c02938 100644 --- a/config/items.js +++ b/config/items.js @@ -1,21 +1,172 @@ // 物品配置 +// Phase 5 内容扩展 - 更多物品 export const ITEM_CONFIG = { - // 武器 + // ===== 武器 ===== wooden_stick: { id: 'wooden_stick', name: '木棍', type: 'weapon', subtype: 'one_handed', icon: '🪵', - baseValue: 10, // 基础价值(铜币) + baseValue: 10, baseDamage: 5, attackSpeed: 1.0, - quality: 100, // 默认品质 + quality: 100, unlockSkill: 'stick_mastery', description: '一根粗糙的木棍,至少比空手强。' }, - // 消耗品 + rusty_sword: { + id: 'rusty_sword', + name: '生锈铁剑', + type: 'weapon', + subtype: 'sword', + icon: '🗡️', + baseValue: 100, + baseDamage: 12, + attackSpeed: 1.1, + quality: 50, + unlockSkill: 'sword_mastery', + stats: { critRate: 2 }, + description: '一把生锈的铁剑,虽然旧了但依然锋利。' + }, + + iron_sword: { + id: 'iron_sword', + name: '铁剑', + type: 'weapon', + subtype: 'sword', + icon: '⚔️', + baseValue: 500, + baseDamage: 25, + attackSpeed: 1.2, + quality: 100, + unlockSkill: 'sword_mastery', + stats: { critRate: 5 }, + description: '一把精工打造的铁剑。' + }, + + wooden_club: { + id: 'wooden_club', + name: '木棒', + type: 'weapon', + subtype: 'blunt', + icon: '🏏', + baseValue: 30, + baseDamage: 8, + attackSpeed: 0.9, + quality: 80, + unlockSkill: 'blunt_mastery', + description: '一根粗大的木棒,攻击力强但速度慢。' + }, + + stone_axe: { + id: 'stone_axe', + name: '石斧', + type: 'weapon', + subtype: 'axe', + icon: '🪓', + baseValue: 80, + baseDamage: 18, + attackSpeed: 0.8, + quality: 60, + unlockSkill: 'axe_mastery', + stats: { critRate: 3 }, + description: '用石头打磨成的斧头,笨重但有效。' + }, + + hunter_bow: { + id: 'hunter_bow', + name: '猎弓', + type: 'weapon', + subtype: 'ranged', + icon: '🏹', + baseValue: 200, + baseDamage: 15, + attackSpeed: 1.3, + quality: 90, + unlockSkill: 'archery', + stats: { accuracy: 10 }, + description: '猎人使用的弓,可以远程攻击。' + }, + + // ===== 防具 ===== + rag_armor: { + id: 'rag_armor', + name: '破布护甲', + type: 'armor', + subtype: 'light', + icon: '👕', + baseValue: 20, + defense: 3, + quality: 50, + description: '用破布拼凑成的简易护甲。' + }, + + leather_armor: { + id: 'leather_armor', + name: '皮甲', + type: 'armor', + subtype: 'light', + icon: '🦺', + baseValue: 150, + defense: 8, + quality: 100, + stats: { evasion: 5 }, + description: '用兽皮制成的轻甲,提供基础保护。' + }, + + iron_armor: { + id: 'iron_armor', + name: '铁甲', + type: 'armor', + subtype: 'heavy', + icon: '🛡️', + baseValue: 800, + defense: 20, + quality: 100, + stats: { maxStamina: -10 }, // 重量影响耐力 + description: '铁制重甲,防御力强但会影响行动。' + }, + + // ===== 盾牌 ===== + wooden_shield: { + id: 'wooden_shield', + name: '木盾', + type: 'shield', + icon: '🛡️', + baseValue: 50, + defense: 5, + blockRate: 10, + quality: 80, + description: '简单的木制盾牌。' + }, + + iron_shield: { + id: 'iron_shield', + name: '铁盾', + type: 'shield', + icon: '🛡️', + baseValue: 300, + defense: 12, + blockRate: 20, + quality: 100, + description: '坚固的铁制盾牌。' + }, + + // ===== 饰品 ===== + lucky_ring: { + id: 'lucky_ring', + name: '幸运戒指', + type: 'accessory', + icon: '💍', + baseValue: 200, + quality: 100, + stats: { critRate: 5, fleeRate: 5 }, + description: '一枚带来幸运的戒指。' + }, + + // ===== 消耗品 - 食物 ===== bread: { id: 'bread', name: '面包', @@ -24,13 +175,63 @@ export const ITEM_CONFIG = { icon: '🍞', baseValue: 10, effect: { - stamina: 20 + stamina: 20, + health: 5 }, - description: '普通的面包,可以恢复耐力。', + description: '普通的面包,可以恢复耐力和少量生命。', stackable: true, maxStack: 99 }, + meat: { + id: 'meat', + name: '肉干', + type: 'consumable', + subtype: 'food', + icon: '🥩', + baseValue: 15, + effect: { + stamina: 35, + health: 10 + }, + description: '风干的肉,营养丰富。', + stackable: true, + maxStack: 50 + }, + + cooked_meat: { + id: 'cooked_meat', + name: '烤肉', + type: 'consumable', + subtype: 'food', + icon: '🍖', + baseValue: 25, + effect: { + stamina: 50, + health: 20 + }, + description: '烤制的肉,美味又营养。', + stackable: true, + maxStack: 50 + }, + + fresh_water: { + id: 'fresh_water', + name: '清水', + type: 'consumable', + subtype: 'drink', + icon: '💧', + baseValue: 5, + effect: { + stamina: 10, + sanity: 5 + }, + description: '干净的清水,解渴提神。', + stackable: true, + maxStack: 99 + }, + + // ===== 消耗品 - 药品 ===== healing_herb: { id: 'healing_herb', name: '草药', @@ -39,30 +240,123 @@ export const ITEM_CONFIG = { icon: '🌿', baseValue: 15, effect: { - health: 15 + health: 20 }, description: '常见的治疗草药,可以恢复生命值。', stackable: true, maxStack: 99 }, - // 书籍 + bandage: { + id: 'bandage', + name: '绷带', + type: 'consumable', + subtype: 'medicine', + icon: '🩹', + baseValue: 20, + effect: { + health: 30, + stamina: 5 + }, + description: '急救用的绷带,可以止血。', + stackable: true, + maxStack: 50 + }, + + health_potion_small: { + id: 'health_potion_small', + name: '小治疗药水', + type: 'consumable', + subtype: 'medicine', + icon: '🧪', + baseValue: 50, + effect: { + health: 50 + }, + description: '小瓶治疗药水,快速恢复生命值。', + stackable: true, + maxStack: 20 + }, + + health_potion: { + id: 'health_potion', + name: '治疗药水', + type: 'consumable', + subtype: 'medicine', + icon: '🧪', + baseValue: 150, + effect: { + health: 100 + }, + description: '治疗药水,大幅恢复生命值。', + stackable: true, + maxStack: 10 + }, + + // ===== 书籍 ===== old_book: { id: 'old_book', name: '破旧书籍', type: 'book', icon: '📖', baseValue: 50, - readingTime: 60, // 秒 + readingTime: 60, expReward: { reading: 10 }, completionBonus: null, description: '一本破旧的书籍,记录着一些基础知识。', - consumable: false // 书籍不消耗 + consumable: false }, - // 素材 + survival_guide: { + id: 'survival_guide', + name: '生存指南', + type: 'book', + icon: '📕', + baseValue: 100, + readingTime: 120, + expReward: { + reading: 25, + survival_instinct: 5 + }, + completionBonus: { maxStamina: 10 }, + description: '荒野生存技巧指南。', + consumable: false + }, + + combat_manual: { + id: 'combat_manual', + name: '战斗手册', + type: 'book', + icon: '📗', + baseValue: 150, + readingTime: 180, + expReward: { + reading: 30 + }, + completionBonus: { critRate: 3 }, + description: '记录战斗技巧的手册。', + consumable: false + }, + + herbalism_book: { + id: 'herbalism_book', + name: '草药图鉴', + type: 'book', + icon: '📙', + baseValue: 120, + readingTime: 150, + expReward: { + reading: 20, + herbalism: 10 + }, + completionBonus: null, + description: '识别和采集草药的图鉴。', + consumable: false + }, + + // ===== 素材 ===== dog_skin: { id: 'dog_skin', name: '狗皮', @@ -74,7 +368,74 @@ export const ITEM_CONFIG = { maxStack: 99 }, - // 关键道具 + wolf_fang: { + id: 'wolf_fang', + name: '狼牙', + type: 'material', + icon: '🦷', + baseValue: 20, + description: '锋利的狼牙,可用于制作武器。', + stackable: true, + maxStack: 99 + }, + + bat_wing: { + id: 'bat_wing', + name: '蝙蝠翼', + type: 'material', + icon: '🦇', + baseValue: 15, + description: '蝙蝠的翅膀,有特殊用途。', + stackable: true, + maxStack: 99 + }, + + leather: { + id: 'leather', + name: '皮革', + type: 'material', + icon: '🟤', + baseValue: 30, + description: '加工过的兽皮,可用于制作装备。', + stackable: true, + maxStack: 99 + }, + + iron_ore: { + id: 'iron_ore', + name: '铁矿石', + type: 'material', + icon: '⛰️', + baseValue: 50, + description: '含铁的矿石,可以提炼金属。', + stackable: true, + maxStack: 99 + }, + + rare_gem: { + id: 'rare_gem', + name: '稀有宝石', + type: 'material', + icon: '💎', + baseValue: 500, + description: '闪闪发光的宝石,价值不菲。', + stackable: true, + maxStack: 10 + }, + + // ===== 货币 ===== + copper_coin: { + id: 'copper_coin', + name: '铜币', + type: 'currency', + icon: '🪙', + baseValue: 1, + description: '通用的货币单位。', + stackable: true, + maxStack: 9999 + }, + + // ===== 关键道具 ===== basement_key: { id: 'basement_key', name: '地下室钥匙', @@ -83,5 +444,71 @@ export const ITEM_CONFIG = { baseValue: 0, description: '一把生锈的钥匙,上面刻着「B」字母。', stackable: false + }, + + cave_key: { + id: 'cave_key', + name: '洞穴钥匙', + type: 'key', + icon: '🗝️', + baseValue: 0, + description: '开启深处洞穴的钥匙。', + stackable: false + }, + + mystic_key: { + id: 'mystic_key', + name: '神秘钥匙', + type: 'key', + icon: '🔮', + baseValue: 1000, + description: '一把散发着神秘光芒的钥匙,似乎能打开某扇重要的门。', + stackable: false, + keyItem: true + }, + + // ===== 特殊物品 ===== + bomb: { + id: 'bomb', + name: '炸弹', + type: 'special', + subtype: 'explosive', + icon: '💣', + baseValue: 100, + description: '可以造成范围伤害的爆炸物,在战斗中特别有效。', + stackable: true, + maxStack: 10, + effect: { + damage: 50, + radius: 1 + }, + consumable: true + }, + + bible: { + id: 'bible', + name: '圣经', + type: 'special', + icon: '📿', + baseValue: 0, + description: '一本神圣的书籍,可以用来祈祷。', + stackable: false, + effect: { sanity: 10 } } } + +/** + * 获取物品商店分类 + * @returns {Object} 分类列表 + */ +export const ITEM_CATEGORIES = { + weapon: { id: 'weapon', name: '武器', icon: '⚔️' }, + armor: { id: 'armor', name: '防具', icon: '🛡️' }, + shield: { id: 'shield', name: '盾牌', icon: '🛡️' }, + accessory: { id: 'accessory', name: '饰品', icon: '💍' }, + consumable: { id: 'consumable', name: '消耗品', icon: '🧪' }, + book: { id: 'book', name: '书籍', icon: '📖' }, + material: { id: 'material', name: '素材', icon: '📦' }, + key: { id: 'key', name: '钥匙', icon: '🔑' }, + special: { id: 'special', name: '特殊', icon: '✨' } +} diff --git a/config/locations.js b/config/locations.js index 78eea87..f9ba6d1 100644 --- a/config/locations.js +++ b/config/locations.js @@ -8,7 +8,7 @@ export const LOCATION_CONFIG = { description: '一个临时的幸存者营地,相对安全。', connections: ['market', 'blackmarket', 'wild1'], npcs: ['injured_adventurer'], - activities: ['rest', 'talk', 'trade'] + activities: ['rest', 'talk', 'trade', 'crafting'] }, market: { diff --git a/config/skills.js b/config/skills.js index e927d67..5165f40 100644 --- a/config/skills.js +++ b/config/skills.js @@ -49,5 +49,67 @@ export const SKILL_CONFIG = { 10: { desc: '黑暗惩罚-25%', effect: { darkPenaltyReduce: 25 } } }, unlockCondition: { location: 'basement' } + }, + + // ===== 制造技能 ===== + crafting: { + id: 'crafting', + name: '制造', + type: 'life', + category: 'crafting', + icon: '🔨', + maxLevel: 20, + expPerLevel: (level) => level * 80, + parentSkill: null, + milestones: { + 1: { desc: '解锁基础制造配方', effect: {} }, + 3: { desc: '制造时间-10%', effect: { craftingSpeed: 0.1 } }, + 5: { desc: '所有制造成功率+5%', effect: { craftingSuccessRate: 5 } }, + 10: { desc: '制造时间-25%', effect: { craftingSpeed: 0.25 } }, + 15: { desc: '所有制造成功率+10%', effect: { craftingSuccessRate: 10 } }, + 20: { desc: '制造品质+10', effect: { craftingQuality: 10 } } + }, + unlockCondition: null + }, + + blacksmith: { + id: 'blacksmith', + name: '锻造', + type: 'life', + category: 'crafting', + icon: '⚒️', + maxLevel: 15, + expPerLevel: (level) => level * 120, + parentSkill: 'crafting', + milestones: { + 1: { desc: '解锁武器锻造', effect: {} }, + 5: { desc: '武器品质+15', effect: { weaponQuality: 15 } }, + 10: { desc: '防具品质+15', effect: { armorQuality: 15 } }, + 15: { desc: '所有锻造成功率+15%', effect: { smithingSuccessRate: 15 } } + }, + unlockCondition: { + type: 'skill', + skillId: 'crafting', + level: 5 + } + }, + + herbalism: { + id: 'herbalism', + name: '草药学', + type: 'life', + category: 'crafting', + icon: '🌿', + maxLevel: 15, + expPerLevel: (level) => level * 60, + parentSkill: null, + milestones: { + 1: { desc: '解锁药水制作', effect: {} }, + 3: { desc: '药水效果+20%', effect: { potionEffect: 1.2 } }, + 5: { desc: '解锁高级药水', effect: {} }, + 10: { desc: '药水效果+50%', effect: { potionEffect: 1.5 } }, + 15: { desc: '所有制药成功率+20%', effect: { herbingSuccessRate: 20 } } + }, + unlockCondition: null } } diff --git a/pages/index/index.vue b/pages/index/index.vue index 09556d7..b24cb3d 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -43,6 +43,14 @@ > + + + + + @@ -58,6 +66,7 @@ import LogPanel from '@/components/panels/LogPanel.vue' import InventoryDrawer from '@/components/drawers/InventoryDrawer.vue' import EventDrawer from '@/components/drawers/EventDrawer.vue' import ShopDrawer from '@/components/drawers/ShopDrawer.vue' +import CraftingDrawer from '@/components/drawers/CraftingDrawer.vue' const game = useGameStore() const player = usePlayerStore() diff --git a/store/game.js b/store/game.js index 2aa736d..7d1bffc 100644 --- a/store/game.js +++ b/store/game.js @@ -9,7 +9,8 @@ export const useGameStore = defineStore('game', () => { const drawerState = ref({ inventory: false, event: false, - shop: false + shop: false, + crafting: false }) // 日志 diff --git a/uni.scss b/uni.scss index 90c4383..7867b8a 100644 --- a/uni.scss +++ b/uni.scss @@ -92,6 +92,7 @@ $accent: #4ecdc4; $danger: #ff6b6b; $warning: #ffe66d; $success: #4ade80; +$info: #60a5fa; /* 品质颜色 */ $quality-trash: #808080; diff --git a/utils/combatSystem.js b/utils/combatSystem.js index 6e0843a..bf4cad3 100644 --- a/utils/combatSystem.js +++ b/utils/combatSystem.js @@ -539,23 +539,6 @@ export function initCombat(enemyId, enemyConfig, environment = 'normal') { } } -/** - * 计算姿态切换后的攻击速度修正 - * @param {String} stance - 姿态 - * @param {Number} baseSpeed - 基础攻击速度 - * @returns {Number} 修正后的攻击速度 - */ -export function getAttackSpeed(stance, baseSpeed = 1.0) { - const speedModifiers = { - attack: 1.3, // 攻击姿态: 速度 +30% - defense: 0.9, // 防御姿态: 速度 -10% - balance: 1.0, - rapid: 2.0, // 快速打击: 速度 +100% - heavy: 0.5 // 重击: 速度 -50% - } - return baseSpeed * (speedModifiers[stance] || 1.0) -} - /** * 获取姿态显示名称 * @param {String} stance - 姿态 @@ -572,6 +555,23 @@ export function getStanceDisplayName(stance) { return names[stance] || '平衡' } +/** + * 获取攻击速度修正 + * @param {String} stance - 姿态 + * @param {Number} baseSpeed - 基础攻击速度 + * @returns {Number} 修正后的攻击速度 + */ +export function getAttackSpeed(stance, baseSpeed = 1.0) { + const speedModifiers = { + attack: 1.3, // 攻击姿态: 速度 +30% + defense: 0.9, // 防御姿态: 速度 -10% + balance: 1.0, + rapid: 2.0, // 快速打击: 速度 +100% + heavy: 0.5 // 重击: 速度 -50% + } + return baseSpeed * (speedModifiers[stance] || 1.0) +} + /** * 获取环境类型 * @param {String} locationId - 位置ID diff --git a/utils/environmentSystem.js b/utils/environmentSystem.js index c5917ec..64c512c 100644 --- a/utils/environmentSystem.js +++ b/utils/environmentSystem.js @@ -5,6 +5,7 @@ import { LOCATION_CONFIG } from '@/config/locations.js' import { SKILL_CONFIG } from '@/config/skills.js' +import { applyMilestoneBonus } from './skillSystem.js' /** * 环境类型枚举 @@ -198,7 +199,10 @@ export function processEnvironmentExp(gameStore, playerStore, locationId, deltaT } // 检查里程碑奖励(需要调用技能系统) - // TODO: 调用 applyMilestoneBonus + const newLevel = playerStore.skills[adaptSkillId].level + if (SKILL_CONFIG[adaptSkillId].milestones && SKILL_CONFIG[adaptSkillId].milestones[newLevel]) { + applyMilestoneBonus(playerStore, adaptSkillId, newLevel) + } } return { [adaptSkillId]: finalExp } diff --git a/utils/eventSystem.js b/utils/eventSystem.js index cffa030..9323a66 100644 --- a/utils/eventSystem.js +++ b/utils/eventSystem.js @@ -6,8 +6,11 @@ import { NPC_CONFIG } from '@/config/npcs.js' import { EVENT_CONFIG } from '@/config/events.js' import { LOCATION_CONFIG } from '@/config/locations.js' +import { SKILL_CONFIG } from '@/config/skills.js' +import { ENEMY_CONFIG } from '@/config/enemies.js' import { unlockSkill } from './skillSystem.js' import { addItemToInventory } from './itemSystem.js' +import { initCombat, getEnvironmentType } from './combatSystem.js' /** * 检查并触发事件 @@ -401,11 +404,16 @@ export function processDialogueAction(gameStore, playerStore, action, actionData // 开始战斗 if (actionData.enemyId) { gameStore.drawerState.event = false - // TODO: 启动战斗 + // 战斗已在探索事件中初始化,这里只是关闭事件抽屉 return { success: true, message: '开始战斗', closeEvent: true } } return { success: false, message: '敌人参数错误', closeEvent: false } + case 'flee': + // 逃跑 + gameStore.drawerState.event = false + return tryFlee(gameStore, playerStore) + case 'close': // 直接关闭 return { success: true, message: '', closeEvent: true } @@ -547,3 +555,108 @@ export function checkScheduledEvents(gameStore, playerStore) { export function clearScheduledEvents(gameStore) { gameStore.scheduledEvents = [] } + +/** + * 触发探索事件 + * @param {Object} gameStore - 游戏Store + * @param {Object} playerStore - 玩家Store + * @returns {Object} 探索结果 + */ +export function triggerExploreEvent(gameStore, playerStore) { + const location = LOCATION_CONFIG[playerStore.currentLocation] + if (!location) { + return { success: false, message: '当前位置错误' } + } + + // 检查当前位置是否有敌人配置 + const availableEnemies = location.enemies || [] + if (availableEnemies.length === 0) { + return { success: false, message: '这里没什么可探索的' } + } + + // 随机决定探索结果 + const roll = Math.random() + + // 40% 没发现 + if (roll < 0.4) { + triggerEvent(gameStore, 'explore_nothing', {}) + return { success: true, type: 'nothing' } + } + + // 25% 发现草药 + if (roll < 0.65) { + const herbCount = Math.floor(Math.random() * 3) + 1 + const result = addItemToInventory(playerStore, 'healing_herb', herbCount, 100) + if (result.success) { + triggerEvent(gameStore, 'explore_find_herb', {}) + return { success: true, type: 'find', item: 'healing_herb', count: herbCount } + } + } + + // 20% 发现铜币 (1-5枚) + if (roll < 0.85) { + const coinAmount = Math.floor(Math.random() * 5) + 1 + playerStore.currency.copper += coinAmount + if (gameStore.addLog) { + gameStore.addLog(`探索发现了 ${coinAmount} 铜币`, 'reward') + } + triggerEvent(gameStore, 'explore_find_coin', { amount: coinAmount }) + return { success: true, type: 'coin', amount: coinAmount } + } + + // 15% 遭遇敌人 + const enemyId = availableEnemies[Math.floor(Math.random() * availableEnemies.length)] + const enemyConfig = ENEMY_CONFIG[enemyId] + + if (enemyConfig) { + const environment = getEnvironmentType(playerStore.currentLocation) + gameStore.combatState = initCombat(enemyId, enemyConfig, environment) + gameStore.inCombat = true + + triggerEvent(gameStore, 'explore_encounter', { + enemyName: enemyConfig.name, + enemyId + }) + + return { success: true, type: 'combat', enemyId } + } + + // 默认:没发现 + triggerEvent(gameStore, 'explore_nothing', {}) + return { success: true, type: 'nothing' } +} + +/** + * 尝试逃跑 + * @param {Object} gameStore - 游戏Store + * @param {Object} playerStore - 玩家Store + * @returns {Object} 逃跑结果 + */ +export function tryFlee(gameStore, playerStore) { + // 基础逃跑成功率50% + const baseFleeChance = 0.5 + + // 敏捷影响逃跑率 + const agilityBonus = (playerStore.baseStats?.dexterity || 0) * 0.01 + + // 装备加成 + const equipmentBonus = (playerStore.globalBonus?.fleeRate || 0) + + const fleeChance = Math.min(0.9, baseFleeChance + agilityBonus + equipmentBonus) + + if (Math.random() < fleeChance) { + // 逃跑成功 + gameStore.inCombat = false + gameStore.combatState = null + if (gameStore.addLog) { + gameStore.addLog('成功逃跑了!', 'system') + } + return { success: true, message: '逃跑成功' } + } else { + // 逃跑失败,敌人攻击 + if (gameStore.addLog) { + gameStore.addLog('逃跑失败!', 'combat') + } + return { success: false, message: '逃跑失败' } + } +} diff --git a/utils/gameLoop.js b/utils/gameLoop.js index bf08c4d..08a491a 100644 --- a/utils/gameLoop.js +++ b/utils/gameLoop.js @@ -13,6 +13,7 @@ import { processEnvironmentExp, processEnvironmentEffects } from './environmentS import { checkScheduledEvents } from './eventSystem.js' import { addSkillExp } from './skillSystem.js' import { addItemToInventory } from './itemSystem.js' +import { addExp, calculateCombatExp } from './levelingSystem.js' // 游戏循环定时器 let gameLoopInterval = null @@ -196,24 +197,14 @@ export function handleCombatVictory(gameStore, playerStore, combatResult) { // 添加日志 gameStore.addLog(`战胜了 ${enemy.name}!`, 'reward') - // 给予经验值 + // 给予经验值 (使用新的等级系统) if (enemy.expReward) { - playerStore.level.exp += enemy.expReward - - // 检查升级 - const maxExp = playerStore.level.maxExp - if (playerStore.level.exp >= maxExp) { - playerStore.level.current++ - playerStore.level.exp -= maxExp - playerStore.level.maxExp = Math.floor(playerStore.level.maxExp * 1.5) - - gameStore.addLog(`升级了! 等级: ${playerStore.level.current}`, 'reward') - - // 升级恢复状态 - playerStore.currentStats.health = playerStore.currentStats.maxHealth - playerStore.currentStats.stamina = playerStore.currentStats.maxStamina - playerStore.currentStats.sanity = playerStore.currentStats.maxSanity - } + const adjustedExp = calculateCombatExp( + enemy.level || 1, + playerStore.level.current, + enemy.expReward + ) + addExp(playerStore, gameStore, adjustedExp) } // 给予武器技能经验奖励 @@ -374,6 +365,24 @@ function processDrops(gameStore, playerStore, drops) { * @param {Object} rewards - 奖励 */ function handleTaskCompletion(gameStore, playerStore, task, rewards) { + // 处理制造任务 + if (task.type === 'crafting') { + if (rewards.success !== false && rewards.craftedItem) { + // 制造成功,添加物品到背包 + const { completeCrafting } = require('./craftingSystem.js') + const result = completeCrafting(gameStore, playerStore, task, rewards) + if (result.success) { + gameStore.addLog(result.message, 'reward') + } else if (result.failed) { + gameStore.addLog(result.message, 'warning') + } + } else if (rewards.success === false) { + // 制造失败 + gameStore.addLog('制造失败,材料已消耗', 'warning') + } + return + } + // 应用货币奖励 if (rewards.currency) { playerStore.currency.copper += rewards.currency @@ -381,7 +390,7 @@ function handleTaskCompletion(gameStore, playerStore, task, rewards) { // 应用技能经验 for (const [skillId, exp] of Object.entries(rewards)) { - if (skillId !== 'currency' && skillId !== 'completionBonus' && typeof exp === 'number') { + if (skillId !== 'currency' && skillId !== 'completionBonus' && skillId !== 'craftedItem' && skillId !== 'craftedCount' && typeof exp === 'number') { addSkillExp(playerStore, skillId, exp) } } @@ -392,7 +401,8 @@ function handleTaskCompletion(gameStore, playerStore, task, rewards) { resting: '休息', training: '训练', working: '工作', - praying: '祈祷' + praying: '祈祷', + crafting: '制造' } gameStore.addLog(`${taskNames[task.type]}任务完成`, 'reward') } diff --git a/utils/levelingSystem.js b/utils/levelingSystem.js new file mode 100644 index 0000000..50d8806 --- /dev/null +++ b/utils/levelingSystem.js @@ -0,0 +1,287 @@ +/** + * 等级系统 - 经验曲线、升级计算 + * Phase 4 数值调整 - 等级经验曲线 + */ + +/** + * 经验曲线类型 + */ +export const EXP_CURVE_TYPES = { + LINEAR: 'linear', // 线性增长: base * level + QUADRATIC: 'quadratic', // 二次增长: base * level^2 + EXPONENTIAL: 'exponential', // 指数增长: base * multiplier^level + HYBRID: 'hybrid' // 混合曲线: 更平滑的体验 +} + +/** + * 等级配置 + */ +export const LEVEL_CONFIG = { + // 经验曲线类型 + expCurveType: EXP_CURVE_TYPES.HYBRID, + + // 基础经验值 (1级升级所需) + baseExp: 100, + + // 经验增长倍率 (每级增长) + expMultiplier: 1.15, + + // 最大等级 + maxLevel: 50, + + // 等级属性加成 (每级获得的属性点) + statPointsPerLevel: 3, + + // 初始属性 + baseStats: { + strength: 10, + agility: 8, + dexterity: 8, + intuition: 10, + vitality: 10 + } +} + +/** + * 计算指定等级所需的总经验 + * @param {Number} level - 目标等级 + * @returns {Number} 所需经验 + */ +export function getExpForLevel(level) { + if (level <= 1) return 0 + + const { expCurveType, baseExp, expMultiplier } = LEVEL_CONFIG + + switch (expCurveType) { + case EXP_CURVE_TYPES.LINEAR: + // 线性: 100, 200, 300, 400... + return baseExp * (level - 1) + + case EXP_CURVE_TYPES.QUADRATIC: + // 二次: 100, 400, 900, 1600... + return baseExp * Math.pow(level - 1, 2) + + case EXP_CURVE_TYPES.EXPONENTIAL: + // 指数: 100, 115, 132, 152... + return baseExp * Math.pow(expMultiplier, level - 1) + + case EXP_CURVE_TYPES.HYBRID: + default: + // 混合曲线 - 平滑的增长体验 + // 公式: base * (1.15 ^ (level - 1)) * level / 2 + // 结果: Lv2=100, Lv5=700, Lv10=2800, Lv20=15000, Lv30=55000, Lv50=250000 + return Math.floor(baseExp * Math.pow(expMultiplier, level - 2) * (level - 1) * 0.8) + } +} + +/** + * 计算从当前等级到下一等级所需的经验 + * @param {Number} currentLevel - 当前等级 + * @returns {Number} 升级所需经验 + */ +export function getExpToNextLevel(currentLevel) { + if (currentLevel >= LEVEL_CONFIG.maxLevel) { + return 0 // 已达最高等级 + } + return getExpForLevel(currentLevel + 1) - getExpForLevel(currentLevel) +} + +/** + * 检查并处理升级 + * @param {Object} playerStore - 玩家Store + * @param {Object} gameStore - 游戏Store + * @returns {Object} { leveledUp: boolean, newLevel: number, levelsGained: number } + */ +export function checkLevelUp(playerStore, gameStore) { + const currentLevel = playerStore.level.current + if (currentLevel >= LEVEL_CONFIG.maxLevel) { + return { leveledUp: false, newLevel: currentLevel, levelsGained: 0 } + } + + let levelsGained = 0 + let newLevel = currentLevel + let remainingExp = playerStore.level.exp + + // 连续升级检查(支持一次获得大量经验连升多级) + while (newLevel < LEVEL_CONFIG.maxLevel) { + const expNeeded = getExpToNextLevel(newLevel) + if (remainingExp >= expNeeded) { + remainingExp -= expNeeded + newLevel++ + levelsGained++ + } else { + break + } + } + + if (levelsGained > 0) { + // 应用升级 + const oldLevel = playerStore.level.current + playerStore.level.current = newLevel + playerStore.level.exp = remainingExp + playerStore.level.maxExp = getExpToNextLevel(newLevel) + + // 应用等级属性加成 + applyLevelStats(playerStore, oldLevel, newLevel) + + // 记录日志 + if (levelsGained === 1) { + gameStore?.addLog(`升级了! 等级: ${newLevel}`, 'reward') + } else { + gameStore?.addLog(`连升${levelsGained}级! 等级: ${newLevel}`, 'reward') + } + + // 升级恢复状态 + playerStore.currentStats.health = playerStore.currentStats.maxHealth + playerStore.currentStats.stamina = playerStore.currentStats.maxStamina + playerStore.currentStats.sanity = playerStore.currentStats.maxSanity + + return { leveledUp: true, newLevel, levelsGained } + } + + return { leveledUp: false, newLevel: currentLevel, levelsGained: 0 } +} + +/** + * 应用等级属性加成 + * @param {Object} playerStore - 玩家Store + * @param {Number} oldLevel - 旧等级 + * @param {Number} newLevel - 新等级 + */ +function applyLevelStats(playerStore, oldLevel, newLevel) { + const levelsGained = newLevel - oldLevel + const statPoints = levelsGained * LEVEL_CONFIG.statPointsPerLevel + + // 自动分配属性点 (简化版: 均匀分配) + // 实际游戏中可以让玩家手动选择 + const stats = ['strength', 'agility', 'dexterity', 'intuition', 'vitality'] + const pointsPerStat = Math.floor(statPoints / stats.length) + const remainder = statPoints % stats.length + + stats.forEach((stat, index) => { + playerStore.baseStats[stat] += pointsPerStat + if (index < remainder) { + playerStore.baseStats[stat] += 1 + } + }) + + // 更新生命值上限 (体质影响HP) + const hpPerVitality = 10 + const oldMaxHp = playerStore.currentStats.maxHealth + const newMaxHp = 100 + (playerStore.baseStats.vitality - 10) * hpPerVitality + + if (newMaxHp !== oldMaxHp) { + playerStore.currentStats.maxHealth = newMaxHp + // 按比例恢复HP + const hpRatio = playerStore.currentStats.health / oldMaxHp + playerStore.currentStats.health = Math.floor(newMaxHp * hpRatio) + } +} + +/** + * 添加经验值 + * @param {Object} playerStore - 玩家Store + * @param {Object} gameStore - 游戏Store + * @param {Number} exp - 获得的经验值 + * @returns {Object} { leveledUp: boolean, newLevel: number } + */ +export function addExp(playerStore, gameStore, exp) { + playerStore.level.exp += exp + return checkLevelUp(playerStore, gameStore) +} + +/** + * 获取等级信息 + * @param {Number} level - 等级 + * @returns {Object} 等级信息 + */ +export function getLevelInfo(level) { + const totalExp = getExpForLevel(level) + const toNext = getExpToNextLevel(level) + const isMaxLevel = level >= LEVEL_CONFIG.maxLevel + + return { + level, + totalExp, + toNext, + isMaxLevel, + maxLevel: LEVEL_CONFIG.maxLevel + } +} + +/** + * 获取当前经验进度百分比 + * @param {Object} playerStore - 玩家Store + * @returns {Number} 0-100 + */ +export function getExpProgress(playerStore) { + const currentLevel = playerStore.level.current + if (currentLevel >= LEVEL_CONFIG.maxLevel) { + return 100 + } + + const toNext = getExpToNextLevel(currentLevel) + const current = playerStore.level.exp + + return Math.min(100, Math.floor((current / toNext) * 100)) +} + +/** + * 根据敌人等级计算经验奖励 + * @param {Number} enemyLevel - 敌人等级 + * @param {Number} playerLevel - 玩家等级 + * @param {Number} baseExp - 基础经验值 + * @returns {Number} 调整后的经验值 + */ +export function calculateCombatExp(enemyLevel, playerLevel, baseExp) { + const levelDiff = enemyLevel - playerLevel + + // 等级差异调整 + let multiplier = 1.0 + + if (levelDiff > 5) { + // 敌人等级远高于玩家 - 额外奖励 + multiplier = 1.5 + } else if (levelDiff > 2) { + // 敌人等级稍高 - 小幅奖励 + multiplier = 1.2 + } else if (levelDiff < -5) { + // 敌人等级远低于玩家 - 大幅惩罚 + multiplier = 0.1 + } else if (levelDiff < -2) { + // 敌人等级稍低 - 小幅惩罚 + multiplier = 0.5 + } + + return Math.floor(baseExp * multiplier) +} + +/** + * 获取等级称号 + * @param {Number} level - 等级 + * @returns {String} 称号 + */ +export function getLevelTitle(level) { + const titles = { + 1: '新手', + 5: '冒险者', + 10: '老手', + 15: '精英', + 20: '专家', + 25: '大师', + 30: '宗师', + 35: '传奇', + 40: '英雄', + 45: '神话', + 50: '至尊' + } + + let title = '幸存者' + for (const [lvl, t] of Object.entries(titles).sort((a, b) => b[0] - a[0])) { + if (level >= parseInt(lvl)) { + title = t + break + } + } + return title +} diff --git a/utils/skillSystem.js b/utils/skillSystem.js index f2ec37c..1ca5b82 100644 --- a/utils/skillSystem.js +++ b/utils/skillSystem.js @@ -265,96 +265,6 @@ export function unlockSkill(playerStore, skillId) { return true } -/** - * 检查技能是否可解锁 - * @param {Object} playerStore - 玩家Store - * @param {String} skillId - 技能ID - * @returns {Object} { canUnlock: boolean, reason: string } - */ -export function canUnlockSkill(playerStore, skillId) { - const config = SKILL_CONFIG[skillId] - if (!config) { - return { canUnlock: false, reason: '技能不存在' } - } - - // 已解锁 - if (playerStore.skills[skillId] && playerStore.skills[skillId].unlocked) { - return { canUnlock: true, reason: '已解锁' } - } - - // 检查解锁条件 - if (config.unlockCondition) { - const condition = config.unlockCondition - - // 物品条件 - if (condition.type === 'item') { - const hasItem = playerStore.inventory?.some(i => i.id === condition.item) - if (!hasItem) { - return { canUnlock: false, reason: '需要特定物品' } - } - } - - // 位置条件 - if (condition.location) { - if (playerStore.currentLocation !== condition.location) { - return { canUnlock: false, reason: '需要在特定位置解锁' } - } - } - - // 技能等级条件 - if (condition.skillLevel) { - const requiredSkill = playerStore.skills[condition.skillId] - if (!requiredSkill || requiredSkill.level < condition.skillLevel) { - return { canUnlock: false, reason: '需要前置技能达到特定等级' } - } - } - } - - return { canUnlock: true, reason: '' } -} - -/** - * 获取技能经验倍率(含父技能加成) - * @param {Object} playerStore - 玩家Store - * @param {String} skillId - 技能ID - * @returns {Number} 经验倍率 - */ -export function getSkillExpRate(playerStore, skillId) { - let rate = 1.0 - - // 全局经验加成 - if (playerStore.globalBonus && playerStore.globalBonus.globalExpRate) { - rate *= (1 + playerStore.globalBonus.globalExpRate / 100) - } - - // 父技能加成 - const config = SKILL_CONFIG[skillId] - if (config && config.parentSkill) { - const parentSkill = playerStore.skills[config.parentSkill] - if (parentSkill && parentSkill.unlocked) { - const parentConfig = SKILL_CONFIG[config.parentSkill] - if (parentConfig && parentConfig.milestones) { - // 检查父技能里程碑奖励 - for (const [level, milestone] of Object.entries(parentConfig.milestones)) { - if (parentSkill.level >= parseInt(level) && milestone.effect?.expRate) { - rate *= milestone.effect.expRate - } - } - } - - // 如果子技能等级低于父技能,提供额外加成 - if (parentSkill.level > 0 && playerStore.skills[skillId]) { - const childLevel = playerStore.skills[skillId].level - if (childLevel < parentSkill.level) { - rate *= 1.5 // 父技能倍率加成 - } - } - } - } - - return rate -} - /** * 获取技能显示信息 * @param {Object} playerStore - 玩家Store diff --git a/utils/soundSystem.js b/utils/soundSystem.js new file mode 100644 index 0000000..7e48c0b --- /dev/null +++ b/utils/soundSystem.js @@ -0,0 +1,334 @@ +/** + * 音效系统 + * Phase 3 UI美化 - 音效支持 + * + * 注意:uni-app的音频功能有限,不同平台支持情况不同 + * H5端:使用Web Audio API + * 小程序端:使用 wx.createInnerAudioContext() + * App端:使用 plus.audio + */ + +/** + * 音效类型枚举 + */ +export const SOUND_TYPES = { + // UI音效 + CLICK: 'click', + OPEN: 'open', + CLOSE: 'close', + TOGGLE: 'toggle', + + // 游戏音效 + COMBAT_START: 'combat_start', + COMBAT_HIT: 'combat_hit', + COMBAT_HIT_PLAYER: 'combat_hit_player', + COMBAT_VICTORY: 'combat_victory', + COMBAT_DEFEAT: 'combat_defeat', + + // 奖励音效 + REWARD: 'reward', + LEVEL_UP: 'level_up', + SKILL_UP: 'skill_up', + + // 错误音效 + ERROR: 'error', + WARNING: 'warning', + + // 系统音效 + NOTIFICATION: 'notification', + MESSAGE: 'message' +} + +/** + * 音效配置 + */ +const SOUND_CONFIG = { + // 是否启用音效 + enabled: true, + // 音量 (0-1) + volume: 0.5, + // 音效文件路径配置 (需要实际文件) + soundPaths: { + [SOUND_TYPES.CLICK]: '/static/sounds/click.mp3', + [SOUND_TYPES.OPEN]: '/static/sounds/open.mp3', + [SOUND_TYPES.CLOSE]: '/static/sounds/close.mp3', + [SOUND_TYPES.TOGGLE]: '/static/sounds/toggle.mp3', + [SOUND_TYPES.COMBAT_START]: '/static/sounds/combat_start.mp3', + [SOUND_TYPES.COMBAT_HIT]: '/static/sounds/combat_hit.mp3', + [SOUND_TYPES.COMBAT_HIT_PLAYER]: '/static/sounds/combat_hit_player.mp3', + [SOUND_TYPES.COMBAT_VICTORY]: '/static/sounds/combat_victory.mp3', + [SOUND_TYPES.COMBAT_DEFEAT]: '/static/sounds/combat_defeat.mp3', + [SOUND_TYPES.REWARD]: '/static/sounds/reward.mp3', + [SOUND_TYPES.LEVEL_UP]: '/static/sounds/level_up.mp3', + [SOUND_TYPES.SKILL_UP]: '/static/sounds/skill_up.mp3', + [SOUND_TYPES.ERROR]: '/static/sounds/error.mp3', + [SOUND_TYPES.WARNING]: '/static/sounds/warning.mp3', + [SOUND_TYPES.NOTIFICATION]: '/static/sounds/notification.mp3', + [SOUND_TYPES.MESSAGE]: '/static/sounds/message.mp3' + } +} + +/** + * 音频上下文缓存 + */ +let audioContext = null +let audioCache = {} + +/** + * 初始化音频系统 + */ +export function initSoundSystem() { + // #ifdef H5 + try { + audioContext = new (window.AudioContext || window.webkitAudioContext)() + } catch (e) { + console.warn('Web Audio API not supported:', e) + } + // #endif + + // #ifdef MP-WEIXIN + // 小程序端使用微信音频API + // #endif + + // 从本地存储加载音效设置 + loadSoundSettings() +} + +/** + * 播放音效 + * @param {String} soundType - 音效类型 + * @param {Object} options - 选项 { volume, speed } + */ +export function playSound(soundType, options = {}) { + if (!SOUND_CONFIG.enabled) { + return { success: false, message: '音效已禁用' } + } + + const volume = options.volume !== undefined ? options.volume : SOUND_CONFIG.volume + + // #ifdef H5 + return playSoundH5(soundType, volume) + // #endif + + // #ifdef MP-WEIXIN + return playSoundWeixin(soundType, volume) + // #endif + + // #ifdef APP-PLUS + return playSoundApp(soundType, volume) + // #endif + + return { success: false, message: '当前平台不支持音效' } +} + +/** + * H5端播放音效 (使用Web Audio API合成简单音效) + */ +function playSoundH5(soundType, volume) { + if (!audioContext) { + initSoundSystem() + if (!audioContext) { + return { success: false, message: '音频上下文初始化失败' } + } + } + + // 恢复音频上下文(某些浏览器需要用户交互后才能恢复) + if (audioContext.state === 'suspended') { + audioContext.resume() + } + + try { + // 创建振荡器生成简单音效 + const oscillator = audioContext.createOscillator() + const gainNode = audioContext.createGain() + + oscillator.connect(gainNode) + gainNode.connect(audioContext.destination) + + // 根据音效类型设置不同的频率和包络 + const soundParams = getSoundParameters(soundType) + oscillator.type = soundParams.type || 'sine' + oscillator.frequency.setValueAtTime(soundParams.frequency, audioContext.currentTime) + + // 设置音量包络 + const now = audioContext.currentTime + gainNode.gain.setValueAtTime(0, now) + gainNode.gain.linearRampToValueAtTime(volume * 0.3, now + 0.01) + gainNode.gain.exponentialRampToValueAtTime(0.001, now + soundParams.duration) + + oscillator.start(now) + oscillator.stop(now + soundParams.duration) + + return { success: true } + } catch (e) { + console.warn('播放音效失败:', e) + return { success: false, message: e.message } + } +} + +/** + * 获取音效参数 (用于合成音效) + */ +function getSoundParameters(soundType) { + const params = { + // 点击音效 - 短促的高音 + [SOUND_TYPES.CLICK]: { type: 'sine', frequency: 800, duration: 0.05 }, + // 打开音效 - 上升音调 + [SOUND_TYPES.OPEN]: { type: 'sine', frequency: 400, duration: 0.15 }, + // 关闭音效 - 下降音调 + [SOUND_TYPES.CLOSE]: { type: 'sine', frequency: 300, duration: 0.1 }, + // 切换音效 + [SOUND_TYPES.TOGGLE]: { type: 'square', frequency: 500, duration: 0.08 }, + // 战斗开始 - 低沉 + [SOUND_TYPES.COMBAT_START]: { type: 'sawtooth', frequency: 150, duration: 0.3 }, + // 命中敌人 - 尖锐 + [SOUND_TYPES.COMBAT_HIT]: { type: 'square', frequency: 1200, duration: 0.08 }, + // 被命中 - 低频 + [SOUND_TYPES.COMBAT_HIT_PLAYER]: { type: 'sawtooth', frequency: 200, duration: 0.15 }, + // 胜利 - 上升音阶 + [SOUND_TYPES.COMBAT_VICTORY]: { type: 'sine', frequency: 523, duration: 0.4 }, + // 失败 - 下降音 + [SOUND_TYPES.COMBAT_DEFEAT]: { type: 'sawtooth', frequency: 100, duration: 0.4 }, + // 奖励 - 悦耳的高音 + [SOUND_TYPES.REWARD]: { type: 'sine', frequency: 880, duration: 0.2 }, + // 升级 - 双音 + [SOUND_TYPES.LEVEL_UP]: { type: 'sine', frequency: 659, duration: 0.3 }, + // 技能升级 + [SOUND_TYPES.SKILL_UP]: { type: 'sine', frequency: 784, duration: 0.25 }, + // 错误 - 低频嗡鸣 + [SOUND_TYPES.ERROR]: { type: 'sawtooth', frequency: 100, duration: 0.2 }, + // 警告 + [SOUND_TYPES.WARNING]: { type: 'square', frequency: 440, duration: 0.15 }, + // 通知 + [SOUND_TYPES.NOTIFICATION]: { type: 'sine', frequency: 660, duration: 0.2 }, + // 消息 + [SOUND_TYPES.MESSAGE]: { type: 'sine', frequency: 587, duration: 0.15 } + } + + return params[soundType] || { type: 'sine', frequency: 440, duration: 0.1 } +} + +/** + * 微信小程序端播放音效 + */ +function playSoundWeixin(soundType, volume) { + // #ifdef MP-WEIXIN + const soundPath = SOUND_CONFIG.soundPaths[soundType] + if (!soundPath) { + return { success: false, message: '音效文件未配置' } + } + + try { + const audio = uni.createInnerAudioContext() + audio.src = soundPath + audio.volume = volume + audio.play() + return { success: true } + } catch (e) { + return { success: false, message: e.message } + } + // #endif + + return { success: false, message: '非微信小程序环境' } +} + +/** + * App端播放音效 + */ +function playSoundApp(soundType, volume) { + // #ifdef APP-PLUS + const soundPath = SOUND_CONFIG.soundPaths[soundType] + if (!soundPath) { + return { success: false, message: '音效文件未配置' } + } + + try { + const player = plus.audio.createPlayer(soundPath) + player.setVolume(volume * 100) + player.play() + return { success: true } + } catch (e) { + return { success: false, message: e.message } + } + // #endif + + return { success: false, message: '非App环境' } +} + +/** + * 设置音效开关 + * @param {Boolean} enabled - 是否启用音效 + */ +export function setSoundEnabled(enabled) { + SOUND_CONFIG.enabled = enabled + saveSoundSettings() +} + +/** + * 设置音量 + * @param {Number} volume - 音量 (0-1) + */ +export function setSoundVolume(volume) { + SOUND_CONFIG.volume = Math.max(0, Math.min(1, volume)) + saveSoundSettings() +} + +/** + * 获取音效设置 + * @returns {Object} { enabled, volume } + */ +export function getSoundSettings() { + return { + enabled: SOUND_CONFIG.enabled, + volume: SOUND_CONFIG.volume + } +} + +/** + * 保存音效设置到本地存储 + */ +function saveSoundSettings() { + try { + uni.setStorageSync('soundSettings', JSON.stringify({ + enabled: SOUND_CONFIG.enabled, + volume: SOUND_CONFIG.volume + })) + } catch (e) { + console.warn('保存音效设置失败:', e) + } +} + +/** + * 从本地存储加载音效设置 + */ +function loadSoundSettings() { + try { + const settings = uni.getStorageSync('soundSettings') + if (settings) { + const parsed = JSON.parse(settings) + SOUND_CONFIG.enabled = parsed.enabled !== undefined ? parsed.enabled : true + SOUND_CONFIG.volume = parsed.volume !== undefined ? parsed.volume : 0.5 + } + } catch (e) { + console.warn('加载音效设置失败:', e) + } +} + +/** + * 快捷音效播放函数 + */ +export const playClick = () => playSound(SOUND_TYPES.CLICK) +export const playOpen = () => playSound(SOUND_TYPES.OPEN) +export const playClose = () => playSound(SOUND_TYPES.CLOSE) +export const playCombatStart = () => playSound(SOUND_TYPES.COMBAT_START) +export const playCombatHit = () => playSound(SOUND_TYPES.COMBAT_HIT) +export const playCombatVictory = () => playSound(SOUND_TYPES.COMBAT_VICTORY) +export const playCombatDefeat = () => playSound(SOUND_TYPES.COMBAT_DEFEAT) +export const playReward = () => playSound(SOUND_TYPES.REWARD) +export const playLevelUp = () => playSound(SOUND_TYPES.LEVEL_UP) +export const playSkillUp = () => playSound(SOUND_TYPES.SKILL_UP) +export const playError = () => playSound(SOUND_TYPES.ERROR) +export const playWarning = () => playSound(SOUND_TYPES.WARNING) + +// 初始化音效系统 +initSoundSystem() diff --git a/utils/taskSystem.js b/utils/taskSystem.js index 55e55c0..021b8d3 100644 --- a/utils/taskSystem.js +++ b/utils/taskSystem.js @@ -5,6 +5,8 @@ import { SKILL_CONFIG } from '@/config/skills.js' import { ITEM_CONFIG } from '@/config/items.js' +import { RECIPE_CONFIG } from '@/config/recipes.js' +import { addSkillExp, unlockSkill } from './skillSystem.js' /** * 任务类型枚举 @@ -16,7 +18,8 @@ export const TASK_TYPES = { WORKING: 'working', // 工作 COMBAT: 'combat', // 战斗 EXPLORE: 'explore', // 探索 - PRAYING: 'praying' // 祈祷 + PRAYING: 'praying', // 祈祷 + CRAFTING: 'crafting' // 制造 } /** @@ -27,15 +30,19 @@ const MUTEX_RULES = { full: [ [TASK_TYPES.COMBAT, TASK_TYPES.TRAINING], // 战斗 vs 训练 [TASK_TYPES.COMBAT, TASK_TYPES.WORKING], // 战斗 vs 工作 + [TASK_TYPES.COMBAT, TASK_TYPES.CRAFTING], // 战斗 vs 制造 [TASK_TYPES.TRAINING, TASK_TYPES.WORKING], // 训练 vs 工作 - [TASK_TYPES.EXPLORE, TASK_TYPES.RESTING] // 探索 vs 休息 + [TASK_TYPES.TRAINING, TASK_TYPES.CRAFTING], // 训练 vs 制造 + [TASK_TYPES.EXPLORE, TASK_TYPES.RESTING], // 探索 vs 休息 + [TASK_TYPES.EXPLORE, TASK_TYPES.CRAFTING] // 探索 vs 制造 ], // 部分互斥(需要一心多用技能) partial: [ [TASK_TYPES.READING, TASK_TYPES.COMBAT], // 阅读 vs 战斗 [TASK_TYPES.READING, TASK_TYPES.TRAINING], // 阅读 vs 训练 - [TASK_TYPES.READING, TASK_TYPES.WORKING] // 阅读 vs 工作 + [TASK_TYPES.READING, TASK_TYPES.WORKING], // 阅读 vs 工作 + [TASK_TYPES.READING, TASK_TYPES.CRAFTING] // 阅读 vs 制造 ] } @@ -182,6 +189,17 @@ function checkTaskConditions(playerStore, taskType, taskData) { return { canStart: false, reason: '需要圣经' } } break + + case TASK_TYPES.CRAFTING: + // 需要配方ID + if (!taskData.recipeId) { + return { canStart: false, reason: '需要选择配方' } + } + // 需要耐力 + if (playerStore.currentStats.stamina < 5) { + return { canStart: false, reason: '耐力不足' } + } + break } return { canStart: true, reason: '' } @@ -275,6 +293,9 @@ function processSingleTask(gameStore, playerStore, task, deltaTime) { case TASK_TYPES.PRAYING: return processPrayingTask(gameStore, playerStore, task, elapsedSeconds) + case TASK_TYPES.CRAFTING: + return processCraftingTask(gameStore, playerStore, task, elapsedSeconds) + default: return { completed: false } } @@ -491,6 +512,134 @@ function processPrayingTask(gameStore, playerStore, task, elapsedSeconds) { return { completed: false } } +/** + * 处理制造任务 + */ +function processCraftingTask(gameStore, playerStore, task, elapsedSeconds) { + const recipeId = task.data.recipeId + const recipe = RECIPE_CONFIG[recipeId] + + if (!recipe) { + return { completed: true, error: '配方不存在' } + } + + // 消耗耐力(制造也需要体力) + const staminaCost = 1 * elapsedSeconds + if (playerStore.currentStats.stamina < staminaCost) { + return { completed: true, rewards: {}, failed: true } + } + + playerStore.currentStats.stamina -= staminaCost + + // 计算实际制造时间(受技能加成影响) + const craftingSkill = playerStore.skills[recipe.requiredSkill] + let timeMultiplier = 1.0 + + if (craftingSkill && craftingSkill.level > 0) { + // 制造技能每级减少5%时间 + timeMultiplier = 1 - (Math.min(craftingSkill.level, 20) * 0.05) + } + + // 累积制造进度(考虑时间加成) + const effectiveProgress = task.progress * timeMultiplier + + // 检查是否完成 + if (effectiveProgress >= recipe.baseTime) { + // 计算成功率 + const successRate = calculateCraftingSuccessRate(playerStore, recipe) + + // 判定是否成功 + const success = Math.random() < successRate + + if (success) { + // 给予制造技能经验 + const expGain = recipe.baseTime * 0.5 // 基于制造时间的经验 + + if (!task.accumulatedExp) { + task.accumulatedExp = {} + } + if (!task.accumulatedExp[recipe.requiredSkill]) { + task.accumulatedExp[recipe.requiredSkill] = 0 + } + task.accumulatedExp[recipe.requiredSkill] += expGain + + const rewards = { + [recipe.requiredSkill]: task.accumulatedExp[recipe.requiredSkill] || 0, + craftedItem: recipe.resultItem, + craftedCount: recipe.resultCount + } + + // 添加制造物品到背包(这将在任务完成回调中处理) + if (gameStore.addLog) { + const itemConfig = ITEM_CONFIG[recipe.resultItem] + gameStore.addLog(`制造成功:${itemConfig?.name || recipe.resultItem}`, 'reward') + } + + return { completed: true, rewards, success: true } + } else { + // 制造失败 + if (gameStore.addLog) { + gameStore.addLog(`制造失败:${recipeId}`, 'warning') + } + + // 失败也给予少量经验 + const expGain = recipe.baseTime * 0.1 + if (!task.accumulatedExp) { + task.accumulatedExp = {} + } + if (!task.accumulatedExp[recipe.requiredSkill]) { + task.accumulatedExp[recipe.requiredSkill] = 0 + } + task.accumulatedExp[recipe.requiredSkill] += expGain + + const rewards = { + [recipe.requiredSkill]: task.accumulatedExp[recipe.requiredSkill] || 0 + } + + return { completed: true, rewards, success: false } + } + } + + // 进行中,给予少量经验 + const expPerTick = 0.5 * elapsedSeconds + if (!task.accumulatedExp) { + task.accumulatedExp = {} + } + if (!task.accumulatedExp[recipe.requiredSkill]) { + task.accumulatedExp[recipe.requiredSkill] = 0 + } + task.accumulatedExp[recipe.requiredSkill] += expPerTick + + return { completed: false } +} + +/** + * 计算制造成功率 + * @param {Object} playerStore - 玩家Store + * @param {Object} recipe - 配方对象 + * @returns {Number} 成功率 (0-1) + */ +function calculateCraftingSuccessRate(playerStore, recipe) { + let successRate = recipe.baseSuccessRate + + // 技能等级加成(每级+2%) + const skill = playerStore.skills[recipe.requiredSkill] + if (skill && skill.level > 0) { + successRate += Math.min(skill.level, 20) * 0.02 + } + + // 运气加成 + const luck = playerStore.baseStats?.luck || 10 + successRate += luck * 0.001 + + // 检查是否有制造技能的全局加成 + if (playerStore.globalBonus?.craftingSuccessRate) { + successRate += playerStore.globalBonus.craftingSuccessRate / 100 + } + + return Math.min(0.98, Math.max(0.05, successRate)) +} + /** * 获取任务奖励 */ @@ -521,17 +670,17 @@ function applyTaskRewards(playerStore, task, rewards) { continue } if (skillId === 'faith') { - // 信仰技能经验 + // 信仰技能经验 - 使用addSkillExp处理 if (!playerStore.skills.faith) { - playerStore.skills.faith = { level: 0, exp: 0, unlocked: true } + unlockSkill(playerStore, 'faith') } - playerStore.skills.faith.exp += exp + addSkillExp(playerStore, 'faith', exp) continue } - // 普通技能经验 + // 普通技能经验 - 使用addSkillExp处理升级和里程碑 if (playerStore.skills[skillId]) { - playerStore.skills[skillId].exp += exp + addSkillExp(playerStore, skillId, exp) } } } @@ -547,7 +696,8 @@ function getTaskTypeName(taskType) { working: '工作', combat: '战斗', explore: '探索', - praying: '祈祷' + praying: '祈祷', + crafting: '制造' } return names[taskType] || taskType } diff --git a/计划第二步.md b/计划第二步.md new file mode 100644 index 0000000..2094d38 --- /dev/null +++ b/计划第二步.md @@ -0,0 +1,45 @@ +接下来开发计划 +阶段1:修复代码问题(优先) +# 任务 文件 +1 添加gameStore中shop抽屉状态 store/game.js +2 修复eventSystem中SKILL_CONFIG导入 utils/eventSystem.js +3 修复environmentSystem中里程碑奖励调用 utils/environmentSystem.js +4 统一击杀数存储方式 utils/eventSystem.js, components/panels/MapPanel.vue +5 InventoryDrawer使用itemSystem函数 components/drawers/InventoryDrawer.vue +6 任务完成使用addSkillExp utils/taskSystem.js +阶段2:补充未实现功能 +# 功能 描述 +1 阅读功能 将书籍任务与UI关联,显示阅读进度 +2 训练功能 添加训练任务UI,技能训练 +3 任务UI面板 显示活动中的挂机任务进度 +4 装备属性生效 确保装备属性正确应用到战斗计算 +5 探索功能 添加探索事件触发 +阶段3:UI美化 +# 内容 说明 +1 状态面板优化 添加属性详情展开/折叠 +2 战斗视觉反馈 伤害数字跳动、暴击动画 +3 品质特效 不同品质物品的光效/边框 +4 日志颜色优化 更精细的日志类型着色 +5 技能面板 里程碑奖励可视化显示 +6 过渡动画 页面切换、抽屉弹出动画 +阶段4:数值调整 +# 内容 当前 建议 +1 初始属性 全部10 按角色差异化 +2 敌人伤害 野狗攻击8 对新手可能偏高 +3 经验曲线 线性增长 考虑指数衰减 +4 耐力消耗 战斗2/秒 平衡性测试 +5 物品价格 基础价格10-50 经济平衡 +6 品质概率 随机50-150 高品质概率过低 +阶段5:内容扩展(剧情之前先讨论) +# 内容 说明 +1 更多武器类型 剑、斧、法杖等 +2 更多消耗品 药水、食物种类 +3 更多敌人 不同区域的怪物 +4 更多被动技能 适应不同环境 +5 成就系统 记录玩家成就 +🎯 建议优先处理顺序 +立即修复:代码逻辑问题(阶段1) +核心功能补全:阅读、训练、任务UI(阶段2) +数值平衡测试:实际游玩体验后调整(阶段4) +UI美化:在功能稳定后进行(阶段3) +剧情讨论:最后我们一起讨论完善(阶段5) \ No newline at end of file