DFProfiler Path Finder

Find the fastest path in DFProfiler

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        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;

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

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

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


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

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

            maybeUpdatePath();

            setTimeout(updatePlayerTrackPath, 30000);
        };


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

            unsafeWindow.document.querySelector('td.coord.x' + x + '.y' + y).classList.add('path');
            maybeUpdatePath();
            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();
        });

    });
})();