Energy To Burn

Calculate optimal energy to burn before Xanax cooldown is ready

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Energy To Burn
// @namespace    TornScripts.EnergyBurn
// @license      GPL-3.0
// @version      1.0.1
// @description  Calculate optimal energy to burn before Xanax cooldown is ready
// @author       ButtChew [3840391]
// @icon         https://www.torn.com/favicon.ico
// @homepageURL  https://greasyfork.org/en/scripts/555959-energy-to-burn
// @match        https://www.torn.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @connect      api.torn.com
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // ========== SETTINGS ==========
    const SETTINGS = {
        apiKey: GM_getValue('tornApiKey', ''),
        gymEnergyCost: GM_getValue('gymEnergyCost', 10)
    };

    // Register Tampermonkey menu commands
    GM_registerMenuCommand('⚙️ Energy Burn Settings', openSettingsModal);

    // ========== UTILITY FUNCTIONS ==========
    function formatTime(seconds) {
        if (seconds <= 0) return 'Ready!';
        const hours = Math.floor(seconds / 3600);
        const minutes = Math.floor((seconds % 3600) / 60);
        const secs = seconds % 60;

        if (hours > 0) {
            return `${hours}h ${minutes}m`;
        } else if (minutes > 0) {
            return `${minutes}m ${secs}s`;
        } else {
            return `${secs}s`;
        }
    }

    function formatNumber(num) {
        return num.toLocaleString('en-US');
    }

    // ========== API FUNCTIONS ==========
    function fetchTornAPI(endpoint, callback) {
        const apiKey = GM_getValue('tornApiKey', '');

        if (!apiKey) {
            showError('API Key not set. Please configure in Tampermonkey menu: Energy Burn Settings');
            return;
        }

        const url = `https://api.torn.com/v2/user/${endpoint}?key=${apiKey}`;

        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            onload: function(response) {
                if (response.status === 200) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (data.error) {
                            showError(`API Error: ${data.error.error}`);
                        } else {
                            callback(data);
                        }
                    } catch (e) {
                        showError('Failed to parse API response');
                    }
                } else {
                    showError(`API request failed: ${response.status}`);
                }
            },
            onerror: function() {
                showError('Network error while fetching API data');
            }
        });
    }

    // ========== CALCULATION LOGIC ==========
    function calculateEnergyBurn(energyData, cooldownData) {
        const currentEnergy = energyData.bars.energy.current;
        const maxEnergy = energyData.bars.energy.maximum;
        const energyIncrement = energyData.bars.energy.increment;
        const energyInterval = energyData.bars.energy.interval;
        const energyFullTime = energyData.bars.energy.full_time;
        const energyTickTime = energyData.bars.energy.tick_time;

        const xanaxCooldown = cooldownData.cooldowns.drug;
        const gymEnergyCost = GM_getValue('gymEnergyCost', 10);

        // If Xanax is ready or will be ready before energy is full
        if (xanaxCooldown <= energyFullTime) {
            return {
                currentEnergy: currentEnergy,
                maxEnergy: maxEnergy,
                energyFullTime: energyFullTime,
                xanaxCooldown: xanaxCooldown,
                energyToBurn: 0,
                actionsToDo: 0,
                actualBurn: 0,
                gymEnergyCost: gymEnergyCost,
                message: 'No energy burn needed! Xanax will be ready before or when energy is full.'
            };
        }

        // Calculate optimal energy to burn
        const optimalEnergyToBurn = (xanaxCooldown * energyIncrement / energyInterval) + currentEnergy - maxEnergy;

        // Round down to gym increment (never over-burn)
        const actionsToDo = Math.floor(optimalEnergyToBurn / gymEnergyCost);
        const actualBurn = actionsToDo * gymEnergyCost;

        // Check if user has enough energy to burn right now
        const canBurnNow = currentEnergy >= actualBurn;

        // Calculate time until user has enough energy to burn
        let timeUntilCanBurn = 0;
        if (!canBurnNow && actualBurn > 0) {
            const energyNeededToBurn = actualBurn - currentEnergy;
            const ticksNeededToBurn = Math.ceil(energyNeededToBurn / energyIncrement);
            // First tick happens at tick_time, remaining ticks at interval
            timeUntilCanBurn = energyTickTime + ((ticksNeededToBurn - 1) * energyInterval);
        }

        // Calculate new full time after burning
        // If can't burn now, assume we burn at 0 (after waiting to regenerate the needed amount)
        const newEnergy = canBurnNow ? currentEnergy - actualBurn : 0;
        const energyNeeded = maxEnergy - newEnergy;
        const ticksNeeded = Math.ceil(energyNeeded / energyIncrement);
        const newFullTime = canBurnNow ? ticksNeeded * energyInterval : (timeUntilCanBurn + ticksNeeded * energyInterval);

        return {
            currentEnergy: currentEnergy,
            maxEnergy: maxEnergy,
            energyFullTime: energyFullTime,
            xanaxCooldown: xanaxCooldown,
            energyToBurn: optimalEnergyToBurn,
            actionsToDo: actionsToDo,
            actualBurn: actualBurn,
            newEnergy: newEnergy,
            newFullTime: newFullTime,
            gymEnergyCost: gymEnergyCost,
            timeDifference: Math.abs(newFullTime - xanaxCooldown),
            canBurnNow: canBurnNow,
            timeUntilCanBurn: timeUntilCanBurn,
            message: null
        };
    }

    // ========== UI FUNCTIONS ==========
    function addSidebarLink() {
        // Wait for sidebar to load
        const checkSidebar = setInterval(() => {
            const areasSection = document.querySelector('.areas___ElnyB .toggle-content___BJ9Q9');
            if (areasSection && !document.getElementById('nav-energy_burn')) {
                clearInterval(checkSidebar);

                // Create the energy burn link
                const energyBurnDiv = document.createElement('div');
                energyBurnDiv.className = 'area-desktop___bpqAS';
                energyBurnDiv.id = 'nav-energy_burn';
                energyBurnDiv.innerHTML = `
                    <div class="area-row___iBD8N">
                        <a href="#" class="desktopLink___SG2RU" id="energy-burn-link">
                            <span class="svgIconWrap___AMIqR">
                                <span class="defaultIcon___iiNis mobile___paLva">
                                    <svg xmlns="http://www.w3.org/2000/svg" stroke="transparent" stroke-width="0" width="16" height="20" viewBox="0 0 16 20">
                                        <path d="M8.5,0L0,10h6l-2,10l10-12H8L8.5,0z" fill="url(#sidebar_svg_gradient_regular_desktop)"/>
                                    </svg>
                                </span>
                            </span>
                            <span class="linkName___FoKha">Energy Burn</span>
                        </a>
                    </div>
                `;

                // Insert after Gym (or at end of areas)
                const gymNav = document.getElementById('nav-gym');
                if (gymNav && gymNav.nextSibling) {
                    areasSection.insertBefore(energyBurnDiv, gymNav.nextSibling);
                } else {
                    areasSection.appendChild(energyBurnDiv);
                }

                // Add click handler
                document.getElementById('energy-burn-link').addEventListener('click', (e) => {
                    e.preventDefault();
                    openEnergyBurnModal();
                });
            }
        }, 500);

        // Clear interval after 10 seconds if sidebar not found
        setTimeout(() => clearInterval(checkSidebar), 10000);
    }

    function openEnergyBurnModal() {
        // Check if API key is set
        if (!GM_getValue('tornApiKey', '')) {
            if (confirm('API Key not configured. Would you like to set it up now?')) {
                openSettingsModal();
            }
            return;
        }

        // Create modal overlay
        if (document.getElementById('energy-burn-modal')) {
            document.getElementById('energy-burn-modal').remove();
        }

        const modal = document.createElement('div');
        modal.id = 'energy-burn-modal';
        modal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.8);
            z-index: 99999;
            display: flex;
            align-items: center;
            justify-content: center;
        `;

        modal.innerHTML = `
            <div style="
                background: #2e2e2e;
                border-radius: 10px;
                width: 500px;
                max-width: 90%;
                box-shadow: 0 4px 20px rgba(0,0,0,0.5);
            ">
                <div style="
                    background: linear-gradient(to bottom, #1a1a1a, #2a2a2a);
                    padding: 15px 20px;
                    border-radius: 10px 10px 0 0;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                ">
                    <h2 style="margin: 0; color: #fff; font-size: 18px;">⚡ Energy To Burn</h2>
                    <button id="close-energy-modal" style="
                        background: none;
                        border: none;
                        color: #aaa;
                        font-size: 24px;
                        cursor: pointer;
                        padding: 0;
                        width: 30px;
                        height: 30px;
                    ">×</button>
                </div>
                <div style="
                    padding: 20px;
                    color: #ccc;
                ">
                    <div id="energy-burn-content" style="text-align: center; padding: 20px;">
                        <p style="color: #aaa;">Loading data...</p>
                        <div style="
                            border: 3px solid #f3f3f3;
                            border-radius: 50%;
                            border-top: 3px solid #3498db;
                            width: 40px;
                            height: 40px;
                            animation: spin 1s linear infinite;
                            margin: 20px auto;
                        "></div>
                    </div>
                    <div style="margin-top: 15px; text-align: center;">
                        <button id="refresh-energy-data" class="torn-btn" style="
                            background: linear-gradient(to bottom, #799427, #a3c248);
                            border: none;
                            color: white;
                            padding: 10px 20px;
                            border-radius: 5px;
                            cursor: pointer;
                            font-size: 14px;
                            margin-right: 10px;
                            display: inline-flex;
                            align-items: center;
                            justify-content: center;
                            vertical-align: middle;
                        ">🔄 Refresh</button>
                        <button id="open-settings-btn" class="torn-btn" style="
                            background: linear-gradient(to bottom, #555, #777);
                            border: none;
                            color: white;
                            padding: 10px 20px;
                            border-radius: 5px;
                            cursor: pointer;
                            font-size: 14px;
                            display: inline-flex;
                            align-items: center;
                            justify-content: center;
                            vertical-align: middle;
                        ">⚙️ Settings</button>
                    </div>
                </div>
            </div>
        `;

        // Add spinner animation
        const style = document.createElement('style');
        style.textContent = `
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
        `;
        document.head.appendChild(style);

        document.body.appendChild(modal);

        // Event listeners
        document.getElementById('close-energy-modal').addEventListener('click', () => {
            modal.remove();
        });

        document.getElementById('refresh-energy-data').addEventListener('click', () => {
            loadEnergyData();
        });

        document.getElementById('open-settings-btn').addEventListener('click', () => {
            openSettingsModal();
        });

        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.remove();
            }
        });

        // Load data
        loadEnergyData();
    }

    function loadEnergyData() {
        const contentDiv = document.getElementById('energy-burn-content');
        contentDiv.innerHTML = `
            <p style="color: #aaa;">Fetching energy data...</p>
            <div style="
                border: 3px solid #f3f3f3;
                border-radius: 50%;
                border-top: 3px solid #3498db;
                width: 40px;
                height: 40px;
                animation: spin 1s linear infinite;
                margin: 20px auto;
            "></div>
        `;

        // Fetch both APIs
        let energyData = null;
        let cooldownData = null;

        fetchTornAPI('bars', (data) => {
            energyData = data;
            checkAndDisplay();
        });

        fetchTornAPI('cooldowns', (data) => {
            cooldownData = data;
            checkAndDisplay();
        });

        function checkAndDisplay() {
            if (energyData && cooldownData) {
                displayResults(energyData, cooldownData);
            }
        }
    }

    function displayResults(energyData, cooldownData) {
        const result = calculateEnergyBurn(energyData, cooldownData);
        const contentDiv = document.getElementById('energy-burn-content');

        if (result.message) {
            // No burn needed
            contentDiv.innerHTML = `
                <div style="text-align: center; padding: 10px;">
                    <div style="font-size: 48px; margin-bottom: 10px;">✅</div>
                    <p style="font-size: 16px; color: #90EE90; font-weight: bold;">${result.message}</p>
                    <hr style="border: 1px solid #444; margin: 20px 0;">
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; text-align: left;">
                        <div>
                            <p style="color: #aaa; margin: 5px 0;">Current Energy:</p>
                            <p style="color: #fff; font-size: 18px; font-weight: bold; margin: 5px 0;">${result.currentEnergy} / ${result.maxEnergy}</p>
                        </div>
                        <div>
                            <p style="color: #aaa; margin: 5px 0;">Energy Full In:</p>
                            <p style="color: #fff; font-size: 18px; font-weight: bold; margin: 5px 0;">${formatTime(result.energyFullTime)}</p>
                        </div>
                        <div>
                            <p style="color: #aaa; margin: 5px 0;">Xanax Ready In:</p>
                            <p style="color: #fff; font-size: 18px; font-weight: bold; margin: 5px 0;">${formatTime(result.xanaxCooldown)}</p>
                        </div>
                        <div>
                            <p style="color: #aaa; margin: 5px 0;">Gym Energy Cost:</p>
                            <p style="color: #fff; font-size: 18px; font-weight: bold; margin: 5px 0;">${result.gymEnergyCost}</p>
                        </div>
                    </div>
                </div>
            `;
        } else {
            // Burn needed
            const earlyLate = result.newFullTime > result.xanaxCooldown ?
                `<span style="color: #FFB84D;">${formatTime(result.timeDifference)} after Xanax ready</span>` :
                `<span style="color: #90EE90;">${formatTime(result.timeDifference)} before Xanax ready</span>`;

            contentDiv.innerHTML = `
                <div style="padding: 10px;">
                    <div style="background: rgba(255, 107, 62, 0.2); border-left: 4px solid #EF6B3E; padding: 15px; margin-bottom: 20px; border-radius: 5px;">
                        <div style="font-size: 14px; color: #aaa; margin-bottom: 5px;">RECOMMENDED ACTION</div>
                        <div style="font-size: 24px; color: #EF6B3E; font-weight: bold;">
                            Burn ${formatNumber(result.actualBurn)} Energy
                        </div>
                        <div style="font-size: 14px; color: #ccc; margin-top: 5px;">
                            = ${result.actionsToDo} Gym Train${result.actionsToDo !== 1 ? 's' : ''} @ ${result.gymEnergyCost} energy each
                        </div>
                    </div>

                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;">
                        <div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 5px;">
                            <p style="color: #aaa; font-size: 12px; margin: 0 0 5px 0;">CURRENT ENERGY</p>
                            <p style="color: #fff; font-size: 20px; font-weight: bold; margin: 0;">${result.currentEnergy} / ${result.maxEnergy}</p>
                        </div>
                        <div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 5px;">
                            <p style="color: #aaa; font-size: 12px; margin: 0 0 5px 0;">ENERGY FULL IN</p>
                            <p style="color: #FFB84D; font-size: 20px; font-weight: bold; margin: 0;">${formatTime(result.energyFullTime)}</p>
                        </div>
                        <div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 5px;">
                            <p style="color: #aaa; font-size: 12px; margin: 0 0 5px 0;">XANAX READY IN</p>
                            <p style="color: #90EE90; font-size: 20px; font-weight: bold; margin: 0;">${formatTime(result.xanaxCooldown)}</p>
                        </div>
                        <div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 5px;">
                            <p style="color: #aaa; font-size: 12px; margin: 0 0 5px 0;">CAN BURN IN</p>
                            <p style="color: ${result.canBurnNow ? '#90EE90' : '#FFB84D'}; font-size: 20px; font-weight: bold; margin: 0;">
                                ${result.canBurnNow ? 'READY!' : formatTime(result.timeUntilCanBurn)}
                            </p>
                        </div>
                    </div>

                    <hr style="border: 1px solid #444; margin: 15px 0;">

                    <div style="background: rgba(144, 238, 144, 0.1); padding: 15px; border-radius: 5px; border-left: 4px solid #90EE90;">
                        <p style="color: #aaa; font-size: 12px; margin: 0 0 10px 0;">AFTER BURNING ${formatNumber(result.actualBurn)} ENERGY:</p>
                        <p style="color: #fff; margin: 5px 0;">Energy Full In: <strong>${formatTime(result.newFullTime)}</strong></p>
                        <p style="color: #ccc; margin: 5px 0; font-size: 13px;">Energy reaches full ${earlyLate}</p>
                    </div>

                    ${result.actualBurn < result.energyToBurn ? `
                        <div style="background: rgba(255, 184, 77, 0.1); padding: 10px; margin-top: 10px; border-radius: 5px; font-size: 12px; color: #FFB84D;">
                            ℹ️ Note: Optimal burn is ${result.energyToBurn.toFixed(1)} energy, but we round down to ${result.actualBurn} to ensure energy is full when Xanax is ready.
                        </div>
                    ` : ''}
                </div>
            `;
        }
    }

    function showError(message) {
        const contentDiv = document.getElementById('energy-burn-content');
        if (contentDiv) {
            contentDiv.innerHTML = `
                <div style="text-align: center; padding: 20px;">
                    <div style="font-size: 48px; color: #EF6B3E; margin-bottom: 10px;">⚠️</div>
                    <p style="color: #EF6B3E; font-size: 16px; font-weight: bold;">Error</p>
                    <p style="color: #ccc; margin-top: 10px;">${message}</p>
                </div>
            `;
        } else {
            alert('Energy Burn Error: ' + message);
        }
    }

    function openSettingsModal() {
        const currentApiKey = GM_getValue('tornApiKey', '');
        const currentGymCost = GM_getValue('gymEnergyCost', 10);

        const settingsModal = document.createElement('div');
        settingsModal.id = 'energy-settings-modal';
        settingsModal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.8);
            z-index: 100000;
            display: flex;
            align-items: center;
            justify-content: center;
        `;

        settingsModal.innerHTML = `
            <div style="
                background: #2e2e2e;
                border-radius: 10px;
                width: 450px;
                max-width: 90%;
                box-shadow: 0 4px 20px rgba(0,0,0,0.5);
            ">
                <div style="
                    background: linear-gradient(to bottom, #1a1a1a, #2a2a2a);
                    padding: 15px 20px;
                    border-radius: 10px 10px 0 0;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                ">
                    <h2 style="margin: 0; color: #fff; font-size: 18px;">⚙️ Energy Burn Settings</h2>
                    <button id="close-settings-modal" style="
                        background: none;
                        border: none;
                        color: #aaa;
                        font-size: 24px;
                        cursor: pointer;
                        padding: 0;
                        width: 30px;
                        height: 30px;
                    ">×</button>
                </div>
                <div style="padding: 20px; color: #ccc;">
                    <div style="margin-bottom: 20px;">
                        <label style="display: block; margin-bottom: 5px; color: #aaa; font-size: 14px;">
                            Torn API Key:
                        </label>
                        <input type="text" id="api-key-input" value="${currentApiKey}" placeholder="Enter your Torn API key" style="
                            width: 100%;
                            padding: 10px;
                            background: #1a1a1a;
                            border: 1px solid #444;
                            border-radius: 5px;
                            color: #fff;
                            font-size: 14px;
                            box-sizing: border-box;
                        ">
                        <p style="font-size: 12px; color: #888; margin-top: 5px;">
                            Requires "Minimal" API key or higher.
                        </p>
                    </div>

                    <div style="margin-bottom: 20px;">
                        <label style="display: block; margin-bottom: 5px; color: #aaa; font-size: 14px;">
                            Gym Energy Cost per Train:
                        </label>
                        <input type="number" id="gym-energy-input" value="${currentGymCost}" min="1" max="100" style="
                            width: 100%;
                            padding: 10px;
                            background: #1a1a1a;
                            border: 1px solid #444;
                            border-radius: 5px;
                            color: #fff;
                            font-size: 14px;
                            box-sizing: border-box;
                        ">
                        <p style="font-size: 12px; color: #888; margin-top: 5px;">
                            Common values: 10 (basic gyms), 20, 50 (advanced gyms)
                        </p>
                    </div>

                    <div style="text-align: right;">
                        <button id="cancel-settings" style="
                            background: linear-gradient(to bottom, #555, #777);
                            border: none;
                            color: white;
                            padding: 10px 20px;
                            border-radius: 5px;
                            cursor: pointer;
                            font-size: 14px;
                            margin-right: 10px;
                            display: inline-flex;
                            align-items: center;
                            justify-content: center;
                            vertical-align: middle;
                        ">Cancel</button>
                        <button id="save-settings" style="
                            background: linear-gradient(to bottom, #799427, #a3c248);
                            border: none;
                            color: white;
                            padding: 10px 20px;
                            border-radius: 5px;
                            cursor: pointer;
                            font-size: 14px;
                            display: inline-flex;
                            align-items: center;
                            justify-content: center;
                            vertical-align: middle;
                        ">Save Settings</button>
                    </div>
                </div>
            </div>
        `;

        document.body.appendChild(settingsModal);

        // Event listeners
        document.getElementById('close-settings-modal').addEventListener('click', () => {
            settingsModal.remove();
        });

        document.getElementById('cancel-settings').addEventListener('click', () => {
            settingsModal.remove();
        });

        document.getElementById('save-settings').addEventListener('click', () => {
            const apiKey = document.getElementById('api-key-input').value.trim();
            const gymCost = parseInt(document.getElementById('gym-energy-input').value);

            if (!apiKey) {
                alert('Please enter a valid API key');
                return;
            }

            if (gymCost < 1 || gymCost > 100) {
                alert('Gym energy cost must be between 1 and 100');
                return;
            }

            GM_setValue('tornApiKey', apiKey);
            GM_setValue('gymEnergyCost', gymCost);

            alert('Settings saved successfully!');
            settingsModal.remove();

            // Refresh data if main modal is open
            if (document.getElementById('energy-burn-modal')) {
                loadEnergyData();
            }
        });

        settingsModal.addEventListener('click', (e) => {
            if (e.target === settingsModal) {
                settingsModal.remove();
            }
        });
    }

    // ========== INITIALIZATION ==========
    function init() {
        // Wait for page to fully load
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', addSidebarLink);
        } else {
            addSidebarLink();
        }
    }

    init();
})();