2026-01-21 17:13:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 事件系统 - 事件触发检测、对话管理
|
|
|
|
|
|
* Phase 6 核心系统实现
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { NPC_CONFIG } from '@/config/npcs.js'
|
|
|
|
|
|
import { EVENT_CONFIG } from '@/config/events.js'
|
|
|
|
|
|
import { LOCATION_CONFIG } from '@/config/locations.js'
|
2026-01-23 16:20:10 +08:00
|
|
|
|
import { SKILL_CONFIG } from '@/config/skills.js'
|
|
|
|
|
|
import { ENEMY_CONFIG } from '@/config/enemies.js'
|
2026-02-02 15:52:32 +08:00
|
|
|
|
import { ITEM_CONFIG } from '@/config/items.js'
|
2026-01-21 17:13:51 +08:00
|
|
|
|
import { unlockSkill } from './skillSystem.js'
|
|
|
|
|
|
import { addItemToInventory } from './itemSystem.js'
|
2026-01-23 16:20:10 +08:00
|
|
|
|
import { initCombat, getEnvironmentType } from './combatSystem.js'
|
2026-02-02 15:52:32 +08:00
|
|
|
|
import { installProsthetic, removeProsthetic } from './prostheticSystem.js'
|
2026-01-21 17:13:51 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查并触发事件
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @param {String} triggerType - 触发类型 'enter' | 'action' | 'timer'
|
|
|
|
|
|
* @param {Object} context - 触发上下文
|
|
|
|
|
|
* @returns {Object|null} 触发的事件信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function checkAndTriggerEvent(gameStore, playerStore, triggerType = 'enter', context = {}) {
|
|
|
|
|
|
// 检查首次进入事件
|
|
|
|
|
|
if (!playerStore.flags) {
|
|
|
|
|
|
playerStore.flags = {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新游戏初始化事件
|
|
|
|
|
|
if (!playerStore.flags.introTriggered) {
|
|
|
|
|
|
playerStore.flags.introTriggered = true
|
|
|
|
|
|
triggerEvent(gameStore, 'intro', { playerStore })
|
|
|
|
|
|
return { eventId: 'intro', type: 'story' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查位置事件
|
|
|
|
|
|
if (triggerType === 'enter') {
|
|
|
|
|
|
const locationEvent = checkLocationEvent(gameStore, playerStore, context.locationId)
|
|
|
|
|
|
if (locationEvent) {
|
|
|
|
|
|
return locationEvent
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查NPC对话事件
|
|
|
|
|
|
if (triggerType === 'npc') {
|
|
|
|
|
|
const npcEvent = checkNPCDialogue(gameStore, playerStore, context.npcId)
|
|
|
|
|
|
if (npcEvent) {
|
|
|
|
|
|
return npcEvent
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查战斗相关事件
|
|
|
|
|
|
if (triggerType === 'combat') {
|
|
|
|
|
|
const combatEvent = checkCombatEvent(gameStore, playerStore, context)
|
|
|
|
|
|
if (combatEvent) {
|
|
|
|
|
|
return combatEvent
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查解锁事件
|
|
|
|
|
|
if (triggerType === 'unlock') {
|
|
|
|
|
|
const unlockEvent = checkUnlockEvent(gameStore, playerStore, context)
|
|
|
|
|
|
if (unlockEvent) {
|
|
|
|
|
|
return unlockEvent
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查位置事件
|
|
|
|
|
|
*/
|
|
|
|
|
|
function checkLocationEvent(gameStore, playerStore, locationId) {
|
|
|
|
|
|
const location = LOCATION_CONFIG[locationId]
|
|
|
|
|
|
if (!location) return null
|
|
|
|
|
|
|
|
|
|
|
|
// 首次进入地下室警告
|
|
|
|
|
|
if (locationId === 'basement' && !playerStore.flags.basementFirstEnter) {
|
|
|
|
|
|
playerStore.flags.basementFirstEnter = true
|
|
|
|
|
|
triggerEvent(gameStore, 'dark_warning', { locationName: location.name })
|
|
|
|
|
|
return { eventId: 'dark_warning', type: 'tips' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 首次进入野外战斗提示
|
|
|
|
|
|
if (locationId === 'wild1' && !playerStore.flags.wild1FirstEnter) {
|
|
|
|
|
|
playerStore.flags.wild1FirstEnter = true
|
|
|
|
|
|
triggerEvent(gameStore, 'first_combat', { locationName: location.name })
|
|
|
|
|
|
return { eventId: 'first_combat', type: 'tips' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查NPC对话事件
|
|
|
|
|
|
*/
|
|
|
|
|
|
function checkNPCDialogue(gameStore, playerStore, npcId) {
|
|
|
|
|
|
const npc = NPC_CONFIG[npcId]
|
|
|
|
|
|
if (!npc) return null
|
|
|
|
|
|
|
|
|
|
|
|
// 启动NPC对话
|
|
|
|
|
|
startNPCDialogue(gameStore, npcId, 'first')
|
|
|
|
|
|
return { eventId: `npc_${npcId}`, type: 'dialogue' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查战斗事件
|
|
|
|
|
|
*/
|
|
|
|
|
|
function checkCombatEvent(gameStore, playerStore, context) {
|
|
|
|
|
|
// 检查Boss解锁
|
|
|
|
|
|
if (context.result === 'victory' && context.enemyId === 'wild_dog') {
|
|
|
|
|
|
const killCount = (playerStore.killCount || {})[context.enemyId] || 0
|
|
|
|
|
|
playerStore.killCount = playerStore.killCount || {}
|
|
|
|
|
|
playerStore.killCount[context.enemyId] = killCount + 1
|
|
|
|
|
|
|
|
|
|
|
|
// 击杀5只野狗解锁Boss巢
|
|
|
|
|
|
if (playerStore.killCount[context.enemyId] >= 5 && !playerStore.flags.bossUnlockTriggered) {
|
|
|
|
|
|
playerStore.flags.bossUnlockTriggered = true
|
|
|
|
|
|
triggerEvent(gameStore, 'boss_unlock', {})
|
|
|
|
|
|
return { eventId: 'boss_unlock', type: 'unlock' }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Boss击败事件
|
|
|
|
|
|
if (context.result === 'victory' && context.enemyId === 'test_boss' && !playerStore.flags.bossDefeatTriggered) {
|
|
|
|
|
|
playerStore.flags.bossDefeatTriggered = true
|
|
|
|
|
|
triggerEvent(gameStore, 'boss_defeat', {})
|
|
|
|
|
|
return { eventId: 'boss_defeat', type: 'story' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查解锁事件
|
|
|
|
|
|
*/
|
|
|
|
|
|
function checkUnlockEvent(gameStore, playerStore, context) {
|
|
|
|
|
|
// 区域解锁事件
|
|
|
|
|
|
if (context.unlockType === 'location' && context.locationId === 'boss_lair') {
|
|
|
|
|
|
// 已在战斗事件中处理
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 触发事件
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {String} eventId - 事件ID
|
|
|
|
|
|
* @param {Object} context - 事件上下文
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function triggerEvent(gameStore, eventId, context = {}) {
|
|
|
|
|
|
const config = EVENT_CONFIG[eventId]
|
|
|
|
|
|
if (!config) {
|
|
|
|
|
|
console.warn(`Event ${eventId} not found`)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建事件数据
|
|
|
|
|
|
const eventData = {
|
|
|
|
|
|
id: eventId,
|
|
|
|
|
|
type: config.type,
|
|
|
|
|
|
title: config.title || '',
|
|
|
|
|
|
text: config.text || '',
|
|
|
|
|
|
npc: config.npcId ? NPC_CONFIG[config.npcId] : null,
|
|
|
|
|
|
choices: config.choices || [],
|
|
|
|
|
|
actions: config.actions || [],
|
|
|
|
|
|
context,
|
|
|
|
|
|
triggers: config.triggers || []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理动态文本(替换变量)
|
|
|
|
|
|
if (config.textTemplate && context) {
|
|
|
|
|
|
eventData.text = renderTemplate(config.textTemplate, context)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置当前事件
|
|
|
|
|
|
gameStore.currentEvent = eventData
|
|
|
|
|
|
|
|
|
|
|
|
// 打开事件抽屉
|
|
|
|
|
|
gameStore.drawerState.event = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 渲染模板文本
|
|
|
|
|
|
*/
|
|
|
|
|
|
function renderTemplate(template, context) {
|
|
|
|
|
|
let text = template
|
|
|
|
|
|
for (const [key, value] of Object.entries(context)) {
|
|
|
|
|
|
text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), value)
|
|
|
|
|
|
}
|
|
|
|
|
|
return text
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动NPC对话
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {String} npcId - NPC ID
|
|
|
|
|
|
* @param {String} dialogueKey - 对话键名
|
2026-02-02 15:52:32 +08:00
|
|
|
|
* @param {Object} playerStore - 玩家Store(用于动态选项生成)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
*/
|
2026-02-02 15:52:32 +08:00
|
|
|
|
export function startNPCDialogue(gameStore, npcId, dialogueKey = 'first', playerStore = null) {
|
2026-01-21 17:13:51 +08:00
|
|
|
|
const npc = NPC_CONFIG[npcId]
|
|
|
|
|
|
if (!npc) {
|
|
|
|
|
|
console.warn(`NPC ${npcId} not found`)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const dialogue = npc.dialogue[dialogueKey]
|
|
|
|
|
|
if (!dialogue) {
|
|
|
|
|
|
console.warn(`Dialogue ${dialogueKey} not found for NPC ${npcId}`)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 15:52:32 +08:00
|
|
|
|
// 处理模板变量
|
|
|
|
|
|
let text = dialogue.text
|
|
|
|
|
|
if (playerStore) {
|
|
|
|
|
|
text = renderDialogueTemplate(text, playerStore, dialogue)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成选项列表(包括动态选项)
|
|
|
|
|
|
let choices = dialogue.choices || []
|
|
|
|
|
|
if (dialogue.dynamicChoices && playerStore) {
|
|
|
|
|
|
const dynamicChoices = generateDynamicChoices(dialogue.dynamicChoices, playerStore)
|
|
|
|
|
|
choices = [...dynamicChoices, ...choices]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 17:13:51 +08:00
|
|
|
|
// 构建事件数据
|
|
|
|
|
|
const eventData = {
|
|
|
|
|
|
id: `npc_${npcId}_${dialogueKey}`,
|
|
|
|
|
|
type: 'dialogue',
|
|
|
|
|
|
title: npc.name,
|
2026-02-02 15:52:32 +08:00
|
|
|
|
text: text,
|
2026-01-21 17:13:51 +08:00
|
|
|
|
npc: npc,
|
2026-02-02 15:52:32 +08:00
|
|
|
|
choices: choices,
|
2026-01-21 17:13:51 +08:00
|
|
|
|
currentDialogue: dialogueKey,
|
|
|
|
|
|
npcId
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gameStore.currentEvent = eventData
|
|
|
|
|
|
gameStore.drawerState.event = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 15:52:32 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 渲染对话模板(替换变量)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function renderDialogueTemplate(template, playerStore, dialogue) {
|
|
|
|
|
|
let text = template
|
|
|
|
|
|
|
|
|
|
|
|
// 替换义体列表
|
|
|
|
|
|
if (text.includes('{prosthetic_list}')) {
|
|
|
|
|
|
const prostheticList = getProstheticInventoryList(playerStore)
|
|
|
|
|
|
text = text.replace('{prosthetic_list}', prostheticList)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 替换已装备的义体
|
|
|
|
|
|
if (text.includes('{equipped_prosthetic}')) {
|
|
|
|
|
|
const equipped = playerStore.equipment?.prosthetic
|
|
|
|
|
|
const equippedText = equipped ? `${equipped.name} (${equipped.description})` : '无'
|
|
|
|
|
|
text = text.replace('{equipped_prosthetic}', equippedText)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return text
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取玩家背包中的义体列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getProstheticInventoryList(playerStore) {
|
|
|
|
|
|
const prosthetics = playerStore.inventory.filter(item => item.type === 'prosthetic')
|
|
|
|
|
|
if (prosthetics.length === 0) {
|
|
|
|
|
|
return '(你没有义体可以安装)'
|
|
|
|
|
|
}
|
|
|
|
|
|
return prosthetics.map(p => `• ${p.name}: ${p.description}`).join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成动态选项
|
|
|
|
|
|
*/
|
|
|
|
|
|
function generateDynamicChoices(type, playerStore) {
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case 'prosthetic_inventory':
|
|
|
|
|
|
// 生成义体安装选项
|
|
|
|
|
|
const prosthetics = playerStore.inventory.filter(item => item.type === 'prosthetic')
|
|
|
|
|
|
if (prosthetics.length === 0) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
return prosthetics.map(p => ({
|
|
|
|
|
|
text: `安装 ${p.name}`,
|
|
|
|
|
|
action: 'install_prosthetic',
|
|
|
|
|
|
actionData: { itemId: p.id }
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 17:13:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 处理对话选择
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @param {Object} choice - 选择项
|
|
|
|
|
|
* @returns {Object} 处理结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function handleDialogueChoice(gameStore, playerStore, choice) {
|
|
|
|
|
|
const result = {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
closeEvent: false,
|
|
|
|
|
|
message: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理对话动作
|
|
|
|
|
|
if (choice.action) {
|
|
|
|
|
|
const actionResult = processDialogueAction(gameStore, playerStore, choice.action, choice.actionData)
|
|
|
|
|
|
result.message = actionResult.message
|
|
|
|
|
|
if (actionResult.closeEvent) {
|
|
|
|
|
|
result.closeEvent = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查下一步
|
|
|
|
|
|
if (choice.next === null) {
|
|
|
|
|
|
// 对话结束
|
|
|
|
|
|
result.closeEvent = true
|
|
|
|
|
|
} else if (choice.next) {
|
|
|
|
|
|
// 继续对话
|
|
|
|
|
|
const currentEvent = gameStore.currentEvent
|
|
|
|
|
|
if (currentEvent && currentEvent.npc) {
|
2026-02-02 15:52:32 +08:00
|
|
|
|
startNPCDialogue(gameStore, currentEvent.npc.id, choice.next, playerStore)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
result.closeEvent = false
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (!choice.action) {
|
|
|
|
|
|
// 没有next也没有action,关闭
|
|
|
|
|
|
result.closeEvent = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果需要关闭事件
|
|
|
|
|
|
if (result.closeEvent) {
|
|
|
|
|
|
gameStore.drawerState.event = false
|
|
|
|
|
|
gameStore.currentEvent = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理对话动作
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @param {String} action - 动作类型
|
|
|
|
|
|
* @param {Object} actionData - 动作数据
|
|
|
|
|
|
* @returns {Object} 处理结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function processDialogueAction(gameStore, playerStore, action, actionData = {}) {
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
|
case 'give_stick':
|
|
|
|
|
|
// 给予木棍
|
|
|
|
|
|
const stickResult = addItemToInventory(playerStore, 'wooden_stick', 1, 100)
|
|
|
|
|
|
if (stickResult.success) {
|
|
|
|
|
|
// 自动解锁木棍精通技能
|
|
|
|
|
|
unlockSkill(playerStore, 'stick_mastery')
|
|
|
|
|
|
if (gameStore.addLog) {
|
|
|
|
|
|
gameStore.addLog('获得了 木棍', 'reward')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: true, message: '获得了木棍', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'open_shop':
|
|
|
|
|
|
// 打开商店
|
|
|
|
|
|
gameStore.drawerState.event = false
|
|
|
|
|
|
// TODO: 打开商店抽屉
|
|
|
|
|
|
gameStore.drawerState.shop = true
|
|
|
|
|
|
return { success: true, message: '', closeEvent: true }
|
|
|
|
|
|
|
|
|
|
|
|
case 'give_item':
|
|
|
|
|
|
// 给予物品
|
|
|
|
|
|
if (actionData.itemId) {
|
|
|
|
|
|
const itemResult = addItemToInventory(
|
|
|
|
|
|
playerStore,
|
|
|
|
|
|
actionData.itemId,
|
|
|
|
|
|
actionData.count || 1,
|
|
|
|
|
|
actionData.quality || null
|
|
|
|
|
|
)
|
|
|
|
|
|
if (itemResult.success && gameStore.addLog) {
|
|
|
|
|
|
gameStore.addLog(`获得了 ${itemResult.item.name}`, 'reward')
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: true, message: `获得了 ${actionData.itemId}`, closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '物品参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'unlock_skill':
|
|
|
|
|
|
// 解锁技能
|
|
|
|
|
|
if (actionData.skillId) {
|
|
|
|
|
|
unlockSkill(playerStore, actionData.skillId)
|
|
|
|
|
|
if (gameStore.addLog) {
|
|
|
|
|
|
const skillConfig = SKILL_CONFIG[actionData.skillId]
|
|
|
|
|
|
gameStore.addLog(`解锁了技能: ${skillConfig?.name || actionData.skillId}`, 'system')
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: true, message: `解锁了技能 ${actionData.skillId}`, closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '技能参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'heal':
|
|
|
|
|
|
// 治疗
|
|
|
|
|
|
if (actionData.amount) {
|
|
|
|
|
|
playerStore.currentStats.health = Math.min(
|
|
|
|
|
|
playerStore.currentStats.maxHealth,
|
|
|
|
|
|
playerStore.currentStats.health + actionData.amount
|
|
|
|
|
|
)
|
|
|
|
|
|
return { success: true, message: `恢复了${actionData.amount}生命`, closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '治疗参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'restore_stamina':
|
|
|
|
|
|
// 恢复耐力
|
|
|
|
|
|
if (actionData.amount) {
|
|
|
|
|
|
playerStore.currentStats.stamina = Math.min(
|
|
|
|
|
|
playerStore.currentStats.maxStamina,
|
|
|
|
|
|
playerStore.currentStats.stamina + actionData.amount
|
|
|
|
|
|
)
|
|
|
|
|
|
return { success: true, message: `恢复了${actionData.amount}耐力`, closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '耐力参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'teleport':
|
|
|
|
|
|
// 传送
|
|
|
|
|
|
if (actionData.locationId) {
|
|
|
|
|
|
playerStore.currentLocation = actionData.locationId
|
|
|
|
|
|
if (gameStore.addLog) {
|
|
|
|
|
|
const location = LOCATION_CONFIG[actionData.locationId]
|
|
|
|
|
|
gameStore.addLog(`来到了 ${location?.name || actionData.locationId}`, 'system')
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: true, message: `传送到 ${actionData.locationId}`, closeEvent: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '位置参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'add_currency':
|
|
|
|
|
|
// 添加货币
|
|
|
|
|
|
if (actionData.amount) {
|
|
|
|
|
|
playerStore.currency.copper += actionData.amount
|
|
|
|
|
|
if (gameStore.addLog) {
|
|
|
|
|
|
gameStore.addLog(`获得了 ${actionData.amount} 铜币`, 'reward')
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: true, message: `获得了${actionData.amount}铜币`, closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '货币参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'set_flag':
|
|
|
|
|
|
// 设置标志
|
|
|
|
|
|
if (actionData.flag) {
|
|
|
|
|
|
playerStore.flags = playerStore.flags || {}
|
|
|
|
|
|
playerStore.flags[actionData.flag] = actionData.value !== undefined ? actionData.value : true
|
|
|
|
|
|
return { success: true, message: `设置标志 ${actionData.flag}`, closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '标志参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'check_flag':
|
|
|
|
|
|
// 检查标志(返回结果由调用者处理)
|
|
|
|
|
|
if (actionData.flag) {
|
|
|
|
|
|
const value = playerStore.flags?.[actionData.flag]
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '',
|
|
|
|
|
|
closeEvent: false,
|
|
|
|
|
|
flagValue: value
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '标志参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'start_combat':
|
|
|
|
|
|
// 开始战斗
|
|
|
|
|
|
if (actionData.enemyId) {
|
|
|
|
|
|
gameStore.drawerState.event = false
|
2026-01-23 16:20:10 +08:00
|
|
|
|
// 战斗已在探索事件中初始化,这里只是关闭事件抽屉
|
2026-01-21 17:13:51 +08:00
|
|
|
|
return { success: true, message: '开始战斗', closeEvent: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '敌人参数错误', closeEvent: false }
|
|
|
|
|
|
|
2026-01-23 16:20:10 +08:00
|
|
|
|
case 'flee':
|
|
|
|
|
|
// 逃跑
|
|
|
|
|
|
gameStore.drawerState.event = false
|
|
|
|
|
|
return tryFlee(gameStore, playerStore)
|
|
|
|
|
|
|
2026-01-21 17:13:51 +08:00
|
|
|
|
case 'close':
|
|
|
|
|
|
// 直接关闭
|
|
|
|
|
|
return { success: true, message: '', closeEvent: true }
|
|
|
|
|
|
|
2026-02-02 15:52:32 +08:00
|
|
|
|
case 'install_prosthetic':
|
|
|
|
|
|
// 安装义体
|
|
|
|
|
|
if (actionData.itemId) {
|
|
|
|
|
|
const item = playerStore.inventory.find(i => i.id === actionData.itemId)
|
|
|
|
|
|
if (!item) {
|
|
|
|
|
|
return { success: false, message: '找不到该义体', closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
const installResult = installProsthetic(playerStore, gameStore, item)
|
|
|
|
|
|
if (installResult.success) {
|
|
|
|
|
|
// 从背包移除义体
|
|
|
|
|
|
const index = playerStore.inventory.findIndex(i => i.id === actionData.itemId)
|
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
|
playerStore.inventory.splice(index, 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 解锁义体技能
|
|
|
|
|
|
if (item.grantedSkill) {
|
|
|
|
|
|
unlockSkill(playerStore, item.grantedSkill)
|
|
|
|
|
|
if (gameStore.addLog) {
|
|
|
|
|
|
gameStore.addLog(`解锁了技能: ${SKILL_CONFIG[item.grantedSkill]?.name || item.grantedSkill}`, 'system')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 更新成就进度
|
|
|
|
|
|
gameStore.achievementProgress.prostheticEquipped = 1
|
|
|
|
|
|
gameStore.checkAchievements('equip_prosthetic', { prostheticCount: 1 })
|
|
|
|
|
|
// 重新显示对话框
|
|
|
|
|
|
const currentEvent = gameStore.currentEvent
|
|
|
|
|
|
if (currentEvent && currentEvent.npc) {
|
|
|
|
|
|
startNPCDialogue(gameStore, currentEvent.npc.id, 'first', playerStore)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: installResult.success, message: installResult.message, closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '义体参数错误', closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'remove_prosthetic':
|
|
|
|
|
|
// 卸下义体
|
|
|
|
|
|
const removeResult = removeProsthetic(playerStore, gameStore)
|
|
|
|
|
|
if (removeResult.success) {
|
|
|
|
|
|
// 重新显示对话框
|
|
|
|
|
|
const currentEvent = gameStore.currentEvent
|
|
|
|
|
|
if (currentEvent && currentEvent.npc) {
|
|
|
|
|
|
startNPCDialogue(gameStore, currentEvent.npc.id, 'first', playerStore)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: removeResult.success, message: removeResult.message, closeEvent: false }
|
|
|
|
|
|
|
|
|
|
|
|
case 'refresh_list':
|
|
|
|
|
|
// 刷新列表(重新显示当前对话框)
|
|
|
|
|
|
const evt = gameStore.currentEvent
|
|
|
|
|
|
if (evt && evt.npc && evt.currentDialogue) {
|
|
|
|
|
|
startNPCDialogue(gameStore, evt.npc.id, evt.currentDialogue, playerStore)
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: true, message: '', closeEvent: false }
|
|
|
|
|
|
|
2026-01-21 17:13:51 +08:00
|
|
|
|
default:
|
|
|
|
|
|
console.warn(`Unknown dialogue action: ${action}`)
|
|
|
|
|
|
return { success: false, message: '未知动作', closeEvent: false }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理事件选择
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @param {Number} choiceIndex - 选择索引
|
|
|
|
|
|
* @returns {Object} 处理结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function handleEventChoice(gameStore, playerStore, choiceIndex) {
|
|
|
|
|
|
const currentEvent = gameStore.currentEvent
|
|
|
|
|
|
if (!currentEvent || !currentEvent.choices || !currentEvent.choices[choiceIndex]) {
|
|
|
|
|
|
return { success: false, message: '选择无效' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const choice = currentEvent.choices[choiceIndex]
|
|
|
|
|
|
return handleDialogueChoice(gameStore, playerStore, choice)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 关闭当前事件
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function closeEvent(gameStore) {
|
|
|
|
|
|
gameStore.drawerState.event = false
|
|
|
|
|
|
gameStore.currentEvent = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前事件信息
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @returns {Object|null} 当前事件
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getCurrentEvent(gameStore) {
|
|
|
|
|
|
return gameStore.currentEvent
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查NPC是否可用
|
|
|
|
|
|
* @param {String} npcId - NPC ID
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function isNPCAvailable(npcId, playerStore) {
|
|
|
|
|
|
const npc = NPC_CONFIG[npcId]
|
|
|
|
|
|
if (!npc) return false
|
|
|
|
|
|
|
|
|
|
|
|
// 检查位置
|
|
|
|
|
|
if (npc.location && playerStore.currentLocation !== npc.location) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查解锁条件
|
|
|
|
|
|
if (npc.unlockCondition) {
|
|
|
|
|
|
const condition = npc.unlockCondition
|
|
|
|
|
|
if (condition.flag && !playerStore.flags?.[condition.flag]) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
if (condition.item && !playerStore.inventory.some(i => i.id === condition.item)) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前位置可用的NPC列表
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @returns {Array} NPC ID列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getAvailableNPCs(playerStore) {
|
|
|
|
|
|
const location = LOCATION_CONFIG[playerStore.currentLocation]
|
|
|
|
|
|
if (!location || !location.npcs) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return location.npcs.filter(npcId => isNPCAvailable(npcId, playerStore))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置定时事件
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {String} eventId - 事件ID
|
|
|
|
|
|
* @param {Number} delayMs - 延迟时间(毫秒)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function scheduleEvent(gameStore, eventId, delayMs) {
|
|
|
|
|
|
if (!gameStore.scheduledEvents) {
|
|
|
|
|
|
gameStore.scheduledEvents = []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gameStore.scheduledEvents.push({
|
|
|
|
|
|
eventId,
|
|
|
|
|
|
triggerTime: Date.now() + delayMs,
|
|
|
|
|
|
triggered: false
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查并触发定时事件
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @returns {Array} 触发的事件列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function checkScheduledEvents(gameStore, playerStore) {
|
|
|
|
|
|
if (!gameStore.scheduledEvents) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const now = Date.now()
|
|
|
|
|
|
const triggeredEvents = []
|
|
|
|
|
|
|
|
|
|
|
|
for (const event of gameStore.scheduledEvents) {
|
|
|
|
|
|
if (!event.triggered && now >= event.triggerTime) {
|
|
|
|
|
|
event.triggered = true
|
|
|
|
|
|
triggerEvent(gameStore, event.eventId, { playerStore })
|
|
|
|
|
|
triggeredEvents.push(event.eventId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理已触发的事件
|
|
|
|
|
|
gameStore.scheduledEvents = gameStore.scheduledEvents.filter(e => !e.triggered)
|
|
|
|
|
|
|
|
|
|
|
|
return triggeredEvents
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清除所有定时事件
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function clearScheduledEvents(gameStore) {
|
|
|
|
|
|
gameStore.scheduledEvents = []
|
|
|
|
|
|
}
|
2026-01-23 16:20:10 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 触发探索事件
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @returns {Object} 探索结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function triggerExploreEvent(gameStore, playerStore) {
|
|
|
|
|
|
const location = LOCATION_CONFIG[playerStore.currentLocation]
|
|
|
|
|
|
if (!location) {
|
|
|
|
|
|
return { success: false, message: '当前位置错误' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查当前位置是否有敌人配置
|
|
|
|
|
|
const availableEnemies = location.enemies || []
|
|
|
|
|
|
if (availableEnemies.length === 0) {
|
|
|
|
|
|
return { success: false, message: '这里没什么可探索的' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 随机决定探索结果
|
|
|
|
|
|
const roll = Math.random()
|
|
|
|
|
|
|
|
|
|
|
|
// 40% 没发现
|
|
|
|
|
|
if (roll < 0.4) {
|
|
|
|
|
|
triggerEvent(gameStore, 'explore_nothing', {})
|
|
|
|
|
|
return { success: true, type: 'nothing' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 25% 发现草药
|
|
|
|
|
|
if (roll < 0.65) {
|
|
|
|
|
|
const herbCount = Math.floor(Math.random() * 3) + 1
|
|
|
|
|
|
const result = addItemToInventory(playerStore, 'healing_herb', herbCount, 100)
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
triggerEvent(gameStore, 'explore_find_herb', {})
|
|
|
|
|
|
return { success: true, type: 'find', item: 'healing_herb', count: herbCount }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 20% 发现铜币 (1-5枚)
|
|
|
|
|
|
if (roll < 0.85) {
|
|
|
|
|
|
const coinAmount = Math.floor(Math.random() * 5) + 1
|
|
|
|
|
|
playerStore.currency.copper += coinAmount
|
|
|
|
|
|
if (gameStore.addLog) {
|
|
|
|
|
|
gameStore.addLog(`探索发现了 ${coinAmount} 铜币`, 'reward')
|
|
|
|
|
|
}
|
|
|
|
|
|
triggerEvent(gameStore, 'explore_find_coin', { amount: coinAmount })
|
|
|
|
|
|
return { success: true, type: 'coin', amount: coinAmount }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 15% 遭遇敌人
|
|
|
|
|
|
const enemyId = availableEnemies[Math.floor(Math.random() * availableEnemies.length)]
|
|
|
|
|
|
const enemyConfig = ENEMY_CONFIG[enemyId]
|
|
|
|
|
|
|
|
|
|
|
|
if (enemyConfig) {
|
|
|
|
|
|
const environment = getEnvironmentType(playerStore.currentLocation)
|
|
|
|
|
|
gameStore.combatState = initCombat(enemyId, enemyConfig, environment)
|
|
|
|
|
|
gameStore.inCombat = true
|
|
|
|
|
|
|
|
|
|
|
|
triggerEvent(gameStore, 'explore_encounter', {
|
|
|
|
|
|
enemyName: enemyConfig.name,
|
|
|
|
|
|
enemyId
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return { success: true, type: 'combat', enemyId }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 默认:没发现
|
|
|
|
|
|
triggerEvent(gameStore, 'explore_nothing', {})
|
|
|
|
|
|
return { success: true, type: 'nothing' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 尝试逃跑
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
* @returns {Object} 逃跑结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function tryFlee(gameStore, playerStore) {
|
|
|
|
|
|
// 基础逃跑成功率50%
|
|
|
|
|
|
const baseFleeChance = 0.5
|
|
|
|
|
|
|
|
|
|
|
|
// 敏捷影响逃跑率
|
|
|
|
|
|
const agilityBonus = (playerStore.baseStats?.dexterity || 0) * 0.01
|
|
|
|
|
|
|
|
|
|
|
|
// 装备加成
|
|
|
|
|
|
const equipmentBonus = (playerStore.globalBonus?.fleeRate || 0)
|
|
|
|
|
|
|
|
|
|
|
|
const fleeChance = Math.min(0.9, baseFleeChance + agilityBonus + equipmentBonus)
|
|
|
|
|
|
|
|
|
|
|
|
if (Math.random() < fleeChance) {
|
|
|
|
|
|
// 逃跑成功
|
|
|
|
|
|
gameStore.inCombat = false
|
|
|
|
|
|
gameStore.combatState = null
|
|
|
|
|
|
if (gameStore.addLog) {
|
|
|
|
|
|
gameStore.addLog('成功逃跑了!', 'system')
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: true, message: '逃跑成功' }
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 逃跑失败,敌人攻击
|
|
|
|
|
|
if (gameStore.addLog) {
|
|
|
|
|
|
gameStore.addLog('逃跑失败!', 'combat')
|
|
|
|
|
|
}
|
|
|
|
|
|
return { success: false, message: '逃跑失败' }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|