2026-01-21 17:13:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 游戏循环系统 - 主循环、时间推进、战斗处理、市场刷新
|
|
|
|
|
|
* 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'
|
2026-01-23 16:20:10 +08:00
|
|
|
|
import { addExp, calculateCombatExp } from './levelingSystem.js'
|
2026-01-21 17:13:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 游戏循环定时器
|
|
|
|
|
|
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')
|
|
|
|
|
|
|
2026-01-23 16:20:10 +08:00
|
|
|
|
// 给予经验值 (使用新的等级系统)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
if (enemy.expReward) {
|
2026-01-23 16:20:10 +08:00
|
|
|
|
const adjustedExp = calculateCombatExp(
|
|
|
|
|
|
enemy.level || 1,
|
|
|
|
|
|
playerStore.level.current,
|
|
|
|
|
|
enemy.expReward
|
|
|
|
|
|
)
|
|
|
|
|
|
addExp(playerStore, gameStore, adjustedExp)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 给予武器技能经验奖励
|
|
|
|
|
|
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) {
|
2026-01-23 16:20:10 +08:00
|
|
|
|
// 处理制造任务
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 17:13:51 +08:00
|
|
|
|
// 应用货币奖励
|
|
|
|
|
|
if (rewards.currency) {
|
|
|
|
|
|
playerStore.currency.copper += rewards.currency
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 应用技能经验
|
|
|
|
|
|
for (const [skillId, exp] of Object.entries(rewards)) {
|
2026-01-23 16:20:10 +08:00
|
|
|
|
if (skillId !== 'currency' && skillId !== 'completionBonus' && skillId !== 'craftedItem' && skillId !== 'craftedCount' && typeof exp === 'number') {
|
2026-01-21 17:13:51 +08:00
|
|
|
|
addSkillExp(playerStore, skillId, exp)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加完成日志
|
|
|
|
|
|
const taskNames = {
|
|
|
|
|
|
reading: '阅读',
|
|
|
|
|
|
resting: '休息',
|
|
|
|
|
|
training: '训练',
|
|
|
|
|
|
working: '工作',
|
2026-01-23 16:20:10 +08:00
|
|
|
|
praying: '祈祷',
|
|
|
|
|
|
crafting: '制造'
|
2026-01-21 17:13:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|