Files
text-adventure-game/utils/gameLoop.js
Claude 16223c89a5 feat: 实现游戏核心系统和UI组件
核心系统:
- combatSystem: 战斗逻辑、伤害计算、战斗状态管理
- skillSystem: 技能系统、技能解锁、经验值、里程碑
- taskSystem: 任务系统、任务类型、任务执行和完成
- eventSystem: 事件系统、随机事件处理
- environmentSystem: 环境系统、时间流逝、区域效果
- levelingSystem: 升级系统、属性成长
- soundSystem: 音效系统

配置文件:
- enemies: 敌人配置、掉落表
- events: 事件配置、事件效果
- items: 物品配置、装备属性
- locations: 地点配置、探索事件
- skills: 技能配置、技能树

UI组件:
- CraftingDrawer: 制造界面
- InventoryDrawer: 背包界面
- 其他UI优化和动画

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 16:20:10 +08:00

560 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 游戏循环系统 - 主循环、时间推进、战斗处理、市场刷新
* 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
}