pet_data/app/app.vue

3916 lines
106 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="pet-system-container">
<!-- 名字輸入對話框 -->
<div v-if="showNameInput" class="name-input-modal">
<div class="modal-content">
<h2> 為你的寵物命名</h2>
<p>請為你的新寵物取一個名字</p>
<input
v-model="inputPetName"
@keyup.enter="confirmName"
type="text"
placeholder="輸入寵物名字..."
maxlength="20"
class="name-input"
/>
<div class="modal-buttons">
<button @click="confirmName" :disabled="!inputPetName || inputPetName.trim().length === 0">
確認
</button>
</div>
</div>
</div>
<div class="header">
<h1>{{ petState?.name || 'LOADING...' }}</h1>
<p class="subtitle">{{ systemStatus }}</p>
</div>
<!-- 寵物狀態顯示 -->
<div class="pet-status" v-if="petState">
<!-- 基礎屬性 -->
<div class="status-section">
<h3>基礎屬性</h3>
<div class="status-grid">
<div class="stat-item">
<span class="stat-label">飢餓</span>
<div class="stat-bar">
<div class="stat-fill" :style="{ width: `${petState.hunger || 0}%` }"></div>
</div>
<span class="stat-value">{{ Math.round(petState.hunger || 0) }}</span>
</div>
<div class="stat-item">
<span class="stat-label">快樂</span>
<div class="stat-bar">
<div class="stat-fill" :style="{ width: `${petState.happiness || 0}%` }"></div>
</div>
<span class="stat-value">{{ Math.round(petState.happiness || 0) }}</span>
</div>
<div class="stat-item">
<span class="stat-label">健康</span>
<div class="stat-bar">
<div class="stat-fill" :style="{ width: `${(petState.health / getMaxHealth(petState)) * 100 || 0}%` }"></div>
</div>
<span class="stat-value">{{ Math.round(petState.health || 0) }}/{{ getMaxHealth(petState) }}</span>
</div>
</div>
</div>
<!-- 寵物資料 -->
<div class="status-section">
<h3>═ 寵物資料 ═</h3>
<div class="pet-info-grid">
<div class="info-item">
<span class="info-label">名字</span>
<span class="info-value">{{ petState.name || '未命名' }}</span>
</div>
<div class="info-item">
<span class="info-label">階段</span>
<span class="info-value">{{ getStageName(petState.stage) }}</span>
</div>
<div class="info-item">
<span class="info-label">年齡</span>
<span class="info-value">{{ formatAge(petState.ageSeconds) }}</span>
</div>
<div class="info-item">
<span class="info-label">身高</span>
<span class="info-value">{{ Math.round(petState.height || 0) }} cm</span>
</div>
<div class="info-item">
<span class="info-label">身高</span>
<span class="info-value">{{ Math.round(petState.height || 0) }} cm</span>
</div>
<div class="info-item">
<span class="info-label">體重</span>
<span class="info-value">{{ Math.round(petState.weight || 0) }} g</span>
</div>
<div class="info-item">
<span class="info-label">命格</span>
<div class="info-value-group">
<span class="info-value highlight">{{ petState.destiny ? petState.destiny.name : '無' }}</span>
<span v-if="petState.destiny" class="info-sub">{{ petState.destiny.description }}</span>
</div>
</div>
<div class="info-item">
<span class="info-label">力量 (STR)</span>
<span class="info-value">{{ Math.round(petState.effectiveStr || petState.str || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">智力 (INT)</span>
<span class="info-value">{{ Math.round(petState.effectiveInt || petState.int || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">敏捷 (DEX)</span>
<span class="info-value">{{ Math.round(petState.effectiveDex || petState.dex || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">運勢 (LUCK)</span>
<span class="info-value">{{ Math.round(petState.effectiveLuck || petState.luck || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">世代</span>
<span class="info-value">第 {{ petState.generation || 1 }} 代</span>
</div>
<div class="info-item">
<span class="info-label">神明好感</span>
<span class="info-value">{{ getDeityFavorDisplay(petState) }}</span>
</div>
<div class="info-item">
<span class="info-label">HP</span>
<span class="info-value">{{ Math.round(petState.hp || petState.health || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">攻擊</span>
<span class="info-value">{{ Math.round(petState.attack || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">防禦</span>
<span class="info-value">{{ Math.round(petState.defense || 0) }}</span>
</div>
<div class="info-item">
<span class="info-label">速度</span>
<span class="info-value">{{ Math.round(petState.speed || 0) }}</span>
</div>
</div>
</div>
<div class="status-section">
<h3>═ 加成與隱藏數值 ═</h3>
<div class="bonus-grid">
<div
class="bonus-item"
v-for="(value, key) in getAllBonuses(petState)"
:key="key"
:class="{ 'not-implemented': !isImplemented(key) }"
>
<span class="bonus-label">{{ getBonusName(key) }}</span>
<span class="bonus-value" :class="{ positive: value > 0, negative: value < 0 }">
{{ value > 0 ? '+' : '' }}{{ value }}
</span>
</div>
</div>
</div>
<!-- 狀態標籤 -->
<div class="status-badges">
<span v-if="petState.isSleeping" class="badge">[SLEEP] 睡覺中</span>
<span v-if="petState.isSick" class="badge sick">[SICK] 生病</span>
<span v-if="petState.poopCount > 0" class="badge">[POOP] 便便 x{{ petState.poopCount }}</span>
</div>
<!-- 成就系統 -->
<div class="status-section">
<h3>═ 成就系統 ═</h3>
<div class="achievement-summary">
<div class="achievement-stats">
<span>已解鎖: {{ unlockedAchievementsCount }}/{{ totalAchievementsCount }}</span>
<span>進度: {{ achievementProgress }}%</span>
</div>
</div>
<details class="achievement-list">
<summary>查看成就列表 ({{ unlockedAchievementsCount }}/{{ totalAchievementsCount }})</summary>
<div class="achievement-grid">
<div
v-for="achievement in allAchievements"
:key="achievement.id"
class="achievement-item"
:class="{ unlocked: isAchievementUnlocked(achievement.id), locked: !isAchievementUnlocked(achievement.id) }"
>
<div class="achievement-icon">{{ achievement.icon || '🏆' }}</div>
<div class="achievement-info">
<div class="achievement-name">{{ achievement.name }}</div>
<div class="achievement-desc">{{ achievement.description }}</div>
<div v-if="!isAchievementUnlocked(achievement.id)" class="achievement-progress">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: getAchievementProgressPercent(achievement) + '%' }"></div>
</div>
<span class="progress-text">{{ getAchievementProgressText(achievement) }}</span>
</div>
<div v-if="isAchievementUnlocked(achievement.id) && achievement.reward" class="achievement-reward">
<span class="reward-label">獎勵:</span>
<span v-for="(value, key) in achievement.reward.buffs" :key="key" class="reward-item">
{{ getBonusName(key) }}{{ formatBonusValue(value) }}
</span>
</div>
</div>
</div>
</div>
</details>
</div>
<!-- 背包系統 -->
<div class="status-section">
<h3>═ 背包系統 ═</h3>
<!-- 裝備槽位 -->
<div class="equipment-slots">
<h4>裝備槽位</h4>
<div class="rarity-legend">
<div class="legend-title">稀有度說明:</div>
<div class="legend-items">
<span
v-for="(rarityData, rarityKey) in itemRarity"
:key="rarityKey"
class="legend-item"
:class="`rarity-${rarityKey}`"
>
{{ rarityData.name }} (掉落: {{ (rarityData.dropRate * 100).toFixed(0) }}%)
</span>
</div>
<div class="legend-note">邊框顏色代表稀有度,數字為基礎掉落機率</div>
</div>
<div class="slots-grid">
<div
v-for="(slotName, slotKey) in equipmentSlots"
:key="slotKey"
class="equipment-slot-container"
>
<div class="slot-header">{{ slotName.name }} {{ slotName.icon }}</div>
<!-- 實際裝備槽位 -->
<div class="equipment-slot equipment-slot-main" :class="{ equipped: hasEquipped(slotKey, 'equipment') }">
<div class="slot-label">實際裝備</div>
<div v-if="hasEquipped(slotKey, 'equipment')" class="slot-item">
<span class="item-icon">{{ getEquippedItemIcon(slotKey, 'equipment') }}</span>
<span class="item-name">{{ getEquippedItemName(slotKey, 'equipment') }}</span>
<button
@click="handleUnequip(slotKey, 'equipment')"
class="unequip-btn"
title="卸下實際裝備"
>
</button>
</div>
<div v-else class="slot-empty">空</div>
</div>
<!-- 外觀套件槽位 -->
<div class="equipment-slot equipment-slot-appearance" :class="{ equipped: hasEquipped(slotKey, 'appearance') }">
<div class="slot-label">外觀套件</div>
<div v-if="hasEquipped(slotKey, 'appearance')" class="slot-item">
<span class="item-icon">{{ getEquippedItemIcon(slotKey, 'appearance') }}</span>
<span class="item-name">{{ getEquippedItemName(slotKey, 'appearance') }}</span>
<button
@click="handleUnequip(slotKey, 'appearance')"
class="unequip-btn"
title="卸下外觀套件"
>
</button>
</div>
<div v-else class="slot-empty">空</div>
</div>
</div>
</div>
</div>
<!-- 背包道具列表 -->
<details class="inventory-list">
<summary>查看背包 ({{ inventoryItemCount }} 種道具)</summary>
<div class="inventory-grid">
<div
v-for="(itemData, itemId) in inventoryItems"
:key="itemId"
class="inventory-item"
:class="getRarityClass(itemData.item.rarity)"
@mouseenter="showItemTooltip($event, itemData.item, itemId, itemData.instance)"
@mouseleave="hideItemTooltip"
>
<div class="item-header">
<span class="item-icon">{{ itemData.item.icon || '📦' }}</span>
<span class="item-name" :style="{ color: getRarityColor(itemData.item.rarity) }">
{{ itemData.item.name }}
</span>
<span v-if="itemData.count > 1" class="item-count">x{{ itemData.count }}</span>
</div>
<div class="item-info">
<div class="item-rarity" :style="{ color: getRarityColor(itemData.item.rarity) }">
{{ getRarityName(itemData.item.rarity) }}
<span v-if="itemData.item.dropRate !== undefined" class="drop-rate">
(掉落: {{ (itemData.item.dropRate * 100).toFixed(0) }}%)
</span>
</div>
<div class="item-type">{{ itemTypeNames[itemData.item.type] || itemData.item.type }}</div>
<div class="item-description">{{ itemData.item.description }}</div>
<!-- 道具效果說明 -->
<div v-if="itemData.item.effects" class="item-effects">
<div class="effects-title">效果:</div>
<div v-if="itemData.item.effects.flat" class="effects-flat">
<span v-for="(value, key) in itemData.item.effects.flat" :key="key" class="effect-item">
{{ getStatName(key) }}{{ formatStatValue(value) }}
</span>
</div>
<div v-if="itemData.item.effects.percent" class="effects-percent">
<span v-for="(value, key) in itemData.item.effects.percent" :key="key" class="effect-item">
{{ getStatName(key) }}{{ formatPercentValue(value) }}
</span>
</div>
</div>
<div v-if="itemData.instance && itemData.instance.durability !== undefined && itemData.instance.durability !== Infinity" class="item-durability">
耐久度: {{ itemData.instance.durability }}/{{ itemData.item.maxDurability }}
</div>
</div>
<div class="item-actions">
<button
v-if="itemData.item.type === 'consumable'"
@click="handleUseItem(itemId)"
class="action-btn use-btn"
:disabled="!inventorySystem || itemData.count < 1"
>
使用
</button>
<button
v-if="canEquip(itemData.item) && itemData.item.type !== 'appearance'"
@click="handleEquip(itemId, itemData.item.slot, 'equipment')"
class="action-btn equip-btn"
:disabled="!inventorySystem"
title="裝備為實際裝備(有數值效果)"
>
裝備(實際)
</button>
<button
v-if="canEquip(itemData.item) && itemData.item.type === 'appearance'"
@click="handleEquip(itemId, itemData.item.slot, 'appearance')"
class="action-btn equip-btn appearance-btn"
:disabled="!inventorySystem"
title="裝備為外觀套件(只改變外觀)"
>
裝備(外觀)
</button>
<button
v-if="itemData.item.type === 'equipment' && itemData.item.durability !== Infinity"
@click="handleRepair(itemId)"
class="action-btn repair-btn"
:disabled="!inventorySystem || getItemDurability(itemId) >= itemData.item.maxDurability"
>
修復
</button>
</div>
<!-- 刪除區域 -->
<div class="item-delete-section">
<div class="delete-controls">
<label class="delete-label">刪除數量:</label>
<input
type="number"
:min="1"
:max="itemData.count"
v-model.number="deleteCounts[itemId]"
class="delete-input"
@input="updateDeleteCount(itemId, $event.target.value, itemData.count)"
/>
<span class="delete-max">/ {{ itemData.count }}</span>
</div>
<button
@click="handleDeleteItem(itemId, deleteCounts[itemId] || 1)"
class="action-btn delete-btn"
:disabled="!inventorySystem || !deleteCounts[itemId] || deleteCounts[itemId] < 1"
:title="`刪除 ${deleteCounts[itemId] || 1} 個 ${itemData.item.name}`"
>
刪除
</button>
</div>
</div>
<div v-if="inventoryItemCount === 0" class="inventory-empty">
背包是空的
</div>
</div>
</details>
</div>
</div>
<!-- 道具 Tooltip -->
<div
v-if="itemTooltip && tooltipContent"
class="item-tooltip"
:style="{ left: tooltipPosition.x + 'px', top: tooltipPosition.y + 'px' }"
>
<div class="tooltip-header">
<span class="tooltip-icon">{{ tooltipContent.icon }}</span>
<span class="tooltip-name" :style="{ color: tooltipContent.rarityColor }">
{{ tooltipContent.name }}
</span>
</div>
<div class="tooltip-rarity" :style="{ color: tooltipContent.rarityColor }">
{{ tooltipContent.rarity }} (掉落: {{ tooltipContent.dropRate }}%)
</div>
<div class="tooltip-type">{{ tooltipContent.type }}</div>
<div class="tooltip-description">{{ tooltipContent.description }}</div>
<div v-if="tooltipContent.effects.length > 0" class="tooltip-effects">
<div class="tooltip-effects-title">效果:</div>
<div v-for="(effect, idx) in tooltipContent.effects" :key="idx" class="tooltip-effect-item">
{{ effect.stat }}{{ effect.value }}
</div>
</div>
<div v-if="tooltipContent.durability" class="tooltip-durability">
耐久度: {{ tooltipContent.durability.current }}/{{ tooltipContent.durability.max }}
</div>
</div>
<!-- 操作按鈕區 -->
<div class="action-panel">
<div class="action-group">
<h3> 寵物互動</h3>
<div class="button-grid">
<button @click="handleFeed"
:disabled="!canDoAction('feed')"
:title="!canDoAction('feed') ? getDisabledReason('feed') : '增加飢餓值 (+體重)'">
[FEED] 餵食
</button>
<!-- 玩耍選項 -->
<div class="play-group" style="grid-column: span 2; display: flex; gap: 5px;">
<button @click="handlePlay('normal')"
:disabled="!canDoAction('play')"
:title="!canDoAction('play') ? getDisabledReason('play') : '一般玩耍 (體重 -1g)'"
style="flex: 1;">
[PLAY] 玩耍
</button>
<button @click="handlePlay('training')"
:disabled="!canDoAction('play')"
:title="!canDoAction('play') ? getDisabledReason('play') : '體能訓練 (體重 -3g)'"
style="flex: 1;">
[TRAIN] 訓練
</button>
<button @click="handlePlay('puzzle')"
:disabled="!canDoAction('play')"
:title="!canDoAction('play') ? getDisabledReason('play') : '益智遊戲 (體重不變)'"
style="flex: 1;">
[PUZZLE] 益智
</button>
</div>
<button @click="handleClean"
:disabled="!canDoAction('clean')"
:title="!canDoAction('clean') ? getDisabledReason('clean') : '清理便便'">
[CLEAN] 清理
</button>
<button @click="handleHeal"
:disabled="!canDoAction('heal')"
:title="!canDoAction('heal') ? getDisabledReason('heal') : '恢復健康'">
[HEAL] 治療
</button>
<button @click="handleSleep"
:disabled="!canDoAction('sleep')"
:title="!canDoAction('sleep') ? getDisabledReason('sleep') : '睡覺恢復健康'">
{{ petState?.isSleeping ? '[WAKE] 起床' : '[SLEEP] 睡覺' }}
</button>
<button @click="handleShowStatus" :disabled="!isReady">[STATUS] 查看狀態</button>
<button @click="handleCheckEvolution" :disabled="!isReady" title="檢查進化條件">[CHECK] 進化檢查</button>
<button @click="handleDeletePet" :disabled="!isReady" class="danger-button">[DEL] 刪除寵物</button>
</div>
</div>
<div class="action-group">
<h3>[ROLL] 事件系統</h3>
<div class="button-grid">
<button @click="handleStart" :disabled="!isReady || isRunning">[START] 啟動循環</button>
<button @click="handleStop" :disabled="!isRunning">[STOP] 停止循環</button>
<button @click="handleListEvents" :disabled="!isReady">[LIST] 事件列表</button>
<button @click="handleEventHistory" :disabled="!isReady">[HISTORY] 事件歷史</button>
<button @click="handleTestAllEvents" :disabled="!isReady">[TEST] 測試所有事件</button>
</div>
<!-- 快速觸發事件 -->
<details class="event-list">
<summary>* 快速觸發事件</summary>
<div class="button-grid small">
<button @click="handleTriggerEvent('lucky_find')" :disabled="!isReady">幸運發現</button>
<button @click="handleTriggerEvent('find_treat')" :disabled="!isReady">發現點心</button>
<button @click="handleTriggerEvent('playful_moment')" :disabled="!isReady">玩耍時光</button>
<button @click="handleTriggerEvent('deity_blessing')" :disabled="!isReady">神明祝福</button>
<button @click="handleTriggerEvent('over_eat')" :disabled="!isReady">吃太多</button>
<button @click="handleTriggerEvent('catch_cold')" :disabled="!isReady">感冒</button>
</div>
</details>
</div>
<div class="action-group">
<h3>[PRAY] 神明系統</h3>
<div class="button-grid">
<button @click="handlePray" :disabled="!isReady">[PRAY] 祈福</button>
<!-- 若神明有多個籤詩類型,顯示下拉選單 -->
<div v-if="deityLotTypes.length > 1" class="lot-type-select" style="margin: 5px 0;">
<label for="lotTypeSelect" style="color: #00ffff; margin-right: 4px;">籤詩類型:</label>
<select id="lotTypeSelect" v-model="selectedLotType" style="background: #111; color: #00ff00; border: 1px solid #00ffff;">
<option v-for="type in deityLotTypes" :key="type" :value="type">{{ type }}</option>
</select>
</div>
<button @click="handleDrawLot" :disabled="!isReady">[LOT] 求籤</button>
<button @click="handleVerifyLot" :disabled="!isReady">[VERIFY] 驗證</button>
<button @click="handleThrowJiaobei" :disabled="!isReady">[JIAOBEI] 擲筊</button>
<button @click="handleListDeities" :disabled="!isReady">[LIST] 神明列表</button>
</div>
<!-- 切換神明 -->
<details class="deity-list">
<summary>[SWITCH] 切換神明</summary>
<div class="button-grid small">
<button @click="handleSwitchDeity('mazu')" :disabled="!isReady"
:class="{ active: petState?.currentDeityId === 'mazu' }">媽祖</button>
<button @click="handleSwitchDeity('earthgod')" :disabled="!isReady"
:class="{ active: petState?.currentDeityId === 'earthgod' }">土地公</button>
<button @click="handleSwitchDeity('yuelao')" :disabled="!isReady"
:class="{ active: petState?.currentDeityId === 'yuelao' }">月老</button>
<button @click="handleSwitchDeity('wenchang')" :disabled="!isReady"
:class="{ active: petState?.currentDeityId === 'wenchang' }">文昌</button>
<button @click="handleSwitchDeity('guanyin')" :disabled="!isReady"
:class="{ active: petState?.currentDeityId === 'guanyin' }">觀音</button>
</div>
</details>
</div>
</div>
<!-- Debug 面板 -->
<div class="debug-toggle" @click="showDebug = !showDebug">[DEBUG]</div>
<div v-if="showDebug" class="debug-panel">
<h3>[TOOLS] Debug 工具</h3>
<div class="debug-group">
<h4>神明系統</h4>
<button @click="debugResetPrayer">重置祈福次數</button>
<button @click="debugAddFavor(10)">好感度 +10</button>
<button @click="debugAddFavor(50)">好感度 +50</button>
</div>
<div class="debug-group">
<h4>事件系統</h4>
<button @click="debugTriggerEvent">隨機觸發事件</button>
</div>
<div class="debug-group">
<h4>寵物狀態</h4>
<button @click="debugFillStats">填滿飢餓/快樂</button>
<button @click="debugMaxAttributes">屬性全滿 (100)</button>
<button @click="debugAddPoop">[POOP] 便便 x4</button>
<button @click="debugKill">一鍵瀕死</button>
</div>
<div class="debug-group">
<h4>成就系統</h4>
<input
v-model="debugAchievementId"
type="text"
placeholder="輸入成就 ID..."
class="debug-input"
@keyup.enter="debugUnlockAchievement"
/>
<button @click="debugUnlockAchievement">解鎖成就 (ID)</button>
<button @click="debugListAchievements">查看成就列表</button>
<button @click="debugUnlockAllAchievements">解鎖全部成就</button>
<button @click="debugResetAchievements">重置成就</button>
</div>
<div class="debug-group">
<h4>背包系統</h4>
<button @click="debugAddRandomItem" style="margin-bottom: 8px;">🎲 隨機產生道具</button>
<div style="margin-top: 8px; margin-bottom: 8px;">
<div style="font-size: 10px; color: #888; margin-bottom: 4px;">按類型:</div>
<div style="display: flex; flex-direction: column; gap: 4px;">
<button @click="debugAddItemByType('equipment')">⚔️ 產生裝備類</button>
<button @click="debugAddItemByType('appearance')">👕 產生外觀類</button>
<button @click="debugAddItemByType('consumable')">🍪 產生消耗品</button>
<button @click="debugAddItemByType('talisman')">🔮 產生護身符</button>
<button @click="debugAddItemByType('special')">⭐ 產生特殊道具</button>
</div>
</div>
<div style="margin-top: 8px;">
<div style="font-size: 10px; color: #888; margin-bottom: 4px;">按類別:</div>
<div style="display: flex; flex-direction: column; gap: 4px;">
<button @click="debugAddItemByCategory('weapon')">🗡️ 產生武器</button>
<button @click="debugAddItemByCategory('armor')">🛡️ 產生防具</button>
<button @click="debugAddItemByCategory('food')">🍪 產生食物</button>
<button @click="debugAddItemByCategory('potion')">🧪 產生藥水</button>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
/* Debug 面板樣式 */
.debug-toggle {
position: fixed;
bottom: 10px;
right: 10px;
width: 40px;
height: 40px;
background: rgba(0, 0, 0, 0.5);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 9999;
font-size: 20px;
}
.debug-panel {
position: fixed;
bottom: 60px;
right: 10px;
background: rgba(0, 0, 0, 0.95);
color: white;
padding: 15px;
border-radius: 10px;
z-index: 9999;
width: 280px;
max-height: calc(100vh - 80px);
overflow-y: auto;
overflow-x: hidden;
border: 2px solid #ff00ff;
box-shadow: 0 0 20px rgba(255, 0, 255, 0.5);
}
/* Debug 面板滾動條樣式 */
.debug-panel::-webkit-scrollbar {
width: 8px;
}
.debug-panel::-webkit-scrollbar-track {
background: rgba(255, 0, 255, 0.1);
border-radius: 4px;
}
.debug-panel::-webkit-scrollbar-thumb {
background: rgba(255, 0, 255, 0.5);
border-radius: 4px;
}
.debug-panel::-webkit-scrollbar-thumb:hover {
background: rgba(255, 0, 255, 0.8);
}
.debug-panel h3 {
margin: 0 0 10px 0;
font-size: 16px;
border-bottom: 1px solid #444;
padding-bottom: 5px;
}
.debug-group {
margin-bottom: 15px;
}
.debug-group h4 {
margin: 0 0 5px 0;
font-size: 12px;
color: #aaa;
}
.debug-panel button {
display: block;
width: 100%;
margin-bottom: 5px;
padding: 5px;
background: #333;
border: 1px solid #555;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.debug-panel button:hover {
background: #555;
}
</style>
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { PetSystem } from '../core/pet-system.js'
import { EventSystem } from '../core/event-system.js'
import { TempleSystem } from '../core/temple-system.js'
import { AchievementSystem } from '../core/achievement-system.js'
import { InventorySystem } from '../core/inventory-system.js'
import { ApiService } from '../core/api-service.js'
// 創建 API 服務
const apiService = new ApiService({
useMock: true,
baseUrl: 'http://localhost:3000/api',
mockDelay: 100
})
// 響應式狀態
const petState = ref(null)
const systemStatus = ref('正在初始化...')
const isReady = ref(false)
const isRunning = ref(false)
const showNameInput = ref(false)
const inputPetName = ref('')
const deityLotTypes = ref([])
const selectedLotType = ref('')
// 系統實例
let petSystem, eventSystem, templeSystem, achievementSystem, inventorySystem
// 更新狀態顯示
function updatePetState() {
if (petSystem) {
petState.value = petSystem.getState()
}
}
// 檢查當前階段是否可以執行某個動作
function canDoAction(action) {
if (!isReady.value || !petSystem) return false
return petSystem.isActionAllowed(action)
}
// 獲取按鈕禁用的原因
function getDisabledReason(action) {
if (!isReady.value || !petSystem) return '系統尚未就緒'
if (!petSystem.isActionAllowed(action)) {
const stage = petState.value?.stage || 'unknown'
const stageNames = {
'egg': '蛋階段',
'baby': '幼體',
'child': '幼年',
'adult': '成年'
}
return `${stageNames[stage] || stage}不能執行此操作`
}
return ''
}
// 格式化年齡
function formatAge(ageSeconds) {
if (!ageSeconds) return '0 秒'
const days = Math.floor(ageSeconds / 86400)
const hours = Math.floor((ageSeconds % 86400) / 3600)
const minutes = Math.floor((ageSeconds % 3600) / 60)
const seconds = Math.floor(ageSeconds % 60)
if (days > 0) return `${days}${hours} 小時`
if (hours > 0) return `${hours} 小時 ${minutes} 分鐘`
if (minutes > 0) return `${minutes} 分鐘 ${seconds}`
return `${seconds}`
}
// 獲取階段名稱
function getStageName(stage) {
const stageNames = {
'egg': '蛋',
'baby': '幼體',
'child': '幼年',
'adult': '成年'
}
return stageNames[stage] || stage
}
// 獲取神明好感顯示
function getDeityFavorDisplay(state) {
if (!state || !state.currentDeityId) return '無'
const favor = state.deityFavors?.[state.currentDeityId] || 0
const deityNames = {
'mazu': '媽祖',
'earthgod': '土地公',
'yuelao': '月老',
'wenchang': '文昌',
'guanyin': '觀音'
}
return `${deityNames[state.currentDeityId] || state.currentDeityId}: ${favor}/100`
}
// 獲取所有加成
// 獲取所有加成
function getAllBonuses(state) {
if (!state) return {}
const bonuses = {}
// 1. 命格加成
if (state.destiny && state.destiny.buffs) {
for (const [key, value] of Object.entries(state.destiny.buffs)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
// 獲取當前神明(如果在神明系統範圍內)
let currentDeity = null
if (templeSystem) {
currentDeity = templeSystem.getCurrentDeity()
}
// 2. 神明基礎加成
if (currentDeity && currentDeity.buffs) {
for (const [key, value] of Object.entries(currentDeity.buffs)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
// 3. 臨時 Buff (需要從 eventSystem 獲取)
if (eventSystem) {
const activeBuffs = eventSystem.getBuffManager().getActiveBuffs()
for (const buff of activeBuffs) {
if (buff.effects) {
for (const [key, value] of Object.entries(buff.effects)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
}
}
// 4. 神明好感度等級加成
if (currentDeity && currentDeity.favorLevelBuffs) {
const favor = state.deityFavors?.[state.currentDeityId] || 0
const level = Math.floor(favor / currentDeity.favorLevelBuffs.interval)
if (level > 0 && currentDeity.favorLevelBuffs.buffsPerLevel) {
for (const [key, valuePerLevel] of Object.entries(currentDeity.favorLevelBuffs.buffsPerLevel)) {
bonuses[key] = (bonuses[key] || 0) + (valuePerLevel * level)
}
}
}
// 5. 神明滿級特殊 Buff
if (currentDeity && currentDeity.maxFavorBuff) {
const favor = state.deityFavors?.[state.currentDeityId] || 0
if (favor >= 100 && currentDeity.maxFavorBuff.effects) {
for (const [key, value] of Object.entries(currentDeity.maxFavorBuff.effects)) {
// 排除非數值效果(如 sicknessImmune
if (typeof value === 'number') {
bonuses[key] = (bonuses[key] || 0) + value
}
}
}
}
// 6. 成就加成
if (state.achievementBuffs) {
for (const [key, value] of Object.entries(state.achievementBuffs)) {
bonuses[key] = (bonuses[key] || 0) + value
}
}
return bonuses
}
// 检查加成是否已实现
function isImplemented(key) {
const implemented = [
'str', 'int', 'dex', 'luck', 'health',
'strGain', 'intGain', 'dexGain',
'happinessRecovery', 'healthRecovery'
]
return implemented.includes(key)
}
// 獲取加成名稱
function getBonusName(key) {
const names = {
// 基礎屬性(影响战斗数值)
str: '力量',
int: '智力',
dex: '敏捷',
luck: '運勢',
health: '最大健康',
// 成長效率
strGain: '力量成長',
intGain: '智力成長',
dexGain: '敏捷成長',
// 状态恢复
happinessRecovery: '快樂恢復',
healthRecovery: '健康恢復',
// 未实现的加成
gameSuccessRate: '小遊戲成功率',
sicknessReduction: '生病抗性',
resourceGain: '資源獲得',
breedingSuccess: '繁殖成功率',
dropRate: '掉落率',
miniGameBonus: '小遊戲獎勵',
badEventReduction: '壞事件減少'
}
return names[key] || key
}
// 获取最大健康值
function getMaxHealth(state) {
if (!state) return 100
const bonuses = getAllBonuses(state)
return 100 + (bonuses.health || 0)
}
// 顯示狀態(簡潔版)
function showStatus() {
if (!petSystem || !eventSystem || !templeSystem) {
console.log('系統尚未初始化')
return
}
const state = petSystem.getState()
if (!state) {
console.log('狀態尚未載入')
return
}
updatePetState()
const buffs = eventSystem.getBuffManager().getActiveBuffs()
const currentDeity = templeSystem.getCurrentDeity()
const favorStars = templeSystem.getFavorStars(state.currentDeityId)
const buffNames = buffs.map(b => b.name)
// 檢查神明滿級 Buff
if (currentDeity && state.deityFavors?.[state.currentDeityId] >= 100 && currentDeity.maxFavorBuff) {
const buff = currentDeity.maxFavorBuff
buffNames.push(`*${buff.name}(${buff.description})`)
}
// 安全獲取數值,避免 undefined
const safeNum = (val) => (typeof val === 'number' && !isNaN(val) ? val.toFixed(0) : '0')
console.log(` 狀態 | 飢餓:${safeNum(state.hunger)} 快樂:${safeNum(state.happiness)} 健康:${safeNum(state.health)} | 力量:${safeNum(state.str)} 智力:${safeNum(state.int)} 敏捷:${safeNum(state.dex)} 運勢:${safeNum(state.luck)} | ${currentDeity?.name || '未知'}${favorStars} | Buff:${buffNames.length > 0 ? buffNames.join(', ') : '無'}`)
}
// 啟動遊戲循環
function start() {
if (isRunning.value) {
console.log('[WARN] 遊戲循環已在運行中')
return
}
isRunning.value = true
// 移除 tick 回調中的自動輸出,只在有事件時才輸出
petSystem.startTickLoop(() => {
// 更新狀態顯示
updatePetState()
})
eventSystem.startEventCheck()
// 初始化全局 Debug 工具
window.debug = {
// 重置祈福次數
resetPrayer: async () => {
await templeSystem.debugResetDailyPrayer()
console.log('[OK] 祈福次數已重置')
showStatus()
},
// 設置好感度
setFavor: async (deityId, amount) => {
const state = petSystem.getState()
await petSystem.updateState({
deityFavors: {
...state.deityFavors,
[deityId]: amount
}
})
console.log(`[OK] ${deityId} 好感度已設置為 ${amount}`)
showStatus()
},
// 觸發事件
triggerEvent: async (eventId) => {
await eventSystem.debugTriggerEvent(eventId)
showStatus()
},
// 設置屬性
setStat: async (stat, value) => {
await petSystem.debugSetStat(stat, value)
showStatus()
},
// 幫助
help: () => {
console.log(`
[TOOLS] Debug 工具列表:
- debug.resetPrayer() 重置每日祈福次數
- debug.setFavor(id, amount) 設置神明好感度 (例如: 'mazu', 99)
- debug.triggerEvent(id) 觸發事件 (不填 ID 則隨機)
- debug.setStat(stat, value) 設置屬性 (例如: 'hunger', 100)
`)
}
}
console.log('[DEBUG] Debug 工具已就緒,輸入 debug.help() 查看指令')
console.log('[OK] 遊戲循環已啟動(靜默模式,只在事件觸發時輸出)')
}
// 停止遊戲循環
function stop() {
if (!isRunning.value) {
console.log('[WARN] 遊戲循環未運行')
return
}
petSystem.stopTickLoop()
eventSystem.stopEventCheck()
isRunning.value = false
console.log('[STOP] 遊戲循環已停止')
}
// 餵食
async function feed(amount = 20) {
const result = await petSystem.feed(amount)
if (result.success) {
console.log(`[OK] 餵食 飢餓+${amount} 體重+${(amount * 0.5).toFixed(1)}g | STR+${result.strGain?.toFixed(2)}`)
showStatus()
} else {
console.log(`[ERR] ${result.message}`)
}
}
// 玩耍
async function play(amount = 15) {
const result = await petSystem.play(amount)
if (result.success) {
const baseAmount = amount
const actualGain = result.happinessGain
const bonusPercent = actualGain > baseAmount ? `+${((actualGain / baseAmount - 1) * 100).toFixed(0)}%` : ''
console.log(`[OK] 玩耍 快樂 ${actualGain.toFixed(1)} ${bonusPercent} | DEX+${result.dexGain.toFixed(2)} INT+${result.intGain.toFixed(2)}`)
showStatus()
} else {
console.log(`[ERR] ${result.message}`)
}
}
// 清理便便
async function clean() {
const result = await petSystem.cleanPoop()
if (result.success) {
console.log('[OK] 清理便便 +10 快樂')
showStatus()
} else {
console.log(`[ERR] ${result.message}`)
}
}
// 治療
async function heal(amount = 20) {
const result = await petSystem.heal(amount)
if (result.success) {
const actualHeal = result.healAmount?.toFixed(1) || amount
const bonusText = result.healAmount > amount ? ` (+${((result.healAmount / amount - 1) * 100).toFixed(0)}%)` : ''
console.log(`[OK] 治療 健康+${actualHeal}${bonusText}${result.cured ? ' (已治癒)' : ''}`)
showStatus()
} else {
console.log(`[ERR] ${result.message}`)
}
}
// 睡覺/起床
async function sleep() {
const result = await petSystem.toggleSleep()
if (result.success) {
if (result.message) {
console.log(`[OK] ${result.message}`)
} else {
console.log(`[OK] ${result.isSleeping ? '已入睡' : '已醒來'}`)
}
showStatus()
} else {
console.log(`[ERR] ${result.message}`)
}
}
// 觸發事件
async function triggerEvent(eventId) {
const result = await eventSystem.triggerEvent(eventId)
if (result) {
console.log(`[OK] 事件 ${eventId} 觸發`)
showStatus()
} else {
console.log(`[ERR] 事件 ${eventId} 條件不滿足`)
}
}
// 查看事件列表
async function listEvents() {
const events = await apiService.getEvents()
console.log('\n[LIST] 可用事件列表 (共 ' + events.length + ' 個):')
console.log('='.repeat(60))
// 按類型分組
const grouped = {
good: events.filter(e => e.type === 'good'),
bad: events.filter(e => e.type === 'bad'),
weird: events.filter(e => e.type === 'weird'),
rare: events.filter(e => e.type === 'rare')
}
for (const [type, list] of Object.entries(grouped)) {
if (list.length === 0) continue
const typeEmoji = {
good: '*',
bad: '💀',
weird: '👻',
rare: '🌟'
}
console.log(`\n${typeEmoji[type]} ${type.toUpperCase()} 事件 (${list.length}個):`)
list.forEach(e => {
const effectsDesc = e.effects.map(eff => {
if (eff.type === 'modifyStats') {
const stats = Object.entries(eff.payload)
.map(([k, v]) => `${k}${v > 0 ? '+' : ''}${v}`)
.join(', ')
return stats
}
return eff.type
}).join(' | ')
console.log(` ${e.id}`)
console.log(` 效果: ${effectsDesc}`)
console.log(` 測試: triggerEvent('${e.id}')`)
})
}
console.log('\n' + '='.repeat(60))
console.log('[TIP] 快速測試所有事件: testAllEvents()')
console.log('='.repeat(60) + '\n')
}
// 查看事件歷史
function eventHistory() {
const history = eventSystem.getHistory()
console.log('\n[HISTORY] 事件歷史:')
console.log('='.repeat(50))
if (history.length === 0) {
console.log(' (無)')
} else {
history.forEach((h, i) => {
const time = new Date(h.timestamp).toLocaleTimeString()
console.log(`${i + 1}. [${time}] ${h.eventId} (${h.eventType})`)
})
}
console.log('='.repeat(50) + '\n')
}
// 祈福
async function pray() {
const result = await templeSystem.pray()
if (result.success) {
let message = `[OK] 祈福 +${result.favorIncrease} 好感 → ${result.newFavor}/100 | ${result.dialogue}`
// 等级提升提示
if (result.levelUp) {
message += `\n🌟 好感度等級提升Lv.${result.oldLevel} → Lv.${result.newLevel}`
if (result.deity.favorLevelBuffs) {
const buffs = Object.entries(result.deity.favorLevelBuffs.buffsPerLevel)
.map(([key, value]) => `${key.toUpperCase()}+${value}`)
.join(', ')
message += ` (${buffs})`
}
}
// 滿級特殊 Buff 提示
if (result.reachedMax && result.deity.maxFavorBuff) {
message += `\n** 滿級祝福!獲得特殊 Buff: ${result.deity.maxFavorBuff.name}`
message += `\n ${result.deity.maxFavorBuff.description}`
}
console.log(message)
showStatus()
} else {
console.log(`[ERR] ${result.message}`)
}
}
// 切換神明
async function switchDeity(deityId) {
const result = await templeSystem.switchDeity(deityId)
if (result.success) {
console.log(`[OK] 已切換到 ${result.deity.name}`)
} else {
console.log(`[ERR] ${result.message}`)
}
showStatus()
}
// 擲筊
async function handleThrowJiaobei() {
const result = await templeSystem.throwJiaobei()
if (result.success) {
const resultSymbols = {
holy: '⚪⚫', // 一正一反 - 聖筊
laughing: '⚪⚪', // 兩個正面 - 笑筊
negative: '⚫⚫' // 兩個反面 - 陰筊
}
const resultNames = {
holy: '聖筊',
laughing: '笑筊',
negative: '陰筊'
}
console.log('\n[JIAOBEI] 擲筊結果:')
console.log('='.repeat(50))
console.log(`神明: ${result.deity}`)
console.log(`結果: ${resultSymbols[result.result]} ${resultNames[result.result]}`)
console.log(`訊息: ${result.message}`)
console.log(`\n概率:`)
console.log(` 聖筊: ${(result.probabilities.holy * 100).toFixed(1)}%`)
console.log(` 笑筊: ${(result.probabilities.laughing * 100).toFixed(1)}%`)
console.log(` 陰筊: ${(result.probabilities.negative * 100).toFixed(1)}%`)
console.log('='.repeat(50) + '\n')
}
showStatus()
}
// 全局变量存储当前抽中的签
let currentLotData = null
// 求籤
async function handleDrawLot() {
const result = await templeSystem.drawLot('guanyin_100')
if (!result.success) {
console.log(`[ERR] ${result.message}`)
return
}
if (result.needVerification) {
currentLotData = result
console.log('\n[LOT] 求籤結果:')
console.log('='.repeat(50))
console.log(`${result.lot.no} (${result.lot.grade})`)
console.log(`神明: ${result.deity}`)
console.log(`\n⚠ 需要三聖筊驗證才能解籤`)
console.log(`目前: ${result.verificationCount}/${result.requiredHoly} 聖筊`)
console.log('\n請使用 verify() 命令擲筊驗證')
console.log('='.repeat(50) + '\n')
}
}
// 驗證籤詩(擲筊確認)
async function handleVerifyLot() {
if (!currentLotData) {
console.log('[ERR] 請先求籤 (使用 [LOT] 求籤 按鈕)')
return
}
const verifyResult = await templeSystem.verifyLot(currentLotData)
console.log('\n[VERIFY] 擲筊驗證:')
console.log('='.repeat(50))
console.log(`結果: ${verifyResult.jiaobeiResult.result === 'holy' ? '⚪⚫ 聖筊' :
verifyResult.jiaobeiResult.result === 'laughing' ? '⚪⚪ 笑筊' : '⚫⚫ 陰筊'}`)
console.log(`訊息: ${verifyResult.message}`)
if (verifyResult.verified) {
// 三聖筊!可以解籤
const lot = currentLotData.lot
console.log('\n' + '🎉'.repeat(20))
console.log(`\n【${lot.no}${lot.grade}`)
console.log('\n【籤詩】')
console.log(lot.poem1)
console.log('\n【解曰】')
console.log(lot.meaning)
console.log('\n【解籤】')
console.log(lot.explanation)
console.log('\n【問神】')
console.log(lot.oracle)
if (lot.story) {
console.log('\n【典故】')
console.log(lot.story)
}
console.log('\n' + '='.repeat(50) + '\n')
// 清除當前籤
currentLotData = null
} else if (verifyResult.needMore) {
// 需要更多聖筊
currentLotData.verificationCount = verifyResult.verificationCount
console.log(`\n✓ 進度: ${verifyResult.verificationCount}/${verifyResult.requiredHoly} 聖筊`)
console.log('請繼續使用 verify() 擲筊驗證')
console.log('='.repeat(50) + '\n')
} else if (verifyResult.needRedraw) {
// 笑筊或陰筊,需要重抽
console.log('\n✗ 需要重新求籤')
console.log('='.repeat(50) + '\n')
currentLotData = null
}
}
// 查看神明列表
function listDeities() {
const deities = templeSystem.getDeities()
console.log('\n[PRAY] 神明列表:')
console.log('='.repeat(50))
deities.forEach(d => {
const state = petSystem.getState()
const favor = state.deityFavors[d.id] || 0
const stars = templeSystem.getFavorStars(d.id)
console.log(`\n${d.id}: ${d.name}`)
console.log(` 個性: ${d.personality}`)
console.log(` 好感: ${stars} (${favor}/100)`)
console.log(` 加成: ${d.buffDescriptions.join(', ')}`)
})
console.log('='.repeat(50) + '\n')
}
// 抽籤(簡化版,兼容舊 API
async function drawFortune() {
// 使用 drawLot 作為實現
const result = await handleDrawLot()
return result
}
// 應用 Buff
async function applyBuffs() {
await eventSystem.applyBuffs()
eventSystem.getBuffManager().tick()
console.log('[OK] Buff 已更新')
showStatus()
}
// 確認名字並初始化
async function confirmName() {
if (!inputPetName.value || inputPetName.value.trim().length === 0) {
return
}
showNameInput.value = false
const name = inputPetName.value.trim()
await init(name)
}
// 初始化系統
async function init(petName = null) {
// 先創建成就系統
achievementSystem = new AchievementSystem(null, null, null, apiService)
// 先創建背包系統(因為其他系統可能需要它)
inventorySystem = new InventorySystem(null, null, apiService)
// 創建其他系統,並傳入成就系統和背包系統
petSystem = new PetSystem(apiService, achievementSystem, inventorySystem)
eventSystem = new EventSystem(petSystem, apiService, achievementSystem, inventorySystem)
templeSystem = new TempleSystem(petSystem, apiService, achievementSystem)
// 更新成就系統的引用
achievementSystem.petSystem = petSystem
achievementSystem.eventSystem = eventSystem
achievementSystem.templeSystem = templeSystem
// 更新背包系統的引用
inventorySystem.petSystem = petSystem
inventorySystem.eventSystem = eventSystem
try {
await petSystem.initialize('tinyTigerCat')
// 如果有提供名字,更新寵物名字
if (petName) {
await petSystem.updateState({ name: petName })
}
await eventSystem.initialize()
await templeSystem.initialize()
await achievementSystem.initialize() // 這會重新應用已解鎖成就的加成
await inventorySystem.initialize() // 初始化背包系統,會重新應用裝備效果
// 設置神明籤詩類型
if (templeSystem) {
const currentDeity = templeSystem.getCurrentDeity()
deityLotTypes.value = currentDeity?.lotTypes || ['guanyin_100']
selectedLotType.value = deityLotTypes.value[0]
}
// 重新計算戰鬥數值(確保成就加成已應用)
petSystem.calculateCombatStats()
// 初始化道具類型名稱和稀有度映射(提前載入)
try {
const { ITEM_TYPES, ITEM_RARITY } = await import('../data/items.js')
for (const [type, typeData] of Object.entries(ITEM_TYPES)) {
itemTypeNames.value[type] = typeData.name
}
for (const [rarity, rarityData] of Object.entries(ITEM_RARITY)) {
itemRarity.value[rarity] = rarityData
}
} catch (error) {
console.warn('[App] 載入道具類型失敗:', error)
}
// 初始檢查成就
await achievementSystem.checkAndUnlockAchievements()
// 更新成就列表
await updateAchievementList()
// 更新背包列表
await updateInventoryList()
updatePetState()
isReady.value = true
systemStatus.value = '[OK] 系統已就緒'
console.log('[OK] 系統已初始化')
console.log('[SWITCH] 自動啟動遊戲循環...')
// 自動啟動遊戲循環
start()
// 定期更新成就列表(每 5 秒)
setInterval(async () => {
if (achievementSystem) {
await achievementSystem.checkAndUnlockAchievements()
await updateAchievementList()
}
// 更新背包列表
if (inventorySystem) {
await updateInventoryList()
}
}, 5000)
showStatus()
} catch (error) {
// 如果沒有名字,顯示輸入對話框
if (error.message && error.message.includes('名字')) {
showNameInput.value = true
systemStatus.value = '請為寵物命名'
} else {
systemStatus.value = '[ERR] 初始化失敗: ' + error.message
console.error(error)
}
}
return { petSystem, eventSystem, templeSystem }
}
// 按鈕處理函數
async function handleFeed() {
await feed(20)
updatePetState()
}
async function handlePlay(gameType = 'normal') {
const result = await play({ amount: 15, gameType })
if (result && result.success) {
console.log(`${result.gameType} 體重變化: ${result.weightChange > 0 ? '+' : ''}${result.weightChange}g`)
}
updatePetState()
}
async function handleClean() {
await clean()
updatePetState()
}
async function handleHeal() {
await heal(20)
updatePetState()
}
async function handleSleep() {
await sleep()
updatePetState()
}
function handleShowStatus() {
showStatus()
}
function handleCheckEvolution() {
if (window.checkEvolution) {
window.checkEvolution()
}
}
function handleTestAllEvents() {
if (window.testAllEvents) {
window.testAllEvents()
}
}
function handleStart() {
start()
}
function handleStop() {
stop()
}
async function handleListEvents() {
await listEvents()
}
function handleEventHistory() {
eventHistory()
}
async function handleTriggerEvent(eventId) {
await triggerEvent(eventId)
updatePetState()
}
async function handlePray() {
await pray()
updatePetState()
}
function handleListDeities() {
listDeities()
}
async function handleSwitchDeity(deityId) {
await switchDeity(deityId)
updatePetState()
}
// 刪除寵物
async function handleDeletePet() {
if (!confirm('確定要刪除當前寵物嗎?此操作無法復原!')) {
return
}
try {
// 停止遊戲循環
if (isRunning.value) {
stop()
}
// 刪除寵物
const result = await petSystem.deletePet()
if (result.success) {
console.log('[OK] ' + result.message)
// 重置所有狀態
petState.value = null
isReady.value = false
isRunning.value = false
systemStatus.value = '寵物已刪除,請重新建立'
// 清除事件系統歷史
if (eventSystem) {
eventSystem.eventHistory = []
eventSystem.getBuffManager().buffs = []
}
// 顯示名字輸入對話框
inputPetName.value = ''
showNameInput.value = true
} else {
console.log('[ERR] 刪除失敗')
}
} catch (error) {
console.error('刪除寵物時發生錯誤:', error)
alert('刪除寵物時發生錯誤,請重試')
}
}
// 幫助
function help() {
console.log('\n' + '='.repeat(50))
console.log('📖 可用命令列表')
console.log('='.repeat(50))
console.log('\n[PLAY] 遊戲控制:')
console.log(' start() - 啟動遊戲循環')
console.log(' stop() - 停止遊戲循環')
console.log(' showStatus() - 顯示當前狀態')
console.log('\n 寵物互動:')
console.log(' feed(amount) - 餵食(預設 +20')
console.log(' play(amount) - 玩耍(預設 +15')
console.log(' clean() - 清理便便')
console.log(' heal(amount) - 治療(預設 +20')
console.log(' sleep() - 睡覺/起床')
console.log('\n[ROLL] 事件系統:')
console.log(' triggerEvent(id) - 手動觸發事件')
console.log(' listEvents() - 查看所有事件')
console.log(' eventHistory() - 查看事件歷史')
console.log(' applyBuffs() - 手動應用 Buff')
console.log('\n[PRAY] 神明系統:')
console.log(' pray() - 祈福(每日 3 次)')
console.log(' drawFortune() - 抽籤')
console.log(' switchDeity(id) - 切換神明')
console.log(' listDeities() - 查看神明列表')
console.log('\n[TIP] 提示:')
console.log(' - 所有數值操作都會同步到 APImock 模式使用 localStorage')
console.log(' - 事件每 10 秒自動檢查10% 機率觸發)')
console.log(' - 遊戲循環每 3 秒執行一次 tick')
console.log(' - 輸入 help() 再次查看此列表')
console.log('='.repeat(50) + '\n')
}
// 測試加成系統
function testBonuses() {
if (!petSystem || !petState.value) {
console.log('[ERR] 系統尚未初始化')
return
}
console.log('\n' + '='.repeat(60))
console.log('[CHECK] 加成系統測試報告')
console.log('='.repeat(60))
const state = petSystem.getState()
// 1. 命格信息
console.log('\n[HISTORY] 命格信息:')
if (state.destiny) {
console.log(` 名稱: ${state.destiny.name}`)
console.log(` 描述: ${state.destiny.description}`)
console.log(` 加成:`, state.destiny.buffs)
} else {
console.log(' [ERR] 未分配命格')
}
// 1.5 神明信息
console.log('\n[PRAY] 當前神明:')
if (templeSystem) {
const deity = templeSystem.getCurrentDeity()
if (deity) {
console.log(` 名稱: ${deity.name}`)
console.log(` 加成:`, deity.buffs)
}
}
// 2. 所有加成(合計)
const bonuses = petSystem.getAllBonuses()
console.log('\n💫 總加成(命格+神明):')
if (Object.keys(bonuses).length === 0) {
console.log(' 無')
} else {
for (const [key, value] of Object.entries(bonuses)) {
const sign = value > 0 ? '+' : ''
const percent = (value * 100).toFixed(0)
console.log(` ${key}: ${sign}${percent}%`)
}
}
// 3. 當前屬性
console.log('\n[STATUS] 當前屬性:')
console.log(` STR: ${state.str.toFixed(1)} | INT: ${state.int.toFixed(1)} | DEX: ${state.dex.toFixed(1)}`)
console.log(` 攻擊: ${state.attack?.toFixed(1)} | 防禦: ${state.defense?.toFixed(1)} | 速度: ${state.speed?.toFixed(1)}`)
// 4. 計算示例
console.log('\n[TEST] 計算示例:')
const strGainBase = 0.5
const strGainWithBonus = strGainBase * (1 + (bonuses.strGain || 0))
console.log(` 餵食時 STR 增長: ${strGainBase}${strGainWithBonus.toFixed(2)} (${bonuses.strGain ? '+' + (bonuses.strGain * 100).toFixed(0) + '%' : '無加成'})`)
const intGainBase = 0.2
const intGainWithBonus = intGainBase * (1 + (bonuses.intGain || 0))
console.log(` 玩耍時 INT 增長: ${intGainBase}${intGainWithBonus.toFixed(2)} (${bonuses.intGain ? '+' + (bonuses.intGain * 100).toFixed(0) + '%' : '無加成'})`)
const happinessGainBase = 15
const happinessGainWithBonus = happinessGainBase * (1 + (bonuses.happinessRecovery || 0))
console.log(` 玩耍時快樂恢復: ${happinessGainBase}${happinessGainWithBonus.toFixed(1)} (${bonuses.happinessRecovery ? '+' + (bonuses.happinessRecovery * 100).toFixed(0) + '%' : '無加成'})`)
console.log('\n' + '='.repeat(60))
console.log('[TIP] 提示: 執行 feed() 或 play() 來驗證實際效果')
}
// 掛載到 window
// Debug UI 狀態
const showDebug = ref(false)
const debugAchievementId = ref('')
// 成就相關響應式數據
const allAchievements = ref([])
const unlockedAchievementsCount = ref(0)
const totalAchievementsCount = ref(0)
const achievementProgress = ref(0)
// 背包相關響應式數據
const inventoryItems = ref({})
const inventoryItemCount = ref(0)
const equipmentSlots = ref({
weapon: { name: '武器', icon: '⚔️' },
armor: { name: '防具', icon: '🛡️' },
hat: { name: '帽子', icon: '🎩' },
accessory: { name: '飾品', icon: '💍' },
talisman: { name: '護身符', icon: '🔮' },
special: { name: '特殊', icon: '⭐' }
})
const itemTypeNames = ref({})
const itemRarity = ref({})
const deleteCounts = ref({}) // 每個道具的刪除數量
// Debug 方法
const debugResetPrayer = async () => {
await window.debug.resetPrayer()
}
const debugAddFavor = async (amount) => {
const state = petSystem.getState()
const currentFavor = state.deityFavors?.[state.currentDeityId] || 0
await window.debug.setFavor(state.currentDeityId, Math.min(100, currentFavor + amount))
}
const debugTriggerEvent = async () => {
await window.debug.triggerEvent()
}
const debugFillStats = async () => {
await window.debug.setStat('hunger', 100)
await window.debug.setStat('happiness', 100)
}
const debugMaxAttributes = async () => {
await window.debug.setStat('str', 100)
await window.debug.setStat('int', 100)
await window.debug.setStat('dex', 100)
}
const debugKill = async () => {
await window.debug.setStat('health', 0)
}
const debugAddPoop = async () => {
await window.debug.setStat('poopCount', 4)
}
// 成就相關函數
function isAchievementUnlocked(achievementId) {
if (!achievementSystem) return false
return achievementSystem.unlockedAchievements.includes(achievementId)
}
function getAchievementProgressPercent(achievement) {
if (!achievementSystem) return 0
const progress = achievementSystem.getAchievementProgress(achievement)
return progress.progress || 0
}
function getAchievementProgressText(achievement) {
if (!achievementSystem) return '0%'
const progress = achievementSystem.getAchievementProgress(achievement)
if (progress.current !== undefined && progress.target !== undefined) {
return `${progress.current}/${progress.target} (${Math.round(progress.progress)}%)`
}
return `${Math.round(progress.progress)}%`
}
function formatBonusValue(value) {
if (typeof value === 'number' && value < 1 && value > 0) {
return ` +${(value * 100).toFixed(0)}%`
}
return value > 0 ? ` +${value}` : ` ${value}`
}
async function updateAchievementList() {
if (!achievementSystem) return
const { ACHIEVEMENTS } = await import('../data/achievements.js')
allAchievements.value = ACHIEVEMENTS
totalAchievementsCount.value = ACHIEVEMENTS.length
unlockedAchievementsCount.value = achievementSystem.unlockedAchievements.length
achievementProgress.value = totalAchievementsCount.value > 0
? Math.round((unlockedAchievementsCount.value / totalAchievementsCount.value) * 100)
: 0
}
// 更新背包列表(過濾掉已裝備的道具)
async function updateInventoryList() {
if (!inventorySystem) return
const inventory = inventorySystem.getInventory()
const equipped = inventorySystem.getEquipped()
const items = {}
const { ITEMS, ITEM_TYPES, ITEM_RARITY } = await import('../data/items.js')
// 更新道具類型名稱映射(只在第一次或需要時更新)
if (Object.keys(itemTypeNames.value).length === 0) {
for (const [type, typeData] of Object.entries(ITEM_TYPES)) {
itemTypeNames.value[type] = typeData.name
}
}
// 更新稀有度映射(只在第一次或需要時更新)
if (Object.keys(itemRarity.value).length === 0) {
for (const [rarity, rarityData] of Object.entries(ITEM_RARITY)) {
itemRarity.value[rarity] = rarityData
}
}
// 收集所有已裝備的道具 ID用於過濾
const equippedItemIds = new Set()
for (const [slot, slotData] of Object.entries(equipped)) {
if (slotData && typeof slotData === 'object') {
if (slotData.equipment) {
const eqItemId = typeof slotData.equipment === 'string' ? slotData.equipment : slotData.equipment.itemId
if (eqItemId) {
// 計算已裝備的實例數量
const instanceId = typeof slotData.equipment === 'object' ? slotData.equipment.instanceId : null
if (instanceId) {
equippedItemIds.add(`${eqItemId}_${instanceId}`)
} else {
equippedItemIds.add(eqItemId)
}
}
}
if (slotData.appearance) {
const apItemId = typeof slotData.appearance === 'string' ? slotData.appearance : slotData.appearance.itemId
if (apItemId) {
const instanceId = typeof slotData.appearance === 'object' ? slotData.appearance.instanceId : null
if (instanceId) {
equippedItemIds.add(`${apItemId}_${instanceId}`)
} else {
equippedItemIds.add(apItemId)
}
}
}
}
}
// 從 inventorySystem 獲取道具數據(已從 API 載入)
const itemsData = inventorySystem.items || {}
for (const [itemId, data] of Object.entries(inventory)) {
// 優先使用 API 載入的道具數據
let item = itemsData[itemId] || ITEMS[itemId]
if (item) {
// 如果道具沒有 dropRate使用稀有度的默認掉落率
if (item.dropRate === undefined && item.rarity) {
const rarityData = ITEM_RARITY[item.rarity]
if (rarityData) {
item = { ...item, dropRate: rarityData.dropRate }
}
}
// 只有消耗品可以堆疊,裝備類每件分開顯示
if (item.type === 'consumable') {
// 消耗品:顯示堆疊數量
items[itemId] = {
item,
count: data.count,
totalCount: data.count,
equippedCount: 0,
isStackable: true
}
} else {
// 裝備類:每件分開顯示
if (data.items && data.items.length > 0) {
// 為每個未裝備的實例創建一個條目
for (const instance of data.items) {
const instanceKey = `${itemId}_${instance.id}`
if (!equippedItemIds.has(instanceKey)) {
// 使用實例 ID 作為 key這樣每件裝備都會單獨顯示
const instanceItemId = `${itemId}_${instance.id}`
items[instanceItemId] = {
item,
count: 1,
totalCount: 1,
equippedCount: 0,
isStackable: false,
instanceId: instance.id,
instance: instance, // 保存實例數據用於顯示耐久度等
originalItemId: itemId // 保存原始 itemId 用於操作
}
}
}
}
}
}
}
inventoryItems.value = items
inventoryItemCount.value = Object.keys(items).length
// 初始化刪除數量(如果還沒有設置)
for (const itemId of Object.keys(items)) {
if (deleteCounts.value[itemId] === undefined) {
deleteCounts.value[itemId] = 1
}
}
// 清理已不存在的道具的刪除數量
for (const itemId of Object.keys(deleteCounts.value)) {
if (!items[itemId]) {
delete deleteCounts.value[itemId]
}
}
}
// 獲取道具類型名稱(備用函數)
function getItemTypeName(type) {
return itemTypeNames.value[type] || type
}
// 獲取稀有度名稱
function getRarityName(rarity) {
return itemRarity.value[rarity]?.name || rarity || '普通'
}
// 獲取稀有度顏色
function getRarityColor(rarity) {
return itemRarity.value[rarity]?.color || '#9d9d9d'
}
// 獲取稀有度 CSS 類
function getRarityClass(rarity) {
return `rarity-${rarity || 'common'}`
}
// 獲取道具掉落率(如果道具沒有指定,使用稀有度的默認值)
function getItemDropRate(item) {
if (item.dropRate !== undefined) {
return (item.dropRate * 100).toFixed(0)
}
// 如果沒有指定掉落率,使用稀有度的默認值
if (item.rarity && itemRarity.value[item.rarity]) {
return (itemRarity.value[item.rarity].dropRate * 100).toFixed(0)
}
return '0'
}
// Tooltip 相關
const itemTooltip = ref(false)
const tooltipContent = ref(null)
const tooltipPosition = ref({ x: 0, y: 0 })
function showItemTooltip(event, item, itemId, instance = null) {
if (!item) return
const tooltip = {
name: item.name,
icon: item.icon || '📦',
type: itemTypeNames.value[item.type] || item.type,
rarity: getRarityName(item.rarity),
rarityColor: getRarityColor(item.rarity),
description: item.description,
effects: [],
durability: null,
dropRate: getItemDropRate(item)
}
// 收集效果
if (item.effects) {
if (item.effects.flat) {
for (const [key, value] of Object.entries(item.effects.flat)) {
tooltip.effects.push({
type: 'flat',
stat: getStatName(key),
value: formatStatValue(value)
})
}
}
if (item.effects.percent) {
for (const [key, value] of Object.entries(item.effects.percent)) {
tooltip.effects.push({
type: 'percent',
stat: getStatName(key),
value: formatPercentValue(value)
})
}
}
}
// 耐久度(如果有實例數據)
if (instance && instance.durability !== undefined && instance.durability !== Infinity) {
tooltip.durability = {
current: instance.durability,
max: instance.maxDurability || item.maxDurability
}
} else if (item.durability !== Infinity && item.maxDurability) {
tooltip.durability = {
current: item.durability,
max: item.maxDurability
}
}
tooltipContent.value = tooltip
tooltipPosition.value = {
x: event.clientX + 15,
y: event.clientY + 15
}
itemTooltip.value = true
}
function hideItemTooltip() {
itemTooltip.value = false
tooltipContent.value = null
}
// 獲取屬性名稱(用於顯示效果)
function getStatName(key) {
const names = {
// 基礎屬性
str: '力量',
int: '智力',
dex: '敏捷',
luck: '運勢',
health: '最大健康',
attack: '攻擊',
defense: '防禦',
speed: '速度',
hp: 'HP',
// 狀態
hunger: '飢餓',
happiness: '快樂',
// 成長
strGain: '力量成長',
intGain: '智力成長',
dexGain: '敏捷成長',
// 恢復
happinessRecovery: '快樂恢復',
healthRecovery: '健康恢復',
// 其他
dropRate: '掉落率',
sicknessReduction: '生病抗性'
}
return names[key] || key
}
// 格式化數值(固定值)
function formatStatValue(value) {
if (value > 0) {
return ` +${value}`
} else if (value < 0) {
return ` ${value}`
}
return ''
}
// 格式化百分比值
function formatPercentValue(value) {
if (typeof value === 'number') {
const percent = (value * 100).toFixed(0)
if (value > 0) {
return ` +${percent}%`
} else if (value < 0) {
return ` ${percent}%`
}
}
return ''
}
// 獲取裝備的道具 ID支持新雙槽位結構
function getEquippedItemId(slot, type = 'equipment') {
if (!inventorySystem) return null
const equipped = inventorySystem.getEquipped()
const slotData = equipped[slot]
if (!slotData) return null
// 兼容舊格式
if (typeof slotData === 'string' || (typeof slotData === 'object' && slotData.itemId)) {
return typeof slotData === 'string' ? slotData : slotData.itemId
}
// 新格式:雙槽位
const equippedData = slotData[type]
if (!equippedData) return null
return typeof equippedData === 'string' ? equippedData : equippedData.itemId
}
// 獲取裝備的道具圖標
function getEquippedItemIcon(slot, type = 'equipment') {
if (!inventorySystem) return '📦'
const itemId = getEquippedItemId(slot, type)
if (!itemId) return '📦'
// 從 inventoryItems 中查找
const itemData = inventoryItems.value[itemId]
return itemData?.item?.icon || '📦'
}
// 獲取裝備的道具名稱
function getEquippedItemName(slot, type = 'equipment') {
if (!inventorySystem) return ''
const itemId = getEquippedItemId(slot, type)
if (!itemId) return ''
// 從 inventoryItems 中查找
const itemData = inventoryItems.value[itemId]
return itemData?.item?.name || ''
}
// 檢查槽位是否有裝備(支持新結構)
function hasEquipped(slot, type = null) {
if (!inventorySystem) return false
const equipped = inventorySystem.getEquipped()
const slotData = equipped[slot]
if (!slotData) return false
// 兼容舊格式
if (typeof slotData === 'string' || (typeof slotData === 'object' && slotData.itemId)) {
return type === null || type === 'equipment'
}
// 新格式:雙槽位
if (type === null) {
return !!(slotData.equipment || slotData.appearance)
}
return !!slotData[type]
}
// 檢查是否可以裝備
function canEquip(item) {
return item.type === 'equipment' ||
item.type === 'appearance' ||
item.type === 'talisman' ||
item.type === 'special'
}
// 獲取道具耐久度
function getItemDurability(itemId, instanceId = null) {
if (!inventorySystem) return 0
const inventory = inventorySystem.getInventory()
// 如果是實例 ID包含下劃線和實例 ID
if (itemId.includes('_') && itemId.split('_').length > 2) {
const parts = itemId.split('_')
const originalItemId = parts[0]
const instanceId = parts.slice(1).join('_')
const itemData = inventory[originalItemId]
if (itemData && itemData.items) {
const instance = itemData.items.find(i => i.id === instanceId)
return instance ? (instance.durability || 0) : 0
}
} else {
// 舊格式或消耗品
const itemData = inventory[itemId]
if (!itemData) return 0
// 如果有實例數據
if (itemData.items && itemData.items.length > 0) {
if (instanceId) {
const instance = itemData.items.find(i => i.id === instanceId)
return instance ? (instance.durability || 0) : 0
}
return itemData.items[0].durability || 0
}
}
return 0
}
// 處理使用道具
async function handleUseItem(itemId) {
if (!inventorySystem) return
try {
const result = await inventorySystem.useItem(itemId, 1)
if (result.success) {
await updateInventoryList()
updatePetState()
console.log(`[UI] 使用了道具: ${itemId}`)
} else {
console.error(`[UI] 使用道具失敗: ${result.message}`)
}
} catch (error) {
console.error('[UI] 使用道具錯誤:', error)
}
}
// 處理裝備道具
// equipType: 'equipment' | 'appearance' | 'auto'
async function handleEquip(itemId, slot, equipType = 'auto') {
if (!inventorySystem) return
try {
// 如果是實例 ID裝備類分開顯示需要提取原始 itemId 和 instanceId
let actualItemId = itemId
let instanceId = null
const itemData = inventoryItems.value[itemId]
if (itemData) {
if (itemData.originalItemId) {
// 這是分開顯示的裝備實例
actualItemId = itemData.originalItemId
instanceId = itemData.instanceId
} else if (itemId.includes('_') && itemId.split('_').length > 2) {
// 實例 ID 格式itemId_instanceId
const parts = itemId.split('_')
actualItemId = parts[0]
instanceId = parts.slice(1).join('_')
}
}
const result = await inventorySystem.equipItem(actualItemId, slot, equipType, instanceId)
if (result.success) {
await updateInventoryList()
updatePetState()
const typeName = result.type === 'appearance' ? '外觀' : '實際'
console.log(`[UI] 裝備了道具: ${actualItemId} (${typeName})`)
} else {
console.error(`[UI] 裝備失敗: ${result.message}`)
}
} catch (error) {
console.error('[UI] 裝備錯誤:', error)
}
}
// 處理卸下裝備
// type: 'equipment' | 'appearance' | null (卸下全部)
async function handleUnequip(slot, type = null) {
if (!inventorySystem) return
try {
const result = await inventorySystem.unequipItem(slot, type)
if (result.success) {
await updateInventoryList()
updatePetState()
const typeName = type === 'appearance' ? '外觀' : (type === 'equipment' ? '實際' : '全部')
console.log(`[UI] 卸下了裝備: ${slot} (${typeName})`)
} else {
console.error(`[UI] 卸下失敗: ${result.message}`)
}
} catch (error) {
console.error('[UI] 卸下錯誤:', error)
}
}
// 處理修復裝備
async function handleRepair(itemId) {
if (!inventorySystem) return
try {
const result = await inventorySystem.repairItem(itemId)
if (result.success) {
await updateInventoryList()
updatePetState()
console.log(`[UI] 修復了裝備: ${itemId}`)
} else {
console.error(`[UI] 修復失敗: ${result.message}`)
}
} catch (error) {
console.error('[UI] 修復錯誤:', error)
}
}
// Debug 成就功能
async function debugUnlockAchievement() {
if (!achievementSystem || !debugAchievementId.value) {
console.log('[ERR] 請輸入成就 ID')
return
}
const achievement = allAchievements.value.find(a => a.id === debugAchievementId.value)
if (!achievement) {
console.log(`[ERR] 找不到成就: ${debugAchievementId.value}`)
return
}
await achievementSystem.unlockAchievement(achievement)
await updateAchievementList()
updatePetState()
console.log(`[OK] 成就已解鎖: ${achievement.name}`)
}
async function debugListAchievements() {
if (!achievementSystem) {
console.log('[ERR] 成就系統未初始化')
return
}
const unlocked = achievementSystem.getUnlockedAchievements()
const locked = achievementSystem.getLockedAchievements()
console.log('\n' + '='.repeat(60))
console.log('🏆 成就列表')
console.log('='.repeat(60))
console.log(`\n✅ 已解鎖 (${unlocked.length}/${allAchievements.value.length}):`)
unlocked.forEach(a => {
console.log(` ${a.icon} ${a.name} - ${a.description}`)
})
console.log(`\n🔒 未解鎖 (${locked.length}):`)
locked.forEach(a => {
const progress = achievementSystem.getAchievementProgress(a)
if (progress.current !== undefined && progress.target !== undefined) {
console.log(` ${a.icon} ${a.name} - ${a.description}`)
console.log(` 進度: ${progress.current}/${progress.target} (${Math.round(progress.progress)}%)`)
} else {
console.log(` ${a.icon} ${a.name} - ${a.description}`)
}
})
console.log('='.repeat(60) + '\n')
}
async function debugUnlockAllAchievements() {
if (!achievementSystem) {
console.log('[ERR] 成就系統未初始化')
return
}
const { ACHIEVEMENTS } = await import('../data/achievements.js')
let count = 0
for (const achievement of ACHIEVEMENTS) {
if (!achievementSystem.unlockedAchievements.includes(achievement.id)) {
await achievementSystem.unlockAchievement(achievement)
count++
}
}
await updateAchievementList()
updatePetState()
console.log(`[OK] 已解鎖 ${count} 個成就`)
}
async function debugResetAchievements() {
if (!achievementSystem) {
console.log('[ERR] 成就系統未初始化')
return
}
if (!confirm('確定要重置所有成就嗎?此操作無法復原!')) {
return
}
achievementSystem.unlockedAchievements = []
achievementSystem.achievementStats = {
actionCounts: {
feed: 0,
play: 0,
clean: 0,
heal: 0,
sleep: 0,
pray: 0,
drawFortune: 0
},
eventCount: 0,
eventTypeCounts: {
good: 0,
bad: 0,
weird: 0,
rare: 0
},
recoveredFromDying: false,
perfectStateReached: false
}
// 清除成就加成
await petSystem.updateState({ achievementBuffs: {} })
petSystem.calculateCombatStats()
// 同步到 API
try {
await achievementSystem.api.saveAchievements({
unlocked: [],
stats: achievementSystem.achievementStats
})
} catch (error) {
localStorage.setItem('achievements', JSON.stringify({
unlocked: [],
stats: achievementSystem.achievementStats
}))
}
await updateAchievementList()
updatePetState()
console.log('[OK] 成就已重置')
}
// Debug 背包功能
async function debugAddRandomItem() {
if (!inventorySystem) {
console.log('[ERR] 背包系統未初始化')
return
}
const { ITEMS } = await import('../data/items.js')
const itemIds = Object.keys(ITEMS)
const randomItemId = itemIds[Math.floor(Math.random() * itemIds.length)]
const result = await inventorySystem.addItem(randomItemId, 1)
if (result.success) {
await updateInventoryList()
updatePetState()
console.log(`[OK] 隨機產生道具: ${result.item.name}`)
} else {
console.error(`[ERR] 產生道具失敗: ${result.message}`)
}
}
async function debugAddItemByType(type) {
if (!inventorySystem) {
console.log('[ERR] 背包系統未初始化')
return
}
const { ITEMS } = await import('../data/items.js')
const itemsOfType = Object.entries(ITEMS)
.filter(([id, item]) => item.type === type)
.map(([id]) => id)
if (itemsOfType.length === 0) {
console.log(`[ERR] 沒有找到類型為 ${type} 的道具`)
return
}
const randomItemId = itemsOfType[Math.floor(Math.random() * itemsOfType.length)]
const result = await inventorySystem.addItem(randomItemId, 1)
if (result.success) {
await updateInventoryList()
updatePetState()
console.log(`[OK] 產生 ${type} 道具: ${result.item.name}`)
} else {
console.error(`[ERR] 產生道具失敗: ${result.message}`)
}
}
async function debugAddItemByCategory(category) {
if (!inventorySystem) {
console.log('[ERR] 背包系統未初始化')
return
}
const { ITEMS } = await import('../data/items.js')
const itemsOfCategory = Object.entries(ITEMS)
.filter(([id, item]) => item.category === category)
.map(([id]) => id)
if (itemsOfCategory.length === 0) {
console.log(`[ERR] 沒有找到類別為 ${category} 的道具`)
return
}
const randomItemId = itemsOfCategory[Math.floor(Math.random() * itemsOfCategory.length)]
const result = await inventorySystem.addItem(randomItemId, 1)
if (result.success) {
await updateInventoryList()
updatePetState()
console.log(`[OK] 產生 ${category} 道具: ${result.item.name}`)
} else {
console.error(`[ERR] 產生道具失敗: ${result.message}`)
}
}
// 處理鍵盤輸入(可選)
const handleKeydown = (e) => {
// Ctrl + Shift + D 開關 Debug 面板
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
showDebug.value = !showDebug.value
}
}
onMounted(async () => {
window.addEventListener('keydown', handleKeydown)
// 檢查是否已有寵物
try {
const existingState = localStorage.getItem('petState')
if (existingState) {
// 已有寵物,直接初始化
systemStatus.value = '正在載入寵物...'
await init()
} else {
// 新寵物,顯示名字輸入
systemStatus.value = '歡迎!請為你的寵物命名'
showNameInput.value = true
}
// 將所有函數掛載到 window
window.petSystem = petSystem
window.eventSystem = eventSystem
window.templeSystem = templeSystem
window.inventorySystem = inventorySystem
window.showStatus = showStatus
window.start = start
window.stop = stop
window.feed = feed
window.play = play
window.clean = clean
window.heal = heal
window.sleep = sleep
window.triggerEvent = triggerEvent
window.listEvents = listEvents
window.eventHistory = eventHistory
window.pray = pray
window.drawFortune = drawFortune
window.switchDeity = switchDeity
window.listDeities = listDeities
window.applyBuffs = applyBuffs
window.help = help
window.init = init
window.testBonuses = testBonuses
// 事件測試函數
window.testEvent = async (eventId) => {
const events = await apiService.getEvents()
const event = events.find(e => e.id === eventId)
if (!event) {
console.log(`[ERR] 找不到事件: ${eventId}`)
return
}
console.log(`\n[TEST] 測試: ${event.id} (${event.type})`)
showStatus()
await triggerEvent(eventId)
await new Promise(r => setTimeout(r, 100))
showStatus()
}
window.testAllEvents = async () => {
const events = await apiService.getEvents()
console.log(`\n[TEST] 測試 ${events.length} 個事件...\n`)
for (const e of events) {
console.log(`[START] ${e.id}`)
await triggerEvent(e.id)
await new Promise(r => setTimeout(r, 50))
}
console.log('\n[OK] 完成')
showStatus()
}
// 檢查進化狀態
window.checkEvolution = () => {
const state = petSystem.getState()
const species = petSystem.speciesConfig
console.log('\n' + '='.repeat(60))
console.log('[CHECK] 進化系統檢查')
console.log('='.repeat(60))
console.log(`\n當前階段: ${state.stage}`)
console.log(`年齡: ${state.ageSeconds.toFixed(1)}`)
console.log(`屬性: STR ${state.str.toFixed(1)} | INT ${state.int.toFixed(1)} | DEX ${state.dex.toFixed(1)}`)
// 找到當前階段
const currentIdx = species.lifecycle.findIndex(s => s.stage === state.stage)
if (currentIdx < 0) {
console.log('\n[ERR] 找不到當前階段配置')
return
}
const currentStage = species.lifecycle[currentIdx]
if (currentIdx < species.lifecycle.length - 1) {
const nextStage = species.lifecycle[currentIdx + 1]
console.log(`\n下一階段: ${nextStage.stage}`)
// 檢查當前階段是否已達到結束時間
const timeRequirement = currentStage.durationSeconds
const timeMet = state.ageSeconds >= timeRequirement
if (timeRequirement === Infinity) {
console.log(`年齡條件: 已達到最終階段,無需再進化`)
} else {
console.log(`年齡條件: ${state.ageSeconds.toFixed(1)}/${timeRequirement}${timeMet ? '[OK]' : '[ERR]'}`)
}
// 檢查屬性條件
if (nextStage.conditions && Object.keys(nextStage.conditions).length > 0) {
console.log('屬性條件:')
if (nextStage.conditions.str) {
const met = state.str >= nextStage.conditions.str
console.log(` STR: ${state.str.toFixed(1)}/${nextStage.conditions.str} ${met ? '[OK]' : '[ERR]'}`)
}
if (nextStage.conditions.int) {
const met = state.int >= nextStage.conditions.int
console.log(` INT: ${state.int.toFixed(1)}/${nextStage.conditions.int} ${met ? '[OK]' : '[ERR]'}`)
}
if (nextStage.conditions.dex) {
const met = state.dex >= nextStage.conditions.dex
console.log(` DEX: ${state.dex.toFixed(1)}/${nextStage.conditions.dex} ${met ? '[OK]' : '[ERR]'}`)
}
} else {
console.log('屬性條件: 無')
}
// 顯示進化建議
if (timeMet && nextStage.conditions) {
const allStatsMet = Object.entries(nextStage.conditions).every(([key, value]) => state[key] >= value)
if (allStatsMet) {
console.log('\n* 條件已滿足!應該會自動進化')
} else {
console.log('\n[TIP] 提示: 年齡已足夠,再提升屬性即可進化')
}
} else if (!timeMet) {
const remaining = timeRequirement - state.ageSeconds
console.log(`\n[WAIT] 還需等待 ${remaining.toFixed(1)}`)
}
} else {
console.log('\n[DONE] 已達到最終階段')
}
console.log('='.repeat(60) + '\n')
}
} catch (error) {
systemStatus.value = '[ERR] 初始化失敗: ' + error.message
console.error(error)
}
})
onUnmounted(() => {
if (isRunning.value) {
stop()
}
})
</script>
<style scoped>
/* 像素字体 - Zpix 支持繁体中文 */
@import url('https://cdn.jsdelivr.net/npm/zpix-pixel-font@latest/dist/Zpix.css');
* {
font-family: 'Zpix', 'Noto Sans TC', monospace;
image-rendering: pixelated;
}
.pet-system-container {
min-height: 100vh;
background: #0a0a0a;
color: #00ff00;
padding: 15px;
}
.header {
text-align: center;
margin-bottom: 20px;
border: 3px solid #00ff00;
padding: 15px;
background: #000;
box-shadow: 0 0 15px #00ff00;
}
.header h1 {
color: #00ffff;
font-size: 18px;
margin: 0 0 10px 0;
text-shadow: 2px 2px 0 #000, 0 0 10px #00ffff;
}
.subtitle {
color: #00aa00;
font-size: 10px;
}
/* 寵物狀態顯示 */
.pet-status {
background: #000;
padding: 15px;
margin-bottom: 15px;
border: 3px solid #00ff00;
box-shadow: inset 0 0 20px rgba(0, 255, 0, 0.1);
}
.status-section {
margin-bottom: 20px;
}
.status-section h3 {
color: #00ffff;
margin-bottom: 10px;
font-size: 14px;
text-align: center;
padding-bottom: 8px;
border-bottom: 2px solid #00ff00;
text-shadow: 0 0 5px #00ffff;
}
.status-grid {
display: grid;
gap: 10px;
margin-bottom: 10px;
}
.stat-item {
display: flex;
align-items: center;
gap: 10px;
}
.stat-label {
min-width: 50px;
color: #00aa00;
font-weight: bold;
font-size: 10px;
}
.stat-bar {
flex: 1;
height: 12px;
background: #000;
border: 2px solid #00ff00;
position: relative;
overflow: hidden;
}
.stat-fill {
height: 100%;
background: #00ff00;
transition: width 0.3s ease;
box-shadow: 0 0 10px #00ff00;
}
.stat-value {
min-width: 40px;
text-align: right;
color: #ffffff;
font-size: 10px;
font-weight: bold;
}
.pet-info-grid,
.bonus-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 8px;
margin-bottom: 10px;
}
.info-item,
.bonus-item {
display: flex;
justify-content: space-between;
padding: 5px 8px;
background: #001100;
border: 1px solid #00ff00;
transition: all 0.2s;
}
.info-item:hover,
.bonus-item:hover {
background: #002200;
box-shadow: 0 0 5px #00ff00;
}
.info-label,
.bonus-label {
color: #00aa00;
font-size: 10px;
}
.info-value,
.bonus-value {
color: #ffffff;
font-size: 10px;
font-weight: bold;
}
.bonus-value.positive {
color: #00ff00;
}
.bonus-value.negative {
color: #ff0000;
}
.bonus-item.not-implemented {
opacity: 0.6;
border-color: #ff0000;
background: #110000;
box-shadow: inset 0 0 10px rgba(255, 0, 0, 0.1);
}
.bonus-item.not-implemented .bonus-label {
color: #ff0000;
text-decoration: line-through;
}
.bonus-item.not-implemented .bonus-value {
color: #ff0000 !important;
}
.status-badges {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 10px;
}
.badge {
background: #000;
border: 2px solid #ffff00;
color: #ffff00;
padding: 4px 8px;
font-size: 10px;
animation: blink 1s infinite;
}
.badge.sick {
border-color: #ff0000;
color: #ff0000;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.5; }
}
/* 操作按鈕區 */
.action-panel {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.action-group {
background: #252526;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #4ec9b0;
}
.action-group h3 {
color: #4ec9b0;
margin-bottom: 15px;
font-size: 1.1em;
}
.button-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
}
button {
background: #000;
border: 2px solid #00ff00;
color: #00ff00;
padding: 10px;
cursor: pointer;
transition: all 0.1s;
font-size: 12px;
}
button:hover:not(:disabled) {
background: #00ff00;
color: #000;
box-shadow: 0 0 10px #00ff00;
}
button:active:not(:disabled) {
transform: translate(2px, 2px);
}
button:disabled {
border-color: #003300;
color: #003300;
cursor: not-allowed;
opacity: 0.5;
}
.danger-button {
background: #d32f2f !important;
}
.danger-button:hover:not(:disabled) {
background: #f44336 !important;
}
.console-output {
background: #000;
border: 3px solid #00ff00;
padding: 10px;
min-height: 200px;
max-height: 400px;
overflow-y: auto;
font-size: 10px;
box-shadow: inset 0 0 20px rgba(0, 255, 0, 0.1);
}
.log-entry {
margin: 3px 0;
padding: 3px;
border-left: 2px solid #00ff00;
padding-left: 8px;
color: #00ff00;
}
/* Debug 面板樣式 */
.debug-toggle {
position: fixed;
top: 10px;
right: 10px;
background: #000;
border: 2px solid #ff00ff;
color: #ff00ff;
padding: 5px 10px;
cursor: pointer;
font-size: 10px;
z-index: 100;
box-shadow: 0 0 10px #ff00ff;
}
.debug-toggle:hover {
background: #ff00ff;
color: #000;
}
.debug-panel {
position: fixed;
top: 50px;
right: 10px;
background: #000;
border: 3px solid #ff00ff;
padding: 15px;
max-width: 250px;
z-index: 99;
box-shadow: 0 0 20px #ff00ff;
}
.debug-panel h3 {
color: #ff00ff;
margin-bottom: 10px;
font-size: 12px;
text-shadow: 0 0 5px #ff00ff;
}
.debug-group {
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #330033;
}
.debug-group:last-child {
border-bottom: none;
}
.debug-group h4 {
color: #ff00ff;
margin-bottom: 5px;
font-size: 10px;
}
.debug-panel button {
width: 100%;
margin-bottom: 5px;
border-color: #ff00ff;
color: #ff00ff;
font-size: 9px;
}
.debug-panel button:hover:not(:disabled) {
background: #ff00ff;
}
.name-input-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: #000;
border: 3px solid #00ffff;
padding: 20px;
min-width: 300px;
box-shadow: 0 0 30px #00ffff;
}
.modal-content h2 {
color: #00ffff;
font-size: 14px;
margin-bottom: 10px;
text-shadow: 0 0 10px #00ffff;
text-align: center;
}
.modal-content p {
color: #00aa00;
font-size: 10px;
margin-bottom: 15px;
text-align: center;
}
.name-input {
width: 100%;
background: #000;
border: 2px solid #00ff00;
color: #00ff00;
padding: 10px;
font-size: 12px;
margin-bottom: 15px;
box-sizing: border-box;
}
.name-input:focus {
outline: none;
border-color: #00ffff;
box-shadow: 0 0 10px #00ffff;
}
.modal-buttons {
display: flex;
justify-content: center;
}
.modal-buttons button {
min-width: 100px;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: #000;
border: 1px solid #00ff00;
}
::-webkit-scrollbar-thumb {
background: #00ff00;
border: 2px solid #000;
}
::-webkit-scrollbar-thumb:hover {
background: #00ffff;
box-shadow: 0 0 5px #00ffff;
}
/* 其他修正 */
.info-value-group {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.info-value.highlight {
color: #00ffff;
text-shadow: 0 0 5px #00ffff;
}
.info-sub {
color: #00aa00;
font-size: 8px;
margin-top: 2px;
}
button.active {
background: #00ff00;
color: #000;
box-shadow: 0 0 10px #00ff00;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: #000;
border: 1px solid #00ff00;
}
::-webkit-scrollbar-thumb {
background: #00ff00;
border: 2px solid #000;
}
::-webkit-scrollbar-thumb:hover {
background: #00ffff;
box-shadow: 0 0 5px #00ffff;
}
/* 其他修正 */
.info-value-group {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.info-value.highlight {
color: #00ffff;
text-shadow: 0 0 5px #00ffff;
}
.info-sub {
color: #00aa00;
font-size: 8px;
margin-top: 2px;
}
.modal-content {
padding: 30px;
border-radius: 12px;
border: 2px solid #4ec9b0;
min-width: 400px;
max-width: 90%;
}
.modal-content h2 {
color: #4ec9b0;
margin-bottom: 10px;
text-align: center;
}
.modal-content p {
color: #d4d4d4;
margin-bottom: 20px;
text-align: center;
}
.name-input {
width: 100%;
padding: 12px;
background: #1e1e1e;
border: 2px solid #3c3c3c;
border-radius: 6px;
color: #d4d4d4;
font-size: 1.1em;
margin-bottom: 20px;
text-align: center;
}
.name-input:focus {
outline: none;
border-color: #4ec9b0;
}
.modal-buttons {
display: flex;
justify-content: center;
gap: 10px;
}
.modal-buttons button {
background: #007acc;
color: #fff;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 1em;
font-weight: 500;
}
.modal-buttons button:hover:not(:disabled) {
background: #0098ff;
}
.modal-buttons button:disabled {
background: #3c3c3c;
color: #858585;
cursor: not-allowed;
opacity: 0.6;
}
@media (max-width: 768px) {
.action-panel {
grid-template-columns: 1fr;
}
.modal-content {
min-width: 90%;
padding: 20px;
}
}
/* 折疊面板樣式 */
details.event-list, details.deity-list {
margin-top: 10px;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 10px;
}
details summary {
cursor: pointer;
user-select: none;
color: #4ec9b0;
font-weight: bold;
padding: 5px;
list-style-position: inside;
}
details summary:hover {
background: rgba(78, 201, 176, 0.1);
border-radius: 4px;
}
details[open] summary {
margin-bottom: 10px;
border-bottom: 1px solid #3c3c3c;
}
/* 小按鈕網格 */
.button-grid.small {
gap: 8px;
}
.button-grid.small button {
padding: 8px 12px;
font-size: 0.85em;
}
/* 激活狀態的按鈕 */
button.active {
background: #4ec9b0 !important;
color: #1e1e1e !important;
font-weight: bold;
border: 2px solid #6ee2c4;
}
button.active:hover {
background: #6ee2c4 !important;
}
/* 成就系統樣式 */
.achievement-summary {
margin-bottom: 10px;
}
.achievement-stats {
display: flex;
justify-content: space-around;
padding: 10px;
background: #001100;
border: 1px solid #00ff00;
border-radius: 4px;
font-size: 12px;
}
.achievement-stats span {
color: #00ff00;
font-weight: bold;
}
.achievement-list {
margin-top: 10px;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 10px;
}
.achievement-list summary {
cursor: pointer;
user-select: none;
color: #4ec9b0;
font-weight: bold;
padding: 5px;
list-style-position: inside;
}
.achievement-list summary:hover {
background: rgba(78, 201, 176, 0.1);
border-radius: 4px;
}
.achievement-list[open] summary {
margin-bottom: 10px;
border-bottom: 1px solid #3c3c3c;
padding-bottom: 10px;
}
.achievement-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 10px;
max-height: 400px;
overflow-y: auto;
padding: 10px 0;
}
.achievement-item {
display: flex;
gap: 10px;
padding: 10px;
background: #001100;
border: 2px solid #003300;
border-radius: 4px;
transition: all 0.2s;
}
.achievement-item.unlocked {
border-color: #00ff00;
background: #002200;
box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
}
.achievement-item.locked {
opacity: 0.6;
border-color: #333;
}
.achievement-icon {
font-size: 24px;
min-width: 30px;
text-align: center;
}
.achievement-info {
flex: 1;
}
.achievement-name {
color: #00ffff;
font-weight: bold;
font-size: 14px;
margin-bottom: 4px;
}
.achievement-item.locked .achievement-name {
color: #888;
}
.achievement-desc {
color: #aaa;
font-size: 11px;
margin-bottom: 6px;
}
.achievement-progress {
margin-top: 6px;
}
.progress-bar {
width: 100%;
height: 6px;
background: #000;
border: 1px solid #333;
border-radius: 3px;
overflow: hidden;
margin-bottom: 4px;
}
.progress-fill {
height: 100%;
background: #00ff00;
transition: width 0.3s;
}
.progress-text {
color: #888;
font-size: 10px;
}
.achievement-reward {
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid #333;
font-size: 10px;
}
.reward-label {
color: #00aa00;
margin-right: 4px;
}
.reward-item {
color: #00ff00;
margin-right: 8px;
}
/* 背包系統樣式 */
.equipment-slots {
margin-bottom: 15px;
}
.equipment-slots h4 {
color: #4ec9b0;
margin-bottom: 10px;
font-size: 14px;
}
/* 稀有度圖例 */
.rarity-legend {
background: #001100;
border: 2px solid #3c3c3c;
border-radius: 4px;
padding: 10px;
margin-bottom: 15px;
}
.legend-title {
color: #4ec9b0;
font-size: 12px;
font-weight: bold;
margin-bottom: 8px;
}
.legend-items {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 6px;
}
.legend-item {
padding: 4px 8px;
border-radius: 3px;
font-size: 10px;
border: 2px solid;
}
.legend-item.rarity-common {
border-color: #9d9d9d;
color: #9d9d9d;
}
.legend-item.rarity-uncommon {
border-color: #1eff00;
color: #1eff00;
box-shadow: 0 0 5px rgba(30, 255, 0, 0.3);
}
.legend-item.rarity-rare {
border-color: #0070dd;
color: #0070dd;
box-shadow: 0 0 8px rgba(0, 112, 221, 0.4);
}
.legend-item.rarity-epic {
border-color: #a335ee;
color: #a335ee;
box-shadow: 0 0 10px rgba(163, 53, 238, 0.5);
}
.legend-item.rarity-legendary {
border-color: #ff8000;
color: #ff8000;
box-shadow: 0 0 15px rgba(255, 128, 0, 0.6);
}
.legend-note {
color: #888;
font-size: 9px;
font-style: italic;
}
.slots-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 10px;
}
.equipment-slot-container {
border: 2px solid #3c3c3c;
border-radius: 4px;
padding: 8px;
background: #000511;
display: flex;
flex-direction: column;
gap: 6px;
}
.slot-header {
font-size: 11px;
color: #4ec9b0;
font-weight: bold;
text-align: center;
padding-bottom: 4px;
border-bottom: 1px solid #3c3c3c;
}
.equipment-slot {
border: 2px solid #3c3c3c;
border-radius: 4px;
padding: 6px;
background: #001100;
min-height: 50px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
}
.equipment-slot-main.equipped {
border-color: #00ff00;
background: #002200;
box-shadow: 0 0 5px rgba(0, 255, 0, 0.3);
}
.equipment-slot-appearance.equipped {
border-color: #00ffff;
background: #002222;
box-shadow: 0 0 5px rgba(0, 255, 255, 0.3);
}
.slot-label {
font-size: 10px;
color: #888;
margin-bottom: 4px;
}
.slot-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
position: relative;
width: 100%;
}
.slot-item .item-icon {
font-size: 20px;
}
.slot-item .item-name {
font-size: 10px;
color: #00ff00;
text-align: center;
}
.unequip-btn, .delete-btn {
width: 20px;
height: 20px;
padding: 0;
background: #ff0000;
border: 1px solid #ff0000;
border-radius: 3px;
color: white;
font-size: 10px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.unequip-btn:hover, .delete-btn:hover {
background: #cc0000;
transform: scale(1.1);
}
.delete-btn {
background: #aa0000;
border-color: #aa0000;
}
.delete-btn:hover {
background: #880000;
}
.slot-empty {
color: #666;
font-size: 11px;
}
.inventory-list {
margin-top: 10px;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 10px;
}
.inventory-list summary {
cursor: pointer;
user-select: none;
color: #4ec9b0;
font-weight: bold;
padding: 5px;
list-style-position: inside;
}
.inventory-list summary:hover {
background: rgba(78, 201, 176, 0.1);
border-radius: 4px;
}
.inventory-list[open] summary {
margin-bottom: 10px;
border-bottom: 1px solid #3c3c3c;
padding-bottom: 10px;
}
.inventory-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 10px;
max-height: 400px;
overflow-y: auto;
padding: 10px 0;
}
.inventory-item {
border: 2px solid #3c3c3c;
border-radius: 4px;
padding: 10px;
background: #001100;
display: flex;
flex-direction: column;
gap: 8px;
transition: all 0.2s;
}
/* 稀有度邊框顏色 */
.inventory-item.rarity-common {
border-color: #9d9d9d;
}
.inventory-item.rarity-uncommon {
border-color: #1eff00;
box-shadow: 0 0 5px rgba(30, 255, 0, 0.3);
}
.inventory-item.rarity-rare {
border-color: #0070dd;
box-shadow: 0 0 8px rgba(0, 112, 221, 0.4);
}
.inventory-item.rarity-epic {
border-color: #a335ee;
box-shadow: 0 0 10px rgba(163, 53, 238, 0.5);
}
.inventory-item.rarity-legendary {
border-color: #ff8000;
box-shadow: 0 0 15px rgba(255, 128, 0, 0.6);
background: linear-gradient(135deg, #001100 0%, #331100 100%);
}
.inventory-item .item-header {
display: flex;
align-items: center;
gap: 8px;
}
.inventory-item .item-icon {
font-size: 20px;
}
.inventory-item .item-name {
flex: 1;
color: #00ffff;
font-weight: bold;
font-size: 13px;
}
.inventory-item .item-count {
color: #888;
font-size: 11px;
}
.inventory-item .item-info {
font-size: 11px;
color: #aaa;
}
.inventory-item .item-type {
color: #4ec9b0;
margin-bottom: 4px;
}
.inventory-item .item-rarity {
font-weight: bold;
margin-bottom: 4px;
}
.inventory-item .drop-rate {
color: #888;
font-size: 10px;
font-weight: normal;
margin-left: 4px;
}
.inventory-item .item-description {
margin-bottom: 4px;
}
.inventory-item .item-durability {
color: #ffaa00;
}
.inventory-item .item-actions {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.action-btn {
padding: 4px 8px;
font-size: 10px;
background: #333;
border: 1px solid #555;
color: #fff;
border-radius: 3px;
cursor: pointer;
transition: all 0.2s;
}
.action-btn:hover:not(:disabled) {
background: #555;
border-color: #777;
}
.action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.use-btn:hover:not(:disabled) {
background: #00aa00;
border-color: #00ff00;
}
.equip-btn:hover:not(:disabled) {
background: #0066aa;
border-color: #0099ff;
}
.appearance-btn {
background: #333366 !important;
border-color: #6666aa !important;
}
.appearance-btn:hover:not(:disabled) {
background: #444477 !important;
border-color: #7777bb !important;
}
.repair-btn:hover:not(:disabled) {
background: #aa6600;
border-color: #ff9900;
}
/* 刪除區域樣式 */
.item-delete-section {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #333;
display: flex;
flex-direction: column;
gap: 6px;
}
.delete-controls {
display: flex;
align-items: center;
gap: 6px;
}
.delete-label {
color: #888;
font-size: 10px;
white-space: nowrap;
}
.delete-input {
width: 50px;
padding: 4px;
background: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 3px;
color: #fff;
font-size: 11px;
text-align: center;
}
.delete-input:focus {
outline: none;
border-color: #ff0000;
box-shadow: 0 0 5px rgba(255, 0, 0, 0.3);
}
.delete-max {
color: #666;
font-size: 10px;
}
.action-btn.delete-btn {
background: #aa0000 !important;
border-color: #aa0000 !important;
color: white !important;
width: 100%;
}
.action-btn.delete-btn:hover:not(:disabled) {
background: #cc0000 !important;
border-color: #cc0000 !important;
}
.inventory-empty {
grid-column: 1 / -1;
text-align: center;
color: #666;
padding: 20px;
}
.debug-input {
width: 100%;
padding: 5px;
margin-bottom: 5px;
background: #1e1e1e;
border: 1px solid #ff00ff;
color: #ff00ff;
font-size: 10px;
border-radius: 3px;
}
.debug-input:focus {
outline: none;
border-color: #ff00ff;
box-shadow: 0 0 5px rgba(255, 0, 255, 0.5);
}
/* 道具效果說明 */
.item-effects {
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid #333;
font-size: 10px;
}
.effects-title {
color: #4ec9b0;
font-weight: bold;
margin-bottom: 4px;
}
.effects-flat, .effects-percent {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 4px;
}
.effect-item {
padding: 2px 6px;
background: #001122;
border: 1px solid #3c3c3c;
border-radius: 3px;
color: #00ff00;
white-space: nowrap;
}
/* 道具 Tooltip 樣式 */
.item-tooltip {
position: fixed;
background: #000;
border: 2px solid #00ff00;
border-radius: 4px;
padding: 10px;
min-width: 200px;
max-width: 300px;
z-index: 10000;
box-shadow: 0 0 20px rgba(0, 255, 0, 0.5);
pointer-events: none;
font-size: 11px;
}
.tooltip-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
}
.tooltip-icon {
font-size: 20px;
}
.tooltip-name {
font-weight: bold;
font-size: 13px;
color: #00ffff;
}
.tooltip-rarity {
font-size: 10px;
margin-bottom: 4px;
font-weight: bold;
}
.tooltip-type {
color: #4ec9b0;
font-size: 10px;
margin-bottom: 6px;
}
.tooltip-description {
color: #aaa;
font-size: 10px;
margin-bottom: 8px;
line-height: 1.4;
}
.tooltip-effects {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #333;
}
.tooltip-effects-title {
color: #4ec9b0;
font-weight: bold;
font-size: 10px;
margin-bottom: 4px;
}
.tooltip-effect-item {
color: #00ff00;
font-size: 10px;
padding: 2px 0;
}
.tooltip-durability {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #333;
color: #ffaa00;
font-size: 10px;
font-weight: bold;
}
</style>