fix: good friend

This commit is contained in:
王性驊 2025-11-27 17:48:36 +08:00
parent c779fd9a0e
commit 8167312462
5 changed files with 217 additions and 65 deletions

View File

@ -56,6 +56,14 @@
{{ log }} {{ log }}
</div> </div>
</div> </div>
<!-- Run Away Button -->
<button
@click="$emit('runAway')"
class="mt-4 w-full border border-gray-500 text-gray-400 py-2 hover:bg-gray-800 hover:text-white transition-colors text-xs tracking-widest uppercase"
>
逃跑 (Run Away)
</button>
</PixelFrame> </PixelFrame>
</div> </div>
</div> </div>

View File

@ -29,6 +29,7 @@
:isFighting="isFighting" :isFighting="isFighting"
:battleLogs="battleLogs" :battleLogs="battleLogs"
:backgroundImage="currentLocation?.backgroundImage || ''" :backgroundImage="currentLocation?.backgroundImage || ''"
@runAway="handleRunAway"
/> />
</div> </div>
@ -498,35 +499,121 @@ const handleStartAdventure = (selection: any) => {
}; };
const startCombatLoop = (location: any) => { const startCombatLoop = (location: any) => {
// Select random enemy from pool // 1. Start Adventure Timer (10 seconds)
const enemyId = location.enemyPool[Math.floor(Math.random() * location.enemyPool.length)]; const adventureDuration = 10000; // 10 seconds
const result = petSystem.value.startCombat(enemyId); const startTime = Date.now();
let isAdventureActive = true;
if (result.success) {
battleLogs.value = result.combatState.logs;
// Clear existing interval if any
if (combatInterval.value) clearInterval(combatInterval.value);
// Start Combat Interval // Timer to end adventure
combatInterval.value = setInterval(() => { const adventureTimer = setTimeout(() => {
const roundResult = petSystem.value.combatRound(); isAdventureActive = false;
if (roundResult) { }, adventureDuration);
battleLogs.value = [...roundResult.logs]; // Update logs
// Function to start a single fight
if (roundResult.isOver) { const startNextFight = () => {
clearInterval(combatInterval.value); if (!isAdventureActive) {
combatInterval.value = null; 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 if (roundResult.isOver) {
setTimeout(() => { clearInterval(combatInterval.value);
isFighting.value = false; combatInterval.value = null;
battleResult.value = { logs: roundResult.logs, rewards: roundResult.rewards || {} };
showBattleResult.value = true; // Accumulate rewards
}, 3000); if (!battleResult.value) {
} battleResult.value = { logs: [], rewards: { gold: 0, items: [] } };
} }
}, 1500); // 1.5s per round
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) => { const handleSellItem = async (item: Item) => {
if (petSystem.value) { 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 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 }); await petSystem.value.updateState({ coins: newCoins, inventory: newInventory });
systemState.value = petSystem.value.getState(); systemState.value = petSystem.value.getState();
console.log(`💰 Sold ${itemDef ? itemDef.name : item.id} for ${sellPrice} gold`);
} }
}; };

View File

@ -1111,12 +1111,13 @@ export class PetSystem {
// Ensure combat stats are up to date // Ensure combat stats are up to date
this.calculateCombatStats(); 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 = { const pStats = {
atk: this.state.attack || (this.state.strength * 2), atk: this.state.attack || 0,
def: this.state.defense || this.state.intelligence, def: this.state.defense || 0,
spd: this.state.speed || this.state.agility, spd: this.state.speed || 0,
hp: this.state.health, hp: this.state.combat.playerHp, // Use combat HP
luck: this.state.effectiveLuck || this.state.luck || 0 luck: this.state.effectiveLuck || this.state.luck || 0
}; };
@ -1134,9 +1135,9 @@ export class PetSystem {
// HP Recovery (Start of Turn) // HP Recovery (Start of Turn)
const hpRecovery = (this.state.buffs?.healthRecovery || 0); const hpRecovery = (this.state.buffs?.healthRecovery || 0);
if (hpRecovery > 0 && this.state.health < 100) { if (hpRecovery > 0 && this.state.combat.playerHp < this.state.combat.playerMaxHp) {
const recovered = Math.min(100 - this.state.health, hpRecovery); const recovered = Math.min(this.state.combat.playerMaxHp - this.state.combat.playerHp, hpRecovery);
this.state.health += recovered; this.state.combat.playerHp += recovered;
logs.push(`💚 回復了 ${recovered.toFixed(1)} HP`); logs.push(`💚 回復了 ${recovered.toFixed(1)} HP`);
} }
@ -1181,7 +1182,7 @@ export class PetSystem {
logs.push(`🏆 你擊敗了 ${enemy.name}`); logs.push(`🏆 你擊敗了 ${enemy.name}`);
// Full HP Recovery on Win // Full HP Recovery on Win
this.state.health = 100; this.state.combat.playerHp = this.state.combat.playerMaxHp;
logs.push(`💖 勝利讓你的 HP 完全恢復了!`); logs.push(`💖 勝利讓你的 HP 完全恢復了!`);
// Rewards // Rewards
@ -1200,11 +1201,11 @@ export class PetSystem {
enemyDmg = Math.floor(enemyDmg * (1 - this.state.buffs.damageReduction)); enemyDmg = Math.floor(enemyDmg * (1 - this.state.buffs.damageReduction));
} }
this.state.health = Math.max(0, this.state.health - enemyDmg); this.state.combat.playerHp = Math.max(0, this.state.combat.playerHp - enemyDmg);
logs.push(`💔 你受到了 ${enemyDmg} 點傷害!(HP: ${this.state.health.toFixed(1)})`); logs.push(`💔 你受到了 ${enemyDmg} 點傷害!(HP: ${this.state.combat.playerHp.toFixed(1)})`);
// Check Player Death // Check Player Death
if (this.state.health <= 0) { if (this.state.combat.playerHp <= 0) {
this.state.combat.isActive = false; this.state.combat.isActive = false;
logs.push(`💀 你被 ${enemy.name} 擊敗了...`); logs.push(`💀 你被 ${enemy.name} 擊敗了...`);
this.state.combat.logs = [...this.state.combat.logs, ...logs]; this.state.combat.logs = [...this.state.combat.logs, ...logs];
@ -1216,11 +1217,20 @@ export class PetSystem {
} }
handleCombatRewards(enemy, logs, luck) { handleCombatRewards(enemy, logs, luck) {
// Exp (based on enemy HP/Stats) // Gold Reward
const expGain = Math.floor(enemy.stats.hp / 10) + 5; let goldGain = 0;
this.state.trainingExp += expGain; if (enemy.goldReward) {
this.state.combat.rewards.exp = expGain; const min = enemy.goldReward.min || 1;
logs.push(`✨ 獲得了 ${expGain} 經驗值!`); 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 // Drops
if (enemy.drops) { if (enemy.drops) {

View File

@ -6,11 +6,12 @@ export const ENEMIES = {
name: '巨大的蟑螂', name: '巨大的蟑螂',
description: '生命力頑強的害蟲,雖然弱小但很噁心。', description: '生命力頑強的害蟲,雖然弱小但很噁心。',
stats: { stats: {
hp: 20, hp: 20000,
attack: 5, attack: 5,
defense: 0, defense: 9,
speed: 5 speed: 260
}, },
goldReward: { min: 1, max: 5 },
drops: [ drops: [
{ itemId: 'cookie', chance: 0.3, count: 1 } { itemId: 'cookie', chance: 0.3, count: 1 }
] ]
@ -25,6 +26,7 @@ export const ENEMIES = {
defense: 2, defense: 2,
speed: 15 speed: 15
}, },
goldReward: { min: 3, max: 8 },
drops: [ drops: [
{ itemId: 'cookie', chance: 0.4, count: 1 }, { itemId: 'cookie', chance: 0.4, count: 1 },
{ itemId: 'wooden_sword', chance: 0.05, count: 1 } { itemId: 'wooden_sword', chance: 0.05, count: 1 }
@ -42,8 +44,9 @@ export const ENEMIES = {
defense: 5, defense: 5,
speed: 10 speed: 10
}, },
goldReward: { min: 10, max: 20 },
drops: [ 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 } { itemId: 'leather_armor', chance: 0.1, count: 1 }
] ]
}, },
@ -57,9 +60,10 @@ export const ENEMIES = {
defense: 8, defense: 8,
speed: 25 speed: 25
}, },
goldReward: { min: 20, max: 40 },
drops: [ drops: [
{ itemId: 'premium_food', chance: 0.2, count: 1 }, // { itemId: 'premium_food', chance: 0.2, count: 1 }, // not in items.js
{ itemId: 'lucky_charm', chance: 0.05, count: 1 } { itemId: 'lucky_amulet', chance: 0.05, count: 1 } // lucky_charm -> lucky_amulet
] ]
}, },
@ -74,8 +78,9 @@ export const ENEMIES = {
defense: 10, defense: 10,
speed: 30 speed: 30
}, },
goldReward: { min: 30, max: 60 },
drops: [ drops: [
{ itemId: 'vitality_potion', chance: 0.2, count: 1 } { itemId: 'health_potion', chance: 0.2, count: 1 } // vitality_potion -> health_potion
] ]
}, },
formosan_bear: { formosan_bear: {
@ -88,9 +93,10 @@ export const ENEMIES = {
defense: 40, defense: 40,
speed: 15 speed: 15
}, },
goldReward: { min: 100, max: 200 },
drops: [ drops: [
{ itemId: 'gold_coin', chance: 0.5, count: 50 }, // { itemId: 'gold_coin', chance: 0.5, count: 50 }, // gold is handled by goldReward
{ itemId: 'hero_sword', chance: 0.05, count: 1 } { itemId: 'iron_sword', chance: 0.05, count: 1 } // hero_sword -> iron_sword (or add hero_sword to items)
] ]
}, },
bad_spirit: { bad_spirit: {
@ -103,8 +109,9 @@ export const ENEMIES = {
defense: 5, defense: 5,
speed: 25 speed: 25
}, },
goldReward: { min: 50, max: 100 },
drops: [ drops: [
{ itemId: 'lucky_charm', chance: 0.1, count: 1 } { itemId: 'lucky_amulet', chance: 0.1, count: 1 } // lucky_charm -> lucky_amulet
] ]
} }
} }

View File

@ -21,6 +21,7 @@ export const ITEMS = {
}, },
description: '基礎木製武器,增加攻擊力', description: '基礎木製武器,增加攻擊力',
icon: '⚔️', icon: '⚔️',
price: 50,
appearance: null // 武器不改變外觀 appearance: null // 武器不改變外觀
}, },
@ -40,6 +41,7 @@ export const ITEMS = {
}, },
description: '堅固的鐵製武器,大幅增加攻擊力', description: '堅固的鐵製武器,大幅增加攻擊力',
icon: '🗡️', icon: '🗡️',
price: 150,
appearance: null appearance: null
}, },
@ -59,6 +61,7 @@ export const ITEMS = {
}, },
description: '蘊含魔力的法杖,增加智力和智力成長', description: '蘊含魔力的法杖,增加智力和智力成長',
icon: '🪄', icon: '🪄',
price: 300,
appearance: null appearance: null
}, },
@ -79,6 +82,7 @@ export const ITEMS = {
}, },
description: '輕便的皮製護甲,增加防禦力和最大健康', description: '輕便的皮製護甲,增加防禦力和最大健康',
icon: '🛡️', icon: '🛡️',
price: 50,
appearance: null appearance: null
}, },
@ -98,6 +102,7 @@ export const ITEMS = {
}, },
description: '厚重的鐵製護甲,大幅增加防禦力', description: '厚重的鐵製護甲,大幅增加防禦力',
icon: '⚔️', icon: '⚔️',
price: 150,
appearance: null appearance: null
}, },
@ -117,6 +122,7 @@ export const ITEMS = {
}, },
description: '可愛的帽子,讓寵物看起來更萌', description: '可愛的帽子,讓寵物看起來更萌',
icon: '🎩', icon: '🎩',
price: 100,
appearance: { appearance: {
hat: 'cute_hat' hat: 'cute_hat'
} }
@ -136,6 +142,7 @@ export const ITEMS = {
}, },
description: '超酷的墨鏡,增加快樂恢復和敏捷', description: '超酷的墨鏡,增加快樂恢復和敏捷',
icon: '🕶️', icon: '🕶️',
price: 200,
appearance: { appearance: {
accessory: 'cool_sunglasses' accessory: 'cool_sunglasses'
} }
@ -153,6 +160,7 @@ export const ITEMS = {
effects: {}, effects: {},
description: '溫暖的紅色圍巾,純外觀道具', description: '溫暖的紅色圍巾,純外觀道具',
icon: '🧣', icon: '🧣',
price: 80,
appearance: { appearance: {
accessory: 'red_scarf' accessory: 'red_scarf'
} }
@ -171,7 +179,8 @@ export const ITEMS = {
modifyStats: { hunger: 20, happiness: 10 } modifyStats: { hunger: 20, happiness: 10 }
}, },
description: '美味的餅乾,增加飢餓和快樂', description: '美味的餅乾,增加飢餓和快樂',
icon: '🍪' icon: '🍪',
price: 10
}, },
health_potion: { health_potion: {
@ -186,7 +195,8 @@ export const ITEMS = {
cureSickness: true cureSickness: true
}, },
description: '恢復健康並治癒疾病', description: '恢復健康並治癒疾病',
icon: '🧪' icon: '🧪',
price: 20
}, },
energy_drink: { energy_drink: {
@ -207,7 +217,8 @@ export const ITEMS = {
} }
}, },
description: '提供臨時速度和敏捷加成', description: '提供臨時速度和敏捷加成',
icon: '🥤' icon: '🥤',
price: 30
}, },
growth_pill: { growth_pill: {
@ -221,7 +232,8 @@ export const ITEMS = {
modifyStats: { str: 1, int: 1, dex: 1 } modifyStats: { str: 1, int: 1, dex: 1 }
}, },
description: '永久增加力量、智力、敏捷各 1 點', description: '永久增加力量、智力、敏捷各 1 點',
icon: '💊' icon: '💊',
price: 500
}, },
// ========== 護身符類(永久加成)========== // ========== 護身符類(永久加成)==========
@ -240,7 +252,8 @@ export const ITEMS = {
percent: { dropRate: 0.10 } percent: { dropRate: 0.10 }
}, },
description: '帶來好運的護身符,增加運勢和掉落率', description: '帶來好運的護身符,增加運勢和掉落率',
icon: '🔮' icon: '🔮',
price: 250
}, },
protection_amulet: { protection_amulet: {
@ -257,7 +270,8 @@ export const ITEMS = {
percent: { sicknessReduction: 0.20, healthRecovery: 0.15 } percent: { sicknessReduction: 0.20, healthRecovery: 0.15 }
}, },
description: '保佑平安的護身符,增加健康上限和恢復效率', description: '保佑平安的護身符,增加健康上限和恢復效率',
icon: '🛡️' icon: '🛡️',
price: 300
}, },
wisdom_amulet: { wisdom_amulet: {
@ -274,7 +288,8 @@ export const ITEMS = {
percent: { intGain: 0.15, happinessRecovery: 0.10 } percent: { intGain: 0.15, happinessRecovery: 0.10 }
}, },
description: '提升智慧的護身符,增加智力和智力成長', description: '提升智慧的護身符,增加智力和智力成長',
icon: '📿' icon: '📿',
price: 300
}, },
// ========== 特殊道具類 ========== // ========== 特殊道具類 ==========
@ -292,7 +307,8 @@ export const ITEMS = {
percent: { strGain: 0.15, dexGain: 0.15 } percent: { strGain: 0.15, dexGain: 0.15 }
}, },
description: '訓練指南,增加力量和敏捷成長效率', description: '訓練指南,增加力量和敏捷成長效率',
icon: '📖' icon: '📖',
price: 150
}, },
time_crystal: { time_crystal: {
@ -314,7 +330,8 @@ export const ITEMS = {
} }
}, },
description: '神秘的水晶,全面提升成長和恢復效率', description: '神秘的水晶,全面提升成長和恢復效率',
icon: '💎' icon: '💎',
price: 2000
}, },
// ========== 永久裝備(不會壞)========== // ========== 永久裝備(不會壞)==========
@ -334,6 +351,7 @@ export const ITEMS = {
}, },
description: '傳說中的黃金王冠,全面提升所有屬性', description: '傳說中的黃金王冠,全面提升所有屬性',
icon: '👑', icon: '👑',
price: 5000,
appearance: { appearance: {
hat: 'golden_crown' hat: 'golden_crown'
} }