287 lines
7.3 KiB
JavaScript
287 lines
7.3 KiB
JavaScript
|
|
// 寵物系統核心 - 與 API 整合
|
|||
|
|
import { apiService } from './api-service.js'
|
|||
|
|
import { PET_SPECIES } from '../data/pet-species.js'
|
|||
|
|
|
|||
|
|
export class PetSystem {
|
|||
|
|
constructor(api = apiService) {
|
|||
|
|
this.api = api
|
|||
|
|
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)
|
|||
|
|
await this.api.savePetState(this.state)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 載入種族配置
|
|||
|
|
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
|
|||
|
|
|
|||
|
|
return this.state
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[PetSystem] 初始化失敗:', error)
|
|||
|
|
// 降級到本地狀態
|
|||
|
|
this.state = this.createInitialState(speciesId)
|
|||
|
|
this.speciesConfig = PET_SPECIES[speciesId]
|
|||
|
|
return this.state
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 創建初始狀態
|
|||
|
|
createInitialState(speciesId) {
|
|||
|
|
const config = PET_SPECIES[speciesId]
|
|||
|
|
return {
|
|||
|
|
speciesId,
|
|||
|
|
stage: 'baby',
|
|||
|
|
hunger: 100,
|
|||
|
|
happiness: 100,
|
|||
|
|
health: 100,
|
|||
|
|
weight: 500,
|
|||
|
|
ageSeconds: 0,
|
|||
|
|
poopCount: 0,
|
|||
|
|
str: 0,
|
|||
|
|
int: 0,
|
|||
|
|
dex: 0,
|
|||
|
|
luck: config.baseStats.luck || 10,
|
|||
|
|
isSleeping: false,
|
|||
|
|
isSick: false,
|
|||
|
|
isDead: false,
|
|||
|
|
currentDeityId: 'mazu',
|
|||
|
|
deityFavors: {
|
|||
|
|
mazu: 0,
|
|||
|
|
earthgod: 0,
|
|||
|
|
yuelao: 0,
|
|||
|
|
wenchang: 0,
|
|||
|
|
guanyin: 0
|
|||
|
|
},
|
|||
|
|
dailyPrayerCount: 0,
|
|||
|
|
destiny: null,
|
|||
|
|
buffs: [],
|
|||
|
|
inventory: [],
|
|||
|
|
generation: 1,
|
|||
|
|
lastTickTime: Date.now()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新狀態(同步到 API)
|
|||
|
|
async updateState(updates) {
|
|||
|
|
this.state = { ...this.state, ...updates }
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await this.api.updatePetState(updates)
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('[PetSystem] API 更新失敗,使用本地狀態:', error)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.state
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 獲取當前狀態
|
|||
|
|
getState() {
|
|||
|
|
return { ...this.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 循環(每 3 秒)
|
|||
|
|
startTickLoop(callback) {
|
|||
|
|
if (this.tickInterval) this.stopTickLoop()
|
|||
|
|
|
|||
|
|
this.tickInterval = setInterval(async () => {
|
|||
|
|
await this.tick()
|
|||
|
|
if (callback) callback(this.getState())
|
|||
|
|
}, 3000)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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()
|
|||
|
|
|
|||
|
|
// 基礎數值衰減
|
|||
|
|
const config = this.speciesConfig.baseStats
|
|||
|
|
this.state.hunger = Math.max(0, this.state.hunger - config.hungerDecayPerTick * 100)
|
|||
|
|
this.state.happiness = Math.max(0, this.state.happiness - config.happinessDecayPerTick * 100)
|
|||
|
|
|
|||
|
|
// 便便機率
|
|||
|
|
if (Math.random() < config.poopChancePerTick) {
|
|||
|
|
this.state.poopCount = Math.min(4, this.state.poopCount + 1)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生病機率
|
|||
|
|
if (!this.state.isSick && Math.random() < config.sicknessChance) {
|
|||
|
|
this.state.isSick = true
|
|||
|
|
await this.updateState({ isSick: true })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 健康檢查
|
|||
|
|
if (this.state.health <= 0) {
|
|||
|
|
this.state.isDead = true
|
|||
|
|
await this.updateState({ isDead: true })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 同步到 API
|
|||
|
|
await this.updateState({
|
|||
|
|
ageSeconds: this.state.ageSeconds,
|
|||
|
|
hunger: this.state.hunger,
|
|||
|
|
happiness: this.state.happiness,
|
|||
|
|
poopCount: this.state.poopCount,
|
|||
|
|
isSick: this.state.isSick,
|
|||
|
|
isDead: this.state.isDead
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 檢查階段轉換
|
|||
|
|
checkStageTransition() {
|
|||
|
|
const config = this.speciesConfig
|
|||
|
|
const currentStage = this.state.stage
|
|||
|
|
const ageSeconds = this.state.ageSeconds
|
|||
|
|
|
|||
|
|
for (const stageConfig of config.lifecycle) {
|
|||
|
|
if (stageConfig.stage === currentStage) {
|
|||
|
|
// 找到下一個階段
|
|||
|
|
const currentIndex = config.lifecycle.findIndex(s => s.stage === currentStage)
|
|||
|
|
if (currentIndex < config.lifecycle.length - 1) {
|
|||
|
|
const nextStage = config.lifecycle[currentIndex + 1]
|
|||
|
|
if (ageSeconds >= nextStage.durationSeconds && nextStage.durationSeconds > 0) {
|
|||
|
|
this.state.stage = nextStage.stage
|
|||
|
|
this.updateState({ stage: nextStage.stage })
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 餵食
|
|||
|
|
async feed(amount = 20) {
|
|||
|
|
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
|
|||
|
|
|
|||
|
|
const newHunger = Math.min(100, this.state.hunger + amount)
|
|||
|
|
const newWeight = this.state.weight + (amount * 0.5)
|
|||
|
|
|
|||
|
|
await this.updateState({
|
|||
|
|
hunger: newHunger,
|
|||
|
|
weight: newWeight
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return { success: true, hunger: newHunger, weight: newWeight }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 玩耍
|
|||
|
|
async play(amount = 15) {
|
|||
|
|
if (this.state.isDead || this.state.isSleeping) {
|
|||
|
|
return { success: false, message: '寵物無法玩耍' }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const newHappiness = Math.min(100, this.state.happiness + amount)
|
|||
|
|
const newDex = this.state.dex + 0.5
|
|||
|
|
|
|||
|
|
await this.updateState({
|
|||
|
|
happiness: newHappiness,
|
|||
|
|
dex: newDex
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return { success: true, happiness: newHappiness, dex: newDex }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理便便
|
|||
|
|
async cleanPoop() {
|
|||
|
|
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
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return { success: true, poopCount: newPoopCount, happiness: newHappiness }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 治療
|
|||
|
|
async heal(amount = 20) {
|
|||
|
|
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
|
|||
|
|
|
|||
|
|
const newHealth = Math.min(100, this.state.health + amount)
|
|||
|
|
const wasSick = this.state.isSick
|
|||
|
|
|
|||
|
|
await this.updateState({
|
|||
|
|
health: newHealth,
|
|||
|
|
isSick: false
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
health: newHealth,
|
|||
|
|
isSick: false,
|
|||
|
|
cured: wasSick && !this.state.isSick
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 睡覺/起床
|
|||
|
|
async toggleSleep() {
|
|||
|
|
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
|
|||
|
|
|
|||
|
|
const newIsSleeping = !this.state.isSleeping
|
|||
|
|
const healthChange = newIsSleeping ? 5 : 0
|
|||
|
|
|
|||
|
|
await this.updateState({
|
|||
|
|
isSleeping: newIsSleeping,
|
|||
|
|
health: Math.min(100, this.state.health + healthChange)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return { success: true, isSleeping: newIsSleeping }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|