pet/src/composables/usePetSystem.js

406 lines
14 KiB
JavaScript
Raw Normal View History

2025-11-20 09:15:38 +00:00
import { ref, computed, onMounted, onUnmounted } from 'vue';
export function usePetSystem() {
// --- State ---
const stage = ref('egg'); // egg, baby, adult
const state = ref('idle'); // idle, sleep, eating, sick, dead, refuse
2025-11-22 13:25:35 +00:00
// --- 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 }
];
2025-11-20 09:15:38 +00:00
// --- 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
2025-11-22 03:23:02 +00:00
age: 1, // days (start at day 1)
2025-11-22 13:25:35 +00:00
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, // 神明好感度
2025-11-22 15:50:28 +00:00
destiny: null, // 天生命格 (Object)
// Deity System
currentDeity: 'mazu',
deityFavors: {
mazu: 0,
earthgod: 0,
matchmaker: 0,
wenchang: 0,
guanyin: 0
},
dailyPrayerCount: 0
2025-11-20 09:15:38 +00:00
});
2025-11-22 03:23:02 +00:00
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: '💖' }
]);
2025-11-20 09:15:38 +00:00
// --- Internal Timers ---
let gameLoopId = null;
2025-11-22 03:23:02 +00:00
let tickCount = 0;
2025-11-20 09:15:38 +00:00
const TICK_RATE = 3000; // 3 seconds per tick
2025-11-22 03:23:02 +00:00
const TICKS_PER_DAY = 20; // For testing: 1 minute = 1 day (usually 28800 for 24h)
2025-11-20 09:15:38 +00:00
const isCleaning = ref(false);
// --- Actions ---
2025-11-22 13:25:35 +00:00
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);
}
2025-11-20 09:15:38 +00:00
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;
2025-11-20 16:00:13 +00:00
// Chance to poop after eating (降低機率)
2025-11-22 13:25:35 +00:00
// Destiny Effect: Gluttony (暴食) might increase poop chance? Or just hunger decay.
2025-11-20 16:00:13 +00:00
if (Math.random() < 0.15) { // 從 0.3 降到 0.15
2025-11-20 09:15:38 +00:00
setTimeout(() => {
if (stats.value.poopCount < 4) {
stats.value.poopCount++;
}
}, 4000);
}
return true;
}
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);
return true;
}
function clean() {
if (stats.value.poopCount > 0 && !isCleaning.value) {
isCleaning.value = true;
// Delay removal for animation
setTimeout(() => {
stats.value.poopCount = 0;
stats.value.happiness += 10;
isCleaning.value = false;
}, 2000); // 2 seconds flush animation
return true;
}
return false;
}
function sleep() {
if (isCleaning.value) return;
if (state.value === 'idle') {
state.value = 'sleep';
} else if (state.value === 'sleep') {
state.value = 'idle'; // Wake up
}
}
// --- Game Loop ---
function tick() {
if (state.value === 'dead' || stage.value === 'egg') return;
// Decrease stats naturally
2025-11-22 13:25:35 +00:00
// Destiny Effect: Gluttony (暴食) - Hunger decreases faster (+30%)
let hungerDecay = 0.05;
if (stats.value.destiny?.id === 'gluttony') {
hungerDecay *= 1.3;
}
// Destiny Effect: Playful (愛玩) - Happiness decreases faster
let happinessDecay = 0.08;
if (stats.value.destiny?.id === 'playful') {
happinessDecay *= 1.2; // Faster decay
}
// Destiny Effect: DEX (敏捷) - Hunger decreases slower
// DEX 10 = -10% decay, DEX 50 = -50% decay
if (stats.value.dex > 0) {
const reduction = Math.min(0.5, stats.value.dex * 0.01); // Max 50% reduction
hungerDecay *= (1 - reduction);
}
2025-11-20 16:00:13 +00:00
2025-11-20 09:15:38 +00:00
if (state.value !== 'sleep') {
2025-11-22 13:25:35 +00:00
stats.value.hunger = Math.max(0, stats.value.hunger - hungerDecay);
stats.value.happiness = Math.max(0, stats.value.happiness - happinessDecay);
2025-11-20 09:15:38 +00:00
} else {
2025-11-20 16:00:13 +00:00
// Slower decay when sleeping (約 1/3 速度)
2025-11-22 13:25:35 +00:00
stats.value.hunger = Math.max(0, stats.value.hunger - (hungerDecay * 0.3));
stats.value.happiness = Math.max(0, stats.value.happiness - (happinessDecay * 0.3));
2025-11-20 09:15:38 +00:00
}
2025-11-20 16:00:13 +00:00
// Random poop generation (更低的機率:約 0.5% per tick)
// 平均約每 200 ticks = 10 分鐘拉一次
if (state.value !== 'sleep' && Math.random() < 0.005 && stats.value.poopCount < 4 && !isCleaning.value) {
2025-11-20 09:15:38 +00:00
stats.value.poopCount++;
}
2025-11-20 16:00:13 +00:00
// Health Logic (更溫和的健康下降)
// 便便影響健康:每個便便每 tick -0.1 health
2025-11-20 09:15:38 +00:00
if (stats.value.poopCount > 0) {
2025-11-20 16:00:13 +00:00
stats.value.health = Math.max(0, stats.value.health - (0.1 * stats.value.poopCount));
}
// 飢餓影響健康:飢餓值低於 20 時開始影響健康
if (stats.value.hunger < 20) {
const hungerPenalty = (20 - stats.value.hunger) * 0.02; // 飢餓越嚴重,扣越多
stats.value.health = Math.max(0, stats.value.health - hungerPenalty);
2025-11-20 09:15:38 +00:00
}
2025-11-20 16:00:13 +00:00
// 不開心影響健康:快樂值低於 20 時開始影響健康(較輕微)
if (stats.value.happiness < 20) {
const happinessPenalty = (20 - stats.value.happiness) * 0.01;
stats.value.health = Math.max(0, stats.value.health - happinessPenalty);
2025-11-20 09:15:38 +00:00
}
2025-11-20 16:00:13 +00:00
// Sickness Check (更低的生病機率)
2025-11-22 13:25:35 +00:00
// Destiny Effect: Purification (淨化) - Sickness chance -20%
2025-11-22 15:50:28 +00:00
// Deity Buff: 媽祖 - Sickness chance -15%
2025-11-22 13:25:35 +00:00
let sickChance = 0.1;
if (stats.value.destiny?.id === 'purification') {
sickChance *= 0.8;
}
2025-11-22 15:50:28 +00:00
if (stats.value.currentDeity === 'mazu' && stats.value.deityFavors?.mazu > 0) {
sickChance *= 0.85;
}
2025-11-22 13:25:35 +00:00
2025-11-20 09:15:38 +00:00
if (stats.value.health < 30 && state.value !== 'sick') {
2025-11-22 13:25:35 +00:00
if (Math.random() < sickChance) {
2025-11-20 09:15:38 +00:00
state.value = 'sick';
}
}
2025-11-20 16:00:13 +00:00
// Health Recovery (健康值可以緩慢恢復)
// 如果沒有便便、飢餓值和快樂值都高,健康值會緩慢恢復
2025-11-22 15:50:28 +00:00
let healthRecovery = 0.05;
// Deity Buff: 觀音 - Health 回復 +20%
if (stats.value.currentDeity === 'guanyin' && stats.value.deityFavors?.guanyin > 0) {
healthRecovery *= 1.2;
}
// Deity Buff: 月老 - Happiness 回復 +25%
if (stats.value.currentDeity === 'matchmaker' && stats.value.deityFavors?.matchmaker > 0) {
// Apply to happiness decay reduction (slower decay = faster recovery)
happinessDecay *= 0.75;
}
2025-11-20 16:00:13 +00:00
if (stats.value.poopCount === 0 && stats.value.hunger > 50 && stats.value.happiness > 50 && stats.value.health < 100 && state.value !== 'sick') {
2025-11-22 15:50:28 +00:00
stats.value.health = Math.min(100, stats.value.health + healthRecovery);
2025-11-20 09:15:38 +00:00
}
2025-11-20 16:00:13 +00:00
// Death Check (移除死亡機制,依照之前的討論)
// if (stats.value.health === 0) {
// state.value = 'dead';
// }
2025-11-22 03:23:02 +00:00
// Evolution / Growth
tickCount++;
if (tickCount >= TICKS_PER_DAY) {
stats.value.age++;
tickCount = 0;
checkEvolution();
}
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);
}
function checkEvolution() {
// Simple evolution logic
if (stage.value === 'baby' && stats.value.age >= 3) {
stage.value = 'child';
triggerState('happy', 2000); // Celebrate
} else if (stage.value === 'child' && stats.value.age >= 7) {
stage.value = 'adult';
triggerState('happy', 2000);
}
2025-11-20 09:15:38 +00:00
}
// --- Helpers ---
function triggerState(tempState, duration) {
const previousState = state.value;
state.value = tempState;
setTimeout(() => {
if (state.value === tempState) { // Only revert if state hasn't changed again
state.value = previousState === 'sleep' ? 'idle' : 'idle';
}
}, duration);
}
function hatchEgg() {
if (stage.value === 'egg') {
stage.value = 'baby'; // or 'adult' for now since we only have that sprite
// Let's map 'baby' to our 'adult' sprite for now, or just use 'adult'
stage.value = 'adult';
state.value = 'idle';
stats.value.hunger = 50;
stats.value.happiness = 50;
stats.value.health = 100;
stats.value.poopCount = 0;
2025-11-22 13:25:35 +00:00
// v2: Assign Destiny
assignDestiny();
2025-11-20 09:15:38 +00:00
isCleaning.value = false;
}
}
function reset() {
stage.value = 'egg';
state.value = 'idle';
isCleaning.value = false;
stats.value = {
hunger: 100,
happiness: 100,
health: 100,
weight: 500,
2025-11-22 03:23:02 +00:00
age: 1,
2025-11-22 13:25:35 +00:00
poopCount: 0,
// v2 Reset
str: 0,
int: 0,
dex: 0,
generation: 1,
deityFavor: 0,
destiny: null
2025-11-20 09:15:38 +00:00
};
2025-11-22 03:23:02 +00:00
tickCount = 0;
2025-11-20 09:15:38 +00:00
}
2025-11-22 13:25:35 +00:00
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;
// Penalty or Ghost Buff?
// For now just a console log, maybe visual effect later
console.log('Pet Resurrected!');
}
function reincarnate() {
// Inherit logic
const prevDestiny = stats.value.destiny;
const prevFavor = stats.value.deityFavor;
const nextGen = (stats.value.generation || 1) + 1;
// Reset everything
reset();
// Apply Inheritance
stats.value.generation = nextGen;
// 20% Favor inheritance
stats.value.deityFavor = Math.floor(prevFavor * 0.2);
// Destiny Inheritance (Optional: Maybe keep if it was a rare one?)
// For now, let's say if you had a Rare (2) destiny, you keep it.
// Otherwise, you get a new one on hatch.
if (prevDestiny && prevDestiny.rarity === 2) {
stats.value.destiny = prevDestiny;
}
console.log('Pet Reincarnated to Gen', nextGen);
}
2025-11-20 09:15:38 +00:00
// --- Lifecycle ---
onMounted(() => {
gameLoopId = setInterval(tick, TICK_RATE);
});
onUnmounted(() => {
if (gameLoopId) clearInterval(gameLoopId);
});
return {
stage,
state,
stats,
isCleaning,
feed,
play,
clean,
sleep,
hatchEgg,
2025-11-22 03:23:02 +00:00
reset,
achievements,
2025-11-22 13:25:35 +00:00
unlockAllAchievements,
assignDestiny, // Export for debug if needed
resurrect,
reincarnate
2025-11-20 09:15:38 +00:00
};
}