Torn Weapon Effects Highlighter

Highlights and explain on hover effects.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Torn Weapon Effects Highlighter
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Highlights and explain on hover effects.
// @author       aquagloop
// @match        https://www.torn.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const weaponEffects = {
        'blindfire': 'Expends remaining ammunition in current clip with reduced accuracy',
        'burn': 'Burning DOT effect over 3 turns (45% initial damage)',
        'demoralized': '10% debuff to all opponent stats (stacks up to 5x)',
        'emasculate': 'Grants percentage of max happy on finishing hit',
        'freeze': '50% debuff to opponent speed and dexterity',
        'hazardous': 'Take percentage of damage you deal',
        'laceration': 'Devastating DOT effect over 9 turns (90% initial damage)',
        'poisoned': 'Long DOT effect over 19 turns (95% initial damage)',
        'severe burning': 'Short DOT effect over 3 turns (45% initial damage)',
        'shock': 'Causes opponent to miss next turn',
        'sleep': 'Enemy misses turns until damaged',
        'smash': 'Double damage but requires recharge turn',
        'spray': 'Empty full clip for double damage',
        'storage': 'Allows two temporary items in fight',
        'toxin': '25% debuff to random opponent stat (stacks up to 3x)',
        'achilles': 'Increased foot damage',
        'assassinate': 'Increased damage on first turn',
        'backstab': 'Double damage when opponent distracted',
        'berserk': 'Increased damage but reduced hit chance',
        'bleed': 'Bleeding DOT effect over 9 turns (45% initial damage)',
        'blindside': 'Increased damage if target has full life',
        'bloodlust': 'Life regenerated by percentage of damage dealt',
        'comeback': 'Increased damage while under 25% life',
        'conserve': 'Increased ammo conservation',
        'cripple': 'Reduces dexterity by 25% (stacks up to 3x for -75% total)',
        'crusher': 'Increased head damage',
        'cupid': 'Increased heart damage',
        'deadeye': 'Increased critical hit damage',
        'deadly': 'Chance of deadly hit (+500% damage)',
        'disarm': 'Disables opponent weapon for multiple turns',
        'double-edged': 'Double damage at cost of self injury',
        'double tap': 'Hit twice in single turn',
        'empower': 'Increased strength while using weapon',
        'eviscerate': 'Opponent receives extra damage under effect',
        'execute': 'Instant defeat when opponent below threshold life',
        'expose': 'Increased critical hit rate',
        'finale': 'Increased damage for every turn weapon not used',
        'focus': 'Hit chance increase for successive misses',
        'frenzy': 'Damage and accuracy increase on successive hits',
        'fury': 'Hit twice in single turn',
        'grace': 'Increased hit chance but reduced damage',
        'home run': 'Deflect incoming temporary items',
        'irradiate': 'Apply radiation poisoning on finishing hit',
        'motivation': 'Increase all stats by 10% (stacks 5x)',
        'paralyzed': '50% chance of missing turns for 300 seconds',
        'parry': 'Block incoming melee attacks',
        'penetrate': 'Ignore percentage of enemy armor',
        'plunder': 'Increase money mugged on finishing hit',
        'powerful': 'Increased damage',
        'proficience': 'Increase XP gained on finishing hit',
        'puncture': 'Ignore armor completely',
        'quicken': 'Increased speed while using weapon',
        'rage': 'Hit 2-8 times in single turn',
        'revitalize': 'Restore energy spent attacking on finishing hit',
        'roshambo': 'Increased groin damage',
        'slow': 'Reduce opponent speed by 25% (stacks 3x)',
        'smurf': 'Damage increase for each level under opponent',
        'specialist': 'Increased damage but limited to single clip',
        'stricken': 'Increased hospital time on final hit',
        'stun': 'Cause opponent to miss next turn',
        'suppress': '25% chance for opponent to miss future turns',
        'sure shot': 'Guaranteed hit',
        'throttle': 'Increased throat damage',
        'warlord': 'Increases respect gained',
        'weaken': 'Reduce opponent defense by 25% (stacks 3x)',
        'wind-up': 'Increased damage after spending turn to wind up',
        'wither': 'Reduce opponent strength by 25% (stacks 3x)'
    };

    const effectPatterns = [
        { regex: /\bcrippled\b/gi, key: 'cripple' },
        { regex: /\bweakened\b/gi, key: 'weaken' },
        { regex: /\bwithered\b/gi, key: 'wither' },
        { regex: /\bslowed\b/gi, key: 'slow' },
        { regex: /\bdemoralized\b/gi, key: 'demoralized' },
        { regex: /\bfrozen\b/gi, key: 'freeze' },
        { regex: /\beviscerated\b/gi, key: 'eviscerate' },
        { regex: /\bstunned\b/gi, key: 'stun' },
        { regex: /\bpoisoned\b/gi, key: 'poisoned' },
        { regex: /\bbleeding\b/gi, key: 'bleed' },
        { regex: /\bburning\b/gi, key: 'burn' },
        { regex: /powerful hit/gi, key: 'powerful' },
        { regex: /double damage/gi, key: 'backstab' },
        { regex: /punctured through armor/gi, key: 'puncture' },
        { regex: /ignores armor/gi, key: 'puncture' },
        { regex: /deadly hit/gi, key: 'deadly' },
        { regex: /fired \d+ rounds.*hitting.*\d+ times/gi, key: 'rage' },
        { regex: /executed/gi, key: 'execute' },
        { regex: /finishing blow/gi, key: 'execute' }
    ];

    const css = `
        .wep-highlight {
            display: inline;
            background-color: #add8e6; /* Light Blue */
            color: #ffffff;             /* White Text */
            border: 1px solid #88b0c2;   /* Darker Blue Border */
            border-radius: 2px;
            padding: 0 2px;
            cursor: help;
        }
        .wep-highlight:hover {
            background-color: #87ceeb; /* Sky Blue on hover */
        }
    `;

    const styleTag = document.createElement('style');
    styleTag.textContent = css;
    document.head.appendChild(styleTag);

    function makeHighlightSpan(matchedText, effectKey) {
        const span = document.createElement('span');
        span.className = 'wep-highlight';
        span.setAttribute('title', `${effectKey.toUpperCase()}: ${weaponEffects[effectKey]}`);
        span.textContent = matchedText;
        return span;
    }

    function replaceInTextNodes(node, regex, effectKey) {
        if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('wep-highlight')) {
            return;
        }
        if (node.nodeType === Node.TEXT_NODE) {
            const text = node.nodeValue;
            if (!regex.test(text)) return;

            const frag = document.createDocumentFragment();
            let lastIndex = 0;

            text.replace(regex, (match, ...args) => {
                const matchIndex = args[args.length - 2];
                if (matchIndex > lastIndex) {
                    frag.appendChild(document.createTextNode(text.slice(lastIndex, matchIndex)));
                }
                const hlSpan = makeHighlightSpan(match, effectKey);
                frag.appendChild(hlSpan);
                lastIndex = matchIndex + match.length;
            });

            if (lastIndex < text.length) {
                frag.appendChild(document.createTextNode(text.slice(lastIndex)));
            }
            node.parentNode.replaceChild(frag, node);
            return;
        }

        if (node.nodeType === Node.ELEMENT_NODE) {
            Array.from(node.childNodes).forEach(child => replaceInTextNodes(child, regex, effectKey));
        }
    }

    function highlightInsideMessage(msgNode) {
        const rawText = msgNode.innerText;
        if (!rawText || !rawText.trim()) return;

        Object.keys(weaponEffects).forEach(key => {
            const regex = new RegExp(`\\b${key}\\b`, 'gi');
            if (regex.test(rawText)) {
                replaceInTextNodes(msgNode, regex, key);
            }
        });

        effectPatterns.forEach(({ regex, key }) => {
            if (regex.test(rawText)) {
                replaceInTextNodes(msgNode, regex, key);
            }
        });
    }

    function processNode(node) {
        if (node.nodeType !== Node.ELEMENT_NODE) return;

        const messageNodes = node.matches('.message')
            ? [node]
            : Array.from(node.querySelectorAll('.message'));

        messageNodes.forEach(highlightInsideMessage);
    }

    function processExistingLog() {
        document.querySelectorAll('.log-list.overview li').forEach(li => {
            const msg = li.querySelector('.message');
            if (msg) highlightInsideMessage(msg);
        });
    }

    function watchForNewLines() {
        const container = document.querySelector('.log-list.overview') || document.body;
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType !== Node.ELEMENT_NODE) return;

                    if (node.tagName === 'LI') {
                        const msg = node.querySelector('.message');
                        if (msg) highlightInsideMessage(msg);
                    } else {
                        const newLis = node.querySelectorAll?.('li') || [];
                        newLis.forEach(li => {
                            const msg = li.querySelector('.message');
                            if (msg) highlightInsideMessage(msg);
                        });
                    }
                });
            });
        });
        observer.observe(container, { childList: true, subtree: true });
    }

    function init() {
        processExistingLog();
        watchForNewLines();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();