Emoji Hit Tracker

我说君子六艺,有没有懂的

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Emoji Hit Tracker
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  我说君子六艺,有没有懂的
// @license MIT
// @author       Ratatatata
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 鸡蛋图片
    const emojiImg1 = new Image();
    const emojiImg2 = new Image();
    const emojiImg3 = new Image();
    const emojiImg4 = new Image();
    const emojiImg5 = new Image();

    emojiImg1.src = 'https://tupian.li/images/2025/05/09/681df8331e01d.png';
    emojiImg2.src = 'https://tupian.li/images/2025/05/09/681df85629f50.png';
    emojiImg3.src = 'https://tupian.li/images/2025/05/09/681df8583d73e.webp';
    emojiImg4.src = 'https://tupian.li/images/2025/05/09/681df8584b8bc.webp';
    emojiImg5.src = 'https://tupian.li/images/2025/05/09/681df863d2c3b.png';

    const emojiImages = [
       emojiImg1,emojiImg2,emojiImg3,emojiImg4,emojiImg5
    ];

    // 创建全屏 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*5;
            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;
            */

        this.x = sx; this.y = sy; this.tx = tx; this.ty = ty; this.targetEl = targetEl;
        this.gravity = 0.3; // 保持原版重力

        const dx = tx - sx;
        const dy = ty - sy;

        const horizontalSlowdownFactor = 1.8+Math.random()*0.4;

        const originalHorizontalTime = 60;
        const horizontalTime = originalHorizontalTime * horizontalSlowdownFactor;
        this.vx = dx / horizontalTime;

        const heightReductionFactor = 0.1+Math.random()*0.2;

        const verticalTime = horizontalTime;
        const peakTime = verticalTime / 2;
        const originalVy = dy / verticalTime - 0.5 * this.gravity * verticalTime;
        this.vy = originalVy * heightReductionFactor;
        this.gravity = this.gravity * heightReductionFactor;

        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;
        this.emojiType=emojiImages[ Math.floor(Math.random() * 5)];
        }
        update() {
            this.vy += this.gravity;
            this.x += this.vx;
            this.y += this.vy;
            this.rot += 0.05;
        }
        draw() {
            ctx.save();
            ctx.translate(this.x, this.y);
            ctx.rotate(this.rot);
            ctx.drawImage(this.emojiType, -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;
    }
})();