Torn Execute Tracker

Inline execute perk tracker - injects directly into Torn's attack UI

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Torn Execute Tracker
// @namespace    torn-execute-tracker
// @version      2.1
// @description  Inline execute perk tracker - injects directly into Torn's attack UI
// @author       Solenya [4053619]
// @match        https://www.torn.com/page.php?sid=attack*
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    let executeThreshold = parseFloat(GM_getValue('et_threshold', 25));

    function detectExecuteBonus() {
        // 1. Check by class name first (most reliable)
        const byClass = document.querySelector('[class*="bonus-attachment-execute"]');
        if (byClass) {
            const desc = byClass.getAttribute('data-bonus-attachment-description') || '';
            const match = desc.match(/(\d+)%/);
            if (match) return parseInt(match[1]);
        }
        const bonusEls = document.querySelectorAll('[data-bonus-attachment-description]');
        for (const el of bonusEls) {
            const desc = el.getAttribute('data-bonus-attachment-description') || '';
            const match = desc.match(/execute\D+(\d+)%/i) || desc.match(/(\d+)%\D+execute/i);
            if (match) return parseInt(match[1]);
        }
        return null;
    }

    function getEnemyHealth() {
        const enemyWrapper = document.querySelector('[class*="headerWrapper"][class*="rose"]');
        if (!enemyWrapper) return null;
        const healthIcon = enemyWrapper.querySelector('[class*="iconHealth"]');
        if (!healthIcon) return null;
        const span = healthIcon.nextElementSibling;
        if (!span) return null;
        const text = span.textContent.trim();
        const match = text.match(/([\d,]+)\s*\/\s*([\d,]+)/);
        if (!match) return null;
        return {
            current: parseInt(match[1].replace(/,/g, '')),
            max:     parseInt(match[2].replace(/,/g, '')),
        };
    }

    const style = document.createElement('style');
    style.textContent = `
        /* Tick mark on the health bar */
        #et-tick {
            position: absolute;
            top: 0;
            width: 2px;
            height: 100%;
            background: #58a6ff;
            border-radius: 1px;
            pointer-events: none;
            z-index: 10;
            transition: left 0.2s;
        }

        /* Container injected below the health bar */
        #et-inline-wrap {
            padding: 4px 10px 5px 10px;
            display: flex;
            flex-direction: column;
            gap: 3px;
        }

        /* Status row: current % on left, threshold badge on right */
        #et-status-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            font-size: 11px;
        }

        #et-pct-label {
            color: #8b949e;
            font-weight: 500;
            font-family: 'Segoe UI', system-ui, sans-serif;
            min-width: 38px;
        }

        #et-threshold-badge {
            display: flex;
            align-items: center;
            gap: 4px;
            background: rgba(88,166,255,0.1);
            border: 1px solid rgba(88,166,255,0.25);
            border-radius: 4px;
            padding: 1px 6px;
            font-size: 10px;
            color: #58a6ff;
            font-family: 'Segoe UI', system-ui, sans-serif;
            cursor: pointer;
            user-select: none;
            transition: background 0.15s, border-color 0.15s;
        }
        #et-threshold-badge:hover {
            background: rgba(88,166,255,0.2);
            border-color: rgba(88,166,255,0.5);
        }
        #et-threshold-badge svg {
            opacity: 0.6;
            flex-shrink: 0;
        }

        /* Execute alert — hidden by default */
        #et-execute-alert {
            display: none;
            padding: 5px 10px;
            background: #da3633;
            border-radius: 0 0 4px 4px;
            text-align: center;
            font-weight: 700;
            font-size: 12px;
            letter-spacing: 0.5px;
            color: #fff;
            font-family: 'Segoe UI', system-ui, sans-serif;
            animation: et-pulse 0.55s ease-in-out infinite alternate;
        }
        #et-execute-alert.et-visible { display: block; }
        @keyframes et-pulse {
            from { background: #da3633; }
            to   { background: #f85149; }
        }

        /* Settings panel */
        #et-settings {
            display: none;
            align-items: center;
            gap: 5px;
            padding: 4px 0 2px 0;
            flex-wrap: wrap;
        }
        #et-settings.et-open { display: flex; }

        #et-settings label {
            font-size: 11px;
            color: #8b949e;
            font-family: 'Segoe UI', system-ui, sans-serif;
            white-space: nowrap;
        }
        #et-threshold-input {
            width: 46px;
            background: rgba(255,255,255,0.06);
            border: 1px solid #30363d;
            color: #e6edf3;
            border-radius: 4px;
            padding: 2px 5px;
            font-size: 12px;
            text-align: center;
            font-family: 'Segoe UI', system-ui, sans-serif;
        }
        #et-threshold-input:focus {
            outline: none;
            border-color: #58a6ff;
        }

        #et-save-btn, #et-detect-btn {
            background: rgba(255,255,255,0.06);
            border: 1px solid #30363d;
            color: #c9d1d9;
            border-radius: 4px;
            padding: 2px 7px;
            font-size: 11px;
            cursor: pointer;
            font-family: 'Segoe UI', system-ui, sans-serif;
            transition: background 0.15s, border-color 0.15s;
            white-space: nowrap;
        }
        #et-save-btn:hover { background: #1f6feb; border-color: #1f6feb; color: #fff; }
        #et-detect-btn:hover { background: rgba(255,255,255,0.1); border-color: #484f58; }

        #et-settings-msg {
            font-size: 10px;
            color: #d29922;
            font-family: 'Segoe UI', system-ui, sans-serif;
            width: 100%;
        }

        /* Make the Torn health bar wrap position:relative so the tick works */
        [class*="pbWrap"] {
            position: relative !important;
        }
    `;
    document.head.appendChild(style);

    // ─── Build + inject inline UI into the enemy header ────────────────────────
    let injected = false;

    function injectUI() {
        if (injected) return true;

        const bottomSection = document.querySelector('[class*="bottomSection"]');
        if (!bottomSection) return false;

        // Try to place tick on enemy health bar if it already exists
        const pbWrap = document.querySelector('[class*="headerWrapper"][class*="rose"] [class*="pbWrap"]');
        if (pbWrap && !document.getElementById('et-tick')) {
            const tick = document.createElement('div');
            tick.id = 'et-tick';
            tick.style.left = `${executeThreshold}%`;
            pbWrap.appendChild(tick);
        }

        const wrap = document.createElement('div');
        wrap.id = 'et-inline-wrap';
        wrap.innerHTML = `
            <div id="et-status-row">
                <span id="et-pct-label">—</span>
                <span id="et-threshold-badge" title="Click to change threshold">
                    <svg width="9" height="9" viewBox="0 0 16 16" fill="currentColor">
                      <path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"/>
                      <path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.474l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/>
                    </svg>
                    Execute ≤ ${executeThreshold}%
                </span>
            </div>
            <div id="et-settings">
                <label>Threshold %</label>
                <input id="et-threshold-input" type="number" min="1" max="99" value="${executeThreshold}">
                <button id="et-detect-btn">Auto-detect</button>
                <button id="et-save-btn">Save</button>
                <span id="et-settings-msg"></span>
            </div>
            <div id="et-execute-alert">⚔ EXECUTE!</div>
        `;
        bottomSection.appendChild(wrap);

        // ── Wire up settings toggle
        document.getElementById('et-threshold-badge').addEventListener('click', () => {
            document.getElementById('et-settings').classList.toggle('et-open');
            document.getElementById('et-settings-msg').textContent = '';
        });

        // ── Save
        document.getElementById('et-save-btn').addEventListener('click', () => {
            const val = parseFloat(document.getElementById('et-threshold-input').value);
            const msg = document.getElementById('et-settings-msg');
            if (isNaN(val) || val <= 0 || val >= 100) {
                msg.style.color = '#f85149';
                msg.textContent = '⚠ Enter 1–99';
                return;
            }
            executeThreshold = val;
            GM_setValue('et_threshold', val);
            document.getElementById('et-tick').style.left = `${val}%`;
            document.getElementById('et-threshold-badge').lastChild.textContent = ` Execute ≤ ${val}%`;
            msg.style.color = '#238636';
            msg.textContent = '✓ Saved';
            updateDisplay();
            setTimeout(() => {
                document.getElementById('et-settings').classList.remove('et-open');
            }, 700);
        });

        // ── Auto-detect
        document.getElementById('et-detect-btn').addEventListener('click', () => {
            const msg = document.getElementById('et-settings-msg');
            const detected = detectExecuteBonus();
            if (detected !== null) {
                document.getElementById('et-threshold-input').value = detected;
                msg.style.color = '#238636';
                msg.textContent = `Detected: ${detected}%`;
            } else {
                msg.style.color = '#f85149';
                msg.textContent = 'No execute bonus found';
            }
        });

        injected = true;
        return true;
    }

    // ─── Update the inline display ─────────────────────────────────────────────
    function updateDisplay() {
        if (!injected) return;

        const hp = getEnemyHealth();
        const pctEl   = document.getElementById('et-pct-label');
        const alertEl = document.getElementById('et-execute-alert');
        if (!pctEl || !alertEl) return;

        if (!hp || hp.max === 0) {
            pctEl.textContent = '—';
            alertEl.classList.remove('et-visible');
            return;
        }

        const pct = (hp.current / hp.max) * 100;
        pctEl.textContent = pct.toFixed(1) + '%';

        if (pct <= executeThreshold) {
            alertEl.classList.add('et-visible');
            pctEl.style.color = '#f85149';
        } else {
            alertEl.classList.remove('et-visible');
            pctEl.style.color = pct <= executeThreshold * 1.5 ? '#d29922' : '#8b949e';
        }
    }

    // ─── Targeted observer on enemy HP span only ───────────────────────────────
    let activeObserver = null;

    function attachObserver() {
        if (activeObserver) { activeObserver.disconnect(); activeObserver = null; }
        const enemyWrapper = document.querySelector('[class*="headerWrapper"][class*="rose"]');
        if (!enemyWrapper) return false;
        const healthIcon = enemyWrapper.querySelector('[class*="iconHealth"]');
        if (!healthIcon) return false;
        const span = healthIcon.nextElementSibling;
        if (!span) return false;
        activeObserver = new MutationObserver(updateDisplay);
        activeObserver.observe(span, { characterData: true, childList: true, subtree: true });
        return true;
    }

    // ─── Init loop: wait for enemy panel, inject, attach observer ─────────────
    const initInterval = setInterval(() => {
        if (injectUI()) {
            clearInterval(initInterval);
            attachObserver();
            updateDisplay();

            // Auto-detect execute bonus once weapons are rendered
            setTimeout(() => {
                const detected = detectExecuteBonus();
                if (detected !== null && detected !== executeThreshold) {
                    executeThreshold = detected;
                    GM_setValue('et_threshold', detected);
                    const tick  = document.getElementById('et-tick');
                    const badge = document.getElementById('et-threshold-badge');
                    const input = document.getElementById('et-threshold-input');
                    if (tick)  tick.style.left = `${detected}%`;
                    if (input) input.value = detected;
                    if (badge) badge.lastChild.textContent = ` Execute ≤ ${detected}%`;
                    updateDisplay();
                } else if (detected === null) {
                    // No execute bonus — open settings so user can set manually
                    const settings = document.getElementById('et-settings');
                    const msg      = document.getElementById('et-settings-msg');
                    if (settings) settings.classList.add('et-open');
                    if (msg) { msg.style.color = '#d29922'; msg.textContent = '⚠ Set % manually'; }
                }
            }, 1500);
        }
    }, 500);

    // Re-check every 4s in case a new fight starts (panel gets replaced in DOM)
    setInterval(() => {
        if (!activeObserver || !document.getElementById('et-inline-wrap')) {
            injected = false;
            injectUI();
            attachObserver();
        }
        // Re-try tick if enemy panel appeared after the initial inject
        if (!document.getElementById('et-tick')) {
            const pbWrap = document.querySelector('[class*="headerWrapper"][class*="rose"] [class*="pbWrap"]');
            if (pbWrap) {
                const tick = document.createElement('div');
                tick.id = 'et-tick';
                tick.style.left = `${executeThreshold}%`;
                pbWrap.appendChild(tick);
            }
        }
        updateDisplay();
    }, 4000);

})();