MouseHunt Location HUD Preview

Keep track of what state you left an area in, because travelling just to check is jank

// ==UserScript==
// @name         MouseHunt Location HUD Preview
// @description  Keep track of what state you left an area in, because travelling just to check is jank
// @author       LethalVision
// @version      0.5.2
// @require      https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js
// @match        https://www.mousehuntgame.com/*
// @match        https://apps.facebook.com/mousehunt/*
// @icon         https://www.google.com/s2/favicons?domain=mousehuntgame.com
// @grant        none
// @license      MIT
// @namespace    https://greasyfork.org/en/users/683695-lethalvision
// ==/UserScript==

(function () {
    const STORAGE_TAG = 'mh-lochud-cache';

    const MAIN_ID = 'hudPreview-main';
    const PREVIEW_ID = 'hudPreview';

    const LG_COMPLEX = ['desert_oasis', 'sand_dunes', 'lost_city'];
    const TWISTED = '_twisted';
    const LABYKOR = ['labyrinth', 'ancient_city'];

    var areaHuds = { display:true };

    // == local storage ==
    function loadCache() {
        var cacheJson = window.localStorage.getItem(STORAGE_TAG);
        if (cacheJson){
            try{
                if (!cacheJson.startsWith('{')) { // compressed data
                    cacheJson = LZString.decompressFromUTF16(cacheJson);
                }
                areaHuds = JSON.parse(cacheJson);
            } catch (err) {
                console.log('Cache parse error: ' + err);
            }
        }
    }

    function saveCache() {
        var jsonString = LZString.compressToUTF16(JSON.stringify(areaHuds));
        window.localStorage.setItem(STORAGE_TAG, jsonString);
        //console.log('Saved cache: ' + jsonString);
    }

    // == init hooks ==
    function initHooks() {
        // hook showEnv
        const _parentShowEnv = app.pages.TravelPage.showEnvironment;
        app.pages.TravelPage.showEnvironment = function (envType, instant) {
            updatePreview(envType);
            _parentShowEnv(envType, instant);
        }
        // hook toggleQuickMap
        const _parentToggleQuick = app.pages.TravelPage.toggleQuickMap;
        app.pages.TravelPage.toggleQuickMap = function () {
            _parentToggleQuick();
            updateVisibility();
        }
        // hook page.php
        const pageUrl = 'managers/ajax/pages/page.php';
        const travelPageUrl = 'travel.php';
        const travelUrl = 'managers/ajax/users/changeenvironment.php';

        const originalOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function() {
            this.addEventListener('load', function() {
                if (this.responseURL.indexOf(pageUrl) != -1) {
                    // page is called basically when you do anything in MH
                    storeHud();
                } else if (this.responseURL.indexOf(travelUrl) != -1) {
                    // travelling to new location
                    storeHud();
                    updatePreview(user.environment_type);
                }
            });
            originalOpen.apply(this, arguments);
        };
        // in case the window is refreshed in travel.php
        if (window.location.href.includes('travel.php')) {
            updatePreview(user.environment_type);
        }
    }

    // UI, my one love in life
    function updatePreview(envType) {
        var mainDiv = document.getElementById(MAIN_ID);
        if (!mainDiv) {
            // create the main div if it doesn't exist
            var mapContainer = document.querySelector('.travelPage-mapContainer.full');
            if (!mapContainer) return; // idk just return
            mainDiv = getMainDiv();
            mapContainer.parentNode.insertBefore(mainDiv, mapContainer);
        }
        const previewElem = mainDiv.querySelector(`#${PREVIEW_ID}`);
        // load cached HUD or handle based on exceptions
        const cachedHud = areaHuds[envType];
        if (LG_COMPLEX.includes(envType)) {
            previewElem.innerHTML = renderLgComplex(envType);
        } else if (user.environment_type == envType) {
            previewElem.innerHTML = getTextDiv('You are currently hunting here.');
        } else if (cachedHud) {
            previewElem.innerHTML = cachedHud;
            // the frox HUD specifically sets margin-bottom to display correctly, don't override it
            previewElem.querySelector(' div').style.marginBottom = (envType == 'fort_rox') ? '' :'0px';
        } else if (cachedHud != undefined) {
            previewElem.innerHTML = getTextDiv('This location does not have a HUD.');
        } else {
            previewElem.innerHTML = getTextDiv('There is no saved data for this location, travel here at least once to save the HUD.');
        }
    }

    // it's called LG complex, not LG simple, amirite
    function renderLgComplex(envType) {
        if (user.environment_type == envType) { // current location, display the inverse version
            if (document.querySelector('#hudLocationContent .corrupted')) { // at twisted
                return areaHuds[envType] || getTextDiv('There is no saved data for the normal version of this location.');
            } else { // at normal
                return areaHuds[envType + TWISTED] || getTextDiv('There is no saved data for the twisted version of this location.');
            }
        } else { // not at location, display both (if available)
            const normal = areaHuds[envType] || '';
            const twisted = areaHuds[envType + TWISTED] || '';
            const noData = getTextDiv('There is no saved data for this location, travel here at least once to save the HUD.');
            return (normal + twisted) ? normal + twisted : noData;
        }
    }

    // hide the whole thing when Quick Travel is selected
    function updateVisibility() {
        const mapTab = document.querySelector('.mousehuntHud-page-tabContent.map');
        if (!mapTab) return; // idk just return
        const mainDiv = document.getElementById(MAIN_ID);
        if (!mapTab) return; // idk just return again
        // hide mainDiv if it's quick travel, and vice versa
        mainDiv.style.display = mapTab.classList.contains('quick') ? 'none' : '';
    }

    function getMainDiv() {
        const mainDiv = document.createElement('div');
        mainDiv.style.color = '#FFF'
        mainDiv.style.background = 'url(https://www.mousehuntgame.com/images/ui/backgrounds/hud_bg_blue_repeating.png?asset_cache_version=2) repeat-y bottom center';
        mainDiv.style.borderRadius = '12px';
        mainDiv.style.paddingTop = '10px';
        mainDiv.style.paddingBottom = '10px';
        mainDiv.style.marginBottom = '10px';
        mainDiv.style.boxShadow = '-1px -1px 1px #ccc inset';
        mainDiv.id = MAIN_ID;
        // title div
        const titleDiv = document.createElement('div');
        titleDiv.style.height = '20px';
        titleDiv.style.paddingLeft = '10px';
        titleDiv.style.paddingRight = '10px';
        titleDiv.style.display = 'flex';
        titleDiv.style.justifyContent = 'space-between';
        titleDiv.style.alignItems = 'center';
        const titleText = document.createElement('div');
        titleText.innerHTML = '<b>Location HUD Preview</b>'
        titleDiv.appendChild(titleText);
        const toggleBtn = document.createElement('button');
        toggleBtn.innerHTML = areaHuds.display ? '-' : '+';
        toggleBtn.style.padding = '0';
        toggleBtn.style.height = '18px';
        toggleBtn.style.width = '18px';
        toggleBtn.style.borderRadius = '5px';
        toggleBtn.style.background = '#ccc';
        toggleBtn.style.border = '1px solid #999';
        toggleBtn.addEventListener('click', (e) => toggleDisplay(e.target));
        titleDiv.appendChild(toggleBtn);
        mainDiv.appendChild(titleDiv);
        // preview elem
        const previewElem = document.createElement('div');
        previewElem.style.paddingTop = '10px';
        previewElem.id = PREVIEW_ID;
        previewElem.style.display = areaHuds.display ? '' : 'none';
        mainDiv.appendChild(previewElem);
        return mainDiv;
    }

    function getTextDiv(text) {
        return `<div style="padding-left:10px;">${text}</div>`;
    }

    // button onclicks
    function toggleDisplay(element) {
        const previewElem = document.getElementById(PREVIEW_ID);
        if (!previewElem) return;
        areaHuds.display = !areaHuds.display;
        previewElem.style.display = areaHuds.display ? '' : 'none';
        element.innerHTML = areaHuds.display ? '-' : '+';
        //saveCache();
    }

    // process and store the current location HUD
    function storeHud() {
        var hudElem = document.getElementById('hudLocationContent');
        if (!hudElem) {
            console.log('failed to get HUD element');
            return;
        }
        const envType = user.environment_type;
        var hudName = envType;
        var cloneHud = hudElem.cloneNode(true);
        // disable all warning elements (wrong powertype etc)
        var activeList = cloneHud.querySelectorAll('.active');
        activeList.forEach((elem) => {
            if (elem.className.toLowerCase().indexOf('warning') != -1) {
                elem.classList.remove('active');
            }
        });
        // location-based processing
        if (LG_COMPLEX.includes(envType)) {
            cloneHud.querySelectorAll('.baitWarning').forEach((elem) => elem.remove());
            if (cloneHud.querySelector('.corrupted')) { // is twisted version
                hudName = envType + TWISTED;
            }
        } else if (LABYKOR.includes(envType)) {
            // remove zokor hud if in laby, and vice versa - both areas can't coexist
            const pairEnv = LABYKOR[(LABYKOR.indexOf(envType) + 1) % 2]; // fancy mirror math that returns the counterpart
            delete areaHuds[pairEnv];
        }

        fixHud(envType, cloneHud);
        // strip mouse events and href from the cached HUD to disable clicks
        const cloneInnerHtml = cloneHud.innerHTML
        .replace(/on[a-z]+?=".+?"/g, '')
        .replace(/ href="#"/g, '');
        areaHuds[hudName] = cloneInnerHtml;
        saveCache();
    }

    // fix HUD elements that don't display properly as preview
    function fixHud(envType, hudElem) {
        var colorSelector = [];
        switch(envType) {
            case 'zugzwang_tower':
                // I could fix floating elements, but I will delete them instead
                // do not try me
                hudElem.querySelectorAll('.zugzwangsTowerHUD-retreatButton').forEach((elem) => elem.remove());
                break;
            case 'sunken_city':
                colorSelector.push('.item.quantity');
                break;
            case 'rift_gnawnia':
                colorSelector.push('.riftGnawniaHud-label');
                break;
            case 'rift_valour':
                colorSelector.push('.valourRiftHUD-fuelContainer-armButton');
                colorSelector.push('.valourRiftHUD-powerUp-title');
                break;
            case 'rift_bristle_woods':
                colorSelector.push('.riftBristleWoodsHUD-chamberSpecificTextContainer');
                break;
        }
        colorSelector.forEach((selector) => {
            hudElem.querySelectorAll(selector).forEach((elem) => {elem.style.color = 'white'});
        });
    }

    // init
    loadCache();
    initHooks();
    storeHud();
})();