// ==UserScript==
// @name IQRPG Stats
// @namespace https://www.iqrpg.com/
// @version 0.58
// @description Includes a drop tracker for all your IQ drops, as well as tracking hits to kill/die breakpoints and showing how much you need to reach the next breakpoint! Plus various other battle stats.
// @author Coastis
// @match *://*.iqrpg.com/*
// @require http://code.jquery.com/jquery-latest.js
// @run-at document-idle
// @grant GM_addStyle
// ==/UserScript==
///////////////////////////////////
/////////// config ////////////////
///////////////////////////////////
const track_battle_stats = true; // true or false - enable or disable battle stats
const track_stats_n_drops = true; // true or false - enable or disable drop tracker
const dropalyse_decimal_precision = 3; // the number of decimal places to show for drops per hour/day averages
const track_boss_battles = false; // true or false
const track_clan_battles = false; // true or false
const track_abyss_battles = false; // true or false
const track_min_health_perc = true; // true or false
//////////////////////////////////////////////////////////////////
/////////// Don't change anything below this line ////////////////
//////////////////////////////////////////////////////////////////
/* globals jQuery, $ */
// init
var player_stats = { dmg:0, hits:0, misses:0, hp_perc:100, max_hp:0 }; //TODO update from local storage
var enemy_stats = { dmg:0, hits:0, misses:0, dodges:0,max_hp:0 }; //TODO update from local storage
var player_cache = '';
var enemy_cache = '';
//var action_timer_cache = ''; // delete
var actions = 0;
var dropalyse_rendered = false;
var dropalyse_format = 'hour'; // hour/day/total
var track_extra_battle_stats = true;
// setup persistant vars
var dropalyse_cache = '';
var dropalyse_store = {};
//var dropalyse_start_datum = Date.now(); // delete
var dropalyse_filters = [];
dropalyse_load_ls();
// run every xxx ms updating stuffs
// NOTE we set a delay of 1000ms before starting to help slow rendering cpu's, otherwise there is the possibility of the very 1st drop not being counted
setTimeout(function() {
// setup observer
if(track_stats_n_drops === true) {
if(dropalyse_rendered === false) { // insert p/h panel
dropalyse_insert_log();
dropalyse_rendered = true;
}
const actionsElement = document.querySelector(".action-timer__text");
const actionObserver = new MutationObserver(actionTimerChanged);
actionObserver.observe(actionsElement, { childList: true, subtree: true,characterData: true, characterDataOldValue: true});
}
// others
setInterval(iqrpgbs_loop, 100);
//setInterval(iqrpgbs_timer_loop, 1000);
}, 1000);
// observes action timer changes and updates actions count as needed
function actionTimerChanged(mutationList, observer) {
mutationList.forEach((mutation) => {
if(mutation.type === 'characterData') {
if(actionTimerClean(mutation.target.textContent) < actionTimerClean(mutation.oldValue)) {
actions++;
parse_drop_log(); // parse it
dropalyse_render_content(); // update p/h
}
}
});
}
function actionTimerClean(entry) {
return +(entry.replace("Autos Remaining: ",""));
}
// main loop
function iqrpgbs_loop() {
/*
NEW in v0.56
- For Battle Stats i've added "Kill In:Die in" breakpoint tracking which helps you improve your winrate and investment strategy
- Droptracker now has a popup confirmation box when clicking reset
- Added new ckan dragon type
*/
//TODO dungeon key total tokens
//TODO add persistance for battle stats
//TODO add "number of rushes" for gold/resources - would need to remove the popup associated with [Gold][Wood] etc
//TODO seperate out battle stats so boss/dungeon etc stats track seperately
//rarity tracking could detect if a new class is found and exit if carl changes css, thus preventing our nightmare
//TODO battle stats - winrate% improvment calculations e,g, attacj winrate% delta
// dropalyse
if(track_stats_n_drops === true) {
// first run?
if(dropalyse_cache === '' && $( 'div#log-div > div' ).length > 0 ) {
dropalyse_cache = dropalyse_clean_entry( $( 'div#log-div > div:first' ) );
}
/* // insert p/h panel
if(dropalyse_rendered === false) {
dropalyse_insert_log();
dropalyse_rendered = true;
} */
// NEW drop log parsing
/* if( $("div.fixed-top > div.section-2 > div.action-timer > div.action-timer__text").length > 0 ) {
let action_data = $("div.fixed-top > div.section-2 > div.action-timer > div.action-timer__text").prop('innerHTML').trim();
if(action_data !== action_timer_cache) {
action_timer_cache = action_data;
parse_drop_log(); // parse it
dropalyse_render_content(); // update p/h
}
} else {
return false; // skip as autos hasnt rendered yet
} */
}
// battle stats
if(track_battle_stats === false) return true; // enable or disable battle stats
var we_battling = false;
var display_needs_update = false;
// check we're on the battle page
if(document.getElementsByClassName("battle-container").length > 0) {
we_battling = true;
} else return false;
// and not in a boss battle - Boss Tokens
if(track_boss_battles!==true && $("div.game-grid > div.main-game-section > div.main-section__body:contains('Boss Tokens')").length > 0) {
return false;
}
// and not in a dungeon - Dungeoneering Exp
if($("div.game-grid > div.main-game-section > div.main-section__body:contains('Dungeoneering Exp')").length > 0) {
return false;
}
// and not in a standard clan battle - Clan Exp
if(track_clan_battles!==true && $("div.game-grid > div.main-game-section > div.main-section__body:contains('Clan Exp')").length > 0) {
return false;
}
// all is well, so let's get the mob name
var n_obj = $("div.battle-container > div:nth-child(3) > div.participant-name > span");
if(n_obj.length > 0) {
var mob_name = n_obj.prop('innerHTML').trim();
} else {
return false; // couldn't find the mob name, lets skip just in case
}
// and not in a clan dragon battle
// exact matches
const clan_dragons = ['Baby Dragon','Young Dragon','Adolescent Dragon','Adult Dragon','Elder Dragon','Dragon','Mythical Dragon'];
if(track_clan_battles!==true && clan_dragons.indexOf(mob_name) > -1 ) {
//console.log('Skipping Clan Dragons');
return false;
}
// and not in an abyss battle or clan battle - Abyssal
if(track_abyss_battles!==true && ['Abyssal'].some(term => mob_name.includes(term))) {
return false;
}
// all good
if(we_battling === true) {
// Add the battle stat panel to dom, if not already there
if(!document.getElementById("iqrpgbs_bs_panel")) {
var iqrpg_body = $( "div.game-grid > div.main-game-section > div.main-section__body" );
iqrpg_body[0].children[0].children[0].insertAdjacentHTML('beforeend', render_battle_stats_panel() );
document.getElementById('igrpg_bs_reset').addEventListener('click', iqrpg_bs_reset_stats, false);
}
// get the players stat line & compare it to previous stored stats
var player_sl = $("div.game-grid > div.main-game-section > div.main-section__body > div > div > div > div:nth-child(2)");
if(player_sl.prop('innerHTML') !== player_cache) {
player_cache = player_sl.prop('innerHTML');
parse_player_stat_line(player_sl);
display_needs_update = true;
}
// get the mobs stat line & compare it to previous stored stats
var mobs_sl = $("div.game-grid > div.main-game-section > div.main-section__body > div > div > div > div:nth-child(3)");
if(mobs_sl.prop('innerHTML') !== enemy_cache) {
enemy_cache = mobs_sl.prop('innerHTML');
parse_enemy_stat_line(mobs_sl);
display_needs_update = true;
}
// we already have display_needs_update, so let's use it as a trigger for our new health tracking
if(display_needs_update === true ) {
// get player health line
let players_hp_sl = $("div.game-grid > div.main-game-section > div.main-section__body > div > div > div > div.battle-container > div.battle-container__section > div:nth-child(2) > div.progress__text");
player_stats.max_hp = players_hp_sl.prop('innerHTML').split(" / ")[1].replaceAll(",", "");
// get enemy health line
let enemy_hp_sl = $("div.game-grid > div.main-game-section > div.main-section__body > div > div > div > div.battle-container > div.battle-container__section:nth-child(3) > div:nth-child(2) > div.progress__text ");
enemy_stats.max_hp = enemy_hp_sl.prop('innerHTML').split(" / ")[1].replaceAll(",", "");
if(track_min_health_perc === true) {
let hp_totals = players_hp_sl.prop('innerHTML').split(" / ");
let this_perc = (parseInt(hp_totals[0].replaceAll(",", "")) / parseInt(hp_totals[1].replaceAll(",", ""))) * 100;
if(this_perc < player_stats.hp_perc) player_stats.hp_perc = this_perc;
}
}
// update displayed values
if(display_needs_update === true) {
update_display();
}
}
}
// parses the drop log and builds array of quantified contents
function parse_drop_log() {
if( $( 'div#log-div > div' ).length == 0 ) return false; // either log isn't rendered yet, or it's just been cleared
const skiplist = ['[Gold]','Gold Rush:','Action Bonus:','Resource Rush:','Skill:','Mastery:','[Wood]','[Stone]','[Metal]'];
let first_log_entry = dropalyse_clean_entry( $( 'div#log-div > div:first' ) ); // capture cached entry for possible later use
let count = 0;
$( 'div#log-div > div' ).each(function( index ) {
// check if already analysed
let str = dropalyse_clean_entry($(this));
//console.log("str - " + str);
//console.log("cache - " + dropalyse_cache );
if(str === dropalyse_cache) return false; // break loop
// skip unwanted
if (skiplist.some(v => str.includes(v))) return true; // continue loop
// parse into time, qty, item
let entry = parse_drop_log_entry($( this ));
count++;
// add to data store
if (typeof dropalyse_store[entry.item] !== 'undefined') {
dropalyse_store[entry.item] += entry.qty;
} else {
dropalyse_store[entry.item] = entry.qty;
}
});
// do we have new entries?
if(count>0) {
dropalyse_cache = first_log_entry;
}
// save data to localstorage
dropalyse_save_ls();
}
function dropalyse_clean_entry(txt) {
//console.log("Cleaning - " + $(txt).text() );
var r = txt.clone();
r.find('.popup').remove();
//console.log("Clean - " + r.text() );
let ret = r.text();
// rarity
//let rarity = $("div.item > p[class^='text-rarity-']", r).attr('class');
//if(typeof rarity !== 'undefined') ret = ret + "#iqrpgstats#" + rarity;
return ret;
}
function parse_drop_log_entry(entry) {
let r = {};
// timestamp - not needed??
r.timestamp = $('span:first', entry).text();
// item
let data_str = $('span', entry).eq(1).text().replaceAll(",","");
let matches = data_str.match(/^[+-]?\d+(\.\d+)?[%]?/g);
if(matches && matches.length>0) {
let n = matches[0];
r.qty = Number(n.replace('+', '').replace('%', ''));
r.item = data_str.replace(n,'').trim();
} else {
r.qty = 1; // it's something unusual
r.item = data_str.trim();
}
// strip extra data
r.item = r.item.split("]")[0].replace("[","").replace("]","");
// append new rarity data
let rarity = $("div.item > p[class^='text-rarity-']", entry).attr('class');
if(typeof rarity !== 'undefined') r.item = r.item + "#iqrpgstats#" + rarity;
//console.log(rarity + " - " + r.item);
return r;
}
function dropalyse_load_ls() {
if (localStorage.getItem('dropalyse_cache')) { dropalyse_cache = localStorage.getItem('dropalyse_cache'); }
if (localStorage.getItem('dropalyse_actions')) { actions = localStorage.getItem('dropalyse_actions'); }
if (localStorage.getItem('dropalyse_store')) { dropalyse_store = JSON.parse(localStorage.getItem('dropalyse_store')); }
if (localStorage.getItem('dropalyse_filters')) { dropalyse_filters = JSON.parse(localStorage.getItem('dropalyse_filters')); }
// if we have a start datum, convert to new actions method, and delete start datum from LS
if (localStorage.getItem('dropalyse_start_datum')) {
let dropalyse_start_datum = localStorage.getItem('dropalyse_start_datum');
actions = Math.floor((( Date.now() - dropalyse_start_datum ) / 1000) /6 ); // approximate actions from old timing data
localStorage.removeItem('dropalyse_start_datum'); // remove old data
dropalyse_save_ls(); // save new data
}
}
function dropalyse_save_ls() {
localStorage.setItem('dropalyse_cache', dropalyse_cache);
localStorage.setItem('dropalyse_store', JSON.stringify(dropalyse_store));
localStorage.setItem('dropalyse_actions', actions);
localStorage.setItem('dropalyse_filters', JSON.stringify(dropalyse_filters));
localStorage.setItem('dropalyse_version', GM_info.script.version);
// DELETE
//localStorage.setItem('dropalyse_start_datum', dropalyse_start_datum);
//localStorage.setItem('dropalyse_save_datum', Date.now());
}
function dropalyse_reset() {
if (confirm("Are you sure you wish to reset drop tracker?") == true) {
dropalyse_store = {};
actions = 0;
dropalyse_save_ls(); // update persistant storage
dropalyse_render_content();
}
}
function dropalyse_render_content() {
let html = '';
if(Object.entries(dropalyse_store).length == 0 ) {
html = '<div>Waiting for drops...</div>';
} else {
// get a sorted copy of the store
let copy_of_dropalyse_store = Object.assign(dropalyse_objectOrder(), dropalyse_store);
for (let [key, qty] of Object.entries(copy_of_dropalyse_store)) {
// skip null
if(Number(qty) == 0 ) continue;
// format qty
let formatted_qty = qty;
if(dropalyse_format==='hour') {
//formatted_qty = ( ( qty / dropalyse_get_time_elapsed() ) * 60 * 60 ).toFixed(dropalyse_decimal_precision);
formatted_qty = ( ( qty / actions ) * 10 * 60 ).toFixed(dropalyse_decimal_precision);
} else if(dropalyse_format==='day') {
//formatted_qty = ( ( qty / dropalyse_get_time_elapsed() ) * 60 * 60 * 24).toFixed(dropalyse_decimal_precision);
formatted_qty = ( ( qty / actions ) * 10 * 60 * 24).toFixed(dropalyse_decimal_precision);
} else {
formatted_qty = Number(formatted_qty.toFixed(dropalyse_decimal_precision)).toString();
}
// format rarity
let rarity_class = '';
let parts = key.split("#iqrpgstats#");
if(parts.length === 2) rarity_class = parts[1];
// is it filtered?
if(dropalyse_filters.includes(parts[0])) continue;
// render
html += '<div style="position: relative;" class="iqrpgbs_hover_highlight"><div style="display: flex; justify-content: space-between;">'
+ '<span class="'+ escapeHTML(rarity_class) + '">' + escapeHTML(parts[0]) + '</span>'
+ '<span style="color:#3c3">' + escapeHTML(formatted_qty) + '</span>'
+ '</div></div>';
}
}
$('div#iqrpgbs_drop_log_content').html(html);
document.querySelector('#iqrpgbs_dropalyse_actioncount').textContent = Number(actions).toLocaleString();
document.querySelector('#iqrpgbs_dropalyse_actiondatum').textContent = dropalyse_render_nice_timer( actions * 6);
}
function escapeHTML(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function dropalyse_insert_log() {
let html = `<div id="iqrpgbs_dropalyse_container" class="main-section" style="background-color: #0a0a0a;margin-bottom: .2rem;border: 1px solid #333;">
<div id="iqrpgbs_drop_log_header" style="cursor:pointer;">
<p><span id="iqrpgbs_drop_log_header_title">Drops per hour</span><span class="grey-text" style="margin-left: 0.5rem; font-size: 0.9rem;display:none;">(Collapsed)</span></p><!---->
</div>
<div id="iqrpgbs_drop_log_body" class="main-section__body" style="border-top: 1px solid #333;padding: .5rem;">
<div>
<div id="iqrpgbs_drop_log_content">Waiting for drops...</div>
<div class="iqrpgbs_dropalyse_spacing"><span id="iqrpgbs_dropalyse_actiondatum">0s</span> <a title="Based on 6 seconds per action" style="cursor:help">🛈</a></div>
<div class="iqrpgbs_dropalyse_spacing"><span id="iqrpgbs_dropalyse_actioncount">0</span> Actions</div>
<div id="iqrpgbs_dropalyse_options">[<a id="iqrpgbs_dropalyse_opt_hour" href="#">Hour</a>
- <a id="iqrpgbs_dropalyse_opt_day" href="#">Day</a>
- <a id="iqrpgbs_dropalyse_opt_total" href="#">Total</a>]
[<a id="iqrpgbs_dropalyse_backup_toggle" href="#">Options</a>]
[<a id="iqrpgbs_dropalyse_reset" href="#">Reset</a>]</div>
<div id="iqrpgbs_dropalyse_backup_panel" style="margin-top:12px;display:none;">
<p class="heading">Drop Filters</p>
<p>You can filter out drops that you're not interested in tracking by entering them, one per line, in the field below. e.g. to filter out goblin keys, you would enter Goblin Cave Key on a new line, then click the update button</p>
<textarea rows="5" placeholder="Enter one filter per line..." style="width:100%;margin-top:6px;height: auto;" id="iqrpgbs_dropalyse_filter_textarea"></textarea>
<p class="text-center" style="margin:6px;"><button id="iqrpgbs_dropalyse_but_update_filters">Update Filters</button></p>
<p> </p>
<p class="heading">Backup & Restore Drops</p>
<p>You can download a copy of your drop data by clicking the button below...</p>
<p class="text-center" style="margin:6px;"><button id="iqrpgbs_dropalyse_but_export">Backup Data</button></p>
<p>To restore your data, paste the contents of your backup file into the field below and click the button</p>
<textarea placeholder="" style="width:100%;margin-top:6px;" id="iqrpgbs_dropalyse_import_textarea"></textarea>
<p class="text-center" style="margin:6px;"><button id="iqrpgbs_dropalyse_but_import">Restore Data</button></p>
</div>
</div></div></div>`;
$(html).insertAfter($('div.game-grid > div:first > div.main-section').last());
dropalyse_render_filters();
// setup format options and events
dropalyse_set_format(dropalyse_format); // set the initial format
document.getElementById('iqrpgbs_dropalyse_opt_hour').addEventListener("click", function(e) { e.preventDefault(); dropalyse_set_format('hour'); });
document.getElementById('iqrpgbs_dropalyse_opt_day').addEventListener("click", function(e) { e.preventDefault(); dropalyse_set_format('day'); });
document.getElementById('iqrpgbs_dropalyse_opt_total').addEventListener("click", function(e) { e.preventDefault(); dropalyse_set_format('total'); });
document.getElementById('iqrpgbs_dropalyse_reset').addEventListener("click", function(e) { e.preventDefault(); dropalyse_reset(); });
document.getElementById('iqrpgbs_dropalyse_but_export').addEventListener("click", function(e) { e.preventDefault(); dropalyse_export_data(); });
document.getElementById('iqrpgbs_dropalyse_but_import').addEventListener("click", function(e) { e.preventDefault(); dropalyse_import_data(); });
$('a#iqrpgbs_dropalyse_backup_toggle').click(function(e){
e.preventDefault();
$("div#iqrpgbs_dropalyse_backup_panel").toggle();
$('a#iqrpgbs_dropalyse_backup_toggle').toggleClass( "iqrpgbs_highlight" );
});
document.getElementById('iqrpgbs_drop_log_header').addEventListener("click", function(e) {
e.preventDefault();
$("div#iqrpgbs_drop_log_body").toggle();
$("div#iqrpgbs_drop_log_header > p > span.grey-text").toggle();
});
document.getElementById('iqrpgbs_dropalyse_but_update_filters').addEventListener("click", function(e) { e.preventDefault(); dropalyse_update_filters(); });
}
function dropalyse_set_format(format) {
$('a#iqrpgbs_dropalyse_opt_hour').removeClass("iqrpgbs_highlight");
$('a#iqrpgbs_dropalyse_opt_day').removeClass("iqrpgbs_highlight");
$('a#iqrpgbs_dropalyse_opt_total').removeClass("iqrpgbs_highlight");
if(format==='hour') {
dropalyse_format = 'hour';
$('a#iqrpgbs_dropalyse_opt_hour').addClass("iqrpgbs_highlight");
$('div#iqrpgbs_drop_log_header > p > span#iqrpgbs_drop_log_header_title').html('Drops Per Hour');
} else if(format==='day') {
dropalyse_format = 'day';
$('a#iqrpgbs_dropalyse_opt_day').addClass("iqrpgbs_highlight");
$('div#iqrpgbs_drop_log_header > p > span#iqrpgbs_drop_log_header_title').html('Drops Per Day');
} else if(format==='total') {
dropalyse_format = 'total';
$('a#iqrpgbs_dropalyse_opt_total').addClass("iqrpgbs_highlight");
$('div#iqrpgbs_drop_log_header > p > span#iqrpgbs_drop_log_header_title').html('Drops - Total');
}
dropalyse_render_content(); // update view
}
// DELETE ME
/* function dropalyse_get_time_elapsed() {
return ( Date.now() - dropalyse_start_datum )/1000;
} */
function dropalyse_render_nice_timer(delta) {
//var delta = dropalyse_get_time_elapsed();
var days = Math.floor(delta / 86400);
delta -= days * 86400;
var hours = Math.floor(delta / 3600) % 24;
delta -= hours * 3600;
var minutes = Math.floor(delta / 60) % 60;
delta -= minutes * 60;
var seconds = delta % 60;
let html = '';
if(days>0) html += days + 'd ';
if(hours>0||days>0) html += hours + 'h ';
if(hours>0||days>0||minutes>0) html += minutes + 'm ';
html += Math.floor(seconds) + 's';
return html;
}
function dropalyse_render_filters() {
//document.getElementById("iqrpgbs_dropalyse_filter_textarea").value = escapeHTML(dropalyse_filters.join("\r\n"));
// don't need to escape it as long as we set it using .value = 'stuffs'
document.getElementById("iqrpgbs_dropalyse_filter_textarea").value = dropalyse_filters.join("\r\n");
}
function dropalyse_update_filters() {
let our_filters = document.getElementById("iqrpgbs_dropalyse_filter_textarea").value;
dropalyse_filters = our_filters.trim().split(/\r?\n/).map(s => s.trim());
dropalyse_save_ls(); // save to ls
dropalyse_render_content(); // render drops again
// hide panel - don't hide it?
$("div#iqrpgbs_dropalyse_backup_panel").hide();
$('a#iqrpgbs_dropalyse_backup_toggle').removeClass( "iqrpgbs_highlight" );
}
function dropalyse_import_data() {
// get and test for empty data
let our_data = document.getElementById("iqrpgbs_dropalyse_import_textarea").value;
if(our_data=='') return false; // blank data
// catch errors on parsing
try {
let backup = JSON.parse(our_data);
// let's set the new vals
dropalyse_cache = ''; // reset cache to '' so we can pull fresh data in, in our main loop
dropalyse_store = backup.dropalyse_store;
if(backup.dropalyse_filters) dropalyse_filters = backup.dropalyse_filters;
if(backup.dropalyse_actions) { // if actions, use them
actions = backup.dropalyse_actions;
} else if(backup.dropalyse_start_datum) { // approximate actions from old timing data
let start_datum = ( Date.now() - Number(backup.dropalyse_backup_datum)) + Number(backup.dropalyse_start_datum);
actions = Math.floor((( Date.now() - start_datum ) / 1000) /6 );
} else {
actions = 0;
console.log('Drop Tracker could not determine action count from backup, reseting it to 0 as a fallback!');
}
//dropalyse_start_datum = ( Date.now() - Number(backup.dropalyse_backup_datum)) + Number(backup.dropalyse_start_datum);
// all done, let's re-render the tracker
dropalyse_render_content();
$("div#iqrpgbs_dropalyse_backup_panel").hide();
$('a#iqrpgbs_dropalyse_backup_toggle').removeClass( "iqrpgbs_highlight" );
document.getElementById("iqrpgbs_dropalyse_import_textarea").value = '';
} catch(e) {
alert("Backup data does not appear to be valid JSON!"); // error in the above string (in this case, yes)!
return false;
}
}
function dropalyse_export_data() {
const backup = {
dropalyse_cache: dropalyse_cache,
dropalyse_store: dropalyse_store,
dropalyse_actions: actions,
dropalyse_filters: dropalyse_filters,
dropalyse_version: GM_info.script.version,
dropalyse_backup_datum: Date.now()
};
// Convert object to Blob
const blobConfig = new Blob(
[ JSON.stringify(backup) ],
{ type: 'text/json;charset=utf-8' }
)
// Convert Blob to URL
const blobUrl = URL.createObjectURL(blobConfig);
// Create an a element with blobl URL
const anchor = document.createElement('a');
anchor.href = blobUrl;
anchor.target = "_blank";
anchor.download = "IQRPG-Stats-" + Date.now() + ".json";
// Auto click on a element, trigger the file download
anchor.click();
// Don't forget ;)
URL.revokeObjectURL(blobUrl);
}
// Dropalyse item order
function dropalyse_objectOrder() {
return {
// attributes
'Health': 0, 'Attack': 0, 'Defence':0, 'Accuracy':0, 'Dodge':0,
// credits
'Credits':0, 'Bound Credits':0, 'Dungeoneering Tokens#iqrpgstats#text-rarity-1':0,
// components + gather shards
'Weapon Component#iqrpgstats#text-rarity-2': 0, 'Armor Component#iqrpgstats#text-rarity-2': 0, 'Tool Component#iqrpgstats#text-rarity-2':0, 'Gathering Skill Shard#iqrpgstats#text-rarity-2':0,
'Resource Cache#iqrpgstats#text-rarity-2': 0,
'Gem Fragments#iqrpgstats#text-rarity-2': 0,
'Trinket Fragments#iqrpgstats#text-rarity-3': 0,
'Runic Leather#iqrpgstats#text-rarity-4':0,
// keys
'Goblin Cave Key#iqrpgstats#text-rarity-2': 0,
'Mountain Pass Key#iqrpgstats#text-rarity-2': 0,
'Desolate Tombs Key#iqrpgstats#text-rarity-2':0,
'Dragonkin Lair Key#iqrpgstats#text-rarity-2':0,
'Sunken Ruins Key#iqrpgstats#text-rarity-2':0,
'Abandoned Tower Key#iqrpgstats#text-rarity-3':0,
'Haunted Cells Key#iqrpgstats#text-rarity-3':0,
'Hall Of Dragons Key#iqrpgstats#text-rarity-3':0,
'The Vault Key#iqrpgstats#text-rarity-4':0,
'The Treasury Key#iqrpgstats#text-rarity-4':0,
// upgrade stones
'Health Upgrade Stone#iqrpgstats#text-rarity-3': 0,
'Damage Upgrade Stone#iqrpgstats#text-rarity-4': 0,
// wood drops - in alchemy?
// stone drops
'Sandstone#iqrpgstats#text-rarity-2': 0,
'Marble#iqrpgstats#text-rarity-3': 0,
'Malachite#iqrpgstats#text-rarity-4':0,
// metal drops
'Sapphire#iqrpgstats#text-rarity-2':0,
'Ruby#iqrpgstats#text-rarity-3':0,
'Emerald#iqrpgstats#text-rarity-3':0,
'Diamond#iqrpgstats#text-rarity-4':0,
// alchemy ingrediants
'Tree Sap#iqrpgstats#text-rarity-2': 0,
'Spider Egg#iqrpgstats#text-rarity-2': 0,
'Bone Meal#iqrpgstats#text-rarity-2': 0,
'Vial Of Orc Blood#iqrpgstats#text-rarity-3': 0,
'Undead Heart#iqrpgstats#text-rarity-3': 0,
"Bird's Nest#iqrpgstats#text-rarity-3": 0,
'Alchemic Essence#iqrpgstats#text-rarity-3': 0,
'Golden Egg#iqrpgstats#text-rarity-4': 0,
'Demonic Dust#iqrpgstats#text-rarity-4': 0,
// potions
'Training Gathering Potion#iqrpgstats#text-rarity-2':0,
'Training Exp Potion#iqrpgstats#text-rarity-2':0,
'Minor Gathering Potion#iqrpgstats#text-rarity-2':0,
'Minor Exp Potion#iqrpgstats#text-rarity-2':0,
'Haste Potion#iqrpgstats#text-rarity-3':0,
'Minor Autos Potion#iqrpgstats#text-rarity-3':0,
'Aptitude Potion#iqrpgstats#text-rarity-3':0,
'Greater Exp Potion#iqrpgstats#text-rarity-3':0,
'Heroic Potion#iqrpgstats#text-rarity-4':0,
'Ultra Exp Potion#iqrpgstats#text-rarity-4':0,
// runes
'Training Rune 1#iqrpgstats#text-rarity-2':0,
'Training Rune 2#iqrpgstats#text-rarity-2':0,
'Training Rune 3#iqrpgstats#text-rarity-2':0,
'Training Rune 4#iqrpgstats#text-rarity-2':0,
'Rune Of The Warrior#iqrpgstats#text-rarity-3':0,
'Rune Of The Gladiator#iqrpgstats#text-rarity-3':0,
'Rune Of The Warlord#iqrpgstats#text-rarity-4':0,
'Rune Of The Overlord#iqrpgstats#text-rarity-4':0,
'Greater Rune Of The Warlord#iqrpgstats#text-rarity-5':0,
// jewels
'Sapphire Jewel#iqrpgstats#text-rarity-1':0,
'Sapphire Jewel#iqrpgstats#text-rarity-2':0,
'Sapphire Jewel#iqrpgstats#text-rarity-3':0,
'Sapphire Jewel#iqrpgstats#text-rarity-4':0,
'Sapphire Jewel#iqrpgstats#text-rarity-5':0,
'Sapphire Jewel#iqrpgstats#text-rarity-6':0,
'Ruby Jewel#iqrpgstats#text-rarity-1':0,
'Ruby Jewel#iqrpgstats#text-rarity-2':0,
'Ruby Jewel#iqrpgstats#text-rarity-3':0,
'Ruby Jewel#iqrpgstats#text-rarity-4':0,
'Ruby Jewel#iqrpgstats#text-rarity-5':0,
'Ruby Jewel#iqrpgstats#text-rarity-6':0,
'Emerald Jewel#iqrpgstats#text-rarity-1':0,
'Emerald Jewel#iqrpgstats#text-rarity-2':0,
'Emerald Jewel#iqrpgstats#text-rarity-3':0,
'Emerald Jewel#iqrpgstats#text-rarity-4':0,
'Emerald Jewel#iqrpgstats#text-rarity-5':0,
'Diamond Jewel#iqrpgstats#text-rarity-1':0,
'Diamond Jewel#iqrpgstats#text-rarity-2':0,
'Diamond Jewel#iqrpgstats#text-rarity-3':0,
'Diamond Jewel#iqrpgstats#text-rarity-4':0,
'Diamond Jewel#iqrpgstats#text-rarity-5':0,
// res cache??
}
}
///////////////////////////////////
// Battle Stats
///////////////////////////////////
function parse_player_stat_line(player_sl) {
var hits = player_sl.find("p:nth-child(1) > span:nth-child(2)");
if(hits.length > 0) {
var actual_hits = hits.prop('innerHTML').replaceAll(" time(s)", "");
player_stats.hits += parseInt(actual_hits);
}
var dmg = player_sl.find("p:nth-child(1) > span:nth-child(3)");
if(dmg.length > 0 && hits.length > 0) {
var actual_dmg = dmg.prop('innerHTML').replaceAll(" damage", "").replaceAll(",", "");
player_stats.dmg += parseInt(actual_dmg) * parseInt(actual_hits);
}
var misses = player_sl.find("p:nth-child(2) > span:nth-child(1)");
if(misses.length > 0) {
var actual_misses = misses.prop('innerHTML').replaceAll(" time(s)", "");
player_stats.misses += parseInt(actual_misses);
}
}
function parse_enemy_stat_line(stat_line) {
var hits = stat_line.find("p:nth-child(1) > span:nth-child(2)");
if(hits.length > 0) {
var actual_hits = hits.prop('innerHTML').replaceAll(" time(s)", "");
enemy_stats.hits += parseInt(actual_hits);
}
var dmg = stat_line.find("p:nth-child(1) > span:nth-child(3)");
if(dmg.length > 0 && hits.length > 0) {
var actual_dmg = dmg.prop('innerHTML').replaceAll(" damage", "").replaceAll(",", "");
enemy_stats.dmg += parseInt(actual_dmg) * parseInt(actual_hits);
}
var misses = stat_line.find("p:nth-child(2) > span:nth-child(2)");
if(misses.length > 0) {
var actual_misses = misses.prop('innerHTML').replaceAll(" time(s)", "");
enemy_stats.misses += parseInt(actual_misses);
}
var dodges = stat_line.find("p:nth-child(2) > span:nth-child(3)");
if(dodges.length > 0) {
var actual_dodges = dodges.prop('innerHTML').replaceAll(" attack(s)", "");
enemy_stats.dodges += parseInt(actual_dodges);
}
}
function iqrpg_bs_reset_stats(e) {
e.preventDefault();
player_stats.dmg = 0;
player_stats.hits = 0;
player_stats.misses = 0;
player_stats.hp_perc = 100;
enemy_stats.dmg = 0;
enemy_stats.hits = 0;
enemy_stats.misses = 0;
enemy_stats.dodges = 0;
update_display();
}
function update_display() {
// players
$("#iqrpgbs_pl_dmg").html(new Intl.NumberFormat().format(player_stats.dmg));
$("#iqrpgbs_pl_hits").html(new Intl.NumberFormat().format(player_stats.hits));
var avg = Math.round(player_stats.dmg / player_stats.hits) || 0;
$("#iqrpgbs_pl_avg").html(new Intl.NumberFormat().format(avg));
var acc = (player_stats.hits / (player_stats.hits + player_stats.misses))*100 || 0;
$("#iqrpgbs_pl_acc").html(new Intl.NumberFormat().format(acc.toFixed(2)));
let min_hp = player_stats.hp_perc || 0;
$("#iqrpgbs_min_hp").html(new Intl.NumberFormat().format(min_hp));
// enemy
var enemy_avg = Math.round(enemy_stats.dmg / enemy_stats.hits) || 0;
$("#iqrpgbs_enmy_avg").html(new Intl.NumberFormat().format(enemy_avg));
var enemy_acc = ( (enemy_stats.hits + enemy_stats.dodges) / (enemy_stats.hits + enemy_stats.misses + enemy_stats.dodges))*100 || 0;
$("#iqrpgbs_enmy_acc").html(new Intl.NumberFormat().format(enemy_acc.toFixed(2)));
var enemy_dodges = (enemy_stats.dodges / (enemy_stats.hits /*+ enemy_stats.misses*/ + enemy_stats.dodges))*100 || 0;
$("#iqrpgbs_enmy_dodges").html(new Intl.NumberFormat().format(enemy_dodges.toFixed(2)));
// extras
if(track_extra_battle_stats===true) {
// hits to kill
let hits_to_kill_enemy = (enemy_stats.max_hp / avg);
let attk_next_breakpoint = enemy_stats.max_hp / (Math.ceil(hits_to_kill_enemy)-1)
let p_dmg_delta = attk_next_breakpoint - avg + 1;
$("#iqrpgbs_attk_delta").html("You kill in " + Number(Math.ceil(hits_to_kill_enemy)).toString() + " hits"
+ ", to improve winrate increase avg dmg by " + Intl.NumberFormat().format(Math.ceil(p_dmg_delta)) );
// survive longer
let hits_to_die = Math.ceil(player_stats.max_hp / enemy_avg);
let p_def_delta = enemy_avg - Math.ceil(player_stats.max_hp / (hits_to_die+0.0000001) );
$("#iqrpgbs_def_delta").html("You die in " + Number(hits_to_die).toString() + " hits"
+ ", to improve winrate reduce avg incoming dmg by " + Intl.NumberFormat().format(p_def_delta) );
}
}
function render_battle_stats_panel() {
var content = `
<div id="iqrpgbs_bs_panel" class="margin-top-large">
<div>Battle Stats <span>by Coastis</span></div>
<div>You dealt a total of <span id="iqrpgbs_pl_dmg">0</span> damage in <span id="iqrpgbs_pl_hits">0</span> hits,
with an average of <span id="iqrpgbs_pl_avg">0</span> per hit and <span id="iqrpgbs_pl_acc">0</span>% accuracy</div>
<div>Enemy dealt an average of <span id="iqrpgbs_enmy_avg">0</span> per hit
with an accuracy of <span id="iqrpgbs_enmy_acc">0</span>%, and you dodged <span id="iqrpgbs_enmy_dodges">0</span>% of attacks</div>
`;
if(track_min_health_perc === true) content += '<div>Your health reached a low of <span id="iqrpgbs_min_hp">100</span>%</div>';
if(track_extra_battle_stats === true) {
content += '<div><span id="iqrpgbs_attk_delta"></span></div>'
+ '<div><span id="iqrpgbs_def_delta"></span></div>';
}
content += '<div>[<a href="#" id="igrpg_bs_reset">Reset Battle Stats</a>]</div>';
content += '</div>';
return content;
}
GM_addStyle ( `
div#iqrpgbs_bs_panel div { text-align:center;padding:1px;}
div#iqrpgbs_bs_panel div:nth-child(1) { font-weight:bold;}
div#iqrpgbs_bs_panel div:nth-child(1) span { font-size:8px;font-weight:normal;}
div#iqrpgbs_drop_log_header {display: flex; justify-content: center; align-items: center; padding: .5rem; background: linear-gradient(#000,#151515);}
div#iqrpgbs_dropalyse_timer { padding:2px; margin-top:4px; text-align:center; }
.iqrpgbs_dropalyse_spacing { padding:2px; margin-top:4px; text-align:center; }
div#iqrpgbs_dropalyse_options { padding:2px; text-align:center; }
.iqrpgbs_highlight { color:#3c3 !important; }
div.iqrpgbs_hover_highlight:hover { background-color:#222222; }
` );