good version
This commit is contained in:
parent
310844bf1e
commit
384c8df9c7
955
app/app.vue
955
app/app.vue
File diff suppressed because it is too large
Load Diff
|
|
@ -44,7 +44,7 @@ export class AchievementSystem {
|
|||
this.achievementStats = { ...this.achievementStats, ...saved.stats }
|
||||
}
|
||||
console.log(`[AchievementSystem] 載入 ${this.unlockedAchievements.length} 個已解鎖成就`)
|
||||
|
||||
|
||||
// 重新應用已解鎖成就的加成
|
||||
await this.reapplyAchievementBuffs()
|
||||
} catch (error) {
|
||||
|
|
@ -55,7 +55,7 @@ export class AchievementSystem {
|
|||
const data = JSON.parse(saved)
|
||||
this.unlockedAchievements = data.unlocked || []
|
||||
this.achievementStats = { ...this.achievementStats, ...data.stats }
|
||||
|
||||
|
||||
// 重新應用已解鎖成就的加成
|
||||
await this.reapplyAchievementBuffs()
|
||||
}
|
||||
|
|
@ -65,10 +65,10 @@ export class AchievementSystem {
|
|||
// 重新應用已解鎖成就的加成(初始化時使用)
|
||||
async reapplyAchievementBuffs() {
|
||||
if (!this.petSystem) return
|
||||
|
||||
|
||||
const unlocked = this.achievements.filter(a => this.unlockedAchievements.includes(a.id))
|
||||
const achievementBuffs = {}
|
||||
|
||||
|
||||
// 累積所有已解鎖成就的加成
|
||||
for (const achievement of unlocked) {
|
||||
if (achievement.reward && achievement.reward.buffs) {
|
||||
|
|
@ -77,7 +77,7 @@ export class AchievementSystem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 更新寵物狀態
|
||||
if (Object.keys(achievementBuffs).length > 0) {
|
||||
await this.petSystem.updateState({ achievementBuffs })
|
||||
|
|
@ -127,9 +127,9 @@ export class AchievementSystem {
|
|||
|
||||
case 'perfect_state':
|
||||
if (condition.value) {
|
||||
return state.hunger >= 100 &&
|
||||
state.happiness >= 100 &&
|
||||
state.health >= 100
|
||||
return state.hunger >= 100 &&
|
||||
state.happiness >= 100 &&
|
||||
state.health >= 100
|
||||
}
|
||||
return false
|
||||
|
||||
|
|
@ -171,14 +171,14 @@ export class AchievementSystem {
|
|||
// 將成就加成添加到寵物狀態
|
||||
const currentState = this.petSystem.getState()
|
||||
const achievementBuffs = currentState.achievementBuffs || {}
|
||||
|
||||
|
||||
// 合併加成
|
||||
for (const [key, value] of Object.entries(achievement.reward.buffs)) {
|
||||
achievementBuffs[key] = (achievementBuffs[key] || 0) + value
|
||||
}
|
||||
|
||||
await this.petSystem.updateState({ achievementBuffs })
|
||||
|
||||
|
||||
// 重新計算戰鬥數值
|
||||
this.petSystem.calculateCombatStats()
|
||||
}
|
||||
|
|
@ -246,10 +246,10 @@ export class AchievementSystem {
|
|||
// 檢查完美狀態
|
||||
checkPerfectState() {
|
||||
const state = this.petSystem.getState()
|
||||
if (state.hunger >= 100 &&
|
||||
state.happiness >= 100 &&
|
||||
state.health >= 100 &&
|
||||
!this.achievementStats.perfectStateReached) {
|
||||
if (state.hunger >= 100 &&
|
||||
state.happiness >= 100 &&
|
||||
state.health >= 100 &&
|
||||
!this.achievementStats.perfectStateReached) {
|
||||
this.achievementStats.perfectStateReached = true
|
||||
this.checkAndUnlockAchievements()
|
||||
}
|
||||
|
|
@ -331,5 +331,44 @@ export class AchievementSystem {
|
|||
|
||||
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()
|
||||
case 'inventory/save':
|
||||
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:
|
||||
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 資料方法 ==========
|
||||
|
||||
getMockPetState() {
|
||||
|
|
@ -188,6 +213,11 @@ export class ApiService {
|
|||
|
||||
updateMockPetState(updates) {
|
||||
const current = this.getMockPetState()
|
||||
if (!current) {
|
||||
console.warn('[ApiService] No current state found, cannot update')
|
||||
return { success: false, message: '找不到當前狀態' }
|
||||
}
|
||||
// 使用深度合併,確保不會丟失原有字段
|
||||
const updated = { ...current, ...updates }
|
||||
localStorage.setItem('petState', JSON.stringify(updated))
|
||||
return { success: true, data: updated }
|
||||
|
|
@ -345,6 +375,11 @@ export class ApiService {
|
|||
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' (自動判斷)
|
||||
async equipItem(itemId, slot = null, equipType = 'auto') {
|
||||
// instanceId: 指定要裝備的實例ID(可選,如果不指定則自動選擇第一個未裝備的)
|
||||
async equipItem(itemId, slot = null, equipType = 'auto', instanceId = null) {
|
||||
const item = this.items[itemId]
|
||||
if (!item) {
|
||||
return { success: false, message: '道具不存在' }
|
||||
|
|
|
|||
|
|
@ -24,20 +24,30 @@ export class PetSystem {
|
|||
if (!this.state) {
|
||||
// 創建新寵物
|
||||
this.state = this.createInitialState(speciesId)
|
||||
// 載入種族配置
|
||||
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
|
||||
// 計算戰鬥數值(在保存前)
|
||||
this.calculateCombatStats()
|
||||
// 保存完整狀態(包含計算後的戰鬥數值)
|
||||
await this.api.savePetState(this.state)
|
||||
} else {
|
||||
// 確保 achievementBuffs 存在(向後兼容)
|
||||
if (!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.calculateCombatStats()
|
||||
}
|
||||
|
||||
// 載入種族配置
|
||||
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
|
||||
|
||||
// 計算戰鬥數值(在 speciesConfig 設置後)
|
||||
this.calculateCombatStats()
|
||||
|
||||
return this.state
|
||||
} catch (error) {
|
||||
console.error('[PetSystem] 初始化失敗:', error)
|
||||
|
|
@ -85,8 +95,9 @@ export class PetSystem {
|
|||
generation: 1,
|
||||
lastTickTime: Date.now(),
|
||||
achievementBuffs: {}, // 成就加成
|
||||
equipmentBuffs: {}, // 裝備加成
|
||||
appearance: {} // 外觀設定
|
||||
equipmentBuffs: { flat: {}, percent: {} }, // 裝備加成
|
||||
appearance: {}, // 外觀設定
|
||||
coins: 100 // 金幣(初始 100)
|
||||
}
|
||||
|
||||
// 分配命格
|
||||
|
|
@ -493,7 +504,7 @@ export class PetSystem {
|
|||
const wasDying = this.state.dyingSeconds > 0
|
||||
this.state.dyingSeconds = 0
|
||||
console.log('✨ 寵物脫離瀕死狀態')
|
||||
|
||||
|
||||
// 通知成就系統(如果存在)
|
||||
if (this.achievementSystem && wasDying) {
|
||||
this.achievementSystem.recordRecoveryFromDying()
|
||||
|
|
@ -559,7 +570,13 @@ export class PetSystem {
|
|||
// 戰鬥數值
|
||||
attack: this.state.attack,
|
||||
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
|
||||
const missingStats = []
|
||||
let missingStats = []
|
||||
|
||||
if (nextStage.conditions) {
|
||||
if (nextStage.conditions.str && this.state.str < nextStage.conditions.str) {
|
||||
statsConditionMet = false
|
||||
|
|
@ -616,10 +634,65 @@ export class PetSystem {
|
|||
}
|
||||
|
||||
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.updateState({ stage: nextStage.stage })
|
||||
this.updateState(updates)
|
||||
} else if (timeConditionMet && !statsConditionMet) {
|
||||
// 時間到了但屬性不足,只在第一次提示
|
||||
if (!this.state._evolutionWarned) {
|
||||
|
|
|
|||
|
|
@ -131,7 +131,9 @@ export class TempleSystem {
|
|||
|
||||
// 獲取好感度星級(每 20 點一星)
|
||||
getFavorStars(deityId) {
|
||||
if (!deityId) return '☆☆☆☆☆'
|
||||
const state = this.petSystem.getState()
|
||||
if (!state || !state.deityFavors) return '☆☆☆☆☆'
|
||||
const favor = state.deityFavors[deityId] || 0
|
||||
const stars = Math.floor(favor / 20)
|
||||
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 }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
export const ITEMS = {
|
||||
// ========== 裝備類(有耐久度)==========
|
||||
|
||||
|
||||
// 武器類
|
||||
wooden_sword: {
|
||||
id: 'wooden_sword',
|
||||
|
|
@ -23,7 +23,7 @@ export const ITEMS = {
|
|||
icon: '⚔️',
|
||||
appearance: null // 武器不改變外觀
|
||||
},
|
||||
|
||||
|
||||
iron_sword: {
|
||||
id: 'iron_sword',
|
||||
name: '鐵劍',
|
||||
|
|
@ -42,7 +42,7 @@ export const ITEMS = {
|
|||
icon: '🗡️',
|
||||
appearance: null
|
||||
},
|
||||
|
||||
|
||||
magic_staff: {
|
||||
id: 'magic_staff',
|
||||
name: '魔法杖',
|
||||
|
|
@ -61,7 +61,7 @@ export const ITEMS = {
|
|||
icon: '🪄',
|
||||
appearance: null
|
||||
},
|
||||
|
||||
|
||||
// 防具類
|
||||
leather_armor: {
|
||||
id: 'leather_armor',
|
||||
|
|
@ -81,7 +81,7 @@ export const ITEMS = {
|
|||
icon: '🛡️',
|
||||
appearance: null
|
||||
},
|
||||
|
||||
|
||||
iron_armor: {
|
||||
id: 'iron_armor',
|
||||
name: '鐵甲',
|
||||
|
|
@ -100,9 +100,9 @@ export const ITEMS = {
|
|||
icon: '⚔️',
|
||||
appearance: null
|
||||
},
|
||||
|
||||
|
||||
// ========== 外觀類(無耐久,不壞)==========
|
||||
|
||||
|
||||
cute_hat: {
|
||||
id: 'cute_hat',
|
||||
name: '可愛帽子',
|
||||
|
|
@ -121,7 +121,7 @@ export const ITEMS = {
|
|||
hat: 'cute_hat'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
cool_sunglasses: {
|
||||
id: 'cool_sunglasses',
|
||||
name: '酷炫墨鏡',
|
||||
|
|
@ -140,7 +140,7 @@ export const ITEMS = {
|
|||
accessory: 'cool_sunglasses'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
red_scarf: {
|
||||
id: 'red_scarf',
|
||||
name: '紅色圍巾',
|
||||
|
|
@ -157,9 +157,9 @@ export const ITEMS = {
|
|||
accessory: 'red_scarf'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// ========== 消耗品類 ==========
|
||||
|
||||
|
||||
cookie: {
|
||||
id: 'cookie',
|
||||
name: '幸運餅乾',
|
||||
|
|
@ -173,7 +173,7 @@ export const ITEMS = {
|
|||
description: '美味的餅乾,增加飢餓和快樂',
|
||||
icon: '🍪'
|
||||
},
|
||||
|
||||
|
||||
health_potion: {
|
||||
id: 'health_potion',
|
||||
name: '治療藥水',
|
||||
|
|
@ -188,7 +188,7 @@ export const ITEMS = {
|
|||
description: '恢復健康並治癒疾病',
|
||||
icon: '🧪'
|
||||
},
|
||||
|
||||
|
||||
energy_drink: {
|
||||
id: 'energy_drink',
|
||||
name: '能量飲料',
|
||||
|
|
@ -209,7 +209,7 @@ export const ITEMS = {
|
|||
description: '提供臨時速度和敏捷加成',
|
||||
icon: '🥤'
|
||||
},
|
||||
|
||||
|
||||
growth_pill: {
|
||||
id: 'growth_pill',
|
||||
name: '成長藥丸',
|
||||
|
|
@ -223,9 +223,9 @@ export const ITEMS = {
|
|||
description: '永久增加力量、智力、敏捷各 1 點',
|
||||
icon: '💊'
|
||||
},
|
||||
|
||||
|
||||
// ========== 護身符類(永久加成)==========
|
||||
|
||||
|
||||
lucky_amulet: {
|
||||
id: 'lucky_amulet',
|
||||
name: '幸運護身符',
|
||||
|
|
@ -242,7 +242,7 @@ export const ITEMS = {
|
|||
description: '帶來好運的護身符,增加運勢和掉落率',
|
||||
icon: '🔮'
|
||||
},
|
||||
|
||||
|
||||
protection_amulet: {
|
||||
id: 'protection_amulet',
|
||||
name: '平安符',
|
||||
|
|
@ -259,7 +259,7 @@ export const ITEMS = {
|
|||
description: '保佑平安的護身符,增加健康上限和恢復效率',
|
||||
icon: '🛡️'
|
||||
},
|
||||
|
||||
|
||||
wisdom_amulet: {
|
||||
id: 'wisdom_amulet',
|
||||
name: '智慧護身符',
|
||||
|
|
@ -276,9 +276,9 @@ export const ITEMS = {
|
|||
description: '提升智慧的護身符,增加智力和智力成長',
|
||||
icon: '📿'
|
||||
},
|
||||
|
||||
|
||||
// ========== 特殊道具類 ==========
|
||||
|
||||
|
||||
training_manual: {
|
||||
id: 'training_manual',
|
||||
name: '訓練手冊',
|
||||
|
|
@ -294,7 +294,7 @@ export const ITEMS = {
|
|||
description: '訓練指南,增加力量和敏捷成長效率',
|
||||
icon: '📖'
|
||||
},
|
||||
|
||||
|
||||
time_crystal: {
|
||||
id: 'time_crystal',
|
||||
name: '時間水晶',
|
||||
|
|
@ -305,9 +305,9 @@ export const ITEMS = {
|
|||
durability: Infinity,
|
||||
maxDurability: Infinity,
|
||||
effects: {
|
||||
percent: {
|
||||
strGain: 0.10,
|
||||
intGain: 0.10,
|
||||
percent: {
|
||||
strGain: 0.10,
|
||||
intGain: 0.10,
|
||||
dexGain: 0.10,
|
||||
happinessRecovery: 0.10,
|
||||
healthRecovery: 0.10
|
||||
|
|
@ -316,9 +316,9 @@ export const ITEMS = {
|
|||
description: '神秘的水晶,全面提升成長和恢復效率',
|
||||
icon: '💎'
|
||||
},
|
||||
|
||||
|
||||
// ========== 永久裝備(不會壞)==========
|
||||
|
||||
|
||||
golden_crown: {
|
||||
id: 'golden_crown',
|
||||
name: '黃金王冠',
|
||||
|
|
@ -351,37 +351,37 @@ export const ITEM_TYPES = {
|
|||
|
||||
// 稀有度定義(資料檔保持英文 key,但 name 為中文顯示用)
|
||||
export const ITEM_RARITY = {
|
||||
common: {
|
||||
name: '普通',
|
||||
color: '#9d9d9d',
|
||||
common: {
|
||||
name: '普通',
|
||||
color: '#9d9d9d',
|
||||
multiplier: 1.0,
|
||||
dropRate: 0.50, // 50% 基礎掉落機率
|
||||
description: '最常見的道具,容易獲得'
|
||||
},
|
||||
uncommon: {
|
||||
name: '優秀',
|
||||
color: '#1eff00',
|
||||
uncommon: {
|
||||
name: '優秀',
|
||||
color: '#1eff00',
|
||||
multiplier: 1.2,
|
||||
dropRate: 0.30, // 30% 基礎掉落機率
|
||||
description: '較為少見的道具,有一定價值'
|
||||
},
|
||||
rare: {
|
||||
name: '稀有',
|
||||
color: '#0070dd',
|
||||
rare: {
|
||||
name: '稀有',
|
||||
color: '#0070dd',
|
||||
multiplier: 1.5,
|
||||
dropRate: 0.15, // 15% 基礎掉落機率
|
||||
description: '稀有的道具,效果顯著'
|
||||
},
|
||||
epic: {
|
||||
name: '史詩',
|
||||
color: '#a335ee',
|
||||
epic: {
|
||||
name: '史詩',
|
||||
color: '#a335ee',
|
||||
multiplier: 2.0,
|
||||
dropRate: 0.05, // 5% 基礎掉落機率
|
||||
description: '史詩級道具,非常珍貴'
|
||||
},
|
||||
legendary: {
|
||||
name: '傳說',
|
||||
color: '#ff8000',
|
||||
legendary: {
|
||||
name: '傳說',
|
||||
color: '#ff8000',
|
||||
multiplier: 3.0,
|
||||
dropRate: 0.01, // 1% 基礎掉落機率
|
||||
description: '傳說級道具,極其稀有且強大'
|
||||
|
|
|
|||
|
|
@ -153,7 +153,67 @@ export const PET_SPECIES = {
|
|||
str: 50, // 3天內累積
|
||||
int: 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: ['活潑', '黏人']
|
||||
|
|
|
|||
|
|
@ -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