War End

Adds a toggle-able side bar to estimate War End for a given faction.

// ==UserScript==
// @name         War End
// @version      Beta1.6
// @namespace    https://greasyfork.org/
// @description  Adds a toggle-able side bar to estimate War End for a given faction.
// @author       Gravity0000
// @supportURL   https://www.torn.com/profiles.php?XID=2131364
// @license      MIT
// @match        https://www.torn.com/*
// @grant        GM_addStyle
// ==/UserScript==


(function () {
    'use strict';

    const styleTag = document.createElement('style');
    document.head.appendChild(styleTag);
    const isDarkMode = document.body.classList.contains('dark-mode');
    let placeholderColor = isDarkMode ? '#bbbbbb' : '#555555';
    function updatePlaceholderStyle() {
        styleTag.textContent = `
            .leadBox::placeholder {
                color: ${placeholderColor} !important;
            }
        `;
    }

    const container = document.createElement('div');
    container.id = 'myScriptContainer';
    container.style.display = 'none';
    document.body.appendChild(container);

    const infoBox = document.createElement('div');
    infoBox.id = 'myInfoBox';
    infoBox.innerHTML = 'Initial Info';
    container.appendChild(infoBox);

    const leadBox = document.createElement('input');
    leadBox.type = 'number';
    leadBox.id = 'myLeadBox';
    leadBox.placeholder = 'Est. Lead or Blank';
    leadBox.classList.add('leadBox');
    container.appendChild(leadBox);

    leadBox.addEventListener('keypress', event => {
        const key = event.keyCode || event.which;
        if (!((key >= 48 && key <= 57) || key === 8)) event.preventDefault();
    });

    const toggleButton = document.createElement('button');
    toggleButton.innerText = 'WarEnd';
    toggleButton.id = 'myToggleButton';
    Object.assign(toggleButton.style, {
        position: 'fixed',
        top: '65%',
        right: '0%',
        zIndex: '9999',
        cursor: 'pointer'
    });
    document.body.appendChild(toggleButton);
    toggleButton.addEventListener('click', () => {
        container.style.display = container.style.display === 'none' ? '' : 'none';
    });

    const updateButton = document.createElement('button');
    updateButton.id = 'myUpdateButton';
    updateButton.textContent = 'Update';
    container.appendChild(updateButton);

    function applyThemeColors(isDark) {
        const textColor = isDark ? 'white' : 'black';
        const bgColor = isDark ? 'black' : 'white';
        const containerBg = isDark ? '#A9A9A9' : '#D3D3D3';
        const buttonBg = isDark ? '#009407' : '#98f59b';
        const borderColor = isDark ? 'white' : 'black';
        [infoBox, leadBox, toggleButton, updateButton].forEach(el => el.style.color = textColor);
        [infoBox, leadBox].forEach(el => el.style.backgroundColor = bgColor);
        [toggleButton, updateButton].forEach(el => {
            el.style.border = `1px solid ${borderColor}`;
            el.style.backgroundColor = buttonBg;
        });
        container.style.backgroundColor = containerBg;
        placeholderColor = isDark ? '#FFFFFF' : '#000000';
        updatePlaceholderStyle();
    }

    const observer = new MutationObserver(() => {
        applyThemeColors(document.body.classList.contains('dark-mode'));
    });
    observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });

    updateButton.addEventListener('click', () => {
        const leadInput = parseInt(leadBox.value, 10);
        const noWar = document.querySelector('.f-msg .title')?.textContent.includes('NOT IN A WAR');
        const timerElem = document.querySelector('.infoBlock___bb_KF.timer___fSGg8');

        if (noWar && !timerElem) {
            infoBox.innerHTML = 'No current war';
            return;
        }

        if (!noWar && timerElem) {
            const spans = timerElem.querySelectorAll('span');
            const days = parseInt(spans[0].textContent + spans[1].textContent, 10);
            const hours = parseInt(spans[3].textContent + spans[4].textContent, 10);
            const minutes = parseInt(spans[6].textContent + spans[7].textContent, 10);
            const seconds = parseInt(spans[9].textContent + spans[10].textContent, 10);

            const targetElem = document.querySelector('.scoreBlock___Pr3xV .target___NBVXq');
            const scoreElem = document.querySelector('.scoreBlock___Pr3xV .right.scoreText___uVRQm.currentFaction___Omz6o');

            if (!targetElem || !scoreElem) {
                infoBox.innerHTML = 'War not started';
                return;
            }

            const match = targetElem.textContent.match(/(\d{1,3}(?:,\d{3})*|\d+)\s*\/\s*(\d{1,3}(?:,\d{3})*|\d+)/);
            if (!match) {
                infoBox.innerHTML = 'Unable to parse target lead';
                return;
            }

            const currentLead = parseInt(match[1].replace(/,/g, ''), 10);
            const targetLead = parseInt(match[2].replace(/,/g, ''), 10);
            const hypotheticalLead = !isNaN(leadInput) ? leadInput : currentLead;

            const elapsedMs = (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000;
            const nowMs = Date.now();
            const warStartMs = nowMs - elapsedMs;

            const elapsedHoursTotal = days * 24 + hours + minutes / 60 + seconds / 3600;

            const scoreGap = Math.max(0, targetLead - hypotheticalLead);

            let timeLeftHours = 0;

            if (scoreGap <= 0) {
                timeLeftHours = 0;
            } else {
                if (elapsedHoursTotal < 24) {
                    // Decay has not started yet, first tick at 24h
                    const decayPerHour = targetLead / 100; // fixed for first 100 ticks
                    const hoursUntilFirstTick = 24 - elapsedHoursTotal;
                    const remainingScore = scoreGap;
                    const additionalHours = Math.ceil(remainingScore / decayPerHour);
                    timeLeftHours = hoursUntilFirstTick + additionalHours;
                } else {
                    // Decay has started
                    const fullElapsedHours = Math.floor(elapsedHoursTotal);
                    const remainingDecayHours = 123 - fullElapsedHours;
                    const decayPerHour = targetLead / remainingDecayHours;
                    timeLeftHours = Math.ceil(scoreGap / decayPerHour) - (elapsedHoursTotal - fullElapsedHours);
                }
            }

            const dispDays = Math.floor(timeLeftHours / 24);
            const dispHours = Math.floor(timeLeftHours % 24);
            const dispMins = Math.round((timeLeftHours % 1) * 60);

            let timeLeftStr;
            if (dispDays > 0) {
                timeLeftStr = `${dispDays}d ${dispHours}h ${dispMins}m`;
            } else if (dispHours > 0) {
                timeLeftStr = `${dispHours}h ${dispMins}m`;
            } else {
                timeLeftStr = `${dispMins}m`;
            }

            const endMs = nowMs + timeLeftHours * 3600000;
const endDate = new Date(endMs);

// Round up to next full minute
if (endDate.getSeconds() > 0 || endDate.getMilliseconds() > 0) {
    endDate.setMinutes(endDate.getMinutes() + 1);
}
endDate.setSeconds(0, 0);

const endHours = String(endDate.getUTCHours()).padStart(2, '0');
const endMinutes = String(endDate.getUTCMinutes()).padStart(2, '0');
            let tctStr = `End Time TCT:<br>${endHours}:${endMinutes}`;
            if (dispDays > 0) tctStr = `End Time TCT:<br>In ${dispDays}D at ${endHours}:${endMinutes}`;

            infoBox.innerHTML = `Time Left:<br>${timeLeftStr}<br><br>${tctStr}`;
        } else {
            infoBox.innerHTML = 'War not started';
        }
    });

    GM_addStyle(`
        #myScriptContainer {
            position: fixed;
            width: 150px;
            top: 70%;
            right: 0%;
            background-color: #f0f0f0;
            border: 1px solid #ccc;
            padding: 10px;
            z-index: 9999;
            display: flex;
            flex-direction: column;
            gap: 5px;
        }
        #myLeadBox {
            padding: 5px;
            border: 1px solid #ddd;
        }
        #myInfoBox {
            color: black;
            padding: 5px;
            text-align: center;
            background-color: #f2f2f2;
        }
        #myUpdateButton, #myToggleButton {
            padding: 5px 10px;
            cursor: pointer;
        }
    `);

    // Apply initial theme
    applyThemeColors(isDarkMode);
})();