324 lines
8.6 KiB
JavaScript
324 lines
8.6 KiB
JavaScript
// 事件系統核心 - 與 API 整合
|
||
import { apiService } from './api-service.js'
|
||
|
||
export class EventSystem {
|
||
constructor(petSystem, api = apiService) {
|
||
this.petSystem = petSystem
|
||
this.api = api
|
||
this.events = []
|
||
this.buffManager = new BuffManager()
|
||
this.eventHistory = []
|
||
this.eventCheckInterval = null
|
||
this.lastEventCheckTime = 0
|
||
}
|
||
|
||
// 初始化(從 API 載入事件配置)
|
||
async initialize() {
|
||
try {
|
||
this.events = await this.api.getEvents()
|
||
console.log(`[EventSystem] 載入 ${this.events.length} 個事件`)
|
||
} catch (error) {
|
||
console.error('[EventSystem] 載入事件失敗:', error)
|
||
// 降級到本地載入
|
||
const { EVENT_CONFIG } = await import('../data/events.js')
|
||
this.events = EVENT_CONFIG
|
||
}
|
||
}
|
||
|
||
// 啟動事件檢查循環(每 10 秒檢查一次)
|
||
startEventCheck() {
|
||
if (this.eventCheckInterval) this.stopEventCheck()
|
||
|
||
this.eventCheckInterval = setInterval(() => {
|
||
this.checkTriggers()
|
||
}, 10000) // 每 10 秒
|
||
}
|
||
|
||
stopEventCheck() {
|
||
if (this.eventCheckInterval) {
|
||
clearInterval(this.eventCheckInterval)
|
||
this.eventCheckInterval = null
|
||
}
|
||
}
|
||
|
||
// 隨機選擇事件(依權重)
|
||
selectRandomEvent() {
|
||
const totalWeight = this.events.reduce((sum, e) => sum + e.weight, 0)
|
||
let rand = Math.random() * totalWeight
|
||
|
||
for (const event of this.events) {
|
||
rand -= event.weight
|
||
if (rand <= 0) return event
|
||
}
|
||
|
||
return this.events[0] // fallback
|
||
}
|
||
|
||
// 檢查觸發條件(10% 機率 + 條件檢查)
|
||
async checkTriggers() {
|
||
const petState = this.petSystem.getState()
|
||
|
||
if (petState.isDead) return
|
||
|
||
// 10% 機率觸發
|
||
if (Math.random() >= 0.1) return
|
||
|
||
const event = this.selectRandomEvent()
|
||
|
||
// 檢查條件
|
||
if (event.condition && !event.condition(petState)) {
|
||
return
|
||
}
|
||
|
||
// 觸發事件
|
||
await this.triggerEvent(event.id, petState)
|
||
}
|
||
|
||
// 觸發事件(同步到 API)
|
||
async triggerEvent(eventId, petState = null) {
|
||
const event = this.events.find(e => e.id === eventId)
|
||
if (!event) {
|
||
console.warn(`[EventSystem] 找不到事件: ${eventId}`)
|
||
return null
|
||
}
|
||
|
||
const currentState = petState || this.petSystem.getState()
|
||
|
||
// 檢查條件
|
||
if (event.condition && !event.condition(currentState)) {
|
||
console.log(`[EventSystem] 事件 ${eventId} 條件不滿足`)
|
||
return null
|
||
}
|
||
|
||
console.log(`\n[事件觸發] ${event.id} (${event.type}): 機率檢查通過!`)
|
||
|
||
// 執行效果
|
||
const results = []
|
||
for (const effect of event.effects) {
|
||
const result = await this.executeEffect(effect, currentState)
|
||
results.push(result)
|
||
}
|
||
|
||
// 記錄歷史
|
||
this.eventHistory.push({
|
||
timestamp: Date.now(),
|
||
eventId: event.id,
|
||
eventType: event.type,
|
||
effects: results
|
||
})
|
||
|
||
// 同步到 API
|
||
try {
|
||
await this.api.triggerEvent(eventId, currentState)
|
||
} catch (error) {
|
||
console.warn('[EventSystem] API 同步失敗:', error)
|
||
}
|
||
|
||
return { event, results }
|
||
}
|
||
|
||
// 執行效果
|
||
async executeEffect(effect, petState) {
|
||
switch (effect.type) {
|
||
case 'modifyStats':
|
||
return await this.modifyStats(effect.payload, petState)
|
||
|
||
case 'addBuff':
|
||
return await this.addBuff(effect.payload)
|
||
|
||
case 'spawnPoop':
|
||
return await this.spawnPoop(effect.payload)
|
||
|
||
case 'templeFavor':
|
||
return await this.modifyTempleFavor(effect.payload)
|
||
|
||
case 'logMessage':
|
||
console.log(`[訊息] ${effect.payload}`)
|
||
return { type: 'logMessage', message: effect.payload }
|
||
|
||
default:
|
||
console.warn(`[EventSystem] 未知效果類型: ${effect.type}`)
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 修改屬性
|
||
async modifyStats(payload, petState) {
|
||
const updates = {}
|
||
|
||
for (const [key, value] of Object.entries(payload)) {
|
||
if (key === 'isSleeping' || key === 'isSick' || key === 'isDead') {
|
||
updates[key] = value
|
||
} else {
|
||
const current = petState[key] || 0
|
||
const newValue = Math.max(0, Math.min(100, current + value))
|
||
updates[key] = newValue
|
||
console.log(`[效果] ${key} ${value > 0 ? '+' : ''}${value} → 新值: ${newValue}`)
|
||
}
|
||
}
|
||
|
||
await this.petSystem.updateState(updates)
|
||
return { type: 'modifyStats', updates }
|
||
}
|
||
|
||
// 添加 Buff
|
||
async addBuff(buffData) {
|
||
this.buffManager.addBuff(buffData)
|
||
|
||
// 同步到 API
|
||
try {
|
||
await this.api.applyBuff(buffData)
|
||
} catch (error) {
|
||
console.warn('[EventSystem] Buff API 同步失敗:', error)
|
||
}
|
||
|
||
return { type: 'addBuff', buff: buffData }
|
||
}
|
||
|
||
// 生成便便
|
||
async spawnPoop(payload) {
|
||
const count = payload?.count || 1
|
||
const currentPoop = this.petSystem.getState().poopCount
|
||
const newPoop = Math.min(4, currentPoop + count)
|
||
|
||
await this.petSystem.updateState({ poopCount: newPoop })
|
||
console.log(`[效果] 生成便便: ${count} 個,總數: ${newPoop}`)
|
||
|
||
return { type: 'spawnPoop', count, total: newPoop }
|
||
}
|
||
|
||
// 修改神明好感度
|
||
async modifyTempleFavor(payload) {
|
||
const { deityId, amount } = payload
|
||
const currentState = this.petSystem.getState()
|
||
const currentFavor = currentState.deityFavors[deityId] || 0
|
||
const newFavor = Math.max(0, Math.min(100, currentFavor + amount))
|
||
|
||
await this.petSystem.updateState({
|
||
deityFavors: {
|
||
...currentState.deityFavors,
|
||
[deityId]: newFavor
|
||
}
|
||
})
|
||
|
||
console.log(`[效果] ${deityId} 好感度 ${amount > 0 ? '+' : ''}${amount} → ${newFavor}`)
|
||
|
||
return { type: 'templeFavor', deityId, favor: newFavor }
|
||
}
|
||
|
||
// 應用 Buff 到狀態(每 tick 呼叫)
|
||
async applyBuffs() {
|
||
const petState = this.petSystem.getState()
|
||
const buffs = this.buffManager.getActiveBuffs()
|
||
|
||
// 計算最終屬性(考慮 Buff)
|
||
const finalStats = this.calculateFinalStats(petState, buffs)
|
||
|
||
// 更新狀態
|
||
await this.petSystem.updateState(finalStats)
|
||
|
||
return finalStats
|
||
}
|
||
|
||
// 計算最終屬性(Base + Flat + Percent)
|
||
calculateFinalStats(baseState, buffs) {
|
||
const stats = { ...baseState }
|
||
|
||
// 收集所有 Buff 的 flat 和 percent 加成
|
||
const flatMods = {}
|
||
const percentMods = {}
|
||
|
||
buffs.forEach(buff => {
|
||
if (buff.flat) {
|
||
Object.entries(buff.flat).forEach(([key, value]) => {
|
||
flatMods[key] = (flatMods[key] || 0) + value
|
||
})
|
||
}
|
||
if (buff.percent) {
|
||
Object.entries(buff.percent).forEach(([key, value]) => {
|
||
percentMods[key] = (percentMods[key] || 0) + value
|
||
})
|
||
}
|
||
})
|
||
|
||
// 應用加成
|
||
Object.keys(stats).forEach(key => {
|
||
if (typeof stats[key] === 'number' && key !== 'ageSeconds' && key !== 'weight' && key !== 'generation') {
|
||
const base = stats[key]
|
||
const flat = flatMods[key] || 0
|
||
const percent = percentMods[key] || 0
|
||
stats[key] = Math.max(0, Math.min(100, (base + flat) * (1 + percent)))
|
||
}
|
||
})
|
||
|
||
return stats
|
||
}
|
||
|
||
// 獲取事件歷史
|
||
getHistory() {
|
||
return [...this.eventHistory]
|
||
}
|
||
|
||
// 獲取 Buff 管理器
|
||
getBuffManager() {
|
||
return this.buffManager
|
||
}
|
||
}
|
||
|
||
// Buff 管理器
|
||
class BuffManager {
|
||
constructor() {
|
||
this.buffs = []
|
||
}
|
||
|
||
addBuff(buff) {
|
||
// 檢查是否可堆疊
|
||
const existing = this.buffs.find(b => b.id === buff.id)
|
||
|
||
if (existing && !buff.stacks) {
|
||
// 不可堆疊,重置持續時間
|
||
existing.currentTicks = buff.durationTicks
|
||
console.log(`[Buff] 重置: ${buff.name} (持續 ${buff.durationTicks} ticks)`)
|
||
} else {
|
||
// 可堆疊或新 Buff
|
||
this.buffs.push({
|
||
...buff,
|
||
currentTicks: buff.durationTicks,
|
||
currentStacks: (existing?.currentStacks || 0) + 1
|
||
})
|
||
console.log(`[Buff] 新增: ${buff.name} (持續 ${buff.durationTicks} ticks)`)
|
||
}
|
||
}
|
||
|
||
tick() {
|
||
this.buffs = this.buffs.filter(b => {
|
||
if (b.durationTicks === Infinity) return true // 永久 Buff
|
||
|
||
b.currentTicks--
|
||
if (b.currentTicks <= 0) {
|
||
console.log(`[Buff] 過期: ${b.name}`)
|
||
return false
|
||
}
|
||
return true
|
||
})
|
||
}
|
||
|
||
getActiveBuffs() {
|
||
return this.buffs.filter(b => b.currentTicks > 0 || b.durationTicks === Infinity)
|
||
}
|
||
|
||
getFinalModifier(statKey) {
|
||
const activeBuffs = this.getActiveBuffs()
|
||
let flatTotal = 0
|
||
let percentTotal = 0
|
||
|
||
activeBuffs.forEach(b => {
|
||
if (b.flat?.[statKey]) flatTotal += b.flat[statKey]
|
||
if (b.percent?.[statKey]) percentTotal += b.percent[statKey]
|
||
})
|
||
|
||
return { flat: flatTotal, percent: percentTotal }
|
||
}
|
||
}
|
||
|