/** * 游戏循环系统 - 主循环、时间推进、战斗处理、市场刷新 * Phase 7 核心实现 */ import { GAME_CONSTANTS } from '@/config/constants.js' import { ITEM_CONFIG } from '@/config/items.js' import { SKILL_CONFIG } from '@/config/skills.js' import { ENEMY_CONFIG } from '@/config/enemies.js' import { combatTick, initCombat, getEnvironmentType } from './combatSystem.js' import { processTaskTick } from './taskSystem.js' import { processEnvironmentExp, processEnvironmentEffects } from './environmentSystem.js' import { checkScheduledEvents } from './eventSystem.js' import { addSkillExp } from './skillSystem.js' import { addItemToInventory } from './itemSystem.js' import { addExp, calculateCombatExp } from './levelingSystem.js' // 游戏循环定时器 let gameLoopInterval = null let isLoopRunning = false // 离线时间记录 let lastSaveTime = Date.now() /** * 启动游戏循环 * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store * @returns {boolean} 是否成功启动 */ export function startGameLoop(gameStore, playerStore) { if (isLoopRunning) { console.warn('Game loop is already running') return false } // 每秒执行一次游戏tick gameLoopInterval = setInterval(() => { gameTick(gameStore, playerStore) }, 1000) isLoopRunning = true console.log('Game loop started') return true } /** * 停止游戏循环 * @returns {boolean} 是否成功停止 */ export function stopGameLoop() { if (!isLoopRunning) { return false } if (gameLoopInterval) { clearInterval(gameLoopInterval) gameLoopInterval = null } isLoopRunning = false console.log('Game loop stopped') return true } /** * 检查游戏循环是否运行中 * @returns {boolean} */ 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 * @param {Object} playerStore - 玩家Store */ 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) } // 3. 处理任务 if (gameStore.activeTasks && gameStore.activeTasks.length > 0) { const completedTasks = processTaskTick(gameStore, playerStore, 1000) // 处理完成的任务奖励 for (const { task, rewards } of completedTasks) { handleTaskCompletion(gameStore, playerStore, task, rewards) } } // 4. 处理环境经验 processEnvironmentExp( gameStore, playerStore, playerStore.currentLocation, 1 ) // 5. 处理环境负面效果 processEnvironmentEffects( playerStore, playerStore.currentLocation, 1 ) // 6. 检查定时事件 checkScheduledEvents(gameStore, playerStore) // 7. 自动保存检查(每分钟保存一次) const currentTime = Date.now() if (currentTime - lastSaveTime >= 60000) { // 触发自动保存(由App.vue处理) lastSaveTime = currentTime if (gameStore.triggerAutoSave) { gameStore.triggerAutoSave() } } } /** * 更新游戏时间 * 现实1秒 = 游戏时间5分钟 * @param {Object} gameStore - 游戏Store */ export function updateGameTime(gameStore) { const { gameTime } = gameStore // 每秒增加5分钟 gameTime.totalMinutes += GAME_CONSTANTS.TIME_SCALE // 计算天、时、分 const minutesPerDay = 24 * 60 const minutesPerHour = 60 gameTime.day = Math.floor(gameTime.totalMinutes / minutesPerDay) + 1 const dayMinutes = gameTime.totalMinutes % minutesPerDay gameTime.hour = Math.floor(dayMinutes / minutesPerHour) gameTime.minute = dayMinutes % minutesPerHour // 检查是否需要刷新市场价格(每天刷新一次) if (gameTime.hour === 0 && gameTime.minute === 0) { refreshMarketPrices(gameStore) } } /** * 处理战斗tick * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store */ function handleCombatTick(gameStore, playerStore) { const result = combatTick(gameStore, playerStore, gameStore.combatState) // 添加战斗日志 if (result.logs && result.logs.length > 0) { for (const log of result.logs) { if (typeof log === 'string') { gameStore.addLog(log, 'combat') } else if (log.message) { gameStore.addLog(log.message, log.type || 'combat') } } } // 处理战斗结果 if (result.victory) { handleCombatVictory(gameStore, playerStore, result) } else if (result.defeat) { handleCombatDefeat(gameStore, playerStore, result) } else if (result.enemy) { // 更新敌人状态 gameStore.combatState.enemy = result.enemy gameStore.combatState.ticks++ } } /** * 处理战斗胜利 * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store * @param {Object} combatResult - 战斗结果 */ export function handleCombatVictory(gameStore, playerStore, combatResult) { const { enemy } = combatResult // 结束战斗状态 gameStore.inCombat = false gameStore.combatState = null // 添加日志 gameStore.addLog(`战胜了 ${enemy.name}!`, 'reward') // 收集所有经验获取信息,用于统一日志显示 const expGains = [] // 给予经验值 (使用新的等级系统) if (enemy.expReward) { const adjustedExp = calculateCombatExp( enemy.level || 1, playerStore.level.current, enemy.expReward ) const levelResult = addExp(playerStore, gameStore, adjustedExp) expGains.push({ type: '战斗经验', amount: adjustedExp, leveledUp: levelResult.leveledUp }) } // 给予武器技能经验奖励 if (enemy.skillExpReward && playerStore.equipment.weapon) { const weapon = playerStore.equipment.weapon // 根据武器ID确定技能ID let skillId = null if (weapon.id === 'wooden_stick') { skillId = 'stick_mastery' } else if (weapon.subtype === 'sword') { skillId = 'sword_mastery' } else if (weapon.subtype === 'axe') { skillId = 'axe_mastery' } 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) { 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) } // 触发战斗胜利事件 gameStore.triggerEvent?.('combat', { result: 'victory', enemyId: enemy.id, enemy }) // 自动战斗模式:战斗结束后继续寻找敌人 if (gameStore.autoCombat) { // 检查玩家状态是否允许继续战斗 const canContinue = playerStore.currentStats.health > 10 && 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 { // 状态过低,自动关闭自动战斗 gameStore.autoCombat = false gameStore.addLog('状态过低,自动战斗已停止', 'warning') } } } /** * 启动自动战斗(在当前位置寻找敌人) * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store */ function startAutoCombat(gameStore, playerStore) { // 检查当前位置是否有敌人 const locationId = playerStore.currentLocation const locationEnemies = LOCATION_ENEMIES[locationId] if (!locationEnemies || locationEnemies.length === 0) { gameStore.autoCombat = false gameStore.isSearching = false gameStore.addLog('当前区域没有敌人,自动战斗已停止', 'info') return } // 随机选择一个敌人 const enemyId = locationEnemies[Math.floor(Math.random() * locationEnemies.length)] 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) gameStore.inCombat = true } // 当前区域的敌人配置 const LOCATION_ENEMIES = { wild1: ['wild_dog'], boss_lair: ['test_boss'] } /** * 处理战斗失败 * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store * @param {Object} combatResult - 战斗结果 */ export function handleCombatDefeat(gameStore, playerStore, combatResult) { // 结束战斗状态 gameStore.inCombat = false gameStore.combatState = null // 添加日志 gameStore.addLog('你被击败了...', 'error') // 惩罚:回到营地 playerStore.currentLocation = 'camp' // 恢复部分状态(在营地) playerStore.currentStats.health = Math.floor(playerStore.currentStats.maxHealth * 0.3) playerStore.currentStats.stamina = Math.floor(playerStore.currentStats.maxStamina * 0.5) gameStore.addLog('你在营地醒来,损失了部分状态', 'info') // 触发战斗失败事件 gameStore.triggerEvent?.('combat', { result: 'defeat' }) } /** * 处理掉落物品 * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store * @param {Array} drops - 掉落列表 */ function processDrops(gameStore, playerStore, drops) { for (const drop of drops) { // 检查掉落概率 if (drop.chance && Math.random() > drop.chance) { continue } // 随机数量 const count = drop.count ? drop.count.min + Math.floor(Math.random() * (drop.count.max - drop.count.min + 1)) : 1 // 添加物品 const result = addItemToInventory(playerStore, drop.itemId, count) if (result.success) { const item = result.item gameStore.addLog(`获得了 ${item.name} x${count}`, 'reward') } } } /** * 处理任务完成 * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store * @param {Object} task - 完成的任务 * @param {Object} rewards - 奖励 */ function handleTaskCompletion(gameStore, playerStore, task, rewards) { // 处理制造任务 if (task.type === 'crafting') { if (rewards.success !== false && rewards.craftedItem) { // 制造成功,添加物品到背包 const { completeCrafting } = require('./craftingSystem.js') const result = completeCrafting(gameStore, playerStore, task, rewards) if (result.success) { gameStore.addLog(result.message, 'reward') } else if (result.failed) { gameStore.addLog(result.message, 'warning') } } else if (rewards.success === false) { // 制造失败 gameStore.addLog('制造失败,材料已消耗', 'warning') } return } // 应用货币奖励 if (rewards.currency) { playerStore.currency.copper += rewards.currency } // 应用技能经验 for (const [skillId, exp] of Object.entries(rewards)) { if (skillId !== 'currency' && skillId !== 'completionBonus' && skillId !== 'craftedItem' && skillId !== 'craftedCount' && typeof exp === 'number') { addSkillExp(playerStore, skillId, exp) } } // 添加完成日志 const taskNames = { reading: '阅读', resting: '休息', training: '训练', working: '工作', praying: '祈祷', crafting: '制造' } gameStore.addLog(`${taskNames[task.type]}任务完成`, 'reward') } /** * 刷新市场价格 * @param {Object} gameStore - 游戏Store */ export function refreshMarketPrices(gameStore) { const prices = {} // 为所有可交易物品生成新价格 for (const [itemId, config] of Object.entries(ITEM_CONFIG)) { if (config.baseValue > 0 && config.type !== 'key') { // 价格波动范围:0.5 - 1.5倍 const fluctuation = 0.5 + Math.random() prices[itemId] = { buyRate: fluctuation * 2, // 购买价格倍率 sellRate: fluctuation * 0.3 // 出售价格倍率 } } } gameStore.marketPrices = { lastRefreshDay: gameStore.gameTime?.day || 1, prices } gameStore.addLog('市场价格已更新', 'info') } /** * 获取物品的当前市场价格 * @param {Object} gameStore - 游戏Store * @param {String} itemId - 物品ID * @returns {Object} { buyPrice: number, sellPrice: number } */ export function getMarketPrice(gameStore, itemId) { const config = ITEM_CONFIG[itemId] if (!config || !config.baseValue) { return { buyPrice: 0, sellPrice: 0 } } const marketData = gameStore.marketPrices?.prices?.[itemId] const basePrice = config.baseValue if (marketData) { return { buyPrice: Math.floor(basePrice * marketData.buyRate), sellPrice: Math.floor(basePrice * marketData.sellRate) } } // 默认价格 return { buyPrice: Math.floor(basePrice * 2), sellPrice: Math.floor(basePrice * 0.3) } } /** * 计算离线收益 * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store * @param {Number} offlineSeconds - 离线秒数 * @returns {Object} 离线收益信息 */ export function calculateOfflineEarnings(gameStore, playerStore, offlineSeconds) { const maxOfflineSeconds = 8 * 60 * 60 // 最多计算8小时 const effectiveSeconds = Math.min(offlineSeconds, maxOfflineSeconds) const earnings = { time: effectiveSeconds, timeDisplay: formatOfflineTime(effectiveSeconds), skillExp: {}, currency: 0 } // 简化计算:基于离线时间给予少量技能经验 // 假设玩家在离线期间进行了基础训练 const baseExpRate = 0.1 // 每秒0.1经验 const totalExp = Math.floor(baseExpRate * effectiveSeconds) // 将经验分配给已解锁的技能 const unlockedSkills = Object.keys(playerStore.skills || {}).filter( skillId => playerStore.skills[skillId]?.unlocked ) if (unlockedSkills.length > 0) { const expPerSkill = Math.floor(totalExp / unlockedSkills.length) for (const skillId of unlockedSkills) { earnings.skillExp[skillId] = expPerSkill } } // 离线货币收益(极少) earnings.currency = Math.floor(effectiveSeconds * 0.01) return earnings } /** * 应用离线收益 * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store * @param {Object} earnings - 离线收益 */ export function applyOfflineEarnings(gameStore, playerStore, earnings) { // 应用技能经验 for (const [skillId, exp] of Object.entries(earnings.skillExp)) { addSkillExp(playerStore, skillId, exp) } // 应用货币 if (earnings.currency > 0) { playerStore.currency.copper += earnings.currency } // 添加日志 gameStore.addLog( `离线 ${earnings.timeDisplay},获得了一些经验`, 'info' ) } /** * 格式化离线时间 * @param {Number} seconds - 秒数 * @returns {String} 格式化的时间 */ function formatOfflineTime(seconds) { const hours = Math.floor(seconds / 3600) const minutes = Math.floor((seconds % 3600) / 60) if (hours > 0) { return `${hours}小时${minutes}分钟` } return `${minutes}分钟` } /** * 更新最后保存时间 */ export function updateLastSaveTime() { lastSaveTime = Date.now() } /** * 获取最后保存时间 * @returns {Number} 时间戳 */ export function getLastSaveTime() { return lastSaveTime }