289 lines
9.4 KiB
JavaScript
289 lines
9.4 KiB
JavaScript
|
|
// 冒險系統核心
|
|||
|
|
import { apiService } from './api-service.js'
|
|||
|
|
import { ADVENTURES } from '../data/adventures.js'
|
|||
|
|
import { ENEMIES } from '../data/enemies.js'
|
|||
|
|
|
|||
|
|
export class AdventureSystem {
|
|||
|
|
constructor(petSystem, inventorySystem, api = apiService) {
|
|||
|
|
this.petSystem = petSystem
|
|||
|
|
this.inventorySystem = inventorySystem
|
|||
|
|
this.api = api
|
|||
|
|
this.currentAdventure = null
|
|||
|
|
this.adventureTimer = null
|
|||
|
|
this.logs = []
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 獲取所有冒險區域
|
|||
|
|
getAdventures() {
|
|||
|
|
return ADVENTURES
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 開始冒險
|
|||
|
|
async startAdventure(adventureId) {
|
|||
|
|
const adventure = ADVENTURES.find(a => a.id === adventureId)
|
|||
|
|
if (!adventure) return { success: false, message: '找不到該冒險區域' }
|
|||
|
|
|
|||
|
|
const state = this.petSystem.getState()
|
|||
|
|
|
|||
|
|
// 1. 檢查條件
|
|||
|
|
if (state.isSick || state.isDead || state.isSleeping) {
|
|||
|
|
return { success: false, message: '寵物狀態不適合冒險' }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查等級/階段要求 (這裡簡化為檢查階段)
|
|||
|
|
// 實際應根據 levelRequirement 檢查
|
|||
|
|
|
|||
|
|
// 檢查屬性要求
|
|||
|
|
if (adventure.statsRequirement) {
|
|||
|
|
for (const [stat, value] of Object.entries(adventure.statsRequirement)) {
|
|||
|
|
if ((state[stat] || 0) < value) {
|
|||
|
|
return { success: false, message: `屬性不足:需要 ${stat.toUpperCase()} ${value}` }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 檢查消耗
|
|||
|
|
if (state.hunger < adventure.cost.hunger) {
|
|||
|
|
return { success: false, message: '飢餓度不足' }
|
|||
|
|
}
|
|||
|
|
if (state.happiness < adventure.cost.happiness) {
|
|||
|
|
return { success: false, message: '快樂度不足' }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 扣除消耗
|
|||
|
|
await this.petSystem.updateState({
|
|||
|
|
hunger: state.hunger - adventure.cost.hunger,
|
|||
|
|
happiness: state.happiness - adventure.cost.happiness
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 4. 初始化冒險狀態
|
|||
|
|
this.currentAdventure = {
|
|||
|
|
id: adventureId,
|
|||
|
|
startTime: Date.now(),
|
|||
|
|
duration: adventure.duration * 1000, // 轉為毫秒
|
|||
|
|
endTime: Date.now() + adventure.duration * 1000,
|
|||
|
|
logs: [],
|
|||
|
|
rewards: { items: [], coins: 0 },
|
|||
|
|
encounters: []
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.addLog(`出發前往 ${adventure.name}!`)
|
|||
|
|
|
|||
|
|
// 5. 啟動計時器
|
|||
|
|
this.startAdventureLoop()
|
|||
|
|
|
|||
|
|
return { success: true, adventure: this.currentAdventure }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 冒險循環
|
|||
|
|
startAdventureLoop() {
|
|||
|
|
if (this.adventureTimer) clearInterval(this.adventureTimer)
|
|||
|
|
|
|||
|
|
this.adventureTimer = setInterval(async () => {
|
|||
|
|
if (!this.currentAdventure) {
|
|||
|
|
this.stopAdventureLoop()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const now = Date.now()
|
|||
|
|
const adventure = ADVENTURES.find(a => a.id === this.currentAdventure.id)
|
|||
|
|
|
|||
|
|
// 檢查是否結束
|
|||
|
|
if (now >= this.currentAdventure.endTime) {
|
|||
|
|
await this.completeAdventure()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 隨機遭遇敵人
|
|||
|
|
if (Math.random() < 0.1) { // 每秒 10% 機率遭遇
|
|||
|
|
await this.encounterEnemy(adventure)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}, 1000)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
stopAdventureLoop() {
|
|||
|
|
if (this.adventureTimer) {
|
|||
|
|
clearInterval(this.adventureTimer)
|
|||
|
|
this.adventureTimer = null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 遭遇敵人
|
|||
|
|
async encounterEnemy(adventure) {
|
|||
|
|
if (!adventure.enemyPool || adventure.enemyPool.length === 0) return
|
|||
|
|
|
|||
|
|
// 隨機選擇敵人
|
|||
|
|
const enemyId = adventure.enemyPool[Math.floor(Math.random() * adventure.enemyPool.length)]
|
|||
|
|
const enemyConfig = ENEMIES[enemyId]
|
|||
|
|
|
|||
|
|
if (!enemyConfig) return
|
|||
|
|
|
|||
|
|
this.addLog(`遭遇了 ${enemyConfig.name}!準備戰鬥!`)
|
|||
|
|
|
|||
|
|
// 執行戰鬥
|
|||
|
|
const combatResult = this.calculateCombat(enemyConfig)
|
|||
|
|
|
|||
|
|
// 記錄戰鬥過程
|
|||
|
|
combatResult.logs.forEach(log => this.addLog(log))
|
|||
|
|
|
|||
|
|
if (combatResult.win) {
|
|||
|
|
this.addLog(`戰鬥勝利!`)
|
|||
|
|
|
|||
|
|
// 計算掉落
|
|||
|
|
if (enemyConfig.drops) {
|
|||
|
|
for (const drop of enemyConfig.drops) {
|
|||
|
|
if (Math.random() < drop.chance) {
|
|||
|
|
// 檢查是否為金幣
|
|||
|
|
if (drop.itemId === 'gold_coin') {
|
|||
|
|
// 金幣直接加到獎勵中
|
|||
|
|
if (!this.currentAdventure.rewards.coins) {
|
|||
|
|
this.currentAdventure.rewards.coins = 0
|
|||
|
|
}
|
|||
|
|
this.currentAdventure.rewards.coins += drop.count || 1
|
|||
|
|
this.addLog(`獲得金幣:${drop.count || 1} 💰`)
|
|||
|
|
} else {
|
|||
|
|
// 其他道具加到物品列表
|
|||
|
|
this.currentAdventure.rewards.items.push({
|
|||
|
|
itemId: drop.itemId,
|
|||
|
|
count: drop.count || 1,
|
|||
|
|
name: drop.itemId // 暫時用 ID,實際應查表
|
|||
|
|
})
|
|||
|
|
this.addLog(`獲得戰利品:${drop.itemId} x${drop.count || 1}`)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
this.addLog(`戰鬥失敗... 寵物受傷逃跑了。`)
|
|||
|
|
// 扣除健康
|
|||
|
|
const state = this.petSystem.getState()
|
|||
|
|
await this.petSystem.updateState({
|
|||
|
|
health: Math.max(0, state.health - 10)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 計算戰鬥 (簡化版回合制)
|
|||
|
|
calculateCombat(enemy) {
|
|||
|
|
const state = this.petSystem.getState()
|
|||
|
|
const petStats = {
|
|||
|
|
hp: state.health, // 用健康度當 HP
|
|||
|
|
attack: state.attack || 10,
|
|||
|
|
defense: state.defense || 0,
|
|||
|
|
speed: state.speed || 10
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const enemyStats = { ...enemy.stats }
|
|||
|
|
const logs = []
|
|||
|
|
let round = 1
|
|||
|
|
let win = false
|
|||
|
|
|
|||
|
|
while (round <= 10) { // 最多 10 回合
|
|||
|
|
// 速度決定先手
|
|||
|
|
const petFirst = petStats.speed >= enemyStats.speed
|
|||
|
|
|
|||
|
|
// 雙方攻擊
|
|||
|
|
const attacker = petFirst ? petStats : enemyStats
|
|||
|
|
const defender = petFirst ? enemyStats : petStats
|
|||
|
|
const attackerName = petFirst ? '你' : enemy.name
|
|||
|
|
const defenderName = petFirst ? enemy.name : '你'
|
|||
|
|
|
|||
|
|
// 第一擊
|
|||
|
|
let damage = Math.max(1, attacker.attack - defender.defense)
|
|||
|
|
// 隨機浮動 0.8 ~ 1.2
|
|||
|
|
damage = Math.floor(damage * (0.8 + Math.random() * 0.4))
|
|||
|
|
defender.hp -= damage
|
|||
|
|
logs.push(`[回合${round}] ${attackerName} 對 ${defenderName} 造成 ${damage} 點傷害`)
|
|||
|
|
|
|||
|
|
if (defender.hp <= 0) {
|
|||
|
|
win = petFirst
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 第二擊
|
|||
|
|
damage = Math.max(1, defender.attack - attacker.defense)
|
|||
|
|
damage = Math.floor(damage * (0.8 + Math.random() * 0.4))
|
|||
|
|
attacker.hp -= damage
|
|||
|
|
logs.push(`[回合${round}] ${defenderName} 對 ${attackerName} 造成 ${damage} 點傷害`)
|
|||
|
|
|
|||
|
|
if (attacker.hp <= 0) {
|
|||
|
|
win = !petFirst
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
round++
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (round > 10) {
|
|||
|
|
logs.push('戰鬥超時,雙方平手(視為失敗)')
|
|||
|
|
win = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { win, logs }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 完成冒險
|
|||
|
|
async completeAdventure() {
|
|||
|
|
this.stopAdventureLoop()
|
|||
|
|
const adventure = ADVENTURES.find(a => a.id === this.currentAdventure.id)
|
|||
|
|
|
|||
|
|
this.addLog('冒險結束!')
|
|||
|
|
|
|||
|
|
// 發放基礎獎勵
|
|||
|
|
if (adventure.rewards) {
|
|||
|
|
if (adventure.rewards.items) {
|
|||
|
|
for (const reward of adventure.rewards.items) {
|
|||
|
|
if (Math.random() < reward.chance) {
|
|||
|
|
this.currentAdventure.rewards.items.push({
|
|||
|
|
itemId: reward.itemId,
|
|||
|
|
count: 1,
|
|||
|
|
name: reward.itemId
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 實際發放道具到背包
|
|||
|
|
if (this.inventorySystem) {
|
|||
|
|
for (const item of this.currentAdventure.rewards.items) {
|
|||
|
|
await this.inventorySystem.addItem(item.itemId, item.count)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 發放金幣
|
|||
|
|
if (this.currentAdventure.rewards.coins > 0) {
|
|||
|
|
const state = this.petSystem.getState()
|
|||
|
|
await this.petSystem.updateState({
|
|||
|
|
coins: (state.coins || 0) + this.currentAdventure.rewards.coins
|
|||
|
|
})
|
|||
|
|
this.addLog(`獲得金幣:${this.currentAdventure.rewards.coins} 💰`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 觸發完成回調(如果有 UI 監聽)
|
|||
|
|
if (this.onAdventureComplete) {
|
|||
|
|
this.onAdventureComplete(this.currentAdventure)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.currentAdventure = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加日誌
|
|||
|
|
addLog(message) {
|
|||
|
|
if (this.currentAdventure) {
|
|||
|
|
const time = new Date().toLocaleTimeString()
|
|||
|
|
this.currentAdventure.logs.push(`[${time}] ${message}`)
|
|||
|
|
// 觸發更新回調
|
|||
|
|
if (this.onLogUpdate) {
|
|||
|
|
this.onLogUpdate(this.currentAdventure.logs)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 獲取當前冒險狀態
|
|||
|
|
getCurrentAdventure() {
|
|||
|
|
return this.currentAdventure
|
|||
|
|
}
|
|||
|
|
}
|