Dead Frontier – Market / Scrap Ratio (SilverScripts Addon)

Highlights items based on Market Price / Scrap Value ratio with stats & scrap multipliers

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Dead Frontier – Market / Scrap Ratio (SilverScripts Addon)
// @namespace    df-market-scrap-ratio
// @version      2.6.2
// @description  Highlights items based on Market Price / Scrap Value ratio with stats & scrap multipliers
// @author       Cezinha
//
// @match        https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=*
// @match        https://fairview.deadfrontier.com/onlinezombiemmo/DF3D/DF3D_InventoryPage.php?page=31*
//
// @require      https://update.greasyfork.org/scripts/441829/1630783/Dead%20Frontier%20-%20API.js
// @grant        unsafeWindow
// ==/UserScript==

(function () {
    'use strict';

    // ============================================
    // GLOBAL CONSTANTS
    // ============================================
    // Contains fixed values used throughout the script: ratio tiers, scrap multipliers, and valid tradezones
    const GLOBAL_CONSTANTS = {
        TIERS: [
            { max: 0, color: '#8B4513' },
            { max: 0.99, color: '#8B0000' },
            { max: 1, color: '#696969' },
            { max: 2, color: '#A9A9A9' },
            { max: 5, color: '#32CD32' },
            { max: 10, color: '#1E90FF' },
            { max: 20, color: '#9370DB' },
            { max: 35, color: '#8A2BE2' },
            { max: 55, color: '#FF8C00' },
            { max: 90, color: '#FFD700' },
            { max: 120, color: '#00CED1' },
            { max: 160, color: '#20B2AA' },
            { max: 200, color: '#FF4500' },
            { max: 300, color: '#FF1493' },
            { max: Infinity, color: '#FF00FF' }
        ],

        SCRAP_MULTIPLIERS: [
            { minScrap: 15, multiplier: 0.3 },
            { minScrap: 100, multiplier: 0.5 },
            { minScrap: 1000, multiplier: 0.8 },
            { minScrap: 3000, multiplier: 1.2 },
            { minScrap: 11000, multiplier: 1.5 },
            { minScrap: 19450, multiplier: 2 },
            { minScrap: 40000, multiplier: 3 },
            { minScrap: 75000, multiplier: 5 },
            { minScrap: 125000, multiplier: 8 },
            { minScrap: 200000, multiplier: 12 },
            { minScrap: 350000, multiplier: 15 },
            { minScrap: 500000, multiplier: 25 },
            { minScrap: 1000000, multiplier: 40 },
            { minScrap: 2000000, multiplier: 60 }
        ],

        VALID_TRADEZONES: ['21', '22', '10', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    };

    // ============================================
    // CONFIGURATION MANAGER
    // ============================================
    const ConfigManager = (function() {
        const CONFIG = {
            ignoreLocked: false,
            ignoreAmmo: false,
            ignoreMC: false,
            ignoreNT: false,
            minScrapValue: 0.01,
            minEffectiveScrap: 500,
            language: 'en-us'  //en-us, pt-br
        };

        return {
            get: (key) => CONFIG[key],
            set: (key, value) => { CONFIG[key] = value; },
            getAll: () => ({...CONFIG})
        };
    })();

    // ============================================
    // REGEX SERVICE
    // ============================================
    // Handles regular expression patterns for parsing item types, stats, and tradezone data
    const RegexService = (function() {
        const PATTERNS = {
            STATS: /_stats(\d+)/i,
            AMMO_SUFFIX: /(?:ammo|rounds|bullets|shells|handgun|rifle|gauge|grenades|gasoline|biomass|cell)$/i,
            TYPE_CLEANUP: /_stats\d+|_nt|_colour\w+|_name[^_]*/gi,
            TRADEZONE_PARAM: /tradezone=(\d+)/,
            NUMBERS: /\d+/g
        };

        return {
            STATS: PATTERNS.STATS,
            getStatsCode: (type) => {
                const match = PATTERNS.STATS.exec(type);
                return match ? match[1] : null;
            },
            isMastercrafted: (type) => PATTERNS.STATS.test(type),
            normalizeType: (type) => type.replace(PATTERNS.TYPE_CLEANUP, '').toLowerCase(),
            getTradezoneFromData: (data) => {
                const match = PATTERNS.TRADEZONE_PARAM.exec(data);
                return match ? match[1] : null;
            },
            getNumbers: (str) => str.match(PATTERNS.NUMBERS)?.[0] || '',
            getStatsFormat: (stats) => {
                if (!stats) return 'none';
                const digits = (stats.match(PATTERNS.NUMBERS)?.[0] || '');

                if (digits.length === 1) return 'single';
                if (digits.length === 3) return 'triple';
                if (digits.length === 4) return 'quad';

                return 'unknown';
            }
        };
    })();

    // ============================================
    // LOCALIZATION SERVICE
    // ============================================
    // Provides localized text for UI elements, labels, and tooltips in multiple languages
    const LocalizationService = (function() {
        const LOCALIZATION = {
            'en-us': {
                tradezoneNames: {
                    '21': 'Outpost', '22': 'Camp Valcrest', '10': 'Wastelands',
                    '1': 'Northwest', '2': 'North', '3': 'Northeast', '4': 'West',
                    '5': 'Central', '6': 'East', '7': 'Southwest', '8': 'South', '9': 'Death Row'
                },
                tierLabels: [
                    '⚠️ No Market Data',
                    '📉 Street Trash',
                    '🗑️ Basic Scrap',
                    '⚙️ Simple Part',
                    '📦 Common Scrap',
                    '🔵 Field Components',
                    '💎 Recovered Equipment',
                    '⭐ Restored Components',
                    '🔶 Valuable Equipment',
                    '👑 Secronom Find',
                    '💠 Death Row Find',
                    '🔥 Wasteland Find',
                    '🛡️ Raven Ridge Find',
                    '🌟 Experimental Component',
                    '🏆 Legendary Artifact'
                ],
                scrapLabels: [
                    'Useless Junk',
                    'Simple Scrap',
                    'Scavenger Scrap',
                    'Field Scrap',
                    'Reinforced Scrap',
                    'Quality Components',
                    'Market Components',
                    'Rare Components',
                    'Secronom Treasures',
                    'Wasteland Treasures',
                    'Death Row Treasures',
                    'Raven Ridge Treasures',
                    'Experimental Components',
                    'Legendary Artifacts'
                ],
                statsLabels: {
                    godcrafted: 'Godcrafted',
                    nearGodcrafted: 'Near Godcrafted',
                    angelcrafted: 'Angelcrafted',
                    hellcrafted: 'Hellcrafted',
                    mastercrafted: 'Mastercrafted',
                    mastercraftedMin: 'Mastercrafted (Min)',
                    normal: 'Normal'
                },
                statsShortLabels: {
                    godcrafted: 'GC',
                    nearGodcrafted: 'NGC',
                    angelcrafted: 'AC',
                    hellcrafted: 'HC',
                    mastercrafted: 'MC',
                    normal: ''
                },
                formatNames: {
                    single: '1-digit',
                    triple: '3-digit',
                    quad: '4-digit',
                    none: 'None',
                    unknown: 'Unknown'
                },
                tooltipTexts: {
                    scaleRange: 'Scale: 1.0x to 2.5x',
                    points: 'pts',
                    minimum: 'MIN',
                    maximum: 'MAX',
                    percentage: '%',
                    format: 'Format',
                    threshold: 'Threshold'
                },
                texts: {
                    market: 'Market',
                    scrap: 'Scrap',
                    baseRatio: 'Base Ratio',
                    stats: 'Stats',
                    scrapMultiplier: 'Scrap',
                    final: 'Final',
                    tradezone: 'Tradezone',
                    notAvailable: 'N/A',
                    forced: 'FORCED',
                    innerCity: 'INNER CITY',
                    actual: 'ACTUAL'
                }
            },
            'pt-br': {
                tradezoneNames: {
                    '21': 'Posto Avançado', '22': 'Acampamento Valcrest', '10': 'Wastelands',
                    '1': 'Noroeste', '2': 'Norte', '3': 'Nordeste', '4': 'Oeste',
                    '5': 'Central', '6': 'Leste', '7': 'Sudoeste', '8': 'Sul', '9': 'Death Row'
                },
                tierLabels: [
                    '⚠️ Sem Dados de Mercado',
                    '📉 Lixo de Rua',
                    '🗑️ Sucata Básica',
                    '⚙️ Peça Simples',
                    '📦 Sucata Comum',
                    '🔵 Componentes de Campo',
                    '💎 Equipamento Recuperado',
                    '⭐ Componentes Restaurados',
                    '🔶 Equipamento Valioso',
                    '👑 Achado do Secronom',
                    '💠 Achado da Death Row',
                    '🔥 Achado da Wasteland',
                    '🛡️ Achado de Raven Ridge',
                    '🌟 Componente Experimental',
                    '🏆 Artefato Lendário'
                ],
                scrapLabels: [
                    'Lixo Inutil',
                    'Sucatas Simples',
                    'Sucatas de Catador',
                    'Sucatas de Campo',
                    'Sucatas Reforçada',
                    'Componentes de Qualidade',
                    'Componentes de Mercado',
                    'Componentes Raros',
                    'Tesouros da Secronom',
                    'Tesouros da Wasteland',
                    'Tesouros da Death Row',
                    'Tesouros de Raven Ridge',
                    'Componente Experimentais',
                    'Artefatos Lendários'
                ],
                statsLabels: {
                    godcrafted: 'Godcrafted',
                    nearGodcrafted: 'Near Godcrafted',
                    angelcrafted: 'Angelcrafted',
                    hellcrafted: 'Hellcrafted',
                    mastercrafted: 'Mastercrafted',
                    mastercraftedMin: 'Mastercrafted (Mínimo)',
                    normal: 'Normal'
                },
                statsShortLabels: {
                    godcrafted: 'GC',
                    nearGodcrafted: 'NGC',
                    angelcrafted: 'AC',
                    hellcrafted: 'HC',
                    mastercrafted: 'MC',
                    normal: ''
                },
                formatNames: {
                    single: '1-dígito',
                    triple: '3-dígitos',
                    quad: '4-dígitos',
                    none: 'Nenhum',
                    unknown: 'Desconhecido'
                },
                tooltipTexts: {
                    scaleRange: 'Escala: 1.0x a 2.5x',
                    points: 'pts',
                    minimum: 'MÍN',
                    maximum: 'MÁX',
                    percentage: '%',
                    format: 'Formato',
                    threshold: 'Limite'
                },
                texts: {
                    market: 'Mercado',
                    scrap: 'Sucata',
                    baseRatio: 'Razão Base',
                    stats: 'Atributos',
                    scrapMultiplier: 'Sucata',
                    final: 'Valor Final',
                    tradezone: 'Zona',
                    notAvailable: 'Indisponível',
                    forced: 'FORÇADA',
                    innerCity: 'CENTRO',
                    actual: 'REAL'
                }
            }
        };

    const language = ConfigManager.get('language');
    const currentLang = LOCALIZATION[language] || LOCALIZATION['en-us'];

    return {
        getText: (key) => currentLang.texts[key],
        getStatsLabel: (key) => currentLang.statsLabels[key],
        getStatsShortLabel: (key) => currentLang.statsShortLabels[key],
        getFormatName: (key) => currentLang.formatNames[key],
        getTooltipText: (key) => currentLang.tooltipTexts[key],
        getTierLabel: (index) => currentLang.tierLabels[index] || currentLang.tierLabels[0],
        getScrapLabel: (index) => currentLang.scrapLabels[index] || currentLang.scrapLabels[0],
        getTradezoneName: (id) => currentLang.tradezoneNames[id] || 'Unknown',

        getTier: (ratio, market) => {
            const tiers = GLOBAL_CONSTANTS.TIERS;

            if (market <= 0) {
                return {
                    max: tiers[0].max,
                    color: tiers[0].color,
                    label: currentLang.tierLabels[0]
                };
            }

            for (let i = 0; i < tiers.length; i++) {
                if (ratio <= tiers[i].max) {
                    return {
                        max: tiers[i].max,
                        color: tiers[i].color,
                        label: currentLang.tierLabels[i]
                    };
                }
            }

            const lastIndex = tiers.length - 1;
            return {
                max: tiers[lastIndex].max,
                color: tiers[lastIndex].color,
                label: currentLang.tierLabels[lastIndex]
            };
        },

        getTiers: () => GLOBAL_CONSTANTS.TIERS.map((tier, index) => ({
            max: tier.max,
            color: tier.color,
            label: currentLang.tierLabels[index]
        })),

        getScrapMultiplierData: () => GLOBAL_CONSTANTS.SCRAP_MULTIPLIERS.map((data, index) => ({
            minScrap: data.minScrap,
            multiplier: data.multiplier,
            label: currentLang.scrapLabels[index]
        })),

        getAllTexts: () => ({...currentLang.texts}),
        getAllTooltipTexts: () => ({...currentLang.tooltipTexts})
    };
})();

    // ============================================
    // TRADEZONE MANAGER
    // ============================================
    // Manages tradezone detection ONLY for inner city inventory
    const TradezoneManager = (function() {
        const isInventoryPage = window.location.href.includes('DF3D_InventoryPage.php?page=31');
        const isMarketplacePage = window.location.href.includes('index.php?page=35');

        let tradezoneForced = false;

        function getCurrentTradezoneFromSilverScripts() {
            if (unsafeWindow.userVars?.DFSTATS_df_tradezone) {
                return unsafeWindow.userVars.DFSTATS_df_tradezone.toString();
            }
            if (unsafeWindow.userData?.tradezone) {
                return unsafeWindow.userData.tradezone.toString();
            }
            if (unsafeWindow.DFAPI?.userData?.tradezone) {
                return unsafeWindow.DFAPI.userData.tradezone.toString();
            }
            return '21';
        }

        function getTargetTradezone() {
            if (isMarketplacePage) {
                return getCurrentTradezoneFromSilverScripts();
            }
            if (isInventoryPage) {
                return '21';
            }
            return getCurrentTradezoneFromSilverScripts();
        }

        function shouldForceTradezone() {
            return isInventoryPage && !isMarketplacePage;
        }

        function forceTradezone(targetTradezone) {
            if (!targetTradezone || isMarketplacePage) return;

            const currentSilverTradezone = getCurrentTradezoneFromSilverScripts();
            const currentName = LocalizationService.getTradezoneName(currentSilverTradezone);
            const targetName = LocalizationService.getTradezoneName(targetTradezone);

            if (currentSilverTradezone !== targetTradezone) {
                if (unsafeWindow.userData) {
                    unsafeWindow.userData.tradezone = targetTradezone;
                }
                if (unsafeWindow.userVars?.DFSTATS_df_tradezone) {
                    unsafeWindow.userVars.DFSTATS_df_tradezone = targetTradezone;
                }
                if (unsafeWindow.DFAPI?.userData) {
                    unsafeWindow.DFAPI.userData.tradezone = targetTradezone;
                }

                tradezoneForced = true;
                console.log(`✅ Inner City: Tradezone forced from ${currentSilverTradezone} (${currentName}) to Outpost (21)`);
            } else {
                console.log(`ℹ️ Inner City: Already using Outpost (21)`);
            }
        }

        function interceptXHR() {
            const originalXHROpen = unsafeWindow.XMLHttpRequest.prototype.open;
            const originalXHRSend = unsafeWindow.XMLHttpRequest.prototype.send;

            unsafeWindow.XMLHttpRequest.prototype.open = function(method, url, ...args) {
                this._url = url;
                return originalXHROpen.call(this, method, url, ...args);
            };

            unsafeWindow.XMLHttpRequest.prototype.send = function(data) {
                if (this._url?.includes('trade_search.php') && data && typeof data === 'string' && shouldForceTradezone()) {
                    const targetTradezone = getTargetTradezone();
                    const match = RegexService.getTradezoneFromData(data);

                    if (match && match !== targetTradezone) {
                        data = data.replace(RegexService.TRADEZONE_PARAM, `tradezone=${targetTradezone}`);
                        console.log(`🔄 XHR Intercepted: ${match} → Outpost (21) for inner city`);
                    }

                    try {
                        const params = new URLSearchParams(data);
                        if (params.has('tradezone') && params.get('tradezone') !== targetTradezone) {
                            params.set('tradezone', targetTradezone);
                            data = params.toString();
                        }
                    } catch (e) {
                    }
                }
                return originalXHRSend.call(this, data);
            };
        }

        function getCurrentTradezoneInfo() {
            const actualTradezoneId = getCurrentTradezoneFromSilverScripts();
            const actualTradezoneName = LocalizationService.getTradezoneName(actualTradezoneId);

            const targetTradezone = getTargetTradezone();
            const shouldForce = shouldForceTradezone();

            let reason = 'normal';
            if (isMarketplacePage) {
                reason = 'marketplace';
            } else if (isInventoryPage) {
                reason = 'innerCity';
            }

            const displayTradezoneName = LocalizationService.getTradezoneName(targetTradezone);

            return {
                displayId: targetTradezone,
                displayName: displayTradezoneName,
                actualId: actualTradezoneId,
                actualName: actualTradezoneName,
                forced: shouldForce,
                reason: reason,
                showActual: shouldForce && actualTradezoneId !== targetTradezone
            };
        }

        function init() {
            /*console.log('🚀 TradezoneManager initializing...');
            console.log(`📍 Page detection - Inventory: ${isInventoryPage}, Marketplace: ${isMarketplacePage}`);*/

            if (isMarketplacePage) {
                //console.log('🛒 Marketplace detected - Showing ACTUAL tradezone prices');
                interceptXHR();
                return;
            }

            if (isInventoryPage) {
                //console.log('🏙️ Inner City Inventory detected - Forcing to Outpost for scrap ratios');
                const targetTradezone = getTargetTradezone();

                setTimeout(() => {
                    forceTradezone(targetTradezone);
                }, 1500);

                const forceInterval = setInterval(() => {
                    if (!tradezoneForced) {
                        forceTradezone(targetTradezone);
                    }
                }, 500);

                /*setTimeout(() => {
                    clearInterval(forceInterval);
                    if (!tradezoneForced) {
                        console.warn('⚠️ Could not force tradezone to Outpost');
                    }
                }, 10000);*/

                interceptXHR();
            } else {
                //console.log('📊 Normal page - Using ACTUAL tradezone');
                interceptXHR();
            }
        }

        return {
            init,
            getCurrentTradezoneInfo,
            isForcing: () => shouldForceTradezone(),
            getReason: () => {
                if (isMarketplacePage) return 'Marketplace - Actual prices';
                if (isInventoryPage) return 'Inner City - Forced to Outpost (21)';
                return 'Normal page - Actual tradezone';
            },
            isForced: () => tradezoneForced,
            isMarketplacePage: () => isMarketplacePage,
            getTargetTradezone
        };
    })();
    // ============================================
    // ITEM CALCULATION SERVICE
    // ============================================
    // Performs core calculations for market prices, scrap values, stats multipliers, and ratios
    const ItemCalculationService = (function() {
        const SPECIAL_STATS = {
            'GC': {
                patterns: ['3', '2424', '888'],
                labelKey: 'godcrafted'
            },
            'NGC': {
                patterns: ['2', '2324', '2423', '788', '878', '887'],
                labelKey: 'nearGodcrafted'
            },
            'AC': {
                patterns: ['777'],
                labelKey: 'angelcrafted'
            },
            'HC': {
                patterns: ['666'],
                labelKey: 'hellcrafted'
            }
        };

        const STATS_RANGES = {
            'single': {
                min: '0',
                max: '3',
                minPoints: 0,
                maxPoints: 3
            },
            'triple': {
                min: '000',
                max: '888',
                minPoints: 0,
                maxPoints: 24
            },
            'quad': {
                min: '0000',
                max: '2424',
                minPoints: 0,
                maxPoints: 48
            }
        };

        function parseStatsString(stats) {
            if (!stats) return {
                totalPoints: 0,
                digits: [],
                format: 'none',
                maxPoints: 0,
                minPoints: 0,
                isMinimum: false,
                isMaximum: false
            };

            const digits = RegexService.getNumbers(stats);
            const format = RegexService.getStatsFormat(stats);
            const range = STATS_RANGES[format];

            if (!range) {
                return {
                    totalPoints: 0,
                    digits: [],
                    format: 'unknown',
                    maxPoints: 0,
                    minPoints: 0,
                    isMinimum: false,
                    isMaximum: false
                };
            }

            const isMinimum = stats === range.min;
            const isMaximum = stats === range.max;

            let totalPoints = 0;
            let parsedDigits = [];

            if (format === 'single') {
                const points = parseInt(digits);
                totalPoints = points;
                parsedDigits = [points];
            }
            else if (format === 'triple') {
                const point1 = parseInt(digits.charAt(0));
                const point2 = parseInt(digits.charAt(1));
                const point3 = parseInt(digits.charAt(2));
                totalPoints = point1 + point2 + point3;
                parsedDigits = [point1, point2, point3];
            }
            else if (format === 'quad') {
                const point1 = parseInt(digits.substring(0, 2));
                const point2 = parseInt(digits.substring(2, 4));
                totalPoints = point1 + point2;
                parsedDigits = [point1, point2];
            }

            return {
                totalPoints,
                digits: parsedDigits,
                format: format,
                maxPoints: range.maxPoints,
                minPoints: range.minPoints,
                isMinimum: isMinimum,
                isMaximum: isMaximum,
                range: range
            };
        }

        function calculateMultiplier(parsedStats) {
            const { totalPoints, minPoints, maxPoints, isMinimum, isMaximum } = parsedStats;

            if (isMinimum) {
                return 1.0;
            }

            if (isMaximum) {
                return 2.5;
            }

            const percentage = (totalPoints - minPoints) / (maxPoints - minPoints);
            const multiplier = 1.0 + (percentage * 1.5);

            return Number(multiplier.toFixed(2));
        }

        function calculateProgressiveScrapMultiplier(scrap) {
            const scrapMultipliers = GLOBAL_CONSTANTS.SCRAP_MULTIPLIERS;

            if (scrap <= 0) return 0;

            if (scrap < scrapMultipliers[0].minScrap) {
                const percentage = scrap / scrapMultipliers[0].minScrap;
                return Number((percentage * scrapMultipliers[0].multiplier).toFixed(2));
            }

            let lowerPoint = scrapMultipliers[0];
            let upperPoint = scrapMultipliers[scrapMultipliers.length - 1];

            for (let i = 0; i < scrapMultipliers.length - 1; i++) {
                if (scrap >= scrapMultipliers[i].minScrap && scrap < scrapMultipliers[i + 1].minScrap) {
                    lowerPoint = scrapMultipliers[i];
                    upperPoint = scrapMultipliers[i + 1];
                    break;
                }
            }

            if (scrap >= scrapMultipliers[scrapMultipliers.length - 1].minScrap) {
                return scrapMultipliers[scrapMultipliers.length - 1].multiplier;
            }

            const scrapRange = upperPoint.minScrap - lowerPoint.minScrap;
            const multiplierRange = upperPoint.multiplier - lowerPoint.multiplier;

            if (scrapRange === 0) return lowerPoint.multiplier;

            const position = (scrap - lowerPoint.minScrap) / scrapRange;
            const multiplier = lowerPoint.multiplier + (multiplierRange * position);

            return Number(multiplier.toFixed(2));
        }

        function getStatsLabelAndShort(stats, parsedStats) {
            for (const [key, data] of Object.entries(SPECIAL_STATS)) {
                if (data.patterns.includes(stats)) {
                    return {
                        label: LocalizationService.getStatsLabel(data.labelKey),
                        shortLabel: LocalizationService.getStatsShortLabel(data.labelKey),
                        isSpecialVariant: true,
                        typeKey: data.labelKey
                    };
                }
            }

            if (stats === '1') {
                return {
                    label: LocalizationService.getStatsLabel('mastercrafted'),
                    shortLabel: LocalizationService.getStatsShortLabel('mastercrafted'),
                    isSpecialVariant: true,
                    typeKey: 'mastercrafted'
                };
            }

            const isMinimum = parsedStats.isMinimum;

            if (isMinimum) {
                return {
                    label: LocalizationService.getStatsLabel('mastercraftedMin'),
                    shortLabel: LocalizationService.getStatsShortLabel('mastercrafted'),
                    isSpecialVariant: false,
                    typeKey: 'mastercraftedMin'
                };
            }

            return {
                label: LocalizationService.getStatsLabel('mastercrafted'),
                shortLabel: LocalizationService.getStatsShortLabel('mastercrafted'),
                isSpecialVariant: false,
                typeKey: 'mastercrafted'
            };
        }

        function getStatsInfo(stats) {
            if (!stats) {
                return {
                    label: LocalizationService.getStatsLabel('normal'),
                    shortLabel: LocalizationService.getStatsShortLabel('normal'),
                    multiplier: 1,
                    isSpecialVariant: false,
                    points: 0,
                    maxPoints: 0,
                    minPoints: 0,
                    originalDigits: [],
                    format: 'none',
                    formatName: LocalizationService.getFormatName('none'),
                    statsCode: null,
                    percentage: 0,
                    isMinimum: false,
                    isMaximum: false
                };
            }

            const parsed = parseStatsString(stats);
            const multiplier = calculateMultiplier(parsed);
            const labelInfo = getStatsLabelAndShort(stats, parsed);
            const formatName = LocalizationService.getFormatName(parsed.format);

            const percentage = parsed.maxPoints > 0 ?
                Math.round(((parsed.totalPoints - parsed.minPoints) / (parsed.maxPoints - parsed.minPoints)) * 100) : 0;

            return {
                label: labelInfo.label,
                shortLabel: labelInfo.shortLabel,
                multiplier: multiplier,
                isSpecialVariant: labelInfo.isSpecialVariant,
                points: parsed.totalPoints,
                maxPoints: parsed.maxPoints,
                minPoints: parsed.minPoints,
                originalDigits: parsed.digits,
                format: parsed.format,
                formatName: formatName,
                statsCode: stats,
                percentage: percentage,
                isMinimum: parsed.isMinimum,
                isMaximum: parsed.isMaximum,
                typeKey: labelInfo.typeKey
            };
        }

        function getScrapMultiplier(scrap) {
            const scrapMultiplierData = LocalizationService.getScrapMultiplierData();

            const multiplier = calculateProgressiveScrapMultiplier(scrap);

            let labelIndex = 0;
            for (let i = 0; i < scrapMultiplierData.length; i++) {
                if (scrap >= scrapMultiplierData[i].minScrap) {
                    labelIndex = i;
                } else {
                    break;
                }
            }

            const label = LocalizationService.getScrapLabel(labelIndex);
            const minScrap = scrapMultiplierData[labelIndex]?.minScrap || 0;

            return {
                multiplier: multiplier,
                label: label,
                minScrap: minScrap
            };
        }

        function isAmmo(type) {
            if (!RegexService.AMMO_SUFFIX.test(type)) return false;

            const baseType = RegexService.normalizeType(type);
            const ammoTypes = [
                '32ammo','35ammo','357ammo','38ammo','40ammo','45ammo','50ammo',
                '55ammo','20gaugeammo','16gaugeammo','12gaugeammo','10gaugeammo',
                'grenadeammo','heavygrenadeammo','55rifleammo','75rifleammo',
                '9rifleammo','127rifleammo','14rifleammo','fuelammo'
            ];
            return ammoTypes.includes(baseType);
        }

        function isArmour(type) {
            const base = RegexService.normalizeType(type);
            const itemData = unsafeWindow.globalData?.[base];
            return itemData?.itemtype === 'armour';
        }

        function getMarketDB() {
            return unsafeWindow.getSilverItemsDataBank?.() || null;
        }

        function getMarketPrice(type, qty = 1) {
            const db = getMarketDB();
            if (!db) return 0;
            const base = RegexService.normalizeType(type);
            const item = db[base];
            if (!item) return 0;
            const price = item.bestPricePerUnit || item.averagePricePerUnit || 0;
            return isArmour(type) ? price : price * qty;
        }

        function getScrap(type, qty) {
            if (!unsafeWindow.scrapValue) return 0;
            if (isArmour(type)) {
                const base = RegexService.normalizeType(type);
                const data = unsafeWindow.globalData?.[base];
                if (data?.hp) return unsafeWindow.scrapValue(type, parseInt(data.hp));
            }
            return unsafeWindow.scrapValue(type, qty);
        }

        function getEffectiveScrap(scrap) {
            if (scrap <= 20) return scrap * 10000;
            if (scrap <= 500) return 500;
            return scrap;
        }


        function calculateRatios(market, scrap, statsMultiplier, scrapMultiplier, statsPresent, isSpecialVariant) {
            const effectiveScrapMultiplier = statsPresent ? 1 : scrapMultiplier;
            const effectiveScrap = getEffectiveScrap(scrap);
            const minEffectiveScrap = ConfigManager.get('minEffectiveScrap');

            const combinedValue = statsPresent ? (market + (scrap / statsMultiplier)) : market;

            const baseRatio = combinedValue > 0 ? (combinedValue / Math.max(effectiveScrap, minEffectiveScrap)) : 0;
            const totalMultiplier = statsMultiplier * effectiveScrapMultiplier;
            let finalRatio = baseRatio * totalMultiplier;

            const displayRatio = combinedValue > 0 ?
                (combinedValue / Math.max(effectiveScrap, minEffectiveScrap)) * statsMultiplier * effectiveScrapMultiplier : 0;

            return {
                baseRatio,
                totalMultiplier,
                finalRatio,
                displayRatio,
                effectiveScrapMultiplier,
                combinedValue,
                statsMultiplierApplied: statsPresent ? statsMultiplier : 1
            };
        }

        return {
            getStatsInfo,
            getScrapMultiplier,
            isAmmo,
            isArmour,
            getMarketPrice,
            getScrap,
            getEffectiveScrap,
            calculateRatios,
            isMastercrafted: RegexService.isMastercrafted,
            getStatsCode: RegexService.getStatsCode
        };
    })();

    // ============================================
    // UI MANAGER
    // ============================================
    // Handles tooltip creation, positioning, and visual styling for item highlighting
    const UIManager = (function() {
        let infoBox = null;
        let isDragging = false;
        let dragStartX = 0;
        let dragStartY = 0;
        const DRAG_THRESHOLD = 5;

        function createTooltip() {
            infoBox = document.createElement('div');
            infoBox.style.cssText = `
                position: absolute;
                pointer-events: none;
                background-color: rgba(0,0,0,0.9);
                color: #fff;
                padding: 10px 14px;
                border-radius: 6px;
                font-size: 12px;
                font-family: "Courier New", monospace;
                box-shadow: 0 4px 12px rgba(0,0,0,0.6);
                z-index: 9999;
                display: none;
                white-space: nowrap;
                border: 2px solid transparent;
                line-height: 1.5em;
                min-width: 180px;
            `;
            document.body.appendChild(infoBox);

            setupDragDetection();

            return infoBox;
        }

        function setupDragDetection() {
            document.addEventListener('mousedown', (e) => {
                const item = e.target.closest('.item[data-type]');
                if (item) {
                    isDragging = false;
                    dragStartX = e.clientX;
                    dragStartY = e.clientY;

                    UIManager.hideTooltip();
                }
            }, true);

            // Listen for mousemove to detect dragging
            document.addEventListener('mousemove', (e) => {
                if (dragStartX !== 0 || dragStartY !== 0) {
                    const deltaX = Math.abs(e.clientX - dragStartX);
                    const deltaY = Math.abs(e.clientY - dragStartY);

                    if (deltaX > DRAG_THRESHOLD || deltaY > DRAG_THRESHOLD) {
                        isDragging = true;
                        UIManager.hideTooltip();
                    }
                }
            }, true);

            document.addEventListener('mouseup', () => {
                isDragging = false;
                dragStartX = 0;
                dragStartY = 0;
            }, true);
        }

        function applySpecialBorders(item, tierColor, stats, market = 1) {
            item.style.border = '2px solid transparent';
            item.style.backgroundColor = '';
            item.style.boxShadow = '';

            if (!stats) {
                if (market <= 0) {
                    item.style.border = `1.8px dashed ${tierColor}`;
                    return '';
                }
                return null;
            }

            const statsInfo = ItemCalculationService.getStatsInfo(stats);
            const shortLabel = statsInfo.shortLabel;

            switch(shortLabel) {
                case 'GC':
                    item.style.border = `2px solid ${tierColor}`;
                    item.style.boxShadow = `1px 1px 5px 1px #FFD60050 inset`;
                    item.style.backgroundColor = `#FFD60025`;
                    break;
                case 'NGC':
                    item.style.border = `2px solid ${tierColor}`;
                    item.style.boxShadow = `1px 1px 5px 1px #FFFFFF50 inset`;
                    item.style.backgroundColor = `#FFFFFF35`;
                    break;
                case 'AC':
                    item.style.border = `2px solid ${tierColor}`;
                    item.style.boxShadow = `1px 1px 5px 1px #D4D4D450 inset`;
                    item.style.backgroundColor = `#D4D4D435`;
                    break;
                case 'HC':
                    item.style.border = `2px solid ${tierColor}`;
                    item.style.boxShadow = `1px 1px 5px 1px #D5000050 inset`;
                    item.style.backgroundColor = `#D5000035`;
                    break;
                case 'MC':
                default:
                    item.style.border = `2px solid ${tierColor}`;
                    item.style.boxShadow = `1px 1px 5px 1px #995A1A50 inset`;
                    item.style.backgroundColor = `#995A1A35`;
                    break;
            }

            return shortLabel;
        }

        function createTooltipHTML(itemData) {
            const {
                shortType, mcLabel, mcFullLabel, tier, tradezone, texts,
                market, scrap, baseRatio, statsMultiplier,
                statsLabel, points, maxPoints, minPoints, originalDigits, format, formatName, percentage, isMinimum, isMaximum,
                scrapMultiplier, scrapLabel, scrapMinScrap, totalMultiplier, finalRatio,
                scrapMultiplierStyle, stats, isSpecialVariant, combinedValue, showTilde
            } = itemData;

            const tooltipTexts = LocalizationService.getAllTooltipTexts();

            let displayIndicator = '';
            let displayColor = '';
            if (tradezone.forced) {
                if (tradezone.reason === 'innerCity') {
                    displayIndicator = `(${texts.innerCity})`;
                    displayColor = '#00CED1';
                } else {
                    displayIndicator = `(${texts.forced})`;
                    displayColor = '#FFD700';
                }
            }

            let tradezoneHtml = '';
            if (tradezone.showActual) {
                tradezoneHtml = `<div style="margin-bottom:3px;"><span style="color:#AAA">${texts.tradezone} (${texts.forced}) : </span><span style="color:#00BFFF">${tradezone.displayName}</span></div>`;
            } else {
                tradezoneHtml = `<div style="margin-bottom:3px;"><span style="color:#AAA">${texts.tradezone}: </span><span style="color:#00BFFF">${tradezone.displayName}</span>${displayIndicator ? '<span style="color:'+displayColor+';font-size:10px;font-weight:bold;"> '+displayIndicator+'</span>' : ''}</div>`;
            }

            const displayMarketValue = (showTilde ? '± ' : '') + Math.floor(combinedValue || market).toLocaleString();

            let statsDetails = '';
            let scaleInfo = '';

            if (stats && format !== 'none') {
                /*if (originalDigits.length > 0) {
                    if (format === 'triple') { statsDetails = ` (${originalDigits[0]}/${originalDigits[1]}/${originalDigits[2]})`;
                    } else if (format === 'quad') { statsDetails = ` (${originalDigits[0]}/${originalDigits[1]})`;
                    } else if (format === 'single') { statsDetails = ` (${originalDigits[0]})`;
                    }
                }*/

                if (isMinimum) {
                    statsDetails += ` - ${points}/${maxPoints} ${tooltipTexts.points}<!--(${tooltipTexts.minimum})-->`;
                    scaleInfo = tooltipTexts.scaleRange;
                } else
                  if (isMaximum) {
                    statsDetails += ` - ${points}/${maxPoints} ${tooltipTexts.points}<!--(${tooltipTexts.maximum})-->`;
                    scaleInfo = tooltipTexts.scaleRange;
                } else {
                    statsDetails += ` - ${points}/${maxPoints} ${tooltipTexts.points}`;
                    //if (percentage > 0) { statsDetails += ` (${percentage}${tooltipTexts.percentage})`;}
                    scaleInfo = tooltipTexts.scaleRange;
                }

                //if (formatName && formatName !== 'None' && formatName !== 'Nenhum') { statsDetails += ` [${formatName}]`;}
            }

            let scrapMultiplierInfo = '';
            if (!stats && scrapMultiplier !== 1) {
                const scrapValue = Math.floor(scrap).toLocaleString();
                const multiplierValue = scrapMultiplier.toFixed(2);
                scrapMultiplierInfo = `<div style="margin-bottom:1px;margin-left:12px;font-size:11px;${scrapMultiplierStyle}">↳ ${texts.scrapMultiplier}: ${multiplierValue}x</div>`;
                scrapMultiplierInfo += `<div style="margin-bottom:3px;margin-left:24px;font-size:10px;color:#888;">(${scrapLabel}<!-- - ${scrapValue} scrap-->)</div>`;

                //if (scrapMinScrap > 0) { scrapMultiplierInfo += `<div style="margin-bottom:3px;margin-left:32px;font-size:9px;color:#666;">${tooltipTexts.threshold}: ${scrapMinScrap.toLocaleString()}+</div>`;}
            }

            return `
                <div style="text-align:center;font-weight:bold;padding-bottom:6px;margin-bottom:6px;font-size:14px;border-bottom:1px solid #333;">
                    ${shortType}${mcLabel ? ' <span style="color:'+tier.color+'">['+mcLabel+']</span>' : ''}
                </div>
                <div style="text-align:left;font-size:12px;">
                    ${tradezoneHtml}
                    <div style="margin-bottom:3px;">
                        <span style="color:#AAA">${texts.market}:</span>
                        <span style="color:#4AF">${market > 0 ? displayMarketValue : texts.notAvailable}</span>
                        ${showTilde ? '<span style="color:#FFD700;margin-left:4px;" title="Market + Scrap value">⚠️</span>' : ''}
                    </div>
                    <div style="margin-bottom:3px;"><span style="color:#AAA">${texts.scrap}:</span> <span style="color:#F84">${Math.floor(scrap).toLocaleString()}</span></div>
                    <div style="margin-bottom:3px;"><span style="color:#AAA">${texts.baseRatio}:</span> <span style="color:#0F0;font-weight:bold">${market > 0 ? baseRatio.toFixed(2)+'x' : '?'}</span></div>
                    ${market > 0 && statsMultiplier > 1 ? `<div style="margin-bottom:1px;margin-left:12px;font-size:11px;color:#FFD700">↳ ${texts.stats}: ${statsMultiplier}x</div>` : ''}
                    ${market > 0 && statsMultiplier > 1 ? `<div style="margin-bottom:3px;margin-left:24px;font-size:10px;color:#888;">(${mcFullLabel}${statsDetails})</div>` : ''}
                    ${scrapMultiplierInfo}
                    ${market > 0 && totalMultiplier !== 1 ? `<div style="margin-bottom:3px;margin-left:12px;font-size:11px;color:#00FF00;font-weight:bold;">↳ ${texts.final}: ${finalRatio.toFixed(2)}x</div>` : ''}
                    <div style="color:${tier.color};font-weight:bold;text-align:center;margin-top:6px;padding-top:6px;border-top:1px solid #333;">${tier.label}</div>
                </div>
            `;
        }

        return {
            init: createTooltip,
            applySpecialBorders,
            createTooltipHTML,
            getTooltip: () => infoBox,
            showTooltip: (html, tierColor) => {
                if (!infoBox || isDragging) return; // Don't show if dragging
                infoBox.innerHTML = html;
                infoBox.style.display = 'block';
                infoBox.style.border = `2px solid ${tierColor}`;
            },
            hideTooltip: () => {
                if (infoBox) infoBox.style.display = 'none';
            },
            positionTooltip: (x, y) => {
                if (!infoBox || isDragging) return; // Don't position if dragging
                infoBox.style.left = x + 'px';
                infoBox.style.top = y + 'px';
            },
            isDragging: () => isDragging
        };
    })();

    // ============================================
    // ITEM PROCESSOR
    // ============================================
    // Processes individual game items, applies visual effects, and attaches tooltip events
    const ItemProcessor = (function() {
        function processItem(item) {
            if (!item?.dataset?.type) return;

            const type = item.dataset.type;
            const qty = parseInt(item.dataset.quantity || 1, 10);
            const stats = ItemCalculationService.getStatsCode(type);

            const slot = item.closest('.validSlot');
            if (ConfigManager.get('ignoreLocked') && slot && slot.classList.contains('locked')) {
                item.style.border = '';
                item.style.boxShadow = '';
                item.style.backgroundColor = '';
                item.style.transform = '';
                item.style.zIndex = '';

                item.onmouseenter = null;
                item.onmousemove = null;
                item.onmouseleave = null;
                return;
            }

            if (ConfigManager.get('ignoreAmmo') && ItemCalculationService.isAmmo(type)) return;
            if (ConfigManager.get('ignoreMC') && ItemCalculationService.isMastercrafted(type)) return;

            const scrap = ItemCalculationService.getScrap(type, qty);
            if (scrap < ConfigManager.get('minScrapValue')) return;

            const market = ItemCalculationService.getMarketPrice(type, qty);
            if (ConfigManager.get('ignoreNT') && market <= 0 && !stats) return;

            const statsInfo = ItemCalculationService.getStatsInfo(stats);

            const scrapMultiplierData = ItemCalculationService.getScrapMultiplier(scrap);

            const ratios = ItemCalculationService.calculateRatios(
                market, scrap, statsInfo.multiplier, scrapMultiplierData.multiplier, !!stats, statsInfo.isSpecialVariant
            );

            const tier = LocalizationService.getTier(ratios.displayRatio, market);
            let mcLabel = UIManager.applySpecialBorders(item, tier.color, stats, market);

            if (!stats && market > 0) {
                item.style.border = `2px solid ${tier.color}`;
                item.style.boxShadow = 'none';
                item.style.backgroundColor = '';
            }

            const tradezone = TradezoneManager.getCurrentTradezoneInfo();
            const texts = LocalizationService.getAllTexts();
            const shortType = type.length > 25 ? type.substring(0, 22) + '...' : type;
            const scrapMultiplierStyle = scrapMultiplierData.multiplier < 1 ? 'color:#FF5555' : scrapMultiplierData.multiplier > 1 ? 'color:#FFD700' : 'color:#FFFFFF';

            const isTierZero = tier === LocalizationService.getTiers()[0];
            const showTildeWarning = !!stats && !isTierZero && market > 0;

            const tooltipData = {
                shortType,
                mcLabel: statsInfo.shortLabel,
                mcFullLabel: statsInfo.label,
                tier,
                tradezone,
                texts: texts,
                market,
                scrap,
                baseRatio: ratios.baseRatio,
                statsMultiplier: statsInfo.multiplier,
                statsLabel: statsInfo.label,
                points: statsInfo.points,
                maxPoints: statsInfo.maxPoints,
                minPoints: statsInfo.minPoints,
                originalDigits: statsInfo.originalDigits,
                format: statsInfo.format,
                formatName: statsInfo.formatName,
                percentage: statsInfo.percentage,
                isMinimum: statsInfo.isMinimum,
                isMaximum: statsInfo.isMaximum,
                scrapMultiplier: scrapMultiplierData.multiplier,
                scrapLabel: scrapMultiplierData.label,
                scrapMinScrap: scrapMultiplierData.minScrap,
                totalMultiplier: ratios.totalMultiplier,
                finalRatio: ratios.finalRatio,
                scrapMultiplierStyle,
                stats,
                isSpecialVariant: statsInfo.isSpecialVariant,
                combinedValue: ratios.combinedValue,
                showTilde: showTildeWarning
            };

            const html = UIManager.createTooltipHTML(tooltipData);

            item.onmouseenter = (e) => {
                if (UIManager.isDragging()) return;

                UIManager.showTooltip(html, tier.color);

                const ssTooltip = unsafeWindow.infoBox;
                if (ssTooltip && ssTooltip.style.display !== 'none') {
                    const rect = ssTooltip.getBoundingClientRect();
                    UIManager.positionTooltip(rect.right + 8 + window.scrollX, rect.top + window.scrollY);
                } else {
                    const rect = item.getBoundingClientRect();
                    UIManager.positionTooltip(rect.right + 8 + window.scrollX, rect.top + window.scrollY);
                }

                item.style.transform = 'scale(1.1)';
                item.style.zIndex = '1000';
            };

            item.onmousemove = (e) => {
                if (UIManager.isDragging()) return;

                const ssTooltip = unsafeWindow.infoBox;
                if (ssTooltip && ssTooltip.style.display !== 'none') {
                    const rect = ssTooltip.getBoundingClientRect();
                    UIManager.positionTooltip(rect.right + 8 + window.scrollX, rect.top + window.scrollY);
                } else {
                    const offset = 10;
                    UIManager.positionTooltip(e.pageX + offset, e.pageY + offset);
                }
            };

            item.onmouseleave = () => {
                UIManager.hideTooltip();
                item.style.transform = 'scale(1)';
                item.style.zIndex = '';
            };

            item.addEventListener('mousedown', () => {
                UIManager.hideTooltip();
            });
        }

        function processAll() {
            document.querySelectorAll('.item[data-type]').forEach(processItem);
        }

        return {
            processItem,
            processAll
        };
    })();

    // ============================================
    // MAIN INITIALIZATION
    // ============================================
    // Entry point that initializes all services and starts the item processing loop
    function init() {
        TradezoneManager.init();

        UIManager.init();

        let observerTimeout;
        const observer = new MutationObserver((mutations) => {
            const relevantMutations = mutations.filter(mutation => {
                return !mutation.target.closest?.('.locked');
            });

            if (relevantMutations.length > 0) {
                clearTimeout(observerTimeout);
                observerTimeout = setTimeout(() => {
                    ItemProcessor.processAll();
                }, 300);
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['class']
        });

        setTimeout(() => {
            ItemProcessor.processAll();

            const tradezoneInfo = TradezoneManager.getCurrentTradezoneInfo();

            console.log(`✅ Market / Scrap Ratio v2.6.2 loaded (${ConfigManager.get('language')})`);
            /*console.log(`📍 ACTUAL Tradezone (SilverScripts): ${tradezoneInfo.actualId} (${tradezoneInfo.actualName})`);
            console.log(`📍 DISPLAY Tradezone: ${tradezoneInfo.displayId} (${tradezoneInfo.displayName})`);
            console.log(`📍 FORCED: ${tradezoneInfo.forced ? 'YES' : 'NO'}`);
            console.log(`📍 Mode: ${TradezoneManager.isForcing() ? 'FORCING tradezone' : 'NORMAL (not forcing)'}`);
            if (TradezoneManager.isForcing()) {
                console.log(`📍 Reason: ${TradezoneManager.getReason()}`);
                console.log(`📍 Force Status: ${TradezoneManager.isForced() ? 'Success' : 'Failed'}`);
            }*/
        }, 2500);
    }

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