// ==UserScript==
// @name Special Gym Ratios (with goals)
// @namespace RGiskard.hanksRatio
// @version 2.3.2
// @description Monitors battle stat ratios and provides warnings if they approach levels that would preclude access to special gyms
// @author RGiskard [1953860], assistance by Xiphias [187717]
// @include *.torn.com/gym.php*
// @require http://code.jquery.com/jquery-latest.js
// @grant none
// ==/UserScript==
// Based off of Torn Gym Pony by Zanoab (http://puu.sh/jFtro/1af393771e.user.js).
// Kivou [2000607] I just added this from original script
var dex = 300000000;
var goals = {
strength: 2*(dex+4*dex/5.)/5.,
defense: 4*dex/5.,
speed: 2*(dex+4*dex/5.)/5.,
dexterity: dex,
}
// Maximum amount below the stat limit another stat can be before we start warning the player.
var statSafeDistance = localStorage.statSafeDistance;
if (statSafeDistance === null) {
statSafeDistance = 1000000;
}
// A second method, "Baldr's Ratio", is in this code, but the ability to select it has been
// deliberately excluded for public release. This has been done for clarity, as there is
// no accompanying information about what this ratio is. Those who would like to use it,
// which unlocks a specialty gym that is one of the same stats as a combo gym
// (e.g.: Frontline Fitness (str/spd) and Gym 3000 (str), can uncomment the lines of code
// adding those options to $specialistGymBuild.
jQuery.noConflict(true)(document).ready(function($) {
var cleanNumber = function(a) {
return Number(a.replace(/[$,]/g, "").trim());
};
/**
* Formats a number into an abbreviated string with an appropriate trailing descriptive unit
* up to 't' for trillion.
* @param {float} number the number to be formatted
* @param {int} maxFractionDigits the maximum number of fractional digits to display
* @returns a string representing the number, abbreviated if appropriate
**/
var FormatAbbreviatedNumber = function(number, maxFractionDigits) {
var abbreviations = [];
abbreviations[0] = '';
abbreviations[1] = 'k';
abbreviations[2] = 'm';
abbreviations[3] = 'b';
abbreviations[4] = 't';
var outputNumber = number;
var abbreviationIndex = 0;
for (; outputNumber >= 1000 && abbreviationIndex < abbreviations.length; ++abbreviationIndex) {
outputNumber = outputNumber / 1000;
}
return outputNumber.toLocaleString('EN', { maximumFractionDigits : maxFractionDigits }) + abbreviations[abbreviationIndex];
};
var getStats = function($doc) {
var ReplaceStatValueAndReturnCleanNumber = function(elementId) {
var $statTotalElement = $doc.find('#' + elementId);
if ($statTotalElement.size() === 0) throw 'No element found with id "' + elementId + '".';
var numericalValue = cleanNumber($statTotalElement.text());
return numericalValue;
};
$doc = $($doc || document);
return {
strength: ReplaceStatValueAndReturnCleanNumber('strength-val'),
defense: ReplaceStatValueAndReturnCleanNumber('defense-val'),
speed: ReplaceStatValueAndReturnCleanNumber('speed-val'),
dexterity: ReplaceStatValueAndReturnCleanNumber('dexterity-val'),
};
};
var noBuildKeyValue = {value: 'none', text: 'No specialty gyms'};
var defenseDexterityGymKeyValue = {value: 'balboas', text: 'Defense and dexterity specialist',
stat1: 'defense', stat2: 'dexterity', secondarystat1: 'strength', secondarystat2: 'speed'};
var strengthSpeedGymKeyValue = {value: 'frontline', text: 'Strength and speed specialist',
stat1: 'strength', stat2: 'speed', secondarystat1: 'defense', secondarystat2: 'dexterity'};
var strengthComboGymKeyValue = {value: 'frontlinegym3000', text: 'Strength combo specialist (Baldr\'s Ratio)', stat: 'strength', combogym: strengthSpeedGymKeyValue};
var defenseComboGymKeyValue = {value: 'balboasisoyamas', text: 'Defense combo specialist (Baldr\'s Ratio)', stat: 'defense', combogym: defenseDexterityGymKeyValue};
var speedComboGymKeyValue = {value: 'frontlinetotalrebound', text: 'Speed combo specialist (Baldr\'s Ratio)', stat: 'speed', combogym: strengthSpeedGymKeyValue};
var dexterityComboGymKeyValue = {value: 'balboaselites', text: 'Dexterity combo specialist (Baldr\'s Ratio)', stat: 'dexterity', combogym: defenseDexterityGymKeyValue};
var strengthGymKeyValue = {value: 'gym3000', text: 'Strength specialist (Hank\'s Ratio)', stat: 'strength', combogym: defenseDexterityGymKeyValue};
var defenseGymKeyValue = {value: 'isoyamas', text: 'Defense specialist (Hank\'s Ratio)', stat: 'defense', combogym: strengthSpeedGymKeyValue};
var speedGymKeyValue = {value: 'totalrebound', text: 'Speed specialist (Hank\'s Ratio)', stat: 'speed', combogym: defenseDexterityGymKeyValue};
var dexterityGymKeyValue = {value: 'elites', text: 'Dexterity specialist (Hank\'s Ratio)', stat: 'dexterity', combogym: strengthSpeedGymKeyValue};
function GetStoredGymKeyValuePair() {
if (localStorage.specialistGymType == defenseDexterityGymKeyValue.value) return defenseDexterityGymKeyValue;
if (localStorage.specialistGymType == strengthSpeedGymKeyValue.value) return strengthSpeedGymKeyValue;
if (localStorage.specialistGymType == strengthComboGymKeyValue.value) return strengthComboGymKeyValue;
if (localStorage.specialistGymType == defenseComboGymKeyValue.value) return defenseComboGymKeyValue;
if (localStorage.specialistGymType == speedComboGymKeyValue.value) return speedComboGymKeyValue;
if (localStorage.specialistGymType == dexterityComboGymKeyValue.value) return dexterityComboGymKeyValue;
if (localStorage.specialistGymType == strengthGymKeyValue.value) return strengthGymKeyValue;
if (localStorage.specialistGymType == defenseGymKeyValue.value) return defenseGymKeyValue;
if (localStorage.specialistGymType == speedGymKeyValue.value) return speedGymKeyValue;
if (localStorage.specialistGymType == dexterityGymKeyValue.value) return dexterityGymKeyValue;
return noBuildKeyValue;
}
var $hanksRatioDiv = $('<div></div>');
var $titleDiv = $('<div>', {'class': 'title-black top-round', 'aria-level': '5', 'text': 'Special Gym Ratios'})
.css('margin-top', '10px');
$hanksRatioDiv.append($titleDiv);
var $bottomDiv = $('<div class="bottom-round gym-box cont-gray p10"></div>');
$bottomDiv.append($('<p class="sub-title">Select desired specialist build:</p>'));
var $specialistGymBuild = $('<select>', {'class': 'vinkuun-enemeyDifficulty'}).css('margin-top', '10px').on('change', function() {
localStorage.specialistGymType = $specialistGymBuild.val();
});
// $specialistGymBuild.append($('<option>', noBuildKeyValue));
$specialistGymBuild.append($('<option>', defenseDexterityGymKeyValue));
// $specialistGymBuild.append($('<option>', strengthSpeedGymKeyValue));
// $specialistGymBuild.append($('<option>', strengthComboGymKeyValue));
// $specialistGymBuild.append($('<option>', defenseComboGymKeyValue));
// $specialistGymBuild.append($('<option>', speedComboGymKeyValue));
$specialistGymBuild.append($('<option>', dexterityComboGymKeyValue));
// $specialistGymBuild.append($('<option>', strengthGymKeyValue));
// $specialistGymBuild.append($('<option>', defenseGymKeyValue));
// $specialistGymBuild.append($('<option>', speedGymKeyValue));
// $specialistGymBuild.append($('<option>', dexterityGymKeyValue));
localStorage.specialistGymType = GetStoredGymKeyValuePair().value; // In case there is bad data, replace it.
$specialistGymBuild.val(GetStoredGymKeyValuePair().value);
$bottomDiv.append($specialistGymBuild);
$hanksRatioDiv.append($bottomDiv);
$('#gymroot').append($hanksRatioDiv);
var oldTotal = 0;
var oldBuild = '';
setInterval(function() {
var stats = getStats();
var total = 0;
var highestSecondaryStat = 0;
for (var stat in stats) {
total += stats[stat];
if (GetStoredGymKeyValuePair().stat && GetStoredGymKeyValuePair().stat != stat && stats[stat] > highestSecondaryStat) {
highestSecondaryStat = stats[stat];
}
}
var currentBuild = $specialistGymBuild.val();
if (oldTotal == total && oldBuild == currentBuild && $('.gymstatus').size() != 0) {
return;
}
var $statContainers = $('[class^="gymContent__"], [class*=" gymContent__"]').find('li');
if (currentBuild == noBuildKeyValue.value) {
// Clear the training info in case it exists.
$statContainers.each(function(index, element) {
var $statInfoDiv = $(element).find('[class^="description__"], [class*=" description__"]');
var $insertedElement = $statInfoDiv.find('.gymstatus');
$insertedElement.remove();
});
return;
}
var isComboGymOnlyRatio = (
localStorage.specialistGymType == defenseDexterityGymKeyValue.value ||
localStorage.specialistGymType == strengthSpeedGymKeyValue.value);
var isComboGymCombinedRatio = (
localStorage.specialistGymType == strengthComboGymKeyValue.value ||
localStorage.specialistGymType == defenseComboGymKeyValue.value ||
localStorage.specialistGymType == speedComboGymKeyValue.value ||
localStorage.specialistGymType == dexterityComboGymKeyValue.value);
var isSingleGymRatio = (
localStorage.specialistGymType == strengthGymKeyValue.value ||
localStorage.specialistGymType == defenseGymKeyValue.value ||
localStorage.specialistGymType == speedGymKeyValue.value ||
localStorage.specialistGymType == dexterityGymKeyValue.value);
// The combined total of the primary stats must be 25% higher than the total of the secondary stats.
var minPrimaryComboSum = 0; // The minimum amount the combined primary stats must be to unlock the gym based on the secondary stat sum.
var maxSecondaryComboSum = 0; // The maximum amount the combined secondary stats must be to unlock the gym based on the primary stat sum.
// The primary stat needs to be 25% higher than the second highest stat.
var minPrimaryStat = 0;
var maxSecondaryStat = 0;
var comboGymKeyValuePair = noBuildKeyValue;
var primaryGymKeyValuePair = noBuildKeyValue;
if (isComboGymOnlyRatio) {
comboGymKeyValuePair = GetStoredGymKeyValuePair();
} else if (isComboGymCombinedRatio || isSingleGymRatio) {
primaryGymKeyValuePair = GetStoredGymKeyValuePair();
comboGymKeyValuePair = primaryGymKeyValuePair.combogym;
minPrimaryStat = highestSecondaryStat * 1.25;
maxSecondaryStat = stats[primaryGymKeyValuePair.stat] / 1.25;
} else {
console.debug('Somehow attempted to calculate stat requirements for invalid gym: ' + GetStoredGymKeyValuePair());
return;
}
minPrimaryComboSum = (stats[comboGymKeyValuePair.secondarystat1] + stats[comboGymKeyValuePair.secondarystat2]) * 1.25;
maxSecondaryComboSum = (stats[comboGymKeyValuePair.stat1] + stats[comboGymKeyValuePair.stat2]) / 1.25;
var distanceFromComboGymMin = minPrimaryComboSum - stats[comboGymKeyValuePair.stat1] - stats[comboGymKeyValuePair.stat2];
var distanceToComboGymMax = maxSecondaryComboSum - stats[comboGymKeyValuePair.secondarystat1] - stats[comboGymKeyValuePair.secondarystat2];
$statContainers.each(function(index, element) {
var $element = $(element);
var title = $element.find('[class^="title__"], [class*=" title__"]');
var stat = $element.attr('zStat');
if (!stat) {
stat = title.text().toLowerCase();
$element.attr('zStat', stat);
}
if (stats[stat]) {
var gymStatus;
var statIdentifierString;
if (isComboGymOnlyRatio) {
if (stat == comboGymKeyValuePair.stat1 || stat == comboGymKeyValuePair.stat2) {
statIdentifierString = GetStatAbbreviation(comboGymKeyValuePair.stat1).capitalizeFirstLetter() +
' + ' + GetStatAbbreviation(comboGymKeyValuePair.stat2);
if (distanceFromComboGymMin > 0) {
gymStatus = '<span class="gymstatus t-red bold">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(distanceFromComboGymMin, 1) + ' too low!</span>';
} else if (distanceFromComboGymMin < statSafeDistance) {
gymStatus = '<span class="gymstatus t-red bold">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(-distanceFromComboGymMin, 1) + ' above the limit.</span>';
} else {
gymStatus = '<span class="gymstatus t-green">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(-distanceFromComboGymMin, 1) + ' above the limit.</span>';
}
} else {
statIdentifierString = GetStatAbbreviation(comboGymKeyValuePair.secondarystat1).capitalizeFirstLetter() +
' + ' + GetStatAbbreviation(comboGymKeyValuePair.secondarystat2);
if (distanceToComboGymMax < 0) {
gymStatus = '<span class="gymstatus t-red bold">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(-distanceToComboGymMax, 1) + ' too high!</span>';
} else if (distanceToComboGymMax < statSafeDistance) {
gymStatus = '<span class="gymstatus t-red bold">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(distanceToComboGymMax, 1) + ' below the limit.</span>';
} else {
gymStatus = '<span class="gymstatus t-green">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(distanceToComboGymMax, 1) + ' below the limit.</span>';
}
}
} else {
var distanceFromSpecialistGymMin = minPrimaryStat - stats[stat];
var distanceToSpecialistGymMax = maxSecondaryStat - stats[stat];
var distanceToMax = 0;
statIdentifierString = stat.capitalizeFirstLetter();
if (stat == primaryGymKeyValuePair.stat) {
if (distanceFromSpecialistGymMin <= 0) {
if (isSingleGymRatio) {
// Specialist stat for Hank's Gym Ratio is never one of the primary combo stats.
// Only set the identifier if we don't already know this stat is too low to unlock its own specific gym.
distanceToMax = distanceToComboGymMax;
if (distanceToMax < 0) {
statIdentifierString = GetStatAbbreviation(comboGymKeyValuePair.secondarystat1).capitalizeFirstLetter() +
' + ' + GetStatAbbreviation(comboGymKeyValuePair.secondarystat2);
}
} else {
// Specialist stat IS the combo stat; we only care to show how it's doing in relation to the specialist gym.
distanceToMax = distanceFromSpecialistGymMin;
}
}
} else if (stat == comboGymKeyValuePair.stat1 || stat == comboGymKeyValuePair.stat2) {
// We don't have to worry about this stat going too high for the combo gym.
distanceToMax = distanceToSpecialistGymMax;
} else {
// This stat is neither the primary stat nor a combo gym stat, so it's limited by both.
distanceToMax = Math.min(distanceToSpecialistGymMax, distanceToComboGymMax);
if (distanceToComboGymMax < distanceToSpecialistGymMax && distanceToMax < 0) {
statIdentifierString = GetStatAbbreviation(comboGymKeyValuePair.secondarystat1).capitalizeFirstLetter() +
' + ' + GetStatAbbreviation(comboGymKeyValuePair.secondarystat2);
}
}
if (stat == primaryGymKeyValuePair.stat) {
console.debug(stat + ' distanceFromSpecialistGymMin: ' + distanceFromSpecialistGymMin);
console.debug(stat + ' distanceToComboGymMax: ' + distanceToComboGymMax);
} else if (stat == comboGymKeyValuePair.stat1 || stat == comboGymKeyValuePair.stat2) {
console.debug(stat + ' distanceToSpecialistGymMax: ' + distanceToSpecialistGymMax);
console.debug(stat + ' distanceFromComboGymMin: ' + distanceFromComboGymMin);
} else {
console.debug(stat + ' distanceToSpecialistGymMax: ' + distanceToSpecialistGymMax);
console.debug(stat + ' distanceToComboGymMax: ' + distanceToComboGymMax);
}
console.debug(stat + ' distanceToMax: ' + distanceToMax);
if (stat == primaryGymKeyValuePair.stat && distanceFromSpecialistGymMin > 0) {
gymStatus = '<span class="gymstatus t-red bold">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(distanceFromSpecialistGymMin, 1) + ' too low!</span>';
} else if (distanceToMax < 0) {
if (stat == primaryGymKeyValuePair.stat && isComboGymCombinedRatio) {
gymStatus = '<span class="gymstatus t-green">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(-distanceToMax, 1) + ' above the limit.</span>';
} else {
gymStatus = '<span class="gymstatus t-red bold">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(-distanceToMax, 1) + ' too high!</span>';
}
} else if (distanceToMax < statSafeDistance) {
gymStatus = '<span class="gymstatus t-red bold">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(distanceToMax, 1) + ' below the limit.</span>';
} else {
gymStatus = '<span class="gymstatus t-green">' + statIdentifierString + ' is ' + FormatAbbreviatedNumber(distanceToMax, 1) + ' below the limit.</span>';
}
}
var remains = goals[stat]-stats[stat];
if (remains > 0) {
gymStatus += '<spa class="gymstatus t-red"><br />' + FormatAbbreviatedNumber(remains, 1) + ' below ' + FormatAbbreviatedNumber(goals[stat], 1) + ' goal. </span>';
} else {
gymStatus += '<spa class="gymstatus t-green"><br />' + FormatAbbreviatedNumber(-remains, 1) + ' above ' + FormatAbbreviatedNumber(goals[stat], 3) + ' goal.</span>';
}
var $statInfoDiv = $element.find('[class^="description__"], [class*=" description__"]');
var $insertedElement = $statInfoDiv.find('.gymstatus');
$insertedElement.remove();
$statInfoDiv.append(gymStatus);
}
});
oldTotal = total;
oldBuild = currentBuild;
console.debug("Stat spread updated!");
}, 400);
});
String.prototype.capitalizeFirstLetter = function() {
return this.charAt(0).toUpperCase() + this.slice(1);
};
function GetStatAbbreviation(statString) {
if (statString == 'strength') {
return 'str';
} else if (statString == 'defense') {
return 'def';
} else if (statString == 'speed') {
return 'spd';
} else if (statString == 'dexterity') {
return 'dex';
}
return statString;
}