Gym Auto-Disabler

Prevents over-training of a gym. Requires torntools to take perks into account when training. Not tested on PDA/mobile.

当前为 2024-11-18 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Gym Auto-Disabler
// @namespace    Titanic
// @version      v1.5
// @description  Prevents over-training of a gym. Requires torntools to take perks into account when training. Not tested on PDA/mobile.
// @author       Titanic_ [2968477]
// @match        https://*.torn.com/gym.php*
// @grant        none
// ==/UserScript==

const statNames = [ "Strength", "Speed", "Dexterity", "Defense" ];
const statConstants = {
    Strength: { A: 1600, B: 1700, C: 700 },
    Speed: { A: 1600, B: 2000, C: 1350 },
    Dexterity: { A: 1800, B: 1500, C: 1000 },
    Defense: { A: 2100, B: -600, C: 1500 },
    Energy: {},
    Happy: {},
    Gym: { Keep: [ // gyms you want to keep in this format: "quotes",
        "Frontline Fitness",
        "Elites",
    ]},
};

/*

  Valid gym names:

  "Balboas Gym",
  "Frontline Fitness",
  "Gym 3000",
  "Mr. Isoyamas",
  "Total Rebound",
  "Elites",

*/

function setBasicValues() {
    const energy = getStatValue("[class*=bar_][class*=energy_] > [class*=bar-stats] > [class*=bar-value]");
    const happy = getStatValue("[class*=bar_][class*=happy_] > [class*=bar-stats] > [class*=bar-value]");

    const selectedGymName = getSelectedGymName();
    if (!selectedGymName) return;

    const gymStats = getGymStatsByName(selectedGymName);
    if (!gymStats) return;

    statConstants.Energy.Total = Number(energy);
    statConstants.Happy.Total = Number(happy);
    statConstants.Gym.Energy = gymStats.energy;

    statConstants.Gym.Happy = {
        5: 2.67,
        10: 5,
        25: 12.67,
        50: 25
    }[statConstants.Gym.Energy] || 0;

    setStatValues(gymStats);
}

function calculateTrainingGain({
    statType,
    currentStat,
    startingHappy,
    gymMultiplier,
    energyPerTrain,
    perkMultiplier
}) {
    const { A, B, C } = statConstants[statType];
    perkMultiplier = (perkMultiplier ? perkMultiplier / 100 : 0) + 1

    if (currentStat > 50000000) {
        const baselog = Math.log(currentStat) / Math.log(10)
        currentStat = (currentStat - 50000000) / (8.77635 * baselog) + 50000000
    }

    const logValue = Math.log(1 + startingHappy / 250);
    const roundedLogValue = parseFloat(logValue.toFixed(4));
    const multiplier = 1 + 0.07 * parseFloat(roundedLogValue.toFixed(4));

    const baseGain = (
        (currentStat * multiplier +
        (8 * (startingHappy ** 1.05)) +
        ((1 - ((startingHappy / 99999) ** 2)) * A) +
        B) * (1 / 200000) * gymMultiplier * energyPerTrain
    );

    const totalGain = baseGain * perkMultiplier;
    return Number(totalGain.toFixed(2)); // to 2 decimal places
}

async function calculateAllTrainingGains() {
    const success = await waitForEl("[class*=propertyValue_]");
    if(!success) return

    setBasicValues();

    for (const stat of statNames) {
        if (!statConstants.Gym[stat]) continue; // skip stat if not exist in this gym

        const input = document.querySelector(`[class*=${stat.toLowerCase()}_] input`)
        const selectedTrains = input.value;
        const maxTrains = Math.floor(statConstants.Energy.Total / statConstants.Gym.Energy)
        const numTrains = selectedTrains

        let totalGains = 0;
        for (let i = 0; i < numTrains; i++) {
            const gains = calculateTrainingGain({
                statType: stat,
                currentStat: statConstants[stat].Total,
                startingHappy: Math.max(0, statConstants.Happy.Total - (i * statConstants.Gym.Happy)),
                gymMultiplier: statConstants.Gym[stat],
                energyPerTrain: statConstants.Gym.Energy,
                perkMultiplier: statConstants[stat].Perk
            });

            totalGains += gains;
        }

        statConstants[stat].Gain = totalGains;

        const button = document.querySelector(`[class*=${stat.toLowerCase()}_] .torn-btn`)
        if(!button) {
            alert("Could not find train button");
            return
        }
        const lock = checkRequirements(stat)

        if (lock) {
            console.log("Disabling",stat);
            button.disabled = true;
        }

        $(input).on("change", () => calculateAllTrainingGains());
        $(button).on("click", () => calculateAllTrainingGains());

    }
}

function checkRequirements(stat) {
    const stats = {
        strength: statConstants.Strength.Total,
        speed: statConstants.Speed.Total,
        dexterity: statConstants.Dexterity.Total,
        defense: statConstants.Defense.Total,
    };

    stats.strength += statConstants.Strength?.Gain || 0;
    stats.speed += statConstants.Speed?.Gain || 0;
    stats.dexterity += statConstants.Dexterity?.Gain || 0;
    stats.defense += statConstants.Defense?.Gain || 0;

    const gyms = statConstants.Gym.Keep;
    const secondStat = secondHighest(stats)

    let lock;
    for (const gym of gyms) {
        lock = true;

        switch (gym) {
            case 'Balboas Gym':
                if (stat == "Defense" || stat == "Dexterity") lock = false;
                else if ((stats.defense + stats.dexterity) >= 1.25 * (stats.speed + stats.strength)) {
                    lock = false;
                }
                break;
            case 'Frontline Fitness':
                if (stat == "Speed" || stat == "Strength") lock = false;
                else if ((stats.speed + stats.strength) >= 1.25 * (stats.defense + stats.dexterity)) {
                    lock = false;
                }
                break;
            case 'Gym 3000':
                if (stat == "Strength") lock = false;
                else if (stats.strength >= 1.25 * secondStat.value) {
                    lock = false;
                } else if (stat.toLowerCase() !== secondStat.name) {
                    lock = false;
                }
                break;
            case 'Mr. Isoyamas':
                if (stat == "Defense") lock = false;
                else if (stats.defense >= 1.25 * secondStat.value) {
                    lock = false;
                } else if (stat.toLowerCase() !== secondStat.name) {
                    lock = false;
                }
                break;
            case 'Total Rebound':
                if (stat == "Speed") lock = false;
                else if (stats.speed >= 1.25 * secondStat.value) {
                    lock = false;
                } else if (stat.toLowerCase() !== secondStat.name) {
                    lock = false;
                }
                break;
            case 'Elites':
                if (stat == "Dexterity") lock = false;
                else if (stats.dexterity >= 1.25 * secondStat.value) {
                    lock = false;
                } else if (stat.toLowerCase() !== secondStat.name) {
                    lock = false;
                }
                break;
            default:
                lock = true;
                break;
        }

        if (lock) break;
    }

    return lock;
}

calculateAllTrainingGains();

///////////////////////////////////////////////////////////////////////
////////////////////////////   UTILITY   //////////////////////////////
///////////////////////////////////////////////////////////////////////

function waitForEl(selector, timeout = 5000, delay = 1000) {
    return new Promise((resolve, reject) => {
        const observer = new MutationObserver((mutationsList, observer) => {
            const element = document.querySelector(selector);
            if (element) {
                observer.disconnect();
                setTimeout(() => resolve(element), delay);
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });

        setTimeout(() => {
            observer.disconnect();
            reject(new Error(`Element with selector "${selector}" not found within the time limit`));
        }, timeout);
    });
}

// Helper to fetch and parse a stat value (energy or happiness)
function getStatValue(selector) {
    const value = document.querySelector(selector)?.textContent.match(/^\d+/)?.[0] || 0;
    return value;
}

// Get the selected gym's name
function getSelectedGymName() {
    const gymElement = document.querySelector("[class*=gymButton_][class*=active_]");
    if (!gymElement) {
        alert("Could not find selected gym");
        return null;
    }

    const aria = gymElement?.getAttribute("aria-label")?.match(/^(.+?)\./)?.[1].trim();
    if (!aria) {
        alert("Could not find gym's aria label")
        return null
    }

    return aria;
}

// Get gym stats by name
function getGymStatsByName(name) {
    for (const gym in gyms) {
        if(gyms[gym].name === name) {
            return gyms[gym];
            break;
        }
    }

    return null
}

// Set values for each stat based on the gym's capabilities
function setStatValues(gymStats) {
    let totalStats = 0;

    for (const stat of statNames) {
        const statDiv = document.querySelector(`[class*=${stat.toLowerCase()}_]`);
        if (!statDiv) {
            alert(`Could not find ${stat}.`);
            continue;
        }

        const value = parseInt(statDiv?.querySelector("[class*=propertyValue_]")?.textContent?.replace(/,/g, "") || -1, 10);
        if(value == -1) {
            alert("Could not find number of trains input")
        }
        const perk = parseFloat(statDiv?.querySelector(".tt-gym-steadfast")?.getAttribute("data-total") || 0);

        statConstants[stat].Total = value;
        statConstants[stat].Perk = perk;

        statConstants.Gym[stat] = gymStats[stat.toLowerCase()] / 10;
        totalStats += value;
    }

    for (const stat of statNames) {
        if (statConstants[stat].Total && totalStats) {
            statConstants[stat].Percent = (statConstants[stat].Total / totalStats) * 100;
        } else {
            statConstants[stat].Percent = 0;
        }
    }

    console.log("StatConstants", statConstants);
}

function secondHighest(statObj) {
    const sortedStats = Object.entries(statObj)
        .sort(([, aValue], [, bValue]) => bValue - aValue); // Sort by value, descending

    const [name, value] = sortedStats[1]; // Get the second entry (index 1)
    return { name, value };
}

////////////////////////////////////////////////////////////////////////
////////////////////////////   GYM DATA   //////////////////////////////
////////////////////////////////////////////////////////////////////////

const gyms = {
    "1": {
        "name": "Premier Fitness",
        "energy": 5,
        "strength": 20,
        "speed": 20,
        "defense": 20,
        "dexterity": 20,
    },
    "2": {
        "name": "Average Joes",
        "energy": 5,
        "strength": 24,
        "speed": 24,
        "defense": 27,
        "dexterity": 24,
    },
    "3": {
        "name": "Woody's Workout Club",
        "energy": 5,
        "strength": 27,
        "speed": 32,
        "defense": 30,
        "dexterity": 27,
    },
    "4": {
        "name": "Beach Bods",
        "energy": 5,
        "strength": 32,
        "speed": 32,
        "defense": 32,
        "dexterity": 0,
    },
    "5": {
        "name": "Silver Gym",
        "energy": 5,
        "strength": 34,
        "speed": 36,
        "defense": 34,
        "dexterity": 32,
    },
    "6": {
        "name": "Pour Femme",
        "energy": 5,
        "strength": 34,
        "speed": 36,
        "defense": 36,
        "dexterity": 38,
    },
    "7": {
        "name": "Davies Den",
        "energy": 5,
        "strength": 37,
        "speed": 0,
        "defense": 37,
        "dexterity": 37,
    },
    "8": {
        "name": "Global Gym",
        "energy": 5,
        "strength": 40,
        "speed": 40,
        "defense": 40,
        "dexterity": 40,
    },
    "9": {
        "name": "Knuckle Heads",
        "energy": 10,
        "strength": 48,
        "speed": 44,
        "defense": 40,
        "dexterity": 42,
    },
    "10": {
        "name": "Pioneer Fitness",
        "energy": 10,
        "strength": 44,
        "speed": 46,
        "defense": 48,
        "dexterity": 44,
    },
    "11": {
        "name": "Anabolic Anomalies",
        "energy": 10,
        "strength": 50,
        "speed": 46,
        "defense": 52,
        "dexterity": 46,
    },
    "12": {
        "name": "Core",
        "energy": 10,
        "strength": 50,
        "speed": 52,
        "defense": 50,
        "dexterity": 50,
    },
    "13": {
        "name": "Racing Fitness",
        "energy": 10,
        "strength": 50,
        "speed": 54,
        "defense": 48,
        "dexterity": 52,
    },
    "14": {
        "name": "Complete Cardio",
        "energy": 10,
        "strength": 55,
        "speed": 57,
        "defense": 55,
        "dexterity": 52,
    },
    "15": {
        "name": "Legs, Bums and Tums",
        "energy": 10,
        "strength": 0,
        "speed": 55,
        "defense": 55,
        "dexterity": 57,
    },
    "16": {
        "name": "Deep Burn",
        "energy": 10,
        "strength": 60,
        "speed": 60,
        "defense": 60,
        "dexterity": 60,
    },
    "17": {
        "name": "Apollo Gym",
        "energy": 10,
        "strength": 60,
        "speed": 62,
        "defense": 64,
        "dexterity": 62,
    },
    "18": {
        "name": "Gun Shop",
        "energy": 10,
        "strength": 65,
        "speed": 64,
        "defense": 62,
        "dexterity": 62,
    },
    "19": {
        "name": "Force Training",
        "energy": 10,
        "strength": 64,
        "speed": 65,
        "defense": 64,
        "dexterity": 68,
    },
    "20": {
        "name": "Cha Cha's",
        "energy": 10,
        "strength": 64,
        "speed": 64,
        "defense": 68,
        "dexterity": 70,
    },
    "21": {
        "name": "Atlas",
        "energy": 10,
        "strength": 70,
        "speed": 64,
        "defense": 64,
        "dexterity": 65,
    },
    "22": {
        "name": "Last Round",
        "energy": 10,
        "strength": 68,
        "speed": 65,
        "defense": 70,
        "dexterity": 65,
    },
    "23": {
        "name": "The Edge",
        "energy": 10,
        "strength": 68,
        "speed": 70,
        "defense": 70,
        "dexterity": 68,
    },
    "24": {
        "name": "George's",
        "energy": 10,
        "strength": 73,
        "speed": 73,
        "defense": 73,
        "dexterity": 73,
    },
    "25": {
        "name": "Balboas Gym",
        "energy": 25,
        "strength": 0,
        "speed": 0,
        "defense": 75,
        "dexterity": 75,
    },
    "26": {
        "name": "Frontline Fitness",
        "energy": 25,
        "strength": 75,
        "speed": 75,
        "defense": 0,
        "dexterity": 0,
    },
    "27": {
        "name": "Gym 3000",
        "energy": 50,
        "strength": 80,
        "speed": 0,
        "defense": 0,
        "dexterity": 0,
    },
    "28": {
        "name": "Mr. Isoyamas",
        "energy": 50,
        "strength": 0,
        "speed": 0,
        "defense": 80,
        "dexterity": 0,
    },
    "29": {
        "name": "Total Rebound",
        "energy": 50,
        "strength": 0,
        "speed": 80,
        "defense": 0,
        "dexterity": 0,
    },
    "30": {
        "name": "Elites",
        "energy": 50,
        "strength": 0,
        "speed": 0,
        "defense": 0,
        "dexterity": 80,
    },
    "31": {
        "name": "The Sports Science Lab",
        "energy": 25,
        "strength": 90,
        "speed": 90,
        "defense": 90,
        "dexterity": 90,
    },
    "32": {
        "name": "Unknown",
        "energy": 10,
        "strength": 100,
        "speed": 100,
        "defense": 100,
        "dexterity": 100,
    },
    "33": {
        "name": "The Jail Gym",
        "energy": 5,
        "strength": 34,
        "speed": 34,
        "defense": 46,
        "dexterity": 0,
    }
}