Initial commit: Text Adventure Game

Features:
- Combat system with AP/EP hit calculation and three-layer defense
- Auto-combat/farming mode
- Item system with stacking support
- Skill system with levels, milestones, and parent skill sync
- Shop system with dynamic pricing
- Inventory management with bulk selling
- Event system
- Game loop with offline earnings
- Save/Load system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-01-21 17:13:51 +08:00
commit cb412544e9
90 changed files with 17149 additions and 0 deletions

549
utils/eventSystem.js Normal file
View File

@@ -0,0 +1,549 @@
/**
* 事件系统 - 事件触发检测、对话管理
* Phase 6 核心系统实现
*/
import { NPC_CONFIG } from '@/config/npcs.js'
import { EVENT_CONFIG } from '@/config/events.js'
import { LOCATION_CONFIG } from '@/config/locations.js'
import { unlockSkill } from './skillSystem.js'
import { addItemToInventory } from './itemSystem.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
// TODO: 启动战斗
return { success: true, message: '开始战斗', closeEvent: true }
}
return { success: false, message: '敌人参数错误', closeEvent: false }
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 = []
}