feat:merge health
This commit is contained in:
parent
ebe90953fe
commit
9b1d27186d
File diff suppressed because it is too large
Load Diff
|
|
@ -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>
|
||||||
1354
app/app.vue
1354
app/app.vue
File diff suppressed because it is too large
Load Diff
|
|
@ -17,9 +17,9 @@ export class ApiService {
|
||||||
// Mock 請求(使用本地資料)
|
// Mock 請求(使用本地資料)
|
||||||
async mockRequest(endpoint, options) {
|
async mockRequest(endpoint, options) {
|
||||||
await this.delay(this.mockDelay)
|
await this.delay(this.mockDelay)
|
||||||
|
|
||||||
const [resource, action] = endpoint.split('/').filter(Boolean)
|
const [resource, action] = endpoint.split('/').filter(Boolean)
|
||||||
|
|
||||||
switch (`${resource}/${action}`) {
|
switch (`${resource}/${action}`) {
|
||||||
case 'pet/state':
|
case 'pet/state':
|
||||||
return this.getMockPetState()
|
return this.getMockPetState()
|
||||||
|
|
@ -37,6 +37,8 @@ export class ApiService {
|
||||||
return this.mockPrayToDeity(options.body)
|
return this.mockPrayToDeity(options.body)
|
||||||
case 'fortune/draw':
|
case 'fortune/draw':
|
||||||
return this.mockDrawFortune()
|
return this.mockDrawFortune()
|
||||||
|
case 'temple/throw-jiaobei':
|
||||||
|
return { localFallback: true } // 使用本地邏輯
|
||||||
case 'items/list':
|
case 'items/list':
|
||||||
return this.getMockItems()
|
return this.getMockItems()
|
||||||
case 'items/use':
|
case 'items/use':
|
||||||
|
|
@ -66,7 +68,7 @@ export class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url, config)
|
const response = await fetch(url, config)
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`API Error: ${response.status} ${response.statusText}`)
|
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() {
|
async getItems() {
|
||||||
return this.request('items/list')
|
return this.request('items/list')
|
||||||
|
|
@ -158,7 +168,7 @@ export class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Mock 資料方法 ==========
|
// ========== Mock 資料方法 ==========
|
||||||
|
|
||||||
getMockPetState() {
|
getMockPetState() {
|
||||||
// 從 localStorage 或預設值讀取
|
// 從 localStorage 或預設值讀取
|
||||||
const stored = localStorage.getItem('petState')
|
const stored = localStorage.getItem('petState')
|
||||||
|
|
@ -214,7 +224,7 @@ export class ApiService {
|
||||||
const random = Math.random()
|
const random = Math.random()
|
||||||
let sum = 0
|
let sum = 0
|
||||||
let selectedGrade = '中'
|
let selectedGrade = '中'
|
||||||
|
|
||||||
for (let i = 0; i < weights.length; i++) {
|
for (let i = 0; i < weights.length; i++) {
|
||||||
sum += weights[i]
|
sum += weights[i]
|
||||||
if (random <= sum) {
|
if (random <= sum) {
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,17 @@ export class EventSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 啟動事件檢查循環(每 10 秒檢查一次)
|
// 啟動事件檢查循環(从配置读取间隔)
|
||||||
startEventCheck() {
|
startEventCheck() {
|
||||||
if (this.eventCheckInterval) this.stopEventCheck()
|
if (this.eventCheckInterval) this.stopEventCheck()
|
||||||
|
|
||||||
|
// 从配置读取间隔时间
|
||||||
|
const petConfig = this.petSystem.speciesConfig
|
||||||
|
const interval = petConfig?.baseStats?.eventCheckInterval || 10000
|
||||||
|
|
||||||
this.eventCheckInterval = setInterval(() => {
|
this.eventCheckInterval = setInterval(() => {
|
||||||
this.checkTriggers()
|
this.checkTriggers()
|
||||||
}, 10000) // 每 10 秒
|
}, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
stopEventCheck() {
|
stopEventCheck() {
|
||||||
|
|
@ -45,31 +49,34 @@ export class EventSystem {
|
||||||
selectRandomEvent() {
|
selectRandomEvent() {
|
||||||
const totalWeight = this.events.reduce((sum, e) => sum + e.weight, 0)
|
const totalWeight = this.events.reduce((sum, e) => sum + e.weight, 0)
|
||||||
let rand = Math.random() * totalWeight
|
let rand = Math.random() * totalWeight
|
||||||
|
|
||||||
for (const event of this.events) {
|
for (const event of this.events) {
|
||||||
rand -= event.weight
|
rand -= event.weight
|
||||||
if (rand <= 0) return event
|
if (rand <= 0) return event
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.events[0] // fallback
|
return this.events[0] // fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
// 檢查觸發條件(10% 機率 + 條件檢查)
|
// 檢查觸發條件(10% 機率 + 條件檢查)
|
||||||
async checkTriggers() {
|
async checkTriggers() {
|
||||||
const petState = this.petSystem.getState()
|
const petState = this.petSystem.getState()
|
||||||
|
|
||||||
if (petState.isDead) return
|
if (petState.isDead) return
|
||||||
|
|
||||||
|
// 睡眠時不觸發事件(類似暫停)
|
||||||
|
if (petState.isSleeping) return
|
||||||
|
|
||||||
// 10% 機率觸發
|
// 10% 機率觸發
|
||||||
if (Math.random() >= 0.1) return
|
if (Math.random() >= 0.1) return
|
||||||
|
|
||||||
const event = this.selectRandomEvent()
|
const event = this.selectRandomEvent()
|
||||||
|
|
||||||
// 檢查條件
|
// 檢查條件
|
||||||
if (event.condition && !event.condition(petState)) {
|
if (event.condition && !event.condition(petState)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 觸發事件
|
// 觸發事件
|
||||||
await this.triggerEvent(event.id, petState)
|
await this.triggerEvent(event.id, petState)
|
||||||
}
|
}
|
||||||
|
|
@ -83,10 +90,19 @@ export class EventSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentState = petState || this.petSystem.getState()
|
const currentState = petState || this.petSystem.getState()
|
||||||
|
|
||||||
// 檢查條件
|
// 檢查條件
|
||||||
if (event.condition && !event.condition(currentState)) {
|
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
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,20 +138,20 @@ export class EventSystem {
|
||||||
switch (effect.type) {
|
switch (effect.type) {
|
||||||
case 'modifyStats':
|
case 'modifyStats':
|
||||||
return await this.modifyStats(effect.payload, petState)
|
return await this.modifyStats(effect.payload, petState)
|
||||||
|
|
||||||
case 'addBuff':
|
case 'addBuff':
|
||||||
return await this.addBuff(effect.payload)
|
return await this.addBuff(effect.payload)
|
||||||
|
|
||||||
case 'spawnPoop':
|
case 'spawnPoop':
|
||||||
return await this.spawnPoop(effect.payload)
|
return await this.spawnPoop(effect.payload)
|
||||||
|
|
||||||
case 'templeFavor':
|
case 'templeFavor':
|
||||||
return await this.modifyTempleFavor(effect.payload)
|
return await this.modifyTempleFavor(effect.payload)
|
||||||
|
|
||||||
case 'logMessage':
|
case 'logMessage':
|
||||||
console.log(`[訊息] ${effect.payload}`)
|
console.log(`[訊息] ${effect.payload}`)
|
||||||
return { type: 'logMessage', message: effect.payload }
|
return { type: 'logMessage', message: effect.payload }
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn(`[EventSystem] 未知效果類型: ${effect.type}`)
|
console.warn(`[EventSystem] 未知效果類型: ${effect.type}`)
|
||||||
return null
|
return null
|
||||||
|
|
@ -144,19 +160,32 @@ export class EventSystem {
|
||||||
|
|
||||||
// 修改屬性
|
// 修改屬性
|
||||||
async modifyStats(payload, petState) {
|
async modifyStats(payload, petState) {
|
||||||
|
// 獲取最新狀態(因為 petState 可能是舊的快照)
|
||||||
|
const currentState = this.petSystem.getState()
|
||||||
const updates = {}
|
const updates = {}
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(payload)) {
|
for (const [key, value] of Object.entries(payload)) {
|
||||||
if (key === 'isSleeping' || key === 'isSick' || key === 'isDead') {
|
if (key === 'isSleeping' || key === 'isSick' || key === 'isDead') {
|
||||||
updates[key] = value
|
updates[key] = value
|
||||||
} else {
|
} else {
|
||||||
const current = petState[key] || 0
|
const current = currentState[key] || 0
|
||||||
const newValue = Math.max(0, Math.min(100, current + value))
|
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
|
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)
|
await this.petSystem.updateState(updates)
|
||||||
return { type: 'modifyStats', updates }
|
return { type: 'modifyStats', updates }
|
||||||
}
|
}
|
||||||
|
|
@ -164,14 +193,14 @@ export class EventSystem {
|
||||||
// 添加 Buff
|
// 添加 Buff
|
||||||
async addBuff(buffData) {
|
async addBuff(buffData) {
|
||||||
this.buffManager.addBuff(buffData)
|
this.buffManager.addBuff(buffData)
|
||||||
|
|
||||||
// 同步到 API
|
// 同步到 API
|
||||||
try {
|
try {
|
||||||
await this.api.applyBuff(buffData)
|
await this.api.applyBuff(buffData)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[EventSystem] Buff API 同步失敗:', error)
|
console.warn('[EventSystem] Buff API 同步失敗:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { type: 'addBuff', buff: buffData }
|
return { type: 'addBuff', buff: buffData }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,10 +209,10 @@ export class EventSystem {
|
||||||
const count = payload?.count || 1
|
const count = payload?.count || 1
|
||||||
const currentPoop = this.petSystem.getState().poopCount
|
const currentPoop = this.petSystem.getState().poopCount
|
||||||
const newPoop = Math.min(4, currentPoop + count)
|
const newPoop = Math.min(4, currentPoop + count)
|
||||||
|
|
||||||
await this.petSystem.updateState({ poopCount: newPoop })
|
await this.petSystem.updateState({ poopCount: newPoop })
|
||||||
console.log(`[效果] 生成便便: ${count} 個,總數: ${newPoop}`)
|
console.log(`[效果] 生成便便: ${count} 個,總數: ${newPoop}`)
|
||||||
|
|
||||||
return { type: 'spawnPoop', count, total: newPoop }
|
return { type: 'spawnPoop', count, total: newPoop }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,16 +222,16 @@ export class EventSystem {
|
||||||
const currentState = this.petSystem.getState()
|
const currentState = this.petSystem.getState()
|
||||||
const currentFavor = currentState.deityFavors[deityId] || 0
|
const currentFavor = currentState.deityFavors[deityId] || 0
|
||||||
const newFavor = Math.max(0, Math.min(100, currentFavor + amount))
|
const newFavor = Math.max(0, Math.min(100, currentFavor + amount))
|
||||||
|
|
||||||
await this.petSystem.updateState({
|
await this.petSystem.updateState({
|
||||||
deityFavors: {
|
deityFavors: {
|
||||||
...currentState.deityFavors,
|
...currentState.deityFavors,
|
||||||
[deityId]: newFavor
|
[deityId]: newFavor
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`[效果] ${deityId} 好感度 ${amount > 0 ? '+' : ''}${amount} → ${newFavor}`)
|
console.log(`[效果] ${deityId} 好感度 ${amount > 0 ? '+' : ''}${amount} → ${newFavor}`)
|
||||||
|
|
||||||
return { type: 'templeFavor', deityId, favor: newFavor }
|
return { type: 'templeFavor', deityId, favor: newFavor }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,24 +239,24 @@ export class EventSystem {
|
||||||
async applyBuffs() {
|
async applyBuffs() {
|
||||||
const petState = this.petSystem.getState()
|
const petState = this.petSystem.getState()
|
||||||
const buffs = this.buffManager.getActiveBuffs()
|
const buffs = this.buffManager.getActiveBuffs()
|
||||||
|
|
||||||
// 計算最終屬性(考慮 Buff)
|
// 計算最終屬性(考慮 Buff)
|
||||||
const finalStats = this.calculateFinalStats(petState, buffs)
|
const finalStats = this.calculateFinalStats(petState, buffs)
|
||||||
|
|
||||||
// 更新狀態
|
// 更新狀態
|
||||||
await this.petSystem.updateState(finalStats)
|
await this.petSystem.updateState(finalStats)
|
||||||
|
|
||||||
return finalStats
|
return finalStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// 計算最終屬性(Base + Flat + Percent)
|
// 計算最終屬性(Base + Flat + Percent)
|
||||||
calculateFinalStats(baseState, buffs) {
|
calculateFinalStats(baseState, buffs) {
|
||||||
const stats = { ...baseState }
|
const stats = { ...baseState }
|
||||||
|
|
||||||
// 收集所有 Buff 的 flat 和 percent 加成
|
// 收集所有 Buff 的 flat 和 percent 加成
|
||||||
const flatMods = {}
|
const flatMods = {}
|
||||||
const percentMods = {}
|
const percentMods = {}
|
||||||
|
|
||||||
buffs.forEach(buff => {
|
buffs.forEach(buff => {
|
||||||
if (buff.flat) {
|
if (buff.flat) {
|
||||||
Object.entries(buff.flat).forEach(([key, value]) => {
|
Object.entries(buff.flat).forEach(([key, value]) => {
|
||||||
|
|
@ -240,7 +269,7 @@ export class EventSystem {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 應用加成
|
// 應用加成
|
||||||
Object.keys(stats).forEach(key => {
|
Object.keys(stats).forEach(key => {
|
||||||
if (typeof stats[key] === 'number' && key !== 'ageSeconds' && key !== 'weight' && key !== 'generation') {
|
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)))
|
stats[key] = Math.max(0, Math.min(100, (base + flat) * (1 + percent)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,6 +292,24 @@ export class EventSystem {
|
||||||
getBuffManager() {
|
getBuffManager() {
|
||||||
return this.buffManager
|
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 管理器
|
// Buff 管理器
|
||||||
|
|
@ -274,7 +321,7 @@ class BuffManager {
|
||||||
addBuff(buff) {
|
addBuff(buff) {
|
||||||
// 檢查是否可堆疊
|
// 檢查是否可堆疊
|
||||||
const existing = this.buffs.find(b => b.id === buff.id)
|
const existing = this.buffs.find(b => b.id === buff.id)
|
||||||
|
|
||||||
if (existing && !buff.stacks) {
|
if (existing && !buff.stacks) {
|
||||||
// 不可堆疊,重置持續時間
|
// 不可堆疊,重置持續時間
|
||||||
existing.currentTicks = buff.durationTicks
|
existing.currentTicks = buff.durationTicks
|
||||||
|
|
@ -293,7 +340,7 @@ class BuffManager {
|
||||||
tick() {
|
tick() {
|
||||||
this.buffs = this.buffs.filter(b => {
|
this.buffs = this.buffs.filter(b => {
|
||||||
if (b.durationTicks === Infinity) return true // 永久 Buff
|
if (b.durationTicks === Infinity) return true // 永久 Buff
|
||||||
|
|
||||||
b.currentTicks--
|
b.currentTicks--
|
||||||
if (b.currentTicks <= 0) {
|
if (b.currentTicks <= 0) {
|
||||||
console.log(`[Buff] 過期: ${b.name}`)
|
console.log(`[Buff] 過期: ${b.name}`)
|
||||||
|
|
@ -311,12 +358,12 @@ class BuffManager {
|
||||||
const activeBuffs = this.getActiveBuffs()
|
const activeBuffs = this.getActiveBuffs()
|
||||||
let flatTotal = 0
|
let flatTotal = 0
|
||||||
let percentTotal = 0
|
let percentTotal = 0
|
||||||
|
|
||||||
activeBuffs.forEach(b => {
|
activeBuffs.forEach(b => {
|
||||||
if (b.flat?.[statKey]) flatTotal += b.flat[statKey]
|
if (b.flat?.[statKey]) flatTotal += b.flat[statKey]
|
||||||
if (b.percent?.[statKey]) percentTotal += b.percent[statKey]
|
if (b.percent?.[statKey]) percentTotal += b.percent[statKey]
|
||||||
})
|
})
|
||||||
|
|
||||||
return { flat: flatTotal, percent: percentTotal }
|
return { flat: flatTotal, percent: percentTotal }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// 寵物系統核心 - 與 API 整合
|
// 寵物系統核心 - 與 API 整合
|
||||||
import { apiService } from './api-service.js'
|
import { apiService } from './api-service.js'
|
||||||
import { PET_SPECIES } from '../data/pet-species.js'
|
import { PET_SPECIES } from '../data/pet-species.js'
|
||||||
|
import { FATES } from '../data/fates.js'
|
||||||
|
import { DEITIES } from '../data/deities.js'
|
||||||
|
|
||||||
export class PetSystem {
|
export class PetSystem {
|
||||||
constructor(api = apiService) {
|
constructor(api = apiService) {
|
||||||
|
|
@ -16,7 +18,7 @@ export class PetSystem {
|
||||||
try {
|
try {
|
||||||
// 從 API 載入現有狀態
|
// 從 API 載入現有狀態
|
||||||
this.state = await this.api.getPetState()
|
this.state = await this.api.getPetState()
|
||||||
|
|
||||||
if (!this.state) {
|
if (!this.state) {
|
||||||
// 創建新寵物
|
// 創建新寵物
|
||||||
this.state = this.createInitialState(speciesId)
|
this.state = this.createInitialState(speciesId)
|
||||||
|
|
@ -25,13 +27,18 @@ export class PetSystem {
|
||||||
|
|
||||||
// 載入種族配置
|
// 載入種族配置
|
||||||
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
|
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
|
||||||
|
|
||||||
|
// 計算戰鬥數值(在 speciesConfig 設置後)
|
||||||
|
this.calculateCombatStats()
|
||||||
|
|
||||||
return this.state
|
return this.state
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[PetSystem] 初始化失敗:', error)
|
console.error('[PetSystem] 初始化失敗:', error)
|
||||||
// 降級到本地狀態
|
// 降級到本地狀態
|
||||||
this.state = this.createInitialState(speciesId)
|
this.state = this.createInitialState(speciesId)
|
||||||
this.speciesConfig = PET_SPECIES[speciesId]
|
this.speciesConfig = PET_SPECIES[speciesId]
|
||||||
|
// 計算戰鬥數值
|
||||||
|
this.calculateCombatStats()
|
||||||
return this.state
|
return this.state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -39,22 +46,23 @@ export class PetSystem {
|
||||||
// 創建初始狀態
|
// 創建初始狀態
|
||||||
createInitialState(speciesId) {
|
createInitialState(speciesId) {
|
||||||
const config = PET_SPECIES[speciesId]
|
const config = PET_SPECIES[speciesId]
|
||||||
return {
|
const state = {
|
||||||
speciesId,
|
speciesId,
|
||||||
stage: 'baby',
|
stage: 'egg',
|
||||||
hunger: 100,
|
hunger: 100,
|
||||||
happiness: 100,
|
happiness: 100,
|
||||||
health: 100,
|
health: 100,
|
||||||
weight: 500,
|
weight: 500,
|
||||||
ageSeconds: 0,
|
ageSeconds: 0,
|
||||||
poopCount: 0,
|
poopCount: 0,
|
||||||
str: 0,
|
str: 10,
|
||||||
int: 0,
|
int: 10,
|
||||||
dex: 0,
|
dex: 10,
|
||||||
luck: config.baseStats.luck || 10,
|
luck: config.baseStats.luck || 10,
|
||||||
isSleeping: false,
|
isSleeping: false,
|
||||||
isSick: false,
|
isSick: false,
|
||||||
isDead: false,
|
isDead: false,
|
||||||
|
dyingSeconds: 0, // 瀕死計時
|
||||||
currentDeityId: 'mazu',
|
currentDeityId: 'mazu',
|
||||||
deityFavors: {
|
deityFavors: {
|
||||||
mazu: 0,
|
mazu: 0,
|
||||||
|
|
@ -70,18 +78,230 @@ export class PetSystem {
|
||||||
generation: 1,
|
generation: 1,
|
||||||
lastTickTime: Date.now()
|
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)
|
// 更新狀態(同步到 API)
|
||||||
async updateState(updates) {
|
async updateState(updates) {
|
||||||
this.state = { ...this.state, ...updates }
|
this.state = { ...this.state, ...updates }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.updatePetState(updates)
|
await this.api.updatePetState(updates)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[PetSystem] API 更新失敗,使用本地狀態:', error)
|
console.warn('[PetSystem] API 更新失敗,使用本地狀態:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.state
|
return this.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,14 +315,14 @@ export class PetSystem {
|
||||||
try {
|
try {
|
||||||
// 停止所有循環
|
// 停止所有循環
|
||||||
this.stopTickLoop()
|
this.stopTickLoop()
|
||||||
|
|
||||||
// 從 API 刪除
|
// 從 API 刪除
|
||||||
await this.api.deletePetState()
|
await this.api.deletePetState()
|
||||||
|
|
||||||
// 重置狀態
|
// 重置狀態
|
||||||
this.state = null
|
this.state = null
|
||||||
this.speciesConfig = null
|
this.speciesConfig = null
|
||||||
|
|
||||||
return { success: true, message: '寵物已刪除' }
|
return { success: true, message: '寵物已刪除' }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[PetSystem] 刪除寵物失敗:', error)
|
console.error('[PetSystem] 刪除寵物失敗:', error)
|
||||||
|
|
@ -113,14 +333,17 @@ export class PetSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick 循環(每 3 秒)
|
// Tick 循環(生理系统 - 从配置读取间隔)
|
||||||
startTickLoop(callback) {
|
startTickLoop(callback) {
|
||||||
if (this.tickInterval) this.stopTickLoop()
|
if (this.tickInterval) this.stopTickLoop()
|
||||||
|
|
||||||
|
// 从配置读取间隔时间
|
||||||
|
const interval = this.speciesConfig?.baseStats?.physiologyTickInterval || 60000
|
||||||
|
|
||||||
this.tickInterval = setInterval(async () => {
|
this.tickInterval = setInterval(async () => {
|
||||||
await this.tick()
|
await this.tick()
|
||||||
if (callback) callback(this.getState())
|
if (callback) callback(this.getState())
|
||||||
}, 3000)
|
}, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTickLoop() {
|
stopTickLoop() {
|
||||||
|
|
@ -144,143 +367,444 @@ export class PetSystem {
|
||||||
// 檢查階段轉換
|
// 檢查階段轉換
|
||||||
this.checkStageTransition()
|
this.checkStageTransition()
|
||||||
|
|
||||||
// 基礎數值衰減
|
// 檢查自動睡眠(基於真實時間)
|
||||||
|
if (this.isSystemEnabled('sleep')) {
|
||||||
|
const sleepPeriod = this.isInSleepTime()
|
||||||
|
if (sleepPeriod && !this.state.isSleeping) {
|
||||||
|
// 在睡眠時段且未睡覺,自動進入睡眠
|
||||||
|
this.state.isSleeping = true
|
||||||
|
const periodConfig = this.getSleepPeriod(sleepPeriod)
|
||||||
|
const periodName = sleepPeriod === 'nightSleep' ? '夜間睡眠' : '午休'
|
||||||
|
console.log(`😴 ${periodName}時間,寵物自動進入睡眠`)
|
||||||
|
} else if (!sleepPeriod && this.state.isSleeping && this.state._autoSlept) {
|
||||||
|
// 不在睡眠時段且是自動睡眠狀態,自動醒來
|
||||||
|
this.state.isSleeping = false
|
||||||
|
this.state._autoSlept = false
|
||||||
|
console.log(`⏰ 睡眠時間結束,寵物自動醒來`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根據當前階段配置決定啟用哪些系統(資料驅動)
|
||||||
const config = this.speciesConfig.baseStats
|
const 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) {
|
const sleepDecayMultiplier = this.state.isSleeping ? (config.sleepDecayMultiplier || 0.1) : 1.0
|
||||||
this.state.poopCount = Math.min(4, this.state.poopCount + 1)
|
|
||||||
|
// 獲取加成(用於檢查免疫)
|
||||||
|
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) {
|
if (this.isSystemEnabled('happiness')) {
|
||||||
this.state.isSick = true
|
this.state.happiness = Math.max(0, this.state.happiness - config.happinessDecayPerTick * sleepDecayMultiplier)
|
||||||
await this.updateState({ isSick: true })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 健康檢查
|
// 便便系統(睡覺時不會拉屎)
|
||||||
|
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) {
|
if (this.state.health <= 0) {
|
||||||
this.state.isDead = true
|
this.state.dyingSeconds = (this.state.dyingSeconds || 0) + deltaTime
|
||||||
await this.updateState({ isDead: true })
|
|
||||||
|
// 每 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
|
// 同步到 API
|
||||||
await this.updateState({
|
await this.updateState({
|
||||||
|
// 基礎屬性
|
||||||
ageSeconds: this.state.ageSeconds,
|
ageSeconds: this.state.ageSeconds,
|
||||||
|
stage: this.state.stage,
|
||||||
hunger: this.state.hunger,
|
hunger: this.state.hunger,
|
||||||
happiness: this.state.happiness,
|
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,
|
poopCount: this.state.poopCount,
|
||||||
|
isSleeping: this.state.isSleeping,
|
||||||
isSick: this.state.isSick,
|
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() {
|
checkStageTransition() {
|
||||||
|
if (!this.speciesConfig || !this.state) return
|
||||||
const config = this.speciesConfig
|
const config = this.speciesConfig
|
||||||
const currentStage = this.state.stage
|
const currentStage = this.state.stage
|
||||||
const ageSeconds = this.state.ageSeconds
|
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 < 0 || currentIndex >= config.lifecycle.length - 1) {
|
||||||
const currentIndex = config.lifecycle.findIndex(s => s.stage === currentStage)
|
return // 找不到或已是最終階段
|
||||||
if (currentIndex < config.lifecycle.length - 1) {
|
}
|
||||||
const nextStage = config.lifecycle[currentIndex + 1]
|
|
||||||
if (ageSeconds >= nextStage.durationSeconds && nextStage.durationSeconds > 0) {
|
const currentStageConfig = config.lifecycle[currentIndex]
|
||||||
this.state.stage = nextStage.stage
|
const nextStage = config.lifecycle[currentIndex + 1]
|
||||||
this.updateState({ stage: nextStage.stage })
|
|
||||||
}
|
// 檢查時間條件:年齡是否超過當前階段的持續時間
|
||||||
}
|
const timeConditionMet = ageSeconds >= currentStageConfig.durationSeconds && currentStageConfig.durationSeconds > 0
|
||||||
break
|
|
||||||
|
// 檢查屬性條件
|
||||||
|
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.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 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({
|
await this.updateState({
|
||||||
hunger: newHunger,
|
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) {
|
if (this.state.isDead || this.state.isSleeping) {
|
||||||
return { success: false, message: '寵物無法玩耍' }
|
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({
|
await this.updateState({
|
||||||
happiness: newHappiness,
|
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() {
|
async cleanPoop() {
|
||||||
|
if (!this.isActionAllowed('clean')) return { success: false, message: '當前階段不需要清理' }
|
||||||
if (this.state.poopCount === 0) {
|
if (this.state.poopCount === 0) {
|
||||||
return { success: false, message: '沒有便便需要清理' }
|
return { success: false, message: '沒有便便需要清理' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const newPoopCount = 0
|
const newPoopCount = 0
|
||||||
const newHappiness = Math.min(100, this.state.happiness + 10)
|
const newHappiness = Math.min(100, this.state.happiness + 10)
|
||||||
|
|
||||||
await this.updateState({
|
await this.updateState({
|
||||||
poopCount: newPoopCount,
|
poopCount: newPoopCount,
|
||||||
happiness: newHappiness
|
happiness: newHappiness
|
||||||
})
|
})
|
||||||
|
|
||||||
return { success: true, poopCount: newPoopCount, happiness: newHappiness }
|
return { success: true, poopCount: newPoopCount, happiness: newHappiness }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 治療
|
// 治療
|
||||||
async heal(amount = 20) {
|
async heal(amount = 20) {
|
||||||
|
if (!this.isActionAllowed('heal')) return { success: false, message: '當前階段不需要治療' }
|
||||||
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
|
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
|
||||||
|
if (this.state.isSleeping) return { success: false, message: '寵物正在睡覺,無法治療' }
|
||||||
const newHealth = Math.min(100, this.state.health + amount)
|
|
||||||
|
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
|
const wasSick = this.state.isSick
|
||||||
|
|
||||||
await this.updateState({
|
await this.updateState({
|
||||||
health: newHealth,
|
health: newHealth,
|
||||||
isSick: false
|
isSick: false
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
health: newHealth,
|
health: newHealth,
|
||||||
isSick: false,
|
isSick: false,
|
||||||
cured: wasSick && !this.state.isSick
|
cured: wasSick && !this.state.isSick,
|
||||||
|
healAmount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 睡覺/起床
|
// 睡覺/起床
|
||||||
async toggleSleep() {
|
async toggleSleep() {
|
||||||
|
if (!this.isActionAllowed('sleep')) return { success: false, message: '當前階段不能睡覺' }
|
||||||
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
|
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
|
||||||
|
|
||||||
const newIsSleeping = !this.state.isSleeping
|
const newIsSleeping = !this.state.isSleeping
|
||||||
const healthChange = newIsSleeping ? 5 : 0
|
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({
|
await this.updateState({
|
||||||
isSleeping: newIsSleeping,
|
isSleeping: willRandomSleep ? true : newIsSleeping,
|
||||||
health: Math.min(100, this.state.health + healthChange)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// 神明系統 - 與 API 整合
|
// 神明系統 - 與 API 整合
|
||||||
import { apiService } from './api-service.js'
|
import { apiService } from './api-service.js'
|
||||||
|
import { JIAOBEI_CONFIG } from '../data/jiaobei-config.js'
|
||||||
|
|
||||||
export class TempleSystem {
|
export class TempleSystem {
|
||||||
constructor(petSystem, api = apiService) {
|
constructor(petSystem, api = apiService) {
|
||||||
|
|
@ -40,13 +41,25 @@ export class TempleSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.petSystem.updateState({ currentDeityId: deityId })
|
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 }
|
return { success: true, deity }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 祈福(每日上限 3 次)
|
// 祈福(每日上限 3 次)
|
||||||
async pray() {
|
async pray() {
|
||||||
const state = this.petSystem.getState()
|
const state = this.petSystem.getState()
|
||||||
|
|
||||||
if (state.dailyPrayerCount >= 3) {
|
if (state.dailyPrayerCount >= 3) {
|
||||||
return { success: false, message: '今日祈福次數已用完' }
|
return { success: false, message: '今日祈福次數已用完' }
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +73,14 @@ export class TempleSystem {
|
||||||
// 更新好感度
|
// 更新好感度
|
||||||
const currentFavor = state.deityFavors[state.currentDeityId] || 0
|
const currentFavor = state.deityFavors[state.currentDeityId] || 0
|
||||||
const newFavor = Math.min(100, currentFavor + result.favorIncrease)
|
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({
|
await this.petSystem.updateState({
|
||||||
deityFavors: {
|
deityFavors: {
|
||||||
...state.deityFavors,
|
...state.deityFavors,
|
||||||
|
|
@ -69,16 +89,31 @@ export class TempleSystem {
|
||||||
dailyPrayerCount: state.dailyPrayerCount + 1
|
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)]
|
const dialogue = deity.dialogues[Math.floor(Math.random() * deity.dialogues.length)]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
favorIncrease: result.favorIncrease,
|
favorIncrease: result.favorIncrease,
|
||||||
newFavor,
|
newFavor,
|
||||||
|
oldLevel,
|
||||||
|
newLevel,
|
||||||
|
levelUp,
|
||||||
|
reachedMax,
|
||||||
dialogue,
|
dialogue,
|
||||||
message: result.message
|
message: result.message,
|
||||||
|
deity
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[TempleSystem] 祈福失敗:', error)
|
console.error('[TempleSystem] 祈福失敗:', error)
|
||||||
|
|
@ -98,11 +133,11 @@ export class TempleSystem {
|
||||||
async drawFortune() {
|
async drawFortune() {
|
||||||
try {
|
try {
|
||||||
const result = await this.api.drawFortune()
|
const result = await this.api.drawFortune()
|
||||||
|
|
||||||
// 根據籤詩等級應用效果
|
// 根據籤詩等級應用效果
|
||||||
const { FORTUNE_LOTS } = await import('../data/fortune-lots.js')
|
const { FORTUNE_LOTS } = await import('../data/fortune-lots.js')
|
||||||
const lot = FORTUNE_LOTS.find(l => l.grade === result.lot.grade) || FORTUNE_LOTS[0]
|
const lot = FORTUNE_LOTS.find(l => l.grade === result.lot.grade) || FORTUNE_LOTS[0]
|
||||||
|
|
||||||
if (lot.effects?.addBuff) {
|
if (lot.effects?.addBuff) {
|
||||||
// 應用 Buff(需要透過 eventSystem)
|
// 應用 Buff(需要透過 eventSystem)
|
||||||
return {
|
return {
|
||||||
|
|
@ -111,7 +146,7 @@ export class TempleSystem {
|
||||||
buff: lot.effects.addBuff
|
buff: lot.effects.addBuff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
lot: result.lot
|
lot: result.lot
|
||||||
|
|
@ -121,5 +156,137 @@ export class TempleSystem {
|
||||||
return { success: false, message: '抽籤失敗' }
|
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: '已重置祈福次數' }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,27 @@ export const DEITIES = [
|
||||||
happinessRecovery: 0.25
|
happinessRecovery: 0.25
|
||||||
},
|
},
|
||||||
buffDescriptions: ['小遊戲 +10%', '生病機率 -15%', '快樂恢復 +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: [
|
dialogues: [
|
||||||
'好孩子,媽祖保佑你平安喔',
|
'好孩子,媽祖保佑你平安喔',
|
||||||
'海上無風浪,心中有媽祖',
|
'海上無風浪,心中有媽祖',
|
||||||
|
|
@ -27,6 +48,25 @@ export const DEITIES = [
|
||||||
resourceGain: 0.15
|
resourceGain: 0.15
|
||||||
},
|
},
|
||||||
buffDescriptions: ['掉落率 +20%', '資源獲得 +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: [
|
dialogues: [
|
||||||
'土地公保佑,財源廣進',
|
'土地公保佑,財源廣進',
|
||||||
'好好照顧寵物,會有福報的',
|
'好好照顧寵物,會有福報的',
|
||||||
|
|
@ -43,6 +83,25 @@ export const DEITIES = [
|
||||||
breedingSuccess: 0.2
|
breedingSuccess: 0.2
|
||||||
},
|
},
|
||||||
buffDescriptions: ['快樂恢復 +30%', '繁殖成功率 +20%'],
|
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: [
|
dialogues: [
|
||||||
'有緣千里來相會',
|
'有緣千里來相會',
|
||||||
'好好對待寵物,感情會更深厚',
|
'好好對待寵物,感情會更深厚',
|
||||||
|
|
@ -59,6 +118,25 @@ export const DEITIES = [
|
||||||
miniGameBonus: 0.15
|
miniGameBonus: 0.15
|
||||||
},
|
},
|
||||||
buffDescriptions: ['智力成長 +25%', '小遊戲獎勵 +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: [
|
dialogues: [
|
||||||
'勤學不輟,智慧增長',
|
'勤學不輟,智慧增長',
|
||||||
'好好學習,寵物也會變聰明',
|
'好好學習,寵物也會變聰明',
|
||||||
|
|
@ -75,6 +153,25 @@ export const DEITIES = [
|
||||||
badEventReduction: 0.15
|
badEventReduction: 0.15
|
||||||
},
|
},
|
||||||
buffDescriptions: ['健康恢復 +20%', '壞事件機率 -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: [
|
dialogues: [
|
||||||
'慈悲為懷,萬物皆靈',
|
'慈悲為懷,萬物皆靈',
|
||||||
'好好照顧,觀音會保佑',
|
'好好照顧,觀音會保佑',
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export const EVENT_CONFIG = [
|
||||||
type: 'good',
|
type: 'good',
|
||||||
weight: 0.3,
|
weight: 0.3,
|
||||||
condition: (state) => state.happiness > 50 && state.luck > 10,
|
condition: (state) => state.happiness > 50 && state.luck > 10,
|
||||||
|
conditionDescription: '需要:快樂 > 50 且 運勢 > 10',
|
||||||
effects: [
|
effects: [
|
||||||
{ type: 'modifyStats', payload: { happiness: +10, luck: +5 } },
|
{ type: 'modifyStats', payload: { happiness: +10, luck: +5 } },
|
||||||
{ type: 'addBuff', payload: { id: 'fortune', name: '福運加持', durationTicks: 5, percent: { luck: 0.2 } } },
|
{ type: 'addBuff', payload: { id: 'fortune', name: '福運加持', durationTicks: 5, percent: { luck: 0.2 } } },
|
||||||
|
|
@ -17,6 +18,7 @@ export const EVENT_CONFIG = [
|
||||||
type: 'good',
|
type: 'good',
|
||||||
weight: 0.25,
|
weight: 0.25,
|
||||||
condition: (state) => state.hunger < 80,
|
condition: (state) => state.hunger < 80,
|
||||||
|
conditionDescription: '需要:飢餓 < 80',
|
||||||
effects: [
|
effects: [
|
||||||
{ type: 'modifyStats', payload: { hunger: +15, happiness: +5 } },
|
{ type: 'modifyStats', payload: { hunger: +15, happiness: +5 } },
|
||||||
{ type: 'logMessage', payload: '寵物發現了美味的點心!' }
|
{ type: 'logMessage', payload: '寵物發現了美味的點心!' }
|
||||||
|
|
@ -27,6 +29,7 @@ export const EVENT_CONFIG = [
|
||||||
type: 'good',
|
type: 'good',
|
||||||
weight: 0.2,
|
weight: 0.2,
|
||||||
condition: (state) => !state.isSleeping && state.happiness < 90,
|
condition: (state) => !state.isSleeping && state.happiness < 90,
|
||||||
|
conditionDescription: '需要:沒有睡覺 且 快樂 < 90',
|
||||||
effects: [
|
effects: [
|
||||||
{ type: 'modifyStats', payload: { happiness: +12, dex: +1 } },
|
{ type: 'modifyStats', payload: { happiness: +12, dex: +1 } },
|
||||||
{ type: 'logMessage', payload: '寵物玩得很開心,敏捷度提升了!' }
|
{ type: 'logMessage', payload: '寵物玩得很開心,敏捷度提升了!' }
|
||||||
|
|
@ -40,6 +43,7 @@ export const EVENT_CONFIG = [
|
||||||
const currentDeity = state.currentDeityId
|
const currentDeity = state.currentDeityId
|
||||||
return state.deityFavors[currentDeity] > 30
|
return state.deityFavors[currentDeity] > 30
|
||||||
},
|
},
|
||||||
|
conditionDescription: '需要:當前神明好感 > 30',
|
||||||
effects: [
|
effects: [
|
||||||
{ type: 'modifyStats', payload: { health: +10, happiness: +8 } },
|
{ type: 'modifyStats', payload: { health: +10, happiness: +8 } },
|
||||||
{ type: 'addBuff', payload: { id: 'blessing', name: '神明祝福', durationTicks: 10, percent: { health: 0.1 } } },
|
{ type: 'addBuff', payload: { id: 'blessing', name: '神明祝福', durationTicks: 10, percent: { health: 0.1 } } },
|
||||||
|
|
@ -64,6 +68,7 @@ export const EVENT_CONFIG = [
|
||||||
type: 'bad',
|
type: 'bad',
|
||||||
weight: 0.4,
|
weight: 0.4,
|
||||||
condition: (state) => state.weight > 600 && state.hunger < 20,
|
condition: (state) => state.weight > 600 && state.hunger < 20,
|
||||||
|
conditionDescription: '需要:體重 > 600 且 飢餓 < 20',
|
||||||
effects: [
|
effects: [
|
||||||
{ type: 'modifyStats', payload: { health: -15, weight: +20 } },
|
{ type: 'modifyStats', payload: { health: -15, weight: +20 } },
|
||||||
{ type: 'addBuff', payload: { id: 'hungry_penalty', name: '飢餓懲罰', durationTicks: 3, flat: { hungerDecay: 0.1 } } },
|
{ type: 'addBuff', payload: { id: 'hungry_penalty', name: '飢餓懲罰', durationTicks: 3, flat: { hungerDecay: 0.1 } } },
|
||||||
|
|
@ -75,6 +80,7 @@ export const EVENT_CONFIG = [
|
||||||
type: 'bad',
|
type: 'bad',
|
||||||
weight: 0.3,
|
weight: 0.3,
|
||||||
condition: (state) => state.health < 70 && !state.isSick,
|
condition: (state) => state.health < 70 && !state.isSick,
|
||||||
|
conditionDescription: '需要:健康 < 70 且 沒有生病',
|
||||||
effects: [
|
effects: [
|
||||||
{ type: 'modifyStats', payload: { health: -10, happiness: -5 } },
|
{ type: 'modifyStats', payload: { health: -10, happiness: -5 } },
|
||||||
{ type: 'addBuff', payload: { id: 'sick', name: '生病', durationTicks: 5, flat: { healthDecay: 0.05 } } },
|
{ type: 'addBuff', payload: { id: 'sick', name: '生病', durationTicks: 5, flat: { healthDecay: 0.05 } } },
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -26,14 +26,14 @@ export const ITEMS = {
|
||||||
name: '酷酷墨鏡',
|
name: '酷酷墨鏡',
|
||||||
type: 'equipment',
|
type: 'equipment',
|
||||||
effects: { addBuff: { id: 'cool', name: '酷炫', durationTicks: Infinity, percent: { happiness: 0.1 } } },
|
effects: { addBuff: { id: 'cool', name: '酷炫', durationTicks: Infinity, percent: { happiness: 0.1 } } },
|
||||||
description: '永久增加快樂 10%'
|
description: '配戴時增加快樂 10%'
|
||||||
},
|
},
|
||||||
training_manual: {
|
training_manual: {
|
||||||
id: 'training_manual',
|
id: 'training_manual',
|
||||||
name: '訓練手冊',
|
name: '訓練手冊',
|
||||||
type: 'equipment',
|
type: 'equipment',
|
||||||
effects: { addBuff: { id: 'training', name: '訓練加成', durationTicks: Infinity, percent: { str: 0.15, dex: 0.15 } } },
|
effects: { addBuff: { id: 'training', name: '訓練加成', durationTicks: Infinity, percent: { str: 0.15, dex: 0.15 } } },
|
||||||
description: '永久增加力量和敏捷成長'
|
description: '配戴時增加力量和敏捷成長'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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次聖筊(大吉)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,18 +5,156 @@ export const PET_SPECIES = {
|
||||||
name: '小虎斑貓',
|
name: '小虎斑貓',
|
||||||
description: '活潑可愛的小貓咪',
|
description: '活潑可愛的小貓咪',
|
||||||
baseStats: {
|
baseStats: {
|
||||||
hungerDecayPerTick: 0.05,
|
// 系統更新間隔(毫秒)
|
||||||
happinessDecayPerTick: 0.08,
|
physiologyTickInterval: 10000, // 生理系統刷新間隔:30秒
|
||||||
poopChancePerTick: 0.02,
|
eventCheckInterval: 10000, // 事件檢查間隔:10秒
|
||||||
sicknessChance: 0.01,
|
|
||||||
|
// 衰減速率 (每 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,
|
dropRate: 0.1,
|
||||||
luck: 10
|
luck: 10
|
||||||
},
|
},
|
||||||
lifecycle: [
|
lifecycle: [
|
||||||
{ stage: 'egg', durationSeconds: 0, unlockedFeatures: [] },
|
// durationSeconds 是該階段結束時的累積年齡(秒)
|
||||||
{ stage: 'baby', durationSeconds: 1800, unlockedFeatures: [] },
|
{
|
||||||
{ stage: 'child', durationSeconds: 3600, unlockedFeatures: ['miniGames'] },
|
stage: 'egg',
|
||||||
{ stage: 'adult', durationSeconds: Infinity, unlockedFeatures: ['miniGames', 'breeding'] }
|
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: ['活潑', '黏人']
|
personality: ['活潑', '黏人']
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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. 檢查戰鬥屬性計算
|
||||||
|
|
||||||
|
## 檢查結果
|
||||||
|
|
||||||
|
待補充...
|
||||||
|
|
@ -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 次** 即可!
|
||||||
|
|
@ -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%)→ 更容易進化成法師
|
||||||
|
- **天乙貴人**(運勢高)→ 可能觸發特殊進化
|
||||||
|
|
@ -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 次**
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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%
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue