diff --git a/app/app.vue b/app/app.vue index 9a5aff5..6207423 100644 --- a/app/app.vue +++ b/app/app.vue @@ -30,7 +30,12 @@
-

基礎屬性

+

+ 基礎屬性 + + {{ currentEmotion.emoji }} + +

飢餓 @@ -84,6 +89,10 @@ 體重 {{ Math.round(petState.weight || 0) }} g
+
+ 💰 金幣 + {{ petState.coins || 0 }} +
命格
@@ -91,6 +100,15 @@ {{ petState.destiny.description }}
+
+ 形態 +
+ {{ petState.evolutionName }} + 🐯 力量特化 + 🐈 敏捷特化 + 😺 智力特化 +
+
力量 (STR) {{ Math.round(petState.effectiveStr || petState.str || 0) }} @@ -232,11 +250,17 @@
實際裝備
-
+
{{ getEquippedItemIcon(slotKey, 'equipment') }} {{ getEquippedItemName(slotKey, 'equipment') }} @@ -383,6 +415,179 @@
+ + + + + + + + + + + +
+ + +
@@ -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 +}