fix: 修复制造系统耦合问题并优化物品堆叠逻辑
- 修复 craftingSystem.js 引用不存在的 inventorySystem.js - 移除 recipes.js 中重复的 getItemCount 函数 - 装备类物品改为按品质等级(1-6)分组堆叠,而非精确品质值 - 统一品质计算,使用 GAME_CONSTANTS.QUALITY_LEVELS 范围 - 移除未使用的 createCraftedItem 函数 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
357
utils/craftingSystem.js
Normal file
357
utils/craftingSystem.js
Normal file
@@ -0,0 +1,357 @@
|
||||
/**
|
||||
* 制造系统 - 配方管理、制造逻辑、品质计算
|
||||
* Phase 6 核心系统实现
|
||||
*/
|
||||
|
||||
import { ITEM_CONFIG } from '@/config/items.js'
|
||||
import { RECIPE_CONFIG, getAvailableRecipes, checkMaterials } from '@/config/recipes.js'
|
||||
import { SKILL_CONFIG } from '@/config/skills.js'
|
||||
import { startTask } from './taskSystem.js'
|
||||
import { TASK_TYPES } from './taskSystem.js'
|
||||
import { addItemToInventory } from './itemSystem.js'
|
||||
import { addSkillExp, unlockSkill } from './skillSystem.js'
|
||||
|
||||
/**
|
||||
* 获取玩家可制作的配方
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
* @returns {Array} 可制作的配方列表(含材料检查)
|
||||
*/
|
||||
export function getCraftableRecipes(playerStore) {
|
||||
const availableRecipes = getAvailableRecipes(playerStore)
|
||||
|
||||
return availableRecipes.map(recipe => {
|
||||
const materialCheck = checkMaterials(playerStore, recipe)
|
||||
return {
|
||||
...recipe,
|
||||
canCraftNow: materialCheck.canCraft,
|
||||
missingMaterials: materialCheck.missingMaterials
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 按分类获取可制作的配方
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
* @param {String} category - 分类 'weapon' | 'armor' | 'shield' | 'consumable' | 'special'
|
||||
* @returns {Array} 配方列表
|
||||
*/
|
||||
export function getRecipesByCategory(playerStore, category) {
|
||||
const recipes = getCraftableRecipes(playerStore)
|
||||
return recipes.filter(recipe => recipe.type === category)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算制造时间
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
* @param {Object} recipe - 配方对象
|
||||
* @returns {Number} 制造时间(秒)
|
||||
*/
|
||||
export function calculateCraftingTime(playerStore, recipe) {
|
||||
let baseTime = recipe.baseTime
|
||||
|
||||
// 技能加成
|
||||
const skill = playerStore.skills[recipe.requiredSkill]
|
||||
if (skill && skill.level > 0) {
|
||||
// 检查制造技能的时间加成里程碑
|
||||
let speedBonus = 0
|
||||
if (playerStore.skills.crafting?.unlocked) {
|
||||
const craftingSkill = playerStore.skills.crafting
|
||||
// 检查里程碑
|
||||
const milestones = SKILL_CONFIG.crafting?.milestones || {}
|
||||
for (const [level, milestone] of Object.entries(milestones)) {
|
||||
if (craftingSkill.level >= parseInt(level) && milestone.effect?.craftingSpeed) {
|
||||
speedBonus += milestone.effect.craftingSpeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 技能等级直接加成(每级5%)
|
||||
speedBonus += Math.min(skill.level, 20) * 0.05
|
||||
|
||||
baseTime = baseTime * (1 - speedBonus)
|
||||
}
|
||||
|
||||
return Math.max(5, Math.floor(baseTime)) // 最少5秒
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算制造成功率
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
* @param {Object} recipe - 配方对象
|
||||
* @returns {Number} 成功率 (0-100)
|
||||
*/
|
||||
export function calculateSuccessRate(playerStore, recipe) {
|
||||
let successRate = recipe.baseSuccessRate * 100
|
||||
|
||||
// 技能等级加成(每级+2%)
|
||||
const skill = playerStore.skills[recipe.requiredSkill]
|
||||
if (skill && skill.level > 0) {
|
||||
successRate += Math.min(skill.level, 20) * 2
|
||||
}
|
||||
|
||||
// 运气加成
|
||||
const luck = playerStore.baseStats?.luck || 10
|
||||
successRate += luck * 0.1
|
||||
|
||||
// 检查制造技能的成功率加成里程碑
|
||||
if (playerStore.skills.crafting?.unlocked) {
|
||||
const craftingSkill = playerStore.skills.crafting
|
||||
const milestones = SKILL_CONFIG.crafting?.milestones || {}
|
||||
for (const [level, milestone] of Object.entries(milestones)) {
|
||||
if (craftingSkill.level >= parseInt(level) && milestone.effect?.craftingSuccessRate) {
|
||||
successRate += milestone.effect.craftingSuccessRate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Math.min(98, Math.max(5, Math.floor(successRate)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算物品品质
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
* @param {Object} recipe - 配方对象
|
||||
* @returns {Number} 品质值 (0-250)
|
||||
*/
|
||||
export function calculateQuality(playerStore, recipe) {
|
||||
// 基础品质(普通范围 50-99)
|
||||
let qualityValue = 50 + Math.floor(Math.random() * 50) // 默认普通
|
||||
|
||||
// 随机因素 (0-100)
|
||||
const randomRoll = Math.random() * 100
|
||||
|
||||
// 技能加成(每级增加高品质概率)
|
||||
const skill = playerStore.skills[recipe.requiredSkill]
|
||||
let skillBonus = 0
|
||||
if (skill) {
|
||||
skillBonus = skill.level * 2
|
||||
}
|
||||
|
||||
// 运气加成
|
||||
const luck = playerStore.baseStats?.luck || 10
|
||||
const luckBonus = luck * 0.5
|
||||
|
||||
// 制造技能品质加成
|
||||
let craftingQualityBonus = 0
|
||||
if (playerStore.skills.crafting?.unlocked) {
|
||||
const craftingSkill = playerStore.skills.crafting
|
||||
const milestones = SKILL_CONFIG.crafting?.milestones || {}
|
||||
for (const [level, milestone] of Object.entries(milestones)) {
|
||||
if (craftingSkill.level >= parseInt(level) && milestone.effect?.craftingQuality) {
|
||||
craftingQualityBonus += milestone.effect.craftingQuality
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const totalBonus = skillBonus + luckBonus + craftingQualityBonus
|
||||
const finalRoll = randomRoll + totalBonus
|
||||
|
||||
// 品质判定 - 与全局品质等级保持一致
|
||||
if (finalRoll >= 95) qualityValue = 200 + Math.floor(Math.random() * 50) // 传说 [200, 250]
|
||||
else if (finalRoll >= 85) qualityValue = 160 + Math.floor(Math.random() * 39) // 史诗 [160, 199]
|
||||
else if (finalRoll >= 70) qualityValue = 130 + Math.floor(Math.random() * 29) // 稀有 [130, 159]
|
||||
else if (finalRoll >= 50) qualityValue = 100 + Math.floor(Math.random() * 29) // 优秀 [100, 129]
|
||||
else if (finalRoll >= 20) qualityValue = 50 + Math.floor(Math.random() * 49) // 普通 [50, 99]
|
||||
else qualityValue = Math.floor(Math.random() * 50) // 垃圾 [0, 49]
|
||||
|
||||
// 特殊道具(如剧情道具)固定为传说品质
|
||||
if (recipe.type === 'special' && recipe.keyItem) {
|
||||
qualityValue = 225 // 传说中值
|
||||
}
|
||||
|
||||
return Math.min(250, Math.max(0, qualityValue))
|
||||
}
|
||||
|
||||
/**
|
||||
* 消耗材料(通过 itemSystem 接口)
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
* @param {Object} recipe - 配方对象
|
||||
* @returns {Boolean} 是否成功消耗
|
||||
*/
|
||||
export function consumeMaterials(playerStore, recipe) {
|
||||
// 先检查材料是否足够
|
||||
const check = checkMaterials(playerStore, recipe)
|
||||
if (!check.canCraft) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 消耗材料 - 使用 itemSystem 接口
|
||||
for (const material of recipe.materials) {
|
||||
// 查找背包中该物品的实例
|
||||
const itemIndex = playerStore.inventory.findIndex(i => i.id === material.itemId)
|
||||
if (itemIndex !== -1) {
|
||||
const item = playerStore.inventory[itemIndex]
|
||||
const countToRemove = Math.min(item.count, material.count)
|
||||
item.count -= countToRemove
|
||||
if (item.count <= 0) {
|
||||
playerStore.inventory.splice(itemIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始制造
|
||||
* @param {Object} gameStore - 游戏Store
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
* @param {String} recipeId - 配方ID
|
||||
* @returns {Object} { success: boolean, message: string, taskId: string|null }
|
||||
*/
|
||||
export function startCrafting(gameStore, playerStore, recipeId) {
|
||||
const recipe = RECIPE_CONFIG[recipeId]
|
||||
if (!recipe) {
|
||||
return { success: false, message: '配方不存在' }
|
||||
}
|
||||
|
||||
// 检查材料
|
||||
const materialCheck = checkMaterials(playerStore, recipe)
|
||||
if (!materialCheck.canCraft) {
|
||||
const missingList = materialCheck.missingMaterials.map(m => `${m.itemName} x${m.need - m.have}`).join(', ')
|
||||
return { success: false, message: `材料不足:${missingList}` }
|
||||
}
|
||||
|
||||
// 检查技能
|
||||
if (recipe.requiredSkill) {
|
||||
const skill = playerStore.skills[recipe.requiredSkill]
|
||||
if (!skill || !skill.unlocked) {
|
||||
return { success: false, message: '需要解锁对应技能' }
|
||||
}
|
||||
if (skill.level < recipe.requiredSkillLevel) {
|
||||
return { success: false, message: `技能等级不足(需要${recipe.requiredSkillLevel}级)` }
|
||||
}
|
||||
}
|
||||
|
||||
// 计算制造时间
|
||||
const craftingTime = calculateCraftingTime(playerStore, recipe)
|
||||
|
||||
// 消耗材料(在开始制造时消耗)
|
||||
if (!consumeMaterials(playerStore, recipe)) {
|
||||
return { success: false, message: '材料消耗失败' }
|
||||
}
|
||||
|
||||
// 开始制造任务
|
||||
const result = startTask(gameStore, playerStore, TASK_TYPES.CRAFTING, {
|
||||
recipeId,
|
||||
duration: craftingTime
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
// 预先计算品质(制造开始时确定)
|
||||
const task = gameStore.activeTasks.find(t => t.id === result.taskId)
|
||||
if (task) {
|
||||
task.preCalculatedQuality = calculateQuality(playerStore, recipe)
|
||||
}
|
||||
|
||||
const itemConfig = ITEM_CONFIG[recipe.resultItem]
|
||||
return {
|
||||
success: true,
|
||||
message: `开始制造:${itemConfig?.name || recipe.resultItem}`,
|
||||
taskId: result.taskId,
|
||||
estimatedTime: craftingTime,
|
||||
successRate: calculateSuccessRate(playerStore, recipe)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成制造(任务完成回调)
|
||||
* @param {Object} _gameStore - 游戏Store(未使用,保留以兼容接口)
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
* @param {Object} task - 制造任务
|
||||
* @param {Object} result - 任务结果
|
||||
* @returns {Object} { success: boolean, item: Object|null }
|
||||
*/
|
||||
export function completeCrafting(_gameStore, playerStore, task, result) {
|
||||
const recipeId = task.data.recipeId
|
||||
const recipe = RECIPE_CONFIG[recipeId]
|
||||
|
||||
if (!recipe) {
|
||||
return { success: false, message: '配方不存在' }
|
||||
}
|
||||
|
||||
// 如果制造失败
|
||||
if (!result.success) {
|
||||
// 失败可能部分返还材料(基于成功率)
|
||||
const successRate = calculateSuccessRate(playerStore, recipe)
|
||||
if (successRate > 70) {
|
||||
// 高成功率:返还部分材料
|
||||
// 材料已在开始时消耗,这里可以考虑返还逻辑
|
||||
return {
|
||||
success: false,
|
||||
message: '制造失败,但部分材料已保留',
|
||||
failed: true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: '制造失败,材料已消耗',
|
||||
failed: true
|
||||
}
|
||||
}
|
||||
|
||||
// 制造成功,计算品质(已经是品质值 0-250)
|
||||
const qualityValue = task.preCalculatedQuality || calculateQuality(playerStore, recipe)
|
||||
|
||||
// 添加到背包 - 使用 itemSystem 接口
|
||||
const addResult = addItemToInventory(playerStore, recipe.resultItem, recipe.resultCount, qualityValue)
|
||||
|
||||
if (!addResult.success) {
|
||||
return { success: false, message: '添加物品失败' }
|
||||
}
|
||||
|
||||
// 获取物品显示信息
|
||||
const item = addResult.item
|
||||
|
||||
// 给予制造技能经验
|
||||
if (result.rewards && result.rewards[recipe.requiredSkill]) {
|
||||
const skillId = recipe.requiredSkill
|
||||
const exp = result.rewards[skillId]
|
||||
|
||||
if (!playerStore.skills[skillId]) {
|
||||
unlockSkill(playerStore, skillId)
|
||||
}
|
||||
addSkillExp(playerStore, skillId, exp)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
item,
|
||||
message: `制造成功:${item.qualityName} ${item.name}`,
|
||||
addResult
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类显示信息
|
||||
* @param {String} type - 配方类型
|
||||
* @returns {Object} 分类信息
|
||||
*/
|
||||
export function getCategoryInfo(type) {
|
||||
const categories = {
|
||||
weapon: { id: 'weapon', name: '武器', icon: '⚔️' },
|
||||
armor: { id: 'armor', name: '防具', icon: '🛡️' },
|
||||
shield: { id: 'shield', name: '盾牌', icon: '🛡️' },
|
||||
consumable: { id: 'consumable', name: '消耗品', icon: '🧪' },
|
||||
special: { id: 'special', name: '特殊', icon: '✨' }
|
||||
}
|
||||
return categories[type] || { id: type, name: type, icon: '📦' }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有分类
|
||||
* @returns {Array} 分类列表
|
||||
*/
|
||||
export function getAllCategories() {
|
||||
return [
|
||||
{ id: 'weapon', name: '武器', icon: '⚔️' },
|
||||
{ id: 'armor', name: '防具', icon: '🛡️' },
|
||||
{ id: 'shield', name: '盾牌', icon: '🛡️' },
|
||||
{ id: 'consumable', name: '消耗品', icon: '🧪' },
|
||||
{ id: 'special', name: '特殊', icon: '✨' }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user