From 9b1d27186d72289ec14c43f7ad3077f63236e66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Mon, 24 Nov 2025 15:38:44 +0800 Subject: [PATCH] feat:merge health --- app/app-original.vue.backup | 1814 +++++++++++++++++++++++++++++++++++ app/app-retro.vue | 573 +++++++++++ app/app.vue | 1354 ++++++++++++++++++++++---- core/api-service.js | 20 +- core/event-system.js | 127 ++- core/pet-system.js | 670 +++++++++++-- core/temple-system.js | 181 +++- data/deities.js | 97 ++ data/events.js | 6 + data/fates.js | 63 ++ data/items.js | 4 +- data/jiaobei-config.js | 52 + data/pet-species.js | 154 ++- docs/BONUS_CHECK.md | 26 + docs/CONFIG_GUIDE.md | 197 ++++ docs/EVOLUTION_SYSTEM.md | 209 ++++ docs/GAME_BALANCE.md | 156 +++ docs/SLEEP_SYSTEM.md | 118 +++ docs/STATS_BONUSES.md | 125 +++ public/test-events.js | 86 ++ 20 files changed, 5719 insertions(+), 313 deletions(-) create mode 100644 app/app-original.vue.backup create mode 100644 app/app-retro.vue create mode 100644 data/fates.js create mode 100644 data/jiaobei-config.js create mode 100644 docs/BONUS_CHECK.md create mode 100644 docs/CONFIG_GUIDE.md create mode 100644 docs/EVOLUTION_SYSTEM.md create mode 100644 docs/GAME_BALANCE.md create mode 100644 docs/SLEEP_SYSTEM.md create mode 100644 docs/STATS_BONUSES.md create mode 100644 public/test-events.js diff --git a/app/app-original.vue.backup b/app/app-original.vue.backup new file mode 100644 index 0000000..d9d3c3d --- /dev/null +++ b/app/app-original.vue.backup @@ -0,0 +1,1814 @@ + + + + + + + diff --git a/app/app-retro.vue b/app/app-retro.vue new file mode 100644 index 0000000..98db4a0 --- /dev/null +++ b/app/app-retro.vue @@ -0,0 +1,573 @@ + + + + + diff --git a/app/app.vue b/app/app.vue index 433dabc..67f1220 100644 --- a/app/app.vue +++ b/app/app.vue @@ -3,7 +3,7 @@
快樂
- {{ Math.round(petState.happiness || 0) }}
+ {{ Math.round(petState.happiness || 0) }}
健康
-
- {{ Math.round(petState.health || 0) }} +
+ {{ Math.round(petState.health || 0) }}/{{ getMaxHealth(petState) }}
@@ -76,25 +76,36 @@ 身高 {{ Math.round(petState.height || 0) }} cm +
+ 身高 + {{ Math.round(petState.height || 0) }} cm +
體重 {{ Math.round(petState.weight || 0) }} g
命格 - {{ petState.destiny ? petState.destiny.name : '無' }} +
+ {{ petState.destiny ? petState.destiny.name : '無' }} + {{ petState.destiny.description }} +
力量 (STR) - {{ Math.round(petState.str || 0) }} + {{ Math.round(petState.effectiveStr || petState.str || 0) }}
智力 (INT) - {{ Math.round(petState.int || 0) }} + {{ Math.round(petState.effectiveInt || petState.int || 0) }}
敏捷 (DEX) - {{ Math.round(petState.dex || 0) }} + {{ Math.round(petState.effectiveDex || petState.dex || 0) }} +
+
+ 運勢 (LUCK) + {{ Math.round(petState.effectiveLuck || petState.luck || 0) }}
世代 @@ -123,62 +134,232 @@
+ + +
+

═ 加成與隱藏數值 ═

+
+
+ {{ getBonusName(key) }} + + {{ value > 0 ? '+' : '' }}{{ value }} + +
+
+
+
- 😴 睡覺中 - 🤒 生病 - 💩 便便 x{{ petState.poopCount }} + [SLEEP] 睡覺中 + [SICK] 生病 + [POOP] 便便 x{{ petState.poopCount }}
-

🐾 寵物互動

+

寵物互動

- - - - - - - + + +
+ + + +
+ + + + + + +
-

🎲 事件系統

+

[ROLL] 事件系統

- - - - - + + + + +
+ +
+ * 快速觸發事件 +
+ + + + + + +
+
-

🙏 神明系統

+

[PRAY] 神明系統

- - - - - - + + + +
+ +
+ [SWITCH] 切換神明 +
+ + + + + +
+
- -
-
✅ 系統載入中...
+ +
[DEBUG]
+ +
+

[TOOLS] Debug 工具

+
+

神明系統

+ + + +
+ +
+

事件系統

+ +
+ +
+

寵物狀態

+ + + + +
+ + diff --git a/core/api-service.js b/core/api-service.js index 07621db..38717be 100644 --- a/core/api-service.js +++ b/core/api-service.js @@ -17,9 +17,9 @@ export class ApiService { // Mock 請求(使用本地資料) async mockRequest(endpoint, options) { await this.delay(this.mockDelay) - + const [resource, action] = endpoint.split('/').filter(Boolean) - + switch (`${resource}/${action}`) { case 'pet/state': return this.getMockPetState() @@ -37,6 +37,8 @@ export class ApiService { return this.mockPrayToDeity(options.body) case 'fortune/draw': return this.mockDrawFortune() + case 'temple/throw-jiaobei': + return { localFallback: true } // 使用本地邏輯 case 'items/list': return this.getMockItems() case 'items/use': @@ -66,7 +68,7 @@ export class ApiService { } const response = await fetch(url, config) - + if (!response.ok) { throw new Error(`API Error: ${response.status} ${response.statusText}`) } @@ -145,6 +147,14 @@ export class ApiService { }) } + // 擲筊相關 + async throwJiaobei(params) { + return this.request('temple/throw-jiaobei', { + method: 'POST', + body: params + }) + } + // 道具相關 async getItems() { return this.request('items/list') @@ -158,7 +168,7 @@ export class ApiService { } // ========== Mock 資料方法 ========== - + getMockPetState() { // 從 localStorage 或預設值讀取 const stored = localStorage.getItem('petState') @@ -214,7 +224,7 @@ export class ApiService { const random = Math.random() let sum = 0 let selectedGrade = '中' - + for (let i = 0; i < weights.length; i++) { sum += weights[i] if (random <= sum) { diff --git a/core/event-system.js b/core/event-system.js index 7c64d9b..6d6019b 100644 --- a/core/event-system.js +++ b/core/event-system.js @@ -25,13 +25,17 @@ export class EventSystem { } } - // 啟動事件檢查循環(每 10 秒檢查一次) + // 啟動事件檢查循環(从配置读取间隔) startEventCheck() { if (this.eventCheckInterval) this.stopEventCheck() - + + // 从配置读取间隔时间 + const petConfig = this.petSystem.speciesConfig + const interval = petConfig?.baseStats?.eventCheckInterval || 10000 + this.eventCheckInterval = setInterval(() => { this.checkTriggers() - }, 10000) // 每 10 秒 + }, interval) } stopEventCheck() { @@ -45,31 +49,34 @@ export class EventSystem { selectRandomEvent() { const totalWeight = this.events.reduce((sum, e) => sum + e.weight, 0) let rand = Math.random() * totalWeight - + for (const event of this.events) { rand -= event.weight if (rand <= 0) return event } - + return this.events[0] // fallback } // 檢查觸發條件(10% 機率 + 條件檢查) async checkTriggers() { const petState = this.petSystem.getState() - + if (petState.isDead) return - + + // 睡眠時不觸發事件(類似暫停) + if (petState.isSleeping) return + // 10% 機率觸發 if (Math.random() >= 0.1) return - + const event = this.selectRandomEvent() - + // 檢查條件 if (event.condition && !event.condition(petState)) { return } - + // 觸發事件 await this.triggerEvent(event.id, petState) } @@ -83,10 +90,19 @@ export class EventSystem { } const currentState = petState || this.petSystem.getState() - + // 檢查條件 if (event.condition && !event.condition(currentState)) { - console.log(`[EventSystem] 事件 ${eventId} 條件不滿足`) + console.log(`\n❌ 事件 ${event.id} 觸發失敗`) + console.log(` 條件: ${event.conditionDescription || '未定義'}`) + console.log(` 當前狀態:`) + console.log(` - 飢餓: ${currentState.hunger.toFixed(1)}`) + console.log(` - 快樂: ${currentState.happiness.toFixed(1)}`) + console.log(` - 健康: ${currentState.health.toFixed(1)}`) + console.log(` - 運勢: ${currentState.luck.toFixed(1)}`) + console.log(` - 體重: ${currentState.weight.toFixed(1)}`) + console.log(` - DEX: ${currentState.dex.toFixed(1)}`) + console.log(` - INT: ${currentState.int.toFixed(1)}`) return null } @@ -122,20 +138,20 @@ export class EventSystem { switch (effect.type) { case 'modifyStats': return await this.modifyStats(effect.payload, petState) - + case 'addBuff': return await this.addBuff(effect.payload) - + case 'spawnPoop': return await this.spawnPoop(effect.payload) - + case 'templeFavor': return await this.modifyTempleFavor(effect.payload) - + case 'logMessage': console.log(`[訊息] ${effect.payload}`) return { type: 'logMessage', message: effect.payload } - + default: console.warn(`[EventSystem] 未知效果類型: ${effect.type}`) return null @@ -144,19 +160,32 @@ export class EventSystem { // 修改屬性 async modifyStats(payload, petState) { + // 獲取最新狀態(因為 petState 可能是舊的快照) + const currentState = this.petSystem.getState() const updates = {} - + for (const [key, value] of Object.entries(payload)) { if (key === 'isSleeping' || key === 'isSick' || key === 'isDead') { updates[key] = value } else { - const current = petState[key] || 0 - const newValue = Math.max(0, Math.min(100, current + value)) + const current = currentState[key] || 0 + let newValue = current + + if (value > 0) { + // 增加屬性:如果是自然增長(非道具),上限為 100 + // 如果當前已經 >= 100,則不再增加(除非是道具,但這裡是事件系統) + // 如果當前 < 100,則最多增加到 100 + newValue = current < 100 ? Math.min(100, current + value) : current + } else { + // 減少屬性:總是生效,最低為 0 + newValue = Math.max(0, current + value) + } + updates[key] = newValue - console.log(`[效果] ${key} ${value > 0 ? '+' : ''}${value} → 新值: ${newValue}`) + console.log(`[效果] ${key} ${value > 0 ? '+' : ''}${value} (${current.toFixed(1)} → ${newValue.toFixed(1)})`) } } - + await this.petSystem.updateState(updates) return { type: 'modifyStats', updates } } @@ -164,14 +193,14 @@ export class EventSystem { // 添加 Buff async addBuff(buffData) { this.buffManager.addBuff(buffData) - + // 同步到 API try { await this.api.applyBuff(buffData) } catch (error) { console.warn('[EventSystem] Buff API 同步失敗:', error) } - + return { type: 'addBuff', buff: buffData } } @@ -180,10 +209,10 @@ export class EventSystem { const count = payload?.count || 1 const currentPoop = this.petSystem.getState().poopCount const newPoop = Math.min(4, currentPoop + count) - + await this.petSystem.updateState({ poopCount: newPoop }) console.log(`[效果] 生成便便: ${count} 個,總數: ${newPoop}`) - + return { type: 'spawnPoop', count, total: newPoop } } @@ -193,16 +222,16 @@ export class EventSystem { const currentState = this.petSystem.getState() const currentFavor = currentState.deityFavors[deityId] || 0 const newFavor = Math.max(0, Math.min(100, currentFavor + amount)) - + await this.petSystem.updateState({ deityFavors: { ...currentState.deityFavors, [deityId]: newFavor } }) - + console.log(`[效果] ${deityId} 好感度 ${amount > 0 ? '+' : ''}${amount} → ${newFavor}`) - + return { type: 'templeFavor', deityId, favor: newFavor } } @@ -210,24 +239,24 @@ export class EventSystem { async applyBuffs() { const petState = this.petSystem.getState() const buffs = this.buffManager.getActiveBuffs() - + // 計算最終屬性(考慮 Buff) const finalStats = this.calculateFinalStats(petState, buffs) - + // 更新狀態 await this.petSystem.updateState(finalStats) - + return finalStats } // 計算最終屬性(Base + Flat + Percent) calculateFinalStats(baseState, buffs) { const stats = { ...baseState } - + // 收集所有 Buff 的 flat 和 percent 加成 const flatMods = {} const percentMods = {} - + buffs.forEach(buff => { if (buff.flat) { Object.entries(buff.flat).forEach(([key, value]) => { @@ -240,7 +269,7 @@ export class EventSystem { }) } }) - + // 應用加成 Object.keys(stats).forEach(key => { if (typeof stats[key] === 'number' && key !== 'ageSeconds' && key !== 'weight' && key !== 'generation') { @@ -250,7 +279,7 @@ export class EventSystem { stats[key] = Math.max(0, Math.min(100, (base + flat) * (1 + percent))) } }) - + return stats } @@ -263,6 +292,24 @@ export class EventSystem { getBuffManager() { return this.buffManager } + + // Debug: 強制觸發事件 + async debugTriggerEvent(eventId = null) { + let event + if (eventId) { + event = this.events.find(e => e.id === eventId) + if (!event) { + console.warn(`[Debug] 找不到事件: ${eventId}`) + return + } + } else { + event = this.selectRandomEvent() + } + + console.log(`[Debug] 強制觸發事件: ${event.id}`) + await this.triggerEvent(event.id) + return event + } } // Buff 管理器 @@ -274,7 +321,7 @@ class BuffManager { addBuff(buff) { // 檢查是否可堆疊 const existing = this.buffs.find(b => b.id === buff.id) - + if (existing && !buff.stacks) { // 不可堆疊,重置持續時間 existing.currentTicks = buff.durationTicks @@ -293,7 +340,7 @@ class BuffManager { tick() { this.buffs = this.buffs.filter(b => { if (b.durationTicks === Infinity) return true // 永久 Buff - + b.currentTicks-- if (b.currentTicks <= 0) { console.log(`[Buff] 過期: ${b.name}`) @@ -311,12 +358,12 @@ class BuffManager { const activeBuffs = this.getActiveBuffs() let flatTotal = 0 let percentTotal = 0 - + activeBuffs.forEach(b => { if (b.flat?.[statKey]) flatTotal += b.flat[statKey] if (b.percent?.[statKey]) percentTotal += b.percent[statKey] }) - + return { flat: flatTotal, percent: percentTotal } } } diff --git a/core/pet-system.js b/core/pet-system.js index 3be0855..1fc61fb 100644 --- a/core/pet-system.js +++ b/core/pet-system.js @@ -1,6 +1,8 @@ // 寵物系統核心 - 與 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' export class PetSystem { constructor(api = apiService) { @@ -16,7 +18,7 @@ export class PetSystem { try { // 從 API 載入現有狀態 this.state = await this.api.getPetState() - + if (!this.state) { // 創建新寵物 this.state = this.createInitialState(speciesId) @@ -25,13 +27,18 @@ export class PetSystem { // 載入種族配置 this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId] - + + // 計算戰鬥數值(在 speciesConfig 設置後) + this.calculateCombatStats() + return this.state } catch (error) { console.error('[PetSystem] 初始化失敗:', error) // 降級到本地狀態 this.state = this.createInitialState(speciesId) this.speciesConfig = PET_SPECIES[speciesId] + // 計算戰鬥數值 + this.calculateCombatStats() return this.state } } @@ -39,22 +46,23 @@ export class PetSystem { // 創建初始狀態 createInitialState(speciesId) { const config = PET_SPECIES[speciesId] - return { + const state = { speciesId, - stage: 'baby', + stage: 'egg', hunger: 100, happiness: 100, health: 100, weight: 500, ageSeconds: 0, poopCount: 0, - str: 0, - int: 0, - dex: 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, @@ -70,18 +78,230 @@ export class PetSystem { generation: 1, lastTickTime: Date.now() } + + // 分配命格 + 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.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 + } + } + } + } + + 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 + } } // 更新狀態(同步到 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 } @@ -95,14 +315,14 @@ export class PetSystem { try { // 停止所有循環 this.stopTickLoop() - + // 從 API 刪除 await this.api.deletePetState() - + // 重置狀態 this.state = null this.speciesConfig = null - + return { success: true, message: '寵物已刪除' } } catch (error) { console.error('[PetSystem] 刪除寵物失敗:', error) @@ -113,14 +333,17 @@ export class PetSystem { } } - // Tick 循環(每 3 秒) + // Tick 循環(生理系统 - 从配置读取间隔) startTickLoop(callback) { if (this.tickInterval) this.stopTickLoop() - + + // 从配置读取间隔时间 + const interval = this.speciesConfig?.baseStats?.physiologyTickInterval || 60000 + this.tickInterval = setInterval(async () => { await this.tick() if (callback) callback(this.getState()) - }, 3000) + }, interval) } stopTickLoop() { @@ -144,143 +367,444 @@ export class PetSystem { // 檢查階段轉換 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 - 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) + // 睡眠狀態下的衰減倍率(從配置讀取) + 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.state.isSick && Math.random() < config.sicknessChance) { - this.state.isSick = true - await this.updateState({ isSick: true }) + // 快樂系統 + 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.isDead = true - await this.updateState({ isDead: true }) + 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) { + this.state.dyingSeconds = 0 + console.log('✨ 寵物脫離瀕死狀態') + } } + // 重新计算属性(含加成) + this.calculateCombatStats() + // 同步到 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 + isDead: this.state.isDead, + dyingSeconds: this.state.dyingSeconds, + // 戰鬥數值 + attack: this.state.attack, + defense: this.state.defense, + speed: this.state.speed }) } + // 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 - 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 + // 找到當前階段 + 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 + const 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) { + console.log(`\n✨ 進化!${currentStage} → ${nextStage.stage} (年齡: ${ageSeconds.toFixed(1)}秒)`) + this.state.stage = nextStage.stage + this.state._evolutionWarned = false // 重置警告狀態 + this.updateState({ stage: nextStage.stage }) + } 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 = 20) { + 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) - const newWeight = this.state.weight + (amount * 0.5) - + + // 體重隨機變化 ±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 + weight: newWeight, + str: newStr }) - - return { success: true, hunger: newHunger, weight: newWeight } + + this.calculateCombatStats() + + return { success: true, hunger: newHunger, weight: newWeight, strGain, weightChange } } // 玩耍 - async play(amount = 15) { + async play(options = {}) { + if (!this.isActionAllowed('play')) return { success: false, message: '當前階段不能玩耍' } 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 - + + // 檢查飢餓度 + 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, - dex: newDex + hunger: newHunger, + dex: newDex, + int: newInt, + weight: newWeight }) - - return { success: true, happiness: newHappiness, dex: newDex } + + this.calculateCombatStats() + + 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 }) - + 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: '寵物已死亡' } - - const newHealth = Math.min(100, this.state.health + amount) + 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 }) - - return { - success: true, - health: newHealth, + + return { + success: true, + health: newHealth, isSick: false, - cured: wasSick && !this.state.isSick + 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: newIsSleeping, - health: Math.min(100, this.state.health + healthChange) + isSleeping: willRandomSleep ? true : newIsSleeping, + health: Math.min(maxHealth, this.state.health + healthChange), + _autoSlept: willRandomSleep ? true : (newIsSleeping ? false : undefined) }) - - return { success: true, isSleeping: newIsSleeping } + + return { + success: true, + isSleeping: willRandomSleep ? true : newIsSleeping, + message, + randomSleep: willRandomSleep + } } } diff --git a/core/temple-system.js b/core/temple-system.js index a36a718..8739636 100644 --- a/core/temple-system.js +++ b/core/temple-system.js @@ -1,5 +1,6 @@ // 神明系統 - 與 API 整合 import { apiService } from './api-service.js' +import { JIAOBEI_CONFIG } from '../data/jiaobei-config.js' export class TempleSystem { constructor(petSystem, api = apiService) { @@ -40,13 +41,25 @@ export class TempleSystem { } await this.petSystem.updateState({ currentDeityId: deityId }) + + // 重新計算屬性(新神明的加成) + this.petSystem.calculateCombatStats() + + // 同步 effective 属性到状态 + await this.petSystem.updateState({ + effectiveStr: this.petSystem.state.effectiveStr, + effectiveInt: this.petSystem.state.effectiveInt, + effectiveDex: this.petSystem.state.effectiveDex, + effectiveLuck: this.petSystem.state.effectiveLuck + }) + return { success: true, deity } } // 祈福(每日上限 3 次) async pray() { const state = this.petSystem.getState() - + if (state.dailyPrayerCount >= 3) { return { success: false, message: '今日祈福次數已用完' } } @@ -60,7 +73,14 @@ export class TempleSystem { // 更新好感度 const currentFavor = state.deityFavors[state.currentDeityId] || 0 const newFavor = Math.min(100, currentFavor + result.favorIncrease) - + + // 檢查是否等級提升或達到滿級 + const deity = this.getCurrentDeity() + const oldLevel = deity.favorLevelBuffs ? Math.floor(currentFavor / deity.favorLevelBuffs.interval) : 0 + const newLevel = deity.favorLevelBuffs ? Math.floor(newFavor / deity.favorLevelBuffs.interval) : 0 + const levelUp = newLevel > oldLevel + const reachedMax = currentFavor < 100 && newFavor >= 100 + await this.petSystem.updateState({ deityFavors: { ...state.deityFavors, @@ -69,16 +89,31 @@ export class TempleSystem { dailyPrayerCount: state.dailyPrayerCount + 1 }) + // 重新計算屬性(好感度變化影響加成) + this.petSystem.calculateCombatStats() + + // 同步 effective 属性到状态 + await this.petSystem.updateState({ + effectiveStr: this.petSystem.state.effectiveStr, + effectiveInt: this.petSystem.state.effectiveInt, + effectiveDex: this.petSystem.state.effectiveDex, + effectiveLuck: this.petSystem.state.effectiveLuck + }) + // 獲取神明對話 - const deity = this.getCurrentDeity() const dialogue = deity.dialogues[Math.floor(Math.random() * deity.dialogues.length)] return { success: true, favorIncrease: result.favorIncrease, newFavor, + oldLevel, + newLevel, + levelUp, + reachedMax, dialogue, - message: result.message + message: result.message, + deity } } catch (error) { console.error('[TempleSystem] 祈福失敗:', error) @@ -98,11 +133,11 @@ export class TempleSystem { async drawFortune() { try { const result = await this.api.drawFortune() - + // 根據籤詩等級應用效果 const { FORTUNE_LOTS } = await import('../data/fortune-lots.js') const lot = FORTUNE_LOTS.find(l => l.grade === result.lot.grade) || FORTUNE_LOTS[0] - + if (lot.effects?.addBuff) { // 應用 Buff(需要透過 eventSystem) return { @@ -111,7 +146,7 @@ export class TempleSystem { buff: lot.effects.addBuff } } - + return { success: true, lot: result.lot @@ -121,5 +156,137 @@ export class TempleSystem { return { success: false, message: '抽籤失敗' } } } + + // 擲筊 + async throwJiaobei(question = '') { + const state = this.petSystem.getState() + const deity = this.getCurrentDeity() + + try { + // 計算實際概率 + const probabilities = this.calculateJiaobeiProbabilities() + + // 嘗試使用 API + const apiResult = await this.api.throwJiaobei({ + petId: state.id, + deityId: deity.id, + question, + probabilities + }) + + // 如果 API 回傳本地降級標記,使用本地邏輯 + if (!apiResult.localFallback) { + return apiResult + } + } catch (error) { + console.warn('[TempleSystem] 擲筊 API 失敗,使用本地邏輯', error) + } + + // 本地邏輯 + const probabilities = this.calculateJiaobeiProbabilities() + const result = this.rollJiaobei(probabilities) + + // 選擇訊息 + const messages = JIAOBEI_CONFIG.messages[result] + const message = messages[Math.floor(Math.random() * messages.length)] + + // 聖筊獎勵:增加好感度 + if (result === 'holy') { + const currentFavor = state.deityFavors[deity.id] || 0 + const bonus = JIAOBEI_CONFIG.holyFavorBonus || 1 + const newFavor = Math.min(100, currentFavor + bonus) + + await this.petSystem.updateState({ + deityFavors: { + ...state.deityFavors, + [deity.id]: newFavor + } + }) + + // 重新計算屬性 + this.petSystem.calculateCombatStats() + + // 同步 effective 属性 + await this.petSystem.updateState({ + effectiveStr: this.petSystem.state.effectiveStr, + effectiveInt: this.petSystem.state.effectiveInt, + effectiveDex: this.petSystem.state.effectiveDex, + effectiveLuck: this.petSystem.state.effectiveLuck + }) + } + + return { + success: true, + result, + message, + deity: deity.name, + probabilities, + question + } + } + + // 計算擲筊概率(考慮運勢和好感度) + calculateJiaobeiProbabilities() { + const state = this.petSystem.getState() + const deity = this.getCurrentDeity() + + // 動態載入配置(同步) + + // 基礎概率 + let { holy, laughing, negative } = JIAOBEI_CONFIG.baseProbability + + // 運勢影響:增加聖筊概率 + const luck = state.luck || 10 + const luckBonus = Math.min( + luck * JIAOBEI_CONFIG.luckModifier, + JIAOBEI_CONFIG.maxModifier + ) + + // 好感度影響:減少陰筊概率 + const favor = state.deityFavors[deity.id] || 0 + const favorBonus = Math.min( + favor * JIAOBEI_CONFIG.favorModifier, + JIAOBEI_CONFIG.maxModifier + ) + + // 調整概率 + holy += luckBonus + favorBonus / 2 + negative -= favorBonus + laughing -= luckBonus / 2 + + // 確保非負 + holy = Math.max(0.1, holy) + laughing = Math.max(0.1, laughing) + negative = Math.max(0.1, negative) + + // 歸一化(確保總和為1) + const total = holy + laughing + negative + + return { + holy: holy / total, + laughing: laughing / total, + negative: negative / total + } + } + + // 投擲筊杯(隨機結果) + rollJiaobei(probabilities) { + const rand = Math.random() + + if (rand < probabilities.holy) { + return 'holy' + } else if (rand < probabilities.holy + probabilities.laughing) { + return 'laughing' + } else { + return 'negative' + } + } + + // Debug: 重置每日祈福次數 + async debugResetDailyPrayer() { + console.log('[Debug] 重置每日祈福次數') + await this.petSystem.updateState({ dailyPrayerCount: 0 }) + return { success: true, message: '已重置祈福次數' } + } } diff --git a/data/deities.js b/data/deities.js index 8a48848..ea40ee8 100644 --- a/data/deities.js +++ b/data/deities.js @@ -10,6 +10,27 @@ export const DEITIES = [ happinessRecovery: 0.25 }, buffDescriptions: ['小遊戲 +10%', '生病機率 -15%', '快樂恢復 +25%'], + + // 好感度等級加成(每10點好感度) + favorLevelBuffs: { + interval: 10, // 每10點好感度提升一級 + buffsPerLevel: { + str: 0.5, // 每級 +0.5 力量 + health: 1 // 每級 +1 最大健康 + } + }, + + // 滿級特殊 Buff(好感度 = 100) + maxFavorBuff: { + id: 'mazu_divine_protection', + name: '媽祖神佑', + description: '媽祖滿級祝福:免疫生病,健康恢復 +50%', + effects: { + sicknessImmune: true, + healthRecovery: 0.5 + } + }, + dialogues: [ '好孩子,媽祖保佑你平安喔', '海上無風浪,心中有媽祖', @@ -27,6 +48,25 @@ export const DEITIES = [ resourceGain: 0.15 }, buffDescriptions: ['掉落率 +20%', '資源獲得 +15%'], + + favorLevelBuffs: { + interval: 10, + buffsPerLevel: { + luck: 0.3, // 每級 +0.3 運勢 + str: 0.3 // 每級 +0.3 力量 + } + }, + + maxFavorBuff: { + id: 'earthgod_prosperity', + name: '土地公賜福', + description: '土地公滿級祝福:掉落率 +50%,資源獲得 +30%', + effects: { + dropRate: 0.5, + resourceGain: 0.3 + } + }, + dialogues: [ '土地公保佑,財源廣進', '好好照顧寵物,會有福報的', @@ -43,6 +83,25 @@ export const DEITIES = [ breedingSuccess: 0.2 }, buffDescriptions: ['快樂恢復 +30%', '繁殖成功率 +20%'], + + favorLevelBuffs: { + interval: 10, + buffsPerLevel: { + happiness: 0.5, // 每級 +0.5 基礎快樂 + dex: 0.4 // 每級 +0.4 敏捷 + } + }, + + maxFavorBuff: { + id: 'yuelao_eternal_love', + name: '月老牽線', + description: '月老滿級祝福:快樂恢復 +60%,繁殖成功率 +50%', + effects: { + happinessRecovery: 0.6, + breedingSuccess: 0.5 + } + }, + dialogues: [ '有緣千里來相會', '好好對待寵物,感情會更深厚', @@ -59,6 +118,25 @@ export const DEITIES = [ miniGameBonus: 0.15 }, buffDescriptions: ['智力成長 +25%', '小遊戲獎勵 +15%'], + + favorLevelBuffs: { + interval: 10, + buffsPerLevel: { + int: 0.6, // 每級 +0.6 智力 + dex: 0.2 // 每級 +0.2 敏捷 + } + }, + + maxFavorBuff: { + id: 'wenchang_supreme_wisdom', + name: '文昌賜智', + description: '文昌滿級祝福:智力成長 +60%,小遊戲獎勵 +40%', + effects: { + intGain: 0.6, + miniGameBonus: 0.4 + } + }, + dialogues: [ '勤學不輟,智慧增長', '好好學習,寵物也會變聰明', @@ -75,6 +153,25 @@ export const DEITIES = [ badEventReduction: 0.15 }, buffDescriptions: ['健康恢復 +20%', '壞事件機率 -15%'], + + favorLevelBuffs: { + interval: 10, + buffsPerLevel: { + health: 1.5, // 每級 +1.5 最大健康 + int: 0.3 // 每級 +0.3 智力 + } + }, + + maxFavorBuff: { + id: 'guanyin_mercy', + name: '觀音慈悲', + description: '觀音滿級祝福:健康恢復 +50%,壞事件機率 -40%', + effects: { + healthRecovery: 0.5, + badEventReduction: 0.4 + } + }, + dialogues: [ '慈悲為懷,萬物皆靈', '好好照顧,觀音會保佑', diff --git a/data/events.js b/data/events.js index 30d510d..d93fff3 100644 --- a/data/events.js +++ b/data/events.js @@ -6,6 +6,7 @@ export const EVENT_CONFIG = [ type: 'good', weight: 0.3, condition: (state) => state.happiness > 50 && state.luck > 10, + conditionDescription: '需要:快樂 > 50 且 運勢 > 10', effects: [ { type: 'modifyStats', payload: { happiness: +10, luck: +5 } }, { type: 'addBuff', payload: { id: 'fortune', name: '福運加持', durationTicks: 5, percent: { luck: 0.2 } } }, @@ -17,6 +18,7 @@ export const EVENT_CONFIG = [ type: 'good', weight: 0.25, condition: (state) => state.hunger < 80, + conditionDescription: '需要:飢餓 < 80', effects: [ { type: 'modifyStats', payload: { hunger: +15, happiness: +5 } }, { type: 'logMessage', payload: '寵物發現了美味的點心!' } @@ -27,6 +29,7 @@ export const EVENT_CONFIG = [ type: 'good', weight: 0.2, condition: (state) => !state.isSleeping && state.happiness < 90, + conditionDescription: '需要:沒有睡覺 且 快樂 < 90', effects: [ { type: 'modifyStats', payload: { happiness: +12, dex: +1 } }, { type: 'logMessage', payload: '寵物玩得很開心,敏捷度提升了!' } @@ -40,6 +43,7 @@ export const EVENT_CONFIG = [ const currentDeity = state.currentDeityId return state.deityFavors[currentDeity] > 30 }, + conditionDescription: '需要:當前神明好感 > 30', effects: [ { type: 'modifyStats', payload: { health: +10, happiness: +8 } }, { type: 'addBuff', payload: { id: 'blessing', name: '神明祝福', durationTicks: 10, percent: { health: 0.1 } } }, @@ -64,6 +68,7 @@ export const EVENT_CONFIG = [ type: 'bad', weight: 0.4, condition: (state) => state.weight > 600 && state.hunger < 20, + conditionDescription: '需要:體重 > 600 且 飢餓 < 20', effects: [ { type: 'modifyStats', payload: { health: -15, weight: +20 } }, { type: 'addBuff', payload: { id: 'hungry_penalty', name: '飢餓懲罰', durationTicks: 3, flat: { hungerDecay: 0.1 } } }, @@ -75,6 +80,7 @@ export const EVENT_CONFIG = [ type: 'bad', weight: 0.3, condition: (state) => state.health < 70 && !state.isSick, + conditionDescription: '需要:健康 < 70 且 沒有生病', effects: [ { type: 'modifyStats', payload: { health: -10, happiness: -5 } }, { type: 'addBuff', payload: { id: 'sick', name: '生病', durationTicks: 5, flat: { healthDecay: 0.05 } } }, diff --git a/data/fates.js b/data/fates.js new file mode 100644 index 0000000..f5e5cc6 --- /dev/null +++ b/data/fates.js @@ -0,0 +1,63 @@ +// 命格資料配置 +export const FATES = [ + { + id: 'lonestar', + name: '天煞孤星', + description: '孤獨的強者,攻擊力極高但容易抑鬱', + buffs: { + attack: 0.25, + happinessRecovery: -0.2, + sicknessReduction: 0.1 + } + }, + { + id: 'lucky_star', + name: '天乙貴人', + description: '吉星高照,處處逢凶化吉', + buffs: { + luck: 20, + badEventReduction: 0.3, + dropRate: 0.15 + } + }, + { + id: 'martial_god', + name: '武曲星', + description: '天生神力,適合戰鬥', + buffs: { + strGain: 0.3, + defense: 0.15, + speed: 0.1 + } + }, + { + id: 'scholar', + name: '文曲星', + description: '聰明絕頂,學習能力強', + buffs: { + intGain: 0.4, + miniGameBonus: 0.2, + gameSuccessRate: 0.15 + } + }, + { + id: 'peach_blossom', + name: '紅鸞星', + description: '人見人愛,容易獲得好感', + buffs: { + happinessRecovery: 0.2, + breedingSuccess: 0.3, + resourceGain: 0.1 + } + }, + { + id: 'broken_army', + name: '破軍星', + description: '衝鋒陷陣,速度極快但防禦較低', + buffs: { + speed: 0.3, + attack: 0.15, + defense: -0.1 + } + } +] diff --git a/data/items.js b/data/items.js index e657129..d25903d 100644 --- a/data/items.js +++ b/data/items.js @@ -26,14 +26,14 @@ export const ITEMS = { name: '酷酷墨鏡', type: 'equipment', effects: { addBuff: { id: 'cool', name: '酷炫', durationTicks: Infinity, percent: { happiness: 0.1 } } }, - description: '永久增加快樂 10%' + description: '配戴時增加快樂 10%' }, training_manual: { id: 'training_manual', name: '訓練手冊', type: 'equipment', effects: { addBuff: { id: 'training', name: '訓練加成', durationTicks: Infinity, percent: { str: 0.15, dex: 0.15 } } }, - description: '永久增加力量和敏捷成長' + description: '配戴時增加力量和敏捷成長' } } diff --git a/data/jiaobei-config.js b/data/jiaobei-config.js new file mode 100644 index 0000000..a314b65 --- /dev/null +++ b/data/jiaobei-config.js @@ -0,0 +1,52 @@ +// 擲筊配置 +export const JIAOBEI_CONFIG = { + // 基礎自然概率(模擬真實物理) + baseProbability: { + holy: 0.50, // 聖筊:一正一反(50%) + laughing: 0.25, // 笑筊:兩個正面(25%) + negative: 0.25 // 陰筊:兩個反面(25%) + }, + + // 運勢影響(每點LUCK增加聖筊概率) + luckModifier: 0.001, + + // 好感度影響(每點好感度減少陰筊概率) + favorModifier: 0.001, + + // 最大概率調整幅度 + maxModifier: 0.15, + + // 結果訊息 + messages: { + holy: [ + '聖筊!神明允准', + '一正一反,吉兆也', + '神明應允,大吉', + '筊杯示意,可行之', + '天意如此,順遂矣' + ], + laughing: [ + '笑筊,神明莞爾', + '兩面皆陽,再問一次', + '神明未決,請重新稟告', + '筊杯發笑,尚需思量', + '神意未明,且慢行之' + ], + negative: [ + '陰筊,神明不允', + '兩面皆陰,不宜此事', + '神明示警,需三思', + '筊杯示凶,宜緩行', + '天意不從,另謀他法' + ] + }, + + // 聖筊獎勵(好感度增加) + holyFavorBonus: 1, + + // 連續聖筊獎勵(未來擴展) + consecutiveHolyBonus: { + 2: 5, // 連2次聖筊 + 3: 15 // 連3次聖筊(大吉) + } +} diff --git a/data/pet-species.js b/data/pet-species.js index c34c298..c0f2022 100644 --- a/data/pet-species.js +++ b/data/pet-species.js @@ -5,18 +5,156 @@ export const PET_SPECIES = { name: '小虎斑貓', description: '活潑可愛的小貓咪', baseStats: { - hungerDecayPerTick: 0.05, - happinessDecayPerTick: 0.08, - poopChancePerTick: 0.02, - sicknessChance: 0.01, + // 系統更新間隔(毫秒) + physiologyTickInterval: 10000, // 生理系統刷新間隔:30秒 + eventCheckInterval: 10000, // 事件檢查間隔:10秒 + + // 衰減速率 (每 tick 60秒) + // 調整為更輕鬆:飢餓 8 小時,快樂 5 小時 + hungerDecayPerTick: 0.2, // 原 0.28 → 0.2 (更慢) + happinessDecayPerTick: 0.33, // 原 0.42 → 0.33 (更慢) + + // 便便系統 + poopChancePerTick: 0.05, // 約 20 分鐘產生一次 + poopHealthDamage: 0.5, // 大幅降低!每坨每分鐘只扣 0.5 (原 3.0) + + // 飢餓系統 + hungerHealthDamage: 1, // 大幅降低!餓肚子每分鐘只扣 1 (原 6.0) + + // 生病系統 + sicknessThreshold: 40, // 健康低於 40 會生病 + + // 瀕死系統 + dyingTimeSeconds: 7200, // 瀕死 2 小時後死亡 + + // 睡眠系統配置 + sleepSchedule: { + nightSleep: { + startHour: 21, // 晚上 21:00 開始 + startMinute: 30, // 21:30 + endHour: 8, // 早上 8:00 結束 + endMinute: 0, + autoSleep: true, // 自動進入睡眠 + randomWakeChance: 0.3 // 在此時段唤醒後,30% 機率隨機再次入睡 + }, + noonNap: { + startHour: 12, // 中午 12:00 開始 + startMinute: 0, + endHour: 13, // 下午 13:00 結束 + endMinute: 0, + autoSleep: true, // 自動進入睡眠 + randomWakeChance: 0.3 // 在此時段唤醒後,30% 機率隨機再次入睡 + } + }, + + // 便便系統配置 + maxPoopCount: 4, // 便便最大數量 + + // 睡眠系統配置 + sleepDecayMultiplier: 0.1, // 睡眠时衰减倍率(饥饿、快乐衰减降低到10%) + + // 默認數值 + defaultHeight: 10, // 默認身高(cm) + defaultWeight: 500, // 默認體重(g) + + // 互動消耗 + playHungerCost: 1, // 玩耍消耗的飢餓值(降低以便玩更久) + + // 戰鬥數值計算系數 + combatFormulas: { + attack: { + strMultiplier: 2.5, // 攻擊力 = STR * 2.5 + DEX * 0.5 + dexMultiplier: 0.5 + }, + defense: { + strMultiplier: 1.0, // 防禦力 = STR * 1.0 + INT * 2.0 + intMultiplier: 2.0 + }, + speed: { + dexMultiplier: 3.0, // 速度 = DEX * 3.0 + INT * 0.5 + intMultiplier: 0.5 + } + }, + + // 其他 dropRate: 0.1, luck: 10 }, lifecycle: [ - { stage: 'egg', durationSeconds: 0, unlockedFeatures: [] }, - { stage: 'baby', durationSeconds: 1800, unlockedFeatures: [] }, - { stage: 'child', durationSeconds: 3600, unlockedFeatures: ['miniGames'] }, - { stage: 'adult', durationSeconds: Infinity, unlockedFeatures: ['miniGames', 'breeding'] } + // durationSeconds 是該階段結束時的累積年齡(秒) + { + stage: 'egg', + durationSeconds: 300, // 蛋在 300 秒(5分鐘)後孵化 + height: 5, // 身高 5cm + baseWeight: 100, // 基礎體重 100g + weightRange: { min: 90, max: 110 }, // ±10% + unlockedFeatures: [], + allowedActions: [], // 蛋階段不能進行任何互動 + enabledSystems: { + hunger: false, // 不會飢餓 + happiness: false, // 不會不開心 + poop: false, // 不會便便 + sickness: false, // 不會生病 + sleep: false // 不會睡覺 + } + }, + { + stage: 'baby', + durationSeconds: 21900, // 幼體持續到 6小時5分鐘 (300 + 21600) + height: 10, // 身高 10cm + baseWeight: 200, // 基礎體重 200g + weightRange: { min: 180, max: 220 }, // ±10% + unlockedFeatures: [], + allowedActions: ['feed', 'play', 'clean', 'heal', 'sleep'], // 所有基礎互動 + enabledSystems: { + hunger: true, + happiness: true, + poop: true, + sickness: true, + sleep: true + } + }, + { + stage: 'child', + durationSeconds: 281100, // 幼年持續到 3天6小時5分鐘 (21900 + 259200) + height: 18, // 身高 18cm + baseWeight: 400, // 基礎體重 400g + weightRange: { min: 360, max: 440 }, // ±10% + unlockedFeatures: ['miniGames'], + allowedActions: ['feed', 'play', 'clean', 'heal', 'sleep'], + enabledSystems: { + hunger: true, + happiness: true, + poop: true, + sickness: true, + sleep: true + }, + conditions: { + str: 8, // 6小時內約需餵食 30-40 次,可獲得 ~10 STR + int: 8 // 6小時內約需玩耍 60-70 次,可獲得 ~10 INT + } + }, + { + stage: 'adult', + durationSeconds: Infinity, // 成年是最終階段 + height: 25, // 身高 25cm + baseWeight: 500, // 基礎體重 500g + weightRange: { min: 450, max: 550 }, // ±10% + unlockedFeatures: ['miniGames', 'breeding'], + allowedActions: ['feed', 'play', 'clean', 'heal', 'sleep'], + enabledSystems: { + hunger: true, + happiness: true, + poop: true, + sickness: true, + sleep: true + }, + conditions: { + str: 50, // 3天內累積 + int: 50, + dex: 50 + } + } ], personality: ['活潑', '黏人'] } diff --git a/docs/BONUS_CHECK.md b/docs/BONUS_CHECK.md new file mode 100644 index 0000000..0e075a2 --- /dev/null +++ b/docs/BONUS_CHECK.md @@ -0,0 +1,26 @@ +# 加成系統檢查清單 + +## 需要驗證的項目 + +### 1. 初始化檢查 +- [ ] assignDestiny() 是否被調用 +- [ ] destiny 是否正確保存到 state +- [ ] getAllBonuses() 是否能正確讀取 destiny.buffs + +### 2. 功能檢查 +- [ ] 餵食:strGain 加成 +- [ ] 玩耍:happinessRecovery, dexGain, intGain 加成 +- [ ] 治療:healthRecovery 加成 +- [ ] 生病:sicknessReduction 減免 +- [ ] 戰鬥屬性:attack, defense, speed 倍率 + +### 3. 測試步驟 +1. 創建新寵物,檢查 console 是否顯示命格 +2. 玩耍,查看快樂增加量是否有加成 +3. 餵食,查看力量增長是否有加成 +4. 治療,查看健康恢復是否有加成 +5. 檢查戰鬥屬性計算 + +## 檢查結果 + +待補充... diff --git a/docs/CONFIG_GUIDE.md b/docs/CONFIG_GUIDE.md new file mode 100644 index 0000000..bf504ee --- /dev/null +++ b/docs/CONFIG_GUIDE.md @@ -0,0 +1,197 @@ +# 数据驱动配置说明 + +## ✅ 完全数据驱动 + +现在所有游戏核心参数都在 **`data/pet-species.js`** 中配置,您只需要修改这个文件就可以调整游戏体验! + +--- + +## 📋 配置文件位置 + +``` +/home/daniel/playone/pet_data/data/pet-species.js +``` + +--- + +## ⚙️ 可配置的参数 + +### 1. 系统更新间隔 + +```javascript +baseStats: { + // 系統更新間隔(毫秒) + physiologyTickInterval: 60000, // 生理系統:60秒 (1分鐘) + eventCheckInterval: 10000, // 事件檢查:10秒 +``` + +**快速调整示例**: + +| 需求 | 修改值 | +|------|--------| +| 生理变化更慢 | `physiologyTickInterval: 120000` (2分钟) | +| 生理变化更快 | `physiologyTickInterval: 30000` (30秒) | +| 事件更频繁 | `eventCheckInterval: 5000` (5秒) | +| 事件更少 | `eventCheckInterval: 20000` (20秒) | + +--- + +### 2. 属性衰减速率 + +```javascript + // 衰減速率 (每 tick) + hungerDecayPerTick: 0.28, // 飢餓衰減 + happinessDecayPerTick: 0.42, // 快樂衰減 +``` + +**计算公式**: +``` +每 tick 衰减值 = 100 ÷ (目标秒数 ÷ tick间隔) +``` + +**示例**: +- 想让饥饿 **8小时** 耗尽,tick间隔 60秒: + ```javascript + hungerDecayPerTick: 100 ÷ (8*3600 ÷ 60) = 100 ÷ 480 = 0.208 + ``` + +--- + +### 3. 便便系统 + +```javascript + // 便便系統 + poopChancePerTick: 0.067, // 產生機率(每 tick) + poopHealthDamage: 3, // 每坨便便的傷害 +``` + +**快速调整示例**: + +| 需求 | 修改值 | +|------|--------| +| 更少便便 | `poopChancePerTick: 0.033` (约30分钟一次) | +| 更多便便 | `poopChancePerTick: 0.133` (约7.5分钟一次) | +| 便便伤害降低 | `poopHealthDamage: 1.5` | +| 便便伤害提高 | `poopHealthDamage: 6` | + +--- + +### 4. 惩罚系统 + +```javascript + // 飢餓系統 + hungerHealthDamage: 6, // 飢餓為 0 時每 tick 扣血 + + // 生病系統 + sicknessThreshold: 40, // 健康低於此值會生病 + + // 瀕死系統 + dyingTimeSeconds: 7200, // 瀕死多久後死亡(秒) +``` + +--- + +### 5. 睡眠时间表 + +```javascript + sleepSchedule: { + nightSleep: { + startHour: 21, // 開始時間 + startMinute: 30, + endHour: 8, // 結束時間 + endMinute: 0, + autoSleep: true, + randomWakeChance: 0.3 // 唤醒後隨機再睡機率 + }, + noonNap: { + startHour: 12, + startMinute: 0, + endHour: 13, + endMinute: 0, + autoSleep: true, + randomWakeChance: 0.3 + } + } +``` + +--- + +## 🎯 常见调整场景 + +### 场景 1:让游戏更轻松(适合忙碌玩家) + +```javascript +baseStats: { + physiologyTickInterval: 120000, // 2分钟一次 tick + hungerDecayPerTick: 0.14, // 12小时耗尽 + happinessDecayPerTick: 0.21, // 8小时耗尽 + poopChancePerTick: 0.033, // 30分钟一次 + dyingTimeSeconds: 14400, // 瀕死 4 小时 +} +``` + +### 场景 2:让游戏更有挑战(硬核模式) + +```javascript +baseStats: { + physiologyTickInterval: 30000, // 30秒一次 tick + hungerDecayPerTick: 0.56, // 3小时耗尽 + happinessDecayPerTick: 0.83, // 2小时耗尽 + poopChancePerTick: 0.2, // 5分钟一次 + dyingTimeSeconds: 1800, // 瀕死 30 分钟 +} +``` + +### 场景 3:观赏模式(几乎不用照顾) + +```javascript +baseStats: { + physiologyTickInterval: 300000, // 5分钟一次 tick + hungerDecayPerTick: 0.07, // 24小时耗尽 + happinessDecayPerTick: 0.14, // 12小时耗尽 + poopChancePerTick: 0.017, // 60分钟一次 + dyingTimeSeconds: 28800, // 瀕死 8 小时 +} +``` + +--- + +## 📝 修改后的操作 + +1. **保存文件** - 修改 `pet-species.js` 后保存 +2. **刷新浏览器** - 简单刷新即可(HMR 会自动加载) +3. **重新创建宠物**(重要!)- 在控制台执行: + ```javascript + handleDeletePet() // 删除旧宠物 + // 刷新页面创建新宠物 + ``` + +--- + +## ⚠️ 注意事项 + +1. **tick 间隔改变时,记得调整衰减值** + - 例如:从 60 秒改为 120 秒,衰减值应该 ×2 + +2. **计算公式**: + ``` + 每tick衰减值 = 100 ÷ (目标小时数 × 3600 ÷ tick间隔秒数) + ``` + +3. **已有宠物不受影响** + - 配置只对**新创建的宠物**生效 + - 已有宠物使用创建时的配置 + - 想要应用新配置需要删除重建 + +--- + +## 🎮 目前的设置(默认值) + +- **生理 Tick**:60秒(1分钟) +- **事件检查**:10秒 +- **饥饿耗尽**:约 6 小时 +- **快乐耗尽**:约 4 小时 +- **便便产生**:约 15 分钟一次 +- **瀕死缓冲**:2 小时 + +配合睡眠系统(夜间 10.5h + 午休 1h),玩家每天只需照顾 **2-3 次** 即可! diff --git a/docs/EVOLUTION_SYSTEM.md b/docs/EVOLUTION_SYSTEM.md new file mode 100644 index 0000000..6242ba9 --- /dev/null +++ b/docs/EVOLUTION_SYSTEM.md @@ -0,0 +1,209 @@ +# 進化系統設計文檔 + +## 現有實現 + +### 基礎進化機制 +目前系統已支援基於**時間 + 屬性條件**的進化: + +```javascript +{ + stage: 'child', + durationSeconds: 600, // 時間條件:需要 600 秒 + conditions: { // 屬性條件:同時滿足 + str: 20, + int: 20 + } +} +``` + +### 進化條件檢查 +- **時間條件**:寵物年齡 >= durationSeconds +- **屬性條件**:所有指定屬性都要達標 +- **邏輯**:時間 AND 屬性 都滿足才進化 + +## 進化分支系統設計 + +### 方案 1:多分支進化(推薦) + +```javascript +lifecycle: [ + { stage: 'egg', durationSeconds: 0 }, + { stage: 'baby', durationSeconds: 300 }, + { + stage: 'child', + durationSeconds: 600, + conditions: { str: 20, int: 20 } + }, + // 成年體可以有多種進化路線 + { + stage: 'adult_warrior', // 戰士型 + durationSeconds: 1200, + conditions: { str: 60, dex: 40 }, + priority: 1 + }, + { + stage: 'adult_mage', // 法師型 + durationSeconds: 1200, + conditions: { int: 60, str: 30 }, + priority: 2 + }, + { + stage: 'adult_balanced', // 平衡型 + durationSeconds: 1200, + conditions: { str: 50, int: 50, dex: 50 }, + priority: 3 + }, + { + stage: 'adult', // 普通成年(保底) + durationSeconds: 1200, + conditions: {}, + priority: 999 + } +] +``` + +**檢查邏輯**: +1. 時間到達後,檢查所有同階段的進化選項 +2. 按 priority 從小到大檢查 +3. 第一個滿足條件的就進化成該階段 + +### 方案 2:樹狀進化圖 + +```javascript +export const EVOLUTION_TREE = { + tinyTigerCat: { + baby: { + next: ['child'], // 固定進化 + conditions: { ageSeconds: 300 } + }, + child: { + next: ['warrior', 'mage', 'balanced', 'adult'], // 多選一 + conditions: { ageSeconds: 600 }, + branches: { + warrior: { str: 60, dex: 40 }, + mage: { int: 60, str: 30 }, + balanced: { str: 50, int: 50, dex: 50 }, + adult: {} // 保底 + } + } + } +} +``` + +### 方案 3:觸發式進化 + +```javascript +{ + stage: 'adult_legendary', // 傳說型 + trigger: 'special', // 不走正常流程 + conditions: { + str: 100, + int: 100, + dex: 100, + destiny: 'lucky_star', // 需要特定命格 + deityFavor: 100 // 神明好感滿 + } +} +``` + +## 建議實現 + +### 第一階段(基礎) +使用**方案 1**,在 `pet-species.js` 中定義多條進化路線: + +```javascript +lifecycle: [ + { stage: 'baby', ... }, + { stage: 'child', ... }, + { + stage: 'warrior', + branch: 'combat', // 標記分支類型 + durationSeconds: 1200, + conditions: { str: 60, dex: 40 }, + priority: 1, + description: '力量與速度的化身' + }, + { + stage: 'mage', + branch: 'magic', + durationSeconds: 1200, + conditions: { int: 60 }, + priority: 2, + description: '智慧的追求者' + }, + { + stage: 'adult', + branch: 'normal', + durationSeconds: 1200, + conditions: {}, + priority: 999, + description: '普通的成年體' + } +] +``` + +### 第二階段(進階) +- 添加**超進化**:需要特殊道具或事件觸發 +- 添加**退化機制**:照顧不當可能退化 +- 添加**命格影響**:特定命格解鎖特殊進化路線 + +## 實現細節 + +### 修改 checkStageTransition() + +```javascript +checkStageTransition() { + const ageSeconds = this.state.ageSeconds + const currentStage = this.state.stage + + // 找出當前階段的所有可能進化 + const candidates = this.speciesConfig.lifecycle + .filter(stage => + stage.durationSeconds > 0 && + ageSeconds >= stage.durationSeconds && + stage.stage !== currentStage + ) + .sort((a, b) => (a.priority || 999) - (b.priority || 999)) + + // 檢查第一個滿足條件的 + for (const candidate of candidates) { + if (this.checkEvolutionConditions(candidate)) { + this.evolve(candidate.stage) + break + } + } +} + +checkEvolutionConditions(stageConfig) { + if (!stageConfig.conditions) return true + + const { str, int, dex, ...others } = stageConfig.conditions + + if (str && this.state.str < str) return false + if (int && this.state.int < int) return false + if (dex && this.state.dex < dex) return false + + // 可擴展:檢查其他條件 + return true +} +``` + +## 數值平衡建議 + +### 進化時間規劃 +- **Egg → Baby**: 5 分鐘(300秒) +- **Baby → Child**: 6 小時(21,900秒) +- **Child → Adult**: 3 天(281,100秒) + +### 屬性要求 +- **Baby → Child**: + - STR 8, INT 8 + - 設計目標:6小時內約需餵食 30-40 次,玩耍 60-70 次 +- **Child → Adult**: + - STR 50, INT 50, DEX 50 + - 設計目標:3天內累積,需要持續照顧 + +### 命格影響 +- **武曲星**(力量成長+30%)→ 更容易進化成戰士 +- **文曲星**(智力成長+40%)→ 更容易進化成法師 +- **天乙貴人**(運勢高)→ 可能觸發特殊進化 diff --git a/docs/GAME_BALANCE.md b/docs/GAME_BALANCE.md new file mode 100644 index 0000000..b29b632 --- /dev/null +++ b/docs/GAME_BALANCE.md @@ -0,0 +1,156 @@ +# 游戏平衡设计文档 + +## 📊 平衡目标 + +参考经典电子宠物(Tamagotchi)的设计,让玩家能够轻松养育宠物,不需要频繁操作。 + +### 核心原则 +- **每 1-2 小时检查一次即可** +- **睡眠时段自动保护**(夜间 + 午休) +- **渐进式挑战**(不同阶段不同难度) +- **容错空间**(有足够时间反应) + +--- + +## ⏱️ 时间轴设计 + +### 当前配置(调整后) + +| 属性 | 完全耗尽时间 | 检查频率建议 | +|------|------------|------------| +| **饥饿** | ~2 小时 | 每 1-2 小时餵食一次 | +| **快乐** | ~1.5 小时 | 每 1-1.5 小时玩耍一次 | +| **便便** | ~10 分钟产生一次 | 每 30-60 分钟清理一次 | +| **健康** | 缓慢下降 | 出现问题时治疗 | + +### 与 Tamagotchi 对比 + +| 特性 | Tamagotchi | 我们的设计 | +|------|-----------|-----------| +| 婴儿期喂食间隔 | 3-45 分钟 | ~2 小时(更宽松)| +| 成年期喂食间隔 | 30-90 分钟 | ~2 小时 | +| 夜间保护 | ✅ 自动睡眠 | ✅ 自动睡眠 | +| 午休保护 | ❌ 无 | ✅ 有 | +| 瀕死缓冲时间 | ~少 | 1 小时 | + +--- + +## 🎯 详细数值说明 + +### 衰减速率 + +```javascript +// Tick 间隔: 3 秒 +hungerDecayPerTick: 0.014 // 每 tick 减少 1.4% +happinessDecayPerTick: 0.018 // 每 tick 减少 1.8% +``` + +**计算**: +- 饥饿: 100 ÷ 1.4 = 71.4 个 tick ≈ 214 秒 ≈ **3.6 分钟降低到 0**... 等等! + +让我重新计算: +- 每 tick 3 秒 +- 100% ÷ 0.014 = 7142.8 tick +- 7142.8 × 3 秒 = 21,428 秒 ≈ **357 分钟** ≈ **6 小时** + +实际上应该是: +- hungerDecayPerTick: 0.014 表示每 tick 减少 **1.4 点**(不是 1.4%) +- 100 ÷ 1.4 = 71.4 tick × 3 秒 = **214 秒** ≈ **3.6 分钟** + +这还是太快了!让我重新调整... + +--- + +## 🔧 正确的计算和调整 + +### 目标设定 +- **饥饿**: 2 小时 (7200 秒) 降到 0 +- **快乐**: 1.5 小时 (5400 秒) 降到 0 + +### 计算公式 +``` +每 tick 衰减值 = 100 ÷ (目标秒数 ÷ tick间隔) +``` + +**饥饿**: +- 100 ÷ (7200 ÷ 3) = 100 ÷ 2400 = **0.0417** + +**快乐**: +- 100 ÷ (5400 ÷ 3) = 100 ÷ 1800 = **0.0556** + +### 睡眠时衰减(10%) +- 饥饿睡眠衰减: 0.0417 × 0.1 = 0.00417 +- 夜间睡眠 10.5 小时衰减: 0.00417 × (10.5 × 3600 ÷ 3) ≈ **52.5%** +- 白天清醒 13.5 小时可恢复 + +--- + +## 📝 推荐调整 + +### 保守模式(轻松养育) +```javascript +hungerDecayPerTick: 0.042 // 2 小时耗尽 +happinessDecayPerTick: 0.056 // 1.5 小时耗尽 +poopChancePerTick: 0.005 // 约 10 分钟一次 +``` + +### 标准模式(平衡) +```javascript +hungerDecayPerTick: 0.056 // 1.5 小时耗尽 +happinessDecayPerTick: 0.083 // 1 小时耗尽 +poopChancePerTick: 0.008 // 约 6 分钟一次 +``` + +### 挑战模式(需要更多照顾) +```javascript +hungerDecayPerTick: 0.083 // 1 小时耗尽 +happinessDecayPerTick: 0.125 // 40 分钟耗尽 +poopChancePerTick: 0.017 // 约 3 分钟一次 +``` + +--- + +## 🌙 睡眠系统的重要性 + +### 睡眠保护时段 +- **夜间**: 21:30 - 08:00 (10.5 小时) +- **午休**: 12:00 - 13:00 (1 小时) +- **总计**: 每天 11.5 小时受保护 + +### 睡眠时的消耗 +- 属性衰减降低到 **10%** +- 10.5 小时夜间睡眠的实际消耗 = 1.05 小时清醒消耗 +- 这给玩家充足的休息时间 + +--- + +## 💡 建议的游戏体验 + +### 理想的日常流程 +1. **早上 08:00** - 寠物自动醒来 +2. **09:00** - 餵食 + 玩耍 + 清理 +3. **12:00** - 寠物午休 +4. **13:00** - 午休结束 +5. **14:00** - 餵食 + 玩耍 +6. **17:00** - 餵食 + 玩耍 + 清理 +7. **21:30** - 寠物自动睡觉 +8. **夜间** - 玩家休息,不用担心 + +### 最低维护频率 +- **工作日**: 早上、午休、下班后各一次(3次/天) +- **周末**: 更灵活,想玩就玩 + +--- + +## 🎮 进一步优化建议 + +### 可选功能 +1. **难度选择**: 让玩家选择保守/标准/挑战模式 +2. **阶段差异**: 婴儿期需求更频繁,成年期更轻松 +3. **物品系统**: 自动餵食器、玩具等辅助道具 +4. **提醒系统**: 饥饿/快乐低于 30% 时提醒 + +### 平衡调整方向 +- 如果玩家觉得**太轻松**: 增加衰减速率 20-30% +- 如果玩家觉得**太困难**: 降低衰减速率 20-30% +- 观察玩家平均每天互动次数,理想是 **3-5 次** diff --git a/docs/SLEEP_SYSTEM.md b/docs/SLEEP_SYSTEM.md new file mode 100644 index 0000000..41435f6 --- /dev/null +++ b/docs/SLEEP_SYSTEM.md @@ -0,0 +1,118 @@ +# 睡眠系统文档 + +## 概述 + +睡眠系统实现了基于真实时间的自动睡眠机制,让宠物在特定时段自动进入睡眠状态。睡眠时寠物处于类似"暂停"的状态,时间和年龄继续增长,但属性衰减大幅降低。 + +## 睡眠时段配置 + +睡眠时段在 `data/pet-species.js` 的 `baseStats.sleepSchedule` 中配置: + +### 夜间睡眠 (Night Sleep) +- **时间**: 21:30 - 08:00 +- **自动睡眠**: 是 +- **唤醒后随机再睡机率**: 30% + +### 午休 (Noon Nap) +- **时间**: 12:00 - 13:00 +- **自动睡眠**: 是 +- **唤醒后随机再睡机率**: 30% + +## 配置结构 + +```javascript +sleepSchedule: { + nightSleep: { + startHour: 21, // 开始小时 + startMinute: 30, // 开始分钟 + endHour: 8, // 结束小时 + endMinute: 0, // 结束分钟 + autoSleep: true, // 是否自动进入睡眠 + randomWakeChance: 0.3 // 唤醒后随机再睡的机率 (0-1) + }, + noonNap: { + startHour: 12, + startMinute: 0, + endHour: 13, + endMinute: 0, + autoSleep: true, + randomWakeChance: 0.3 + } +} +``` + +## 睡眠机制 + +### 1. 自动进入睡眠 +- 每个 tick (3秒) 检查当前时间 +- 如果在睡眠时段且宠物未睡觉,自动进入睡眠 +- 控制台显示:`😴 夜間睡眠時間,寵物自動進入睡眠` + +### 2. 自动醒来 +- 当睡眠时段结束时,自动睡眠的宠物会自动醒来 +- 控制台显示:`⏰ 睡眠時間結束,寵物自動醒來` + +### 3. 手动唤醒 + 随机再睡 +- 在睡眠时段手动唤醒宠物时: + - 有 30% 机率宠物会打个哈欠又睡着 + - 控制台显示:`寵物醒來後打了個哈欠,在夜間時段又睡著了...` + - 有 70% 机率成功唤醒 + +### 4. 手动睡眠 +- 在非睡眠时段,玩家可以手动让宠物睡觉 +- 手动睡眠不受时段限制 + +## 睡眠时的状态 + +### 属性衰减 +- **饥饿衰减**: 降低到 10% +- **快乐衰减**: 降低到 10% +- **便便**: 不会产生新便便 +- **年龄**: 继续增长 + +### 互动限制 +- ❌ 不能餵食 +- ❌ 不能玩耍 +- ❌ 不能治療 +- ✅ 可以清理便便 +- ✅ 可以唤醒 + +### 系统暂停 +- **事件系统**: 睡眠时不会触发任何随机事件 +- **战斗系统**: 睡眠时无法进行战斗 + +## 实现细节 + +### 核心方法 + +#### `isInSleepTime()` +检查当前时间是否在睡眠时段内,返回 `'nightSleep'`、`'noonNap'` 或 `null`。 + +#### `getSleepPeriod(periodName)` +获取指定睡眠时段的配置。 + +#### `toggleSleep()` +切换睡眠状态,包含随机再睡逻辑。 + +### 状态标记 + +- `isSleeping`: 是否在睡眠中 +- `_autoSlept`: 是否为自动睡眠(内部使用,用于区分自动/手动睡眠) + +## 游戏体验 + +睡眠系统提供了以下游戏体验: + +1. **真实感**: 宠物遵循自然的作息时间 +2. **保护机制**: 夜间和午休时宠物自动进入低消耗模式 +3. **互动性**: 玩家可以尝试唤醒宠物,但有机率失败 +4. **平衡性**: 睡眠时不会完全暂停,只是大幅降低消耗 + +## 调整建议 + +可以通过修改配置来调整睡眠系统: + +- **延长/缩短睡眠时段**: 修改 `startHour/endHour` +- **调整唤醒难度**: 修改 `randomWakeChance` (0 = 总是能唤醒, 1 = 总是失败) +- **添加新的睡眠时段**: 在 `sleepSchedule` 中添加新配置 +- **完全禁用自动睡眠**: 设置 `autoSleep: false` diff --git a/docs/STATS_BONUSES.md b/docs/STATS_BONUSES.md new file mode 100644 index 0000000..1df8c89 --- /dev/null +++ b/docs/STATS_BONUSES.md @@ -0,0 +1,125 @@ +# 寵物系統 - 所有數值與加成列表 + +## 基礎屬性 +| 屬性名稱 | 說明 | 初始值 | 範圍 | +|---------|------|--------|------| +| hunger | 飢餓值 | 100 | 0-100 | +| happiness | 快樂值 | 100 | 0-100 | +| health | 健康值 | 100 | 0-100 | +| str | 力量 | 10 | 0-∞ | +| int | 智力 | 10 | 0-∞ | +| dex | 敏捷 | 10 | 0-∞ | +| luck | 運勢 | 10 | 0-∞ | + +## 戰鬥屬性(計算值) +| 屬性名稱 | 計算公式 | 說明 | +|---------|---------|------| +| attack | (STR × 2.5 + DEX × 0.5) × 攻擊加成 | 攻擊力 | +| defense | (STR × 1.0 + INT × 2.0) × 防禦加成 | 防禦力 | +| speed | (DEX × 3.0 + INT × 0.5) × 速度加成 | 速度 | + +## 隱藏數值 +| 數值名稱 | 說明 | 基礎值 | 受影響方式 | +|---------|------|--------|-----------| +| hungerDecayPerTick | 飢餓衰減速度 | 0.05/tick | 每3秒 -5 | +| happinessDecayPerTick | 快樂衰減速度 | 0.08/tick | 每3秒 -8 | +| poopChancePerTick | 便便機率 | 0.02/tick | 每tick 2% | +| sicknessChance | 生病機率 | 0.01/tick | 每tick 1% | +| dropRate | 掉落率 | 0.1 | 基礎10% | + +## 加成類型總覽 + +### 1. 屬性成長加成 +| 加成名稱 | 效果 | 來源 | +|---------|------|------| +| strGain | 力量成長 +X% | 命格、神明 | +| intGain | 智力成長 +X% | 命格、神明 | +| dexGain | 敏捷成長 +X% | 命格 | + +**實際效果**:餵食/玩耍時,屬性增加量 × (1 + 加成) + +### 2. 戰鬥屬性加成 +| 加成名稱 | 效果 | 來源 | +|---------|------|------| +| attack | 攻擊力 +X% | 命格 | +| defense | 防禦力 +X% | 命格 | +| speed | 速度 +X% | 命格 | + +**實際效果**:直接乘以倍率 + +### 3. 恢復與機率加成 +| 加成名稱 | 效果 | 實際含義 | 來源 | +|---------|------|----------|------| +| happinessRecovery | 快樂恢復 +X% | 自然恢復或主動恢復時,恢復量 × (1 + X) | 命格、神明 | +| healthRecovery | 健康恢復 +X% | 治療或睡覺時,恢復量 × (1 + X) | 神明 | +| sicknessReduction | 生病抗性 +X% | 生病機率 × (1 - X) | 命格、神明 | +| badEventReduction | 壞事件減少 +X% | 壞事件觸發機率 × (1 - X) | 命格、神明 | + +### 4. 遊戲與資源加成 +| 加成名稱 | 效果 | 實際含義 | 來源 | +|---------|------|----------|------| +| gameSuccessRate | 小遊戲成功率 +X% | 成功機率提升 X | 神明 | +| miniGameBonus | 小遊戲獎勵 +X% | 獎勵 × (1 + X) | 命格、神明 | +| dropRate | 掉落率 +X% | 掉落機率 × (1 + X) | 命格、神明 | +| resourceGain | 資源獲得 +X% | 資源 × (1 + X) | 神明 | +| breedingSuccess | 繁殖成功率 +X% | 繁殖機率提升 X | 神明 | + +### 5. 其他加成 +| 加成名稱 | 效果 | 來源 | +|---------|------|------| +| luck | 運勢 +X(直接加值) | 命格 | + +## 當前命格列表 + +### 天煞孤星 +- 攻擊力 +25% +- 快樂恢復 -20% +- 生病抗性 +10% + +### 天乙貴人 +- 運勢 +20 +- 壞事件減少 +30% +- 掉落率 +15% + +### 武曲星 +- 力量成長 +30% +- 防禦力 +15% +- 速度 +10% + +### 文曲星 +- 智力成長 +40% +- 小遊戲獎勵 +20% +- 小遊戲成功率 +15% + +### 紅鸞星 +- 快樂恢復 +20% +- 繁殖成功率 +30% +- 資源獲得 +10% + +### 破軍星 +- 速度 +30% +- 攻擊力 +15% +- 防禦力 -10% + +## 當前神明加成列表 + +### 媽祖 +- 小遊戲成功率 +10% +- 生病抗性 +15% +- 快樂恢復 +25% + +### 土地公 +- 掉落率 +20% +- 資源獲得 +15% + +### 月老 +- 快樂恢復 +30% +- 繁殖成功率 +20% + +### 文昌 +- 智力成長 +25% +- 小遊戲獎勵 +15% + +### 觀音 +- 健康恢復 +20% +- 壞事件減少 +15% diff --git a/public/test-events.js b/public/test-events.js new file mode 100644 index 0000000..2fad635 --- /dev/null +++ b/public/test-events.js @@ -0,0 +1,86 @@ +// 測試所有事件 +async function testAllEvents() { + if (!eventSystem) { + console.log('❌ 系統尚未初始化') + return + } + + const events = await apiService.getEvents() + + console.log('\n' + '='.repeat(60)) + console.log('🧪 開始測試所有事件...') + console.log('='.repeat(60)) + + for (const event of events) { + console.log(`\n▶️ 測試事件: ${event.id} (${event.type})`) + console.log(' 狀態 BEFORE:') + const stateBefore = petSystem.getState() + console.log(` 飢餓: ${stateBefore.hunger.toFixed(1)} | 快樂: ${stateBefore.happiness.toFixed(1)} | 健康: ${stateBefore.health.toFixed(1)}`) + + // 觸發事件 + await triggerEvent(event.id) + + // 等待更新 + await new Promise(resolve => setTimeout(resolve, 100)) + + console.log(' 狀態 AFTER:') + const stateAfter = petSystem.getState() + console.log(` 飢餓: ${stateAfter.hunger.toFixed(1)} | 快樂: ${stateAfter.happiness.toFixed(1)} | 健康: ${stateAfter.health.toFixed(1)}`) + + console.log(' ─'.repeat(30)) + } + + console.log('\n' + '='.repeat(60)) + console.log('✅ 所有事件測試完成') + console.log('='.repeat(60) + '\n') + + showStatus() +} + +// 測試單個事件(詳細版) +async function testEvent(eventId) { + if (!eventSystem) { + console.log('❌ 系統尚未初始化') + return + } + + const events = await apiService.getEvents() + const event = events.find(e => e.id === eventId) + + if (!event) { + console.log(`❌ 找不到事件: ${eventId}`) + console.log('💡 使用 listEvents() 查看所有事件') + return + } + + console.log('\n' + '='.repeat(60)) + console.log(`🧪 測試事件: ${event.id}`) + console.log('='.repeat(60)) + console.log(`類型: ${event.type}`) + console.log(`效果數量: ${event.effects.length}`) + console.log('') + + // 顯示效果 + event.effects.forEach((eff, i) => { + console.log(`效果 ${i + 1}: ${eff.type}`) + if (eff.payload) { + console.log(' payload:', eff.payload) + } + }) + + console.log('\n觸發前狀態:') + showStatus() + + console.log('\n▶️ 觸發事件...\n') + await triggerEvent(eventId) + + await new Promise(resolve => setTimeout(resolve, 100)) + + console.log('\n觸發後狀態:') + showStatus() + + console.log('='.repeat(60) + '\n') +} + +window.testAllEvents = testAllEvents +window.testEvent = testEvent