Crown Progress Explorer

A different but simple new way to look at your progress

// ==UserScript==
// @name         Crown Progress Explorer
// @namespace    https://greasyfork.org/users/1028985
// @version      0.0
// @description  A different but simple new way to look at your progress
// @author       CMT
// @match        https://www.mousehuntgame.com/*
// @match        https://apps.facebook.com/mousehunt/*
// @icon         https://www.google.com/s2/favicons?domain=mousehuntgame.com
// ==/UserScript==

((function () {
	'use strict';
    const mouseCats = [
        {type:"Arcane",
        mice: ["Buccaneer Mouse","Hapless Marionette"]},
        {type:"Draconic",
        mice: ["Chameleon Mouse","Dragon Mouse","Dumpling Chef Mouse"]},
        {type:"Forgotten",
        mice: ["Acolyte Mouse","Balack the Banished"]},
        {type:"Hydro",
        mice: ["Aged Mouse","Briegull Mouse","Black Widow Mouse"]},
        {type:"Law",
        mice: ["Bandit Mouse","Gate Guardian Mouse","Gold Mouse"]},
        {type:"Parental",
        mice: ["Aged Mouse"]},
        {type:"Physical",
        mice: ["Abominable Snow Mouse","Bear Mouse"]},
        {type:"Rift",
        mice: ["Fairy Mouse","Fiddler Mouse","Gate Guardian Mouse"]},
        {type:"Shadow",
        mice: ["Aquos Mouse","Bionic Mouse"]},
        {type:"Tactical",
        mice: ["Alchemist Mouse","Alnilam Mouse","Assassin Mouse"]}
    ]
    let defMouseScore =
        {Arcane: 0,
        Draconic: 0,
        Forgotten: 0,
        Hydro: 0,
        Law:0,
        Parental: 0,
        Physical : 0,
        Rift: 0,
        Shadow: 0,
        Tactical:0
    }
	let defMouseTypeList = {Arcane: [],
        Draconic: [],
        Forgotten: [],
        Hydro: [],
        Law:[],
        Parental: [],
        Physical : [],
        Rift: [],
        Shadow: [],
        Tactical:[]
    }
	let mouseTypeList, mouseScore;
	/**
	 * Add styles to the page.
	 *
	 * @param {string} styles The styles to add.
	 */
	const addStyles = (styles) => {
		// Check to see if the existing element exists.
		const existingStyles = document.getElementById('mh-mouseplace-custom-styles');

		// If so, append our new styles to the existing element.
		if (existingStyles) {
			existingStyles.innerHTML += styles;
			return;
		}

		// Otherwise, create a new element and append it to the head.
		const style = document.createElement('style');
		style.id = 'cmt-styles';
		style.innerHTML = styles;
		document.head.appendChild(style);
	};
	/**
	 * POST a request to the server and return the response.
	 *
	 * @param {string} url      The url to post to, not including the base url.
	 * @param {Object} formData The form data to post.
	 *
	 * @return {Promise} The response.
	 */
	const doRequest = async (url, formData) => {
		// If we don't have the needed params, bail.
		if ('undefined' === typeof user || ! user || 'undefined' === typeof user.unique_hash || ! user.unique_hash) { // eslint-disable-line no-undef
			return;
		}

		// Build the form for the request.
		const form = new FormData();
		form.append('sn', 'Hitgrab');
		form.append('hg_is_ajax', 1);
		form.append('uh', user.unique_hash ? user.unique_hash : ''); // eslint-disable-line no-undef

		// Add in the passed in form data.
		for (const key in formData) {
			form.append(key, formData[ key ]);
		}

		// Convert the form to a URL encoded string for the body.
		const requestBody = new URLSearchParams(form).toString();

		// Send the request.
		const response = await fetch(
			callbackurl ? callbackurl + url : 'https://www.mousehuntgame.com/' + url, // eslint-disable-line no-undef
			{
				method: 'POST',
				body: requestBody,
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded',
				},
			}
		);

		// Wait for the response and return it.
		const data = await response.json();
		return data;
	};

	/**
	 *  Add a submenu item to a menu.
	 *
	 * @param {Object} options The options for the submenu item.
	 */
	const addSubmenuItem = (options) => {
		// Default to sensible values.
		const settings = Object.assign({}, {
			menu: 'kingdom',
			label: '',
			icon: '',
			href: '',
			callback: null,
			external: false,
		}, options);


		// Grab the menu item we want to add the submenu to.
		const menuTarget = document.querySelector(`.mousehuntHud-menu .${ settings.menu }`);
		if (! menuTarget) {
			return;
		}

		// If the menu already has a submenu, just add the item to it.
		if (! menuTarget.classList.contains('hasChildren')) {
			menuTarget.classList.add('hasChildren');
		}

		let submenu = menuTarget.querySelector('ul');
		if (! submenu) {
			submenu = document.createElement('ul');
			menuTarget.appendChild(submenu);
		}

		// Create the item.
		const item = document.createElement('li');

		// Add in our class.
		const menuSlug = settings.label.toLowerCase().replace(/ /g, '-');
		item.classList.add(`mh-submenu-item-${ menuSlug }`);

		if (settings.icon) {
			addStyles(`.mousehuntHud-menu .mh-submenu-item-${ menuSlug } .icon { background-image: url(${ settings.icon }); }`);
		}

		// Create the link.
		const link = document.createElement('a');
		link.href = settings.href || '#';

		if (settings.callback) {
			link.addEventListener('click', (e) => {
				e.preventDefault();
				settings.callback();
			});
		}

		// Create the icon.
		const icon = document.createElement('div');
		icon.classList.add('icon');

		// Create the label.
		const name = document.createElement('div');
		name.classList.add('name');
		name.innerText = settings.label;

		// Add the icon and label to the link.
		link.appendChild(icon);
		link.appendChild(name);

		// If it's an external link, also add the icon for it.
		if (settings.external) {
			const externalLinkIcon = document.createElement('div');
			externalLinkIcon.classList.add('external_icon');
			link.appendChild(externalLinkIcon);

			// Set the target to _blank so it opens in a new tab.
			link.target = '_blank';
			link.rel = 'noopener noreferrer';
		}

		// Add the link to the item.
		item.appendChild(link);

		// Add the item to the submenu.
		submenu.appendChild(item);
	};
	/**
	 * Get the mouse stats.
	 *
	 * @return {Object} The mouse stats.
	 */
	const getMouseStats = async () => {
		const data = await doRequest(
			'managers/ajax/mice/getstat.php',
			{
				action: 'get_hunting_stats',
			}
		);

		// Grab the data from the response.
		const mouseData = data?.hunting_stats


		// Return the data.
		return mouseData ? mouseData : [];
	};

    const calculateScore = (stats) => {
		mouseScore = JSON.parse(JSON.stringify(defMouseScore));
		mouseTypeList = JSON.parse(JSON.stringify(defMouseTypeList));
        stats.forEach((mouse) => {
            mouseCats.forEach((cat) => {
                if (cat.mice.includes(mouse.name)){
					if(mouse.num_catches >= 100){
						mouseScore[cat.type] = mouseScore[cat.type] +1
					}
					mouseTypeList[cat.type].push(mouse);
                }
            })
        })
    }

    const buildScoreMarkup = (type)=>{
        for (const cat in mouseCats){
            if (mouseCats[cat].type == type){
                const typeEl = document.createElement('a');
                typeEl.classList.add('cmt-mice-stats');
                typeEl.title = type;
                // Create the image element.
                const image = document.createElement('div');
                image.classList.add('cmt-mice-stats-image');
                image.style.backgroundImage = `url('https://www.mousehuntgame.com/images/powertypes/${type.toLowerCase()}.png')`;
                // Create the name element.
                const name = document.createElement('div');
                name.classList.add('cmt-mice-stats-name');
                name.innerText = type;
                // Create a wrapper for the name and image.
                const imageNameContainer = document.createElement('div');
                imageNameContainer.appendChild(image);
                imageNameContainer.appendChild(name);
				// Create a flat element
				const flat = document.createElement('div');
				flat.classList.add('cmt-mice-stats-catches');
                let flatnumber = mouseScore[type] + " / " + (mouseCats[cat]["mice"].length);
				flat.innerText = flatnumber;
                // Create the percentage element.
                const percentage = document.createElement('div');
                percentage.classList.add('cmt-mice-stats-catches');
                let number = (100*mouseScore[type]/(mouseCats[cat]["mice"].length)).toFixed(3);
                percentage.innerText = number + "%";
                // Add the image and name to the type element.
                typeEl.appendChild(imageNameContainer);
				typeEl.appendChild(flat);
                typeEl.appendChild(percentage);


                // console.log(100*mouseScore[type]/(mouseCats[cat]["mice"].length))
                return typeEl;

            }
        }
    }
    	/**
	 * Show the stat modal.
	 */
	const showModal = async () => {
		// First, check to make sure we have the element we want to append to.
		const target = document.querySelector('.pageFrameView-content');
		if (! target) {
			return;
		}

		// Remove the existing modal.
		const existing = document.getElementById('cmt-mice-stats');
		if (existing) {
			existing.remove();
		}

		// Create the modal.
		const modalWrapper = document.createElement('div');
		modalWrapper.id = 'cmt-mice-stats';

		// Create the wrapper.
		const modal = document.createElement('div');
		modal.classList.add('cmt-mice-stats-wrapper');

		// Create the header.
		const header = document.createElement('div');
		header.classList.add('cmt-mice-stats-header');

		// Add the title;
		const title = document.createElement('h1');
		title.innerText = 'Mouse Catch Stats';
		header.appendChild(title);

		// Create a close button icon.
		const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
		closeIcon.classList.add('cmt-mice-stats-close');
		closeIcon.setAttribute('viewBox', '0 0 24 24');
		closeIcon.setAttribute('width', '18');
		closeIcon.setAttribute('height', '18');
		closeIcon.setAttribute('fill', 'none');
		closeIcon.setAttribute('stroke', 'currentColor');
		closeIcon.setAttribute('stroke-width', '1.5');

		// Create the path.
		const closePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
		closePath.setAttribute('d', 'M18 6L6 18M6 6l12 12');
		closeIcon.appendChild(closePath);

		// Close the modal when the icon is clicked.
		closeIcon.addEventListener('click', () => {
			modalWrapper.remove();
		});

		// Append the button.
		header.appendChild(closeIcon);

		// Add the header to the modal.
		modal.appendChild(header);
//-----
		// Make the mouse stats table.
		const mouseBody = document.createElement('div');
		mouseBody.classList.add('cmt-mice-stats-body');
		// TODO: add column headers
		const mouseHeaders = document.createElement('div');
		mouseHeaders.classList.add('cmt-mice-stats');
		const mouseImageHeader = document.createElement('div');
		const mouseNameHeader = document.createElement('div');
		const mouseFlatHeader = document.createElement('div');
		const mousePercentHeader = document.createElement('div');
		mouseImageHeader.innerText = "____";
		mouseNameHeader.innerText = "Name";
		mouseFlatHeader.innerText = "Flat score";
		mousePercentHeader.innerText = "percentage score";
		mouseHeaders.appendChild(mouseImageHeader);
		mouseHeaders.appendChild(mouseNameHeader);
		mouseHeaders.appendChild(mouseFlatHeader);
		mouseHeaders.appendChild(mousePercentHeader);
		modal.appendChild(mouseHeaders);
		// Get the mouse stats.
		const mouseStats = await getMouseStats();

		// Loop through the stats and add them to the modal.
		calculateScore(mouseStats);
        for (const score in mouseScore){
			let result = buildScoreMarkup(score);
			result.addEventListener('click', () => {
				generateTypeDetails(score);
			});
			mouseBody.appendChild(result);

        }

		// Add the mouse stats to the modal.
		modal.appendChild(mouseBody);

		// Add the modal to the wrapper.
		modalWrapper.appendChild(modal);

		// Add the wrapper to the body.
		target.appendChild(modalWrapper);
	};
	const generateTypeDetails = async (score) => {
		// First, check to make sure we have the element we want to append to.
		const target = document.querySelector('.pageFrameView-content');
		if (! target) {
			return;
		}
		// Remove the existing modal.
		const existing = document.getElementById('cmt-type-details');
		if (existing) {
			existing.remove();
		}
		// Create the modal.
		const modalWrapper = document.createElement('div');
		modalWrapper.id = 'cmt-type-details';


		// Create the wrapper.
		const modal = document.createElement('div');
		modal.classList.add('cmt-type-details-wrapper');

		// Create the header.
		const header = document.createElement('div');
		header.classList.add('cmt-mice-stats-header');

		// Add the title;
		const title = document.createElement('h1');
		title.innerText = score + ' details';
		header.appendChild(title);

		// Create a close button icon.
		const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
		closeIcon.classList.add('cmt-mice-stats-close');
		closeIcon.setAttribute('viewBox', '0 0 24 24');
		closeIcon.setAttribute('width', '18');
		closeIcon.setAttribute('height', '18');
		closeIcon.setAttribute('fill', 'none');
		closeIcon.setAttribute('stroke', 'currentColor');
		closeIcon.setAttribute('stroke-width', '1.5');

		// Create the path.
		const closePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
		closePath.setAttribute('d', 'M18 6L6 18M6 6l12 12');
		closeIcon.appendChild(closePath);

		// Close the modal when the icon is clicked.
		closeIcon.addEventListener('click', () => {
			modalWrapper.remove();
		});

		// Append the button.
		header.appendChild(closeIcon);

		// Add the header to the modal.
		modal.appendChild(header);

		const mouseBody = document.createElement('div');
		mouseBody.classList.add('cmt-type-details-body');

		for (let mouse in mouseTypeList[score]){
			const mouseEl = document.createElement('a');
			mouseEl.classList.add('cmt-mice-stats');

			//Create the image element
			const image = document.createElement('div');
			image.classList.add('cmt-mice-stats-image');
			image.style.backgroundImage = `url('${ mouseTypeList[score][mouse].thumb }')`;

			// Create the name element.
			const name = document.createElement('div');
			name.classList.add('cmt-mice-stats-name');
			name.innerText = mouseTypeList[score][mouse].name;

			// Create a wrapper for the name and image.
			const imageNameContainer = document.createElement('div');
			imageNameContainer.appendChild(image);
			imageNameContainer.appendChild(name);

			// Create the catches element.
			const catches = document.createElement('div');
			catches.classList.add('cmt-mice-stats-catches');
			catches.innerText = mouseTypeList[score][mouse].num_catches;

			// Add the image and name to the mouse element.
			mouseEl.appendChild(imageNameContainer);
			mouseEl.appendChild(catches);
			mouseBody.appendChild(mouseEl)

		}
		modal.appendChild(mouseBody);

		// Add the modal to the wrapper.
		modalWrapper.appendChild(modal);
		// TODO: add it somewhere properly 
		// Add the wrapper to the body.
		target.appendChild(modalWrapper);

		console.log(mouseTypeList[score]);
	}

    addStyles(`#cmt-mice-stats, #cmt-type-details {
        position: absolute;
        top: 10px;
        left: -275px;
    }

    .cmt-mice-stats-wrapper, .cmt-type-details-wrapper {
		z-index: 5000;
        position: fixed;
        width: 450px;
        background: #f6f3eb;
        border: 1px solid #534022;
        box-shadow: 1px 1px 1px 0px #9d917f;
    }

    .cmt-mice-stats-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        border-bottom: 1px solid #ceb7a6;
        background-color: #926944;
        padding: 10px;
        color: #f6f3eb;
    }

    .cmt-mice-stats-header h1 {
        color: #f6f3eb;
    }

    .cmt-mice-stats-close:hover {
        background-color: #ceb7a6;
        border-radius: 50%;
        cursor: pointer;
    }

    .cmt-mice-stats-body {
        max-height: 90vh;
        overflow-y: scroll;
        overflow-x: hidden;
    }

    .cmt-mice-stats-wrapper .cmt-mice-stats:nth-child(odd) {
        background-color: #e8e3d7;
    }

    .cmt-mice-stats, .cmt-type-details {
        display: flex;
        justify-content: space-between;
        padding: 2px 0;
        align-items: center;
        padding: 10px 10px;
        color: #000;
    }

    .cmt-mice-stats:hover, .cmt-type-details:hover,
    .cmt-mice-stats-wrapper .cmt-mice-stats:nth-child(odd):hover {
        outline: 1px solid #ccc;
        background-color: #eee;
        text-decoration: none;
    }

    .cmt-mice-stats-image {
        position: relative;
        width: 40px;
        height: 40px;
        display: inline-block;
        vertical-align: middle;
        background-size: contain;
        background-repeat: no-repeat;
        border-radius: 2px;
        box-shadow: 1px 1px 1px #999;
    }

    .cmt-mice-stats-crown {
        position: absolute;
        right: -5px;
        bottom: -5px;
        width: 20px;
        height: 20px;
        background-repeat: no-repeat;
        background-position: 50% 50%;
        background-color: #fff;
        border: 1px solid #333;
        background-size: 80%;
        border-radius: 50%;
    }

    .cmt-mice-stats-name {
        display: inline-block;
        vertical-align: middle;
        padding-left: 10px;
    }

    .cmt-mice-stats-catches {
        padding-right: 5px;
    }`);

    addSubmenuItem({
        menu: 'mice',
        label: 'Mouse Catch Stats',
        icon: 'https://www.mousehuntgame.com/images/ui/hud/menu/prize_shoppe.png',
        callback: showModal
    });
})());