Files
text-adventure-game/utils/eventSystem.js

788 lines
23 KiB
JavaScript
Raw Normal View History

/**
* 事件系统 - 事件触发检测对话管理
* 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 { ITEM_CONFIG } from '@/config/items.js'
import { unlockSkill } from './skillSystem.js'
import { addItemToInventory } from './itemSystem.js'
import { initCombat, getEnvironmentType } from './combatSystem.js'
import { installProsthetic, removeProsthetic } from './prostheticSystem.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 - 对话键名
* @param {Object} playerStore - 玩家Store用于动态选项生成
*/
export function startNPCDialogue(gameStore, npcId, dialogueKey = 'first', playerStore = null) {
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
}
// 处理模板变量
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]
}
// 构建事件数据
const eventData = {
id: `npc_${npcId}_${dialogueKey}`,
type: 'dialogue',
title: npc.name,
text: text,
npc: npc,
choices: choices,
currentDialogue: dialogueKey,
npcId
}
gameStore.currentEvent = eventData
gameStore.drawerState.event = true
}
/**
* 渲染对话模板替换变量
*/
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 []
}
}
/**
* 处理对话选择
* @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, playerStore)
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 }
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 }
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: '逃跑失败' }
}
}