核心系统: - 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>
560 lines
15 KiB
JavaScript
560 lines
15 KiB
JavaScript
/**
|
||
* 游戏循环系统 - 主循环、时间推进、战斗处理、市场刷新
|
||
* 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
|
||
}
|