good version
This commit is contained in:
parent
310844bf1e
commit
384c8df9c7
937
app/app.vue
937
app/app.vue
File diff suppressed because it is too large
Load Diff
|
|
@ -331,5 +331,44 @@ export class AchievementSystem {
|
||||||
|
|
||||||
return bonuses
|
return bonuses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置成就系統(刪除寵物時使用)
|
||||||
|
async reset() {
|
||||||
|
this.unlockedAchievements = []
|
||||||
|
this.achievementStats = {
|
||||||
|
actionCounts: {
|
||||||
|
feed: 0,
|
||||||
|
play: 0,
|
||||||
|
clean: 0,
|
||||||
|
heal: 0,
|
||||||
|
sleep: 0,
|
||||||
|
pray: 0,
|
||||||
|
drawFortune: 0
|
||||||
|
},
|
||||||
|
eventCount: 0,
|
||||||
|
eventTypeCounts: {
|
||||||
|
good: 0,
|
||||||
|
bad: 0,
|
||||||
|
weird: 0,
|
||||||
|
rare: 0
|
||||||
|
},
|
||||||
|
recoveredFromDying: false,
|
||||||
|
perfectStateReached: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步到 API
|
||||||
|
try {
|
||||||
|
await this.api.saveAchievements({
|
||||||
|
unlocked: this.unlockedAchievements,
|
||||||
|
stats: this.achievementStats
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[AchievementSystem] API 同步失敗:', error)
|
||||||
|
// 降級到 localStorage
|
||||||
|
localStorage.removeItem('achievements')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[AchievementSystem] 成就已重置')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,288 @@
|
||||||
|
// 冒險系統核心
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,6 +55,12 @@ export class ApiService {
|
||||||
return this.getMockInventory()
|
return this.getMockInventory()
|
||||||
case 'inventory/save':
|
case 'inventory/save':
|
||||||
return this.saveMockInventory(options.body)
|
return this.saveMockInventory(options.body)
|
||||||
|
case 'adventure/list':
|
||||||
|
return this.getMockAdventures()
|
||||||
|
case 'adventure/start':
|
||||||
|
return { success: true, message: '冒險開始' } // 實際邏輯在 AdventureSystem
|
||||||
|
case 'adventure/complete':
|
||||||
|
return { success: true, message: '冒險完成' }
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown endpoint: ${endpoint}`)
|
throw new Error(`Unknown endpoint: ${endpoint}`)
|
||||||
}
|
}
|
||||||
|
|
@ -175,6 +181,25 @@ export class ApiService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 冒險相關
|
||||||
|
async getAdventures() {
|
||||||
|
return this.request('adventure/list')
|
||||||
|
}
|
||||||
|
|
||||||
|
async startAdventure(adventureId) {
|
||||||
|
return this.request('adventure/start', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { adventureId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async completeAdventure(result) {
|
||||||
|
return this.request('adventure/complete', {
|
||||||
|
method: 'POST',
|
||||||
|
body: result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Mock 資料方法 ==========
|
// ========== Mock 資料方法 ==========
|
||||||
|
|
||||||
getMockPetState() {
|
getMockPetState() {
|
||||||
|
|
@ -188,6 +213,11 @@ export class ApiService {
|
||||||
|
|
||||||
updateMockPetState(updates) {
|
updateMockPetState(updates) {
|
||||||
const current = this.getMockPetState()
|
const current = this.getMockPetState()
|
||||||
|
if (!current) {
|
||||||
|
console.warn('[ApiService] No current state found, cannot update')
|
||||||
|
return { success: false, message: '找不到當前狀態' }
|
||||||
|
}
|
||||||
|
// 使用深度合併,確保不會丟失原有字段
|
||||||
const updated = { ...current, ...updates }
|
const updated = { ...current, ...updates }
|
||||||
localStorage.setItem('petState', JSON.stringify(updated))
|
localStorage.setItem('petState', JSON.stringify(updated))
|
||||||
return { success: true, data: updated }
|
return { success: true, data: updated }
|
||||||
|
|
@ -345,6 +375,11 @@ export class ApiService {
|
||||||
return { success: false, message: '儲存失敗: ' + error.message }
|
return { success: false, message: '儲存失敗: ' + error.message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMockAdventures() {
|
||||||
|
const { ADVENTURES } = await import('../data/adventures.js')
|
||||||
|
return ADVENTURES
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 預設實例
|
// 預設實例
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,8 @@ export class InventorySystem {
|
||||||
|
|
||||||
// 裝備道具
|
// 裝備道具
|
||||||
// equipType: 'equipment' | 'appearance' | 'auto' (自動判斷)
|
// equipType: 'equipment' | 'appearance' | 'auto' (自動判斷)
|
||||||
async equipItem(itemId, slot = null, equipType = 'auto') {
|
// instanceId: 指定要裝備的實例ID(可選,如果不指定則自動選擇第一個未裝備的)
|
||||||
|
async equipItem(itemId, slot = null, equipType = 'auto', instanceId = null) {
|
||||||
const item = this.items[itemId]
|
const item = this.items[itemId]
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return { success: false, message: '道具不存在' }
|
return { success: false, message: '道具不存在' }
|
||||||
|
|
|
||||||
|
|
@ -24,19 +24,29 @@ export class PetSystem {
|
||||||
if (!this.state) {
|
if (!this.state) {
|
||||||
// 創建新寵物
|
// 創建新寵物
|
||||||
this.state = this.createInitialState(speciesId)
|
this.state = this.createInitialState(speciesId)
|
||||||
|
// 載入種族配置
|
||||||
|
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
|
||||||
|
// 計算戰鬥數值(在保存前)
|
||||||
|
this.calculateCombatStats()
|
||||||
|
// 保存完整狀態(包含計算後的戰鬥數值)
|
||||||
await this.api.savePetState(this.state)
|
await this.api.savePetState(this.state)
|
||||||
} else {
|
} else {
|
||||||
// 確保 achievementBuffs 存在(向後兼容)
|
// 確保 achievementBuffs 存在(向後兼容)
|
||||||
if (!this.state.achievementBuffs) {
|
if (!this.state.achievementBuffs) {
|
||||||
this.state.achievementBuffs = {}
|
this.state.achievementBuffs = {}
|
||||||
}
|
}
|
||||||
|
// 確保 equipmentBuffs 有正確結構(向後兼容)
|
||||||
|
if (!this.state.equipmentBuffs || typeof this.state.equipmentBuffs !== 'object') {
|
||||||
|
this.state.equipmentBuffs = { flat: {}, percent: {} }
|
||||||
|
} else if (!this.state.equipmentBuffs.flat || !this.state.equipmentBuffs.percent) {
|
||||||
|
// 如果是舊的空對象 {},轉換為新結構
|
||||||
|
this.state.equipmentBuffs = { flat: {}, percent: {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 載入種族配置
|
// 載入種族配置
|
||||||
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
|
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
|
||||||
|
// 計算戰鬥數值
|
||||||
// 計算戰鬥數值(在 speciesConfig 設置後)
|
|
||||||
this.calculateCombatStats()
|
this.calculateCombatStats()
|
||||||
|
}
|
||||||
|
|
||||||
return this.state
|
return this.state
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -85,8 +95,9 @@ export class PetSystem {
|
||||||
generation: 1,
|
generation: 1,
|
||||||
lastTickTime: Date.now(),
|
lastTickTime: Date.now(),
|
||||||
achievementBuffs: {}, // 成就加成
|
achievementBuffs: {}, // 成就加成
|
||||||
equipmentBuffs: {}, // 裝備加成
|
equipmentBuffs: { flat: {}, percent: {} }, // 裝備加成
|
||||||
appearance: {} // 外觀設定
|
appearance: {}, // 外觀設定
|
||||||
|
coins: 100 // 金幣(初始 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分配命格
|
// 分配命格
|
||||||
|
|
@ -559,7 +570,13 @@ export class PetSystem {
|
||||||
// 戰鬥數值
|
// 戰鬥數值
|
||||||
attack: this.state.attack,
|
attack: this.state.attack,
|
||||||
defense: this.state.defense,
|
defense: this.state.defense,
|
||||||
speed: this.state.speed
|
speed: this.state.speed,
|
||||||
|
// 命格和神明
|
||||||
|
destiny: this.state.destiny,
|
||||||
|
currentDeityId: this.state.currentDeityId,
|
||||||
|
deityFavors: this.state.deityFavors,
|
||||||
|
// 经济
|
||||||
|
coins: this.state.coins
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -599,7 +616,8 @@ export class PetSystem {
|
||||||
|
|
||||||
// 檢查屬性條件
|
// 檢查屬性條件
|
||||||
let statsConditionMet = true
|
let statsConditionMet = true
|
||||||
const missingStats = []
|
let missingStats = []
|
||||||
|
|
||||||
if (nextStage.conditions) {
|
if (nextStage.conditions) {
|
||||||
if (nextStage.conditions.str && this.state.str < nextStage.conditions.str) {
|
if (nextStage.conditions.str && this.state.str < nextStage.conditions.str) {
|
||||||
statsConditionMet = false
|
statsConditionMet = false
|
||||||
|
|
@ -616,10 +634,65 @@ export class PetSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeConditionMet && statsConditionMet) {
|
if (timeConditionMet && statsConditionMet) {
|
||||||
console.log(`\n✨ 進化!${currentStage} → ${nextStage.stage} (年齡: ${ageSeconds.toFixed(1)}秒)`)
|
// 檢查是否有進化分支
|
||||||
this.state.stage = nextStage.stage
|
let targetStage = nextStage.stage
|
||||||
|
let evolutionBranch = null
|
||||||
|
|
||||||
|
if (nextStage.evolutions && nextStage.evolutions.length > 0) {
|
||||||
|
// 判定進化分支
|
||||||
|
for (const branch of nextStage.evolutions) {
|
||||||
|
let branchMet = true
|
||||||
|
|
||||||
|
// 檢查分支條件
|
||||||
|
if (branch.conditions) {
|
||||||
|
// STR 條件
|
||||||
|
if (branch.conditions.str) {
|
||||||
|
if (branch.conditions.str.min && this.state.str < branch.conditions.str.min) branchMet = false
|
||||||
|
if (branch.conditions.str.dominant && (this.state.str <= this.state.int + this.state.dex)) branchMet = false
|
||||||
|
}
|
||||||
|
// INT 條件
|
||||||
|
if (branch.conditions.int) {
|
||||||
|
if (branch.conditions.int.min && this.state.int < branch.conditions.int.min) branchMet = false
|
||||||
|
if (branch.conditions.int.dominant && (this.state.int <= this.state.str + this.state.dex)) branchMet = false
|
||||||
|
}
|
||||||
|
// DEX 條件
|
||||||
|
if (branch.conditions.dex) {
|
||||||
|
if (branch.conditions.dex.min && this.state.dex < branch.conditions.dex.min) branchMet = false
|
||||||
|
if (branch.conditions.dex.dominant && (this.state.dex <= this.state.str + this.state.int)) branchMet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branchMet) {
|
||||||
|
evolutionBranch = branch
|
||||||
|
break // 找到第一個符合的分支就停止(優先級由數組順序決定)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果沒有符合的特殊分支,使用默認分支(通常是最後一個)
|
||||||
|
if (!evolutionBranch) {
|
||||||
|
evolutionBranch = nextStage.evolutions[nextStage.evolutions.length - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✨ 進化!${currentStage} → ${targetStage} (年齡: ${ageSeconds.toFixed(1)}秒)`)
|
||||||
|
|
||||||
|
const updates = { stage: targetStage }
|
||||||
|
|
||||||
|
// 應用進化分支效果
|
||||||
|
if (evolutionBranch) {
|
||||||
|
console.log(`🌟 觸發特殊進化分支:${evolutionBranch.name}`)
|
||||||
|
updates.evolutionId = evolutionBranch.id
|
||||||
|
updates.evolutionName = evolutionBranch.name
|
||||||
|
|
||||||
|
// 應用屬性修正(永久保存到狀態中)
|
||||||
|
if (evolutionBranch.statModifiers) {
|
||||||
|
updates.statModifiers = evolutionBranch.statModifiers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.stage = targetStage
|
||||||
this.state._evolutionWarned = false // 重置警告狀態
|
this.state._evolutionWarned = false // 重置警告狀態
|
||||||
this.updateState({ stage: nextStage.stage })
|
this.updateState(updates)
|
||||||
} else if (timeConditionMet && !statsConditionMet) {
|
} else if (timeConditionMet && !statsConditionMet) {
|
||||||
// 時間到了但屬性不足,只在第一次提示
|
// 時間到了但屬性不足,只在第一次提示
|
||||||
if (!this.state._evolutionWarned) {
|
if (!this.state._evolutionWarned) {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,9 @@ export class TempleSystem {
|
||||||
|
|
||||||
// 獲取好感度星級(每 20 點一星)
|
// 獲取好感度星級(每 20 點一星)
|
||||||
getFavorStars(deityId) {
|
getFavorStars(deityId) {
|
||||||
|
if (!deityId) return '☆☆☆☆☆'
|
||||||
const state = this.petSystem.getState()
|
const state = this.petSystem.getState()
|
||||||
|
if (!state || !state.deityFavors) return '☆☆☆☆☆'
|
||||||
const favor = state.deityFavors[deityId] || 0
|
const favor = state.deityFavors[deityId] || 0
|
||||||
const stars = Math.floor(favor / 20)
|
const stars = Math.floor(favor / 20)
|
||||||
return '★'.repeat(stars) + '☆'.repeat(5 - stars)
|
return '★'.repeat(stars) + '☆'.repeat(5 - stars)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 冒險區域配置(資料驅動)
|
||||||
|
export const ADVENTURES = [
|
||||||
|
{
|
||||||
|
id: 'backyard',
|
||||||
|
name: '自家後院',
|
||||||
|
description: '安全的新手探險地,偶爾會有小蟲子。',
|
||||||
|
statsRequirement: null, // 無屬性要求
|
||||||
|
cost: {
|
||||||
|
hunger: 5,
|
||||||
|
happiness: 5
|
||||||
|
},
|
||||||
|
duration: 10, // 測試用:10秒 (實際可能 60秒)
|
||||||
|
enemyPool: ['cockroach', 'mouse'],
|
||||||
|
enemyRate: 0.6, // 60% 機率遇到敵人
|
||||||
|
rewards: {
|
||||||
|
items: [
|
||||||
|
{ itemId: 'cookie', chance: 0.2 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'park',
|
||||||
|
name: '附近的公園',
|
||||||
|
description: '熱鬧的公園,但也潛藏著流浪動物的威脅。',
|
||||||
|
statsRequirement: { str: 20 }, // 需要力量 20
|
||||||
|
cost: {
|
||||||
|
hunger: 15,
|
||||||
|
happiness: 10
|
||||||
|
},
|
||||||
|
duration: 30, // 測試用:30秒
|
||||||
|
enemyPool: ['stray_dog', 'wild_cat'],
|
||||||
|
enemyRate: 0.7,
|
||||||
|
rewards: {
|
||||||
|
items: [
|
||||||
|
{ itemId: 'tuna_can', chance: 0.3 },
|
||||||
|
{ itemId: 'ball', chance: 0.2 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'forest',
|
||||||
|
name: '神秘森林',
|
||||||
|
description: '危險的未知區域,只有強者才能生存。',
|
||||||
|
statsRequirement: { str: 50, int: 30 },
|
||||||
|
cost: {
|
||||||
|
hunger: 30,
|
||||||
|
happiness: 20
|
||||||
|
},
|
||||||
|
duration: 60, // 測試用:60秒
|
||||||
|
enemyPool: ['snake', 'bear'],
|
||||||
|
enemyRate: 0.8,
|
||||||
|
rewards: {
|
||||||
|
items: [
|
||||||
|
{ itemId: 'vitality_potion', chance: 0.3 },
|
||||||
|
{ itemId: 'magic_wand', chance: 0.1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
// 敵人配置(資料驅動)
|
||||||
|
export const ENEMIES = {
|
||||||
|
// 新手區敵人
|
||||||
|
cockroach: {
|
||||||
|
id: 'cockroach',
|
||||||
|
name: '巨大的蟑螂',
|
||||||
|
description: '生命力頑強的害蟲,雖然弱小但很噁心。',
|
||||||
|
stats: {
|
||||||
|
hp: 20,
|
||||||
|
attack: 5,
|
||||||
|
defense: 0,
|
||||||
|
speed: 5
|
||||||
|
},
|
||||||
|
drops: [
|
||||||
|
{ itemId: 'cookie', chance: 0.3, count: 1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
mouse: {
|
||||||
|
id: 'mouse',
|
||||||
|
name: '偷吃的老鼠',
|
||||||
|
description: '動作敏捷的小偷,喜歡偷吃東西。',
|
||||||
|
stats: {
|
||||||
|
hp: 35,
|
||||||
|
attack: 8,
|
||||||
|
defense: 2,
|
||||||
|
speed: 15
|
||||||
|
},
|
||||||
|
drops: [
|
||||||
|
{ itemId: 'cookie', chance: 0.4, count: 1 },
|
||||||
|
{ itemId: 'wooden_sword', chance: 0.05, count: 1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 公園區敵人
|
||||||
|
stray_dog: {
|
||||||
|
id: 'stray_dog',
|
||||||
|
name: '兇猛的野狗',
|
||||||
|
description: '為了搶地盤而變得兇暴的野狗。',
|
||||||
|
stats: {
|
||||||
|
hp: 80,
|
||||||
|
attack: 15,
|
||||||
|
defense: 5,
|
||||||
|
speed: 10
|
||||||
|
},
|
||||||
|
drops: [
|
||||||
|
{ itemId: 'tuna_can', chance: 0.3, count: 1 },
|
||||||
|
{ itemId: 'leather_armor', chance: 0.1, count: 1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
wild_cat: {
|
||||||
|
id: 'wild_cat',
|
||||||
|
name: '流浪貓老大',
|
||||||
|
description: '這片區域的老大,身手矯健。',
|
||||||
|
stats: {
|
||||||
|
hp: 100,
|
||||||
|
attack: 20,
|
||||||
|
defense: 8,
|
||||||
|
speed: 25
|
||||||
|
},
|
||||||
|
drops: [
|
||||||
|
{ itemId: 'premium_food', chance: 0.2, count: 1 },
|
||||||
|
{ itemId: 'lucky_charm', chance: 0.05, count: 1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 森林區敵人
|
||||||
|
snake: {
|
||||||
|
id: 'snake',
|
||||||
|
name: '毒蛇',
|
||||||
|
description: '潛伏在草叢中的危險掠食者。',
|
||||||
|
stats: {
|
||||||
|
hp: 150,
|
||||||
|
attack: 35,
|
||||||
|
defense: 10,
|
||||||
|
speed: 30
|
||||||
|
},
|
||||||
|
drops: [
|
||||||
|
{ itemId: 'vitality_potion', chance: 0.2, count: 1 },
|
||||||
|
{ itemId: 'magic_wand', chance: 0.05, count: 1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
bear: {
|
||||||
|
id: 'bear',
|
||||||
|
name: '暴躁的黑熊',
|
||||||
|
description: '森林中的霸主,力量驚人。',
|
||||||
|
stats: {
|
||||||
|
hp: 300,
|
||||||
|
attack: 50,
|
||||||
|
defense: 30,
|
||||||
|
speed: 10
|
||||||
|
},
|
||||||
|
drops: [
|
||||||
|
{ itemId: 'gold_coin', chance: 0.5, count: 10 }, // 假設有金幣
|
||||||
|
{ itemId: 'hero_sword', chance: 0.02, count: 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -153,7 +153,67 @@ export const PET_SPECIES = {
|
||||||
str: 50, // 3天內累積
|
str: 50, // 3天內累積
|
||||||
int: 50,
|
int: 50,
|
||||||
dex: 50
|
dex: 50
|
||||||
|
},
|
||||||
|
// 進化分支配置
|
||||||
|
evolutions: [
|
||||||
|
{
|
||||||
|
id: 'warrior_tiger',
|
||||||
|
name: '戰士猛虎',
|
||||||
|
icon: '🐯',
|
||||||
|
description: '力量強大的猛虎形態',
|
||||||
|
conditions: {
|
||||||
|
str: { min: 50, dominant: true } // STR 需大於 INT+DEX
|
||||||
|
},
|
||||||
|
statModifiers: {
|
||||||
|
attack: 1.3,
|
||||||
|
defense: 1.2,
|
||||||
|
speed: 0.8
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'agile_cat',
|
||||||
|
name: '敏捷靈貓',
|
||||||
|
icon: '🐈',
|
||||||
|
description: '身手矯健的靈貓形態',
|
||||||
|
conditions: {
|
||||||
|
dex: { min: 50, dominant: true } // DEX 需大於 STR+INT
|
||||||
|
},
|
||||||
|
statModifiers: {
|
||||||
|
attack: 1.0,
|
||||||
|
defense: 0.8,
|
||||||
|
speed: 1.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sage_cat',
|
||||||
|
name: '智者賢貓',
|
||||||
|
icon: '😺',
|
||||||
|
description: '充滿智慧的賢貓形態',
|
||||||
|
conditions: {
|
||||||
|
int: { min: 50, dominant: true } // INT 需大於 STR+DEX
|
||||||
|
},
|
||||||
|
statModifiers: {
|
||||||
|
attack: 0.9,
|
||||||
|
defense: 1.1,
|
||||||
|
speed: 1.0,
|
||||||
|
magic: 1.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'balanced_cat',
|
||||||
|
name: '成年貓',
|
||||||
|
icon: '😸',
|
||||||
|
description: '均衡發展的成年貓形態',
|
||||||
|
conditions: {
|
||||||
|
// 默認分支,無特殊條件
|
||||||
|
},
|
||||||
|
statModifiers: {
|
||||||
|
attack: 1.0,
|
||||||
|
defense: 1.0,
|
||||||
|
speed: 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
personality: ['活潑', '黏人']
|
personality: ['活潑', '黏人']
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
// 商店商品配置(資料驅動 - 隨機刷新機制)
|
||||||
|
|
||||||
|
// 商品池 - 所有可能出現的商品及其出現機率
|
||||||
|
export const SHOP_POOL = [
|
||||||
|
// 食物類 - 高機率
|
||||||
|
{
|
||||||
|
itemId: 'cookie',
|
||||||
|
category: 'food',
|
||||||
|
price: 10,
|
||||||
|
appearChance: 0.8, // 80% 機率出現
|
||||||
|
sellPrice: 5 // 賣出價格為購買價一半
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemId: 'tuna_can',
|
||||||
|
category: 'food',
|
||||||
|
price: 30,
|
||||||
|
appearChance: 0.6,
|
||||||
|
sellPrice: 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemId: 'premium_food',
|
||||||
|
category: 'food',
|
||||||
|
price: 50,
|
||||||
|
appearChance: 0.3, // 30% 機率
|
||||||
|
sellPrice: 25
|
||||||
|
},
|
||||||
|
|
||||||
|
// 藥品類 - 中機率
|
||||||
|
{
|
||||||
|
itemId: 'vitality_potion',
|
||||||
|
category: 'medicine',
|
||||||
|
price: 40,
|
||||||
|
appearChance: 0.5,
|
||||||
|
sellPrice: 20
|
||||||
|
},
|
||||||
|
|
||||||
|
// 裝備類 - 低機率
|
||||||
|
{
|
||||||
|
itemId: 'wooden_sword',
|
||||||
|
category: 'equipment',
|
||||||
|
price: 80,
|
||||||
|
appearChance: 0.3,
|
||||||
|
sellPrice: 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemId: 'leather_armor',
|
||||||
|
category: 'equipment',
|
||||||
|
price: 100,
|
||||||
|
appearChance: 0.25,
|
||||||
|
sellPrice: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemId: 'magic_wand',
|
||||||
|
category: 'equipment',
|
||||||
|
price: 150,
|
||||||
|
appearChance: 0.15, // 稀有
|
||||||
|
sellPrice: 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemId: 'hero_sword',
|
||||||
|
category: 'equipment',
|
||||||
|
price: 300,
|
||||||
|
appearChance: 0.05, // 極稀有
|
||||||
|
sellPrice: 150
|
||||||
|
},
|
||||||
|
|
||||||
|
// 玩具類
|
||||||
|
{
|
||||||
|
itemId: 'ball',
|
||||||
|
category: 'toy',
|
||||||
|
price: 20,
|
||||||
|
appearChance: 0.6,
|
||||||
|
sellPrice: 10
|
||||||
|
},
|
||||||
|
|
||||||
|
// 飾品類 - 稀有
|
||||||
|
{
|
||||||
|
itemId: 'lucky_charm',
|
||||||
|
category: 'accessory',
|
||||||
|
price: 150,
|
||||||
|
appearChance: 0.2,
|
||||||
|
sellPrice: 75
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 商品分類
|
||||||
|
export const SHOP_CATEGORIES = {
|
||||||
|
food: { name: '食物', icon: '🍖' },
|
||||||
|
medicine: { name: '藥品', icon: '💊' },
|
||||||
|
equipment: { name: '裝備', icon: '⚔️' },
|
||||||
|
toy: { name: '玩具', icon: '🎾' },
|
||||||
|
accessory: { name: '飾品', icon: '✨' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成隨機商店商品列表
|
||||||
|
export function generateShopItems() {
|
||||||
|
const items = []
|
||||||
|
for (const poolItem of SHOP_POOL) {
|
||||||
|
// 按機率決定是否出現
|
||||||
|
if (Math.random() < poolItem.appearChance) {
|
||||||
|
items.push({
|
||||||
|
...poolItem,
|
||||||
|
stock: -1 // 無限庫存
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 確保至少有3個商品
|
||||||
|
if (items.length < 3) {
|
||||||
|
// 隨機補充商品
|
||||||
|
const remaining = SHOP_POOL.filter(p => !items.find(i => i.itemId === p.itemId))
|
||||||
|
while (items.length < 3 && remaining.length > 0) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * remaining.length)
|
||||||
|
items.push({
|
||||||
|
...remaining[randomIndex],
|
||||||
|
stock: -1
|
||||||
|
})
|
||||||
|
remaining.splice(randomIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue