MouseHunt Custom Themes

Change your hunter's journal to use custom themes (only works locally)

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         MouseHunt Custom Themes
// @author       LethalVision
// @version      1.2.3
// @description  Change your hunter's journal to use custom themes (only works locally)
// @include	http://mousehuntgame.com/*
// @include	https://mousehuntgame.com/*
// @include	http://www.mousehuntgame.com/*
// @include	https://www.mousehuntgame.com/*
// @grant        none
// @namespace    https://greasyfork.org/en/users/683695-lethalvision
// ==/UserScript==

const TAG = "CUST_THEME"
var topUrl = "";
var midUrl = "";
var botUrl = "";
// true = tile mid image, false = stretch
const tileMid = true;

const defaultThemes = {
    "None":{description:"None", links:[]},
    "VR":{description:"Valour Rift by wayew",
         links:["https://cdn.discordapp.com/attachments/487964004162207745/751107541764800672/non_UU_theme.png",
               "https://cdn.discordapp.com/attachments/487964004162207745/748804207171207238/middle_non_UU.png",
               "https://cdn.discordapp.com/attachments/487964004162207745/748804197306073149/bottom_non_UU.png"]},
    "VR_A":{description:"Valour Rift (animated) by wayew & Wellker",
         links:["https://cdn.discordapp.com/attachments/487964004162207745/748810922642112583/lightning_non_UU_gif_cut.gif",
               "https://cdn.discordapp.com/attachments/487964004162207745/748804207171207238/middle_non_UU.png",
               "https://cdn.discordapp.com/attachments/487964004162207745/748804197306073149/bottom_non_UU.png"]},
    "UU":{description:"Ultimate Umbra by wayew",
          links:["https://cdn.discordapp.com/attachments/487964004162207745/748528376750407750/top.png",
                 "https://cdn.discordapp.com/attachments/487964004162207745/756163238768410644/UU_theme_mid.png",
                 "https://cdn.discordapp.com/attachments/487964004162207745/748569283591798864/bottom.png"]},
    "UU_A":{description:"Ultimate Umbra (animated) by wayew & Wellker",
            links:["https://cdn.discordapp.com/attachments/487964004162207745/748572650237853736/UU_theme_top_gif.gif",
                   "https://cdn.discordapp.com/attachments/487964004162207745/756163238768410644/UU_theme_mid.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/748569283591798864/bottom.png"]},
    "CT_A":{description:"Crystal Theme (animated) by Wellker",
            links:["https://cdn.discordapp.com/attachments/487964004162207745/749338680295948428/Crystal_Theme_Top.gif",
                   "https://cdn.discordapp.com/attachments/487964004162207745/756164045291389029/Crystal_theme_middle.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/749332702188142693/Crystal_theme_bot.png"]},
    "SH":{description:"Shrine Theme by Wellker",
            links:["https://cdn.discordapp.com/attachments/487964004162207745/898641949676957706/Sacred_shrine_top.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/898642019956711434/Shrine_mid.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/898642043239272580/shrine_bottom.png"]},
    "SH_A":{description:"Shrine Theme (animated) by Wellker",
            links:["https://cdn.discordapp.com/attachments/487964004162207745/898641930269892638/Sacred_Shrine_top_Animated.gif",
                   "https://cdn.discordapp.com/attachments/487964004162207745/898642019956711434/Shrine_mid.png",
                   "https://cdn.discordapp.com/attachments/487964004162207745/898642043239272580/shrine_bottom.png"]},
    "Custom":{description:"Custom", links:[]}
};

const imgSizes = {
    top:{width:0, height:0, loaded:false},
    mid:{width:0, height:0, loaded:false},
    bot:{width:0, height:0, loaded:false}
};

var preferences = {
    selection: "None",
    topUrl: "",
    midUrl: "",
    botUrl: ""
};

function init() {
    var loadedPref = window.localStorage.getItem(TAG);
    if (loadedPref){
        try{
            loadedPref = JSON.parse(loadedPref);
            console.log('Loaded preference: ' + loadedPref.selection);
            preferences.selection = loadedPref.selection;
            preferences.topUrl = loadedPref.topUrl;
            preferences.midUrl = loadedPref.midUrl;
            preferences.botUrl = loadedPref.botUrl;
        } catch (err) {
            console.log('Preference parse error: ' + err);
        }
    }
    addListener();
    loadUrls(preferences.selection);
}

function loadUrls(selection) {
    if (selection == "Custom"){
        topUrl = preferences.topUrl;
        midUrl = preferences.midUrl;
        botUrl = preferences.botUrl;
        loadImageSize("top", topUrl);
        loadImageSize("mid", midUrl);
        loadImageSize("bot", botUrl);
    } else if (defaultThemes.hasOwnProperty(selection) && defaultThemes[selection].links.length == 3){
        topUrl = defaultThemes[selection].links[0];
        midUrl = defaultThemes[selection].links[1];
        botUrl = defaultThemes[selection].links[2];
        loadImageSize("top", topUrl);
        loadImageSize("mid", midUrl);
        loadImageSize("bot", botUrl);
    } else {
        topUrl = "";
        midUrl = "";
        botUrl = "";
        // no need to load theme
    }
}

function addListener() {
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function() {
        this.addEventListener("load", function() {
            if ( this.responseURL === "https://www.mousehuntgame.com/managers/ajax/pages/page.php") {
                updateTheme();
                updateSelector();
            } else if (this.responseURL === "https://www.mousehuntgame.com/managers/ajax/users/journal_theme.php") {
                updateSelector();
                updateCheckboxes();
                // add interface on first load
                addInterface(true);
            }
        });
        originalOpen.apply(this, arguments);
    };
}

function loadImageSize(portion, url){
    if (url == null || url.length == 0){
        return;
    }
    var img = new Image();
    img.onload = function(){
        setSize(portion, this.width, this.height);
    };
    img.src = url;
}

function setSize(portion, width, height){
    console.log(`${portion}: ${width}, ${height}`);
    switch(portion){
        case "top":
            imgSizes.top.width = width;
            imgSizes.top.height = height;
            imgSizes.top.loaded = true;
            break;
        case "mid":
            imgSizes.mid.width = width;
            imgSizes.mid.height = height;
            imgSizes.mid.loaded = true;
            break;
        case "bot":
            imgSizes.bot.width = width;
            imgSizes.bot.height = height;
            imgSizes.bot.loaded = true;
            break;
        default:
            console.log(`setSize unrecognized portion: ${portion}`);
            return;
    }
    if (imgSizes.top.loaded && imgSizes.mid.loaded && imgSizes.bot.loaded) {
        // all images loaded successfully, apply to theme
        updateTheme();
    }
}

function updateSelector(){
    var updateLink = function(link){
        if (link.getAttribute('attached')){
            // check if the link is already tagged
            return;
        }
        link.addEventListener("click", function(){
            updateCheckboxes();
            addInterface(true);
        });
        // tag the link
        link.setAttribute('attached', true);
    };
     // append new event to all links that bring up the theme selector
    [].forEach.call(document.getElementsByClassName('campPage-tabs-journal-theme'), updateLink);
    [].forEach.call(document.getElementsByClassName('journalContainer-selectTheme'), updateLink);
}

function updateCheckboxes() {
    // get those pesky checkboxes
    // this reapplies the onclick modification on the checkboxes after a short delay as the entire theme selector seems to be recreated upon clicking the checkboxes
    var checkboxes = document.getElementsByClassName('journalThemeSelectorView-filter');
    for (var i = 0; i < checkboxes.length; i++) {
        checkboxes[i].getElementsByTagName("input")[0].onclick = function(){
            setTimeout(function(){
                updateCheckboxes();
                addInterface(false);
            }, 50);
        };
    }
}

function addInterface(forceTab){
    // make sure the theme selector exists
    var parent = document.getElementsByClassName('journalThemeSelectorView-tagContainer');
    if (parent.length == 0){
        return;
    } else {
        parent = parent[0];
    }

    // force tab to "General" if there is an active custom theme
    if (forceTab && preferences.selection != "None"){
        var tabs = document.getElementsByClassName('mousehuntTabHeader');
        for (var i = 0; i < tabs.length; i++) {
            // find and click the "General" tab
            if (tabs[i].getAttribute("data-tab") == "misc") {
                tabs[i].onclick();
            }
        }
    }

    if (document.getElementById('customThemeMain')){
        // if the mainDiv already exists, exit
        return;
    }

    const mainDiv = document.createElement('div');
    mainDiv.id = "customThemeMain";

    // setup title
    var titleDiv = document.createElement('div');
    titleDiv.innerHTML = "<b>Custom Themes</b>";
    titleDiv.style.height = "17px";

    // a horizontal line. Was this really necessary? Nope.
    // it's here anyway so y'all better appreciate it
    var lineDiv = document.createElement('div');
    lineDiv.style.height = "1px";
    lineDiv.style.backgroundColor = "black";

    var descDiv = document.createElement('div');
    descDiv.innerHTML = "Apply custom themes to your Hunter's Journal. Note that these changes are local - only you can see this!";
    descDiv.style.height = "34px";
    descDiv.style.marginTop = "3px";
    descDiv.style.marginRight = "3px";

    mainDiv.appendChild(titleDiv);
    mainDiv.appendChild(lineDiv);
    mainDiv.appendChild(descDiv);

    // setup selector
    var themeDiv = document.createElement('div');
    themeDiv.style.height = "24px";
    var themeSelect = document.createElement('select');
    themeSelect.id = "selectTheme";
    themeSelect.style.width = "75%"
    // iterate through defaultThemes to add options to select
    for (var key in defaultThemes) {
        if (defaultThemes.hasOwnProperty(key)) {
            themeSelect.options[themeSelect.options.length] = new Option(defaultThemes[key].description, key);
        }
    }
    // set current option to selected theme
    themeSelect.value = preferences.selection;
    themeSelect.onchange = function() {
        var customDiv = document.getElementById('customDiv')
        if (document.getElementById('selectTheme').value == "Custom") {
            customDiv.style.display = "block";
        } else {
            customDiv.style.display = "none";
        }
    }
    themeDiv.appendChild(themeSelect);
    mainDiv.appendChild(themeDiv);

    // setup input UI for Custom themes
    var customDiv = document.createElement('div');
    customDiv.id = "customDiv";
    customDiv.style.marginTop = "10px";
    customDiv.style.marginBottom = "10px";

    var detailDiv = document.createElement('div');
    detailDiv.style.height = "34px";
    detailDiv.innerHTML = "Set images for your custom theme here: images must be provided as URLs, <i>local images will not work.</i>";
    customDiv.appendChild(detailDiv);

    var warningDiv = document.createElement('div');
    warningDiv.style.height = "34px";
    warningDiv.innerHTML = "<b>Please check that the URLs link to images before loading them!</b>".fontcolor("red");
    customDiv.appendChild(warningDiv);

    var customTable = document.createElement('table');
    customTable.style.width = "100%";
    descDiv.style.marginRight = "3px";
    customDiv.appendChild(customTable);

    var topRow = customTable.insertRow();
    topRow.style.height = "24px";
    var topDesc = topRow.insertCell(0);
    topDesc.appendChild(document.createTextNode("Top"));
    // set width once and the rest should follow
    topDesc.style.width = "20%"
    var topUrlInput = document.createElement('input');
    topUrlInput.id = "topInput";
    topUrlInput.style.width = "95%";
    topUrlInput.value = preferences.topUrl;
    topRow.insertCell(1).appendChild(topUrlInput);

    var midRow = customTable.insertRow();
    midRow.style.height = "24px";
    midRow.insertCell(0).appendChild(document.createTextNode("Middle"));
    var midUrlInput = document.createElement('input');
    midUrlInput.id = "midInput";
    midUrlInput.style.width = "95%";
    midUrlInput.value = preferences.midUrl;
    midRow.insertCell(1).appendChild(midUrlInput);

    var botRow = customTable.insertRow();
    botRow.style.height = "24px";
    botRow.insertCell(0).appendChild(document.createTextNode("Bottom"));
    var botUrlInput = document.createElement('input');
    botUrlInput.id = "botInput";
    botUrlInput.style.width = "95%";
    botUrlInput.value = preferences.botUrl;
    botRow.insertCell(1).appendChild(botUrlInput);

    mainDiv.appendChild(customDiv);

    // setup buttons
    var buttonDiv = document.createElement("div");
    buttonDiv.style.marginTop = "5px";

    var loadButton = document.createElement("button");
    loadButton.textContent = "Load Theme";
    loadButton.onclick = savePreferences;
    buttonDiv.appendChild(loadButton);

    var clearButton = document.createElement("button");
    clearButton.textContent = "Clear Preferences";
    clearButton.style.marginLeft = "5px";
    clearButton.onclick = clearPreferences;
    buttonDiv.appendChild(clearButton);

    mainDiv.appendChild(buttonDiv);

    // add the whole UI to theme dialog
    parent.appendChild(mainDiv);

    // trigger onchange on themeSelect to set the display attribute correctly on first load
    themeSelect.onchange();
}

function savePreferences() {
    var selectorElem = document.getElementById('selectTheme');
    var selectValue = selectorElem.value;
    // store preference
    preferences.selection = selectValue;
    if (selectValue == "Custom") {
        preferences.topUrl = document.getElementById('topInput').value;
        preferences.midUrl = document.getElementById('midInput').value;
        preferences.botUrl = document.getElementById('botInput').value;
    }
    var jsonString = JSON.stringify(preferences);
    window.localStorage.setItem(TAG, jsonString);
    window.location.reload(false);
}

function clearPreferences() {
    localStorage.removeItem(TAG);
    window.location.reload(false);
}

function updateJournal(journalElem, url, imgSize) {
    // calculate proper height
    var elemWidth = journalElem.clientWidth;
    var elemHeight = journalElem.clientHeight;
    if (elemWidth == 0 || elemHeight == 0){
        return;
    }
    var newHeight = parseInt(imgSize.height * ((elemWidth*1.0)/ imgSize.width));
    //console.log(`newHeight ${journalElem.className}: ${newHeight}px`);

    journalElem.style.height = `${newHeight}px`;
    journalElem.style.backgroundImage = `url('${url}')`;
    // use automatic height
    journalElem.style.backgroundSize = "100% auto";
}

function updateTheme() {
    if (imgSizes.top.width == 0 || imgSizes.top.height == 0 ||
        imgSizes.mid.width == 0 || imgSizes.mid.height == 0 ||
        imgSizes.bot.width == 0 || imgSizes.bot.height == 0){
        // skip update if image load failed for any of the images
        return;
    }
    // "Set Journal Theme" container on camp page
    var campJournalLinks = document.getElementsByClassName("journal-detailLinkContainer");
    // "Set Journal Theme" container on own profile page (it looks broken as of MouseHunt v3.2-86955, will probably be fixed soon - and break this script again.)
    var profileJournalLinks = document.getElementsByClassName("campPage-tabs-journal-link-container");
    if (campJournalLinks.length == 0 && profileJournalLinks.length == 0 && !window.location.href.includes("journal.php")) {
        // proceed if "Set Journal Theme" is found or on "journal.php", skip update otherwise
        // i.e. don't update theme on other people's profiles
        return;
    }

    var themeTop = document.getElementById('journalContainer').children[0];
    var themeMid = document.getElementById('journalContainer').children[1];
    var themeBot = document.getElementById('journalContainer').children[2];

    updateJournal(themeTop, topUrl, imgSizes.top);
    updateJournal(themeBot, botUrl, imgSizes.bot);
    // manually update mid as it needs to be handled differently
    themeMid.style.backgroundColor = "#F2F2F2";
    themeMid.style.backgroundImage = `url('${midUrl}')`;
    if (tileMid){
        themeMid.style.backgroundSize = "100% auto"
    } else {
        themeMid.style.backgroundSize = "100% 100%";
    }
}

function addEvent(element, eventName, fn) {
    if (element.addEventListener){
        element.addEventListener(eventName, fn, false);
    }
    else if (element.attachEvent){
        element.attachEvent('on' + eventName, fn);
    }
}

// init script on load, use addEvent to preserve onLoad events
//addEvent(window, 'load', function(){ init() });
init();