外觀套件
-
+
{{ getEquippedItemIcon(slotKey, 'appearance') }}
{{ getEquippedItemName(slotKey, 'appearance') }}
+
+
+
+
💰 商店
+
+ 您的金幣:
+ {{ petState.coins || 0 }} 💰
+ 🔄
+
+
+
+
+
+ 購買
+
+
+ 賣出
+
+
+
+
+
+
+
+
+ 全部
+
+
+ {{ cat.icon }} {{ cat.name }}
+
+
+
+
+
+
+
{{ getItemIcon(shopItem.itemId) }}
+
+
{{ getItemName(shopItem.itemId) }}
+
💰 {{ shopItem.price }}
+
+
+ 購買
+
+
+
+
+
+
+
+
選擇要賣出的道具
+
+
+
{{ itemData.item.icon }}
+
+
{{ itemData.item.name }}
+
持有: {{ itemData.count || 1 }}
+
+
+ 賣出
+
+
+
+
+
+
關閉
+
+
+
+
+
+
+
⚔️ 選擇冒險區域
+
+
+
+
{{ adv.description }}
+
+ 消耗:
+ 🍖 {{ adv.cost.hunger }}
+ 😊 {{ adv.cost.happiness }}
+
+
+ 要求:
+
+ {{ key.toUpperCase() }} {{ val }}
+
+
+
+ 出發!
+
+
+
+
關閉
+
+
+
+
+
+
+
+
+
+
🎉 冒險完成!
+
+
+ 金幣
+ +{{ adventureResult.rewards.coins }} 💰
+
+
+
獲得道具:
+
+ {{ item.name }} x{{ item.count }}
+
+
+
+
+
確定
+
+
+
[STATUS] 查看狀態
+
+ 💰 商店
+
+
+ ⚔️ 探險
+
+
+ 🎮 玩耍
+
[CHECK] 進化檢查
[DEL] 刪除寵物
@@ -689,6 +903,7 @@ import { EventSystem } from '../core/event-system.js'
import { TempleSystem } from '../core/temple-system.js'
import { AchievementSystem } from '../core/achievement-system.js'
import { InventorySystem } from '../core/inventory-system.js'
+import { AdventureSystem } from '../core/adventure-system.js'
import { ApiService } from '../core/api-service.js'
// 創建 API 服務
@@ -709,7 +924,7 @@ const deityLotTypes = ref([])
const selectedLotType = ref('')
// 系統實例
-let petSystem, eventSystem, templeSystem, achievementSystem, inventorySystem
+let petSystem, eventSystem, templeSystem, achievementSystem, inventorySystem, adventureSystem
// 更新狀態顯示
function updatePetState() {
@@ -1357,6 +1572,7 @@ async function init(petName = null) {
petSystem = new PetSystem(apiService, achievementSystem, inventorySystem)
eventSystem = new EventSystem(petSystem, apiService, achievementSystem, inventorySystem)
templeSystem = new TempleSystem(petSystem, apiService, achievementSystem)
+ adventureSystem = new AdventureSystem(petSystem, inventorySystem, apiService)
// 更新成就系統的引用
achievementSystem.petSystem = petSystem
@@ -1440,7 +1656,13 @@ async function init(petName = null) {
systemStatus.value = '請為寵物命名'
} else {
systemStatus.value = '[ERR] 初始化失敗: ' + error.message
- console.error(error)
+ console.error('[初始化錯誤] 完整錯誤:', error)
+ console.error('[初始化錯誤] 堆棧:', error.stack)
+ // 嘗試清除可能損壞的數據並重試
+ if (confirm('初始化失敗,是否清除所有數據並重新開始?')) {
+ localStorage.clear()
+ location.reload()
+ }
}
}
@@ -1558,6 +1780,20 @@ async function handleDeletePet() {
eventSystem.getBuffManager().buffs = []
}
+ // 重置背包系統(清空道具和裝備)
+ if (inventorySystem) {
+ await inventorySystem.resetInventory()
+ await updateInventoryList()
+ console.log('[OK] 背包已重置')
+ }
+
+ // 重置成就系統
+ if (achievementSystem) {
+ await achievementSystem.reset()
+ await updateAchievementList()
+ console.log('[OK] 成就已重置')
+ }
+
// 顯示名字輸入對話框
inputPetName.value = ''
showNameInput.value = true
@@ -1854,24 +2090,24 @@ async function updateInventoryList() {
isStackable: true
}
} else {
- // 裝備類:每件分開顯示
+ // 裝備類:每件分開顯示(包括已裝備的)
if (data.items && data.items.length > 0) {
- // 為每個未裝備的實例創建一個條目
+ // 為每個實例創建一個條目(不過濾已裝備的)
for (const instance of data.items) {
const instanceKey = `${itemId}_${instance.id}`
- if (!equippedItemIds.has(instanceKey)) {
- // 使用實例 ID 作為 key,這樣每件裝備都會單獨顯示
- const instanceItemId = `${itemId}_${instance.id}`
- items[instanceItemId] = {
- item,
- count: 1,
- totalCount: 1,
- equippedCount: 0,
- isStackable: false,
- instanceId: instance.id,
- instance: instance, // 保存實例數據用於顯示耐久度等
- originalItemId: itemId // 保存原始 itemId 用於操作
- }
+ // 使用實例ID作為key,這樣每件裝備都會單獨顯示
+ const instanceItemId = `${itemId}_${instance.id}`
+ const isEquipped = equippedItemIds.has(instanceKey)
+ items[instanceItemId] = {
+ item,
+ count: 1,
+ totalCount: 1,
+ equippedCount: isEquipped ? 1 : 0,
+ isStackable: false,
+ instanceId: instance.id,
+ instance: instance, // 保存實例數據用於顯示耐久度等
+ originalItemId: itemId, // 保存原始 itemId 用於操作
+ isEquipped: isEquipped // 標記是否已裝備
}
}
}
@@ -1934,6 +2170,53 @@ const itemTooltip = ref(false)
const tooltipContent = ref(null)
const tooltipPosition = ref({ x: 0, y: 0 })
+// 冒險系統狀態
+const showAdventurePanel = ref(false)
+const adventures = ref([])
+const currentAdventure = ref(null)
+const adventureLogs = ref([])
+const adventureResult = ref(null)
+const showAdventureResult = ref(false)
+
+// 商店系統狀態
+const showShopPanel = ref(false)
+const shopItems = ref([])
+const selectedCategory = ref('all')
+const shopMode = ref('buy') // 'buy' 或 'sell'
+
+// 系統狀態
+// const isReady = ref(false) // 已在上方聲明
+
+// 計算當前表情(根據寵物狀態)
+const currentEmotion = computed(() => {
+ if (!petState.value) return { emoji: '🥚', name: 'egg', animation: '' }
+
+ const state = petState.value
+
+ // 優先級從高到低
+ if (state.isDead || state.dyingSeconds > 0) {
+ return { emoji: '💀', name: 'dying', animation: 'shake' }
+ }
+ if (state.isSick) {
+ return { emoji: '🤢', name: 'sick', animation: 'wiggle' }
+ }
+ if (state.isSleeping) {
+ return { emoji: '😴', name: 'sleeping', animation: 'float' }
+ }
+ if (state.hunger < 30) {
+ return { emoji: '😋', name: 'hungry', animation: 'bounce' }
+ }
+ if (state.happiness < 30) {
+ return { emoji: '😢', name: 'sad', animation: '' }
+ }
+ if (state.happiness > 70 && state.health > 50) {
+ return { emoji: '😊', name: 'happy', animation: 'bounce' }
+ }
+
+ // 默認普通狀態
+ return { emoji: '😐', name: 'normal', animation: '' }
+})
+
function showItemTooltip(event, item, itemId, instance = null) {
if (!item) return
@@ -1997,6 +2280,36 @@ function hideItemTooltip() {
tooltipContent.value = null
}
+// 顯示裝備槽位的 tooltip
+function showEquipmentTooltip(event, slot, type) {
+ if (!inventorySystem) return
+
+ const itemId = getEquippedItemId(slot, type)
+ if (!itemId) return
+
+ const item = inventorySystem.items[itemId]
+ if (!item) return
+
+ // 獲取裝備實例數據(用於顯示耐久度)
+ const equipped = inventorySystem.getEquipped()
+ const slotData = equipped[slot]
+ let instance = null
+
+ if (slotData && slotData[type] && typeof slotData[type] === 'object') {
+ const instanceId = slotData[type].instanceId
+ if (instanceId) {
+ const inventory = inventorySystem.getInventory()
+ const itemData = inventory[itemId]
+ if (itemData && itemData.items) {
+ instance = itemData.items.find(i => i.id === instanceId)
+ }
+ }
+ }
+
+ // 使用現有的 showItemTooltip 函數邏輯
+ showItemTooltip(event, item, itemId, instance)
+}
+
// 獲取屬性名稱(用於顯示效果)
function getStatName(key) {
const names = {
@@ -2074,9 +2387,9 @@ function getEquippedItemIcon(slot, type = 'equipment') {
const itemId = getEquippedItemId(slot, type)
if (!itemId) return '📦'
- // 從 inventoryItems 中查找
- const itemData = inventoryItems.value[itemId]
- return itemData?.item?.icon || '📦'
+ // 直接從 inventorySystem.items 中查找(裝備後的道具可能不在 inventoryItems 中)
+ const item = inventorySystem.items[itemId]
+ return item?.icon || '📦'
}
// 獲取裝備的道具名稱
@@ -2085,9 +2398,9 @@ function getEquippedItemName(slot, type = 'equipment') {
const itemId = getEquippedItemId(slot, type)
if (!itemId) return ''
- // 從 inventoryItems 中查找
- const itemData = inventoryItems.value[itemId]
- return itemData?.item?.name || ''
+ // 直接從 inventorySystem.items 中查找(裝備後的道具可能不在 inventoryItems 中)
+ const item = inventorySystem.items[itemId]
+ return item?.name || ''
}
// 檢查槽位是否有裝備(支持新結構)
@@ -2244,6 +2557,38 @@ async function handleRepair(itemId) {
}
}
+// 處理刪除道具
+async function handleDeleteItem(itemId, count = 1) {
+ if (!inventorySystem) return
+
+ try {
+ // 如果是裝備實例ID(格式:itemId_instanceId),需要提取原始 itemId
+ let actualItemId = itemId
+ const itemData = inventoryItems.value[itemId]
+ if (itemData && itemData.originalItemId) {
+ actualItemId = itemData.originalItemId
+ }
+
+ const result = await inventorySystem.removeItem(actualItemId, count)
+ if (result.success) {
+ await updateInventoryList()
+ updatePetState()
+ console.log(`[UI] 刪除了道具: ${actualItemId} x${count}`)
+ } else {
+ console.error(`[UI] 刪除道具失敗: ${result.message}`)
+ }
+ } catch (error) {
+ console.error('[UI] 刪除道具錯誤:', error)
+ }
+}
+
+// 更新刪除數量
+function updateDeleteCount(itemId, value, maxCount) {
+ const numValue = parseInt(value) || 1
+ deleteCounts.value[itemId] = Math.max(1, Math.min(numValue, maxCount))
+}
+
+
// Debug 成就功能
async function debugUnlockAchievement() {
if (!achievementSystem || !debugAchievementId.value) {
@@ -2453,6 +2798,178 @@ const handleKeydown = (e) => {
}
}
+// 冒險系統相关方法
+async function openAdventurePanel() {
+ if (!adventureSystem) return
+ adventures.value = adventureSystem.getAdventures()
+ showAdventurePanel.value = true
+}
+
+function closeAdventurePanel() {
+ showAdventurePanel.value = false
+}
+
+async function handleStartAdventure(adventureId) {
+ if (!adventureSystem) return
+
+ // 設置回調
+ adventureSystem.onLogUpdate = (logs) => {
+ adventureLogs.value = [...logs]
+ // 自動滾動到底部
+ nextTick(() => {
+ const logContainer = document.querySelector('.adventure-logs')
+ if (logContainer) {
+ logContainer.scrollTop = logContainer.scrollHeight
+ }
+ })
+ }
+
+ adventureSystem.onAdventureComplete = (result) => {
+ adventureResult.value = result
+ showAdventureResult.value = true
+ currentAdventure.value = null
+ updateInventoryList() // 更新背包顯示獎勵
+ updatePetState() // 更新寵物狀態(經驗/屬性)
+ }
+
+ const result = await adventureSystem.startAdventure(adventureId)
+ if (result.success) {
+ currentAdventure.value = result.adventure
+ adventureLogs.value = []
+ showAdventurePanel.value = false // 關閉選擇面板,顯示進行中面板
+ } else {
+ alert(result.message)
+ }
+}
+
+function closeAdventureResult() {
+ showAdventureResult.value = false
+ adventureResult.value = null
+}
+
+function getAdventureProgress() {
+ if (!currentAdventure.value) return 0
+ const total = currentAdventure.value.duration
+ const elapsed = Date.now() - currentAdventure.value.startTime
+ return Math.min(100, (elapsed / total) * 100)
+}
+
+// 監聽冒險進度
+setInterval(() => {
+ if (currentAdventure.value) {
+ // 強制更新視圖
+ currentAdventure.value = { ...currentAdventure.value }
+ }
+}, 1000)
+
+// 商店系統相關方法
+async function openShopPanel() {
+ const { generateShopItems, SHOP_CATEGORIES } = await import('../data/shop.js')
+ shopItems.value = generateShopItems() // 隨機生成商品
+ shopCategories.value = SHOP_CATEGORIES
+ showShopPanel.value = true
+}
+
+function closeShopPanel() {
+ showShopPanel.value = false
+}
+
+// 刷新商店商品
+async function refreshShop() {
+ const { generateShopItems } = await import('../data/shop.js')
+ shopItems.value = generateShopItems()
+ alert('商店已刷新!')
+}
+
+const shopCategories = ref({})
+
+const filteredShopItems = computed(() => {
+ if (selectedCategory.value === 'all') {
+ return shopItems.value
+ }
+ return shopItems.value.filter(item => item.category === selectedCategory.value)
+})
+
+async function buyItem(shopItem) {
+ const currentCoins = petState.value.coins || 0
+
+ if (currentCoins < shopItem.price) {
+ alert('金幣不足!')
+ return
+ }
+
+ if (shopItem.stock !== -1 && shopItem.stock <= 0) {
+ alert('商品已售罄!')
+ return
+ }
+
+ // 扣除金幣
+ await petSystem.updateState({
+ coins: currentCoins - shopItem.price
+ })
+
+ // 添加道具到背包
+ await inventorySystem.addItem(shopItem.itemId, 1)
+
+ // 更新庫存(如果有限)
+ if (shopItem.stock !== -1) {
+ shopItem.stock -= 1
+ }
+
+ // 更新狀態顯示
+ updatePetState()
+ updateInventoryList()
+
+ alert(`成功購買 ${getItemName(shopItem.itemId)}!`)
+}
+
+// 賣出道具
+async function sellItem(itemId, instanceId = null) {
+ const { SHOP_POOL } = await import('../data/shop.js')
+
+ // 提取原始 itemId (移除 _instanceId 後綴)
+ const originalItemId = itemId.includes('_') ? itemId.split('_')[0] : itemId
+
+ // 查找該道具的賣出價格
+ const poolItem = SHOP_POOL.find(p => p.itemId === originalItemId)
+ if (!poolItem || !poolItem.sellPrice) {
+ alert('此道具無法出售')
+ return
+ }
+
+ const sellPrice = poolItem.sellPrice
+
+ // 確認
+ if (!confirm(`確定要以 ${sellPrice} 💰 賣出 ${getItemName(originalItemId)} 嗎?`)) {
+ return
+ }
+
+ // 移除道具(使用原始 itemId)
+ await inventorySystem.removeItem(originalItemId, 1, instanceId)
+
+ // 獲得金幣
+ const currentCoins = petState.value.coins || 0
+ await petSystem.updateState({
+ coins: currentCoins + sellPrice
+ })
+
+ // 更新顯示
+ updatePetState()
+ updateInventoryList()
+
+ alert(`成功賣出!獲得 ${sellPrice} 💰`)
+}
+
+function getItemIcon(itemId) {
+ const item = inventorySystem?.items[itemId]
+ return item?.icon || '❓'
+}
+
+function getItemName(itemId) {
+ const item = inventorySystem?.items[itemId]
+ return item?.name || itemId
+}
+
onMounted(async () => {
window.addEventListener('keydown', handleKeydown)
// 檢查是否已有寵物
@@ -3154,6 +3671,384 @@ button.active {
opacity: 0.6;
}
+/* 冒險系統樣式 */
+.adventure-btn {
+ border-color: #e74c3c;
+ color: #e74c3c;
+}
+
+.adventure-btn:hover:not(:disabled) {
+ background: rgba(231, 76, 60, 0.1);
+ box-shadow: 0 0 15px rgba(231, 76, 60, 0.3);
+}
+
+/* 商店系統樣式 */
+.shop-btn {
+ border-color: #f39c12;
+ color: #f39c12;
+}
+
+.shop-btn:hover:not(:disabled) {
+ background: rgba(243, 156, 18, 0.1);
+ box-shadow: 0 0 15px rgba(243, 156, 18, 0.3);
+}
+
+.shop-panel {
+ max-width: 700px;
+ width: 90%;
+ max-height: 80vh;
+ overflow-y: auto;
+}
+
+.shop-balance {
+ text-align: center;
+ font-size: 1.2em;
+ margin: 15px 0;
+ padding: 10px;
+ background: rgba(243, 156, 18, 0.1);
+ border: 1px solid #f39c12;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 15px;
+}
+
+.balance-amount {
+ color: #f39c12;
+ font-weight: bold;
+ margin-left: 10px;
+}
+
+.refresh-btn {
+ padding: 5px 10px;
+ background: transparent;
+ border: 1px solid #0f0;
+ color: #0f0;
+ cursor: pointer;
+ font-size: 1.2em;
+ font-family: 'VT323', monospace;
+}
+
+.refresh-btn:hover {
+ background: #0f0;
+ color: #000;
+}
+
+.shop-mode-tabs {
+ display: flex;
+ gap: 10px;
+ margin: 15px 0;
+ justify-content: center;
+}
+
+.mode-tab-btn {
+ padding: 10px 30px;
+ background: transparent;
+ border: 1px solid #555;
+ color: #0f0;
+ cursor: pointer;
+ font-family: 'VT323', monospace;
+ font-size: 1.1em;
+}
+
+.mode-tab-btn:hover {
+ background: rgba(0, 255, 0, 0.1);
+}
+
+.mode-tab-btn.active {
+ border-color: #f39c12;
+ color: #f39c12;
+ background: rgba(243, 156, 18, 0.2);
+}
+
+.sell-hint {
+ text-align: center;
+ color: #888;
+ margin: 10px 0;
+ font-size: 0.9em;
+}
+
+.sell-btn {
+ padding: 8px 20px;
+ background: transparent;
+ border: 1px solid #f39c12;
+ color: #f39c12;
+ cursor: pointer;
+ font-family: 'VT323', monospace;
+ font-size: 1.1em;
+}
+
+.sell-btn:hover {
+ background: #f39c12;
+ color: #000;
+}
+
+.coins-reward {
+ color: #f39c12;
+ font-weight: bold;
+ font-size: 1.2em;
+}
+
+.shop-categories {
+ display: flex;
+ gap: 10px;
+ margin: 15px 0;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+/* 寵物表情顯示 */
+.mini-pet-emoji {
+ font-size: 1.5em;
+ display: inline-block;
+ margin-left: 10px;
+ vertical-align: middle;
+ cursor: help;
+}
+
+/* 動畫效果 */
+@keyframes bounce {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-10px); }
+}
+
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 25% { transform: translateX(-5px); }
+ 75% { transform: translateX(5px); }
+}
+
+@keyframes wiggle {
+ 0%, 100% { transform: rotate(0deg); }
+ 25% { transform: rotate(-10deg); }
+ 75% { transform: rotate(10deg); }
+}
+
+@keyframes float {
+ 0%, 100% { transform: translateY(0); opacity: 0.8; }
+ 50% { transform: translateY(-5px); opacity: 1; }
+}
+
+.bounce { animation: bounce 1s infinite; }
+.shake { animation: shake 0.5s infinite; }
+.wiggle { animation: wiggle 1s infinite; }
+.float { animation: float 2s infinite ease-in-out; }
+
+.category-btn {
+ padding: 8px 15px;
+ background: transparent;
+ border: 1px solid #555;
+ color: #0f0;
+ cursor: pointer;
+ font-family: 'VT323', monospace;
+ font-size: 1em;
+}
+
+.category-btn:hover {
+ background: rgba(0, 255, 0, 0.1);
+}
+
+.category-btn.active {
+ border-color: #f39c12;
+ color: #f39c12;
+ background: rgba(243, 156, 18, 0.1);
+}
+
+.shop-items-list {
+ display: grid;
+ gap: 15px;
+ margin: 20px 0;
+ max-height: 50vh;
+ overflow-y: auto;
+}
+
+.shop-item-card {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 15px;
+ border: 1px solid #333;
+ background: rgba(0, 20, 0, 0.6);
+}
+
+.shop-item-card .item-icon {
+ font-size: 2em;
+ min-width: 50px;
+ text-align: center;
+}
+
+.shop-item-card .item-details {
+ flex: 1;
+}
+
+.shop-item-card .item-name {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-bottom: 5px;
+}
+
+.shop-item-card .item-price {
+ color: #f39c12;
+ font-size: 1em;
+}
+
+.shop-item-card .item-stock {
+ color: #888;
+ font-size: 0.9em;
+}
+
+.shop-item-card .buy-btn {
+ padding: 8px 20px;
+ background: transparent;
+ border: 1px solid #0f0;
+ color: #0f0;
+ cursor: pointer;
+ font-family: 'VT323', monospace;
+ font-size: 1.1em;
+}
+
+.shop-item-card .buy-btn:hover:not(:disabled) {
+ background: #0f0;
+ color: #000;
+}
+
+.shop-item-card .buy-btn:disabled {
+ border-color: #555;
+ color: #555;
+ cursor: not-allowed;
+}
+
+.adventure-panel {
+ max-width: 600px;
+ width: 90%;
+}
+
+.adventure-list {
+ display: grid;
+ gap: 15px;
+ margin: 20px 0;
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+.adventure-card {
+ border: 1px solid #0f0;
+ padding: 15px;
+ background: rgba(0, 20, 0, 0.8);
+}
+
+.adv-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.adv-desc {
+ color: #aaa;
+ margin-bottom: 10px;
+ font-size: 0.9em;
+}
+
+.adv-cost, .adv-requirements {
+ display: flex;
+ gap: 15px;
+ font-size: 0.9em;
+ margin-bottom: 5px;
+}
+
+.start-adv-btn {
+ width: 100%;
+ margin-top: 10px;
+ padding: 8px;
+ background: transparent;
+ border: 1px solid #e74c3c;
+ color: #e74c3c;
+ cursor: pointer;
+ font-family: 'VT323', monospace;
+ font-size: 1.1em;
+}
+
+.start-adv-btn:hover:not(:disabled) {
+ background: #e74c3c;
+ color: #000;
+}
+
+.start-adv-btn:disabled {
+ border-color: #555;
+ color: #555;
+ cursor: not-allowed;
+}
+
+.adventure-progress-panel {
+ max-width: 500px;
+ width: 90%;
+}
+
+.progress-bar-container {
+ width: 100%;
+ height: 10px;
+ background: #333;
+ margin: 20px 0;
+ border: 1px solid #555;
+}
+
+.progress-bar {
+ height: 100%;
+ background: #0f0;
+ transition: width 1s linear;
+}
+
+.adventure-logs {
+ height: 200px;
+ overflow-y: auto;
+ border: 1px solid #333;
+ padding: 10px;
+ background: #000;
+ font-family: 'VT323', monospace;
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.log-entry {
+ color: #0f0;
+ font-size: 0.9em;
+ line-height: 1.4;
+}
+
+.result-panel {
+ text-align: center;
+}
+
+.rewards-list {
+ margin: 20px 0;
+ text-align: left;
+ border: 1px solid #333;
+ padding: 15px;
+}
+
+.reward-item {
+ margin: 5px 0;
+ color: #ffd700;
+}
+
+.confirm-btn {
+ padding: 8px 30px;
+ background: transparent;
+ border: 1px solid #0f0;
+ color: #0f0;
+ cursor: pointer;
+ font-family: 'VT323', monospace;
+ font-size: 1.2em;
+}
+
+.confirm-btn:hover {
+ background: #0f0;
+ color: #000;
+}
+
@media (max-width: 768px) {
.action-panel {
grid-template-columns: 1fr;
diff --git a/core/achievement-system.js b/core/achievement-system.js
index e2b3900..6fbe214 100644
--- a/core/achievement-system.js
+++ b/core/achievement-system.js
@@ -44,7 +44,7 @@ export class AchievementSystem {
this.achievementStats = { ...this.achievementStats, ...saved.stats }
}
console.log(`[AchievementSystem] 載入 ${this.unlockedAchievements.length} 個已解鎖成就`)
-
+
// 重新應用已解鎖成就的加成
await this.reapplyAchievementBuffs()
} catch (error) {
@@ -55,7 +55,7 @@ export class AchievementSystem {
const data = JSON.parse(saved)
this.unlockedAchievements = data.unlocked || []
this.achievementStats = { ...this.achievementStats, ...data.stats }
-
+
// 重新應用已解鎖成就的加成
await this.reapplyAchievementBuffs()
}
@@ -65,10 +65,10 @@ export class AchievementSystem {
// 重新應用已解鎖成就的加成(初始化時使用)
async reapplyAchievementBuffs() {
if (!this.petSystem) return
-
+
const unlocked = this.achievements.filter(a => this.unlockedAchievements.includes(a.id))
const achievementBuffs = {}
-
+
// 累積所有已解鎖成就的加成
for (const achievement of unlocked) {
if (achievement.reward && achievement.reward.buffs) {
@@ -77,7 +77,7 @@ export class AchievementSystem {
}
}
}
-
+
// 更新寵物狀態
if (Object.keys(achievementBuffs).length > 0) {
await this.petSystem.updateState({ achievementBuffs })
@@ -127,9 +127,9 @@ export class AchievementSystem {
case 'perfect_state':
if (condition.value) {
- return state.hunger >= 100 &&
- state.happiness >= 100 &&
- state.health >= 100
+ return state.hunger >= 100 &&
+ state.happiness >= 100 &&
+ state.health >= 100
}
return false
@@ -171,14 +171,14 @@ export class AchievementSystem {
// 將成就加成添加到寵物狀態
const currentState = this.petSystem.getState()
const achievementBuffs = currentState.achievementBuffs || {}
-
+
// 合併加成
for (const [key, value] of Object.entries(achievement.reward.buffs)) {
achievementBuffs[key] = (achievementBuffs[key] || 0) + value
}
await this.petSystem.updateState({ achievementBuffs })
-
+
// 重新計算戰鬥數值
this.petSystem.calculateCombatStats()
}
@@ -246,10 +246,10 @@ export class AchievementSystem {
// 檢查完美狀態
checkPerfectState() {
const state = this.petSystem.getState()
- if (state.hunger >= 100 &&
- state.happiness >= 100 &&
- state.health >= 100 &&
- !this.achievementStats.perfectStateReached) {
+ if (state.hunger >= 100 &&
+ state.happiness >= 100 &&
+ state.health >= 100 &&
+ !this.achievementStats.perfectStateReached) {
this.achievementStats.perfectStateReached = true
this.checkAndUnlockAchievements()
}
@@ -331,5 +331,44 @@ export class AchievementSystem {
return bonuses
}
+
+ // 重置成就系統(刪除寵物時使用)
+ async reset() {
+ this.unlockedAchievements = []
+ this.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
+ }
+
+ // 同步到 API
+ try {
+ await this.api.saveAchievements({
+ unlocked: this.unlockedAchievements,
+ stats: this.achievementStats
+ })
+ } catch (error) {
+ console.warn('[AchievementSystem] API 同步失敗:', error)
+ // 降級到 localStorage
+ localStorage.removeItem('achievements')
+ }
+
+ console.log('[AchievementSystem] 成就已重置')
+ }
}
diff --git a/core/adventure-system.js b/core/adventure-system.js
new file mode 100644
index 0000000..0056f4c
--- /dev/null
+++ b/core/adventure-system.js
@@ -0,0 +1,288 @@
+// 冒險系統核心
+import { apiService } from './api-service.js'
+import { ADVENTURES } from '../data/adventures.js'
+import { ENEMIES } from '../data/enemies.js'
+
+export class AdventureSystem {
+ constructor(petSystem, inventorySystem, api = apiService) {
+ this.petSystem = petSystem
+ this.inventorySystem = inventorySystem
+ this.api = api
+ this.currentAdventure = null
+ this.adventureTimer = null
+ this.logs = []
+ }
+
+ // 獲取所有冒險區域
+ getAdventures() {
+ return ADVENTURES
+ }
+
+ // 開始冒險
+ async startAdventure(adventureId) {
+ const adventure = ADVENTURES.find(a => a.id === adventureId)
+ if (!adventure) return { success: false, message: '找不到該冒險區域' }
+
+ const state = this.petSystem.getState()
+
+ // 1. 檢查條件
+ if (state.isSick || state.isDead || state.isSleeping) {
+ return { success: false, message: '寵物狀態不適合冒險' }
+ }
+
+ // 檢查等級/階段要求 (這裡簡化為檢查階段)
+ // 實際應根據 levelRequirement 檢查
+
+ // 檢查屬性要求
+ if (adventure.statsRequirement) {
+ for (const [stat, value] of Object.entries(adventure.statsRequirement)) {
+ if ((state[stat] || 0) < value) {
+ return { success: false, message: `屬性不足:需要 ${stat.toUpperCase()} ${value}` }
+ }
+ }
+ }
+
+ // 2. 檢查消耗
+ if (state.hunger < adventure.cost.hunger) {
+ return { success: false, message: '飢餓度不足' }
+ }
+ if (state.happiness < adventure.cost.happiness) {
+ return { success: false, message: '快樂度不足' }
+ }
+
+ // 3. 扣除消耗
+ await this.petSystem.updateState({
+ hunger: state.hunger - adventure.cost.hunger,
+ happiness: state.happiness - adventure.cost.happiness
+ })
+
+ // 4. 初始化冒險狀態
+ this.currentAdventure = {
+ id: adventureId,
+ startTime: Date.now(),
+ duration: adventure.duration * 1000, // 轉為毫秒
+ endTime: Date.now() + adventure.duration * 1000,
+ logs: [],
+ rewards: { items: [], coins: 0 },
+ encounters: []
+ }
+
+ this.addLog(`出發前往 ${adventure.name}!`)
+
+ // 5. 啟動計時器
+ this.startAdventureLoop()
+
+ return { success: true, adventure: this.currentAdventure }
+ }
+
+ // 冒險循環
+ startAdventureLoop() {
+ if (this.adventureTimer) clearInterval(this.adventureTimer)
+
+ this.adventureTimer = setInterval(async () => {
+ if (!this.currentAdventure) {
+ this.stopAdventureLoop()
+ return
+ }
+
+ const now = Date.now()
+ const adventure = ADVENTURES.find(a => a.id === this.currentAdventure.id)
+
+ // 檢查是否結束
+ if (now >= this.currentAdventure.endTime) {
+ await this.completeAdventure()
+ return
+ }
+
+ // 隨機遭遇敵人
+ if (Math.random() < 0.1) { // 每秒 10% 機率遭遇
+ await this.encounterEnemy(adventure)
+ }
+
+ }, 1000)
+ }
+
+ stopAdventureLoop() {
+ if (this.adventureTimer) {
+ clearInterval(this.adventureTimer)
+ this.adventureTimer = null
+ }
+ }
+
+ // 遭遇敵人
+ async encounterEnemy(adventure) {
+ if (!adventure.enemyPool || adventure.enemyPool.length === 0) return
+
+ // 隨機選擇敵人
+ const enemyId = adventure.enemyPool[Math.floor(Math.random() * adventure.enemyPool.length)]
+ const enemyConfig = ENEMIES[enemyId]
+
+ if (!enemyConfig) return
+
+ this.addLog(`遭遇了 ${enemyConfig.name}!準備戰鬥!`)
+
+ // 執行戰鬥
+ const combatResult = this.calculateCombat(enemyConfig)
+
+ // 記錄戰鬥過程
+ combatResult.logs.forEach(log => this.addLog(log))
+
+ if (combatResult.win) {
+ this.addLog(`戰鬥勝利!`)
+
+ // 計算掉落
+ if (enemyConfig.drops) {
+ for (const drop of enemyConfig.drops) {
+ if (Math.random() < drop.chance) {
+ // 檢查是否為金幣
+ if (drop.itemId === 'gold_coin') {
+ // 金幣直接加到獎勵中
+ if (!this.currentAdventure.rewards.coins) {
+ this.currentAdventure.rewards.coins = 0
+ }
+ this.currentAdventure.rewards.coins += drop.count || 1
+ this.addLog(`獲得金幣:${drop.count || 1} 💰`)
+ } else {
+ // 其他道具加到物品列表
+ this.currentAdventure.rewards.items.push({
+ itemId: drop.itemId,
+ count: drop.count || 1,
+ name: drop.itemId // 暫時用 ID,實際應查表
+ })
+ this.addLog(`獲得戰利品:${drop.itemId} x${drop.count || 1}`)
+ }
+ }
+ }
+ }
+ } else {
+ this.addLog(`戰鬥失敗... 寵物受傷逃跑了。`)
+ // 扣除健康
+ const state = this.petSystem.getState()
+ await this.petSystem.updateState({
+ health: Math.max(0, state.health - 10)
+ })
+ }
+ }
+
+ // 計算戰鬥 (簡化版回合制)
+ calculateCombat(enemy) {
+ const state = this.petSystem.getState()
+ const petStats = {
+ hp: state.health, // 用健康度當 HP
+ attack: state.attack || 10,
+ defense: state.defense || 0,
+ speed: state.speed || 10
+ }
+
+ const enemyStats = { ...enemy.stats }
+ const logs = []
+ let round = 1
+ let win = false
+
+ while (round <= 10) { // 最多 10 回合
+ // 速度決定先手
+ const petFirst = petStats.speed >= enemyStats.speed
+
+ // 雙方攻擊
+ const attacker = petFirst ? petStats : enemyStats
+ const defender = petFirst ? enemyStats : petStats
+ const attackerName = petFirst ? '你' : enemy.name
+ const defenderName = petFirst ? enemy.name : '你'
+
+ // 第一擊
+ let damage = Math.max(1, attacker.attack - defender.defense)
+ // 隨機浮動 0.8 ~ 1.2
+ damage = Math.floor(damage * (0.8 + Math.random() * 0.4))
+ defender.hp -= damage
+ logs.push(`[回合${round}] ${attackerName} 對 ${defenderName} 造成 ${damage} 點傷害`)
+
+ if (defender.hp <= 0) {
+ win = petFirst
+ break
+ }
+
+ // 第二擊
+ damage = Math.max(1, defender.attack - attacker.defense)
+ damage = Math.floor(damage * (0.8 + Math.random() * 0.4))
+ attacker.hp -= damage
+ logs.push(`[回合${round}] ${defenderName} 對 ${attackerName} 造成 ${damage} 點傷害`)
+
+ if (attacker.hp <= 0) {
+ win = !petFirst
+ break
+ }
+
+ round++
+ }
+
+ if (round > 10) {
+ logs.push('戰鬥超時,雙方平手(視為失敗)')
+ win = false
+ }
+
+ return { win, logs }
+ }
+
+ // 完成冒險
+ async completeAdventure() {
+ this.stopAdventureLoop()
+ const adventure = ADVENTURES.find(a => a.id === this.currentAdventure.id)
+
+ this.addLog('冒險結束!')
+
+ // 發放基礎獎勵
+ if (adventure.rewards) {
+ if (adventure.rewards.items) {
+ for (const reward of adventure.rewards.items) {
+ if (Math.random() < reward.chance) {
+ this.currentAdventure.rewards.items.push({
+ itemId: reward.itemId,
+ count: 1,
+ name: reward.itemId
+ })
+ }
+ }
+ }
+ }
+
+ // 實際發放道具到背包
+ if (this.inventorySystem) {
+ for (const item of this.currentAdventure.rewards.items) {
+ await this.inventorySystem.addItem(item.itemId, item.count)
+ }
+ }
+
+ // 發放金幣
+ if (this.currentAdventure.rewards.coins > 0) {
+ const state = this.petSystem.getState()
+ await this.petSystem.updateState({
+ coins: (state.coins || 0) + this.currentAdventure.rewards.coins
+ })
+ this.addLog(`獲得金幣:${this.currentAdventure.rewards.coins} 💰`)
+ }
+
+ // 觸發完成回調(如果有 UI 監聽)
+ if (this.onAdventureComplete) {
+ this.onAdventureComplete(this.currentAdventure)
+ }
+
+ this.currentAdventure = null
+ }
+
+ // 添加日誌
+ addLog(message) {
+ if (this.currentAdventure) {
+ const time = new Date().toLocaleTimeString()
+ this.currentAdventure.logs.push(`[${time}] ${message}`)
+ // 觸發更新回調
+ if (this.onLogUpdate) {
+ this.onLogUpdate(this.currentAdventure.logs)
+ }
+ }
+ }
+
+ // 獲取當前冒險狀態
+ getCurrentAdventure() {
+ return this.currentAdventure
+ }
+}
diff --git a/core/api-service.js b/core/api-service.js
index d13eb24..9fdf474 100644
--- a/core/api-service.js
+++ b/core/api-service.js
@@ -55,6 +55,12 @@ export class ApiService {
return this.getMockInventory()
case 'inventory/save':
return this.saveMockInventory(options.body)
+ case 'adventure/list':
+ return this.getMockAdventures()
+ case 'adventure/start':
+ return { success: true, message: '冒險開始' } // 實際邏輯在 AdventureSystem
+ case 'adventure/complete':
+ return { success: true, message: '冒險完成' }
default:
throw new Error(`Unknown endpoint: ${endpoint}`)
}
@@ -175,6 +181,25 @@ export class ApiService {
})
}
+ // 冒險相關
+ async getAdventures() {
+ return this.request('adventure/list')
+ }
+
+ async startAdventure(adventureId) {
+ return this.request('adventure/start', {
+ method: 'POST',
+ body: { adventureId }
+ })
+ }
+
+ async completeAdventure(result) {
+ return this.request('adventure/complete', {
+ method: 'POST',
+ body: result
+ })
+ }
+
// ========== Mock 資料方法 ==========
getMockPetState() {
@@ -188,6 +213,11 @@ export class ApiService {
updateMockPetState(updates) {
const current = this.getMockPetState()
+ if (!current) {
+ console.warn('[ApiService] No current state found, cannot update')
+ return { success: false, message: '找不到當前狀態' }
+ }
+ // 使用深度合併,確保不會丟失原有字段
const updated = { ...current, ...updates }
localStorage.setItem('petState', JSON.stringify(updated))
return { success: true, data: updated }
@@ -345,6 +375,11 @@ export class ApiService {
return { success: false, message: '儲存失敗: ' + error.message }
}
}
+
+ async getMockAdventures() {
+ const { ADVENTURES } = await import('../data/adventures.js')
+ return ADVENTURES
+ }
}
// 預設實例
diff --git a/core/inventory-system.js b/core/inventory-system.js
index 7385666..e28ad9a 100644
--- a/core/inventory-system.js
+++ b/core/inventory-system.js
@@ -337,7 +337,8 @@ export class InventorySystem {
// 裝備道具
// equipType: 'equipment' | 'appearance' | 'auto' (自動判斷)
- async equipItem(itemId, slot = null, equipType = 'auto') {
+ // instanceId: 指定要裝備的實例ID(可選,如果不指定則自動選擇第一個未裝備的)
+ async equipItem(itemId, slot = null, equipType = 'auto', instanceId = null) {
const item = this.items[itemId]
if (!item) {
return { success: false, message: '道具不存在' }
diff --git a/core/pet-system.js b/core/pet-system.js
index ce082c4..3921d08 100644
--- a/core/pet-system.js
+++ b/core/pet-system.js
@@ -24,20 +24,30 @@ export class PetSystem {
if (!this.state) {
// 創建新寵物
this.state = this.createInitialState(speciesId)
+ // 載入種族配置
+ this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
+ // 計算戰鬥數值(在保存前)
+ this.calculateCombatStats()
+ // 保存完整狀態(包含計算後的戰鬥數值)
await this.api.savePetState(this.state)
} else {
// 確保 achievementBuffs 存在(向後兼容)
if (!this.state.achievementBuffs) {
this.state.achievementBuffs = {}
}
+ // 確保 equipmentBuffs 有正確結構(向後兼容)
+ if (!this.state.equipmentBuffs || typeof this.state.equipmentBuffs !== 'object') {
+ this.state.equipmentBuffs = { flat: {}, percent: {} }
+ } else if (!this.state.equipmentBuffs.flat || !this.state.equipmentBuffs.percent) {
+ // 如果是舊的空對象 {},轉換為新結構
+ this.state.equipmentBuffs = { flat: {}, percent: {} }
+ }
+ // 載入種族配置
+ this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
+ // 計算戰鬥數值
+ this.calculateCombatStats()
}
- // 載入種族配置
- this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
-
- // 計算戰鬥數值(在 speciesConfig 設置後)
- this.calculateCombatStats()
-
return this.state
} catch (error) {
console.error('[PetSystem] 初始化失敗:', error)
@@ -85,8 +95,9 @@ export class PetSystem {
generation: 1,
lastTickTime: Date.now(),
achievementBuffs: {}, // 成就加成
- equipmentBuffs: {}, // 裝備加成
- appearance: {} // 外觀設定
+ equipmentBuffs: { flat: {}, percent: {} }, // 裝備加成
+ appearance: {}, // 外觀設定
+ coins: 100 // 金幣(初始 100)
}
// 分配命格
@@ -493,7 +504,7 @@ export class PetSystem {
const wasDying = this.state.dyingSeconds > 0
this.state.dyingSeconds = 0
console.log('✨ 寵物脫離瀕死狀態')
-
+
// 通知成就系統(如果存在)
if (this.achievementSystem && wasDying) {
this.achievementSystem.recordRecoveryFromDying()
@@ -559,7 +570,13 @@ export class PetSystem {
// 戰鬥數值
attack: this.state.attack,
defense: this.state.defense,
- speed: this.state.speed
+ speed: this.state.speed,
+ // 命格和神明
+ destiny: this.state.destiny,
+ currentDeityId: this.state.currentDeityId,
+ deityFavors: this.state.deityFavors,
+ // 经济
+ coins: this.state.coins
})
}
@@ -599,7 +616,8 @@ export class PetSystem {
// 檢查屬性條件
let statsConditionMet = true
- const missingStats = []
+ let missingStats = []
+
if (nextStage.conditions) {
if (nextStage.conditions.str && this.state.str < nextStage.conditions.str) {
statsConditionMet = false
@@ -616,10 +634,65 @@ export class PetSystem {
}
if (timeConditionMet && statsConditionMet) {
- console.log(`\n✨ 進化!${currentStage} → ${nextStage.stage} (年齡: ${ageSeconds.toFixed(1)}秒)`)
- this.state.stage = nextStage.stage
+ // 檢查是否有進化分支
+ let targetStage = nextStage.stage
+ let evolutionBranch = null
+
+ if (nextStage.evolutions && nextStage.evolutions.length > 0) {
+ // 判定進化分支
+ for (const branch of nextStage.evolutions) {
+ let branchMet = true
+
+ // 檢查分支條件
+ if (branch.conditions) {
+ // STR 條件
+ if (branch.conditions.str) {
+ if (branch.conditions.str.min && this.state.str < branch.conditions.str.min) branchMet = false
+ if (branch.conditions.str.dominant && (this.state.str <= this.state.int + this.state.dex)) branchMet = false
+ }
+ // INT 條件
+ if (branch.conditions.int) {
+ if (branch.conditions.int.min && this.state.int < branch.conditions.int.min) branchMet = false
+ if (branch.conditions.int.dominant && (this.state.int <= this.state.str + this.state.dex)) branchMet = false
+ }
+ // DEX 條件
+ if (branch.conditions.dex) {
+ if (branch.conditions.dex.min && this.state.dex < branch.conditions.dex.min) branchMet = false
+ if (branch.conditions.dex.dominant && (this.state.dex <= this.state.str + this.state.int)) branchMet = false
+ }
+ }
+
+ if (branchMet) {
+ evolutionBranch = branch
+ break // 找到第一個符合的分支就停止(優先級由數組順序決定)
+ }
+ }
+
+ // 如果沒有符合的特殊分支,使用默認分支(通常是最後一個)
+ if (!evolutionBranch) {
+ evolutionBranch = nextStage.evolutions[nextStage.evolutions.length - 1]
+ }
+ }
+
+ console.log(`\n✨ 進化!${currentStage} → ${targetStage} (年齡: ${ageSeconds.toFixed(1)}秒)`)
+
+ const updates = { stage: targetStage }
+
+ // 應用進化分支效果
+ if (evolutionBranch) {
+ console.log(`🌟 觸發特殊進化分支:${evolutionBranch.name}`)
+ updates.evolutionId = evolutionBranch.id
+ updates.evolutionName = evolutionBranch.name
+
+ // 應用屬性修正(永久保存到狀態中)
+ if (evolutionBranch.statModifiers) {
+ updates.statModifiers = evolutionBranch.statModifiers
+ }
+ }
+
+ this.state.stage = targetStage
this.state._evolutionWarned = false // 重置警告狀態
- this.updateState({ stage: nextStage.stage })
+ this.updateState(updates)
} else if (timeConditionMet && !statsConditionMet) {
// 時間到了但屬性不足,只在第一次提示
if (!this.state._evolutionWarned) {
diff --git a/core/temple-system.js b/core/temple-system.js
index 7b2772d..f1925b0 100644
--- a/core/temple-system.js
+++ b/core/temple-system.js
@@ -131,7 +131,9 @@ export class TempleSystem {
// 獲取好感度星級(每 20 點一星)
getFavorStars(deityId) {
+ if (!deityId) return '☆☆☆☆☆'
const state = this.petSystem.getState()
+ if (!state || !state.deityFavors) return '☆☆☆☆☆'
const favor = state.deityFavors[deityId] || 0
const stars = Math.floor(favor / 20)
return '★'.repeat(stars) + '☆'.repeat(5 - stars)
diff --git a/data/adventures.js b/data/adventures.js
new file mode 100644
index 0000000..0fe9733
--- /dev/null
+++ b/data/adventures.js
@@ -0,0 +1,59 @@
+// 冒險區域配置(資料驅動)
+export const ADVENTURES = [
+ {
+ id: 'backyard',
+ name: '自家後院',
+ description: '安全的新手探險地,偶爾會有小蟲子。',
+ statsRequirement: null, // 無屬性要求
+ cost: {
+ hunger: 5,
+ happiness: 5
+ },
+ duration: 10, // 測試用:10秒 (實際可能 60秒)
+ enemyPool: ['cockroach', 'mouse'],
+ enemyRate: 0.6, // 60% 機率遇到敵人
+ rewards: {
+ items: [
+ { itemId: 'cookie', chance: 0.2 }
+ ]
+ }
+ },
+ {
+ id: 'park',
+ name: '附近的公園',
+ description: '熱鬧的公園,但也潛藏著流浪動物的威脅。',
+ statsRequirement: { str: 20 }, // 需要力量 20
+ cost: {
+ hunger: 15,
+ happiness: 10
+ },
+ duration: 30, // 測試用:30秒
+ enemyPool: ['stray_dog', 'wild_cat'],
+ enemyRate: 0.7,
+ rewards: {
+ items: [
+ { itemId: 'tuna_can', chance: 0.3 },
+ { itemId: 'ball', chance: 0.2 }
+ ]
+ }
+ },
+ {
+ id: 'forest',
+ name: '神秘森林',
+ description: '危險的未知區域,只有強者才能生存。',
+ statsRequirement: { str: 50, int: 30 },
+ cost: {
+ hunger: 30,
+ happiness: 20
+ },
+ duration: 60, // 測試用:60秒
+ enemyPool: ['snake', 'bear'],
+ enemyRate: 0.8,
+ rewards: {
+ items: [
+ { itemId: 'vitality_potion', chance: 0.3 },
+ { itemId: 'magic_wand', chance: 0.1 }
+ ]
+ }
+ }
+]
diff --git a/data/enemies.js b/data/enemies.js
new file mode 100644
index 0000000..e54a62e
--- /dev/null
+++ b/data/enemies.js
@@ -0,0 +1,97 @@
+// 敵人配置(資料驅動)
+export const ENEMIES = {
+ // 新手區敵人
+ cockroach: {
+ id: 'cockroach',
+ name: '巨大的蟑螂',
+ description: '生命力頑強的害蟲,雖然弱小但很噁心。',
+ stats: {
+ hp: 20,
+ attack: 5,
+ defense: 0,
+ speed: 5
+ },
+ drops: [
+ { itemId: 'cookie', chance: 0.3, count: 1 }
+ ]
+ },
+ mouse: {
+ id: 'mouse',
+ name: '偷吃的老鼠',
+ description: '動作敏捷的小偷,喜歡偷吃東西。',
+ stats: {
+ hp: 35,
+ attack: 8,
+ defense: 2,
+ speed: 15
+ },
+ drops: [
+ { itemId: 'cookie', chance: 0.4, count: 1 },
+ { itemId: 'wooden_sword', chance: 0.05, count: 1 }
+ ]
+ },
+
+ // 公園區敵人
+ stray_dog: {
+ id: 'stray_dog',
+ name: '兇猛的野狗',
+ description: '為了搶地盤而變得兇暴的野狗。',
+ stats: {
+ hp: 80,
+ attack: 15,
+ defense: 5,
+ speed: 10
+ },
+ drops: [
+ { itemId: 'tuna_can', chance: 0.3, count: 1 },
+ { itemId: 'leather_armor', chance: 0.1, count: 1 }
+ ]
+ },
+ wild_cat: {
+ id: 'wild_cat',
+ name: '流浪貓老大',
+ description: '這片區域的老大,身手矯健。',
+ stats: {
+ hp: 100,
+ attack: 20,
+ defense: 8,
+ speed: 25
+ },
+ drops: [
+ { itemId: 'premium_food', chance: 0.2, count: 1 },
+ { itemId: 'lucky_charm', chance: 0.05, count: 1 }
+ ]
+ },
+
+ // 森林區敵人
+ snake: {
+ id: 'snake',
+ name: '毒蛇',
+ description: '潛伏在草叢中的危險掠食者。',
+ stats: {
+ hp: 150,
+ attack: 35,
+ defense: 10,
+ speed: 30
+ },
+ drops: [
+ { itemId: 'vitality_potion', chance: 0.2, count: 1 },
+ { itemId: 'magic_wand', chance: 0.05, count: 1 }
+ ]
+ },
+ bear: {
+ id: 'bear',
+ name: '暴躁的黑熊',
+ description: '森林中的霸主,力量驚人。',
+ stats: {
+ hp: 300,
+ attack: 50,
+ defense: 30,
+ speed: 10
+ },
+ drops: [
+ { itemId: 'gold_coin', chance: 0.5, count: 10 }, // 假設有金幣
+ { itemId: 'hero_sword', chance: 0.02, count: 1 }
+ ]
+ }
+}
diff --git a/data/items.js b/data/items.js
index 49749a6..cb2c868 100644
--- a/data/items.js
+++ b/data/items.js
@@ -3,7 +3,7 @@
export const ITEMS = {
// ========== 裝備類(有耐久度)==========
-
+
// 武器類
wooden_sword: {
id: 'wooden_sword',
@@ -23,7 +23,7 @@ export const ITEMS = {
icon: '⚔️',
appearance: null // 武器不改變外觀
},
-
+
iron_sword: {
id: 'iron_sword',
name: '鐵劍',
@@ -42,7 +42,7 @@ export const ITEMS = {
icon: '🗡️',
appearance: null
},
-
+
magic_staff: {
id: 'magic_staff',
name: '魔法杖',
@@ -61,7 +61,7 @@ export const ITEMS = {
icon: '🪄',
appearance: null
},
-
+
// 防具類
leather_armor: {
id: 'leather_armor',
@@ -81,7 +81,7 @@ export const ITEMS = {
icon: '🛡️',
appearance: null
},
-
+
iron_armor: {
id: 'iron_armor',
name: '鐵甲',
@@ -100,9 +100,9 @@ export const ITEMS = {
icon: '⚔️',
appearance: null
},
-
+
// ========== 外觀類(無耐久,不壞)==========
-
+
cute_hat: {
id: 'cute_hat',
name: '可愛帽子',
@@ -121,7 +121,7 @@ export const ITEMS = {
hat: 'cute_hat'
}
},
-
+
cool_sunglasses: {
id: 'cool_sunglasses',
name: '酷炫墨鏡',
@@ -140,7 +140,7 @@ export const ITEMS = {
accessory: 'cool_sunglasses'
}
},
-
+
red_scarf: {
id: 'red_scarf',
name: '紅色圍巾',
@@ -157,9 +157,9 @@ export const ITEMS = {
accessory: 'red_scarf'
}
},
-
+
// ========== 消耗品類 ==========
-
+
cookie: {
id: 'cookie',
name: '幸運餅乾',
@@ -173,7 +173,7 @@ export const ITEMS = {
description: '美味的餅乾,增加飢餓和快樂',
icon: '🍪'
},
-
+
health_potion: {
id: 'health_potion',
name: '治療藥水',
@@ -188,7 +188,7 @@ export const ITEMS = {
description: '恢復健康並治癒疾病',
icon: '🧪'
},
-
+
energy_drink: {
id: 'energy_drink',
name: '能量飲料',
@@ -209,7 +209,7 @@ export const ITEMS = {
description: '提供臨時速度和敏捷加成',
icon: '🥤'
},
-
+
growth_pill: {
id: 'growth_pill',
name: '成長藥丸',
@@ -223,9 +223,9 @@ export const ITEMS = {
description: '永久增加力量、智力、敏捷各 1 點',
icon: '💊'
},
-
+
// ========== 護身符類(永久加成)==========
-
+
lucky_amulet: {
id: 'lucky_amulet',
name: '幸運護身符',
@@ -242,7 +242,7 @@ export const ITEMS = {
description: '帶來好運的護身符,增加運勢和掉落率',
icon: '🔮'
},
-
+
protection_amulet: {
id: 'protection_amulet',
name: '平安符',
@@ -259,7 +259,7 @@ export const ITEMS = {
description: '保佑平安的護身符,增加健康上限和恢復效率',
icon: '🛡️'
},
-
+
wisdom_amulet: {
id: 'wisdom_amulet',
name: '智慧護身符',
@@ -276,9 +276,9 @@ export const ITEMS = {
description: '提升智慧的護身符,增加智力和智力成長',
icon: '📿'
},
-
+
// ========== 特殊道具類 ==========
-
+
training_manual: {
id: 'training_manual',
name: '訓練手冊',
@@ -294,7 +294,7 @@ export const ITEMS = {
description: '訓練指南,增加力量和敏捷成長效率',
icon: '📖'
},
-
+
time_crystal: {
id: 'time_crystal',
name: '時間水晶',
@@ -305,9 +305,9 @@ export const ITEMS = {
durability: Infinity,
maxDurability: Infinity,
effects: {
- percent: {
- strGain: 0.10,
- intGain: 0.10,
+ percent: {
+ strGain: 0.10,
+ intGain: 0.10,
dexGain: 0.10,
happinessRecovery: 0.10,
healthRecovery: 0.10
@@ -316,9 +316,9 @@ export const ITEMS = {
description: '神秘的水晶,全面提升成長和恢復效率',
icon: '💎'
},
-
+
// ========== 永久裝備(不會壞)==========
-
+
golden_crown: {
id: 'golden_crown',
name: '黃金王冠',
@@ -351,37 +351,37 @@ export const ITEM_TYPES = {
// 稀有度定義(資料檔保持英文 key,但 name 為中文顯示用)
export const ITEM_RARITY = {
- common: {
- name: '普通',
- color: '#9d9d9d',
+ common: {
+ name: '普通',
+ color: '#9d9d9d',
multiplier: 1.0,
dropRate: 0.50, // 50% 基礎掉落機率
description: '最常見的道具,容易獲得'
},
- uncommon: {
- name: '優秀',
- color: '#1eff00',
+ uncommon: {
+ name: '優秀',
+ color: '#1eff00',
multiplier: 1.2,
dropRate: 0.30, // 30% 基礎掉落機率
description: '較為少見的道具,有一定價值'
},
- rare: {
- name: '稀有',
- color: '#0070dd',
+ rare: {
+ name: '稀有',
+ color: '#0070dd',
multiplier: 1.5,
dropRate: 0.15, // 15% 基礎掉落機率
description: '稀有的道具,效果顯著'
},
- epic: {
- name: '史詩',
- color: '#a335ee',
+ epic: {
+ name: '史詩',
+ color: '#a335ee',
multiplier: 2.0,
dropRate: 0.05, // 5% 基礎掉落機率
description: '史詩級道具,非常珍貴'
},
- legendary: {
- name: '傳說',
- color: '#ff8000',
+ legendary: {
+ name: '傳說',
+ color: '#ff8000',
multiplier: 3.0,
dropRate: 0.01, // 1% 基礎掉落機率
description: '傳說級道具,極其稀有且強大'
diff --git a/data/pet-species.js b/data/pet-species.js
index c0f2022..307ba71 100644
--- a/data/pet-species.js
+++ b/data/pet-species.js
@@ -153,7 +153,67 @@ export const PET_SPECIES = {
str: 50, // 3天內累積
int: 50,
dex: 50
- }
+ },
+ // 進化分支配置
+ evolutions: [
+ {
+ id: 'warrior_tiger',
+ name: '戰士猛虎',
+ icon: '🐯',
+ description: '力量強大的猛虎形態',
+ conditions: {
+ str: { min: 50, dominant: true } // STR 需大於 INT+DEX
+ },
+ statModifiers: {
+ attack: 1.3,
+ defense: 1.2,
+ speed: 0.8
+ }
+ },
+ {
+ id: 'agile_cat',
+ name: '敏捷靈貓',
+ icon: '🐈',
+ description: '身手矯健的靈貓形態',
+ conditions: {
+ dex: { min: 50, dominant: true } // DEX 需大於 STR+INT
+ },
+ statModifiers: {
+ attack: 1.0,
+ defense: 0.8,
+ speed: 1.4
+ }
+ },
+ {
+ id: 'sage_cat',
+ name: '智者賢貓',
+ icon: '😺',
+ description: '充滿智慧的賢貓形態',
+ conditions: {
+ int: { min: 50, dominant: true } // INT 需大於 STR+DEX
+ },
+ statModifiers: {
+ attack: 0.9,
+ defense: 1.1,
+ speed: 1.0,
+ magic: 1.5
+ }
+ },
+ {
+ id: 'balanced_cat',
+ name: '成年貓',
+ icon: '😸',
+ description: '均衡發展的成年貓形態',
+ conditions: {
+ // 默認分支,無特殊條件
+ },
+ statModifiers: {
+ attack: 1.0,
+ defense: 1.0,
+ speed: 1.0
+ }
+ }
+ ]
}
],
personality: ['活潑', '黏人']
diff --git a/data/shop.js b/data/shop.js
new file mode 100644
index 0000000..41be24e
--- /dev/null
+++ b/data/shop.js
@@ -0,0 +1,121 @@
+// 商店商品配置(資料驅動 - 隨機刷新機制)
+
+// 商品池 - 所有可能出現的商品及其出現機率
+export const SHOP_POOL = [
+ // 食物類 - 高機率
+ {
+ itemId: 'cookie',
+ category: 'food',
+ price: 10,
+ appearChance: 0.8, // 80% 機率出現
+ sellPrice: 5 // 賣出價格為購買價一半
+ },
+ {
+ itemId: 'tuna_can',
+ category: 'food',
+ price: 30,
+ appearChance: 0.6,
+ sellPrice: 15
+ },
+ {
+ itemId: 'premium_food',
+ category: 'food',
+ price: 50,
+ appearChance: 0.3, // 30% 機率
+ sellPrice: 25
+ },
+
+ // 藥品類 - 中機率
+ {
+ itemId: 'vitality_potion',
+ category: 'medicine',
+ price: 40,
+ appearChance: 0.5,
+ sellPrice: 20
+ },
+
+ // 裝備類 - 低機率
+ {
+ itemId: 'wooden_sword',
+ category: 'equipment',
+ price: 80,
+ appearChance: 0.3,
+ sellPrice: 40
+ },
+ {
+ itemId: 'leather_armor',
+ category: 'equipment',
+ price: 100,
+ appearChance: 0.25,
+ sellPrice: 50
+ },
+ {
+ itemId: 'magic_wand',
+ category: 'equipment',
+ price: 150,
+ appearChance: 0.15, // 稀有
+ sellPrice: 75
+ },
+ {
+ itemId: 'hero_sword',
+ category: 'equipment',
+ price: 300,
+ appearChance: 0.05, // 極稀有
+ sellPrice: 150
+ },
+
+ // 玩具類
+ {
+ itemId: 'ball',
+ category: 'toy',
+ price: 20,
+ appearChance: 0.6,
+ sellPrice: 10
+ },
+
+ // 飾品類 - 稀有
+ {
+ itemId: 'lucky_charm',
+ category: 'accessory',
+ price: 150,
+ appearChance: 0.2,
+ sellPrice: 75
+ }
+]
+
+// 商品分類
+export const SHOP_CATEGORIES = {
+ food: { name: '食物', icon: '🍖' },
+ medicine: { name: '藥品', icon: '💊' },
+ equipment: { name: '裝備', icon: '⚔️' },
+ toy: { name: '玩具', icon: '🎾' },
+ accessory: { name: '飾品', icon: '✨' }
+}
+
+// 生成隨機商店商品列表
+export function generateShopItems() {
+ const items = []
+ for (const poolItem of SHOP_POOL) {
+ // 按機率決定是否出現
+ if (Math.random() < poolItem.appearChance) {
+ items.push({
+ ...poolItem,
+ stock: -1 // 無限庫存
+ })
+ }
+ }
+ // 確保至少有3個商品
+ if (items.length < 3) {
+ // 隨機補充商品
+ const remaining = SHOP_POOL.filter(p => !items.find(i => i.itemId === p.itemId))
+ while (items.length < 3 && remaining.length > 0) {
+ const randomIndex = Math.floor(Math.random() * remaining.length)
+ items.push({
+ ...remaining[randomIndex],
+ stock: -1
+ })
+ remaining.splice(randomIndex, 1)
+ }
+ }
+ return items
+}