450 lines
12 KiB
JavaScript
450 lines
12 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} { 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
|
|||
|
|
* @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
|
|||
|
|
}
|
|||
|
|
}
|