// ==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();
}
})();