NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Kama Motivated Resources
// @namespace http://tampermonkey.net/
// @version 1.4.1
// @description Makes resources motivating with a balanced tools burst effect
// @author Brother [2590792], RGiskard [1953860], Xiphias [187717], Kama [3173749]
// @match https://www.torn.com/gym.php*
// @grant none
// @run-at document-idle
// @require https://code.jquery.com/jquery-latest.js
// @license AGPL-3.0-only
// @downloadURL https://update.greasyfork.org/scripts/573641/Kama%20Motivated%20Resources.user.js
// @updateURL https://update.greasyfork.org/scripts/573641/Kama%20Motivated%20Resources.meta.js
// ==/UserScript==
(function () {
"use strict";
const EMOJIS = {
strength: ['๐จ', '๐ช', '๐๏ธ', '๐ช', '๐ง'],
speed: ['๐', 'โฑ๏ธ', 'โก', '๐ฒ', '๐ฏ', '๐ช'],
defense: ['๐ก๏ธ', '๐งฑ', '๐ข', '๐งค', '๐ฅ'],
dexterity: ['๐', '๐ฅ', '๐', '๐', '๐ฉ'],
gross: ['๐คข', '๐คฎ', '๐ฉ', '๐๏ธ', '๐คก', '๐']
};
function isBadTrain(btn) {
const li = btn.closest('li');
if (!li) return false;
const statusSpan = li.querySelector('.gymstatus.t-red.bold');
if (!statusSpan) return false;
return statusSpan.textContent.toLowerCase().includes('too high!');
}
function createToolBurst(startX, startY, emojiSet) {
const dropCount = 40;
const particles = [];
for (let i = 0; i < dropCount; i++) {
const tool = document.createElement('div');
tool.innerText = emojiSet[Math.floor(Math.random() * emojiSet.length)];
tool.style.position = 'fixed';
tool.style.left = '0px';
tool.style.top = '0px';
tool.style.fontSize = (Math.random() * 16 + 20) + 'px';
tool.style.zIndex = '9999999';
tool.style.pointerEvents = 'none';
tool.style.userSelect = 'none';
document.body.appendChild(tool);
const angle = Math.random() * Math.PI * 2;
const velocity = (Math.random() * 12 + 8) * 0.75;
particles.push({
el: tool,
x: startX,
y: startY,
vx: Math.cos(angle) * velocity,
vy: (Math.sin(angle) * velocity) - 9,
rot: Math.random() * 360,
rotV: (Math.random() - 0.5) * 20
});
}
function animate() {
let active = false;
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.vy += 0.6;
p.x += p.vx;
p.y += p.vy;
p.rot += p.rotV;
p.el.style.transform = `translate3d(${p.x}px, ${p.y}px, 0) rotate(${p.rot}deg)`;
if (p.y > window.innerHeight + 50) {
p.el.remove();
particles.splice(i, 1);
} else {
active = true;
}
}
if (active) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
document.addEventListener("click", (e) => {
const btn = e.target.closest?.(".torn-btn");
if (!btn) return;
const aria = (btn.getAttribute("aria-label") || "").toLowerCase();
let statName = "";
let selectedEmojis = [];
if (aria.includes("train strength")) { statName = "strength"; selectedEmojis = EMOJIS.strength; }
else if (aria.includes("train speed")) { statName = "speed"; selectedEmojis = EMOJIS.speed; }
else if (aria.includes("train defense")) { statName = "defense"; selectedEmojis = EMOJIS.defense; }
else if (aria.includes("train dexterity")) { statName = "dexterity"; selectedEmojis = EMOJIS.dexterity; }
if (!statName) return;
if (isBadTrain(btn)) {
selectedEmojis = EMOJIS.gross;
}
let x = e.clientX, y = e.clientY;
if (!Number.isFinite(x) || !Number.isFinite(y)) {
const r = btn.getBoundingClientRect();
x = r.left + r.width / 2;
y = r.top + r.height / 2;
}
createToolBurst(x, y, selectedEmojis);
}, true);
})();
var statSafeDistance = localStorage.statSafeDistance;
if (statSafeDistance === null) {
statSafeDistance = 1000000;
}
jQuery.noConflict(true)(document).ready(function($) {
if (!window.location.href.includes("gym.php")) return;
var cleanNumber = function(a) {
return Number(a.replace(/[$,]/g, "").trim());
};
var FormatAbbreviatedNumber = function(number, maxFractionDigits) {
var abbreviations = ['', 'k', 'm', 'b', '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 stats = {};
$doc = $($doc || document);
$doc.find('h3:contains("Strength"), h3:contains("Defense"), h3:contains("Speed"), h3:contains("Dexterity")').each(function() {
var statName = $(this).text().toLowerCase();
var statValue = cleanNumber($(this).siblings('span').first().text());
if (["strength", "defense", "speed", "dexterity"].includes(statName)) {
stats[statName] = statValue;
}
});
return stats;
};
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() {
const types = [defenseDexterityGymKeyValue, strengthSpeedGymKeyValue, strengthComboGymKeyValue, defenseComboGymKeyValue, speedComboGymKeyValue, dexterityComboGymKeyValue, strengthGymKeyValue, defenseGymKeyValue, speedGymKeyValue, dexterityGymKeyValue];
const found = types.find(t => t.value == localStorage.specialistGymType);
return found || 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 $ratioDisplay = $('<span>').css({
'margin-left': '10px',
'font-size': '12px',
'color': '#888',
'font-weight': 'bold'
});
function updateRatioText() {
var val = $specialistGymBuild.val();
var text = "";
switch (val) {
case 'balboas': text = "(Def+Dex > Str+Spd by 25%)"; break;
case 'frontline': text = "(Str+Spd > Def+Dex by 25%)"; break;
case 'frontlinegym3000': text = "(Str > 2nd stat by 25% & Str+Spd > Def+Dex by 25%)"; break;
case 'balboasisoyamas': text = "(Def > 2nd stat by 25% & Def+Dex > Str+Spd by 25%)"; break;
case 'frontlinetotalrebound': text = "(Spd > 2nd stat by 25% & Str+Spd > Def+Dex by 25%)"; break;
case 'balboaselites': text = "(Dex > 2nd stat by 25% & Def+Dex > Str+Spd by 25%)"; break;
case 'gym3000': text = "(Str > 2nd stat by 25%)"; break;
case 'isoyamas': text = "(Def > 2nd stat by 25%)"; break;
case 'totalrebound': text = "(Spd > 2nd stat by 25%)"; break;
case 'elites': text = "(Dex > 2nd stat by 25%)"; break;
default: text = "";
}
$ratioDisplay.text(text);
}
var $specialistGymBuild = $('<select>', {'class': 'vinkuun-enemeyDifficulty'}).css('margin-top', '10px').on('change', function() {
localStorage.specialistGymType = $specialistGymBuild.val();
updateRatioText();
});
const options = [noBuildKeyValue, defenseDexterityGymKeyValue, strengthSpeedGymKeyValue, strengthComboGymKeyValue, defenseComboGymKeyValue, speedComboGymKeyValue, dexterityComboGymKeyValue, strengthGymKeyValue, defenseGymKeyValue, speedGymKeyValue, dexterityGymKeyValue];
options.forEach(opt => $specialistGymBuild.append($('<option>', opt)));
localStorage.specialistGymType = GetStoredGymKeyValuePair().value;
$specialistGymBuild.val(GetStoredGymKeyValuePair().value);
$bottomDiv.append($specialistGymBuild);
$bottomDiv.append($ratioDisplay);
$hanksRatioDiv.append($bottomDiv);
$('#gymroot').append($hanksRatioDiv);
updateRatioText();
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) {
$statContainers.each(function(index, element) {
$(element).find('.gymstatus').remove();
});
return;
}
var isComboGymOnlyRatio = [defenseDexterityGymKeyValue.value, strengthSpeedGymKeyValue.value].includes(localStorage.specialistGymType);
var isComboGymCombinedRatio = [strengthComboGymKeyValue.value, defenseComboGymKeyValue.value, speedComboGymKeyValue.value, dexterityComboGymKeyValue.value].includes(localStorage.specialistGymType);
var isSingleGymRatio = [strengthGymKeyValue.value, defenseGymKeyValue.value, speedGymKeyValue.value, dexterityGymKeyValue.value].includes(localStorage.specialistGymType);
var minPrimaryComboSum = 0;
var maxSecondaryComboSum = 0;
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 {
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) {
distanceToMax = distanceToComboGymMax;
if (distanceToMax < 0) {
statIdentifierString = GetStatAbbreviation(comboGymKeyValuePair.secondarystat1).capitalizeFirstLetter() + ' + ' + GetStatAbbreviation(comboGymKeyValuePair.secondarystat2);
}
} else {
distanceToMax = distanceFromSpecialistGymMin;
}
}
} else if (stat == comboGymKeyValuePair.stat1 || stat == comboGymKeyValuePair.stat2) {
distanceToMax = distanceToSpecialistGymMax;
} else {
distanceToMax = Math.min(distanceToSpecialistGymMax, distanceToComboGymMax);
if (distanceToComboGymMax < distanceToSpecialistGymMax && distanceToMax < 0) {
statIdentifierString = GetStatAbbreviation(comboGymKeyValuePair.secondarystat1).capitalizeFirstLetter() + ' + ' + GetStatAbbreviation(comboGymKeyValuePair.secondarystat2);
}
}
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 $statInfoDiv = $element.find('[class^="description__"], [class*=" description__"]');
$statInfoDiv.find('.gymstatus').remove();
$statInfoDiv.append(gymStatus);
}
});
oldTotal = total;
oldBuild = currentBuild;
}, 400);
});
String.prototype.capitalizeFirstLetter = function() {
return this.charAt(0).toUpperCase() + this.slice(1);
};
function GetStatAbbreviation(statString) {
const map = { 'strength': 'str', 'defense': 'def', 'speed': 'spd', 'dexterity': 'dex' };
return map[statString] || statString;
}