192 lines
5.5 KiB
JavaScript
192 lines
5.5 KiB
JavaScript
|
|
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
|
||
|
|
|
||
|
|
// --- 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: 0, // days
|
||
|
|
poopCount: 0 // Number of poops on screen
|
||
|
|
});
|
||
|
|
|
||
|
|
// --- Internal Timers ---
|
||
|
|
let gameLoopId = null;
|
||
|
|
const TICK_RATE = 3000; // 3 seconds per tick
|
||
|
|
|
||
|
|
const isCleaning = ref(false);
|
||
|
|
|
||
|
|
// --- Actions ---
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
// Chance to poop after eating
|
||
|
|
if (Math.random() < 0.3) {
|
||
|
|
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
|
||
|
|
if (state.value !== 'sleep') {
|
||
|
|
stats.value.hunger = Math.max(0, stats.value.hunger - 2);
|
||
|
|
stats.value.happiness = Math.max(0, stats.value.happiness - 1);
|
||
|
|
} else {
|
||
|
|
// Slower decay when sleeping
|
||
|
|
stats.value.hunger = Math.max(0, stats.value.hunger - 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Random poop generation (5% chance per tick)
|
||
|
|
if (state.value !== 'sleep' && Math.random() < 0.05 && stats.value.poopCount < 4 && !isCleaning.value) {
|
||
|
|
stats.value.poopCount++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Health Logic (Poop hurts health)
|
||
|
|
if (stats.value.poopCount > 0) {
|
||
|
|
stats.value.health = Math.max(0, stats.value.health - (2 * stats.value.poopCount));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (stats.value.hunger === 0) {
|
||
|
|
stats.value.health = Math.max(0, stats.value.health - 5);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sickness Check
|
||
|
|
if (stats.value.health < 30 && state.value !== 'sick') {
|
||
|
|
if (Math.random() < 0.3) {
|
||
|
|
state.value = 'sick';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Death Check
|
||
|
|
if (stats.value.health === 0) {
|
||
|
|
state.value = 'dead';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Evolution / Growth (Simple Age increment)
|
||
|
|
// In a real game, 1 day might be 24h, here maybe every 100 ticks?
|
||
|
|
// For now, let's just say age increases slowly.
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- 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;
|
||
|
|
isCleaning.value = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function reset() {
|
||
|
|
stage.value = 'egg';
|
||
|
|
state.value = 'idle';
|
||
|
|
isCleaning.value = false;
|
||
|
|
stats.value = {
|
||
|
|
hunger: 100,
|
||
|
|
happiness: 100,
|
||
|
|
health: 100,
|
||
|
|
weight: 500,
|
||
|
|
age: 0,
|
||
|
|
poopCount: 0
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- Lifecycle ---
|
||
|
|
onMounted(() => {
|
||
|
|
gameLoopId = setInterval(tick, TICK_RATE);
|
||
|
|
});
|
||
|
|
|
||
|
|
onUnmounted(() => {
|
||
|
|
if (gameLoopId) clearInterval(gameLoopId);
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
stage,
|
||
|
|
state,
|
||
|
|
stats,
|
||
|
|
isCleaning,
|
||
|
|
feed,
|
||
|
|
play,
|
||
|
|
clean,
|
||
|
|
sleep,
|
||
|
|
hatchEgg,
|
||
|
|
reset
|
||
|
|
};
|
||
|
|
}
|