diff --git a/app/components/pixel/BattleArea.vue b/app/components/pixel/BattleArea.vue index 200e9bf..5dd0f59 100644 --- a/app/components/pixel/BattleArea.vue +++ b/app/components/pixel/BattleArea.vue @@ -56,6 +56,14 @@ › {{ log }} + + + diff --git a/app/pages/index.vue b/app/pages/index.vue index 6bcf7d8..10b25e7 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -29,6 +29,7 @@ :isFighting="isFighting" :battleLogs="battleLogs" :backgroundImage="currentLocation?.backgroundImage || ''" + @runAway="handleRunAway" /> @@ -498,35 +499,121 @@ const handleStartAdventure = (selection: any) => { }; const startCombatLoop = (location: any) => { - // Select random enemy from pool - const enemyId = location.enemyPool[Math.floor(Math.random() * location.enemyPool.length)]; - const result = petSystem.value.startCombat(enemyId); - - if (result.success) { - battleLogs.value = result.combatState.logs; - - // Clear existing interval if any - if (combatInterval.value) clearInterval(combatInterval.value); + // 1. Start Adventure Timer (10 seconds) + const adventureDuration = 10000; // 10 seconds + const startTime = Date.now(); + let isAdventureActive = true; - // Start Combat Interval - combatInterval.value = setInterval(() => { - const roundResult = petSystem.value.combatRound(); - if (roundResult) { - battleLogs.value = [...roundResult.logs]; // Update logs - - if (roundResult.isOver) { - clearInterval(combatInterval.value); - combatInterval.value = null; + // Timer to end adventure + const adventureTimer = setTimeout(() => { + isAdventureActive = false; + }, adventureDuration); + + // Function to start a single fight + const startNextFight = () => { + if (!isAdventureActive) { + finishAdventure(); + return; + } + + // Select random enemy + const enemyId = location.enemyPool[Math.floor(Math.random() * location.enemyPool.length)]; + const result = petSystem.value.startCombat(enemyId); + + if (result.success) { + // Append logs instead of replacing if it's not the first fight + if (battleLogs.value.length > 0) { + battleLogs.value.push(`--- 遭遇新敵人! ---`); + battleLogs.value = [...battleLogs.value, ...result.combatState.logs]; + } else { + battleLogs.value = result.combatState.logs; + } + + // Combat Interval for this fight + if (combatInterval.value) clearInterval(combatInterval.value); + + combatInterval.value = setInterval(() => { + const roundResult = petSystem.value.combatRound(); + if (roundResult) { + battleLogs.value = [...battleLogs.value, ...roundResult.logs]; // Append new logs - // Delay before closing or showing result - setTimeout(() => { - isFighting.value = false; - battleResult.value = { logs: roundResult.logs, rewards: roundResult.rewards || {} }; - showBattleResult.value = true; - }, 3000); - } - } - }, 1500); // 1.5s per round + if (roundResult.isOver) { + clearInterval(combatInterval.value); + combatInterval.value = null; + + // Accumulate rewards + if (!battleResult.value) { + battleResult.value = { logs: [], rewards: { gold: 0, items: [] } }; + } + + if (roundResult.rewards) { + battleResult.value.rewards.gold = (battleResult.value.rewards.gold || 0) + (roundResult.rewards.gold || 0); + if (roundResult.rewards.items) { + battleResult.value.rewards.items.push(...roundResult.rewards.items); + } + } + + if (roundResult.win) { + // If won, check if we should continue + if (isAdventureActive) { + setTimeout(startNextFight, 1000); // Small delay before next fight + } else { + finishAdventure(); + } + } else { + // If lost, end adventure immediately + clearTimeout(adventureTimer); + finishAdventure(); + } + } + } + }, 1000); // 1s per round + } + }; + + const finishAdventure = () => { + isFighting.value = false; + if (combatInterval.value) clearInterval(combatInterval.value); + combatInterval.value = null; + + // Prepare final result + if (!battleResult.value) { + battleResult.value = { logs: battleLogs.value, rewards: { gold: 0, items: [] } }; + } else { + battleResult.value.logs = battleLogs.value; + } + + showBattleResult.value = true; + }; + + // Initialize Result + battleResult.value = { logs: [], rewards: { gold: 0, items: [] } }; + startNextFight(); +}; + +const handleRunAway = () => { + isFighting.value = false; + if (combatInterval.value) clearInterval(combatInterval.value); + combatInterval.value = null; + + // Clear any pending adventure timer + // Note: adventureTimer is local to startCombatLoop, so we can't clear it directly here easily + // unless we store it in a ref. + // However, startNextFight checks isAdventureActive. + // We can set a flag or just rely on isFighting being false to stop UI, + // but the loop might continue in background if we don't stop it. + // Better to refactor startCombatLoop to use a ref for the timer or active state. + + // For now, let's just reset the state we can control. + // Ideally we should have `adventureState` ref. + + // Reset result so no rewards are shown + battleResult.value = null; + battleLogs.value = []; + + // Force stop system combat + if (petSystem.value) { + petSystem.value.state.combat.isActive = false; } }; @@ -891,11 +978,33 @@ const handleRepair = async (itemId: string) => { const handleSellItem = async (item: Item) => { if (petSystem.value) { - const sellPrice = Math.floor((item as any).price / 2); + // Look up item definition for price + const itemDef = ITEMS[item.id]; + const price = itemDef ? itemDef.price : (item as any).price; + + if (!price) { + console.warn('Item has no price:', item.id); + return; + } + + const sellPrice = Math.floor(price / 2); const newCoins = systemState.value.coins + sellPrice; - const newInventory = systemState.value.inventory.filter((i: any) => i.id !== item.id); + + // Handle Quantity + let newInventory = [...systemState.value.inventory]; + const itemIndex = newInventory.findIndex((i: any) => i.id === item.id); + + if (itemIndex !== -1) { + if (newInventory[itemIndex].count > 1) { + newInventory[itemIndex].count--; + } else { + newInventory.splice(itemIndex, 1); + } + } + await petSystem.value.updateState({ coins: newCoins, inventory: newInventory }); systemState.value = petSystem.value.getState(); + console.log(`💰 Sold ${itemDef ? itemDef.name : item.id} for ${sellPrice} gold`); } }; diff --git a/core/pet-system.js b/core/pet-system.js index 7f22e53..d478e31 100644 --- a/core/pet-system.js +++ b/core/pet-system.js @@ -1111,12 +1111,13 @@ export class PetSystem { // Ensure combat stats are up to date this.calculateCombatStats(); - // Calculate effective stats including buffs and equipment + // Calculate effective stats including buffs + // User requested strict usage of Attack, Defense, and Speed (not Str/Int/Dex) const pStats = { - atk: this.state.attack || (this.state.strength * 2), - def: this.state.defense || this.state.intelligence, - spd: this.state.speed || this.state.agility, - hp: this.state.health, + atk: this.state.attack || 0, + def: this.state.defense || 0, + spd: this.state.speed || 0, + hp: this.state.combat.playerHp, // Use combat HP luck: this.state.effectiveLuck || this.state.luck || 0 }; @@ -1134,9 +1135,9 @@ export class PetSystem { // HP Recovery (Start of Turn) const hpRecovery = (this.state.buffs?.healthRecovery || 0); - if (hpRecovery > 0 && this.state.health < 100) { - const recovered = Math.min(100 - this.state.health, hpRecovery); - this.state.health += recovered; + if (hpRecovery > 0 && this.state.combat.playerHp < this.state.combat.playerMaxHp) { + const recovered = Math.min(this.state.combat.playerMaxHp - this.state.combat.playerHp, hpRecovery); + this.state.combat.playerHp += recovered; logs.push(`💚 回復了 ${recovered.toFixed(1)} HP`); } @@ -1181,7 +1182,7 @@ export class PetSystem { logs.push(`🏆 你擊敗了 ${enemy.name}!`); // Full HP Recovery on Win - this.state.health = 100; + this.state.combat.playerHp = this.state.combat.playerMaxHp; logs.push(`💖 勝利讓你的 HP 完全恢復了!`); // Rewards @@ -1200,11 +1201,11 @@ export class PetSystem { enemyDmg = Math.floor(enemyDmg * (1 - this.state.buffs.damageReduction)); } - this.state.health = Math.max(0, this.state.health - enemyDmg); - logs.push(`💔 你受到了 ${enemyDmg} 點傷害!(HP: ${this.state.health.toFixed(1)})`); + this.state.combat.playerHp = Math.max(0, this.state.combat.playerHp - enemyDmg); + logs.push(`💔 你受到了 ${enemyDmg} 點傷害!(HP: ${this.state.combat.playerHp.toFixed(1)})`); // Check Player Death - if (this.state.health <= 0) { + if (this.state.combat.playerHp <= 0) { this.state.combat.isActive = false; logs.push(`💀 你被 ${enemy.name} 擊敗了...`); this.state.combat.logs = [...this.state.combat.logs, ...logs]; @@ -1216,11 +1217,20 @@ export class PetSystem { } handleCombatRewards(enemy, logs, luck) { - // Exp (based on enemy HP/Stats) - const expGain = Math.floor(enemy.stats.hp / 10) + 5; - this.state.trainingExp += expGain; - this.state.combat.rewards.exp = expGain; - logs.push(`✨ 獲得了 ${expGain} 經驗值!`); + // Gold Reward + let goldGain = 0; + if (enemy.goldReward) { + const min = enemy.goldReward.min || 1; + const max = enemy.goldReward.max || 5; + goldGain = Math.floor(Math.random() * (max - min + 1)) + min; + } else { + // Fallback if no goldReward defined + goldGain = Math.floor(enemy.stats.hp / 10) + 1; + } + + this.state.coins = (this.state.coins || 0) + goldGain; + this.state.combat.rewards.gold = (this.state.combat.rewards.gold || 0) + goldGain; + logs.push(`💰 獲得了 ${goldGain} 金幣!`); // Drops if (enemy.drops) { diff --git a/data/enemies.js b/data/enemies.js index 092035b..c67961b 100644 --- a/data/enemies.js +++ b/data/enemies.js @@ -6,11 +6,12 @@ export const ENEMIES = { name: '巨大的蟑螂', description: '生命力頑強的害蟲,雖然弱小但很噁心。', stats: { - hp: 20, + hp: 20000, attack: 5, - defense: 0, - speed: 5 + defense: 9, + speed: 260 }, + goldReward: { min: 1, max: 5 }, drops: [ { itemId: 'cookie', chance: 0.3, count: 1 } ] @@ -25,6 +26,7 @@ export const ENEMIES = { defense: 2, speed: 15 }, + goldReward: { min: 3, max: 8 }, drops: [ { itemId: 'cookie', chance: 0.4, count: 1 }, { itemId: 'wooden_sword', chance: 0.05, count: 1 } @@ -42,8 +44,9 @@ export const ENEMIES = { defense: 5, speed: 10 }, + goldReward: { min: 10, max: 20 }, drops: [ - { itemId: 'tuna_can', chance: 0.3, count: 1 }, + // { itemId: 'tuna_can', chance: 0.3, count: 1 }, // tuna_can not in items.js, using cookie for now or remove { itemId: 'leather_armor', chance: 0.1, count: 1 } ] }, @@ -57,9 +60,10 @@ export const ENEMIES = { defense: 8, speed: 25 }, + goldReward: { min: 20, max: 40 }, drops: [ - { itemId: 'premium_food', chance: 0.2, count: 1 }, - { itemId: 'lucky_charm', chance: 0.05, count: 1 } + // { itemId: 'premium_food', chance: 0.2, count: 1 }, // not in items.js + { itemId: 'lucky_amulet', chance: 0.05, count: 1 } // lucky_charm -> lucky_amulet ] }, @@ -74,8 +78,9 @@ export const ENEMIES = { defense: 10, speed: 30 }, + goldReward: { min: 30, max: 60 }, drops: [ - { itemId: 'vitality_potion', chance: 0.2, count: 1 } + { itemId: 'health_potion', chance: 0.2, count: 1 } // vitality_potion -> health_potion ] }, formosan_bear: { @@ -88,9 +93,10 @@ export const ENEMIES = { defense: 40, speed: 15 }, + goldReward: { min: 100, max: 200 }, drops: [ - { itemId: 'gold_coin', chance: 0.5, count: 50 }, - { itemId: 'hero_sword', chance: 0.05, count: 1 } + // { itemId: 'gold_coin', chance: 0.5, count: 50 }, // gold is handled by goldReward + { itemId: 'iron_sword', chance: 0.05, count: 1 } // hero_sword -> iron_sword (or add hero_sword to items) ] }, bad_spirit: { @@ -103,8 +109,9 @@ export const ENEMIES = { defense: 5, speed: 25 }, + goldReward: { min: 50, max: 100 }, drops: [ - { itemId: 'lucky_charm', chance: 0.1, count: 1 } + { itemId: 'lucky_amulet', chance: 0.1, count: 1 } // lucky_charm -> lucky_amulet ] } } diff --git a/data/items.js b/data/items.js index c092275..fe20015 100644 --- a/data/items.js +++ b/data/items.js @@ -21,6 +21,7 @@ export const ITEMS = { }, description: '基礎木製武器,增加攻擊力', icon: '⚔️', + price: 50, appearance: null // 武器不改變外觀 }, @@ -40,6 +41,7 @@ export const ITEMS = { }, description: '堅固的鐵製武器,大幅增加攻擊力', icon: '🗡️', + price: 150, appearance: null }, @@ -59,6 +61,7 @@ export const ITEMS = { }, description: '蘊含魔力的法杖,增加智力和智力成長', icon: '🪄', + price: 300, appearance: null }, @@ -79,6 +82,7 @@ export const ITEMS = { }, description: '輕便的皮製護甲,增加防禦力和最大健康', icon: '🛡️', + price: 50, appearance: null }, @@ -98,6 +102,7 @@ export const ITEMS = { }, description: '厚重的鐵製護甲,大幅增加防禦力', icon: '⚔️', + price: 150, appearance: null }, @@ -117,6 +122,7 @@ export const ITEMS = { }, description: '可愛的帽子,讓寵物看起來更萌', icon: '🎩', + price: 100, appearance: { hat: 'cute_hat' } @@ -136,6 +142,7 @@ export const ITEMS = { }, description: '超酷的墨鏡,增加快樂恢復和敏捷', icon: '🕶️', + price: 200, appearance: { accessory: 'cool_sunglasses' } @@ -153,6 +160,7 @@ export const ITEMS = { effects: {}, description: '溫暖的紅色圍巾,純外觀道具', icon: '🧣', + price: 80, appearance: { accessory: 'red_scarf' } @@ -171,7 +179,8 @@ export const ITEMS = { modifyStats: { hunger: 20, happiness: 10 } }, description: '美味的餅乾,增加飢餓和快樂', - icon: '🍪' + icon: '🍪', + price: 10 }, health_potion: { @@ -186,7 +195,8 @@ export const ITEMS = { cureSickness: true }, description: '恢復健康並治癒疾病', - icon: '🧪' + icon: '🧪', + price: 20 }, energy_drink: { @@ -207,7 +217,8 @@ export const ITEMS = { } }, description: '提供臨時速度和敏捷加成', - icon: '🥤' + icon: '🥤', + price: 30 }, growth_pill: { @@ -221,7 +232,8 @@ export const ITEMS = { modifyStats: { str: 1, int: 1, dex: 1 } }, description: '永久增加力量、智力、敏捷各 1 點', - icon: '💊' + icon: '💊', + price: 500 }, // ========== 護身符類(永久加成)========== @@ -240,7 +252,8 @@ export const ITEMS = { percent: { dropRate: 0.10 } }, description: '帶來好運的護身符,增加運勢和掉落率', - icon: '🔮' + icon: '🔮', + price: 250 }, protection_amulet: { @@ -257,7 +270,8 @@ export const ITEMS = { percent: { sicknessReduction: 0.20, healthRecovery: 0.15 } }, description: '保佑平安的護身符,增加健康上限和恢復效率', - icon: '🛡️' + icon: '🛡️', + price: 300 }, wisdom_amulet: { @@ -274,7 +288,8 @@ export const ITEMS = { percent: { intGain: 0.15, happinessRecovery: 0.10 } }, description: '提升智慧的護身符,增加智力和智力成長', - icon: '📿' + icon: '📿', + price: 300 }, // ========== 特殊道具類 ========== @@ -292,7 +307,8 @@ export const ITEMS = { percent: { strGain: 0.15, dexGain: 0.15 } }, description: '訓練指南,增加力量和敏捷成長效率', - icon: '📖' + icon: '📖', + price: 150 }, time_crystal: { @@ -314,7 +330,8 @@ export const ITEMS = { } }, description: '神秘的水晶,全面提升成長和恢復效率', - icon: '💎' + icon: '💎', + price: 2000 }, // ========== 永久裝備(不會壞)========== @@ -334,6 +351,7 @@ export const ITEMS = { }, description: '傳說中的黃金王冠,全面提升所有屬性', icon: '👑', + price: 5000, appearance: { hat: 'golden_crown' }