DFProfiler Path Finder

Find the fastest path in DFProfiler

当前为 2023-11-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name DFProfiler Path Finder
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Find the fastest path in DFProfiler
  6. // @author Runonstof
  7. // @match https://*.dfprofiler.com/bossmap
  8. // @match https://*.dfprofiler.com/profile/view/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=dfprofiler.com
  10. // @grant unsafeWindow
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // === Utility functions ===
  18.  
  19. function GM_addStyle(css) {
  20. var style = document.getElementById("GM_addStyleBy8626") || (function() {
  21. var style = document.createElement('style');
  22. style.type = 'text/css';
  23. style.id = "GM_addStyleBy8626";
  24. document.head.appendChild(style);
  25. return style;
  26. })();
  27. var sheet = style.sheet;
  28. console.log(sheet);
  29. sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length);
  30. }
  31.  
  32. function GM_addStyle_object(selector, styles) {
  33. var css = selector + "{";
  34. for (var key in styles) {
  35. css += key + ":" + styles[key] + ";";
  36. }
  37. css += "}";
  38. GM_addStyle(css);
  39. }
  40.  
  41.  
  42. function ready(fn) {
  43. if (document.readyState != 'loading'){
  44. fn();
  45. } else {
  46. document.addEventListener('DOMContentLoaded', fn);
  47. }
  48. }
  49.  
  50. function AStar(emptyCells) {
  51. this.emptyCells == emptyCells || [];
  52.  
  53. this.Node = function Node(x, y) {
  54. this.x = parseInt(x);
  55. this.y = parseInt(y);
  56. this.g = 0; // cost from start node
  57. this.h = 0; // heuristic (estimated cost to target)
  58. this.f = 0; // total cost (g + h)
  59. this.parent = null;
  60. }
  61.  
  62. this.isInsideMap = function(x, y) {
  63. return x >= 1000 && x <= 1058 && y >= 981 && y <= 1019;
  64. };
  65.  
  66. this.isCellEmpty = function(x, y) {
  67. return emptyCells.some(function(cell) {
  68. return cell.x == x && cell.y == y;
  69. });
  70. };
  71.  
  72. this.heuristic = function(node, target) {
  73. // Manhattan distance heuristic
  74. return Math.abs(node.x - target.x) + Math.abs(node.y - target.y);
  75. };
  76.  
  77. this.find = function(startPos, endPos) {
  78. var openList = [];
  79. var closedList = [];
  80.  
  81. var startNode = new this.Node(startPos.x, startPos.y);
  82. var endNode = new this.Node(endPos.x, endPos.y);
  83.  
  84. openList.push(startNode);
  85.  
  86. while (openList.length > 0) {
  87. var currentNode = openList[0];
  88. var currentIndex = 0;
  89.  
  90. for (var i = 1; i < openList.length; i++) {
  91. if (openList[i].f < currentNode.f) {
  92. currentNode = openList[i];
  93. currentIndex = i;
  94. }
  95. }
  96.  
  97. openList.splice(currentIndex, 1);
  98. closedList.push(currentNode);
  99.  
  100. if (currentNode.x === endNode.x && currentNode.y === endNode.y) {
  101. var path = [];
  102. var current = currentNode;
  103. while (current !== null) {
  104. path.push({ x: current.x, y: current.y });
  105. current = current.parent;
  106. }
  107. return path.reverse();
  108. }
  109.  
  110. var neighbors = [
  111. { x: 1, y: -1 },
  112. { x: 1, y: 1 },
  113. { x: -1, y: 1 },
  114. { x: -1, y: -1 },
  115.  
  116. { x: 0, y: 1 },
  117. { x: 1, y: 0 },
  118. { x: 0, y: -1 },
  119. { x: -1, y: 0 },
  120.  
  121. ];
  122.  
  123. for (var neighbourIndex in neighbors) {
  124. var neighborDelta = neighbors[neighbourIndex];
  125. var neighborX = currentNode.x + neighborDelta.x;
  126. var neighborY = currentNode.y + neighborDelta.y;
  127.  
  128. if (!this.isInsideMap(neighborX, neighborY) || this.isCellEmpty(neighborX, neighborY)) {
  129. // console.log('cell is empty or outside map:', neighborX, neighborY);
  130. continue;
  131. }
  132.  
  133. var neighborNode = new this.Node(neighborX, neighborY);
  134.  
  135. var checkNeighbor = function(node) {
  136. return node.x === neighborX && node.y === neighborY;
  137. };
  138.  
  139. if (closedList.some(checkNeighbor)) {
  140. continue;
  141. }
  142.  
  143. var tentativeG = currentNode.g + 1; // Assuming each step costs 1
  144.  
  145. if (!openList.some(checkNeighbor) || tentativeG < neighborNode.g) {
  146. neighborNode.g = tentativeG;
  147. neighborNode.h = this.heuristic(neighborNode, endNode);
  148. neighborNode.f = neighborNode.g + neighborNode.h;
  149. neighborNode.parent = currentNode;
  150.  
  151. if (!openList.some(checkNeighbor)) {
  152. openList.push(neighborNode);
  153. }
  154. }
  155. }
  156. }
  157.  
  158. // console.log(closedList);
  159.  
  160. return null; // No path found
  161. }
  162. }
  163.  
  164. // === CSS styles ===
  165.  
  166. GM_addStyle_object('#boss-data-section #mission-info, #bossmap-page #mission-info', {
  167. 'border-radius': '25px 25px 0 0',
  168. });
  169. GM_addStyle_object('#boss-data-section #mission-info-distance-viewer, #bossmap-page #mission-info-distance-viewer', {
  170. 'position': 'absolute !important',
  171. 'background-color': 'hsla(0,0%,5%,.8)',
  172. 'border-radius': '0 0 25px 25px',
  173. 'padding': '5px',
  174. 'top': '770px',
  175. 'left': 'calc(50% - 16pt * 20)',
  176. 'right': 'calc(50% - 16pt * 20)',
  177. });
  178.  
  179. GM_addStyle_object('#boss-data-section #mission-info-buttons-title, #bossmap-page #mission-info-buttons-title', {
  180. 'color': 'white',
  181. 'font-size': '20px',
  182. });
  183. GM_addStyle_object('#boss-data-section #mission-info-buttons-subtitle, #bossmap-page #mission-info-buttons-subtitle', {
  184. 'color': 'white',
  185. 'font-size': '14px',
  186. });
  187. GM_addStyle_object('#boss-data-section button.mission-info-button, #bossmap-page button.mission-info-button', {
  188. 'background-color': 'gray',
  189. 'color': 'black',
  190. 'padding': '0.25em 0.5em',
  191. });
  192. GM_addStyle_object('#boss-data-section button.mission-info-button:hover, #bossmap-page button.mission-info-button:hover', {
  193. 'color': 'white',
  194. });
  195. GM_addStyle_object('#boss-data-section #dist-buttons, #bossmap-page #dist-buttons', {
  196. 'display': 'flex',
  197. 'gap': '10px',
  198. 'justify-content': 'center',
  199. });
  200. GM_addStyle_object('#boss-data-section td.coord.path, #bossmap-page td.coord.path', {
  201. 'background-color': 'yellow !important',
  202. 'color': 'black !important',
  203. });
  204.  
  205. ready(function () {
  206.  
  207. // === Create Elements ===
  208. var missionHolder = document.getElementById('mission-holder');
  209.  
  210. var container = document.createElement('div');
  211. container.id = 'mission-info-distance-viewer';
  212.  
  213. container.innerHTML = '<div id="mission-info-buttons-title">Path finder</div>';
  214. container.innerHTML += '<div id="mission-info-buttons-subtitle">No path selected</div>';
  215. container.innerHTML += '<div id="dist-buttons"><button id="dist-set-start" class="mission-info-button">Set start cell</button><button id="dist-set-end" class="mission-info-button">Set end cell</button><button id="dist-clear" style="display: none;" class="mission-info-button">Clear path</button></div>';
  216.  
  217. missionHolder.appendChild(container);
  218.  
  219.  
  220. unsafeWindow.closeMissionHolder = function (event) {
  221. if (event.target.closest('#mission-info-distance-viewer')) return;
  222.  
  223. missionHolder.style.display = 'none';
  224. };
  225.  
  226. missionHolder.setAttribute('onclick', 'closeMissionHolder(event)');
  227.  
  228. var startCellButton = document.getElementById('dist-set-start');
  229. var endCellButton = document.getElementById('dist-set-end');
  230. var clearPathButton = document.getElementById('dist-clear');
  231.  
  232. var subtitle = document.getElementById('mission-info-buttons-subtitle');
  233.  
  234. // === Scan empty cells
  235.  
  236. var emptyCells = Array.from(document.querySelectorAll('td.coord'))
  237. .filter(function (el) {
  238. return el.computedStyleMap().get('opacity').toString() == '0';
  239. })
  240. .map(function (el) {
  241. return {
  242. x: el.classList[1].replace('x', ''),
  243. y: el.classList[2].replace('y', ''),
  244. };
  245. });
  246.  
  247. var startCell = null;
  248. var endCell = null;
  249.  
  250. var pathFinder = new AStar(emptyCells);
  251. function maybeUpdatePath() {
  252. if (!startCell || !endCell) return;
  253.  
  254. var path = pathFinder.find(startCell, endCell);
  255.  
  256. // Clear existing path cells
  257. var pathCells = unsafeWindow.document.querySelectorAll('td.coord.path');
  258. for (var i = 0; i < pathCells.length; i++) {
  259. pathCells[i].classList.remove('path');
  260. }
  261.  
  262. // console.log(path);
  263. if (!path) return;
  264.  
  265. for(var i = 0; i < path.length; i++) {
  266. var cellCoord = path[i];
  267. // console.log(cellCoord);
  268. var cell = unsafeWindow.document.querySelector('td.coord.x' + cellCoord.x + '.y' + cellCoord.y);
  269. cell.classList.add('path');
  270. }
  271. clearPathButton.style.display = 'initial';
  272.  
  273. subtitle.innerHTML = 'Path length: ' + path.length + ' cells';
  274. }
  275.  
  276.  
  277. startCellButton.onclick = function () {
  278. // current pos
  279. var img = unsafeWindow.document.querySelector('#mission-info img');
  280. var matches = img.src.match(/Fairview_(\d+)x(\d+)/);
  281. var x = matches[1];
  282. var y = matches[2];
  283.  
  284. startCell = { x: x, y: y };
  285.  
  286. unsafeWindow.document.querySelector('td.coord.x' + x + '.y' + y).classList.add('path');
  287.  
  288. missionHolder.style.display = 'none';
  289. maybeUpdatePath();
  290. };
  291.  
  292. endCellButton.onclick = function () {
  293. // current pos
  294. var img = document.querySelector('#mission-info img');
  295. var matches = img.src.match(/Fairview_(\d+)x(\d+)/);
  296. var x = matches[1];
  297. var y = matches[2];
  298.  
  299. endCell = { x: x, y: y };
  300.  
  301. unsafeWindow.document.querySelector('td.coord.x' + x + '.y' + y).classList.add('path');
  302.  
  303. missionHolder.style.display = 'none';
  304. maybeUpdatePath();
  305. };
  306.  
  307. clearPathButton.onclick = function () {
  308. startCell = null;
  309. endCell = null;
  310.  
  311. clearPathButton.style.display = 'none';
  312.  
  313. // Clear existing path cells
  314. var pathCells = unsafeWindow.document.querySelectorAll('td.coord.path');
  315. for (var i = 0; i < pathCells.length; i++) {
  316. pathCells[i].classList.remove('path');
  317. }
  318.  
  319. subtitle.innerHTML = 'No path selected';
  320. missionHolder.style.display = 'none';
  321. };
  322. });
  323. })();