Egg Hit Tracker

他们朝我扔鸡蛋,我用鸡蛋做披萨。We throw eggs at em.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Egg Hit Tracker
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  他们朝我扔鸡蛋,我用鸡蛋做披萨。We throw eggs at em.
// @author       TaichiSlippers
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 鸡蛋图片
    const eggImg = new Image();
    eggImg.src = 'https://tupian.li/images/2025/05/09/681dd7ab60709.png';

    // 创建全屏 Canvas
    const canvas = document.createElement('canvas');
    canvas.id = 'eggTrackerCanvas';
    Object.assign(canvas.style, { position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 999 });
    document.body.appendChild(canvas);
    const ctx = canvas.getContext('2d');
    function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
    resize(); window.addEventListener('resize', resize);

    // 抛物线鸡蛋类+抖动
    class EggProjectile {
        constructor(sx, sy, tx, ty, dmg, targetEl) {
            this.x = sx; this.y = sy; this.tx = tx; this.ty = ty; this.targetEl = targetEl; this.gravity = 0.3;
            const dx = tx - sx, dy = ty - sy, t = 60;
            this.vx = dx / t; this.vy = dy / t - 0.5 * this.gravity * t;
            this.rot = 0;
            const minSize = 20, maxSize = 80, minDmg = 1, maxDmg = 1200;
            const ratio = Math.min(Math.max((dmg - minDmg) / (maxDmg - minDmg), 0), 1);
            this.size = ratio * (maxSize - minSize) + minSize;
        }
        update() {
            this.vy += this.gravity;
            this.x += this.vx;
            this.y += this.vy;
            this.rot += 0.2;
        }
        draw() {
            ctx.save();
            ctx.translate(this.x, this.y);
            ctx.rotate(this.rot);
            ctx.drawImage(eggImg, -this.size / 2, -this.size / 2, this.size, this.size);
            ctx.restore();
        }
        done() {
            return this.y > canvas.height || Math.hypot(this.x - this.tx, this.y - this.ty) < 20;
        }
    }

    let projectiles = [];
    function animate() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        for (let i = projectiles.length - 1; i >= 0; i--) {
            const p = projectiles[i];
            p.update();
            p.draw();
            if (p.done()) {
                // 命中抖动特效
                if (p.targetEl) {
                    const shakeAmt = Math.min(10, p.size / 4);
                    p.targetEl.animate([
                        { transform: 'translate(0,0)' },
                        { transform: `translate(-${shakeAmt}px,0)` },
                        { transform: `translate(${shakeAmt}px,0)` },
                        { transform: 'translate(0,0)' }
                    ], { duration: 200, iterations: 2, easing: 'ease-in-out' });
                }
                projectiles.splice(i, 1);
            }
        }
        requestAnimationFrame(animate);
    }
    animate();

    function center(el) {
        const r = el.getBoundingClientRect();
        return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
    }

    // Websocket 劫持
    (function() {
        const desc = Object.getOwnPropertyDescriptor(MessageEvent.prototype, 'data');
        const orig = desc.get;
        desc.get = function() {
            const sock = this.currentTarget;
            const msg = orig.call(this);
            if (sock instanceof WebSocket && sock.url.includes('api.milkywayidle.com/ws')) {
                return handle(msg);
            }
            return msg;
        };
        Object.defineProperty(MessageEvent.prototype, 'data', desc);
    })();

    // 上一帧状态
    const prev = { pMP: [], mHP: [] };
    let autoIdx = 0;
    function handle(message) {
        let obj;
        try { obj = JSON.parse(message); } catch { return message; }
        if (obj.type === 'new_battle') {
            prev.pMP = obj.players.map(p => p.currentManapoints);
            prev.mHP = obj.monsters.map(m => m.currentHitpoints);
            autoIdx = 0;
        } else if (obj.type === 'battle_updated' && prev.mHP.length) {
            const pMap = obj.pMap, mMap = obj.mMap;
            // 检测施法者
            let caster = -1;
            Object.keys(pMap).forEach(i => {
                if (pMap[i].cMP < prev.pMP[i]) caster = +i;
                prev.pMP[i] = pMap[i].cMP;
            });
            const players = document.querySelectorAll('[class*="BattlePanel_playersArea"] [class*="CombatUnit_unit"]');
            const monsters = document.querySelectorAll('[class*="BattlePanel_monstersArea"] [class*="CombatUnit_unit"]');
            const playerCount = players.length;
            Object.keys(mMap).forEach(idx => {
                const i = +idx;
                const oldHP = prev.mHP[i], newHP = mMap[i].cHP;
                if (newHP < oldHP) {
                    const dmg = oldHP - newHP;
                    if (dmg > 0) {
                        const src = caster >= 0 ? caster : autoIdx % playerCount;
                        const fromEl = players[src], toEl = monsters[i];
                        if (fromEl && toEl) {
                            const s = center(fromEl), t = center(toEl);
                            projectiles.push(new EggProjectile(s.x, s.y, t.x, t.y, dmg, toEl));
                        }
                        if (caster < 0) autoIdx++;
                    }
                }
                prev.mHP[i] = newHP;
            });
        }
        return message;
    }
})();