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 }}
</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>
</div>
</div>

View File

@ -29,6 +29,7 @@
:isFighting="isFighting"
:battleLogs="battleLogs"
:backgroundImage="currentLocation?.backgroundImage || ''"
@runAway="handleRunAway"
/>
</div>
@ -498,35 +499,121 @@ const handleStartAdventure = (selection: any) => {
};
const startCombatLoop = (location: any) => {
// Select random enemy from pool
// 1. Start Adventure Timer (10 seconds)
const adventureDuration = 10000; // 10 seconds
const startTime = Date.now();
let isAdventureActive = true;
// 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;
}
// Clear existing interval if any
// Combat Interval for this fight
if (combatInterval.value) clearInterval(combatInterval.value);
// Start Combat Interval
combatInterval.value = setInterval(() => {
const roundResult = petSystem.value.combatRound();
if (roundResult) {
battleLogs.value = [...roundResult.logs]; // Update logs
battleLogs.value = [...battleLogs.value, ...roundResult.logs]; // Append new logs
if (roundResult.isOver) {
clearInterval(combatInterval.value);
combatInterval.value = null;
// Delay before closing or showing result
setTimeout(() => {
// 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;
battleResult.value = { logs: roundResult.logs, rewards: roundResult.rewards || {} };
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;
}, 3000);
}
}
}, 1500); // 1.5s per round
};
// 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`);
}
};

View File

@ -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) {

View File

@ -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
]
}
}

View File

@ -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'
}