feat: 实现游戏核心系统和UI组件
核心系统: - combatSystem: 战斗逻辑、伤害计算、战斗状态管理 - skillSystem: 技能系统、技能解锁、经验值、里程碑 - taskSystem: 任务系统、任务类型、任务执行和完成 - eventSystem: 事件系统、随机事件处理 - environmentSystem: 环境系统、时间流逝、区域效果 - levelingSystem: 升级系统、属性成长 - soundSystem: 音效系统 配置文件: - enemies: 敌人配置、掉落表 - events: 事件配置、事件效果 - items: 物品配置、装备属性 - locations: 地点配置、探索事件 - skills: 技能配置、技能树 UI组件: - CraftingDrawer: 制造界面 - InventoryDrawer: 背包界面 - 其他UI优化和动画 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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: '逃跑失败' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
287
utils/levelingSystem.js
Normal file
287
utils/levelingSystem.js
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
334
utils/soundSystem.js
Normal file
334
utils/soundSystem.js
Normal file
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user