pet_data/core/pet-system.js

1085 lines
38 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 寵物系統核心 - 與 API 整合
import { apiService } from './api-service.js'
import { PET_SPECIES } from '../data/pet-species.js'
import { FATES } from '../data/fates.js'
import { DEITIES } from '../data/deities.js'
import { DeitySystem } from './deity-system.js'
import { QUEST_TYPES } from '../data/deity-quests.js'
export class PetSystem {
constructor(api = apiService, achievementSystem = null, inventorySystem = null) {
this.api = api
this.achievementSystem = achievementSystem
this.inventorySystem = inventorySystem
this.deitySystem = new DeitySystem(this) // Initialize DeitySystem with PetSystem instance
this.state = null
this.speciesConfig = null
this.tickInterval = null
this.eventCheckInterval = null
}
// 初始化寵物(從 API 載入或創建新寵物)
async initialize(speciesId = 'tinyTigerCat') {
try {
// 從 API 載入現有狀態
this.state = await this.api.getPetState()
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()
}
// 初始化神明系統
await this.deitySystem.initialize(this.state.deitySystemState)
return this.state
} catch (error) {
console.error('[PetSystem] 初始化失敗:', error)
// 降級到本地狀態
this.state = this.createInitialState(speciesId)
this.speciesConfig = PET_SPECIES[speciesId]
// 計算戰鬥數值
this.calculateCombatStats()
return this.state
}
}
// 創建初始狀態
createInitialState(speciesId) {
const config = PET_SPECIES[speciesId]
const state = {
speciesId,
name: null, // 寵物名稱,初始為 null
stage: 'egg',
hunger: 100,
happiness: 100,
health: 100,
height: config.lifecycle[0]?.height || config.baseStats.defaultHeight || 10, // 從第一階段獲取身高
weight: config.lifecycle[0]?.baseWeight || config.baseStats.defaultWeight || 500, // 從第一階段獲取體重
ageSeconds: 0,
poopCount: 0,
str: 10,
int: 10,
dex: 10,
luck: config.baseStats.luck || 10,
isSleeping: false,
isSick: false,
isDead: false,
dyingSeconds: 0, // 瀕死計時
currentDeityId: 'mazu',
deityFavors: {
mazu: 0,
earthgod: 0,
yuelao: 0,
wenchang: 0,
guanyin: 0
},
dailyPrayerCount: 0,
destiny: null,
buffs: [],
inventory: [],
generation: 1,
lastTickTime: Date.now(),
achievementBuffs: {}, // 成就加成
equipmentBuffs: { flat: {}, percent: {} }, // 裝備加成
appearance: {}, // 外觀設定
coins: 100 // 金幣(初始 100
}
// 分配命格
this.assignDestiny(state)
// 注意:不在這裡計算戰鬥數值,因為此時 speciesConfig 可能還沒設置
// calculateCombatStats 會在 initialize 之後調用
return state
}
// 分配命格
assignDestiny(state) {
// 隨機選擇一個命格
const fateIndex = Math.floor(Math.random() * FATES.length)
state.destiny = FATES[fateIndex]
console.log(`🎲 命格分配: ${state.destiny.name} - ${state.destiny.description}`)
console.log(' 加成:', state.destiny.buffs)
// 應用命格初始加成 (如果是直接加數值的)
if (state.destiny.buffs.luck) {
state.luck += state.destiny.buffs.luck
console.log(` 運勢加成: +${state.destiny.buffs.luck} -> ${state.luck}`)
}
}
// 獲取所有加成(命格 + 神明基礎 + 神明好感度等級 + 神明滿級 + 成就 + Buff
getAllBonuses() {
const bonuses = {}
// 1. 命格加成
if (this.state.destiny && this.state.destiny.buffs) {
for (const [key, value] of Object.entries(this.state.destiny.buffs)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
// 2. 神明系統加成 (取代舊的神明加成邏輯)
if (this.deitySystem) {
const deityBuffs = this.deitySystem.getActiveBuffs()
for (const [key, value] of Object.entries(deityBuffs)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
/* 舊邏輯已廢棄,由 DeitySystem 接管
// 2. 神明基礎加成
if (this.state.currentDeityId) {
const deity = DEITIES.find(d => d.id === this.state.currentDeityId)
if (deity && deity.buffs) {
for (const [key, value] of Object.entries(deity.buffs)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
// 3. 神明好感度等級加成
if (deity && deity.favorLevelBuffs) {
const favor = this.state.deityFavors?.[this.state.currentDeityId] || 0
const level = Math.floor(favor / deity.favorLevelBuffs.interval)
if (level > 0 && deity.favorLevelBuffs.buffsPerLevel) {
for (const [key, valuePerLevel] of Object.entries(deity.favorLevelBuffs.buffsPerLevel)) {
bonuses[key] = (bonuses[key] || 0) + (valuePerLevel * level)
}
}
}
// 4. 神明滿級特殊 Buff好感度 = 100
if (deity && deity.maxFavorBuff) {
const favor = this.state.deityFavors?.[this.state.currentDeityId] || 0
if (favor >= 100 && deity.maxFavorBuff.effects) {
for (const [key, value] of Object.entries(deity.maxFavorBuff.effects)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
}
}
*/
// 5. 成就加成
if (this.state.achievementBuffs) {
for (const [key, value] of Object.entries(this.state.achievementBuffs)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
// 6. 裝備加成
if (this.state.equipmentBuffs) {
// 處理 flat 加成(直接數值加成)
if (this.state.equipmentBuffs.flat) {
for (const [key, value] of Object.entries(this.state.equipmentBuffs.flat)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
// 處理 percent 加成(百分比加成)
if (this.state.equipmentBuffs.percent) {
for (const [key, value] of Object.entries(this.state.equipmentBuffs.percent)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
}
return bonuses
}
// 獲取最大健康值100 + health加成
getMaxHealth() {
const bonuses = this.getAllBonuses()
return 100 + (bonuses.health || 0)
}
// 獲取體重狀態
getWeightStatus() {
if (!this.speciesConfig || !this.state) return 'unknown'
const currentStageConfig = this.speciesConfig.lifecycle.find(s => s.stage === this.state.stage)
if (!currentStageConfig || !currentStageConfig.weightRange) return 'unknown'
const { min, max } = currentStageConfig.weightRange
const weight = this.state.weight
if (weight < min) return 'underweight'
if (weight > max) return 'overweight'
return 'normal'
}
// 檢查當前階段是否允許某個動作(資料驅動)
isActionAllowed(action) {
if (!this.speciesConfig || !this.state) return false
const currentStageConfig = this.speciesConfig.lifecycle.find(s => s.stage === this.state.stage)
if (!currentStageConfig) return false
const allowedActions = currentStageConfig.allowedActions || []
return allowedActions.includes(action)
}
// 檢查當前階段是否啟用某個系統(資料驅動)
isSystemEnabled(system) {
if (!this.speciesConfig || !this.state) return true
const currentStageConfig = this.speciesConfig.lifecycle.find(s => s.stage === this.state.stage)
if (!currentStageConfig || !currentStageConfig.enabledSystems) return true
return currentStageConfig.enabledSystems[system] !== false
}
// 檢查當前時間是否在睡眠時段
isInSleepTime() {
if (!this.speciesConfig?.baseStats?.sleepSchedule) return null
const now = new Date()
const currentHour = now.getHours()
const currentMinute = now.getMinutes()
const currentTimeInMinutes = currentHour * 60 + currentMinute
const schedule = this.speciesConfig.baseStats.sleepSchedule
// 檢查夜間睡眠 (21:30 - 08:00)
if (schedule.nightSleep && schedule.nightSleep.autoSleep) {
const startMinutes = schedule.nightSleep.startHour * 60 + schedule.nightSleep.startMinute
const endMinutes = schedule.nightSleep.endHour * 60 + schedule.nightSleep.endMinute
// 處理跨午夜的情況
if (startMinutes > endMinutes) {
// 例如 21:30 (1290分) 到 08:00 (480分)
if (currentTimeInMinutes >= startMinutes || currentTimeInMinutes < endMinutes) {
return 'nightSleep'
}
} else {
if (currentTimeInMinutes >= startMinutes && currentTimeInMinutes < endMinutes) {
return 'nightSleep'
}
}
}
// 檢查午休 (12:00 - 13:00)
if (schedule.noonNap && schedule.noonNap.autoSleep) {
const startMinutes = schedule.noonNap.startHour * 60 + schedule.noonNap.startMinute
const endMinutes = schedule.noonNap.endHour * 60 + schedule.noonNap.endMinute
if (currentTimeInMinutes >= startMinutes && currentTimeInMinutes < endMinutes) {
return 'noonNap'
}
}
return null
}
// 獲取睡眠時段配置
getSleepPeriod(periodName) {
if (!this.speciesConfig?.baseStats?.sleepSchedule) return null
return this.speciesConfig.baseStats.sleepSchedule[periodName]
}
// 計算戰鬥數值
calculateCombatStats(state = this.state) {
if (!state || !this.speciesConfig) return
// 1. 獲取所有加成
const bonuses = this.getAllBonuses()
// 2. 計算有效屬性 (基礎 + 加成)
const effectiveStr = state.str + (bonuses.str || 0)
const effectiveInt = state.int + (bonuses.int || 0)
const effectiveDex = state.dex + (bonuses.dex || 0)
const effectiveLuck = (state.luck || 0) + (bonuses.luck || 0)
// 3. 獲取百分比加成 (Modifiers)
const atkMod = 1 + (bonuses.attack || 0)
const defMod = 1 + (bonuses.defense || 0)
const spdMod = 1 + (bonuses.speed || 0)
// 4. 從配置讀取戰鬥數值計算系數
const formulas = this.speciesConfig.baseStats.combatFormulas || {
attack: { strMultiplier: 2.5, dexMultiplier: 0.5 },
defense: { strMultiplier: 1.0, intMultiplier: 2.0 },
speed: { dexMultiplier: 3.0, intMultiplier: 0.5 }
}
// 5. 計算最終戰鬥屬性
state.attack = (effectiveStr * formulas.attack.strMultiplier + effectiveDex * formulas.attack.dexMultiplier) * atkMod
state.defense = (effectiveStr * formulas.defense.strMultiplier + effectiveInt * formulas.defense.intMultiplier) * defMod
state.speed = (effectiveDex * formulas.speed.dexMultiplier + effectiveInt * formulas.speed.intMultiplier) * spdMod
// 保存有效屬性到 state (供 UI 顯示)
state.effectiveStr = effectiveStr
state.effectiveInt = effectiveInt
state.effectiveDex = effectiveDex
state.effectiveLuck = effectiveLuck
// [DEBUG] 輸出屬性計算詳情
console.groupCollapsed('[PetSystem] 屬性計算詳情');
console.log('原始屬性:', { STR: state.str, INT: state.int, DEX: state.dex, LUCK: state.luck });
console.log('加成來源:', bonuses);
console.log('有效屬性 (原始+加成):', { STR: effectiveStr, INT: effectiveInt, DEX: effectiveDex, LUCK: effectiveLuck });
console.log('戰鬥屬性:', { ATK: state.attack, DEF: state.defense, SPD: state.speed });
console.groupEnd();
// 根據當前階段設定身高(每個階段固定)
const currentStageConfig = this.speciesConfig.lifecycle.find(s => s.stage === state.stage)
if (currentStageConfig && currentStageConfig.height) {
state.height = currentStageConfig.height
} else if (!state.height) {
state.height = this.speciesConfig.baseStats.defaultHeight || 10
}
// 體重:如果沒有初始化,使用階段基礎體重
if (!state.weight && currentStageConfig && currentStageConfig.baseWeight) {
state.weight = currentStageConfig.baseWeight
} else if (!state.weight) {
state.weight = this.speciesConfig.baseStats.defaultWeight || 500
}
}
// 更新狀態(同步到 API
async updateState(updates) {
// 如果更新了 speciesId重新載入配置
if (updates.speciesId && updates.speciesId !== this.state.speciesId) {
this.speciesConfig = PET_SPECIES[updates.speciesId]
// 可能需要重新計算屬性或重置某些狀態?
// 暫時只更新配置
}
this.state = { ...this.state, ...updates }
try {
await this.api.updatePetState(updates)
} catch (error) {
console.warn('[PetSystem] API 更新失敗,使用本地狀態:', error)
}
return this.state
}
// 獲取當前狀態
getState() {
const state = { ...this.state };
// Ensure critical values are valid numbers
if (typeof state.happiness !== 'number' || isNaN(state.happiness)) {
console.warn('[PetSystem] Invalid happiness value, resetting to 100');
state.happiness = 100;
this.state.happiness = 100;
}
if (typeof state.hunger !== 'number' || isNaN(state.hunger)) {
console.warn('[PetSystem] Invalid hunger value, resetting to 100');
state.hunger = 100;
this.state.hunger = 100;
}
if (typeof state.health !== 'number' || isNaN(state.health)) {
console.warn('[PetSystem] Invalid health value, resetting to 100');
state.health = 100;
this.state.health = 100;
}
return state;
}
// 刪除寵物
async deletePet() {
try {
// 停止所有循環
this.stopTickLoop()
// 從 API 刪除
await this.api.deletePetState()
// 重置狀態
this.state = null
this.speciesConfig = null
return { success: true, message: '寵物已刪除' }
} catch (error) {
console.error('[PetSystem] 刪除寵物失敗:', error)
// 即使 API 失敗,也清除本地狀態
this.state = null
this.speciesConfig = null
return { success: true, message: '寵物已刪除(本地)' }
}
}
// Tick 循環(生理系统 - 从配置读取间隔)
startTickLoop(callback) {
if (this.tickInterval) this.stopTickLoop()
// 从配置读取间隔时间
const interval = this.speciesConfig?.baseStats?.physiologyTickInterval || 60000
// 立即執行一次回調,確保 UI 獲得最新狀態
if (callback) callback(this.getState())
this.tickInterval = setInterval(async () => {
await this.tick()
if (callback) callback(this.getState())
}, interval)
}
stopTickLoop() {
if (this.tickInterval) {
clearInterval(this.tickInterval)
this.tickInterval = null
}
}
// 單次 Tick 執行
async tick() {
if (!this.state || this.state.isDead) return
const now = Date.now()
const deltaTime = (now - (this.state.lastTickTime || now)) / 1000
this.state.lastTickTime = now
// 年齡增長
this.state.ageSeconds += deltaTime
// 檢查階段轉換
this.checkStageTransition()
// 檢查自動睡眠(基於真實時間)
if (this.isSystemEnabled('sleep')) {
const sleepPeriod = this.isInSleepTime()
if (sleepPeriod && !this.state.isSleeping) {
// 在睡眠時段且未睡覺,自動進入睡眠
this.state.isSleeping = true
const periodConfig = this.getSleepPeriod(sleepPeriod)
const periodName = sleepPeriod === 'nightSleep' ? '夜間睡眠' : '午休'
console.log(`😴 ${periodName}時間,寵物自動進入睡眠`)
} else if (!sleepPeriod && this.state.isSleeping && this.state._autoSlept) {
// 不在睡眠時段且是自動睡眠狀態,自動醒來
this.state.isSleeping = false
this.state._autoSlept = false
console.log(`⏰ 睡眠時間結束,寵物自動醒來`)
}
}
// 根據當前階段配置決定啟用哪些系統(資料驅動)
const config = this.speciesConfig.baseStats
// 睡眠狀態下的衰減倍率(從配置讀取)
const sleepDecayMultiplier = this.state.isSleeping ? (config.sleepDecayMultiplier || 0.1) : 1.0
// 獲取加成(用於檢查免疫)
const bonuses = this.getAllBonuses()
const hasImmunity = bonuses.sicknessImmune // 妈祖满级等
// 飢餓系統
if (this.isSystemEnabled('hunger')) {
this.state.hunger = Math.max(0, this.state.hunger - config.hungerDecayPerTick * sleepDecayMultiplier)
// 飢餓過度扣血(有免疫則跳過)
if (!hasImmunity && this.state.hunger <= 0) {
const damage = config.hungerHealthDamage || 1
this.state.health = Math.max(0, this.state.health - damage)
if (damage > 0) console.log(`📉 飢餓扣除健康: -${damage}`)
}
}
// 快樂系統
if (this.isSystemEnabled('happiness')) {
this.state.happiness = Math.max(0, this.state.happiness - config.happinessDecayPerTick * sleepDecayMultiplier)
}
// 便便系統(睡覺時不會拉屎)
if (this.isSystemEnabled('poop') && !this.state.isSleeping) {
if (Math.random() < config.poopChancePerTick) {
const maxPoop = config.maxPoopCount || 4
this.state.poopCount = Math.min(maxPoop, this.state.poopCount + 1)
}
}
// 便便扣健康(有免疫則跳過)
if (!hasImmunity && this.isSystemEnabled('poop') && this.state.poopCount > 0) {
const damage = (config.poopHealthDamage || 0.5) * this.state.poopCount
this.state.health = Math.max(0, this.state.health - damage)
if (damage > 0) console.log(`📉 便便扣除健康: -${damage} (數量: ${this.state.poopCount})`)
}
// 生病系統
if (this.isSystemEnabled('sickness')) {
// 健康過低導致生病(從配置讀取閾值)
const threshold = config.sicknessThreshold || 60
// 如果有免疫生病 Buff則不會生病
if (!hasImmunity && !this.state.isSick && this.state.health < threshold) {
this.state.isSick = true
console.log(`🤢 健康低於 ${threshold},寵物生病了`)
}
}
// 死亡檢查(從配置讀取瀕死時間)
const dyingTime = config.dyingTimeSeconds || 1800
if (this.state.health <= 0) {
this.state.dyingSeconds = (this.state.dyingSeconds || 0) + deltaTime
// 每 60 秒提示一次
if (Math.floor(this.state.dyingSeconds) % 60 === 0) {
const remaining = dyingTime - this.state.dyingSeconds
console.log(`⚠️ 寵物瀕死中!剩餘時間: ${(remaining / 60).toFixed(1)} 分鐘`)
}
if (this.state.dyingSeconds >= dyingTime) {
this.state.isDead = true
console.log('💀 寵物死亡')
await this.updateState({ isDead: true })
}
} else {
// 健康恢復,重置瀕死計時
if (this.state.dyingSeconds > 0) {
const wasDying = this.state.dyingSeconds > 0
this.state.dyingSeconds = 0
console.log('✨ 寵物脫離瀕死狀態')
// 通知成就系統(如果存在)
if (this.achievementSystem && wasDying) {
this.achievementSystem.recordRecoveryFromDying()
}
}
}
// 檢查成就(年齡、階段、完美狀態等)
if (this.achievementSystem) {
await this.achievementSystem.checkAndUnlockAchievements()
this.achievementSystem.checkPerfectState()
}
// 減少裝備耐久度(每 tick 減少少量,戰鬥或特殊情況會減少更多)
// 只對已裝備過的物品減少耐久度hasBeenEquipped = true
if (this.state.inventory && Array.isArray(this.state.inventory)) {
const equippedItems = this.state.inventory.filter(item => item.isEquipped && !item.isAppearance);
for (const item of equippedItems) {
// 只對已經被裝備過的物品減少耐久度
if (item.hasBeenEquipped && item.maxDurability && item.maxDurability !== Infinity && item.durability > 0) {
// 每 10 個 tick 減少 1 點耐久度(約每 30 秒)
if (this.state._tickCount && this.state._tickCount % 10 === 0) {
item.durability = Math.max(0, item.durability - 1);
// 如果耐久度降為 0自動卸下裝備
if (item.durability <= 0) {
item.isEquipped = false;
item.isAppearance = false;
console.log(`⚠️ ${item.name} 耐久度耗盡,已自動卸下`);
// 重新計算裝備加成
this.calculateCombatStats();
}
}
}
}
}
// Legacy inventory system support (如果使用舊的 inventorySystem)
if (this.inventorySystem) {
// 只有裝備中的道具才會減少耐久度
const equipped = this.inventorySystem.getEquipped()
for (const [slot, equippedItem] of Object.entries(equipped)) {
if (equippedItem) {
// 每 10 個 tick 減少 1 點耐久度(約每 30 秒)
if (this.state._tickCount && this.state._tickCount % 10 === 0) {
await this.inventorySystem.reduceDurability(slot, 1)
}
}
}
}
// 重新计算属性(含加成)
this.calculateCombatStats()
// 初始化 tick 計數器
if (!this.state._tickCount) {
this.state._tickCount = 0
}
this.state._tickCount++
// 同步到 API
await this.updateState({
// 基礎屬性
ageSeconds: this.state.ageSeconds,
stage: this.state.stage,
hunger: this.state.hunger,
happiness: this.state.happiness,
health: this.state.health,
weight: this.state.weight,
height: this.state.height,
// 能力值
str: this.state.str,
int: this.state.int,
dex: this.state.dex,
luck: this.state.luck,
// 有效能力值 (含加成)
effectiveStr: this.state.effectiveStr,
effectiveInt: this.state.effectiveInt,
effectiveDex: this.state.effectiveDex,
effectiveLuck: this.state.effectiveLuck,
// 狀態
poopCount: this.state.poopCount,
isSleeping: this.state.isSleeping,
isSick: this.state.isSick,
isDead: this.state.isDead,
dyingSeconds: this.state.dyingSeconds,
// 戰鬥數值
attack: this.state.attack,
defense: this.state.defense,
speed: this.state.speed,
// 命格和神明
destiny: this.state.destiny,
currentDeityId: this.state.currentDeityId,
deityFavors: this.state.deityFavors,
// 经济
coins: this.state.coins
})
}
// Debug: 直接設置屬性
async debugSetStat(stat, value) {
if (!this.state) return
console.log(`[Debug] 設置 ${stat} = ${value}`)
await this.updateState({ [stat]: value })
// 如果修改的是戰鬥相關屬性,重新計算
if (['str', 'int', 'dex', 'weight'].includes(stat)) {
this.calculateCombatStats()
}
return this.state
}
// 檢查階段轉換
checkStageTransition() {
if (!this.speciesConfig || !this.state) return
const config = this.speciesConfig
const currentStage = this.state.stage
const ageSeconds = this.state.ageSeconds
// 找到當前階段
const currentIndex = config.lifecycle.findIndex(s => s.stage === currentStage)
if (currentIndex < 0 || currentIndex >= config.lifecycle.length - 1) {
return // 找不到或已是最終階段
}
const currentStageConfig = config.lifecycle[currentIndex]
const nextStage = config.lifecycle[currentIndex + 1]
// 檢查時間條件:年齡是否超過當前階段的持續時間
const timeConditionMet = ageSeconds >= currentStageConfig.durationSeconds && currentStageConfig.durationSeconds > 0
// 檢查屬性條件
let statsConditionMet = true
let missingStats = []
if (nextStage.conditions) {
if (nextStage.conditions.str && this.state.str < nextStage.conditions.str) {
statsConditionMet = false
missingStats.push(`STR ${this.state.str.toFixed(1)}/${nextStage.conditions.str}`)
}
if (nextStage.conditions.int && this.state.int < nextStage.conditions.int) {
statsConditionMet = false
missingStats.push(`INT ${this.state.int.toFixed(1)}/${nextStage.conditions.int}`)
}
if (nextStage.conditions.dex && this.state.dex < nextStage.conditions.dex) {
statsConditionMet = false
missingStats.push(`DEX ${this.state.dex.toFixed(1)}/${nextStage.conditions.dex}`)
}
}
if (timeConditionMet && statsConditionMet) {
// 檢查是否有進化分支
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 }
// 更新身高和體重(從新階段配置中獲取)
const newStageIndex = config.lifecycle.findIndex(s => s.stage === targetStage)
if (newStageIndex >= 0) {
const newStageConfig = config.lifecycle[newStageIndex]
if (newStageConfig.height) {
updates.height = newStageConfig.height
}
if (newStageConfig.baseWeight) {
updates.weight = newStageConfig.baseWeight
}
}
// 應用進化分支效果
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(updates)
} else if (timeConditionMet && !statsConditionMet) {
// 時間到了但屬性不足,只在第一次提示
if (!this.state._evolutionWarned) {
console.log(`⚠️ 年齡已達到 (${ageSeconds.toFixed(1)}秒),但屬性不足以進化到 ${nextStage.stage}`)
console.log(` 需要: ${missingStats.join(', ')}`)
this.state._evolutionWarned = true
}
}
}
// 餵食
async feed(amount = 12) {
if (!this.isActionAllowed('feed')) return { success: false, message: '當前階段不能餵食' }
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
if (this.state.isSleeping) return { success: false, message: '寵物正在睡覺,無法餵食' }
if (this.state.hunger >= 100) return { success: false, message: '寵物已經吃飽了' }
const newHunger = Math.min(100, this.state.hunger + amount)
// 體重隨機變化 ±10%
const baseWeightChange = amount * 0.5
const randomFactor = 0.9 + Math.random() * 0.2 // 0.9 ~ 1.1
const weightChange = baseWeightChange * randomFactor
const newWeight = this.state.weight + weightChange
// 應用命格的力量成長加成
const bonuses = this.getAllBonuses()
const strGainBonus = bonuses.strGain || 0
const strGain = 0.3 * (1 + strGainBonus) // 降低成長速度
if (strGainBonus > 0) {
console.log(`[餵食] 力量成長加成: ${(strGainBonus * 100).toFixed(0)}% -> +${strGain.toFixed(2)} STR`)
}
// 自然成長上限為 100
const currentStr = this.state.str
const newStr = currentStr < 100 ? Math.min(100, currentStr + strGain) : currentStr
await this.updateState({
hunger: newHunger,
weight: newWeight,
str: newStr
})
// 記錄到成就系統
if (this.achievementSystem) {
await this.achievementSystem.recordAction('feed')
this.achievementSystem.checkPerfectState()
}
this.calculateCombatStats()
// 觸發任務更新
if (this.deitySystem) {
this.deitySystem.updateQuestProgress(QUEST_TYPES.FEED, 1)
}
return { success: true, hunger: newHunger, weight: newWeight, strGain, weightChange }
}
// 玩耍
async play(options = {}) {
if (!this.isActionAllowed('play')) return { success: false, message: '當前階段不能玩耍' }
if (this.state.isDead || this.state.isSleeping) {
return { success: false, message: '寵物無法玩耍' }
}
// 檢查飢餓度
if (this.state.hunger <= 0) {
return { success: false, message: '寵物太餓了,沒力氣玩耍' }
}
// 可以傳入 { amount: 10, gameType: 'training' | 'puzzle' | 'normal' }
const amount = options.amount || options || 10 // 向後兼容舊的調用方式
const gameType = options.gameType || 'normal'
// 應用命格和神明的屬性成長加成
const bonuses = this.getAllBonuses()
const happinessBonus = bonuses.happinessRecovery || 0
const dexBonus = bonuses.dexGain || 0
const intBonus = bonuses.intGain || 0
const happinessGain = amount * (1 + happinessBonus)
const dexGain = 0.3 * (1 + dexBonus) // 降低成長速度
const intGain = 0.15 * (1 + intBonus) // 降低成長速度
// 體重變化根據遊戲類型,並加入隨機 ±10%
let baseWeightChange = 0
let gameTypeLabel = ''
if (gameType === 'training') {
// 訓練類型:消耗體力,減重
baseWeightChange = -3
gameTypeLabel = '🏃 訓練'
} else if (gameType === 'puzzle') {
// 益智類型:不影響體重
baseWeightChange = 0
gameTypeLabel = '🧩 益智'
} else {
// 一般玩耍:輕微減重
baseWeightChange = -1
gameTypeLabel = '🎮 玩耍'
}
// 添加隨機波動 ±10%(除了益智類型)
const randomFactor = baseWeightChange !== 0 ? (0.9 + Math.random() * 0.2) : 0
const weightChange = baseWeightChange * randomFactor
// 顯示加成細節
if (happinessBonus > 0 || dexBonus > 0 || intBonus > 0) {
// 分別計算命格和神明的加成
let fateBonus = 0
let deityBonus = 0
if (this.state.destiny?.buffs?.happinessRecovery) fateBonus += this.state.destiny.buffs.happinessRecovery
const deity = DEITIES.find(d => d.id === this.state.currentDeityId)
if (deity?.buffs?.happinessRecovery) deityBonus += deity.buffs.happinessRecovery
console.log(`[${gameTypeLabel}] 快樂加成: 命格${(fateBonus * 100).toFixed(0)}% + 神明${(deityBonus * 100).toFixed(0)}% = ${(happinessBonus * 100).toFixed(0)}% | DEX${(dexBonus * 100).toFixed(0)}% INT${(intBonus * 100).toFixed(0)}%`)
}
const newHappiness = Math.min(100, this.state.happiness + happinessGain)
// 自然成長上限為 100
const currentDex = this.state.dex
const currentInt = this.state.int
const newDex = currentDex < 100 ? Math.min(100, currentDex + dexGain) : currentDex
const newInt = currentInt < 100 ? Math.min(100, currentInt + intGain) : currentInt
const newWeight = Math.max(50, this.state.weight + weightChange) // 最低50g
const hungerCost = this.speciesConfig.baseStats.playHungerCost || 1
const newHunger = Math.max(0, this.state.hunger - hungerCost) // 玩耍消耗 3 點飢餓
await this.updateState({
happiness: newHappiness,
hunger: newHunger,
dex: newDex,
int: newInt,
weight: newWeight
})
// 記錄到成就系統
if (this.achievementSystem) {
await this.achievementSystem.recordAction('play')
this.achievementSystem.checkPerfectState()
}
this.calculateCombatStats()
// 觸發任務更新
if (this.deitySystem) {
this.deitySystem.updateQuestProgress(QUEST_TYPES.PLAY, 1)
this.deitySystem.updateQuestProgress(QUEST_TYPES.MINIGAME_WIN, 1) // 假設玩耍算作小遊戲勝利,或者需要區分
}
return {
success: true,
happiness: newHappiness,
happinessGain,
dexGain,
intGain,
weightChange,
gameType: gameTypeLabel
}
}
// 清理便便
async cleanPoop() {
if (!this.isActionAllowed('clean')) return { success: false, message: '當前階段不需要清理' }
if (this.state.poopCount === 0) {
return { success: false, message: '沒有便便需要清理' }
}
const newPoopCount = 0
const newHappiness = Math.min(100, this.state.happiness + 10)
await this.updateState({
poopCount: newPoopCount,
happiness: newHappiness
})
// 記錄到成就系統
if (this.achievementSystem) {
await this.achievementSystem.recordAction('clean')
this.achievementSystem.checkPerfectState()
}
// 觸發任務更新
if (this.deitySystem) {
this.deitySystem.updateQuestProgress(QUEST_TYPES.CLEAN, 1)
}
return { success: true, poopCount: newPoopCount, happiness: newHappiness }
}
// 治療
async heal(amount = 20) {
if (!this.isActionAllowed('heal')) return { success: false, message: '當前階段不需要治療' }
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
if (this.state.isSleeping) return { success: false, message: '寵物正在睡覺,無法治療' }
const maxHealth = this.getMaxHealth()
if (this.state.health >= maxHealth) return { success: false, message: '寵物非常健康,不需要治療' }
// 應用健康恢復加成
const bonuses = this.getAllBonuses()
const healAmount = amount * (1 + (bonuses.healthRecovery || 0))
const newHealth = Math.min(maxHealth, this.state.health + healAmount)
const wasSick = this.state.isSick
await this.updateState({
health: newHealth,
isSick: false
})
// 記錄到成就系統
if (this.achievementSystem) {
await this.achievementSystem.recordAction('heal')
this.achievementSystem.checkPerfectState()
}
return {
success: true,
health: newHealth,
isSick: false,
cured: wasSick && !this.state.isSick,
healAmount
}
}
// 睡覺/起床
async toggleSleep() {
if (!this.isActionAllowed('sleep')) return { success: false, message: '當前階段不能睡覺' }
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
const newIsSleeping = !this.state.isSleeping
const healthChange = newIsSleeping ? 5 : 0
// 檢查是否在睡眠時段
const sleepPeriod = this.isInSleepTime()
let message = ''
let willRandomSleep = false
if (newIsSleeping) {
// 進入睡眠
this.state._autoSlept = false // 標記為手動睡眠
message = '寵物進入睡眠'
} else {
// 嘗試醒來
if (sleepPeriod) {
// 在睡眠時段醒來,可能會隨機再次入睡
const periodConfig = this.getSleepPeriod(sleepPeriod)
const randomWakeChance = periodConfig?.randomWakeChance || 0
if (Math.random() < randomWakeChance) {
// 隨機再次入睡
willRandomSleep = true
this.state._autoSlept = true
const periodName = sleepPeriod === 'nightSleep' ? '夜間' : '午休'
message = `寵物醒來後打了個哈欠,在${periodName}時段又睡著了...`
// 延遲一下再設回睡眠狀態(給用戶看到醒來的訊息)
setTimeout(async () => {
if (this.state && !this.state.isDead) {
this.state.isSleeping = true
await this.updateState({ isSleeping: true, _autoSlept: true })
}
}, 100)
} else {
message = '寵物醒來了'
}
} else {
message = '寵物醒來了'
}
}
const maxHealth = this.getMaxHealth()
await this.updateState({
isSleeping: willRandomSleep ? true : newIsSleeping,
health: Math.min(maxHealth, this.state.health + healthChange),
_autoSlept: willRandomSleep ? true : (newIsSleeping ? false : undefined)
})
// 記錄到成就系統
if (this.achievementSystem && newIsSleeping) {
await this.achievementSystem.recordAction('sleep')
}
return {
success: true,
isSleeping: willRandomSleep ? true : newIsSleeping,
message,
randomSleep: willRandomSleep
}
}
}