Files
text-adventure-game/utils/eventSystem.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

663 lines
19 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 6 核心系统实现
*/
import { NPC_CONFIG } from '@/config/npcs.js'
import { EVENT_CONFIG } from '@/config/events.js'
import { LOCATION_CONFIG } from '@/config/locations.js'
import { SKILL_CONFIG } from '@/config/skills.js'
import { ENEMY_CONFIG } from '@/config/enemies.js'
import { unlockSkill } from './skillSystem.js'
import { addItemToInventory } from './itemSystem.js'
import { initCombat, getEnvironmentType } from './combatSystem.js'
/**
* 检查并触发事件
* @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 - 对话键名
*/
export function startNPCDialogue(gameStore, npcId, dialogueKey = 'first') {
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
}
// 构建事件数据
const eventData = {
id: `npc_${npcId}_${dialogueKey}`,
type: 'dialogue',
title: npc.name,
text: dialogue.text,
npc: npc,
choices: dialogue.choices || [],
currentDialogue: dialogueKey,
npcId
}
gameStore.currentEvent = eventData
gameStore.drawerState.event = true
}
/**
* 处理对话选择
* @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) {
startNPCDialogue(gameStore, currentEvent.npc.id, choice.next)
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
// 战斗已在探索事件中初始化,这里只是关闭事件抽屉
return { success: true, message: '开始战斗', closeEvent: true }
}
return { success: false, message: '敌人参数错误', closeEvent: false }
case 'flee':
// 逃跑
gameStore.drawerState.event = false
return tryFlee(gameStore, playerStore)
case 'close':
// 直接关闭
return { success: true, message: '', closeEvent: true }
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 = []
}
/**
* 触发探索事件
* @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: '逃跑失败' }
}
}