import { ref, computed, onMounted, onUnmounted } from 'vue'; import { api } from '../services/api'; export function usePetSystem() { // --- State --- const stage = ref('egg'); // egg, baby, adult const state = ref('idle'); // idle, sleep, eating, sick, dead, refuse const isLoading = ref(true); const error = ref(null); // --- Destiny Data --- const DESTINIES = [ { id: 'luck', name: '福運', description: '籤詩好籤率 +10%', rarity: 1 }, { id: 'diligence', name: '勤奮', description: '訓練遊戲獎勵 +20%', rarity: 1 }, { id: 'gluttony', name: '暴食', description: '飢餓下降速度 +30%', rarity: 1 }, { id: 'playful', name: '愛玩', description: 'Happiness 更快下降/更快上升', rarity: 1 }, { id: 'purification', name: '淨化', description: '生病機率 -20%', rarity: 1 }, { id: 'thirdeye', name: '天眼', description: '擲筊出聖筊機率微升', rarity: 2 }, { id: 'medium', name: '冥感', description: '死亡後招魂成功率提升', rarity: 2 } ]; // --- Stats --- const stats = ref({ hunger: 100, // 0-100 (0 = Starving) happiness: 100, // 0-100 (0 = Depressed) health: 100, // 0-100 (0 = Sick risk) weight: 500, // grams age: 1, // days (start at day 1) poopCount: 0, // Number of poops on screen // v2 Stats str: 0, // 力量 (Fireball Game) int: 0, // 智力 (Guessing Game) dex: 0, // 敏捷 (Catch Ball) generation: 1, // 輪迴世代 deityFavor: 0, // 神明好感度 destiny: null, // 天生命格 (Object) // Deity System currentDeity: 'mazu', deityFavors: { mazu: 0, earthgod: 0, matchmaker: 0, wenchang: 0, guanyin: 0 }, dailyPrayerCount: 0 }); // Game Config (loaded from API) const config = ref({ rates: { hungerDecay: 0.05, happinessDecay: 0.08, poopChance: 0.005, sickChance: 0.1 } }); const achievements = ref([ { id: 'newbie', name: '新手飼主', desc: '養育超過 1 天', unlocked: false, icon: '🥚' }, { id: 'veteran', name: '資深飼主', desc: '養育超過 7 天', unlocked: false, icon: '🏆' }, { id: 'healthy', name: '健康寶寶', desc: '3歲且健康 > 90', unlocked: false, icon: '💪' }, { id: 'happy', name: '快樂天使', desc: '3歲且快樂 > 90', unlocked: false, icon: '💖' } ]); // --- Internal Timers --- let gameLoopId = null; let tickCount = 0; const TICK_RATE = 3000; // 3 seconds per tick const TICKS_PER_DAY = 20; // For testing: 1 minute = 1 day (usually 28800 for 24h) const isCleaning = ref(false); // --- Initialization --- async function initGame() { try { console.log('🎮 initGame: Starting...'); isLoading.value = true; // Load Pet Data console.log('🎮 initGame: Calling api.getPetStatus()...'); const petData = await api.getPetStatus(); console.log('🎮 initGame: Got petData:', petData); stage.value = petData.stage; state.value = petData.state; stats.value = { ...stats.value, ...petData.stats }; // Merge defaults // Load Config console.log('🎮 initGame: Calling api.getGameConfig()...'); const configData = await api.getGameConfig(); console.log('🎮 initGame: Got configData:', configData); if (configData) { config.value = configData; } console.log('🎮 initGame: Success! Setting isLoading to false'); isLoading.value = false; console.log('🎮 initGame: isLoading.value is now:', isLoading.value); console.log('🎮 initGame: typeof isLoading:', typeof isLoading); console.log('🎮 initGame: isLoading object:', isLoading); startGameLoop(); } catch (err) { console.error("❌ Failed to init game:", err); error.value = "Failed to load game data."; isLoading.value = false; } } function startGameLoop() { if (gameLoopId) clearInterval(gameLoopId); gameLoopId = setInterval(tick, TICK_RATE); } // --- Actions --- function assignDestiny() { // Simple weighted random or just random for now // Rarity 2 has lower chance const roll = Math.random(); let pool = DESTINIES; // 20% chance for rare destiny if (roll < 0.2) { pool = DESTINIES.filter(d => d.rarity === 2); } else { pool = DESTINIES.filter(d => d.rarity === 1); } const picked = pool[Math.floor(Math.random() * pool.length)]; stats.value.destiny = picked; console.log('Assigned Destiny:', picked); // Sync to API api.updatePetStatus({ stats: { destiny: picked } }); } async function feed() { if (state.value === 'sleep' || state.value === 'dead' || stage.value === 'egg' || isCleaning.value) return false; if (state.value === 'sick' || stats.value.hunger >= 90) { // Refuse food if sick or full triggerState('refuse', 2000); return false; } // Eat triggerState('eating', 3000); // Animation duration stats.value.hunger = Math.min(100, stats.value.hunger + 20); stats.value.weight += 50; // Sync to API await api.updatePetStatus({ state: 'eating', stats: { hunger: stats.value.hunger, weight: stats.value.weight } }); // Chance to poop after eating if (Math.random() < 0.15) { setTimeout(async () => { if (stats.value.poopCount < 4) { stats.value.poopCount++; await api.updatePetStatus({ stats: { poopCount: stats.value.poopCount } }); } }, 4000); } return true; } async function play() { if (state.value !== 'idle' || stage.value === 'egg' || isCleaning.value) return false; stats.value.happiness = Math.min(100, stats.value.happiness + 15); stats.value.weight -= 10; // Exercise burns calories stats.value.hunger = Math.max(0, stats.value.hunger - 5); await api.updatePetStatus({ stats: { happiness: stats.value.happiness, weight: stats.value.weight, hunger: stats.value.hunger } }); return true; } async function clean() { if (stats.value.poopCount > 0 && !isCleaning.value) { isCleaning.value = true; // Delay removal for animation setTimeout(async () => { stats.value.poopCount = 0; stats.value.happiness += 10; isCleaning.value = false; await api.updatePetStatus({ stats: { poopCount: 0, happiness: stats.value.happiness } }); }, 2000); // 2 seconds flush animation return true; } return false; } async function sleep() { if (isCleaning.value) return; if (state.value === 'idle') { state.value = 'sleep'; } else if (state.value === 'sleep') { state.value = 'idle'; // Wake up } await api.updatePetStatus({ state: state.value }); } // --- Game Loop --- function tick() { if (state.value === 'dead' || stage.value === 'egg') return; // Use rates from config const rates = config.value.rates || { hungerDecay: 0.05, happinessDecay: 0.08, poopChance: 0.005, sickChance: 0.1 }; // Decrease stats naturally let hungerDecay = rates.hungerDecay; if (stats.value.destiny?.id === 'gluttony') { hungerDecay *= 1.3; } let happinessDecay = rates.happinessDecay; if (stats.value.destiny?.id === 'playful') { happinessDecay *= 1.2; // Faster decay } // Destiny Effect: DEX (敏捷) - Hunger decreases slower if (stats.value.dex > 0) { const reduction = Math.min(0.5, stats.value.dex * 0.01); // Max 50% reduction hungerDecay *= (1 - reduction); } if (state.value !== 'sleep') { stats.value.hunger = Math.max(0, stats.value.hunger - hungerDecay); stats.value.happiness = Math.max(0, stats.value.happiness - happinessDecay); } else { // Slower decay when sleeping (約 1/3 速度) stats.value.hunger = Math.max(0, stats.value.hunger - (hungerDecay * 0.3)); stats.value.happiness = Math.max(0, stats.value.happiness - (happinessDecay * 0.3)); } // Random poop generation if (state.value !== 'sleep' && Math.random() < rates.poopChance && stats.value.poopCount < 4 && !isCleaning.value) { stats.value.poopCount++; } // Health Logic if (stats.value.poopCount > 0) { stats.value.health = Math.max(0, stats.value.health - (0.1 * stats.value.poopCount)); } if (stats.value.hunger < 20) { const hungerPenalty = (20 - stats.value.hunger) * 0.02; stats.value.health = Math.max(0, stats.value.health - hungerPenalty); } if (stats.value.happiness < 20) { const happinessPenalty = (20 - stats.value.happiness) * 0.01; stats.value.health = Math.max(0, stats.value.health - happinessPenalty); } // Sickness Check let sickChance = rates.sickChance; if (stats.value.destiny?.id === 'purification') { sickChance *= 0.8; } if (stats.value.currentDeity === 'mazu' && stats.value.deityFavors?.mazu > 0) { sickChance *= 0.85; } if (stats.value.health < 30 && state.value !== 'sick') { if (Math.random() < sickChance) { state.value = 'sick'; api.updatePetStatus({ state: 'sick' }); } } // Health Recovery let healthRecovery = 0.05; if (stats.value.currentDeity === 'guanyin' && stats.value.deityFavors?.guanyin > 0) { healthRecovery *= 1.2; } if (stats.value.currentDeity === 'matchmaker' && stats.value.deityFavors?.matchmaker > 0) { happinessDecay *= 0.75; } if (stats.value.poopCount === 0 && stats.value.hunger > 50 && stats.value.happiness > 50 && stats.value.health < 100 && state.value !== 'sick') { stats.value.health = Math.min(100, stats.value.health + healthRecovery); } // Evolution / Growth tickCount++; if (tickCount >= TICKS_PER_DAY) { stats.value.age++; tickCount = 0; checkEvolution(); // Sync stats periodically (e.g., every "day") api.updatePetStatus({ stats: stats.value }); } checkAchievements(); } function checkAchievements() { if (!achievements.value[0].unlocked && stats.value.age >= 1) { unlockAchievement(0); } if (!achievements.value[1].unlocked && stats.value.age >= 7) { unlockAchievement(1); } if (!achievements.value[2].unlocked && stats.value.age >= 3 && stats.value.health >= 90) { unlockAchievement(2); } if (!achievements.value[3].unlocked && stats.value.age >= 3 && stats.value.happiness >= 90) { unlockAchievement(3); } } function unlockAchievement(index) { if (!achievements.value[index].unlocked) { achievements.value[index].unlocked = true; triggerState('happy', 2000); // Celebrate achievement } } function unlockAllAchievements() { achievements.value.forEach(a => a.unlocked = true); triggerState('happy', 2000); } async function checkEvolution() { // Simple evolution logic let evolved = false; if (stage.value === 'baby' && stats.value.age >= 3) { stage.value = 'child'; evolved = true; } else if (stage.value === 'child' && stats.value.age >= 7) { stage.value = 'adult'; evolved = true; } if (evolved) { triggerState('happy', 2000); // Celebrate await api.updatePetStatus({ stage: stage.value }); } } // --- Helpers --- function triggerState(tempState, duration) { const previousState = state.value; state.value = tempState; setTimeout(async () => { if (state.value === tempState) { // Only revert if state hasn't changed again state.value = previousState === 'sleep' ? 'idle' : 'idle'; // Sync state revert await api.updatePetStatus({ state: state.value }); } }, duration); } async function hatchEgg() { if (stage.value === 'egg') { stage.value = 'adult'; // Skip to adult for demo state.value = 'idle'; stats.value.hunger = 50; stats.value.happiness = 50; stats.value.health = 100; stats.value.poopCount = 0; // v2: Assign Destiny assignDestiny(); isCleaning.value = false; await api.updatePetStatus({ stage: stage.value, state: state.value, stats: stats.value }); } } async function reset() { await api.resetGame(); await initGame(); // Reload initial data tickCount = 0; } async function resurrect() { if (state.value !== 'dead') return; state.value = 'idle'; stats.value.health = 50; // Revive with half health stats.value.happiness = 50; stats.value.hunger = 50; console.log('Pet Resurrected!'); await api.updatePetStatus({ state: state.value, stats: stats.value }); } async function reincarnate() { // Inherit logic const prevDestiny = stats.value.destiny; const prevFavor = stats.value.deityFavor; const nextGen = (stats.value.generation || 1) + 1; // Reset everything await api.resetGame(); // Reload but keep some stats const petData = await api.getPetStatus(); stage.value = petData.stage; state.value = petData.state; stats.value = { ...stats.value, ...petData.stats }; // Apply Inheritance stats.value.generation = nextGen; stats.value.deityFavor = Math.floor(prevFavor * 0.2); if (prevDestiny && prevDestiny.rarity === 2) { stats.value.destiny = prevDestiny; } console.log('Pet Reincarnated to Gen', nextGen); await api.updatePetStatus({ stats: stats.value }); } // --- Lifecycle --- onMounted(() => { // initGame is called manually or by parent }); onUnmounted(() => { if (gameLoopId) clearInterval(gameLoopId); }); return { stage, state, stats, isCleaning, isLoading, error, initGame, feed, play, clean, sleep, hatchEgg, reset, achievements, unlockAllAchievements, assignDestiny, resurrect, reincarnate }; }