// ==UserScript== // @name MWI-Hit-Tracker // @namespace http://tampermonkey.net/ // @version 0.1 // @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}`; } function createEffect(startElem, endElem) { const svg = document.getElementById('svg-container'); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); Object.assign(path.style, { stroke: '#FF6B6B', strokeWidth: '2px', fill: 'none', strokeLinecap: 'round', filter: 'drop-shadow(0 0 4px rgba(255,107,107,0.8))' }); 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) { const playersContainer = document.querySelector(".BattlePanel_playersArea__vvwlB").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.body.appendChild(svgContainer); // 添加resize监听(确保只添加一次) if (!isResizeListenerAdded) { window.addEventListener('resize', () => { updateViewBox(); // 可以在这里添加已有路径的更新逻辑(如果需要) }); isResizeListenerAdded = true; } } createEffect(effectFrom, effectTo); } 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); } }); } else { createLine(playerIndices[0], mIndex); } } } }); } return message; } })();