// ==UserScript== // @name MWI-Hit-Tracker // @namespace http://tampermonkey.net/ // @version 0.3 // @description 战斗过程中实时显示攻击命中目标 // @author Artintel // @license MIT // @match https://www.milkywayidle.com/* // @match https://test.milkywayidle.com/* // @icon https://www.milkywayidle.com/favicon.svg // @grant none // @downloadURL none // ==/UserScript== (function() { 'use strict'; let monstersHP = []; let playersMP = []; hookWS(); function hookWS() { const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data"); const oriGet = dataProperty.get; dataProperty.get = hookedGet; Object.defineProperty(MessageEvent.prototype, "data", dataProperty); function hookedGet() { const socket = this.currentTarget; if (!(socket instanceof WebSocket)) { return oriGet.call(this); } if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) { return oriGet.call(this); } const message = oriGet.call(this); Object.defineProperty(this, "data", { value: message }); // Anti-loop return handleMessage(message); } } // 动画效果 function getElementCenter(element) { const rect = element.getBoundingClientRect(); return { x: rect.left + rect.width/2, y: rect.top + rect.height/2 }; } function createParabolaPath(startElem, endElem) { const start = getElementCenter(startElem); const end = getElementCenter(endElem); // 弧度调整位置(修改这个数值控制弧度) const curveHeight = -80; // 数值越大弧度越高(负值向上弯曲) const controlPoint = { x: (start.x + end.x) / 2, y: Math.min(start.y, end.y) + curveHeight // 调整这里 }; return `M ${start.x} ${start.y} Q ${controlPoint.x} ${controlPoint.y}, ${end.x} ${end.y}`; } const lineColor = [ "rgba(255, 99, 132, 1)", // 浅粉色 "rgba(54, 162, 235, 1)", // 浅蓝色 "rgba(255, 206, 86, 1)", // 浅黄色 "rgba(75, 192, 192, 1)", // 浅绿色 "rgba(153, 102, 255, 1)", // 浅紫色 "rgba(255, 159, 64, 1)", // 浅橙色 ]; const filterColor = [ "rgba(255, 99, 132, 0.8)", // 浅粉色 "rgba(54, 162, 235, 0.8)", // 浅蓝色 "rgba(255, 206, 86, 0.8)", // 浅黄色 "rgba(75, 192, 192, 0.8)", // 浅绿色 "rgba(153, 102, 255, 0.8)", // 浅紫色 "rgba(255, 159, 64, 0.8)", // 浅橙色 ]; function createEffect(startElem, endElem, hpDiff, index) { let strokeWidth = '1px'; let filterWidth = '1px'; if (hpDiff >= 1000){ strokeWidth = '5px'; filterWidth = '4px'; } else if (hpDiff >= 700) { strokeWidth = '4px'; filterWidth = '3px'; } else if (hpDiff >= 500) { strokeWidth = '3px'; filterWidth = '3px'; } else if (hpDiff >= 300) { strokeWidth = '2px'; filterWidth = '2px'; } else if (hpDiff >= 100) { strokeWidth = '2px'; } const svg = document.getElementById('svg-container'); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); Object.assign(path.style, { stroke: lineColor[index], strokeWidth: strokeWidth, fill: 'none', strokeLinecap: 'round', filter: 'drop-shadow(0 0 '+filterWidth+' '+filterColor[index]+')' }); path.setAttribute('d', createParabolaPath(startElem, endElem)); // 入场动画 const length = path.getTotalLength(); path.style.strokeDasharray = length; path.style.strokeDashoffset = length; svg.appendChild(path); // 绘制动画 requestAnimationFrame(() => { path.style.transition = 'stroke-dashoffset 0.1s linear'; path.style.strokeDashoffset = '0'; }); // 自动移除 setTimeout(() => { path.style.opacity = '0'; path.style.transition = 'opacity 0.1s linear'; setTimeout(() => svg.removeChild(path), 500); }, 800); } // 添加窗口resize监听 let isResizeListenerAdded = false; function createLine(from, to, hpDiff) { const container = document.querySelector(".BattlePanel_playersArea__vvwlB"); if (container && container.children.length > 0) { const playersContainer = container.children[0]; const effectFrom = playersContainer.children[from]; const monsterContainer = document.querySelector(".BattlePanel_monstersArea__2dzrY").children[0]; const effectTo = monsterContainer.children[to]; const svg = document.getElementById('svg-container'); if(!svg){ const svgContainer = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svgContainer.id = 'svg-container'; Object.assign(svgContainer.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', pointerEvents: 'none', overflow: 'visible', zIndex: '190' }); // 设置SVG原生属性 svgContainer.setAttribute('viewBox', `0 0 ${window.innerWidth} ${window.innerHeight}`); svgContainer.setAttribute('preserveAspectRatio', 'none'); // 初始化viewBox const updateViewBox = () => { svgContainer.setAttribute('viewBox', `0 0 ${window.innerWidth} ${window.innerHeight}`); }; updateViewBox(); //playersContainer.appendChild(svgContainer); document.querySelector(".GamePage_mainPanel__2njyb").appendChild(svgContainer); //document.body.appendChild(svgContainer); // 添加resize监听(确保只添加一次) if (!isResizeListenerAdded) { window.addEventListener('resize', () => { updateViewBox(); }); isResizeListenerAdded = true; } } createEffect(effectFrom, effectTo, hpDiff, from); } } function handleMessage(message) { let obj = JSON.parse(message); if (obj && obj.type === "new_battle") { monstersHP = obj.monsters.map((monster) => monster.currentHitpoints); playersMP = obj.players.map((player) => player.currentManapoints); } else if (obj && obj.type === "battle_updated" && monstersHP.length) { const mMap = obj.mMap; const pMap = obj.pMap; const playerIndices = Object.keys(obj.pMap); let castPlayer = -1; playerIndices.forEach((userIndex) => { if(pMap[userIndex].cMP < playersMP[userIndex]){castPlayer = userIndex;} playersMP[userIndex] = pMap[userIndex].cMP; }); monstersHP.forEach((mHP, mIndex) => { const monster = mMap[mIndex]; if (monster) { const hpDiff = mHP - monster.cHP; monstersHP[mIndex] = monster.cHP; if (hpDiff > 0) { if (playerIndices.length > 1) { playerIndices.forEach((userIndex) => { if(userIndex === castPlayer) { createLine(userIndex, mIndex, hpDiff); } }); } else { createLine(playerIndices[0], mIndex, hpDiff); } } } }); } return message; } })();