// ==UserScript== // @name RW Bonus Labels // @namespace https://github.com/MWTBDLTR/torn-scripts // @version 8.3.4 // @description Displays RW bonus values with convenient names consistently across all Torn pages. // @author RyuFive + MrChurch [3654415] // @match https://www.torn.com/displaycase.php* // @match https://www.torn.com/amarket.php* // @match https://www.torn.com/bazaar.php* // @match https://www.torn.com/factions.php?step=* // @match https://www.torn.com/item.php* // @match https://www.torn.com/page.php?sid=ItemMarket* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @downloadURL https://github.com/MWTBDLTR/torn-scripts/raw/main/rw_bonus_labels.js // @updateURL https://github.com/MWTBDLTR/torn-scripts/raw/main/rw_bonus_labels.js // @license MIT // ==/UserScript== let bonusColorsEnabled = true; // Shared constants for optimizations const STATIC_BONUSES = new Set([ "irradiate", "smash", "radiation protection", "dimensiokinesis", "oneirokinesis", ]); const UNIT_OVERRIDES = { disarm: "T", freeze: "s" }; const ABBREVIATIONS = { "radiation protection": "Rad", dimensiokinesis: "Dimens", oneirokinesis: "Oneiro", kinetokinesis: "Kineto", assassinate: "Ass", backstab: "Back", bloodlust: "Blood", blindside: "Blndsi", comeback: "Come", conserve: "Cons", cripple: "Cripp", crusher: "Crush", deadeye: "Ddeye", "double tap": "D-Tap", "double-edged": "D-Edge", empower: "Empwr", eviscerate: "Evisc", execute: "Exec", expose: "Exp", finale: "Fin", "home run": "H-Run", irradiate: "Irrad", motivation: "Motiv", paralyze: "Para", penetrate: "Pene", plunder: "Plndr", powerful: "Power", proficience: "Prof", puncture: "Punct", quicken: "Quick", revitalize: "Revit", roshambo: "Useless", specialist: "Spec", stricken: "Strick", suppress: "Suppr", "sure shot": "Sure-S", throttle: "Thrott", warlord: "War", weaken: "Weak", "wind-up": "Wind-U", wither: "With", blindfire: "Blndfir", demoralize: "Demor", emasculate: "Emasc", hazardous: "Hzrd", laceration: "Lacer", "severe burning": "Sev-Brn", imperviable: "Imperv", immutable: "Immut", irrepressible: "Irrep", impregnable: "Impreg", impenetrable: "Impen", impassable: "Impass", invulnerable: "Invuln", insurmountable: "Insur", }; (function addCustomStyles() { const css = ` .custom-itemmarket-container { display: flex !important; flex-direction: column; margin-top: 15px; white-space: normal; padding-left: 0; } .custom-bazaar-container { float: left; white-space: nowrap; margin-top: 9px; padding-left: 5px; top: 3px; right: 0px; display: inline-block !important; position: relative; } .bonus-attachment-icons { display: inline-table !important; width: auto !important; float: left !important; white-space: nowrap !important; padding-left: 0px !important; padding-top: 3px !important; position: relative !important; top: -40px !important; right: 0px !important; } .custom-bonus-label { font-size: 12px; padding: 1px 4px; border-radius: 3px; margin-left: 2px; margin-bottom: 2px; display: inline-block; text-shadow: 0 1px 1px rgba(0,0,0,0.3); pointer-events: none; user-select: none; white-space: nowrap; max-width: 100%; } /* Standardized global badge styling */ .custom-bonus-badge { display: inline-block; font-size: 11px; font-weight: 600; border-radius: 6px; padding: 2px 6px; margin: 1px 0; white-space: nowrap; line-height: 1.2em; box-shadow: 0 1px 3px rgba(0,0,0,0.2); border: 1px solid rgba(0,0,0,0.6); vertical-align: middle; transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1); cursor: help; } /* Pure CSS Dark Mode Handlers (No JS Required) */ body.dark-mode .custom-bonus-badge { text-shadow: 0 0 2px rgba(0,0,0,1),0 0 3px rgba(0,0,0,0.9),0 0 3px rgba(0,0,0,0.9); color: white !important; } body:not(.dark-mode) .custom-bonus-badge { text-shadow: 0 0 2px rgba(255,255,255,1),0 0 3px rgba(255,255,255,0.9),0 0 3px rgba(255,255,255,0.9); color: black !important; } body.dark-mode .custom-bonus-label { background: linear-gradient(145deg, rgba(51, 51, 51, 0.7), rgba(17, 17, 17, 0.7)) !important; color: white !important; } body:not(.dark-mode) .custom-bonus-label { background: linear-gradient(145deg, rgba(255, 255, 255, 1), rgba(230, 230, 230, 1)) !important; color: black !important; } /* Inline Row Layout for Dual Bonuses in Item Market */ .custom-badge-inline-row { display: flex; flex-direction: row; justify-content: center; align-items: center; width: 100%; margin-bottom: 4px; gap: 3px; } .custom-badge-inline-row .custom-bonus-badge { font-size: 10px; padding: 2px 4px; letter-spacing: -0.2px; } /* Armory Cell Fit Layout */ #armoury-weapons .loaned, #armoury-armour .loaned { width: 75px !important; overflow: visible !important; } #armoury-weapons .type, #armoury-armour .type { width: 126px !important; box-sizing: border-box !important; padding: 0 !important; position: relative !important; overflow: hidden !important; } #armoury-weapons .double, #armoury-armour .double { height: 40px !important; line-height: normal !important; } .custom-armory-bonus-badge { position: absolute !important; left: 0 !important; right: 0 !important; margin: 0 !important; border-radius: 0 !important; box-shadow: none !important; border: none !important; box-sizing: border-box !important; display: block !important; overflow: hidden !important; padding: 0 !important; text-align: left !important; text-indent: 0 !important; } .custom-armory-bonus-text { position: absolute !important; left: 2px !important; right: 2px !important; top: 50% !important; transform: translateY(-50%) !important; white-space: nowrap !important; line-height: 1.1em !important; } .custom-armory-bonus-badge:first-child:last-child { top: 0 !important; bottom: 0 !important; } .double .custom-armory-bonus-badge:first-child { top: 0 !important; bottom: 50% !important; border-bottom: 1px solid rgba(0,0,0,0.5) !important; } .double .custom-armory-bonus-badge:last-child { top: 50% !important; bottom: 0 !important; }`; const style = document.createElement("style"); style.textContent = css; document.head.appendChild(style); })(); const weaponBonuses = { general: { achilles: { orange: { max: 112, min: 75 }, red: { max: 169, min: 113 }, yellow: { max: 74, min: 50 }, }, assassinate: { orange: { max: 100, min: 70 }, red: { max: 148, min: 100 }, yellow: { max: 70, min: 50 }, }, backstab: { orange: { max: 64, min: 44 }, red: { max: 96, min: 66 }, yellow: { max: 43, min: 30 }, }, berserk: { orange: { max: 58, min: 36 }, red: { max: 88, min: 60 }, yellow: { max: 35, min: 20 }, }, bleed: { orange: { max: 46, min: 31 }, red: { max: 72, min: 48 }, yellow: { max: 30, min: 20 }, }, blindside: { orange: { max: 62, min: 40 }, red: { max: 97, min: 63 }, yellow: { max: 39, min: 25 }, }, bloodlust: { orange: { max: 15, min: 12 }, red: { max: 19, min: 15 }, yellow: { max: 12, min: 10 }, }, comeback: { orange: { max: 99, min: 70 }, red: { max: 138, min: 102 }, yellow: { max: 70, min: 50 }, }, conserve: { orange: { max: 37, min: 30 }, red: { max: 49, min: 38 }, yellow: { max: 30, min: 25 }, }, cripple: { orange: { max: 41, min: 29 }, red: { max: 63, min: 43 }, yellow: { max: 29, min: 20 }, }, crusher: { orange: { max: 108, min: 75 }, red: { max: 171, min: 114 }, yellow: { max: 74, min: 50 }, }, cupid: { orange: { max: 110, min: 75 }, red: { max: 168, min: 113 }, yellow: { max: 74, min: 50 }, }, deadeye: { orange: { max: 74, min: 45 }, red: { max: 123, min: 75 }, yellow: { max: 45, min: 25 }, }, deadly: { orange: { max: 6, min: 4 }, red: { max: 11, min: 7 }, yellow: { max: 3, min: 2 }, }, disarm: { orange: { max: 9, min: 5 }, red: { max: 15, min: 9 }, yellow: { max: 5, min: 3 }, }, "double tap": { orange: { max: 36, min: 24 }, red: { max: 58, min: 38 }, yellow: { max: 24, min: 15 }, }, "double-edged": { orange: { max: 24, min: 16 }, red: { max: 40, min: 25 }, yellow: { max: 16, min: 10 }, }, empower: { orange: { max: 148, min: 90 }, red: { max: 245, min: 152 }, yellow: { max: 89, min: 50 }, }, eviscerate: { orange: { max: 25, min: 19 }, red: { max: 34, min: 25 }, yellow: { max: 19, min: 15 }, }, execute: { orange: { max: 22, min: 18 }, red: { max: 30, min: 23 }, yellow: { max: 18, min: 15 }, }, expose: { orange: { max: 14, min: 10 }, red: { max: 21, min: 14 }, yellow: { max: 9, min: 7 }, }, finale: { orange: { max: 15, min: 12 }, red: { max: 18, min: 15 }, yellow: { max: 12, min: 10 }, }, focus: { orange: { max: 24, min: 19 }, red: { max: 35, min: 25 }, yellow: { max: 19, min: 15 }, }, frenzy: { orange: { max: 10, min: 7 }, red: { max: 14, min: 10 }, yellow: { max: 7, min: 5 }, }, fury: { orange: { max: 24, min: 16 }, red: { max: 40, min: 25 }, yellow: { max: 16, min: 10 }, }, grace: { orange: { max: 55, min: 36 }, red: { max: 84, min: 60 }, yellow: { max: 36, min: 20 }, }, "home run": { orange: { max: 74, min: 60 }, red: { max: 99, min: 76 }, yellow: { max: 59, min: 50 }, }, irradiate: { orange: { max: 49, min: 20 }, red: { max: 86, min: 50 }, yellow: { max: 19, min: 0 }, }, motivation: { orange: { max: 25, min: 19 }, red: { max: 35, min: 25 }, yellow: { max: 19, min: 15 }, }, paralyze: { orange: { max: 12, min: 8 }, red: { max: 18, min: 13 }, yellow: { max: 8, min: 5 }, }, parry: { orange: { max: 74, min: 60 }, red: { max: 95, min: 75 }, yellow: { max: 60, min: 50 }, }, penetrate: { orange: { max: 37, min: 30 }, red: { max: 49, min: 38 }, yellow: { max: 30, min: 25 }, }, plunder: { orange: { max: 35, min: 26 }, red: { max: 49, min: 35 }, yellow: { max: 26, min: 20 }, }, powerful: { orange: { max: 32, min: 22 }, red: { max: 50, min: 33 }, yellow: { max: 22, min: 15 }, }, proficience: { orange: { max: 39, min: 28 }, red: { max: 59, min: 40 }, yellow: { max: 28, min: 20 }, }, puncture: { orange: { max: 27, min: 20 }, red: { max: 39, min: 28 }, yellow: { max: 20, min: 15 }, }, quicken: { orange: { max: 149, min: 90 }, red: { max: 245, min: 150 }, yellow: { max: 89, min: 50 }, }, rage: { orange: { max: 11, min: 7 }, red: { max: 18, min: 11 }, yellow: { max: 6, min: 4 }, }, revitalize: { orange: { max: 17, min: 13 }, red: { max: 24, min: 18 }, yellow: { max: 13, min: 10 }, }, roshambo: { orange: { max: 107, min: 75 }, red: { max: 160, min: 113 }, yellow: { max: 73, min: 50 }, }, slow: { orange: { max: 42, min: 29 }, red: { max: 64, min: 43 }, yellow: { max: 29, min: 20 }, }, smurf: { orange: { max: 3, min: 2 }, red: { max: 5, min: 3 }, yellow: { max: 1, min: 1 }, }, specialist: { orange: { max: 39, min: 28 }, red: { max: 59, min: 40 }, yellow: { max: 28, min: 20 }, }, stricken: { orange: { max: 63, min: 44 }, red: { max: 99, min: 69 }, yellow: { max: 43, min: 30 }, }, stun: { orange: { max: 25, min: 16 }, red: { max: 40, min: 25 }, yellow: { max: 16, min: 10 }, }, suppress: { orange: { max: 42, min: 32 }, red: { max: 51, min: 43 }, yellow: { max: 32, min: 25 }, }, "sure shot": { orange: { max: 8, min: 5 }, red: { max: 12, min: 8 }, yellow: { max: 4, min: 3 }, }, throttle: { orange: { max: 111, min: 75 }, red: { max: 170, min: 113 }, yellow: { max: 74, min: 50 }, }, warlord: { orange: { max: 27, min: 20 }, red: { max: 45, min: 28 }, yellow: { max: 20, min: 15 }, }, weaken: { orange: { max: 42, min: 29 }, red: { max: 63, min: 43 }, yellow: { max: 29, min: 20 }, }, "wind-up": { orange: { max: 174, min: 145 }, red: { max: 221, min: 175 }, yellow: { max: 145, min: 125 }, }, wither: { orange: { max: 42, min: 29 }, red: { max: 64, min: 43 }, yellow: { max: 29, min: 20 }, }, }, special: { blindfire: { max: 20, min: 15 }, burn: { max: 50, min: 30 }, demoralize: { max: 23, min: 20 }, emasculate: { max: 16, min: 15 }, freeze: { max: 26, min: 20 }, hazardous: { max: 31, min: 20 }, laceration: { max: 45, min: 35 }, poison: { max: 100, min: 85 }, "severe burning": { max: 100, min: 100 }, shock: { max: 100, min: 75 }, sleep: { max: 0, min: 0 }, smash: { max: 100, min: 100 }, spray: { max: 24, min: 20 }, storage: { max: 0, min: 0 }, toxin: { max: 44, min: 30 }, }, }; const armorBonuses = { 100: { color: "orange", max: 3, min: 2, name: "Imperviable", slots: ["gloves"], }, 110: { color: "yellow", max: 75, min: 75, name: "Kinetokinesis", slots: ["body_armor"], }, 111: { color: "yellow", max: 75, min: 75, name: "Kinetokinesis", slots: ["pants"], }, 112: { color: "yellow", max: 75, min: 75, name: "Kinetokinesis", slots: ["boots"], }, 113: { color: "yellow", max: 75, min: 75, name: "Kinetokinesis", slots: ["gloves"], }, 114: { color: "yellow", max: 75, min: 75, name: "Kinetokinesis", slots: ["helmet"], }, 115: { color: "orange", max: 43, min: 30, name: "Immutable", slots: ["helmet"], }, 116: { color: "orange", max: 52, min: 40, name: "Immutable", slots: ["body_armor"], }, 117: { color: "orange", max: 33, min: 25, name: "Immutable", slots: ["pants"], }, 118: { color: "orange", max: 19, min: 15, name: "Immutable", slots: ["boots"], }, 119: { color: "orange", max: 19, min: 15, name: "Immutable", slots: ["gloves"], }, 121: { color: "orange", max: 41, min: 30, name: "Irrepressible", slots: ["helmet"], }, 122: { color: "orange", max: 52, min: 40, name: "Irrepressible", slots: ["body_armor"], }, 123: { color: "orange", max: 33, min: 25, name: "Irrepressible", slots: ["pants"], }, 124: { color: "orange", max: 19, min: 15, name: "Irrepressible", slots: ["boots"], }, 125: { color: "orange", max: 19, min: 15, name: "Irrepressible", slots: ["gloves"], }, 15: { color: "yellow", max: 29, min: 20, name: "Impregnable", slots: ["helmet", "body_armor", "pants", "boots", "gloves"], }, 17: { color: "yellow", max: 29, min: 20, name: "Impenetrable", slots: ["helmet", "body_armor", "pants", "boots", "gloves"], }, 22: { color: "orange", max: 7, min: 5, name: "Imperviable", slots: ["helmet"], }, 26: { color: "red", max: 29, min: 20, name: "Impassable", slots: ["helmet", "body_armor", "pants", "boots", "gloves"], }, 90: { color: "yellow", max: 82, min: 1, name: "Radiation Protection", slots: ["helmet"], }, 91: { color: "orange", max: 15, min: 12, name: "Invulnerable", slots: ["helmet"], }, 92: { color: "yellow", max: 40, min: 30, name: "Insurmountable", slots: ["helmet", "body_armor", "pants", "boots", "gloves"], }, 93: { color: "orange", max: 11, min: 8, name: "Invulnerable", slots: ["body_armor"], }, 94: { color: "orange", max: 10, min: 7, name: "Invulnerable", slots: ["pants"], }, 95: { color: "orange", max: 7, min: 4, name: "Invulnerable", slots: ["boots"], }, 96: { color: "orange", max: 7, min: 4, name: "Invulnerable", slots: ["gloves"], }, 97: { color: "orange", max: 11, min: 7, name: "Imperviable", slots: ["body_armor"], }, 98: { color: "orange", max: 6, min: 4, name: "Imperviable", slots: ["pants"], }, 99: { color: "orange", max: 3, min: 2, name: "Imperviable", slots: ["boots"], }, }; // DRY Helper for generating CSS gradient string function calculateGradient(colorStr, percent) { if (!bonusColorsEnabled) return "linear-gradient(90deg, #333, #3a3a3a)"; const colors = { red: { fill: "rgba(180,0,40,0.75)", base: "rgba(128,0,32,0.2)" }, orange: { fill: "rgba(191,111,0,0.75)", base: "rgba(191,111,0,0.2)" }, yellow: { fill: "rgba(191,191,0,0.75)", base: "rgba(191,191,0,0.2)" }, }; const selected = colors[colorStr]; if (!selected) return "linear-gradient(90deg, #333, #3a3a3a)"; return `linear-gradient(90deg, ${selected.fill} 0%, ${selected.fill} ${percent - 0.1}%, ${selected.base} ${percent + 0.1}%, ${selected.base} 101%)`; } function getAbbreviation(name) { if (!name) return ""; const lower = name.toLowerCase(); if (ABBREVIATIONS[lower]) return ABBREVIATIONS[lower]; if (name.length > 7) return name.substring(0, 6); return name; } function createBonusBadge(value, bonus_name, item_name, abbreviate = false) { const badge = document.createElement("div"); badge.className = "custom-bonus-badge"; const numericValue = parseInt(String(value).replace(/[^0-9.-]/g, ""), 10) || 0; const bonusName = String(bonus_name || "") .trim() .toLowerCase(); let gradient = "linear-gradient(90deg, #333, #3a3a3a)"; const itemLower = String(item_name || "").toLowerCase(); let itemSlot = null; if (itemLower.includes("gloves")) itemSlot = "gloves"; else if (itemLower.includes("boots") || itemLower.includes("hooves")) itemSlot = "boots"; else if ( itemLower.includes("helmet") || itemLower.includes("mask") || itemLower.includes("respirator") ) itemSlot = "helmet"; else if (itemLower.includes("pants")) itemSlot = "pants"; else if ( itemLower.includes("body") || itemLower.includes("apron") || itemLower.includes("vest") || itemLower.includes("mail") || itemLower.includes("suit") || itemLower.includes("jacket") ) itemSlot = "body_armor"; const armorBonus = Object.values(armorBonuses).find( (b) => b?.name?.trim().toLowerCase() === bonusName && b?.slots?.includes(itemSlot), ); const weaponBonus = weaponBonuses?.general?.[bonusName] || weaponBonuses?.special?.[bonusName]; let minRange = null; let maxRange = null; let determinedColor = null; if (armorBonus) { minRange = armorBonus.min; maxRange = armorBonus.max; determinedColor = armorBonus.color; const percent = armorBonus.max > armorBonus.min ? Math.min( Math.max( ((numericValue - armorBonus.min) / (armorBonus.max - armorBonus.min)) * 100, 0, ), 100, ) : 100; gradient = calculateGradient(armorBonus.color, percent); } else if (weaponBonus) { let color = null; let range = null; if (weaponBonuses.general?.[bonusName]) { const ranges = weaponBonuses.general[bonusName]; if ( ranges.red && numericValue >= ranges.red.min && numericValue <= ranges.red.max ) { color = "red"; range = ranges.red; } else if ( ranges.orange && numericValue >= ranges.orange.min && numericValue <= ranges.orange.max ) { color = "orange"; range = ranges.orange; } else if ( ranges.yellow && numericValue >= ranges.yellow.min && numericValue <= ranges.yellow.max ) { color = "yellow"; range = ranges.yellow; } } else if (weaponBonuses.special?.[bonusName]) { range = weaponBonuses.special[bonusName]; if (numericValue >= range.min && numericValue <= range.max) color = "orange"; } if (range) { minRange = range.min; maxRange = range.max; determinedColor = color; const percent = range.max > range.min ? Math.min( Math.max( ((numericValue - range.min) / (range.max - range.min)) * 100, 0, ), 100, ) : 100; gradient = calculateGradient(color, percent); } } const unit = UNIT_OVERRIDES[bonusName] || "%"; const isStatic = STATIC_BONUSES.has(bonusName); const displayName = abbreviate ? getAbbreviation(bonus_name) : bonus_name; if (isStatic) { badge.textContent = displayName; gradient = "rgba(191,111,0,0.75)"; determinedColor = "orange"; } else { badge.textContent = `${numericValue}${unit} ${displayName}`; } let tierValue = 0; if (determinedColor === "red") tierValue = 3; else if (determinedColor === "orange") tierValue = 2; else if (determinedColor === "yellow") tierValue = 1; badge.dataset.tier = tierValue; if (minRange !== null && maxRange !== null) { badge.title = isStatic ? `Tier Range: ${minRange} - ${maxRange}` : `Tier Range: ${minRange}${unit} - ${maxRange}${unit}`; } badge.style.background = gradient; badge.style.transform = "scale(0.75)"; setTimeout(() => (badge.style.transform = ""), 100); return badge; } const isMobile = () => /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent) || (navigator.maxTouchPoints > 0 && window.innerWidth <= 768); // AUCTION HOUSE / AMARKET ======================================================================================================== function amarket() { document.querySelectorAll(".t-gray-6").forEach((el) => (el.innerHTML = "")); const iconSpans = document.querySelectorAll("span.bonus-attachment-icons"); const uniqueRows = new Set( Array.from(iconSpans) .map((icon) => icon.closest("div.item-cont-wrap")) .filter(Boolean), ); for (const row of uniqueRows) { const container = row.querySelector("p.t-gray-6"); const icons = row.querySelectorAll("span.bonus-attachment-icons"); if (!container || icons.length === 0) continue; container.style.display = "flex"; container.style.flexDirection = "row"; container.style.flexWrap = "wrap"; container.style.alignItems = "center"; container.style.justifyContent = "flex-start"; container.style.gap = "4px"; container.style.marginTop = "2px"; const nameNode = row.querySelector("[class*=name]"); const item_name = nameNode ? nameNode.textContent.trim() : ""; const processIcon = (icon) => { if (!icon || !icon.title) return; const name = icon.title.split(">")[1]?.split("<")[0]; if (!name) return; const value = format(icon.title, name); const badge = createBonusBadge(value, trueName(name), item_name); badge.style.margin = "0"; container.appendChild(badge); }; if (icons[0]) processIcon(icons[0]); if (icons[1]) processIcon(icons[1]); } } // DISPLAY ======================================================================================================== function displaycase() { const iconSpans = document.querySelectorAll("span.bonus-attachment-icons"); const uniqueItems = new Set( Array.from(iconSpans) .map((icon) => icon.closest("div.iconsbonuses")) .filter(Boolean), ); for (const item of uniqueItems) { const bonusIcons = item.querySelectorAll("span.bonus-attachment-icons"); if (bonusIcons.length === 0) continue; item .querySelectorAll("div.custom-bonus-badge") .forEach((el) => el.remove()); const nameNode = bonusIcons[0] .closest(".item-wrap") ?.querySelector("[class*=name]"); const item_name = nameNode ? nameNode.childNodes[3]?.textContent : ""; const first = bonusIcons[0]; if (first && first.title) { let name1 = first.title.split(">")[1]?.split("<")[0]; if (name1) { let value1 = format(first.title, name1); const badge1 = createBonusBadge(value1, trueName(name1), item_name); badge1.style.marginLeft = "4px"; first.appendChild(badge1); } } const second = bonusIcons[1]; if (second && second.title) { let name2 = second.title.split(">")[1]?.split("<")[0]; if (name2) { let value2 = format(second.title, name2); const badge2 = createBonusBadge(value2, trueName(name2), item_name); badge2.style.marginLeft = "4px"; second.appendChild(badge2); } } } } // BAZAAR ======================================================================================================== function bazaar(triggered) { if (!triggered || !triggered[0] || triggered[0].childElementCount < 1) return; const container = triggered[0]; container .querySelectorAll(".custom-bonus-label, .custom-bonus-badge") .forEach((e) => e.remove()); const appendBonus = (bonusWrapper) => { const element = bonusWrapper?.childNodes?.[0]; if (!element) return; const name = element.getAttribute("data-bonus-attachment-title"); const desc = element.getAttribute("data-bonus-attachment-description"); const nameWrap = element.closest("li")?.querySelector("[class*=name]"); const item_name = nameWrap ? nameWrap.textContent : ""; if (!name || !desc) return; const value = formatNew(desc, name); const badge = createBonusBadge(value, trueName(name), item_name); badge.style.marginLeft = "6px"; bonusWrapper.appendChild(badge); }; appendBonus(container.childNodes[0]); if (container.childElementCount === 2) appendBonus(container.childNodes[1]); container.classList.add("custom-bazaar-container"); } function manage(triggered) { if (triggered && triggered[0]) { var className = triggered[0].className; if (className.includes("blank-bonus")) return; var name = className.split("-")[2]; name = name.charAt(0).toUpperCase() + name.slice(1); name = trueName(name); const targetEl = triggered[0].parentElement.parentElement.parentElement.childNodes[2] .childNodes[0]; targetEl.innerHTML = targetEl.innerHTML.split(" x")[0] + " (" + name + ")"; } } // ARMORY ======================================================================================================== function armory(triggered) { const renameTypeHeader = (armouryRoot) => { armouryRoot?.querySelectorAll(".type").forEach((el) => { if (el.textContent.trim() === "Type") el.textContent = "Bonus"; }); }; const root = triggered?.[0]; if (!root) return; const armouryRoot = root?.parentElement?.parentElement?.parentElement?.parentElement; if (!armouryRoot) return; if (isMobile()) { const display = root.parentElement?.parentElement?.childNodes?.[3]; if (!display || display.textContent.endsWith(")")) return; if (display.querySelector(".custom-bonus-badge")) return; display.style.display = "flex"; display.style.flexDirection = "column"; display.style.alignItems = "flex-start"; const bonuses = Array.from(root.children) .filter((child) => child.title) .map((child) => { const title = child.title; const nameMatch = title.match(/>([^<]+) { const badge = createBonusBadge(b.value, b.name, item_name); badge.style.marginRight = "4px"; bonusContainer.appendChild(badge); }); display.parentElement.appendChild(bonusContainer); } } else { renameTypeHeader(armouryRoot); const display = root.parentElement?.parentElement?.childNodes?.[9]; if (!display) return; const titledChild = root.querySelector(":scope > [title]"); if (!titledChild) return; const item_name = root.parentElement.parentElement.querySelector( "[class*=name]", ).textContent; const bonuses = Array.from(root.children) .filter((child) => child.title) .map((child) => { const title = child.title; let name = title.split(">")[1]?.split("<")[0]; if (!name) return null; return { value: format(title, name), name: trueName(name) }; }) .filter(Boolean); if (bonuses.length) { display.textContent = ""; display.style.position = "relative"; display.style.padding = "0"; display.style.overflow = "hidden"; display.classList.toggle("double", bonuses.length > 1); display.style.height = bonuses.length > 1 ? "32px" : ""; bonuses.forEach((b) => { // Pre-generate both full and abbreviated text versions const badge = createBonusBadge(b.value, b.name, item_name, false); const abbrText = createBonusBadge( b.value, b.name, item_name, true, ).textContent; const text = document.createElement("span"); text.className = "custom-armory-bonus-text"; text.textContent = badge.textContent; // Start with the full text badge.textContent = ""; badge.appendChild(text); badge.classList.add("custom-armory-bonus-badge"); // Start double bonuses at 11px to give full names a better chance to fit, singles at 12px const defaultFontSize = bonuses.length > 1 ? "11px" : "12px"; badge.style.fontSize = defaultFontSize; badge.style.lineHeight = "1.1em"; badge.style.transform = ""; display.appendChild(badge); let hasAbbreviated = false; const fitArmoryBadgeText = () => { badge.style.transform = ""; const checkOverflow = () => text.scrollWidth > text.clientWidth + 2 || text.getBoundingClientRect().height > badge.clientHeight + 1; // Attempt to shrink text to make full word fit while (checkOverflow() && parseFloat(badge.style.fontSize) > 10) { badge.style.fontSize = `${parseFloat(badge.style.fontSize) - 0.5}px`; } // If we hit the 10px floor and it's STILL overflowing, swap to abbreviation if (checkOverflow() && !hasAbbreviated) { hasAbbreviated = true; text.textContent = abbrText; // Reset to default font size and try shrinking again with the much shorter text badge.style.fontSize = defaultFontSize; while (checkOverflow() && parseFloat(badge.style.fontSize) > 10) { badge.style.fontSize = `${parseFloat(badge.style.fontSize) - 0.5}px`; } } }; fitArmoryBadgeText(); setTimeout(fitArmoryBadgeText, 120); }); } } } // INVENTORY & BAZAAR ======================================================================================================== function inventoryandbazaar(triggered) { const link = document.URL; const removePreviousSpans = (container) => { if (container) container .querySelectorAll(".custom-bonus-label") .forEach((span) => span.remove()); }; if (link.includes("item")) { if (triggered?.[0]?.childElementCount >= 3) { const row = triggered[0]; const bonusParentIndex = row.childNodes[5]?.className?.includes( "testtest", ) ? 7 : 5; const element = row.childNodes[bonusParentIndex]?.childNodes?.[1]; if (!element?.title) return; const parent = row.parentElement?.parentElement?.parentElement; const container = parent.querySelector(".name"); if (!container) return; removePreviousSpans(container); const parseBonus = (el) => { if (!el?.title) return null; const match = el.title.match(/>([^<]+) { if (!b?.val || !b?.bonusName) return; const badge = createBonusBadge(b.val, b.bonusName, item_name); badge.style.marginLeft = "6px"; container.appendChild(badge); }); } } else if (link.includes("bazaar.php#/add")) { if (triggered && triggered[0] && triggered[0].childElementCount >= 1) { const element = triggered[0].childNodes[2]?.childNodes[0]; if (!element || !element.title) return; let container = triggered[0].parentElement.parentElement.parentElement.childNodes[1] .childNodes[1].childNodes[0]; removePreviousSpans(container); let name = element.title.split(">")[1].split("<")[0]; let value = format(element.title, name); const item_name = triggered[0].parentElement.parentElement.parentElement .querySelector(".name-wrap .t-overflow") .childNodes[0].textContent.trim(); const badge1 = createBonusBadge(value, trueName(name), item_name); badge1.style.marginLeft = "6px"; container.appendChild(badge1); const nextBonus = element.parentElement.childNodes[1]; if ( nextBonus && !nextBonus.className.includes("blank-bonus") && nextBonus.title ) { let name2 = nextBonus.title.split(">")[1].split("<")[0]; let value2 = format(nextBonus.title, name2); const badge2 = createBonusBadge(value2, trueName(name2), item_name); badge2.style.marginLeft = "6px"; container.appendChild(badge2); } } } } // ITEM MARKET ======================================================================================================== function newItemMarket(triggered) { if (!triggered?.[0]) return; if (!document.URL.includes("ItemMarket")) return; const tile = triggered[0]; if (tile.getAttribute("data-badge-added") === "true") return; const bonusContainer = triggered[0].childNodes?.[0]?.childNodes?.[2]?.childNodes?.[0]; const item_name = triggered[0]?.querySelector('[class*="name___"]')?.textContent; const primaryNode = bonusContainer?.childNodes?.[1]?.childNodes?.[0]; if (!primaryNode) return; const leftColumn = bonusContainer.childNodes?.[0]; if (!leftColumn) return; const appendNode = leftColumn.parentElement.parentElement.parentElement.parentElement; if (!appendNode) return; appendNode .querySelectorAll(".custom-bonus-badge, .custom-badge-inline-row") .forEach((el) => el.remove()); const rowWrapper = document.createElement("div"); rowWrapper.className = "custom-badge-inline-row"; appendNode.appendChild(rowWrapper); let badges = []; const hasTwoBonuses = bonusContainer?.childNodes?.[1]?.childElementCount === 2; const extractBadge = (node) => { const name = node.getAttribute("data-bonus-attachment-title"); const desc = node.getAttribute("data-bonus-attachment-description"); if (!name || !desc) return null; const value = formatNew(desc, name); // Only abbreviate on the Item Market if there are two bonuses competing for space const shouldAbbreviate = hasTwoBonuses; const badge = createBonusBadge(value, name, item_name, shouldAbbreviate); badge.style.margin = "0"; return badge; }; const badge1 = extractBadge(primaryNode); if (badge1) badges.push(badge1); if (hasTwoBonuses) { const secondaryNode = bonusContainer.childNodes[1].childNodes[1]; const badge2 = extractBadge(secondaryNode); if (badge2) badges.push(badge2); } badges.sort((a, b) => { const tierA = parseInt(a.dataset.tier || "0", 10); const tierB = parseInt(b.dataset.tier || "0", 10); return tierB - tierA; }); badges.forEach((badge) => rowWrapper.appendChild(badge)); appendNode.style.display = "flex"; appendNode.style.flexDirection = "column"; appendNode.style.justifyContent = "flex-end"; leftColumn.classList.add("custom-itemmarket-container"); tile.setAttribute("data-badge-added", "true"); } // FORMATTING & HELPERS ================================================================================================ const executeFormatting = (str, name) => { if (STATIC_BONUSES.has(name.toLowerCase())) return ""; const specialHandlers = { Disarm: () => str.split(" turns")[0]?.split("for ")[1] + " T ", Bloodlust: () => str.split(" of")[0]?.split("by ")[1] + " ", Execute: () => str.split(" life")[0]?.split("below ")[1] + " ", Penetrate: () => str.split(" of")[0]?.split("Ignores ")[1] + " ", Eviscerate: () => str.split(" extra")[0]?.split("them ")[1] + " ", Poison: () => (str.includes("
") ? str.split(" chance to Poison")[0]?.split("
")[1] : str.split(" chance to Poison")[0]) + " ", }; if (specialHandlers[name]) { try { return specialHandlers[name]() || ""; } catch (e) { return ""; } } try { if (str.includes(">")) return (str.split(">")[3]?.split("%")[0] || "") + "% "; return str.split("%")[0] + "% "; } catch (e) { return ""; } }; function format(title, name) { return executeFormatting(title, name); } function formatNew(desc, name) { return executeFormatting(desc, name); } function trueName(text) { const map = { Full: "EOD", Negative: "Delta", Poisoned: "Poison" }; return map[text] || text; } // OBSERVER INIT ======================================================================================================== const observerMap = { ".item-cont-wrap": amarket, ".display-main-page": displaycase, "[class*='iconBonuses___']": bazaar, "[class*='extraBonusIcon___']": manage, ".bonuses-wrap": inventoryandbazaar, ".bonus": armory, "[class*='itemTile___']": newItemMarket, }; const seenElements = new WeakSet(); const process = () => { for (const [selector, callback] of Object.entries(observerMap)) { document.querySelectorAll(selector).forEach((el) => { if (!seenElements.has(el)) { seenElements.add(el); callback([el]); } }); } }; // Debounced Observer to drastically reduce CPU overhead during heavy DOM modifications let isProcessing = false; const mainObserver = new MutationObserver(() => { if (!isProcessing) { isProcessing = true; requestAnimationFrame(() => { process(); isProcessing = false; }); } }); mainObserver.observe(document.body, { childList: true, subtree: true }); process();