feat: 优化游戏体验和系统平衡性
- 修复商店物品名称显示问题,添加堆叠物品出售数量选择 - 自动战斗状态持久化,战斗结束显示"寻找中"状态 - 战斗日志显示经验获取详情(战斗经验、武器经验) - 技能进度条显示当前/最大经验值 - 阅读自动解锁技能并持续获得阅读经验,背包可直接阅读 - 优化训练平衡:时长60秒,经验5点/秒,耐力消耗降低 - 实现自然回复系统:基于体质回复HP/耐力,休息提供3倍加成 - 战斗和训练时不进行自然回复 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,81 @@ export function isGameLoopRunning() {
|
||||
return isLoopRunning
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家是否正在休息
|
||||
* @param {Object} gameStore - 游戏Store
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isPlayerResting(gameStore) {
|
||||
return gameStore.activeTasks?.some(task => task.type === 'resting') || false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家是否正在训练
|
||||
* @param {Object} gameStore - 游戏Store
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isPlayerTraining(gameStore) {
|
||||
return gameStore.activeTasks?.some(task => task.type === 'training') || false
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自然回复
|
||||
* 基于体质属性自然回复HP和耐力,休息时提供额外加成
|
||||
* 注意:战斗中和训练时不进行自然回复
|
||||
* @param {Object} gameStore - 游戏Store
|
||||
* @param {Object} playerStore - 玩家Store
|
||||
*/
|
||||
function processNaturalRegeneration(gameStore, playerStore) {
|
||||
const { currentStats, baseStats } = playerStore
|
||||
const isResting = isPlayerResting(gameStore)
|
||||
|
||||
// 体质影响HP回复量:每点体质每秒回复0.05点HP
|
||||
// 体质10 = 每秒0.5点HP基础回复
|
||||
const vitality = baseStats?.vitality || 10
|
||||
const baseHpRegen = vitality * 0.05
|
||||
|
||||
// 耐力基础回复:每秒回复1点
|
||||
const baseStaminaRegen = 1
|
||||
|
||||
// 休息时回复倍率
|
||||
const restMultiplier = isResting ? 3 : 1
|
||||
|
||||
// 计算实际回复量
|
||||
const hpRegen = baseHpRegen * restMultiplier
|
||||
const staminaRegen = baseStaminaRegen * restMultiplier
|
||||
|
||||
// 记录旧值用于日志
|
||||
const oldHp = currentStats.health
|
||||
const oldStamina = currentStats.stamina
|
||||
|
||||
// 应用回复(不超过最大值)
|
||||
const newHp = Math.min(currentStats.maxHealth, currentStats.health + hpRegen)
|
||||
const newStamina = Math.min(currentStats.maxStamina, currentStats.stamina + staminaRegen)
|
||||
|
||||
currentStats.health = newHp
|
||||
currentStats.stamina = newStamina
|
||||
|
||||
// 只在有明显回复且每30秒输出一次日志
|
||||
if (gameStore.lastRegenLogTime === undefined) {
|
||||
gameStore.lastRegenLogTime = 0
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const shouldLog = (now - gameStore.lastRegenLogTime) > 30000 && // 30秒
|
||||
((newHp > oldHp && newHp < currentStats.maxHealth) ||
|
||||
(newStamina > oldStamina && newStamina < currentStats.maxStamina))
|
||||
|
||||
if (shouldLog) {
|
||||
gameStore.lastRegenLogTime = now
|
||||
if (isResting) {
|
||||
// 休息中的日志由休息任务处理,这里不重复输出
|
||||
} else if (newHp > oldHp && newStamina > oldStamina) {
|
||||
gameStore.addLog('自然回复:HP和耐力缓慢恢复中...', 'info')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏主tick - 每秒执行一次
|
||||
* @param {Object} gameStore - 游戏Store
|
||||
@@ -80,6 +155,11 @@ export function gameTick(gameStore, playerStore) {
|
||||
// 1. 更新游戏时间
|
||||
updateGameTime(gameStore)
|
||||
|
||||
// 1.5. 自然回复(仅在非战斗、非训练状态)
|
||||
if (!gameStore.inCombat && !isPlayerTraining(gameStore)) {
|
||||
processNaturalRegeneration(gameStore, playerStore)
|
||||
}
|
||||
|
||||
// 2. 处理战斗
|
||||
if (gameStore.inCombat && gameStore.combatState) {
|
||||
handleCombatTick(gameStore, playerStore)
|
||||
@@ -197,6 +277,9 @@ export function handleCombatVictory(gameStore, playerStore, combatResult) {
|
||||
// 添加日志
|
||||
gameStore.addLog(`战胜了 ${enemy.name}!`, 'reward')
|
||||
|
||||
// 收集所有经验获取信息,用于统一日志显示
|
||||
const expGains = []
|
||||
|
||||
// 给予经验值 (使用新的等级系统)
|
||||
if (enemy.expReward) {
|
||||
const adjustedExp = calculateCombatExp(
|
||||
@@ -204,7 +287,12 @@ export function handleCombatVictory(gameStore, playerStore, combatResult) {
|
||||
playerStore.level.current,
|
||||
enemy.expReward
|
||||
)
|
||||
addExp(playerStore, gameStore, adjustedExp)
|
||||
const levelResult = addExp(playerStore, gameStore, adjustedExp)
|
||||
expGains.push({
|
||||
type: '战斗经验',
|
||||
amount: adjustedExp,
|
||||
leveledUp: levelResult.leveledUp
|
||||
})
|
||||
}
|
||||
|
||||
// 给予武器技能经验奖励
|
||||
@@ -222,14 +310,32 @@ export function handleCombatVictory(gameStore, playerStore, combatResult) {
|
||||
|
||||
if (skillId) {
|
||||
const result = addSkillExp(playerStore, skillId, enemy.skillExpReward)
|
||||
const skillName = SKILL_CONFIG[skillId]?.name || skillId
|
||||
expGains.push({
|
||||
type: `${skillName}经验`,
|
||||
amount: enemy.skillExpReward,
|
||||
leveledUp: result.leveledUp,
|
||||
newLevel: result.newLevel
|
||||
})
|
||||
|
||||
if (result.leveledUp) {
|
||||
const skillName = SKILL_CONFIG[skillId]?.name || skillId
|
||||
gameStore.addLog(`${skillName}升级到了 Lv.${result.newLevel}!`, 'reward')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 统一显示经验获取日志
|
||||
if (expGains.length > 0) {
|
||||
const expText = expGains.map(e => {
|
||||
let text = `${e.type}+${e.amount}`
|
||||
if (e.leveledUp) {
|
||||
text += ` ⬆`
|
||||
}
|
||||
return text
|
||||
}).join(', ')
|
||||
gameStore.addLog(`获得: ${expText}`, 'info')
|
||||
}
|
||||
|
||||
// 处理掉落
|
||||
if (enemy.drops && enemy.drops.length > 0) {
|
||||
processDrops(gameStore, playerStore, enemy.drops)
|
||||
@@ -249,11 +355,17 @@ export function handleCombatVictory(gameStore, playerStore, combatResult) {
|
||||
playerStore.currentStats.stamina > 10
|
||||
|
||||
if (canContinue) {
|
||||
// 设置寻找中状态
|
||||
gameStore.isSearching = true
|
||||
gameStore.addLog('正在寻找新的敌人...', 'info')
|
||||
|
||||
// 延迟3秒后开始下一场战斗
|
||||
setTimeout(() => {
|
||||
if (!gameStore.inCombat && gameStore.autoCombat) {
|
||||
startAutoCombat(gameStore, playerStore)
|
||||
}
|
||||
// 清除寻找中状态(无论是否成功找到敌人)
|
||||
gameStore.isSearching = false
|
||||
}, 3000)
|
||||
} else {
|
||||
// 状态过低,自动关闭自动战斗
|
||||
@@ -275,6 +387,7 @@ function startAutoCombat(gameStore, playerStore) {
|
||||
|
||||
if (!locationEnemies || locationEnemies.length === 0) {
|
||||
gameStore.autoCombat = false
|
||||
gameStore.isSearching = false
|
||||
gameStore.addLog('当前区域没有敌人,自动战斗已停止', 'info')
|
||||
return
|
||||
}
|
||||
@@ -284,10 +397,13 @@ function startAutoCombat(gameStore, playerStore) {
|
||||
const enemyConfig = ENEMY_CONFIG[enemyId]
|
||||
|
||||
if (!enemyConfig) {
|
||||
gameStore.isSearching = false
|
||||
gameStore.addLog('敌人配置错误', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
// 清除寻找中状态
|
||||
gameStore.isSearching = false
|
||||
gameStore.addLog(`自动寻找中...遇到了 ${enemyConfig.name}!`, 'combat')
|
||||
const environment = getEnvironmentType(locationId)
|
||||
gameStore.combatState = initCombat(enemyId, enemyConfig, environment)
|
||||
|
||||
@@ -155,6 +155,7 @@ function serializeGameData(gameStore) {
|
||||
activeTasks: JSON.parse(JSON.stringify(gameStore.activeTasks || [])),
|
||||
negativeStatus: JSON.parse(JSON.stringify(gameStore.negativeStatus || [])),
|
||||
marketPrices: JSON.parse(JSON.stringify(gameStore.marketPrices || {})),
|
||||
autoCombat: gameStore.autoCombat || false, // 保存自动战斗状态
|
||||
// 不保存日志(logs不持久化)
|
||||
// 不保存战斗状态(重新登录时退出战斗)
|
||||
// 不保存当前事件(重新登录时清除)
|
||||
@@ -262,6 +263,11 @@ function applyGameData(gameStore, data) {
|
||||
gameStore.marketPrices = JSON.parse(JSON.stringify(data.marketPrices))
|
||||
}
|
||||
|
||||
// 自动战斗状态
|
||||
if (data.autoCombat !== undefined) {
|
||||
gameStore.autoCombat = data.autoCombat
|
||||
}
|
||||
|
||||
// 定时事件
|
||||
if (data.scheduledEvents) {
|
||||
gameStore.scheduledEvents = JSON.parse(JSON.stringify(data.scheduledEvents))
|
||||
|
||||
@@ -312,6 +312,9 @@ function processReadingTask(gameStore, playerStore, task, elapsedSeconds) {
|
||||
|
||||
const readingTime = bookConfig.readingTime || 60
|
||||
|
||||
// 首次阅读时解锁阅读技能
|
||||
unlockSkill(playerStore, 'reading')
|
||||
|
||||
// 检查环境惩罚
|
||||
const location = playerStore.currentLocation
|
||||
let timeMultiplier = 1.0
|
||||
@@ -322,9 +325,26 @@ function processReadingTask(gameStore, playerStore, task, elapsedSeconds) {
|
||||
timeMultiplier = 0.5 + (darkPenaltyReduce / 100) * 0.5
|
||||
}
|
||||
|
||||
// 阅读技能速度加成
|
||||
const readingSkillLevel = playerStore.skills.reading?.level || 0
|
||||
const readingSpeedBonus = playerStore.globalBonus?.readingSpeed || 1
|
||||
timeMultiplier *= readingSpeedBonus
|
||||
|
||||
// 计算有效进度
|
||||
const effectiveProgress = task.progress * timeMultiplier
|
||||
|
||||
// 给予阅读技能经验(每秒给予经验)
|
||||
const readingExpPerSecond = 1
|
||||
const readingExpGain = readingExpPerSecond * elapsedSeconds * timeMultiplier
|
||||
const readingResult = addSkillExp(playerStore, 'reading', readingExpGain)
|
||||
|
||||
// 检查技能是否升级
|
||||
if (readingResult.leveledUp) {
|
||||
if (gameStore.addLog) {
|
||||
gameStore.addLog(`阅读技能升级到了 Lv.${readingResult.newLevel}!`, 'reward')
|
||||
}
|
||||
}
|
||||
|
||||
if (effectiveProgress >= readingTime) {
|
||||
// 阅读完成
|
||||
const rewards = {}
|
||||
@@ -343,13 +363,13 @@ function processReadingTask(gameStore, playerStore, task, elapsedSeconds) {
|
||||
|
||||
// 添加日志
|
||||
if (gameStore.addLog) {
|
||||
gameStore.addLog(`读完了《${bookConfig.name}》`, 'reward')
|
||||
gameStore.addLog(`读完了《${bookConfig.name}》,阅读经验+${Math.floor(readingExpGain * task.progress / readingTime)}`, 'reward')
|
||||
}
|
||||
|
||||
return { completed: true, rewards }
|
||||
}
|
||||
|
||||
// 阅读进行中,给予少量经验
|
||||
// 阅读进行中,给予其他技能经验
|
||||
if (bookConfig.expReward) {
|
||||
for (const [skillId, expPerSecond] of Object.entries(bookConfig.expReward)) {
|
||||
const expPerTick = (expPerSecond / readingTime) * elapsedSeconds * timeMultiplier
|
||||
@@ -412,8 +432,8 @@ function processTrainingTask(gameStore, playerStore, task, elapsedSeconds) {
|
||||
return { completed: true, error: '技能不存在' }
|
||||
}
|
||||
|
||||
// 消耗耐力
|
||||
const staminaCost = 2 * elapsedSeconds
|
||||
// 消耗耐力(降低消耗)
|
||||
const staminaCost = 1 * elapsedSeconds
|
||||
if (playerStore.currentStats.stamina < staminaCost) {
|
||||
// 耐力不足,任务结束
|
||||
return { completed: true, rewards: {} }
|
||||
@@ -421,8 +441,8 @@ function processTrainingTask(gameStore, playerStore, task, elapsedSeconds) {
|
||||
|
||||
playerStore.currentStats.stamina -= staminaCost
|
||||
|
||||
// 给予技能经验
|
||||
const expPerSecond = 1
|
||||
// 给予技能经验(提升获取速度)
|
||||
const expPerSecond = 5 // 从1提升到5
|
||||
const expGain = expPerSecond * elapsedSeconds
|
||||
|
||||
if (!task.accumulatedExp) {
|
||||
@@ -433,6 +453,12 @@ function processTrainingTask(gameStore, playerStore, task, elapsedSeconds) {
|
||||
}
|
||||
task.accumulatedExp[skillId] += expGain
|
||||
|
||||
// 检查技能是否升级
|
||||
const result = addSkillExp(playerStore, skillId, expGain)
|
||||
if (result.leveledUp && gameStore.addLog) {
|
||||
gameStore.addLog(`${skillConfig.name}升级到了 Lv.${result.newLevel}!`, 'reward')
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (task.data.duration && task.progress >= task.data.duration) {
|
||||
const rewards = { [skillId]: task.accumulatedExp[skillId] || 0 }
|
||||
|
||||
Reference in New Issue
Block a user