// ==UserScript==
// @name TornPDA - Gym Gains Calculator
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Embeds a collapsible gym gains calculator with stat goal tracking and enhanced outputs
// @author Jvmie[2094564]
// @match https://www.torn.com/gym.php*
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const gyms = {
"Premier Fitness": { Str: 2, Spe: 2, Def: 2, Dex: 2, Energy: 5 },
"Average Joes": { Str: 2.4, Spe: 2.4, Def: 2.8, Dex: 2.4, Energy: 5 },
"Woody's Workout": { Str: 2.8, Spe: 3.2, Def: 3, Dex: 2.8, Energy: 5 },
"Beach Bods": { Str: 3.2, Spe: 3.2, Def: 3.2, Dex: 0, Energy: 5 },
"Silver Gym": { Str: 3.4, Spe: 3.6, Def: 3.4, Dex: 3.2, Energy: 5 },
"Pour Femme": { Str: 3.4, Spe: 3.6, Def: 3.6, Dex: 3.8, Energy: 5 },
"Davies Den": { Str: 3.7, Spe: 0, Def: 3.7, Dex: 3.7, Energy: 5 },
"Global Gym": { Str: 4, Spe: 4, Def: 4, Dex: 4, Energy: 5 },
"Knuckle Heads": { Str: 4.8, Spe: 4.4, Def: 4, Dex: 4.2, Energy: 10 },
"Pioneer Fitness": { Str: 4.4, Spe: 4.6, Def: 4.8, Dex: 4.4, Energy: 10 },
"Anabolic Anomalies": { Str: 5, Spe: 4.6, Def: 5.2, Dex: 4.6, Energy: 10 },
"Core": { Str: 5, Spe: 5.2, Def: 5, Dex: 5, Energy: 10 },
"Racing Fitness": { Str: 5, Spe: 5.4, Def: 4.8, Dex: 5.2, Energy: 10 },
"Complete Cardio": { Str: 5.5, Spe: 5.8, Def: 5.5, Dex: 5.2, Energy: 10 },
"Legs Bums and Tums": { Str: 0, Spe: 5.6, Def: 5.6, Dex: 5.8, Energy: 10 },
"Deep Burn": { Str: 6, Spe: 6, Def: 6, Dex: 6, Energy: 10 },
"Apollo Gym": { Str: 6, Spe: 6.2, Def: 6.4, Dex: 6.2, Energy: 10 },
"Gun Shop": { Str: 6.6, Spe: 6.4, Def: 6.2, Dex: 6.2, Energy: 10 },
"Force Training": { Str: 6.4, Spe: 6.6, Def: 6.4, Dex: 6.8, Energy: 10 },
"Cha Cha's": { Str: 6.4, Spe: 6.4, Def: 6.8, Dex: 7, Energy: 10 },
"Atlas": { Str: 7, Spe: 6.4, Def: 6.4, Dex: 6.6, Energy: 10 },
"Last Round": { Str: 6.8, Spe: 6.6, Def: 7, Dex: 6.6, Energy: 10 },
"The Edge": { Str: 6.8, Spe: 7, Def: 7, Dex: 6.8, Energy: 10 },
"George's": { Str: 7.3, Spe: 7.3, Def: 7.3, Dex: 7.3, Energy: 10 },
"Balboas Gym": { Str: 0, Spe: 0, Def: 7.5, Dex: 7.5, Energy: 25 },
"Frontline Fitness": { Str: 7.5, Spe: 7.5, Def: 0, Dex: 0, Energy: 25 },
"Gym 3000": { Str: 8, Spe: 0, Def: 0, Dex: 0, Energy: 50 },
"Mr Isoyamas": { Str: 0, Spe: 0, Def: 8, Dex: 0, Energy: 50 },
"Total Rebound": { Str: 0, Spe: 8, Def: 0, Dex: 0, Energy: 50 },
"Elites": { Str: 0, Spe: 0, Def: 0, Dex: 8, Energy: 50 },
"Sports Science Lab": { Str: 9, Spe: 9, Def: 9, Dex: 9, Energy: 25 }
};
function calculateSingleGain(stat, happy, gymDots, bonusMultiplier) {
const baseGain = (gymDots * (0.00019106 * stat + 0.00226263 * happy + 0.55) * 4) / 13.06;
return baseGain * bonusMultiplier;
}
function calculateTotalGain(stat, initialHappy, gymDots, numTrains, bonusMultiplier) {
let totalGain = 0;
let currentHappy = initialHappy;
let currentStat = stat;
for (let i = 0; i < numTrains; i++) {
const gain = calculateSingleGain(currentStat, currentHappy, gymDots, bonusMultiplier);
totalGain += gain;
currentStat += gain;
currentHappy -= 5; // Adjusted to match game log
if (currentHappy < 0) currentHappy = 0;
}
return { singleGain: calculateSingleGain(stat, initialHappy, gymDots, bonusMultiplier), totalGain };
}
function calculateTrainsToGoal(currentStat, goalStat, initialHappy, gymDots, bonusMultiplier, energyPerTrain, sessionEnergy) {
let totalGain = 0;
let currentHappy = initialHappy;
let currentStatValue = currentStat;
let repsNeeded = 0;
while (totalGain < (goalStat - currentStat) && currentHappy > 0) {
const gain = calculateSingleGain(currentStatValue, currentHappy, gymDots, bonusMultiplier);
totalGain += gain;
currentStatValue += gain;
currentHappy -= 5; // Adjusted to match game log
if (currentHappy < 0) currentHappy = 0;
repsNeeded++;
}
const energyRequired = repsNeeded * energyPerTrain;
const trainsNeeded = Math.ceil(energyRequired / sessionEnergy);
return { trainsNeeded, energyRequired, totalGain };
}
const factionBonuses = Array.from({ length: 19 }, (_, i) => i);
const propertyBonuses = [0, 1, 2];
const educationBonuses = [0, 1, 2];
const calculatorHTML = `
<div id="gymCalcContainer" style="margin: 10px; font-family: 'Arial', sans-serif; color: #fff;">
<button id="collapseBtn" style="width: 100%; padding: 10px; background: #1a1a1a; color: #fff; border: none; cursor: pointer; text-align: left; font-size: 16px; font-weight: bold;">
Gym Gains Calculator ▼
</button>
<div id="calcContent" style="display: none; padding: 15px; background: #2a2a2a; border: 1px solid #444; border-radius: 5px;">
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Stat to Train:
<select id="statSelect" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
<option value="Str">Strength</option>
<option value="Spe">Speed</option>
<option value="Def">Defense</option>
<option value="Dex">Dexterity</option>
</select>
</label>
</div>
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Gym:
<select id="gymSelect" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
${Object.keys(gyms).map(gym => `<option value="${gym}" ${gym === "Gun Shop" ? "selected" : ""}>${gym}</option>`).join('')}
</select>
</label>
</div>
</div>
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Current Stat:
<input type="number" id="currentStat" min="0" value="0" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
</label>
</div>
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Goal Stat:
<input type="number" id="goalStat" min="0" value="0" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
</label>
</div>
</div>
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Happiness:
<input type="number" id="happy" min="0" value="0" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
</label>
</div>
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Total Energy:
<input type="number" id="energy" min="0" value="0" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
</label>
</div>
</div>
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Faction Bonus (%):
<select id="factionBonus" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
${factionBonuses.map(val => `<option value="${val}" ${val === 0 ? "selected" : ""}>${val}%</option>`).join('')}
</select>
</label>
</div>
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Property Bonus (%):
<select id="propBonus" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
${propertyBonuses.map(val => `<option value="${val}" ${val === 0 ? "selected" : ""}>${val}%</option>`).join('')}
</select>
</label>
</div>
</div>
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">
<div style="flex: 1; min-width: 200px;">
<label style="font-size: 14px; color: #ccc;">Education Bonus (%):
<select id="eduBonus" style="width: 100%; padding: 5px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px;">
${educationBonuses.map(val => `<option value="${val}" ${val === 0 ? "selected" : ""}>${val}%</option>`).join('')}
</select>
</label>
</div>
</div>
<button id="calcBtn" style="margin-top: 15px; padding: 8px 15px; background: #4CAF50; color: #fff; border: none; border-radius: 3px; cursor: pointer; font-size: 14px;">Calculate</button>
<div id="result" style="margin-top: 15px; font-size: 14px; color: #fff;"></div>
</div>
</div>
`;
const gymPage = document.querySelector('.content-wrapper');
if (gymPage) {
gymPage.insertAdjacentHTML('afterbegin', calculatorHTML);
const collapseBtn = document.getElementById('collapseBtn');
const calcContent = document.getElementById('calcContent');
collapseBtn.addEventListener('click', () => {
if (calcContent.style.display === 'none') {
calcContent.style.display = 'block';
collapseBtn.textContent = 'Gym Gains Calculator ▲';
} else {
calcContent.style.display = 'none';
collapseBtn.textContent = 'Gym Gains Calculator ▼';
}
});
const saveToLocalStorage = (key, value) => {
localStorage.setItem(`gymCalc_${key}`, value);
};
const loadFromLocalStorage = (key, defaultValue) => {
return localStorage.getItem(`gymCalc_${key}`) || defaultValue;
};
const statSelect = document.getElementById('statSelect');
const gymSelect = document.getElementById('gymSelect');
const currentStat = document.getElementById('currentStat');
const goalStat = document.getElementById('goalStat');
const happy = document.getElementById('happy');
const energy = document.getElementById('energy');
const factionBonus = document.getElementById('factionBonus');
const propBonus = document.getElementById('propBonus');
const eduBonus = document.getElementById('eduBonus');
statSelect.value = loadFromLocalStorage('statSelect', 'Str');
gymSelect.value = loadFromLocalStorage('gymSelect', 'Gun Shop');
currentStat.value = loadFromLocalStorage('currentStat', '0');
goalStat.value = loadFromLocalStorage('goalStat', '0');
happy.value = loadFromLocalStorage('happy', '0');
energy.value = loadFromLocalStorage('energy', '0');
factionBonus.value = loadFromLocalStorage('factionBonus', '0');
propBonus.value = loadFromLocalStorage('propBonus', '0');
eduBonus.value = loadFromLocalStorage('eduBonus', '0');
statSelect.addEventListener('change', () => saveToLocalStorage('statSelect', statSelect.value));
gymSelect.addEventListener('change', () => saveToLocalStorage('gymSelect', gymSelect.value));
currentStat.addEventListener('input', () => saveToLocalStorage('currentStat', currentStat.value));
goalStat.addEventListener('input', () => saveToLocalStorage('goalStat', goalStat.value));
happy.addEventListener('input', () => saveToLocalStorage('happy', happy.value));
energy.addEventListener('input', () => saveToLocalStorage('energy', energy.value));
factionBonus.addEventListener('change', () => saveToLocalStorage('factionBonus', factionBonus.value));
propBonus.addEventListener('change', () => saveToLocalStorage('propBonus', propBonus.value));
eduBonus.addEventListener('change', () => saveToLocalStorage('eduBonus', eduBonus.value));
document.getElementById('calcBtn').addEventListener('click', () => {
const statKey = statSelect.value;
const gymName = gymSelect.value;
const currentStatValue = parseFloat(currentStat.value) || 0;
const goalStatValue = parseFloat(goalStat.value) || 0;
const happyValue = parseFloat(happy.value) || 0;
const energyValue = parseFloat(energy.value) || 0;
const factionBonusValue = (parseFloat(factionBonus.value) || 0) / 100;
const propBonusValue = (parseFloat(propBonus.value) || 0) / 100;
const eduBonusValue = (parseFloat(eduBonus.value) || 0) / 100;
const gym = gyms[gymName];
const gymDots = gym[statKey];
const energyPerTrain = gym.Energy;
const numReps = Math.floor(energyValue / energyPerTrain);
const bonusMultiplier = (1 + factionBonusValue) * (1 + propBonusValue) * (1 + eduBonusValue);
if (gymDots === 0) {
document.getElementById('result').innerHTML = `<span style="color: #ff5555;">This gym does not train ${statKey}!</span>`;
return;
}
const { singleGain, totalGain } = calculateTotalGain(currentStatValue, happyValue, gymDots, numReps, bonusMultiplier);
const statDifference = goalStatValue - currentStatValue;
const sessionReps = numReps;
let goalResults = '';
if (goalStatValue > currentStatValue) {
const { trainsNeeded, energyRequired, totalGain: goalGain } = calculateTrainsToGoal(
currentStatValue, goalStatValue, happyValue, gymDots, bonusMultiplier, energyPerTrain, energyValue
);
goalResults = `
<br><b style="color: #4CAF50;">Goal Progress:</b><br>
Stat Difference: ${statDifference.toFixed(2)}<br>
Energy Required: ${energyRequired}<br>
Training Sessions Required: ${trainsNeeded}<br>
Expected Gain to Goal: ${goalGain.toFixed(2)}
`;
}
document.getElementById('result').innerHTML = `
<b style="color: #4CAF50;">Results:</b><br>
Single Rep Gain: ${singleGain.toFixed(2)}<br>
Total Reps: ${numReps}<br>
Training Sessions: 1<br>
Total Gain: ${totalGain.toFixed(2)}
${goalResults}
`;
});
}
})();