2025-11-23 18:03:56 +00:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="pet-system-container">
|
|
|
|
|
|
<!-- 名字輸入對話框 -->
|
|
|
|
|
|
<div v-if="showNameInput" class="name-input-modal">
|
|
|
|
|
|
<div class="modal-content">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<h2> 為你的寵物命名</h2>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
<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">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<h1>{{ petState?.name || 'LOADING...' }}</h1>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
<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>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<span class="stat-value">{{ Math.round(petState.hunger || 0) }}</span>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</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>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<span class="stat-value">{{ Math.round(petState.happiness || 0) }}</span>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-item">
|
|
|
|
|
|
<span class="stat-label">健康</span>
|
|
|
|
|
|
<div class="stat-bar">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<div class="stat-fill" :style="{ width: `${(petState.health / getMaxHealth(petState)) * 100 || 0}%` }"></div>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<span class="stat-value">{{ Math.round(petState.health || 0) }}/{{ getMaxHealth(petState) }}</span>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</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>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<div class="info-item">
|
|
|
|
|
|
<span class="info-label">身高</span>
|
|
|
|
|
|
<span class="info-value">{{ Math.round(petState.height || 0) }} cm</span>
|
|
|
|
|
|
</div>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
<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>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
|
<span class="info-label">力量 (STR)</span>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<span class="info-value">{{ Math.round(petState.effectiveStr || petState.str || 0) }}</span>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
|
<span class="info-label">智力 (INT)</span>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<span class="info-value">{{ Math.round(petState.effectiveInt || petState.int || 0) }}</span>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
|
<span class="info-label">敏捷 (DEX)</span>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</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>
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
<!-- 狀態標籤 -->
|
|
|
|
|
|
<div class="status-badges">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
2025-11-24 10:34:02 +00:00
|
|
|
|
|
|
|
|
|
|
<!-- 成就系統 -->
|
|
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 操作按鈕區 -->
|
|
|
|
|
|
<div class="action-panel">
|
|
|
|
|
|
<div class="action-group">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<h3> 寵物互動</h3>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
<div class="button-grid">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<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] 睡覺' }}
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</button>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="action-group">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<h3>[ROLL] 事件系統</h3>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
<div class="button-grid">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<!-- 快速觸發事件 -->
|
|
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="action-group">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<h3>[PRAY] 神明系統</h3>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
<div class="button-grid">
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<button @click="handlePray" :disabled="!isReady">[PRAY] 祈福</button>
|
2025-11-24 10:34:02 +00:00
|
|
|
|
<!-- 若神明有多個籤詩類型,顯示下拉選單 -->
|
|
|
|
|
|
<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>
|
2025-11-24 08:04:00 +00:00
|
|
|
|
<button @click="handleDrawLot" :disabled="!isReady">[LOT] 求籤</button>
|
|
|
|
|
|
<button @click="handleVerifyLot" :disabled="!isReady">[VERIFY] 驗證</button>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<button @click="handleThrowJiaobei" :disabled="!isReady">[JIAOBEI] 擲筊</button>
|
|
|
|
|
|
<button @click="handleListDeities" :disabled="!isReady">[LIST] 神明列表</button>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<!-- 切換神明 -->
|
|
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<!-- 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>
|
2025-11-24 10:34:02 +00:00
|
|
|
|
|
|
|
|
|
|
<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>
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
<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;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
background: rgba(0, 0, 0, 0.95);
|
2025-11-24 07:38:44 +00:00
|
|
|
|
color: white;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
z-index: 9999;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
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);
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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>
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
<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'
|
2025-11-24 10:34:02 +00:00
|
|
|
|
import { AchievementSystem } from '../core/achievement-system.js'
|
|
|
|
|
|
import { InventorySystem } from '../core/inventory-system.js'
|
2025-11-23 18:03:56 +00:00
|
|
|
|
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('')
|
2025-11-24 10:34:02 +00:00
|
|
|
|
const deityLotTypes = ref([])
|
|
|
|
|
|
const selectedLotType = ref('')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
|
|
|
|
|
|
// 系統實例
|
2025-11-24 10:34:02 +00:00
|
|
|
|
let petSystem, eventSystem, templeSystem, achievementSystem, inventorySystem
|
2025-11-23 18:03:56 +00:00
|
|
|
|
|
|
|
|
|
|
// 更新狀態顯示
|
|
|
|
|
|
function updatePetState() {
|
|
|
|
|
|
if (petSystem) {
|
|
|
|
|
|
petState.value = petSystem.getState()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
// 檢查當前階段是否可以執行某個動作
|
|
|
|
|
|
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 ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
// 格式化年齡
|
|
|
|
|
|
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`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
// 獲取所有加成
|
|
|
|
|
|
// 獲取所有加成
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
// 6. 成就加成
|
|
|
|
|
|
if (state.achievementBuffs) {
|
|
|
|
|
|
for (const [key, value] of Object.entries(state.achievementBuffs)) {
|
|
|
|
|
|
bonuses[key] = (bonuses[key] || 0) + value
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
// 顯示狀態(簡潔版)
|
|
|
|
|
|
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)
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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})`)
|
|
|
|
|
|
}
|
2025-11-23 18:03:56 +00:00
|
|
|
|
|
|
|
|
|
|
// 安全獲取數值,避免 undefined
|
|
|
|
|
|
const safeNum = (val) => (typeof val === 'number' && !isNaN(val) ? val.toFixed(0) : '0')
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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(', ') : '無'}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 啟動遊戲循環
|
|
|
|
|
|
function start() {
|
|
|
|
|
|
if (isRunning.value) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('[WARN] 遊戲循環已在運行中')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isRunning.value = true
|
|
|
|
|
|
// 移除 tick 回調中的自動輸出,只在有事件時才輸出
|
|
|
|
|
|
petSystem.startTickLoop(() => {
|
|
|
|
|
|
// 更新狀態顯示
|
|
|
|
|
|
updatePetState()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
eventSystem.startEventCheck()
|
2025-11-24 07:38:44 +00:00
|
|
|
|
|
|
|
|
|
|
// 初始化全局 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() 查看指令')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('[OK] 遊戲循環已啟動(靜默模式,只在事件觸發時輸出)')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 停止遊戲循環
|
|
|
|
|
|
function stop() {
|
|
|
|
|
|
if (!isRunning.value) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('[WARN] 遊戲循環未運行')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
petSystem.stopTickLoop()
|
|
|
|
|
|
eventSystem.stopEventCheck()
|
|
|
|
|
|
isRunning.value = false
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('[STOP] 遊戲循環已停止')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 餵食
|
|
|
|
|
|
async function feed(amount = 20) {
|
|
|
|
|
|
const result = await petSystem.feed(amount)
|
|
|
|
|
|
if (result.success) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[OK] 餵食 飢餓+${amount} 體重+${(amount * 0.5).toFixed(1)}g | STR+${result.strGain?.toFixed(2)}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[ERR] ${result.message}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 玩耍
|
|
|
|
|
|
async function play(amount = 15) {
|
|
|
|
|
|
const result = await petSystem.play(amount)
|
|
|
|
|
|
if (result.success) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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)}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[ERR] ${result.message}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理便便
|
|
|
|
|
|
async function clean() {
|
|
|
|
|
|
const result = await petSystem.cleanPoop()
|
|
|
|
|
|
if (result.success) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('[OK] 清理便便 +10 快樂')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[ERR] ${result.message}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 治療
|
|
|
|
|
|
async function heal(amount = 20) {
|
|
|
|
|
|
const result = await petSystem.heal(amount)
|
|
|
|
|
|
if (result.success) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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 ? ' (已治癒)' : ''}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[ERR] ${result.message}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 睡覺/起床
|
|
|
|
|
|
async function sleep() {
|
|
|
|
|
|
const result = await petSystem.toggleSleep()
|
|
|
|
|
|
if (result.success) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
if (result.message) {
|
|
|
|
|
|
console.log(`[OK] ${result.message}`)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log(`[OK] ${result.isSleeping ? '已入睡' : '已醒來'}`)
|
|
|
|
|
|
}
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[ERR] ${result.message}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 觸發事件
|
|
|
|
|
|
async function triggerEvent(eventId) {
|
|
|
|
|
|
const result = await eventSystem.triggerEvent(eventId)
|
|
|
|
|
|
if (result) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[OK] 事件 ${eventId} 觸發`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[ERR] 事件 ${eventId} 條件不滿足`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查看事件列表
|
|
|
|
|
|
async function listEvents() {
|
|
|
|
|
|
const events = await apiService.getEvents()
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查看事件歷史
|
|
|
|
|
|
function eventHistory() {
|
|
|
|
|
|
const history = eventSystem.getHistory()
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('\n[HISTORY] 事件歷史:')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
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) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[ERR] ${result.message}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切換神明
|
|
|
|
|
|
async function switchDeity(deityId) {
|
|
|
|
|
|
const result = await templeSystem.switchDeity(deityId)
|
|
|
|
|
|
if (result.success) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log(`[OK] 已切換到 ${result.deity.name}`)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
2025-11-24 07:38:44 +00:00
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 08:04:00 +00:00
|
|
|
|
// 全局变量存储当前抽中的签
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
// 查看神明列表
|
|
|
|
|
|
function listDeities() {
|
|
|
|
|
|
const deities = templeSystem.getDeities()
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('\n[PRAY] 神明列表:')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
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')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
// 抽籤(簡化版,兼容舊 API)
|
|
|
|
|
|
async function drawFortune() {
|
|
|
|
|
|
// 使用 drawLot 作為實現
|
|
|
|
|
|
const result = await handleDrawLot()
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
// 應用 Buff
|
|
|
|
|
|
async function applyBuffs() {
|
|
|
|
|
|
await eventSystem.applyBuffs()
|
|
|
|
|
|
eventSystem.getBuffManager().tick()
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('[OK] Buff 已更新')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
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) {
|
2025-11-24 10:34:02 +00:00
|
|
|
|
// 先創建成就系統
|
|
|
|
|
|
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
|
2025-11-23 18:03:56 +00:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await petSystem.initialize('tinyTigerCat')
|
|
|
|
|
|
// 如果有提供名字,更新寵物名字
|
|
|
|
|
|
if (petName) {
|
|
|
|
|
|
await petSystem.updateState({ name: petName })
|
|
|
|
|
|
}
|
|
|
|
|
|
await eventSystem.initialize()
|
|
|
|
|
|
await templeSystem.initialize()
|
2025-11-24 10:34:02 +00:00
|
|
|
|
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()
|
2025-11-23 18:03:56 +00:00
|
|
|
|
|
|
|
|
|
|
updatePetState()
|
|
|
|
|
|
isReady.value = true
|
2025-11-24 07:38:44 +00:00
|
|
|
|
systemStatus.value = '[OK] 系統已就緒'
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[OK] 系統已初始化')
|
|
|
|
|
|
console.log('[SWITCH] 自動啟動遊戲循環...')
|
|
|
|
|
|
|
|
|
|
|
|
// 自動啟動遊戲循環
|
|
|
|
|
|
start()
|
2025-11-23 18:03:56 +00:00
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
// 定期更新成就列表(每 5 秒)
|
|
|
|
|
|
setInterval(async () => {
|
|
|
|
|
|
if (achievementSystem) {
|
|
|
|
|
|
await achievementSystem.checkAndUnlockAchievements()
|
|
|
|
|
|
await updateAchievementList()
|
|
|
|
|
|
}
|
|
|
|
|
|
// 更新背包列表
|
|
|
|
|
|
if (inventorySystem) {
|
|
|
|
|
|
await updateInventoryList()
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 5000)
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
showStatus()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 如果沒有名字,顯示輸入對話框
|
|
|
|
|
|
if (error.message && error.message.includes('名字')) {
|
|
|
|
|
|
showNameInput.value = true
|
|
|
|
|
|
systemStatus.value = '請為寵物命名'
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
systemStatus.value = '[ERR] 初始化失敗: ' + error.message
|
2025-11-23 18:03:56 +00:00
|
|
|
|
console.error(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { petSystem, eventSystem, templeSystem }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 按鈕處理函數
|
|
|
|
|
|
async function handleFeed() {
|
|
|
|
|
|
await feed(20)
|
|
|
|
|
|
updatePetState()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
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`)
|
|
|
|
|
|
}
|
2025-11-23 18:03:56 +00:00
|
|
|
|
updatePetState()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleClean() {
|
|
|
|
|
|
await clean()
|
|
|
|
|
|
updatePetState()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleHeal() {
|
|
|
|
|
|
await heal(20)
|
|
|
|
|
|
updatePetState()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleSleep() {
|
|
|
|
|
|
await sleep()
|
|
|
|
|
|
updatePetState()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleShowStatus() {
|
|
|
|
|
|
showStatus()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
function handleCheckEvolution() {
|
|
|
|
|
|
if (window.checkEvolution) {
|
|
|
|
|
|
window.checkEvolution()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleTestAllEvents() {
|
|
|
|
|
|
if (window.testAllEvents) {
|
|
|
|
|
|
window.testAllEvents()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
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) {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('[OK] ' + result.message)
|
2025-11-23 18:03:56 +00:00
|
|
|
|
|
|
|
|
|
|
// 重置所有狀態
|
|
|
|
|
|
petState.value = null
|
|
|
|
|
|
isReady.value = false
|
|
|
|
|
|
isRunning.value = false
|
|
|
|
|
|
systemStatus.value = '寵物已刪除,請重新建立'
|
|
|
|
|
|
|
|
|
|
|
|
// 清除事件系統歷史
|
|
|
|
|
|
if (eventSystem) {
|
|
|
|
|
|
eventSystem.eventHistory = []
|
|
|
|
|
|
eventSystem.getBuffManager().buffs = []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 顯示名字輸入對話框
|
|
|
|
|
|
inputPetName.value = ''
|
|
|
|
|
|
showNameInput.value = true
|
|
|
|
|
|
} else {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('[ERR] 刪除失敗')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('刪除寵物時發生錯誤:', error)
|
|
|
|
|
|
alert('刪除寵物時發生錯誤,請重試')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 幫助
|
|
|
|
|
|
function help() {
|
|
|
|
|
|
console.log('\n' + '='.repeat(50))
|
|
|
|
|
|
console.log('📖 可用命令列表')
|
|
|
|
|
|
console.log('='.repeat(50))
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('\n[PLAY] 遊戲控制:')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
console.log(' start() - 啟動遊戲循環')
|
|
|
|
|
|
console.log(' stop() - 停止遊戲循環')
|
|
|
|
|
|
console.log(' showStatus() - 顯示當前狀態')
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('\n 寵物互動:')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
console.log(' feed(amount) - 餵食(預設 +20)')
|
|
|
|
|
|
console.log(' play(amount) - 玩耍(預設 +15)')
|
|
|
|
|
|
console.log(' clean() - 清理便便')
|
|
|
|
|
|
console.log(' heal(amount) - 治療(預設 +20)')
|
|
|
|
|
|
console.log(' sleep() - 睡覺/起床')
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('\n[ROLL] 事件系統:')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
console.log(' triggerEvent(id) - 手動觸發事件')
|
|
|
|
|
|
console.log(' listEvents() - 查看所有事件')
|
|
|
|
|
|
console.log(' eventHistory() - 查看事件歷史')
|
|
|
|
|
|
console.log(' applyBuffs() - 手動應用 Buff')
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('\n[PRAY] 神明系統:')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
console.log(' pray() - 祈福(每日 3 次)')
|
|
|
|
|
|
console.log(' drawFortune() - 抽籤')
|
|
|
|
|
|
console.log(' switchDeity(id) - 切換神明')
|
|
|
|
|
|
console.log(' listDeities() - 查看神明列表')
|
2025-11-24 07:38:44 +00:00
|
|
|
|
console.log('\n[TIP] 提示:')
|
2025-11-23 18:03:56 +00:00
|
|
|
|
console.log(' - 所有數值操作都會同步到 API(mock 模式使用 localStorage)')
|
|
|
|
|
|
console.log(' - 事件每 10 秒自動檢查(10% 機率觸發)')
|
|
|
|
|
|
console.log(' - 遊戲循環每 3 秒執行一次 tick')
|
|
|
|
|
|
console.log(' - 輸入 help() 再次查看此列表')
|
|
|
|
|
|
console.log('='.repeat(50) + '\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 07:38:44 +00:00
|
|
|
|
// 測試加成系統
|
|
|
|
|
|
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() 來驗證實際效果')
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-23 18:03:56 +00:00
|
|
|
|
// 掛載到 window
|
2025-11-24 07:38:44 +00:00
|
|
|
|
// Debug UI 狀態
|
|
|
|
|
|
const showDebug = ref(false)
|
2025-11-24 10:34:02 +00:00
|
|
|
|
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({}) // 每個道具的刪除數量
|
2025-11-24 07:38:44 +00:00
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
// 成就相關函數
|
|
|
|
|
|
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)}%)`
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
2025-11-24 10:34:02 +00:00
|
|
|
|
return `${Math.round(progress.progress)}%`
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
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
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
2025-11-24 10:34:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新稀有度映射(只在第一次或需要時更新)
|
|
|
|
|
|
if (Object.keys(itemRarity.value).length === 0) {
|
|
|
|
|
|
for (const [rarity, rarityData] of Object.entries(ITEM_RARITY)) {
|
|
|
|
|
|
itemRarity.value[rarity] = rarityData
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
2025-11-24 10:34:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收集所有已裝備的道具 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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-24 10:34:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 從 inventorySystem 獲取道具數據(已從 API 載入)
|
|
|
|
|
|
const itemsData = inventorySystem.items || {}
|
|
|
|
|
|
|
|
|
|
|
|
for (const [itemId, data] of Object.entries(inventory)) {
|
|
|
|
|
|
// 優先使用 API 載入的道具數據
|
|
|
|
|
|
let item = itemsData[itemId] || ITEMS[itemId]
|
2025-11-24 07:38:44 +00:00
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
if (item) {
|
|
|
|
|
|
// 如果道具沒有 dropRate,使用稀有度的默認掉落率
|
|
|
|
|
|
if (item.dropRate === undefined && item.rarity) {
|
|
|
|
|
|
const rarityData = ITEM_RARITY[item.rarity]
|
|
|
|
|
|
if (rarityData) {
|
|
|
|
|
|
item = { ...item, dropRate: rarityData.dropRate }
|
|
|
|
|
|
}
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
// 只有消耗品可以堆疊,裝備類每件分開顯示
|
|
|
|
|
|
if (item.type === 'consumable') {
|
|
|
|
|
|
// 消耗品:顯示堆疊數量
|
|
|
|
|
|
items[itemId] = {
|
|
|
|
|
|
item,
|
|
|
|
|
|
count: data.count,
|
|
|
|
|
|
totalCount: data.count,
|
|
|
|
|
|
equippedCount: 0,
|
|
|
|
|
|
isStackable: true
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
2025-11-24 10:34:02 +00:00
|
|
|
|
} 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 用於操作
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
2025-11-24 10:34:02 +00:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
2025-11-24 10:34:02 +00:00
|
|
|
|
|
|
|
|
|
|
// 清理已不存在的道具的刪除數量
|
|
|
|
|
|
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 支持繁体中文 */
|
2025-11-24 07:38:44 +00:00
|
|
|
|
@import url('https://cdn.jsdelivr.net/npm/zpix-pixel-font@latest/dist/Zpix.css');
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
* {
|
|
|
|
|
|
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;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
/* 滚动条样式 */
|
|
|
|
|
|
::-webkit-scrollbar {
|
|
|
|
|
|
width: 12px;
|
|
|
|
|
|
height: 12px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
::-webkit-scrollbar-track {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
background: #000;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
border: 1px solid #00ff00;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
::-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 {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
color: #00ffff;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
text-shadow: 0 0 5px #00ffff;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.info-sub {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
color: #00aa00;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
font-size: 8px;
|
|
|
|
|
|
margin-top: 2px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.modal-content {
|
|
|
|
|
|
padding: 30px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
border: 2px solid #4ec9b0;
|
|
|
|
|
|
min-width: 400px;
|
|
|
|
|
|
max-width: 90%;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.modal-content h2 {
|
|
|
|
|
|
color: #4ec9b0;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-content p {
|
|
|
|
|
|
color: #d4d4d4;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
margin-bottom: 20px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
text-align: center;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.name-input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
background: #1e1e1e;
|
|
|
|
|
|
border: 2px solid #3c3c3c;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
color: #d4d4d4;
|
|
|
|
|
|
font-size: 1.1em;
|
|
|
|
|
|
margin-bottom: 20px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.name-input:focus {
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
border-color: #4ec9b0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-buttons {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
gap: 10px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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 {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
margin-bottom: 10px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
border-bottom: 1px solid #3c3c3c;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
/* 小按鈕網格 */
|
|
|
|
|
|
.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 {
|
2025-11-23 18:03:56 +00:00
|
|
|
|
display: flex;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
justify-content: space-around;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
background: #001100;
|
|
|
|
|
|
border: 1px solid #00ff00;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 12px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.achievement-stats span {
|
|
|
|
|
|
color: #00ff00;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.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 {
|
2025-11-23 18:03:56 +00:00
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.achievement-name {
|
|
|
|
|
|
color: #00ffff;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.achievement-item.locked .achievement-name {
|
|
|
|
|
|
color: #888;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.achievement-desc {
|
|
|
|
|
|
color: #aaa;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
margin-bottom: 6px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.achievement-progress {
|
|
|
|
|
|
margin-top: 6px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.progress-bar {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 6px;
|
|
|
|
|
|
background: #000;
|
|
|
|
|
|
border: 1px solid #333;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
margin-bottom: 4px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.progress-fill {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background: #00ff00;
|
|
|
|
|
|
transition: width 0.3s;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.progress-text {
|
|
|
|
|
|
color: #888;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.achievement-reward {
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
padding-top: 6px;
|
|
|
|
|
|
border-top: 1px solid #333;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.reward-label {
|
|
|
|
|
|
color: #00aa00;
|
|
|
|
|
|
margin-right: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reward-item {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
color: #00ff00;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
margin-right: 8px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
/* 背包系統樣式 */
|
|
|
|
|
|
.equipment-slots {
|
|
|
|
|
|
margin-bottom: 15px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.equipment-slots h4 {
|
|
|
|
|
|
color: #4ec9b0;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
font-size: 14px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
/* 稀有度圖例 */
|
|
|
|
|
|
.rarity-legend {
|
|
|
|
|
|
background: #001100;
|
|
|
|
|
|
border: 2px solid #3c3c3c;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
margin-bottom: 15px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-title {
|
|
|
|
|
|
color: #4ec9b0;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 8px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-items {
|
2025-11-23 18:03:56 +00:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 6px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-item {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
padding: 4px 8px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
border-radius: 3px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
font-size: 10px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
border: 2px solid;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-item.rarity-common {
|
|
|
|
|
|
border-color: #9d9d9d;
|
|
|
|
|
|
color: #9d9d9d;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-item.rarity-uncommon {
|
|
|
|
|
|
border-color: #1eff00;
|
|
|
|
|
|
color: #1eff00;
|
|
|
|
|
|
box-shadow: 0 0 5px rgba(30, 255, 0, 0.3);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-item.rarity-rare {
|
|
|
|
|
|
border-color: #0070dd;
|
|
|
|
|
|
color: #0070dd;
|
|
|
|
|
|
box-shadow: 0 0 8px rgba(0, 112, 221, 0.4);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-item.rarity-epic {
|
|
|
|
|
|
border-color: #a335ee;
|
|
|
|
|
|
color: #a335ee;
|
|
|
|
|
|
box-shadow: 0 0 10px rgba(163, 53, 238, 0.5);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-item.rarity-legendary {
|
|
|
|
|
|
border-color: #ff8000;
|
|
|
|
|
|
color: #ff8000;
|
|
|
|
|
|
box-shadow: 0 0 15px rgba(255, 128, 0, 0.6);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.legend-note {
|
|
|
|
|
|
color: #888;
|
|
|
|
|
|
font-size: 9px;
|
|
|
|
|
|
font-style: italic;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.slots-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 10px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.equipment-slot-container {
|
|
|
|
|
|
border: 2px solid #3c3c3c;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
background: #000511;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 6px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.slot-header {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: #4ec9b0;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding-bottom: 4px;
|
|
|
|
|
|
border-bottom: 1px solid #3c3c3c;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.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;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.equipment-slot-main.equipped {
|
|
|
|
|
|
border-color: #00ff00;
|
|
|
|
|
|
background: #002200;
|
|
|
|
|
|
box-shadow: 0 0 5px rgba(0, 255, 0, 0.3);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.equipment-slot-appearance.equipped {
|
|
|
|
|
|
border-color: #00ffff;
|
|
|
|
|
|
background: #002222;
|
|
|
|
|
|
box-shadow: 0 0 5px rgba(0, 255, 255, 0.3);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.slot-label {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
font-size: 10px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
color: #888;
|
|
|
|
|
|
margin-bottom: 4px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.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;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
color: #00ff00;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
text-align: center;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.unequip-btn, .delete-btn {
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
background: #ff0000;
|
|
|
|
|
|
border: 1px solid #ff0000;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
color: white;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
font-size: 10px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
transition: all 0.2s;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.unequip-btn:hover, .delete-btn:hover {
|
|
|
|
|
|
background: #cc0000;
|
|
|
|
|
|
transform: scale(1.1);
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.delete-btn {
|
|
|
|
|
|
background: #aa0000;
|
|
|
|
|
|
border-color: #aa0000;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.delete-btn:hover {
|
|
|
|
|
|
background: #880000;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.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 {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
margin-bottom: 10px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
border-bottom: 1px solid #3c3c3c;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
max-height: 400px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
padding: 10px 0;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item {
|
|
|
|
|
|
border: 2px solid #3c3c3c;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
background: #001100;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
transition: all 0.2s;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
/* 稀有度邊框顏色 */
|
|
|
|
|
|
.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);
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.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%);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-header {
|
2025-11-23 18:03:56 +00:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
gap: 8px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-icon {
|
|
|
|
|
|
font-size: 20px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-name {
|
|
|
|
|
|
flex: 1;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
color: #00ffff;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
font-size: 13px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-count {
|
|
|
|
|
|
color: #888;
|
|
|
|
|
|
font-size: 11px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-info {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: #aaa;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-type {
|
|
|
|
|
|
color: #4ec9b0;
|
|
|
|
|
|
margin-bottom: 4px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-rarity {
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 4px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .drop-rate {
|
|
|
|
|
|
color: #888;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
font-weight: normal;
|
|
|
|
|
|
margin-left: 4px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-description {
|
|
|
|
|
|
margin-bottom: 4px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-durability {
|
|
|
|
|
|
color: #ffaa00;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.inventory-item .item-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
flex-wrap: wrap;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.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;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.action-btn:hover:not(:disabled) {
|
|
|
|
|
|
background: #555;
|
|
|
|
|
|
border-color: #777;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.action-btn:disabled {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: not-allowed;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.use-btn:hover:not(:disabled) {
|
|
|
|
|
|
background: #00aa00;
|
|
|
|
|
|
border-color: #00ff00;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.equip-btn:hover:not(:disabled) {
|
|
|
|
|
|
background: #0066aa;
|
|
|
|
|
|
border-color: #0099ff;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.appearance-btn {
|
|
|
|
|
|
background: #333366 !important;
|
|
|
|
|
|
border-color: #6666aa !important;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.appearance-btn:hover:not(:disabled) {
|
|
|
|
|
|
background: #444477 !important;
|
|
|
|
|
|
border-color: #7777bb !important;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.repair-btn:hover:not(:disabled) {
|
|
|
|
|
|
background: #aa6600;
|
|
|
|
|
|
border-color: #ff9900;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
/* 刪除區域樣式 */
|
|
|
|
|
|
.item-delete-section {
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
border-top: 1px solid #333;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 6px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.delete-controls {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
display: flex;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.delete-label {
|
|
|
|
|
|
color: #888;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
white-space: nowrap;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.delete-input {
|
|
|
|
|
|
width: 50px;
|
|
|
|
|
|
padding: 4px;
|
|
|
|
|
|
background: #1e1e1e;
|
|
|
|
|
|
border: 1px solid #3c3c3c;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
text-align: center;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.delete-input:focus {
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
border-color: #ff0000;
|
|
|
|
|
|
box-shadow: 0 0 5px rgba(255, 0, 0, 0.3);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.delete-max {
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-size: 10px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.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;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
text-align: center;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
color: #666;
|
|
|
|
|
|
padding: 20px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.debug-input {
|
2025-11-23 18:03:56 +00:00
|
|
|
|
width: 100%;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
padding: 5px;
|
|
|
|
|
|
margin-bottom: 5px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
background: #1e1e1e;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
border: 1px solid #ff00ff;
|
|
|
|
|
|
color: #ff00ff;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
border-radius: 3px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.debug-input:focus {
|
2025-11-23 18:03:56 +00:00
|
|
|
|
outline: none;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
border-color: #ff00ff;
|
|
|
|
|
|
box-shadow: 0 0 5px rgba(255, 0, 255, 0.5);
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
/* 道具效果說明 */
|
|
|
|
|
|
.item-effects {
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
padding-top: 6px;
|
|
|
|
|
|
border-top: 1px solid #333;
|
|
|
|
|
|
font-size: 10px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.effects-title {
|
|
|
|
|
|
color: #4ec9b0;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 4px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.effects-flat, .effects-percent {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-top: 4px;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.effect-item {
|
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
|
background: #001122;
|
|
|
|
|
|
border: 1px solid #3c3c3c;
|
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
|
color: #00ff00;
|
|
|
|
|
|
white-space: nowrap;
|
2025-11-23 18:03:56 +00:00
|
|
|
|
}
|
2025-11-24 07:38:44 +00:00
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
/* 道具 Tooltip 樣式 */
|
|
|
|
|
|
.item-tooltip {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
background: #000;
|
|
|
|
|
|
border: 2px solid #00ff00;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 10px;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
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;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.tooltip-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tooltip-icon {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tooltip-name {
|
2025-11-24 07:38:44 +00:00
|
|
|
|
font-weight: bold;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #00ffff;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.tooltip-rarity {
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
font-weight: bold;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.tooltip-type {
|
|
|
|
|
|
color: #4ec9b0;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
margin-bottom: 6px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.tooltip-description {
|
|
|
|
|
|
color: #aaa;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
line-height: 1.4;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.tooltip-effects {
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
border-top: 1px solid #333;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.tooltip-effects-title {
|
|
|
|
|
|
color: #4ec9b0;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
font-weight: bold;
|
2025-11-24 10:34:02 +00:00
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
margin-bottom: 4px;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 10:34:02 +00:00
|
|
|
|
.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;
|
2025-11-24 07:38:44 +00:00
|
|
|
|
}
|
2025-11-23 18:03:56 +00:00
|
|
|
|
</style>
|