Initial commit: Text Adventure Game
Features: - Combat system with AP/EP hit calculation and three-layer defense - Auto-combat/farming mode - Item system with stacking support - Skill system with levels, milestones, and parent skill sync - Shop system with dynamic pricing - Inventory management with bulk selling - Event system - Game loop with offline earnings - Save/Load system Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
449
utils/skillSystem.js
Normal file
449
utils/skillSystem.js
Normal file
@@ -0,0 +1,449 @@
|
||||
/**
|
||||
* 技能系统 - 技能经验、升级、里程碑、父技能同步
|
||||
* 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user