feat:merge health

This commit is contained in:
王性驊 2025-11-24 15:38:44 +08:00
parent ebe90953fe
commit 9b1d27186d
20 changed files with 5719 additions and 313 deletions

1814
app/app-original.vue.backup Normal file

File diff suppressed because it is too large Load Diff

573
app/app-retro.vue Normal file
View File

@ -0,0 +1,573 @@
<template>
<div class="retro-container">
<!-- 标题栏 -->
<div class="title-bar">
<div class="title-text">[ VIRTUAL PET SYSTEM v1.0 ]</div>
<div class="system-status">{{ systemStatus }}</div>
</div>
<!-- 主游戏区域 -->
<div class="game-area" v-if="petState">
<!-- 状态显示区 -->
<div class="stats-panel">
<div class="panel-title">[ STATUS ]</div>
<div class="stat-row">
<span class="stat-name">NAME:</span>
<span class="stat-value">{{ petState.name || 'UNNAMED' }}</span>
</div>
<div class="stat-row">
<span class="stat-name">STAGE:</span>
<span class="stat-value">{{ getStageName(petState.stage) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">AGE:</span>
<span class="stat-value">{{ formatAge(petState.ageSeconds) }}</span>
</div>
<div class="divider">---------------</div>
<div class="stat-row">
<span class="stat-name">HUNGER:</span>
<span class="stat-bar-text">{{ makeBar(petState.hunger) }} {{ Math.round(petState.hunger) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">HAPPY:</span>
<span class="stat-bar-text">{{ makeBar(petState.happiness) }} {{ Math.round(petState.happiness) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">HEALTH:</span>
<span class="stat-bar-text">{{ makeBar((petState.health / getMaxHealth(petState)) * 100) }} {{ Math.round(petState.health) }}/{{ getMaxHealth(petState) }}</span>
</div>
<div class="divider">---------------</div>
<div class="stat-row">
<span class="stat-name">STR:</span>
<span class="stat-value">{{ Math.round(petState.effectiveStr || petState.str || 0) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">INT:</span>
<span class="stat-value">{{ Math.round(petState.effectiveInt || petState.int || 0) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">DEX:</span>
<span class="stat-value">{{ Math.round(petState.effectiveDex || petState.dex || 0) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">LUCK:</span>
<span class="stat-value">{{ Math.round(petState.effectiveLuck || petState.luck || 0) }}</span>
</div>
<div class="divider">---------------</div>
<div v-if="petState.isSleeping" class="status-flag">[SLEEPING]</div>
<div v-if="petState.isSick" class="status-flag sick">[SICK]</div>
<div v-if="petState.poopCount > 0" class="status-flag">[POOP x{{ petState.poopCount }}]</div>
</div>
<!-- 命令输入区 -->
<div class="command-panel">
<div class="panel-title">[ ACTIONS ]</div>
<div class="command-grid">
<button class="cmd-btn" @click="handleFeed" :disabled="!canDoAction('feed')">
[F]EED
</button>
<button class="cmd-btn" @click="handlePlay('normal')" :disabled="!canDoAction('play')">
[P]LAY
</button>
<button class="cmd-btn" @click="handleClean" :disabled="!canDoAction('clean')">
[C]LEAN
</button>
<button class="cmd-btn" @click="handleHeal" :disabled="!canDoAction('heal')">
[H]EAL
</button>
<button class="cmd-btn" @click="handleSleep" :disabled="!canDoAction('sleep')">
{{ petState?.isSleeping ? '[W]AKE' : '[S]LEEP' }}
</button>
<button class="cmd-btn" @click="handlePray" :disabled="!isReady">
P[R]AY
</button>
</div>
</div>
<!-- 控制台输出 -->
<div class="console-panel">
<div class="panel-title">[ CONSOLE ]</div>
<div class="console-log" id="console-output">
<div class="log-line">SYSTEM READY...</div>
</div>
</div>
</div>
<!-- Debug 面板 -->
<button class="debug-toggle" @click="showDebug = !showDebug">[DEBUG]</button>
<div v-if="showDebug" class="debug-panel">
<div class="panel-title">[ DEBUG TOOLS ]</div>
<button class="cmd-btn" @click="debugResetPrayer">RESET PRAYER</button>
<button class="cmd-btn" @click="debugAddFavor(50)">+50 FAVOR</button>
<button class="cmd-btn" @click="debugMaxAttributes">MAX STATS</button>
<button class="cmd-btn" @click="debugAddPoop">ADD POOP x4</button>
</div>
<!-- 名字输入 Modal -->
<div v-if="showNameInput" class="name-modal">
<div class="modal-box">
<div class="panel-title">[ NAME YOUR PET ]</div>
<input
v-model="inputPetName"
@keyup.enter="confirmName"
type="text"
placeholder="ENTER NAME..."
maxlength="20"
class="name-input"
/>
<button class="cmd-btn" @click="confirmName" :disabled="!inputPetName || inputPetName.trim().length === 0">
CONFIRM
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { PetSystem } from '../core/pet-system.js'
import { EventSystem } from '../core/event-system.js'
import { TempleSystem } from '../core/temple-system.js'
//
const petState = ref(null)
const systemStatus = ref('INITIALIZING...')
const showDebug = ref(false)
const showNameInput = ref(false)
const inputPetName = ref('')
const isReady = ref(false)
const isRunning = ref(false)
//
let petSystem = null
let eventSystem = null
let templeSystem = null
// ASCII
function makeBar(value) {
const MAX_BARS = 10
const filled = Math.round((value || 0) / 10)
return '[' + '█'.repeat(filled) + '░'.repeat(MAX_BARS - filled) + ']'
}
//
function formatAge(seconds) {
if (!seconds) return '0s'
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = Math.floor(seconds % 60)
if (h > 0) return `${h}h${m}m`
if (m > 0) return `${m}m${s}s`
return `${s}s`
}
//
function getStageName(stage) {
const names = {
egg: 'EGG',
baby: 'BABY',
child: 'CHILD',
adult: 'ADULT'
}
return names[stage] || stage
}
//
function getMaxHealth(state) {
if (!state) return 100
const bonuses = getAllBonuses(state)
return 100 + (bonuses.health || 0)
}
//
function getAllBonuses(state) {
if (!petSystem) return {}
return petSystem.getAllBonuses()
}
//
function canDoAction(action) {
if (!petState.value) return false
if (action === 'feed') return petState.value.hunger < 100
if (action === 'play') return petState.value.hunger > 0 && petState.value.happiness < 100
if (action === 'clean') return petState.value.poopCount > 0
if (action === 'heal') return petState.value.health < getMaxHealth(petState.value)
if (action === 'sleep') return !petState.value.isDead
return isReady.value
}
//
async function handleFeed() {
const result = await petSystem.feed()
log(result.success ? `FED PET. HUNGER +${result.hungerGain}` : result.message)
updateState()
}
async function handlePlay(type = 'normal') {
const result = await petSystem.play({ gameType: type })
log(result.success ? `PLAYED. HAPPY +${Math.round(result.happinessGain)}` : result.message)
updateState()
}
async function handleClean() {
const result = await petSystem.clean()
log(result.success ? `CLEANED ${result.poopCount} POOP` : result.message)
updateState()
}
async function handleHeal() {
const result = await petSystem.heal()
log(result.success ? `HEALED +${Math.round(result.healAmount)}` : result.message)
updateState()
}
async function handleSleep() {
const result = await petSystem.sleep()
log(result.success ? result.message : result.message)
updateState()
}
async function handlePray() {
const result = await templeSystem.pray()
if (result.success) {
log(`PRAYED. FAVOR +${result.favorIncrease} (${result.newFavor}/100)`)
if (result.reachedMax) log('*** MAX FAVOR REACHED! ***')
} else {
log(result.message)
}
updateState()
}
// Debug
async function debugResetPrayer() {
templeSystem.debugResetDailyPrayer()
log('[DEBUG] PRAYER RESET')
updateState()
}
async function debugAddFavor(amount) {
const deityId = petState.value.currentDeityId
const current = petState.value.deityFavors[deityId] || 0
await petSystem.updateState({
deityFavors: {
...petState.value.deityFavors,
[deityId]: Math.min(100, current + amount)
}
})
petSystem.calculateCombatStats()
log(`[DEBUG] FAVOR +${amount}`)
updateState()
}
async function debugMaxAttributes() {
await petSystem.debugSetStat('str', 100)
await petSystem.debugSetStat('int', 100)
await petSystem.debugSetStat('dex', 100)
log('[DEBUG] STATS MAXED')
updateState()
}
async function debugAddPoop() {
await petSystem.debugSetStat('poopCount', 4)
log('[DEBUG] ADDED 4 POOP')
updateState()
}
//
function updateState() {
if (petSystem) {
petState.value = { ...petSystem.getState() }
}
}
//
function log(message) {
const consoleEl = document.getElementById('console-output')
if (consoleEl) {
const line = document.createElement('div')
line.className = 'log-line'
line.textContent = `> ${message}`
consoleEl.appendChild(line)
consoleEl.scrollTop = consoleEl.scrollHeight
//
while (consoleEl.children.length > 50) {
consoleEl.removeChild(consoleEl.firstChild)
}
}
}
//
async function confirmName() {
if (inputPetName.value && inputPetName.value.trim().length > 0) {
await petSystem.updateState({ name: inputPetName.value.trim() })
showNameInput.value = false
log(`PET NAMED: ${inputPetName.value.trim()}`)
updateState()
}
}
//
onMounted(async () => {
log('LOADING SYSTEMS...')
petSystem = new PetSystem()
eventSystem = new EventSystem(petSystem)
templeSystem = new TempleSystem(petSystem)
await petSystem.initialize()
await eventSystem.initialize()
updateState()
//
if (!petState.value.name) {
showNameInput.value = true
}
//
petSystem.startTickLoop()
eventSystem.startEventCheck()
isRunning.value = true
isReady.value = true
systemStatus.value = 'RUNNING'
log('SYSTEM INITIALIZED')
log('TYPE COMMANDS TO INTERACT')
})
onUnmounted(() => {
if (petSystem) petSystem.stopTickLoop()
if (eventSystem) eventSystem.stopEventCheck()
})
</script>
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
* {
font-family: 'Press Start 2P', monospace;
image-rendering: pixelated;
}
.retro-container {
background: #000;
color: #0f0;
min-height: 100vh;
padding: 20px;
font-size: 12px;
line-height: 1.8;
}
.title-bar {
border: 2px solid #0f0;
padding: 10px;
margin-bottom: 20px;
text-align: center;
}
.title-text {
font-size: 16px;
margin-bottom: 10px;
}
.system-status {
font-size: 10px;
color: #0a0;
}
.game-area {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.stats-panel,
.command-panel,
.console-panel {
border: 2px solid #0f0;
padding: 15px;
}
.panel-title {
color: #0ff;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #0f0;
}
.stat-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.stat-name {
color: #0a0;
min-width: 80px;
}
.stat-value {
color: #fff;
}
.stat-bar-text {
color: #0ff;
font-size: 10px;
}
.divider {
color: #050;
margin: 10px 0;
font-size: 8px;
}
.status-flag {
color: #ff0;
margin-top: 5px;
animation: blink 1s infinite;
}
.status-flag.sick {
color: #f00;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}
.command-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.cmd-btn {
background: #000;
border: 2px solid #0f0;
color: #0f0;
padding: 10px;
cursor: pointer;
font-family: 'Press Start 2P', monospace;
font-size: 10px;
transition: all 0.1s;
}
.cmd-btn:hover:not(:disabled) {
background: #0f0;
color: #000;
}
.cmd-btn:active:not(:disabled) {
transform: translate(2px, 2px);
}
.cmd-btn:disabled {
border-color: #030;
color: #030;
cursor: not-allowed;
}
.console-panel {
grid-column: 1 / -1;
}
.console-log {
height: 150px;
overflow-y: auto;
background: #001100;
padding: 10px;
font-size: 10px;
}
.log-line {
margin-bottom: 5px;
color: #0c0;
}
.debug-toggle {
position: fixed;
top: 20px;
right: 20px;
background: #000;
border: 2px solid #f0f;
color: #f0f;
padding: 5px 10px;
cursor: pointer;
font-size: 10px;
}
.debug-panel {
position: fixed;
top: 60px;
right: 20px;
background: #000;
border: 2px solid #f0f;
padding: 15px;
max-width: 300px;
}
.debug-panel .cmd-btn {
width: 100%;
margin-bottom: 5px;
border-color: #f0f;
color: #f0f;
}
.name-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-box {
background: #000;
border: 3px solid #0ff;
padding: 30px;
min-width: 400px;
}
.name-input {
width: 100%;
background: #001100;
border: 2px solid #0f0;
color: #0f0;
padding: 10px;
font-family: 'Press Start 2P', monospace;
font-size: 12px;
margin: 20px 0;
}
.name-input:focus {
outline: none;
border-color: #0ff;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #000;
}
::-webkit-scrollbar-thumb {
background: #0f0;
border: 1px solid #000;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -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) {

View File

@ -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 }
}
}

View File

@ -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
}
}
}

View File

@ -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: '已重置祈福次數' }
}
}

View File

@ -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: [
'慈悲為懷,萬物皆靈',
'好好照顧,觀音會保佑',

View File

@ -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 } } },

63
data/fates.js Normal file
View File

@ -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
}
}
]

View File

@ -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: '配戴時增加力量和敏捷成長'
}
}

52
data/jiaobei-config.js Normal file
View File

@ -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次聖筊大吉
}
}

View File

@ -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: ['活潑', '黏人']
}

26
docs/BONUS_CHECK.md Normal file
View File

@ -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. 檢查戰鬥屬性計算
## 檢查結果
待補充...

197
docs/CONFIG_GUIDE.md Normal file
View File

@ -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 次** 即可!

209
docs/EVOLUTION_SYSTEM.md Normal file
View File

@ -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%)→ 更容易進化成法師
- **天乙貴人**(運勢高)→ 可能觸發特殊進化

156
docs/GAME_BALANCE.md Normal file
View File

@ -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 次**

118
docs/SLEEP_SYSTEM.md Normal file
View File

@ -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`

125
docs/STATS_BONUSES.md Normal file
View File

@ -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%

86
public/test-events.js Normal file
View File

@ -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