DFProfiler Path Finder

Find the fastest path in DFProfiler

目前為 2023-11-07 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         DFProfiler Path Finder
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Find the fastest path in DFProfiler
// @author       Runonstof
// @match        https://*.dfprofiler.com/bossmap
// @match        https://*.dfprofiler.com/profile/view/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=dfprofiler.com
// @grant        unsafeWindow
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // === Utility functions ===

    function GM_addStyle(css) {
        var style = document.getElementById("GM_addStyleBy8626") || (function() {
            var style = document.createElement('style');
            style.type = 'text/css';
            style.id = "GM_addStyleBy8626";
            document.head.appendChild(style);
            return style;
        })();
        var sheet = style.sheet;
        console.log(sheet);
        sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length);
    }

    function GM_addStyle_object(selector, styles) {
        var css = selector + "{";
        for (var key in styles) {
            css += key + ":" + styles[key] + ";";
        }
        css += "}";
        GM_addStyle(css);
    }


    function ready(fn) {
        if (document.readyState != 'loading'){
            fn();
        } else {
            document.addEventListener('DOMContentLoaded', fn);
        }
    }

    function AStar(emptyCells) {
        this.emptyCells == emptyCells || [];

        this.Node = function Node(x, y) {
            this.x = parseInt(x);
            this.y = parseInt(y);
            this.g = 0; // cost from start node
            this.h = 0; // heuristic (estimated cost to target)
            this.f = 0; // total cost (g + h)
            this.parent = null;
        }

        this.isInsideMap = function(x, y) {
            return x >= 1000 && x <= 1058 && y >= 981 && y <= 1019;
        };

        this.isCellEmpty = function(x, y) {
            return emptyCells.some(function(cell) {
                return cell.x == x && cell.y == y;
            });
        };

        this.heuristic = function(node, target) {
            // Manhattan distance heuristic
            return Math.abs(node.x - target.x) + Math.abs(node.y - target.y);
        };

        this.find = function(startPos, endPos) {
            var openList = [];
            var closedList = [];

            var startNode = new this.Node(startPos.x, startPos.y);
            var endNode = new this.Node(endPos.x, endPos.y);

            openList.push(startNode);

            while (openList.length > 0) {
                var currentNode = openList[0];
                var currentIndex = 0;

                for (var i = 1; i < openList.length; i++) {
                    if (openList[i].f < currentNode.f) {
                        currentNode = openList[i];
                        currentIndex = i;
                    }
                }

                openList.splice(currentIndex, 1);
                closedList.push(currentNode);

                if (currentNode.x === endNode.x && currentNode.y === endNode.y) {
                    var path = [];
                    var current = currentNode;
                    while (current !== null) {
                        path.push({ x: current.x, y: current.y });
                        current = current.parent;
                    }
                    return path.reverse();
                }

                var neighbors = [
                    { x: 1, y: -1 },
                    { x: 1, y: 1 },
                    { x: -1, y: 1 },
                    { x: -1, y: -1 },

                    { x: 0, y: 1 },
                    { x: 1, y: 0 },
                    { x: 0, y: -1 },
                    { x: -1, y: 0 },

                ];

                for (var neighbourIndex in neighbors) {
                    var neighborDelta = neighbors[neighbourIndex];
                    var neighborX = currentNode.x + neighborDelta.x;
                    var neighborY = currentNode.y + neighborDelta.y;

                    if (!this.isInsideMap(neighborX, neighborY) || this.isCellEmpty(neighborX, neighborY)) {
                        // console.log('cell is empty or outside map:', neighborX, neighborY);
                        continue;
                    }

                    var neighborNode = new this.Node(neighborX, neighborY);

                    var checkNeighbor = function(node) {
                        return node.x === neighborX && node.y === neighborY;
                    };

                    if (closedList.some(checkNeighbor)) {
                        continue;
                    }

                    var tentativeG = currentNode.g + 1; // Assuming each step costs 1

                    if (!openList.some(checkNeighbor) || tentativeG < neighborNode.g) {
                        neighborNode.g = tentativeG;
                        neighborNode.h = this.heuristic(neighborNode, endNode);
                        neighborNode.f = neighborNode.g + neighborNode.h;
                        neighborNode.parent = currentNode;

                        if (!openList.some(checkNeighbor)) {
                            openList.push(neighborNode);
                        }
                    }
                }
            }

            // console.log(closedList);

            return null; // No path found
        }
    }

    // === CSS styles ===

    GM_addStyle_object('#boss-data-section #mission-info, #bossmap-page #mission-info', {
        'border-radius': '25px 25px 0 0',
    });
    GM_addStyle_object('#boss-data-section #mission-info-distance-viewer, #bossmap-page #mission-info-distance-viewer', {
        'position': 'absolute !important',
        'background-color': 'hsla(0,0%,5%,.8)',
        'border-radius': '0 0 25px 25px',
        'padding': '5px',
        'top': '770px',
        'left': 'calc(50% - 16pt * 20)',
        'right': 'calc(50% - 16pt * 20)',
    });

    GM_addStyle_object('#boss-data-section #mission-info-buttons-title, #bossmap-page #mission-info-buttons-title', {
        'color': 'white',
        'font-size': '20px',
    });
    GM_addStyle_object('#boss-data-section #mission-info-buttons-subtitle, #bossmap-page #mission-info-buttons-subtitle', {
        'color': 'white',
        'font-size': '14px',
    });
    GM_addStyle_object('#boss-data-section button.mission-info-button, #bossmap-page button.mission-info-button', {
        'background-color': 'gray',
        'color': 'black',
        'padding': '0.25em 0.5em',
    });
    GM_addStyle_object('#boss-data-section button.mission-info-button:hover, #bossmap-page button.mission-info-button:hover', {
        'color': 'white',
    });
    GM_addStyle_object('#boss-data-section #dist-buttons, #bossmap-page #dist-buttons', {
        'display': 'flex',
        'gap': '10px',
        'justify-content': 'center',
    });
    GM_addStyle_object('#boss-data-section td.coord.path, #bossmap-page td.coord.path', {
        'background-color': 'yellow !important',
        'color': 'black !important',
    });

    ready(function () {

        // === Create Elements ===
        var missionHolder = document.getElementById('mission-holder');

        var container = document.createElement('div');
        container.id = 'mission-info-distance-viewer';

        container.innerHTML = '<div id="mission-info-buttons-title">Path finder</div>';
        container.innerHTML += '<div id="mission-info-buttons-subtitle">No path selected</div>';
        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>';

        missionHolder.appendChild(container);


        unsafeWindow.closeMissionHolder = function (event) {
            if (event.target.closest('#mission-info-distance-viewer')) return;

            missionHolder.style.display = 'none';
        };

        missionHolder.setAttribute('onclick', 'closeMissionHolder(event)');

        var startCellButton = document.getElementById('dist-set-start');
        var endCellButton = document.getElementById('dist-set-end');
        var clearPathButton = document.getElementById('dist-clear');

        var subtitle = document.getElementById('mission-info-buttons-subtitle');

        // === Scan empty cells

        var emptyCells = Array.from(document.querySelectorAll('td.coord'))
            .filter(function (el) {
                return el.computedStyleMap().get('opacity').toString() == '0';
            })
            .map(function (el) {
                return {
                    x: el.classList[1].replace('x', ''),
                    y: el.classList[2].replace('y', ''),
                };
            });

        var startCell = null;
        var endCell = null;

        var pathFinder = new AStar(emptyCells);
        function maybeUpdatePath() {
            if (!startCell || !endCell) return;

            var path = pathFinder.find(startCell, endCell);

            // Clear existing path cells
            var pathCells = unsafeWindow.document.querySelectorAll('td.coord.path');
            for (var i = 0; i < pathCells.length; i++) {
                pathCells[i].classList.remove('path');
            }

            // console.log(path);
            if (!path) return;

            for(var i = 0; i < path.length; i++) {
                var cellCoord = path[i];
                // console.log(cellCoord);
                var cell = unsafeWindow.document.querySelector('td.coord.x' + cellCoord.x + '.y' + cellCoord.y);
                cell.classList.add('path');
            }
            clearPathButton.style.display = 'initial';

            subtitle.innerHTML = 'Path length: ' + path.length + ' cells';
        }


        startCellButton.onclick = function () {
            // current pos
            var img = unsafeWindow.document.querySelector('#mission-info img');
            var matches = img.src.match(/Fairview_(\d+)x(\d+)/);
            var x = matches[1];
            var y = matches[2];

            startCell = { x: x, y: y };

            unsafeWindow.document.querySelector('td.coord.x' + x + '.y' + y).classList.add('path');

            missionHolder.style.display = 'none';
            maybeUpdatePath();
        };

        endCellButton.onclick = function () {
            // current pos
            var img = document.querySelector('#mission-info img');
            var matches = img.src.match(/Fairview_(\d+)x(\d+)/);
            var x = matches[1];
            var y = matches[2];

            endCell = { x: x, y: y };

            unsafeWindow.document.querySelector('td.coord.x' + x + '.y' + y).classList.add('path');

            missionHolder.style.display = 'none';
            maybeUpdatePath();
        };

        clearPathButton.onclick = function () {
            startCell = null;
            endCell = null;

            clearPathButton.style.display = 'none';

            // Clear existing path cells
            var pathCells = unsafeWindow.document.querySelectorAll('td.coord.path');
            for (var i = 0; i < pathCells.length; i++) {
                pathCells[i].classList.remove('path');
            }

            subtitle.innerHTML = 'No path selected';
            missionHolder.style.display = 'none';
        };
    });
})();