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.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();
        });

    });
})();