pet_data/core/pet-system.js

978 lines
34 KiB
JavaScript
Raw Normal View History

2025-11-23 18:03:56 +00:00
// 寵物系統核心 - 與 API 整合
import { apiService } from './api-service.js'
import { PET_SPECIES } from '../data/pet-species.js'
2025-11-24 07:38:44 +00:00
import { FATES } from '../data/fates.js'
import { DEITIES } from '../data/deities.js'
2025-11-23 18:03:56 +00:00
export class PetSystem {
2025-11-24 10:34:02 +00:00
constructor(api = apiService, achievementSystem = null, inventorySystem = null) {
2025-11-23 18:03:56 +00:00
this.api = api
2025-11-24 10:34:02 +00:00
this.achievementSystem = achievementSystem
this.inventorySystem = inventorySystem
2025-11-23 18:03:56 +00:00
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()
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
if (!this.state) {
// 創建新寵物
this.state = this.createInitialState(speciesId)
2025-11-24 13:45:09 +00:00
// 載入種族配置
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
// 計算戰鬥數值(在保存前)
this.calculateCombatStats()
// 保存完整狀態(包含計算後的戰鬥數值)
2025-11-23 18:03:56 +00:00
await this.api.savePetState(this.state)
2025-11-24 10:34:02 +00:00
} else {
// 確保 achievementBuffs 存在(向後兼容)
if (!this.state.achievementBuffs) {
this.state.achievementBuffs = {}
}
2025-11-24 13:45:09 +00:00
// 確保 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()
2025-11-23 18:03:56 +00:00
}
return this.state
} catch (error) {
console.error('[PetSystem] 初始化失敗:', error)
// 降級到本地狀態
this.state = this.createInitialState(speciesId)
this.speciesConfig = PET_SPECIES[speciesId]
2025-11-24 07:38:44 +00:00
// 計算戰鬥數值
this.calculateCombatStats()
2025-11-23 18:03:56 +00:00
return this.state
}
}
// 創建初始狀態
createInitialState(speciesId) {
const config = PET_SPECIES[speciesId]
2025-11-24 07:38:44 +00:00
const state = {
2025-11-23 18:03:56 +00:00
speciesId,
2025-11-24 07:38:44 +00:00
stage: 'egg',
2025-11-23 18:03:56 +00:00
hunger: 100,
happiness: 100,
health: 100,
weight: 500,
ageSeconds: 0,
poopCount: 0,
2025-11-24 07:38:44 +00:00
str: 10,
int: 10,
dex: 10,
2025-11-23 18:03:56 +00:00
luck: config.baseStats.luck || 10,
isSleeping: false,
isSick: false,
isDead: false,
2025-11-24 07:38:44 +00:00
dyingSeconds: 0, // 瀕死計時
2025-11-23 18:03:56 +00:00
currentDeityId: 'mazu',
deityFavors: {
mazu: 0,
earthgod: 0,
yuelao: 0,
wenchang: 0,
guanyin: 0
},
dailyPrayerCount: 0,
destiny: null,
buffs: [],
inventory: [],
generation: 1,
2025-11-24 10:34:02 +00:00
lastTickTime: Date.now(),
achievementBuffs: {}, // 成就加成
2025-11-24 13:45:09 +00:00
equipmentBuffs: { flat: {}, percent: {} }, // 裝備加成
appearance: {}, // 外觀設定
coins: 100 // 金幣(初始 100
2025-11-23 18:03:56 +00:00
}
2025-11-24 07:38:44 +00:00
// 分配命格
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}`)
}
}
2025-11-24 10:34:02 +00:00
// 獲取所有加成(命格 + 神明基礎 + 神明好感度等級 + 神明滿級 + 成就 + Buff
2025-11-24 07:38:44 +00:00
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.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
}
}
}
}
2025-11-24 10:34:02 +00:00
// 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
}
}
}
2025-11-24 07:38:44 +00:00
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
// 根據當前階段設定身高(每個階段固定)
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
}
2025-11-23 18:03:56 +00:00
}
// 更新狀態(同步到 API
async updateState(updates) {
this.state = { ...this.state, ...updates }
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
try {
await this.api.updatePetState(updates)
} catch (error) {
console.warn('[PetSystem] API 更新失敗,使用本地狀態:', error)
}
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
return this.state
}
// 獲取當前狀態
getState() {
return { ...this.state }
}
// 刪除寵物
async deletePet() {
try {
// 停止所有循環
this.stopTickLoop()
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
// 從 API 刪除
await this.api.deletePetState()
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
// 重置狀態
this.state = null
this.speciesConfig = null
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
return { success: true, message: '寵物已刪除' }
} catch (error) {
console.error('[PetSystem] 刪除寵物失敗:', error)
// 即使 API 失敗,也清除本地狀態
this.state = null
this.speciesConfig = null
return { success: true, message: '寵物已刪除(本地)' }
}
}
2025-11-24 07:38:44 +00:00
// Tick 循環(生理系统 - 从配置读取间隔)
2025-11-23 18:03:56 +00:00
startTickLoop(callback) {
if (this.tickInterval) this.stopTickLoop()
2025-11-24 07:38:44 +00:00
// 从配置读取间隔时间
const interval = this.speciesConfig?.baseStats?.physiologyTickInterval || 60000
2025-11-23 18:03:56 +00:00
this.tickInterval = setInterval(async () => {
await this.tick()
if (callback) callback(this.getState())
2025-11-24 07:38:44 +00:00
}, interval)
2025-11-23 18:03:56 +00:00
}
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()
2025-11-24 07:38:44 +00:00
// 檢查自動睡眠(基於真實時間)
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(`⏰ 睡眠時間結束,寵物自動醒來`)
}
}
// 根據當前階段配置決定啟用哪些系統(資料驅動)
2025-11-23 18:03:56 +00:00
const config = this.speciesConfig.baseStats
2025-11-24 07:38:44 +00:00
// 睡眠狀態下的衰減倍率(從配置讀取)
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}`)
}
2025-11-23 18:03:56 +00:00
}
2025-11-24 07:38:44 +00:00
// 快樂系統
if (this.isSystemEnabled('happiness')) {
this.state.happiness = Math.max(0, this.state.happiness - config.happinessDecayPerTick * sleepDecayMultiplier)
2025-11-23 18:03:56 +00:00
}
2025-11-24 07:38:44 +00:00
// 便便系統(睡覺時不會拉屎)
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
2025-11-23 18:03:56 +00:00
if (this.state.health <= 0) {
2025-11-24 07:38:44 +00:00
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) {
2025-11-24 10:34:02 +00:00
const wasDying = this.state.dyingSeconds > 0
2025-11-24 07:38:44 +00:00
this.state.dyingSeconds = 0
console.log('✨ 寵物脫離瀕死狀態')
2025-11-24 13:45:09 +00:00
2025-11-24 10:34:02 +00:00
// 通知成就系統(如果存在)
if (this.achievementSystem && wasDying) {
this.achievementSystem.recordRecoveryFromDying()
}
}
}
// 檢查成就(年齡、階段、完美狀態等)
if (this.achievementSystem) {
await this.achievementSystem.checkAndUnlockAchievements()
this.achievementSystem.checkPerfectState()
}
// 減少裝備耐久度(每 tick 減少少量,戰鬥或特殊情況會減少更多)
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)
}
}
2025-11-24 07:38:44 +00:00
}
2025-11-23 18:03:56 +00:00
}
2025-11-24 07:38:44 +00:00
// 重新计算属性(含加成)
this.calculateCombatStats()
2025-11-24 10:34:02 +00:00
// 初始化 tick 計數器
if (!this.state._tickCount) {
this.state._tickCount = 0
}
this.state._tickCount++
2025-11-23 18:03:56 +00:00
// 同步到 API
await this.updateState({
2025-11-24 07:38:44 +00:00
// 基礎屬性
2025-11-23 18:03:56 +00:00
ageSeconds: this.state.ageSeconds,
2025-11-24 07:38:44 +00:00
stage: this.state.stage,
2025-11-23 18:03:56 +00:00
hunger: this.state.hunger,
happiness: this.state.happiness,
2025-11-24 07:38:44 +00:00
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,
// 狀態
2025-11-23 18:03:56 +00:00
poopCount: this.state.poopCount,
2025-11-24 07:38:44 +00:00
isSleeping: this.state.isSleeping,
2025-11-23 18:03:56 +00:00
isSick: this.state.isSick,
2025-11-24 07:38:44 +00:00
isDead: this.state.isDead,
dyingSeconds: this.state.dyingSeconds,
// 戰鬥數值
attack: this.state.attack,
defense: this.state.defense,
2025-11-24 13:45:09 +00:00
speed: this.state.speed,
// 命格和神明
destiny: this.state.destiny,
currentDeityId: this.state.currentDeityId,
deityFavors: this.state.deityFavors,
// 经济
coins: this.state.coins
2025-11-23 18:03:56 +00:00
})
}
2025-11-24 07:38:44 +00:00
// 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
}
2025-11-23 18:03:56 +00:00
// 檢查階段轉換
checkStageTransition() {
2025-11-24 07:38:44 +00:00
if (!this.speciesConfig || !this.state) return
2025-11-23 18:03:56 +00:00
const config = this.speciesConfig
const currentStage = this.state.stage
const ageSeconds = this.state.ageSeconds
2025-11-24 07:38:44 +00:00
// 找到當前階段
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
2025-11-24 13:45:09 +00:00
let missingStats = []
2025-11-24 07:38:44 +00:00
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) {
2025-11-24 13:45:09 +00:00
// 檢查是否有進化分支
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
2025-11-24 07:38:44 +00:00
this.state._evolutionWarned = false // 重置警告狀態
2025-11-24 13:45:09 +00:00
this.updateState(updates)
2025-11-24 07:38:44 +00:00
} else if (timeConditionMet && !statsConditionMet) {
// 時間到了但屬性不足,只在第一次提示
if (!this.state._evolutionWarned) {
console.log(`⚠️ 年齡已達到 (${ageSeconds.toFixed(1)}秒),但屬性不足以進化到 ${nextStage.stage}`)
console.log(` 需要: ${missingStats.join(', ')}`)
this.state._evolutionWarned = true
2025-11-23 18:03:56 +00:00
}
}
}
// 餵食
2025-11-24 07:38:44 +00:00
async feed(amount = 12) {
if (!this.isActionAllowed('feed')) return { success: false, message: '當前階段不能餵食' }
2025-11-23 18:03:56 +00:00
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
2025-11-24 07:38:44 +00:00
if (this.state.isSleeping) return { success: false, message: '寵物正在睡覺,無法餵食' }
if (this.state.hunger >= 100) return { success: false, message: '寵物已經吃飽了' }
2025-11-23 18:03:56 +00:00
const newHunger = Math.min(100, this.state.hunger + amount)
2025-11-24 07:38:44 +00:00
// 體重隨機變化 ±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
2025-11-23 18:03:56 +00:00
await this.updateState({
hunger: newHunger,
2025-11-24 07:38:44 +00:00
weight: newWeight,
str: newStr
2025-11-23 18:03:56 +00:00
})
2025-11-24 07:38:44 +00:00
2025-11-24 10:34:02 +00:00
// 記錄到成就系統
if (this.achievementSystem) {
await this.achievementSystem.recordAction('feed')
this.achievementSystem.checkPerfectState()
}
2025-11-24 07:38:44 +00:00
this.calculateCombatStats()
return { success: true, hunger: newHunger, weight: newWeight, strGain, weightChange }
2025-11-23 18:03:56 +00:00
}
// 玩耍
2025-11-24 07:38:44 +00:00
async play(options = {}) {
if (!this.isActionAllowed('play')) return { success: false, message: '當前階段不能玩耍' }
2025-11-23 18:03:56 +00:00
if (this.state.isDead || this.state.isSleeping) {
return { success: false, message: '寵物無法玩耍' }
}
2025-11-24 07:38:44 +00:00
// 檢查飢餓度
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 點飢餓
2025-11-23 18:03:56 +00:00
await this.updateState({
happiness: newHappiness,
2025-11-24 07:38:44 +00:00
hunger: newHunger,
dex: newDex,
int: newInt,
weight: newWeight
2025-11-23 18:03:56 +00:00
})
2025-11-24 07:38:44 +00:00
2025-11-24 10:34:02 +00:00
// 記錄到成就系統
if (this.achievementSystem) {
await this.achievementSystem.recordAction('play')
this.achievementSystem.checkPerfectState()
}
2025-11-24 07:38:44 +00:00
this.calculateCombatStats()
return {
success: true,
happiness: newHappiness,
happinessGain,
dexGain,
intGain,
weightChange,
gameType: gameTypeLabel
}
2025-11-23 18:03:56 +00:00
}
// 清理便便
async cleanPoop() {
2025-11-24 07:38:44 +00:00
if (!this.isActionAllowed('clean')) return { success: false, message: '當前階段不需要清理' }
2025-11-23 18:03:56 +00:00
if (this.state.poopCount === 0) {
return { success: false, message: '沒有便便需要清理' }
}
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
const newPoopCount = 0
const newHappiness = Math.min(100, this.state.happiness + 10)
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
await this.updateState({
poopCount: newPoopCount,
happiness: newHappiness
})
2025-11-24 07:38:44 +00:00
2025-11-24 10:34:02 +00:00
// 記錄到成就系統
if (this.achievementSystem) {
await this.achievementSystem.recordAction('clean')
this.achievementSystem.checkPerfectState()
}
2025-11-23 18:03:56 +00:00
return { success: true, poopCount: newPoopCount, happiness: newHappiness }
}
// 治療
async heal(amount = 20) {
2025-11-24 07:38:44 +00:00
if (!this.isActionAllowed('heal')) return { success: false, message: '當前階段不需要治療' }
2025-11-23 18:03:56 +00:00
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
2025-11-24 07:38:44 +00:00
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)
2025-11-23 18:03:56 +00:00
const wasSick = this.state.isSick
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
await this.updateState({
health: newHealth,
isSick: false
})
2025-11-24 07:38:44 +00:00
2025-11-24 10:34:02 +00:00
// 記錄到成就系統
if (this.achievementSystem) {
await this.achievementSystem.recordAction('heal')
this.achievementSystem.checkPerfectState()
}
2025-11-24 07:38:44 +00:00
return {
success: true,
health: newHealth,
2025-11-23 18:03:56 +00:00
isSick: false,
2025-11-24 07:38:44 +00:00
cured: wasSick && !this.state.isSick,
healAmount
2025-11-23 18:03:56 +00:00
}
}
// 睡覺/起床
async toggleSleep() {
2025-11-24 07:38:44 +00:00
if (!this.isActionAllowed('sleep')) return { success: false, message: '當前階段不能睡覺' }
2025-11-23 18:03:56 +00:00
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
2025-11-24 07:38:44 +00:00
2025-11-23 18:03:56 +00:00
const newIsSleeping = !this.state.isSleeping
const healthChange = newIsSleeping ? 5 : 0
2025-11-24 07:38:44 +00:00
// 檢查是否在睡眠時段
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()
2025-11-23 18:03:56 +00:00
await this.updateState({
2025-11-24 07:38:44 +00:00
isSleeping: willRandomSleep ? true : newIsSleeping,
health: Math.min(maxHealth, this.state.health + healthChange),
_autoSlept: willRandomSleep ? true : (newIsSleeping ? false : undefined)
2025-11-23 18:03:56 +00:00
})
2025-11-24 07:38:44 +00:00
2025-11-24 10:34:02 +00:00
// 記錄到成就系統
if (this.achievementSystem && newIsSleeping) {
await this.achievementSystem.recordAction('sleep')
}
2025-11-24 07:38:44 +00:00
return {
success: true,
isSleeping: willRandomSleep ? true : newIsSleeping,
message,
randomSleep: willRandomSleep
}
2025-11-23 18:03:56 +00:00
}
}