Virtual Pet - Cursor-Friendly

A virtual creature that follows your cursor, that you've nurtured, raised, and played with.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Virtual Pet - Cursor-Friendly
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  A virtual creature that follows your cursor, that you've nurtured, raised, and played with.
// @author       Mustafa Hakan
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_notification
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // Wait for body to be available
    function waitForBody(callback) {
        if (document.body) {
            callback();
        } else {
            setTimeout(() => waitForBody(callback), 100);
        }
    }

    // Creature data
    const PET = {
        name: GM_getValue('pet_name', 'Minik'),
        type: GM_getValue('pet_type', 'cat'),
        level: GM_getValue('pet_level', 1),
        xp: GM_getValue('pet_xp', 0),
        hunger: GM_getValue('pet_hunger', 100),
        happiness: GM_getValue('pet_happiness', 100),
        energy: GM_getValue('pet_energy', 100),
        coins: GM_getValue('pet_coins', 0),
        accessories: JSON.parse(GM_getValue('pet_acc', '[]')),
        color: GM_getValue('pet_color', '#ff6b35'),
        born: GM_getValue('pet_born', Date.now()),
        lastFed: GM_getValue('pet_lastFed', Date.now()),
        lastPlayed: GM_getValue('pet_lastPlayed', Date.now())
    };

    // Creature types
    const TYPES = {
        cat: { name: 'Cat', emoji: '🐱', sound: 'Meow!', foods: ['🐟','🥛','🐭'], toys: ['🧶','🎾','📦'] },
        dog: { name: 'Dog', emoji: '🐶', sound: 'Woof!', foods: ['🦴','🥩','🍖'], toys: ['🦴','🎾','🥏'] },
        dragon: { name: 'Dragon', emoji: '🐉', sound: 'Roar!', foods: ['🔥','🍖','💎'], toys: ['🏰','⚔️','💍'] },
        bunny: { name: 'Bunny', emoji: '🐰', sound: 'Hop!', foods: ['🥕','🥬','🍎'], toys: ['🏀','🪁','🎪'] },
        panda: { name: 'Panda', emoji: '🐼', sound: 'Purr!', foods: ['🎋','🍎','🍯'], toys: ['🪵','🧸','🏀'] },
        fox: { name: 'Fox', emoji: '🦊', sound: 'Yip!', foods: ['🍇','🧀','🥚'], toys: ['🪶','🎯','🧩'] }
    };

    const petType = TYPES[PET.type] || TYPES.cat;
    let petEl, statsEl, menuEl;
    let x = window.innerWidth / 2, y = window.innerHeight / 2;
    let targetX = x, targetY = y;
    let velX = 0, velY = 0;
    let animFrame;
    let state = 'idle';
    let stateTimer = 0;
    let particles = [];
    let mouseX = x, mouseY = y;

    function save() {
        try {
            GM_setValue('pet_name', PET.name);
            GM_setValue('pet_type', PET.type);
            GM_setValue('pet_level', PET.level);
            GM_setValue('pet_xp', PET.xp);
            GM_setValue('pet_hunger', PET.hunger);
            GM_setValue('pet_happiness', PET.happiness);
            GM_setValue('pet_energy', PET.energy);
            GM_setValue('pet_coins', PET.coins);
            GM_setValue('pet_acc', JSON.stringify(PET.accessories));
            GM_setValue('pet_color', PET.color);
            GM_setValue('pet_lastFed', PET.lastFed);
            GM_setValue('pet_lastPlayed', PET.lastPlayed);
        } catch(e) {
            console.log('Pet save error:', e);
        }
    }

    function addXP(amount) {
        PET.xp += amount;
        if (PET.xp >= PET.level * 100) {
            PET.xp = 0;
            PET.level++;
            PET.coins += PET.level * 10;
            try {
                GM_notification({ text: `🎉 ${PET.name} reached level ${PET.level}! +${PET.level*10} 💰`, timeout: 3000 });
            } catch(e) {}
            spawnParticles('levelup');
        }
        save();
    }

    function spawnParticles(type) {
        const emojis = {
            eat: ['✨','💫','🌟'],
            play: ['🎈','💝','💖'],
            levelup: ['🎉','🎊','⭐','🌟','💫'],
            sleep: ['💤','💤','💤'],
            love: ['💕','💗','💖']
        };
        const list = emojis[type] || ['✨'];
        for (let i = 0; i < 8; i++) {
            particles.push({
                emoji: list[Math.floor(Math.random() * list.length)],
                x: x, y: y - 20,
                vx: (Math.random() - 0.5) * 4,
                vy: -Math.random() * 4 - 2,
                life: 1,
                size: 16 + Math.random() * 16
            });
        }
    }

    function feedPet() {
        if (PET.hunger >= 100) { showMsg('I\'m full! 😊'); return; }
        if (Date.now() - PET.lastFed < 5000) { showMsg('Not hungry yet! ⏳'); return; }
        PET.hunger = Math.min(100, PET.hunger + 20);
        PET.lastFed = Date.now();
        PET.coins = Math.max(0, PET.coins - 5);
        addXP(10);
        state = 'eating';
        stateTimer = 30;
        spawnParticles('eat');
        showMsg('Yummy! 😋');
        save();
    }

    function playPet() {
        if (PET.energy < 20) { showMsg('Too tired... 😴'); return; }
        if (Date.now() - PET.lastPlayed < 3000) { showMsg('Need a break! ⏳'); return; }
        PET.happiness = Math.min(100, PET.happiness + 20);
        PET.energy = Math.max(0, PET.energy - 20);
        PET.hunger = Math.max(0, PET.hunger - 10);
        PET.lastPlayed = Date.now();
        PET.coins += Math.floor(Math.random() * 10) + 1;
        addXP(15);
        state = 'playing';
        stateTimer = 40;
        spawnParticles('play');
        if (Math.random() < 0.3) {
            PET.coins += 5;
            showMsg('💰 Found treasure! +5 coin');
        } else {
            showMsg('Yay! 🎾');
        }
        save();
    }

    function sleepPet() {
        if (PET.energy > 80) { showMsg('Not sleepy! 😄'); return; }
        state = 'sleeping';
        stateTimer = 60;
        PET.energy = Math.min(100, PET.energy + 30);
        PET.happiness = Math.min(100, PET.happiness + 5);
        spawnParticles('sleep');
        showMsg('Zzz... 😴');
        save();
    }

    function showMsg(text) {
        if (!petEl) return;
        const msg = document.createElement('div');
        msg.textContent = text;
        msg.style.cssText = `
            position: fixed; left: ${x}px; top: ${y - 50}px;
            background: rgba(0,0,0,0.85); color: #fff; padding: 8px 16px;
            border-radius: 20px; font: 13px system-ui; pointer-events: none;
            z-index: 2147483648; animation: petMsg 2s forwards; white-space: nowrap;
        `;
        document.body.appendChild(msg);
        setTimeout(() => { if (msg.parentNode) msg.remove(); }, 2000);
    }

    function changeAccessory() {
        const all = ['🎩','👑','🎀','🕶️','🧢','💍','📿','🎒','🧣','👒','🪖','🎭'];
        const available = all.filter(a => !PET.accessories.includes(a));
        if (available.length === 0) { showMsg('Collected them all! 🏆'); return; }
        const chosen = available[Math.floor(Math.random() * available.length)];
        PET.accessories.push(chosen);
        PET.coins = Math.max(0, PET.coins - 10);
        spawnParticles('love');
        showMsg(`New accessory: ${chosen}!`);
        save();
    }

    function createPet() {
        if (document.getElementById('virtual-pet')) return;
        petEl = document.createElement('div');
        petEl.id = 'virtual-pet';
        petEl.style.cssText = `
            position: fixed; left: ${x}px; top: ${y}px; z-index: 2147483645;
            font-size: ${30 + PET.level * 3}px; cursor: pointer; pointer-events: all;
            transition: none; user-select: none; transform: translate(-50%, -50%);
            filter: drop-shadow(0 5px 15px rgba(0,0,0,0.5));
        `;
        updatePetAppearance();
        document.body.appendChild(petEl);

        petEl.addEventListener('click', (e) => {
            e.stopPropagation();
            if (state === 'sleeping') { showMsg('Shh sleeping... 😴'); return; }
            PET.happiness = Math.min(100, PET.happiness + 5);
            spawnParticles('love');
            if (Math.random() < 0.2) PET.coins++;
            save();
        });

        petEl.addEventListener('dblclick', (e) => {
            e.stopPropagation();
            toggleMenu();
        });
    }

    function updatePetAppearance() {
        if (!petEl) return;
        let display = petType.emoji;
        if (PET.accessories.length > 0) {
            display += PET.accessories[PET.accessories.length - 1];
        }
        if (state === 'sleeping') display += '💤';
        petEl.textContent = display;
        petEl.style.fontSize = (30 + PET.level * 3) + 'px';
        petEl.style.filter = `drop-shadow(0 5px 15px rgba(0,0,0,0.5)) drop-shadow(0 0 ${5+PET.level*2}px ${PET.color})`;
    }

    function createStats() {
        if (document.getElementById('pet-stats')) return;
        statsEl = document.createElement('div');
        statsEl.id = 'pet-stats';
        statsEl.style.cssText = `
            position: fixed; bottom: 20px; left: 20px; background: rgba(15,15,20,0.92);
            border: 1px solid rgba(255,255,255,0.15); border-radius: 16px; padding: 15px;
            z-index: 2147483645; font: 12px system-ui; color: #ccc; min-width: 200px;
            backdrop-filter: blur(20px); box-shadow: 0 10px 30px rgba(0,0,0,0.6);
        `;
        updateStats();
        document.body.appendChild(statsEl);
    }

    function updateStats() {
        if (!statsEl) return;
        statsEl.innerHTML = `
            <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
                <span style="font-size:24px;">${petType.emoji}</span>
                <div>
                    <div style="color:#fff;font-weight:700;">${PET.name}</div>
                    <div style="color:#888;font-size:10px;">${petType.name} • Lv ${PET.level}</div>
                </div>
            </div>
            ${bar('🍗', 'Hunger', PET.hunger, '#f97316')}
            ${bar('😊', 'Happiness', PET.happiness, '#f472b6')}
            ${bar('⚡', 'Energy', PET.energy, '#4ade80')}
            <div style="display:flex;justify-content:space-between;margin-top:8px;">
                <span>⭐ XP: ${PET.xp}/${PET.level*100}</span>
                <span>💰 ${PET.coins}</span>
            </div>
            <div style="display:flex;gap:4px;margin-top:8px;">
                <button class="pet-feed-btn" style="flex:1;padding:8px;border-radius:8px;border:1px solid #f97316;background:#f9731622;color:#f97316;cursor:pointer;font-size:16px;" title="Feed (-5💰)">🍗</button>
                <button class="pet-play-btn" style="flex:1;padding:8px;border-radius:8px;border:1px solid #f472b6;background:#f472b622;color:#f472b6;cursor:pointer;font-size:16px;" title="Play">🎾</button>
                <button class="pet-sleep-btn" style="flex:1;padding:8px;border-radius:8px;border:1px solid #4ade80;background:#4ade8022;color:#4ade80;cursor:pointer;font-size:16px;" title="Sleep">💤</button>
                <button class="pet-acc-btn" style="flex:1;padding:8px;border-radius:8px;border:1px solid #a78bfa;background:#a78bfa22;color:#a78bfa;cursor:pointer;font-size:16px;" title="Accessory (-10💰)">🎀</button>
            </div>
            <div style="color:#666;font-size:9px;text-align:center;margin-top:6px;">Click: Pet • Double-click: Menu</div>
        `;

        // Add event listeners directly
        const feedBtn = statsEl.querySelector('.pet-feed-btn');
        const playBtn = statsEl.querySelector('.pet-play-btn');
        const sleepBtn = statsEl.querySelector('.pet-sleep-btn');
        const accBtn = statsEl.querySelector('.pet-acc-btn');

        if (feedBtn) feedBtn.onclick = feedPet;
        if (playBtn) playBtn.onclick = playPet;
        if (sleepBtn) sleepBtn.onclick = sleepPet;
        if (accBtn) accBtn.onclick = changeAccessory;
    }

    function bar(icon, label, value, color) {
        return `<div style="margin:4px 0;"><span style="font-size:10px;">${icon} ${label}</span><div style="height:6px;background:#333;border-radius:3px;margin-top:2px;"><div style="height:100%;width:${value}%;background:${color};border-radius:3px;transition:width 0.5s;"></div></div></div>`;
    }

    function createMenu() {
        if (menuEl) { menuEl.remove(); menuEl = null; }
        menuEl = document.createElement('div');
        menuEl.id = 'pet-menu';
        menuEl.style.cssText = `
            position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
            background: rgba(15,15,20,0.95); border: 1px solid rgba(255,255,255,0.2);
            border-radius: 20px; padding: 25px; z-index: 2147483649; font: 14px system-ui;
            color: #fff; min-width: 280px; backdrop-filter: blur(30px);
            box-shadow: 0 20px 60px rgba(0,0,0,0.8);
        `;

        const menuHTML = `
            <div style="font-weight:700;font-size:18px;margin-bottom:15px;text-align:center;">🐾 ${PET.name} Menu</div>
            <div style="display:flex;flex-direction:column;gap:8px;">
                <button id="pet-btn-name" style="padding:12px;border-radius:10px;border:1px solid #444;background:#222;color:#fff;cursor:pointer;">✏️ Change Name</button>
                <button id="pet-btn-type" style="padding:12px;border-radius:10px;border:1px solid #444;background:#222;color:#fff;cursor:pointer;">🔄 Change Type</button>
                <button id="pet-btn-color" style="padding:12px;border-radius:10px;border:1px solid #444;background:#222;color:#fff;cursor:pointer;">🎨 Change Color</button>
                <button id="pet-btn-stats" style="padding:12px;border-radius:10px;border:1px solid #444;background:#222;color:#fff;cursor:pointer;">📊 Detailed Stats</button>
            </div>
            <button id="pet-btn-close" style="margin-top:15px;width:100%;padding:10px;border-radius:10px;border:1px solid #444;background:transparent;color:#888;cursor:pointer;">Close</button>
        `;
        menuEl.innerHTML = menuHTML;
        document.body.appendChild(menuEl);

        document.getElementById('pet-btn-name').onclick = () => {
            const name = prompt('New name:', PET.name);
            if (name && name.trim()) { 
                PET.name = name.trim(); 
                save(); 
                updateStats(); 
                updatePetAppearance(); 
                showMsg(`My name is now ${PET.name}! 🎉`); 
            }
        };

        document.getElementById('pet-btn-type').onclick = () => {
            const types = Object.keys(TYPES);
            const current = types.indexOf(PET.type);
            PET.type = types[(current + 1) % types.length];
            save(); 
            updateStats(); 
            updatePetAppearance();
            showMsg(`Now I'm a ${TYPES[PET.type].name}! ${TYPES[PET.type].emoji}`);
        };

        document.getElementById('pet-btn-color').onclick = () => {
            const color = prompt('Color (hex):', PET.color);
            if (color) { 
                PET.color = color; 
                save(); 
                updatePetAppearance(); 
                showMsg('My color changed! ✨'); 
            }
        };

        document.getElementById('pet-btn-stats').onclick = () => {
            const age = Math.floor((Date.now() - PET.born) / 86400000);
            const acc = PET.accessories.length > 0 ? PET.accessories.join(' ') : 'None';
            alert(`${PET.name} - ${petType.name} Lv ${PET.level}\n\n🍗 Hunger: ${PET.hunger}%\n😊 Happiness: ${PET.happiness}%\n⚡ Energy: ${PET.energy}%\n⭐ XP: ${PET.xp}/${PET.level*100}\n💰 Coin: ${PET.coins}\n🎀 Accessories: ${acc}\n📅 Age: ${age} days`);
        };

        document.getElementById('pet-btn-close').onclick = () => {
            if (menuEl) { menuEl.remove(); menuEl = null; }
        };

        // Hover effects
        ['pet-btn-name','pet-btn-type','pet-btn-color','pet-btn-stats'].forEach(id => {
            const btn = document.getElementById(id);
            if (btn) {
                btn.onmouseover = () => { btn.style.background = '#333'; };
                btn.onmouseout = () => { btn.style.background = '#222'; };
            }
        });
    }

    function toggleMenu() {
        if (document.getElementById('pet-menu')) {
            const existing = document.getElementById('pet-menu');
            existing.remove();
            menuEl = null;
        } else {
            createMenu();
        }
    }

    function updatePet() {
        if (!petEl) return;

        const dx = mouseX - x;
        const dy = mouseY - y;
        const dist = Math.sqrt(dx * dx + dy * dy);

        if (state === 'sleeping') {
            velX *= 0.9;
            velY *= 0.9;
        } else if (dist > 30) {
            const speed = 0.08;
            velX += dx * speed;
            velY += dy * speed;
        } else {
            velX *= 0.95;
            velY *= 0.95;
            if (dist < 5 && Math.random() < 0.01) {
                spawnParticles('love');
            }
        }

        const maxSpeed = state === 'playing' ? 12 : 8;
        const spd = Math.sqrt(velX * velX + velY * velY);
        if (spd > maxSpeed) {
            velX = velX / spd * maxSpeed;
            velY = velY / spd * maxSpeed;
        }

        x += velX;
        y += velY;

        x = Math.max(20, Math.min(window.innerWidth - 20, x));
        y = Math.max(20, Math.min(window.innerHeight - 20, y));

        if (stateTimer > 0) {
            stateTimer--;
        } else if (state !== 'idle') {
            state = 'idle';
        }

        petEl.style.left = x + 'px';
        petEl.style.top = y + 'px';

        particles = particles.filter(p => {
            p.x += p.vx;
            p.y += p.vy;
            p.vy += 0.1;
            p.life -= 0.03;
            return p.life > 0;
        });

        updatePetAppearance();
        updateStats();
        renderParticles();

        if (Math.random() < 0.001) {
            PET.hunger = Math.max(0, PET.hunger - 1);
            PET.happiness = Math.max(0, PET.happiness - 0.5);
            PET.energy = Math.min(100, PET.energy + 0.5);
            save();
        }

        animFrame = requestAnimationFrame(updatePet);
    }

    let particlesCanvas, particlesCtx;
    function renderParticles() {
        if (!particlesCanvas) {
            particlesCanvas = document.createElement('canvas');
            particlesCanvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:2147483646;';
            document.body.appendChild(particlesCanvas);
            particlesCanvas.width = window.innerWidth;
            particlesCanvas.height = window.innerHeight;
            particlesCtx = particlesCanvas.getContext('2d');
        }
        if (!particlesCtx) return;
        particlesCtx.clearRect(0, 0, particlesCanvas.width, particlesCanvas.height);
        particles.forEach(p => {
            particlesCtx.globalAlpha = p.life;
            particlesCtx.font = p.size + 'px serif';
            particlesCtx.fillText(p.emoji, p.x, p.y);
        });
        particlesCtx.globalAlpha = 1;
    }

    function init() {
        GM_addStyle(`
            @keyframes petMsg { 
                0% { opacity: 0; transform: translateY(10px); } 
                20% { opacity: 1; transform: translateY(0); } 
                80% { opacity: 1; } 
                100% { opacity: 0; transform: translateY(-30px); } 
            }
        `);

        document.addEventListener('mousemove', (e) => {
            mouseX = e.clientX;
            mouseY = e.clientY;
        });

        document.addEventListener('click', (e) => {
            if (e.target.closest('#pet-menu') || e.target.closest('#virtual-pet') || e.target.closest('#pet-stats')) return;
            if (document.getElementById('pet-menu')) {
                document.getElementById('pet-menu').remove();
                menuEl = null;
            }
        });

        window.addEventListener('resize', () => {
            if (particlesCanvas) {
                particlesCanvas.width = window.innerWidth;
                particlesCanvas.height = window.innerHeight;
            }
        });

        createPet();
        createStats();
        updatePet();

        try {
            GM_notification({ text: `🐾 ${PET.name} is here! Click to pet, double-click for menu.`, timeout: 3000 });
        } catch(e) {}

        console.log('🐾 Virtual Pet loaded!');
    }

    waitForBody(init);
})();