// ==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;
}
})();