Noldor / Max stat calculator v0.7

// ==UserScript==
// @name         Max stat calculator v0.7
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  Max stat calculator
/* USAGE INSTRUCTIONS:
1) Open initium in a fresh tab (DO NOT DUPLICATE CURRENT TAB!) and switch to the character you want to test.
2) Hit the reset button
3) Start bashing monsters and watch the stat calculator estimate your max stats.  The precision of the estimate increases with each hit.
// @acknowledgements     The stat calculation code comes directly from Marcus stats calculator, with minor adaptations to function in this setting.  Many thanks
//                       to Marcus for his calculator.
// @author       Noldor
// @match        https://www.playinitium.com/*
// @grant        none
// ==/UserScript==
/* jshint -W097 */
/* [10:44] KingdomDrake: You can get the character ID using window.characterId
[10:44] KingdomDrake: var charStatMap = {}; charStatMap[characterID] = {stats: ...., blah:...} Object Map */
(function() {
	'use strict';
	var $ = window.jQuery;

	// runs add click event function on page refresh vv
	addClickEvent();

	// select the target node
	var buttonList = document.getElementById('main-button-list');

	// create an observer instance
	var observer = new MutationObserver(function(mutations) {
		mutations.forEach(function(mutation) {
			addClickEvent();
		});    
	});

	// pass in the target node, as well as the observer options
	observer.observe(buttonList, { childList: true, subtree: true, characterData: true });

	var swingCount;
	var statsInit;
	var statsCur;
	var statsMax;
	var charStatsMap = {};
	var charStats = {};

	// clear swings count vv
	function resetCalc(){
		statsInit = document.getElementById("newui").children[0].getAttribute('minitip').split(/[/<]/);
		// remove unwanted bits from statsInit vv
		statsInit.splice(3,4);
		// transform array strings to floating numbers vv
		for (var i=0; i < statsInit.length; i++) {
			statsInit[i] = parseFloat(statsInit[i]);
		}
		charStatsMap = JSON.parse(localStorage.getItem('charStats'));
		if (charStatsMap === null) {
			charStatsMap = {};
		}
		// set initial conditions vv 
		charStatsMap [window.characterId] = {'initial': statsInit, 
											 'diff': [0, 0, 0], 
											 'saved': [0, 0, 0], 
											 'hits': 0
											};
		localStorage.setItem('charStats', JSON.stringify(charStatsMap));
		// transform arrays to strings, store as initial stats vv
		//localStorage.setItem('initialstats', JSON.stringify(statsInit));
		// localStorage.setItem('swings', 0);
		// localStorage.setItem('characterName', document.getElementById('newui').children[2].children[0].innerHTML);
		// localStorage.setItem('statsDiff', '[0, 0, 0]');
		// localStorage.setItem('savedstatsMax', '[0, 0, 0]');
	}

	// clear swings button vv
	var clearButton = document.createElement("input");
	clearButton.type = "button";
	clearButton.value = "Reset";
	clearButton.id = "resetCalculator";
	clearButton.style = "margin-left:10px";
	var getElement = document.getElementsByClassName("header");
	getElement[0].appendChild(clearButton);
	document.getElementById("resetCalculator").addEventListener("click", resetCalc);

	// create and display stats span vv
	// var charDiv = $('.character-display-box').children(" div:nth-child(3)").children( 'a' );
	var statSpan = document.createElement("SPAN");
	statSpan.style = "margin-left:10px; font-size:medium";
	statSpan.id = "statsDisplay";
	getElement[0].appendChild(statSpan);

	// add click event listeners to all attack buttons. vv
	function addClickEvent(){
		if(document.getElementById('banner-text-overlay').children[0] !== undefined) {
			document.getElementById('banner-text-overlay').children[0].addEventListener("click", countSwing);
			document.getElementById('banner-text-overlay').children[1].addEventListener("click", countSwing);
			document.getElementById('main-button-list').children[0].addEventListener("click", countSwing);
			document.getElementById('main-button-list').children[1].addEventListener('click', countSwing);
		} else {
			if (statSpan !== undefined) {
				charStatsMap = JSON.parse(localStorage.getItem('charStats'));
				var spanStats = {};
				for (var i = 0; i < 3; i++) {
					if (charStatsMap[window.characterId].saved[i] === null || charStatsMap[window.characterId].saved[i] === undefined){
						charStatsMap[window.characterId].saved[i] = 0;
					}
					spanStats[i] = Math.round(charStatsMap[window.characterId].saved[i] * 100)/100;
				}
				statSpan.textContent = "Cap: " + "S: " + spanStats[0] + "  " + "D: " + spanStats[1] + "  " + "I: " + spanStats[2];
			}
		}
	}
	// get swings, increment swing count, store swings, call read getStats() into statsMax and display  vv
	function countSwing(){
		charStatsMap = JSON.parse(localStorage.getItem('charStats'));
		swingCount = charStatsMap[window.characterId].hits;
		// swingCount = localStorage.getItem('swings');
		/*if (swingCount === null){
			alert("You need to hit the reset button first!");
			return;
		} This needs re-write to be functional ^^*/ 
		statsCur = document.getElementById("newui").children[0].getAttribute('minitip').split('/');
		statsCur.splice(3,4);
		for (var i=0; i < statsCur.length; i++) {
			statsCur[i] = parseFloat(statsCur[i]);
		}
		console.log("new iteration");
		// note: mini tool tip update is not instantaneous, and so the data gathered in the previous steps is actually the stats from the previous swing.
		// therefore, the this script is configured to pass the previous swing count the calculator as well, and the swing count is not incremented 
		// until after the calculator has finished running.
		console.log(statsCur);

		if (swingCount !== 0) {
			statsMax = getStats();

			// Take the results of statsmax and read in older calculations where no new calculation was made vv
			for (i = 0; i < 3; i++) {
				if (statsMax[i] === 0 || statsMax[i] === null ) {
					statsMax[i] = charStatsMap[window.characterId].saved[i];
				}
			}
			// display stats
			
			statSpan.textContent = "Cap: " + "S: " + statsMax[0].toFixed(2) + "  " + "D: " +statsMax[1].toFixed(2) + "  " + "I: " + statsMax[2].toFixed(2);
			charStatsMap[window.characterId].saved = statsMax;
		}
		swingCount = swingCount + 1;
		console.log(swingCount);
		charStatsMap[window.characterId].hits = swingCount;
		localStorage.setItem('charStats', JSON.stringify(charStatsMap));
	}

	// define remaining variables and run calculations
	function getStats() {		
		statsInit = charStatsMap[window.characterId].initial;
		console.log(statsInit);
		var hits = swingCount;
		console.log(hits);
		var stats;
		var compMod = [0, 0, 0];
		var diff = [0, 0, 0];
		if (statsInit[0]==5) {compMod[0]=0.005; }
		diff[0] = Math.round((statsCur[0]-statsInit[0])*100)/100-compMod[0];
		if (statsInit[1]==5) { compMod[1]=0.005; }
		diff[1] = Math.round((statsCur[1]-statsInit[1])*100)/100-compMod[1];
		if (statsInit[2]==5) { compMod[2]=0.005; }
		diff[2] = Math.round((statsCur[2]-statsInit[2])*100)/100-compMod[2];
		console.log(diff);
		console.log(charStatsMap[window.characterId].diff);
		var mod = [0.0009954, 0.00057334, 0.0001414791];
		var modmax = [0.0012947, 0.00072171, 0.000199425];
		var modin = [0.0000014965, 0.0000007418493, 0.0000002897295];
		var max = [11, 10, 10];
		var minmax = [9, 8, 8];
		var i, j, g, k;
		var statMax = [0,0,0];

		// This part only calculates and updates statMax if the stat has increased between iterations
		if ( charStatsMap[window.characterId].diff[0] != diff[0] ) {
			statMax[0]=(evalmaxStats(statsInit[0], statsCur[0], hits, diff[0], stats, mod[0], modmax[0], modin[0], max[0], minmax[0], i, j, g, 0) + 
						evalminStats(statsInit[0], statsCur[0], hits, diff[0], stats, mod[0], modmax[0], modin[0], max[0], minmax[0], i, j, g, 0))/2;
		}

		if ( charStatsMap[window.characterId].diff[1] != diff[1] ) {
			statMax[1]=(evalmaxStats(statsInit[1], statsCur[1], hits, diff[1], stats, mod[1], modmax[1], modin[1], max[1], minmax[1], i, j, g, 1) + 
						evalminStats(statsInit[1], statsCur[1], hits, diff[1], stats, mod[1], modmax[1], modin[1], max[1], minmax[1], i, j, g, 1))/2;
		}

		if ( charStatsMap[window.characterId].diff[2] != diff[2] ) {
			statMax[2]=(evalmaxStats(statsInit[2], statsCur[2], hits, diff[2], stats, mod[2], modmax[2], modin[2], max[2], minmax[2], i, j, g, 2) + 
						evalminStats(statsInit[2], statsCur[2], hits, diff[2], stats, mod[2], modmax[2], modin[2], max[2], minmax[2], i, j, g, 2))/2;
		}

		// store stats difference for future reference vv
		charStatsMap[window.characterId].diff = diff;
		console.log(statMax);
		return statMax;	
	}

	//calculator functions (with thanks to Marcus) vv
	function evalmaxStats(initial, current, hits, diff, stats, mod, modmax, modin, max, minmax, i, j, g, k) {		
		j=mod;
		for (g=max; g>=minmax; g=g-0.01) {
			initial = statsInit[k];
			for (i=1; i<=hits; i++) {
				stats=initial+(g-initial)*j;
				initial=stats;

				if (((Math.round(stats*100))/100)==current && i==hits) {
					return g;
				}
			}
			j=j+modin;
		}	
	}	

	function evalminStats(initial, current, hits, diff, stats, mod, modmax, modin, max, minmax, i, j, g, k) {		
		j=modmax;
		for (g=minmax; g<=max; g=g+0.01) {
			initial = statsInit[k];
			for (i=1; i<=hits; i++) {
				stats=initial+(g-initial)*j;
				initial=stats;

				if (((Math.round(stats*100))/100)==current && i==hits) {
					return g;
				}
			}
			j=j-modin;
		}	
	}
})();