Files
text-adventure-game/utils/gameLoop.js

560 lines
15 KiB
JavaScript
Raw Normal View History

/**
* 游戏循环系统 - 主循环时间推进战斗处理市场刷新
* 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
}
/**
* 游戏主tick - 每秒执行一次
* @param {Object} gameStore - 游戏Store
* @param {Object} playerStore - 玩家Store
*/
export function gameTick(gameStore, playerStore) {
// 1. 更新游戏时间
updateGameTime(gameStore)
// 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')
// 给予经验值 (使用新的等级系统)
if (enemy.expReward) {
const adjustedExp = calculateCombatExp(
enemy.level || 1,
playerStore.level.current,
enemy.expReward
)
addExp(playerStore, gameStore, adjustedExp)
}
// 给予武器技能经验奖励
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)
if (result.leveledUp) {
const skillName = SKILL_CONFIG[skillId]?.name || skillId
gameStore.addLog(`${skillName}升级到了 Lv.${result.newLevel}!`, 'reward')
}
}
}
// 处理掉落
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) {
// 延迟3秒后开始下一场战斗
setTimeout(() => {
if (!gameStore.inCombat && gameStore.autoCombat) {
startAutoCombat(gameStore, playerStore)
}
}, 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.addLog('当前区域没有敌人,自动战斗已停止', 'info')
return
}
// 随机选择一个敌人
const enemyId = locationEnemies[Math.floor(Math.random() * locationEnemies.length)]
const enemyConfig = ENEMY_CONFIG[enemyId]
if (!enemyConfig) {
gameStore.addLog('敌人配置错误', 'error')
return
}
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
}