您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Monitors battle stat ratios and provides warnings if they approach levels that would preclude access to special gyms
// ==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; }