Loadout Reveal

NST: Loadout reveal for those in gunshops

// ==UserScript==
// @name         Loadout Reveal
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  NST: Loadout reveal for those in gunshops
// @author       Hwa [2466470]
// @match        https://www.torn.com/loader.php?sid=attack*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant        none
// @license      MIT
// ==/UserScript==


(function() {
    'use strict';

    // Wait until a selector exists anywhere
    function waitForSelector(selector, callback, root = document.body) {
        const el = root.querySelector(selector);
        if (el) return callback(el);

        const observer = new MutationObserver((mutations, obs) => {
            const el = root.querySelector(selector);
            if (el) {
                obs.disconnect();
                callback(el);
            }
        });

        observer.observe(root, { childList: true, subtree: true });
    }

    // extract armor
    function extractArmor(playerEl) {
        const armorAreas = playerEl.querySelectorAll('area');
        const armor = {};
        ['Helmet','Chest','Gloves','Pants','Boots'].forEach(part => {
            const area = Array.from(armorAreas).find(a => a.alt === part);
            armor[part.toLowerCase()] = area ? (area.title || "N/A") : "N/A";
        });
        return armor;
    }

    // Extract armor prefix
    function getArmorPrefix(armor) {
        const parts = ['helmet', 'chest', 'gloves', 'pants', 'boots'];
        const names = parts.map(part => armor[part]);
        if (names.some(name => !name)) return null;
        const prefixes = names.map(name => name.split(' ')[0]);
        return prefixes.every(p => p === prefixes[0]) ? prefixes[0] : null;
    }

    // extract weapons
    function extractWeapons(playerEl) {
        const weaponListDiv = playerEl.querySelector('[class^="weaponList"]');
        if (!weaponListDiv) return {};

        const weapons = {};
        ['weapon_main','weapon_second','weapon_melee'].forEach(id => {
            const div = weaponListDiv.querySelector(`#${id}`);
            if (div) {
                const img = div.querySelector('figure img');
                const weaponName = img?.alt || 'N/A';
                const damage = div.querySelector('[id^="damage-value_"]')?.textContent || 'N/A';
                const accuracy = div.querySelector('span[id^="accuracy_"]')?.textContent || 'N/A';
                const bonus_attachments = Array.from(div.querySelectorAll('[data-bonus-attachment-title]'))
                .map(i => ({
                    title: i.getAttribute('data-bonus-attachment-title') || '',
                    description: i.getAttribute('data-bonus-attachment-description') || ''
                })).filter(b => b.title);

                weapons[id.replace('weapon_','').replace('main','Primary').replace('second','Secondary').replace('melee','Melee')] = {
                    weapon: weaponName,
                    damage,
                    accuracy,
                    bonus_attachments
                };
            } else {
                weapons[id.replace('weapon_','').replace('main','Primary').replace('second','Secondary').replace('melee','Melee')] = 'N/A';
            }
        });

        // Temp weapon
        weapons.Temp = weaponListDiv.querySelector('#weapon_temp figure img')?.alt || 'N/A';

        return weapons;
    }

    // Format weapon info
    function formatWeaponData(weaponData) {
        if (!weaponData || typeof weaponData !== 'object' || !weaponData.weapon) return "N/A";

        let text = `${weaponData.weapon}, damage: ${weaponData.damage}, accuracy: ${weaponData.accuracy}`;
        if (Array.isArray(weaponData.bonus_attachments) && weaponData.bonus_attachments.length) {
            text += '<br>Bonuses:';
            for (const bonus of weaponData.bonus_attachments) {
                text += `<br>- ${bonus.title} (${bonus.description.replace('+','').trim()})`;
            }
        }
        return text;
    }

    // Create table with copy button
    function createDataTable(dataObj) {
        const container = document.createElement('div');
        container.style.display = 'flex';
        container.style.alignItems = 'flex-start';
        container.style.marginBottom = '10px';
        container.style.gap = '10px';

        const table = document.createElement('table');
        table.style.borderCollapse = 'collapse';
        table.style.background = '#111';
        table.style.color = '#fff';
        table.style.fontFamily = 'monospace';
        table.style.zIndex = '9999';
        table.style.position = 'relative';

        for (const [key, value] of Object.entries(dataObj)) {
            const row = document.createElement('tr');

            const keyCell = document.createElement('td');
            keyCell.textContent = key;
            keyCell.style.border = '1px solid #888';
            keyCell.style.padding = '4px';
            keyCell.style.background = '#222';
            keyCell.style.color = '#fff';

            const valueCell = document.createElement('td');
            if (value && typeof value === 'object' && value.weapon) {
                valueCell.innerHTML = formatWeaponData(value);
            } else {
                valueCell.textContent = value;
            }
            valueCell.style.border = '1px solid #888';
            valueCell.style.padding = '4px';
            valueCell.style.background = '#333';
            valueCell.style.color = '#fff';

            row.appendChild(keyCell);
            row.appendChild(valueCell);
            table.appendChild(row);
        }

        const button = document.createElement('button');
        button.textContent = 'Copy';
        button.style.marginLeft = '5px';
        button.style.padding = '2px 5px';
        button.style.backgroundColor = '#444';
        button.style.color = '#fff';
        button.style.border = '1px solid #888';
        button.style.borderRadius = '4px';
        button.style.cursor = 'pointer';
        button.style.fontFamily = 'monospace';
        button.style.fontSize = '14px';

        button.addEventListener('click', () => {
            const rows = Array.from(table.querySelectorAll('tr')).map(row => {
                const cells = row.querySelectorAll('td');
                return `${cells[0].textContent}: ${cells[1].innerText}`;
            });
            navigator.clipboard.writeText(rows.join('\n')).then(() => {
                button.textContent = 'Copied!';
                setTimeout(() => button.textContent = 'Copy', 2000);
            });
        });

        container.appendChild(table);
        container.appendChild(button);
        return container;
    }

    // Insert table
    function insertTable(data) {
        const container = document.querySelector('[class^="coreWrap"]');
        if (!container) return;
        const afterEl = container.querySelector('[class^="logStatsWrap__"]');
        const tableEl = createDataTable(data);
        afterEl ? afterEl.after(tableEl) : container.appendChild(tableEl);

        return container;
    }

    // Extract data from a player element
    function extractData(playerEl, nameEl) {
        // Armor
        const armor = extractArmor(playerEl);
        const armorPrefix = getArmorPrefix(armor);

        // Weapons
        const weapons = extractWeapons(playerEl);

        const data = {
            ...(armorPrefix ? { 'Full Set': armorPrefix } : armor),
            ...weapons,
        };

        console.log("Extracted Data:", data);
        return data;
    }

    // ------------- MOBILE ONLY ---------------
    function getPageType() {
        // Look for any weapon slot element
        const weaponEl = document.querySelector('[id^="weapon_main"]');

        if (!weaponEl) return 'unknown';

        // If the weapon element is for the defender, we’re on the second tab
        const isDefender = Array.from(weaponEl.classList).some(c => c.includes('defender_'));

        if (isDefender) {
            return 'second'; // Defender's weapon view (switched tab)
        }

        return 'first'; // Default (defender armor + your weapons)
    }

    function observeMobileWeapons(callback) {
        const containerSelector = 'div[class^="weaponList_"]';

        const tryAttach = () => {
            const container = document.querySelector(containerSelector);
            if (!container) {
                console.warn("Weapon container not found yet, retrying...");
                setTimeout(tryAttach, 500);
                return;
            }

            const observer = new MutationObserver(mutations => {
                for (const mutation of mutations) {
                    // Only look for added nodes
                    if (mutation.type === 'childList' && mutation.addedNodes.length) {
                        mutation.addedNodes.forEach(node => {
                            if (!(node instanceof HTMLElement)) return;
                            const weaponMain = node.id?.startsWith('weapon_main') ? node : node.querySelector('[id^="weapon_main"]');
                            if (!weaponMain) return;

                            const isDefender = Array.from(weaponMain.classList).some(c => c.includes('defender_'));
                            callback(isDefender ? 'second' : 'first', weaponMain);
                        });
                    }
                }
            });

            observer.observe(container, {
                childList: true,
                subtree: true
            });

            // Initial detection in case it’s already loaded
            const weaponMain = container.querySelector('[id^="weapon_main"]');
            if (weaponMain) {
                const isDefender = Array.from(weaponMain.classList).some(c => c.includes('defender_'));
                callback(isDefender ? 'second' : 'first', weaponMain);
            }
        };

        tryAttach();
    }

    function updateDataTable(container, dataObj) {
        if (!container) return;

        const table = container.querySelector('table');
        if (!table) return;

        for (const [key, value] of Object.entries(dataObj)) {
            let row = table.querySelector(`tr[data-key="${key}"]`);

            if (!row) {
                // Create new row if it doesn't exist
                row = document.createElement('tr');
                row.dataset.key = key;

                const keyCell = document.createElement('td');
                keyCell.textContent = key;
                keyCell.style.border = '1px solid #888';
                keyCell.style.padding = '4px';
                keyCell.style.background = '#222';
                keyCell.style.color = '#fff';

                const valueCell = document.createElement('td');
                valueCell.style.border = '1px solid #888';
                valueCell.style.padding = '4px';
                valueCell.style.background = '#333';
                valueCell.style.color = '#fff';

                row.appendChild(keyCell);
                row.appendChild(valueCell);
                table.appendChild(row);
            }

            // Update row content
            const valueCell = row.querySelector('td:nth-child(2)');
            if (value && typeof value === 'object' && value.weapon) {
                valueCell.innerHTML = formatWeaponData(value);
            } else {
                valueCell.textContent = value;
            }
        }
    }

    // Wait for full fight page
    waitForSelector('[class^="playerArea_"]', playerAreas => {
        const players = document.querySelectorAll('[class^="playerArea_"]');
        const isMobile = players.length === 1; // only one player area = mobile view
        console.log("Is mobile?", isMobile);

        const nameEls = document.querySelectorAll('span[class*="userName___"][class*="user-name"]');
        const dataTableContainer = insertTable({ Name: nameEls[1]?.textContent.trim() || 'N/A' });

        if (isMobile) {
            observeMobileWeapons((pageType, el) => {

                const playerEl = document.querySelector('[class^="playerArea_"]');
                if (pageType === 'first') {
                    const armor = extractArmor(playerEl);
                    const armorPrefix = getArmorPrefix(armor);
                    updateDataTable(dataTableContainer, armorPrefix ? { 'Full Set': armorPrefix } : armor);
                } else {
                    const weapons = extractWeapons(playerEl);
                    updateDataTable(dataTableContainer, weapons);
                }
            });
        } else {
            const secondPlayer = players[1];
            const data = extractData(secondPlayer);
            updateDataTable(dataTableContainer, data);
        }
    });

})();