288 lines
7.6 KiB
JavaScript
288 lines
7.6 KiB
JavaScript
|
|
/**
|
||
|
|
* 等级系统 - 经验曲线、升级计算
|
||
|
|
* 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
|
||
|
|
}
|