Files
text-adventure-game/utils/skillSystem.js
Claude 16223c89a5 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>
2026-01-23 16:20:10 +08:00

360 lines
9.0 KiB
JavaScript

/**
* 技能系统 - 技能经验、升级、里程碑、父技能同步
* Phase 6 核心系统实现
*/
import { SKILL_CONFIG } from '@/config/skills.js'
import { GAME_CONSTANTS } from '@/config/constants.js'
/**
* 添加技能经验
* @param {Object} playerStore - 玩家Store
* @param {String} skillId - 技能ID
* @param {Number} amount - 经验值
* @param {Object} options - 选项 { partialSuccess: boolean }
* @returns {Object} { leveledUp: boolean, newLevel: number, milestoneReached: number|null }
*/
export function addSkillExp(playerStore, skillId, amount, options = {}) {
const { partialSuccess = false } = options
// 检查技能是否存在且已解锁
if (!playerStore.skills || !playerStore.skills[skillId]) {
return { leveledUp: false, newLevel: 0, milestoneReached: null }
}
const skill = playerStore.skills[skillId]
if (!skill.unlocked) {
return { leveledUp: false, newLevel: 0, milestoneReached: null }
}
// 部分成功时经验减半
const finalAmount = partialSuccess ? Math.floor(amount * GAME_CONSTANTS.EXP_PARTIAL_SUCCESS) : amount
// 检查玩家等级限制
const maxSkillLevel = playerStore.level ? playerStore.level.current * 2 : 2
if (skill.level >= maxSkillLevel) {
return { leveledUp: false, newLevel: skill.level, milestoneReached: null, capped: true }
}
// 获取技能配置
const config = SKILL_CONFIG[skillId]
if (!config) {
return { leveledUp: false, newLevel: 0, milestoneReached: null }
}
// 计算有效等级上限(取技能上限和玩家限制的最小值)
const effectiveMaxLevel = Math.min(config.maxLevel, maxSkillLevel)
// 添加经验
skill.exp += finalAmount
let leveledUp = false
let milestoneReached = null
// 检查升级(可能连续升级)
while (skill.level < effectiveMaxLevel && skill.exp >= getNextLevelExp(skillId, skill.level)) {
const result = levelUpSkill(playerStore, skillId, skill.level)
leveledUp = true
if (result.milestone) {
milestoneReached = result.milestone
}
}
return {
leveledUp,
newLevel: skill.level,
milestoneReached,
capped: skill.level >= effectiveMaxLevel
}
}
/**
* 获取下一级所需经验
* @param {String} skillId - 技能ID
* @param {Number} currentLevel - 当前等级
* @returns {Number} 所需经验值
*/
export function getNextLevelExp(skillId, currentLevel) {
const config = SKILL_CONFIG[skillId]
if (!config) return 100
// 使用配置中的经验公式,默认为 level * 100
if (typeof config.expPerLevel === 'function') {
return config.expPerLevel(currentLevel + 1)
}
return (currentLevel + 1) * 100
}
/**
* 技能升级
* @param {Object} playerStore - 玩家Store
* @param {String} skillId - 技能ID
* @param {Number} currentLevel - 当前等级
* @returns {Object} { milestone: number|null, effects: Object }
*/
export function levelUpSkill(playerStore, skillId, currentLevel) {
const skill = playerStore.skills[skillId]
const config = SKILL_CONFIG[skillId]
if (!skill || !config) {
return { milestone: null, effects: {} }
}
// 增加等级
skill.level++
const requiredExp = getNextLevelExp(skillId, currentLevel)
skill.exp -= requiredExp
// 检查里程碑奖励
let milestone = null
let effects = {}
if (config.milestones && config.milestones[skill.level]) {
milestone = skill.level
effects = applyMilestoneBonus(playerStore, skillId, skill.level)
}
// 更新父技能等级
updateParentSkill(playerStore, skillId)
return { milestone, effects }
}
/**
* 应用里程碑奖励
* @param {Object} playerStore - 玩家Store
* @param {String} skillId - 技能ID
* @param {Number} level - 里程碑等级
* @returns {Object} 应用的效果
*/
export function applyMilestoneBonus(playerStore, skillId, level) {
const config = SKILL_CONFIG[skillId]
if (!config || !config.milestones || !config.milestones[level]) {
return {}
}
const milestone = config.milestones[level]
const effects = { ...milestone.effect }
// 初始化里程碑奖励存储
if (!playerStore.milestoneBonuses) {
playerStore.milestoneBonuses = {}
}
const bonusKey = `${skillId}_${level}`
// 如果已经应用过,跳过
if (playerStore.milestoneBonuses[bonusKey]) {
return effects
}
// 标记里程碑已应用
playerStore.milestoneBonuses[bonusKey] = {
skillId,
level,
desc: milestone.desc,
effect: effects,
appliedAt: Date.now()
}
// 应用全局属性加成
if (!playerStore.globalBonus) {
playerStore.globalBonus = {
critRate: 0,
attackBonus: 0,
expRate: 1,
critMult: 0,
darkPenaltyReduce: 0,
globalExpRate: 0,
readingSpeed: 1
}
}
// 应用具体效果
if (effects.critRate) {
playerStore.globalBonus.critRate += effects.critRate
}
if (effects.attackBonus) {
playerStore.globalBonus.attackBonus += effects.attackBonus
}
if (effects.expRate) {
playerStore.globalBonus.expRate *= effects.expRate
}
if (effects.critMult) {
playerStore.globalBonus.critMult += effects.critMult
}
if (effects.darkPenaltyReduce) {
playerStore.globalBonus.darkPenaltyReduce += effects.darkPenaltyReduce
}
if (effects.globalExpRate) {
playerStore.globalBonus.globalExpRate += effects.globalExpRate
}
if (effects.readingSpeed) {
playerStore.globalBonus.readingSpeed *= effects.readingSpeed
}
return effects
}
/**
* 更新父技能等级
* @param {Object} playerStore - 玩家Store
* @param {String} skillId - 子技能ID
*/
export function updateParentSkill(playerStore, skillId) {
const config = SKILL_CONFIG[skillId]
if (!config || !config.parentSkill) {
return
}
const parentSkillId = config.parentSkill
// 查找所有子技能的最高等级
const childSkills = Object.keys(SKILL_CONFIG).filter(id =>
SKILL_CONFIG[id].parentSkill === parentSkillId
)
let maxChildLevel = 0
for (const childId of childSkills) {
const childSkill = playerStore.skills[childId]
if (childSkill && childSkill.unlocked) {
maxChildLevel = Math.max(maxChildLevel, childSkill.level)
}
}
// 更新父技能等级
if (!playerStore.skills[parentSkillId]) {
playerStore.skills[parentSkillId] = {
level: 0,
exp: 0,
unlocked: true
}
}
playerStore.skills[parentSkillId].level = maxChildLevel
}
/**
* 解锁技能
* @param {Object} playerStore - 玩家Store
* @param {String} skillId - 技能ID
* @returns {boolean} 是否成功解锁
*/
export function unlockSkill(playerStore, skillId) {
if (!playerStore.skills) {
playerStore.skills = {}
}
const config = SKILL_CONFIG[skillId]
if (!config) {
return false
}
// 检查是否已解锁
if (playerStore.skills[skillId] && playerStore.skills[skillId].unlocked) {
return true
}
// 初始化技能数据
playerStore.skills[skillId] = {
level: 0,
exp: 0,
unlocked: true,
maxExp: getNextLevelExp(skillId, 0)
}
return true
}
/**
* 获取技能显示信息
* @param {Object} playerStore - 玩家Store
* @param {String} skillId - 技能ID
* @returns {Object|null} 技能显示信息
*/
export function getSkillDisplayInfo(playerStore, skillId) {
const config = SKILL_CONFIG[skillId]
if (!config) {
return null
}
const skillData = playerStore.skills[skillId] || {
level: 0,
exp: 0,
unlocked: false
}
const maxExp = getNextLevelExp(skillId, skillData.level)
const progress = skillData.level >= config.maxLevel ? 1 : skillData.exp / maxExp
// 获取已达到的里程碑
const reachedMilestones = []
if (config.milestones && playerStore.milestoneBonuses) {
for (const [level, milestone] of Object.entries(config.milestones)) {
const bonusKey = `${skillId}_${level}`
if (playerStore.milestoneBonuses[bonusKey]) {
reachedMilestones.push({
level: parseInt(level),
desc: milestone.desc
})
}
}
}
return {
id: skillId,
name: config.name,
type: config.type,
category: config.category,
icon: config.icon,
level: skillData.level,
maxLevel: config.maxLevel,
exp: skillData.exp,
maxExp,
progress,
unlocked: skillData.unlocked,
reachedMilestones,
nextMilestone: getNextMilestone(config, skillData.level)
}
}
/**
* 获取下一个里程碑
* @param {Object} config - 技能配置
* @param {Number} currentLevel - 当前等级
* @returns {Object|null} 下一个里程碑
*/
function getNextMilestone(config, currentLevel) {
if (!config.milestones) {
return null
}
for (const [level, milestone] of Object.entries(config.milestones)) {
if (parseInt(level) > currentLevel) {
return {
level: parseInt(level),
desc: milestone.desc
}
}
}
return null
}
/**
* 重置技能系统(用于新游戏)
* @param {Object} playerStore - 玩家Store
*/
export function resetSkillSystem(playerStore) {
playerStore.skills = {}
playerStore.milestoneBonuses = {}
playerStore.globalBonus = {
critRate: 0,
attackBonus: 0,
expRate: 1,
critMult: 0,
darkPenaltyReduce: 0,
globalExpRate: 0,
readingSpeed: 1
}
}