DFProfiler Path Finder

Find the fastest path in DFProfiler

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         DFProfiler Path Finder
// @namespace    http://tampermonkey.net/
// @version      0.4
// @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;

        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.withoutParent = function () {
                var node = new Node(this.x, this.y);
                node.g = this.g;
                node.h = this.h;
                node.f = this.f;
                return node;
            }
        }

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

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

        this.heuristic = function(node, target) {
            var dx = Math.abs(node.x - target.x);
            var dy = Math.abs(node.y - target.y);
            var diagonalSteps = Math.min(dx, dy);
            var straightSteps = Math.abs(dx - dy);

            return (1.2 * diagonalSteps) + straightSteps;
        }

        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);

            var diagonalCost = 1.2;
            var neighbors = [
                { x: 1, y: 0 },  // Right
                { x: -1, y: 0 }, // Left
                { x: 0, y: 1 },  // Down
                { x: 0, y: -1 }, // Up
                { x: 1, y: 1 },  // Diagonal down-right
                { x: -1, y: -1 },// Diagonal up-left
                { x: 1, y: -1 }, // Diagonal up-right
                { x: -1, y: 1 },   // Diagonal down-left
            ];

            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(current.withoutParent());
                        current = current.parent;
                    }
                    return path.reverse();
                }


                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 movementCost = 1;

                    if (neighborDelta.x != 0 && neighborDelta.y != 0) {
                        movementCost = diagonalCost;
                    }
                    //     movementCost = diagonalCost;
                    //     // // console.log('diagonal');
                    //     // // movementCost = diagonalCost;=
                    //     // // This is a diagonal movement
                    //     // if (this.isCellEmpty(currentNode.x + neighborDelta.x, currentNode.y) ||
                    //     //     this.isCellEmpty(currentNode.x, currentNode.y + neighborDelta.y)) {
                    //     //     // Increase diagonal cost if there is an adjacent empty cell
                    //     //     movementCost = 1.50; // You can adjust this value based on your preference
                    //     // } else {
                    //     //     movementCost = 1;
                    //     // }

                    // }

                    var tentativeG = currentNode.g + movementCost; // 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
        }
    }

    // formats ms to hh:mm:ss
    function formatTime(ms) {
        var seconds = Math.floor(ms / 1000);
        var minutes = Math.floor(seconds / 60);
        var hours = Math.floor(minutes / 60);

        seconds -= minutes * 60;
        minutes -= hours * 60;

        var time = '';
        if (hours > 0) {
            time += hours + 'h ';
        }
        if (minutes > 0) {
            time += minutes + 'm ';
        }
        if (seconds > 0) {
            time += seconds + 's';
        }

        return time;
    }

    // === 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',
        'margin-bottom': '10px',
        'align-items': 'center',
    });
    GM_addStyle_object('#boss-data-section td.coord.path, #bossmap-page td.coord.path', {
        'background-color': 'yellow !important',
        'color': 'black !important',
    });

    // GM_addStyle_object('#coord-hover-tooltip', {
    //     'position': 'absolute',
    //     'background-color': 'hsla(0,0%,5%,.8)',
    //     'border-radius': '5px',
    //     'padding': '5px',
    // });


    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 class="dist-buttons"><button id="dist-clear" style="display: none;" class="mission-info-button">Clear path</button></div>';
        container.innerHTML += '<div class="dist-buttons"><button id="dist-set-start" class="mission-info-button">Set path start</button><button id="dist-set-end" class="mission-info-button">Set path end</button></div>';
        container.innerHTML += '<div class="dist-buttons" style="display:none;">Player navigation: <button id="dist-set-goal" class="mission-info-button">Set path goal</button></div>';

        missionHolder.appendChild(container);

        var mapTopInfo = document.createElement('div');
        mapTopInfo.id = 'map-top-info';
        mapTopInfo.innerHTML = '<div id="mission-info-buttons-subtitle">No path selected, click on a cell to set a start and end point</div>';

        var bossTable = document.querySelector('#boss-table');
        if (bossTable.previousElementSibling) {
            bossTable.previousElementSibling.insertAdjacentElement('afterend', mapTopInfo);
        } else {
            bossTable.insertAdjacentElement('beforebegin', mapTopInfo);
        }

        // var tooltip = document.createElement('div');
        // tooltip.id = 'coord-hover-tooltip';
        // tooltip.style.display = 'none';

        // unsafeWindow.document.body.appendChild(tooltip);

        // function showTooltip(event, info) {
        //     tooltip.style.display = 'block';
        //     tooltip.style.position = 'absolute';
        //     // muose position
        //     tooltip.style.top = event.pageY + 10 + 'px';
        //     tooltip.style.left = event.pageX + 10 + 'px';
        //     tooltip.innerHTML = info;
        // }

        // function hideTooltip() {
        //     tooltip.style.display = 'none';
        // }


        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 setGoalButton = document.getElementById('dist-set-goal');

        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 trackingGps = false;
        var lastTrackTime = null;
        var lastRemainingCellCount = 0;


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

            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');
                delete pathCells[i].dataset.distanceDebug;
                delete pathCells[i].dataset.distanceIndex;
                // pathCells[i].onmouseover = null;
            }

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

            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');
                cell.dataset.distanceDebug = JSON.stringify(cellCoord);
                cell.dataset.distanceIndex = i;
                // cell.onmouseover = function(event) {
                //     if (!event.target.dataset.distanceDebug) return;

                //     var info = JSON.parse(event.target.dataset.distanceDebug);
                //     var index = parseInt(event.target.dataset.distanceIndex) + 1;
                //     showTooltip(event, 'Index: ' + index + '<br>Cell: ' + info.x + 'x' + info.y + '<br>Total cost: ' + info.g + '<br>Heuristic: ' + info.h + '<br>Total: ' + info.f);
                // };
            }
            clearPathButton.style.display = 'initial';

            subtitle.innerHTML = 'Path length: ' + path.length + ' cells';
            if (trackingGps) {
                subtitle.innerHTML += ' (tracking player)';

                // subtitle.innerHTML += '<br>' + (lastTrackTime === null ? 'Start walking to see estimated time' : 'Estimated time: ');
                subtitle.innerHTML += '<br>';

                if (lastTrackTime === null) {
                    subtitle.innerHTML += 'Start walking to see estimated time';
                } else {
                    var now = new Date().getTime();
                    var timeDiff = now - lastTrackTime;
                    var cellsTraveled = Math.abs(lastRemainingCellCount - path.length);

                    var timeRemaining;
                    if (!cellsTraveled) {
                        timeRemaining = 'Unknown';
                    } else {
                        var timePerCell = timeDiff / cellsTraveled;
                        timeRemaining = formatTime(timePerCell * path.length);
                    }

                    subtitle.innerHTML += 'Estimated time remaining: ' + timeRemaining;
                    subtitle.innerHTML += '<br>Cells traveled: ' + cellsTraveled;
                    subtitle.innerHTML += '<br>Time passed: ' + formatTime(timeDiff);
                }
            }
            return true;
        }

        var playerCoords = null;
        var playerCell = unsafeWindow.document.querySelector('td.playerlocation');

        if (playerCell) {
            playerCoords = [
                playerCell.classList[1].replace('x', ''),
                playerCell.classList[2].replace('y', ''),
            ];
        }


        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];
            trackingGps = false;
            lastTrackTime = null;

            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;
            trackingGps = false;
            lastTrackTime = 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, click on a cell to set a start and end point';
            missionHolder.style.display = 'none';
        };

        var updatePlayerTrackPath = function () {
            if (!trackingGps) return;

            var playerCell = unsafeWindow.document.querySelector('td.playerlocation');
            if (!playerCell) {
                setTimeout(updatePlayerTrackPath, 7500);
                return;
            };


            playerCoords = [
                playerCell.classList[1].replace('x', ''),
                playerCell.classList[2].replace('y', ''),
            ];

            startCell = { x: playerCoords[0], y: playerCoords[1] };

            maybeUpdatePath();

            setTimeout(updatePlayerTrackPath, 7500);
        };


        setGoalButton.onclick = function () {
            trackingGps = true;
            missionHolder.style.display = 'none';

            // 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 };

            if (playerCoords) {
                startCell = { x: playerCoords[0], y: playerCoords[1] };
            } else {
                startCell = { x: x, y: y };
            }

            unsafeWindow.document.querySelector('td.coord.x' + x + '.y' + y).classList.add('path');
            var result = maybeUpdatePath();
            if (!result) {
                trackingGps = false;
                return;
            }

            lastTrackTime = new Date().getTime();
            lastRemainingCellCount = pathFinder.find(startCell, endCell).length;
            updatePlayerTrackPath();
        };


        $(unsafeWindow.document).ajaxComplete(function(event, jqXHR, ajaxOptions) {
            console.log('ajaxComplete', ajaxOptions.url)
            if (ajaxOptions.url.indexOf('/profile/json/') == -1) return;
            setGoalButton.parentElement.style.display = 'initial';
            playerCoords = jqXHR.responseJSON.gpscoords;

            // if (!trackingGps) return;
            // startCell = { x: coords[0], y: coords[1] };
            // maybeUpdatePath();
        });
    });
})();