OC Success Chance 2.0

Optimized OC success chance calculator with debug

当前为 2025-03-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         OC Success Chance 2.0
// @namespace    http://tampermonkey.net/
// @version      2.0.8
// @description  Optimized OC success chance calculator with debug
// @author       Allenone [2033011]
// @match        https://www.torn.com/factions.php?step=your*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @connect      tornprobability.com
// @grant        GM.xmlHttpRequest
// @grant        GM_info
// ==/UserScript==

(function() {
    'use strict';
    const DEBUG = false; // Set to false for production
    const ocs = new Map();
    let observer;

    // Add parameter mapping for each OC type
    const PARAM_MAP = {
        'Blast From The Past': {
            'slot-P1': 'Picklock1',
            'slot-P2': 'Hacker1',
            'slot-P3': 'Engineer1',
            'slot-P4': 'Bomber1',
            'slot-P5': 'Muscle1',
            'slot-P6': 'Picklock2'
        },
        'Break The Bank': {
            'slot-P1': 'Robber1',
            'slot-P2': 'Muscle1',
            'slot-P3': 'Muscle2',
            'slot-P4': 'Thief1',
            'slot-P5': 'Muscle3',
            'slot-P6': 'Thief2'
        }
    };

    // Enhanced logging
    function log(...args) {
        if (DEBUG) console.log('[OC Success]', ...args);
    }

    function logError(...args) {
        console.error('[OC Success]', ...args);
    }

    // DOM observer configuration
    const observerConfig = {
        childList: true,
        subtree: true,
        attributes: false,
        characterData: false
    };

    // Centralized API caller with timeout
    async function callOCAPI(endpoint, data, timeout = 5000) {
        return new Promise((resolve, reject) => {
            const controller = new AbortController();
            const timeoutId = setTimeout(() => {
                controller.abort();
                reject(new Error(`API call timed out after ${timeout}ms`));
            }, timeout);

            GM.xmlHttpRequest({
                method: 'POST',
                url: `https://tornprobability.com:3000/${endpoint}`,
                headers: { 'Content-Type': 'application/json' },
                data: JSON.stringify(data),
                signal: controller.signal,
                onload: (response) => {
                    clearTimeout(timeoutId);
                    try {
                        const result = JSON.parse(response.responseText);
                        log('API response:', result);
                        resolve(result);
                    } catch (err) {
                        logError('API parse error:', err);
                        reject(err);
                    }
                },
                onerror: (err) => {
                    clearTimeout(timeoutId);
                    logError('API request failed:', err);
                    reject(err);
                }
            });
        });
    }

    // Process individual OC element with validation
    async function processOCElement(element) {
        try {
            if (!element || !element.querySelector) {
                logError('Invalid element passed to processOCElement');
                return;
            }

            const ocName = element.querySelector('.panelTitle___aoGuV')?.textContent?.trim();
            log('Processing OC:', ocName);

            if (!PARAM_MAP[ocName]) {
                log('Skipping unsupported OC:', ocName);
                return;
            }

            const slots = element.querySelectorAll('.wrapper___Lpz_D');
            const successChances = {};

            for (const slot of slots) {
                try {
                    const fiberKey = Object.keys(slot).find(k => k.startsWith('__reactFiber$'));
                    if (!fiberKey) {
                        logError('No React fiber found for slot');
                        continue;
                    }

                    const fiberNode = slot[fiberKey];
                    const key = fiberNode?.return?.key;
                    const chanceText = slot.querySelector('.successChance___ddHsR')?.textContent;

                    if (key && chanceText) {
                        // Map slot key to API parameter name
                        const paramName = PARAM_MAP[ocName][key];
                        if (paramName) {
                            successChances[paramName] = parseFloat(chanceText.replace('%', ''));
                            log(`Mapped ${key} → ${paramName}: ${successChances[paramName]}%`);
                        }
                    }
                } catch (error) {
                    logError('Slot processing error:', error);
                }
            }

            if (Object.keys(successChances).length === 0) {
                logError('No success chances found for', ocName);
                return;
            }

            const endpointMap = {
                'Blast From The Past': 'BlastFromThePast',
                'Break The Bank': 'BreakTheBank'
            };

            const endpoint = endpointMap[ocName];
            if (!endpoint) {
                logError('No endpoint mapping for:', ocName);
                return;
            }

            const result = await callOCAPI(endpoint, successChances);
            if (result?.successChance) {
                ocs.set(ocName, result.successChance);
                injectSuccessChance(element, result.successChance);
            }
        } catch (error) {
            logError('OC processing failed:', error);
        }
    }


    // Main processor with mutation observer
    function processOCs() {
        log('Scanning for OC elements...');
        document.querySelectorAll('.wrapper___U2Ap7:not(.oc-processed)').forEach(element => {
            try {
                element.classList.add('oc-processed');
                processOCElement(element);
            } catch (error) {
                logError('Element processing error:', error);
            }
        });
    }

    // Enhanced injection with styling
    function injectSuccessChance(element, chance) {
        try {
            const displayClass = 'oc-success-display';
            let display = element.querySelector(`.${displayClass}`);

            if (!display) {
                display = document.createElement('p');
                display.className = displayClass;
                element.querySelector('.panelTitle___aoGuV')?.after(display);
            }

            display.innerText = `Success Chance: ${(chance * 100).toFixed(2)}%`;
            log('Injected success chance:', chance);
        } catch (error) {
            logError('Injection failed:', error);
        }
    }

    // Initialize observer and event listeners
    function initialize() {
        log('Initializing script...');

        // Main observer
        const rootNode = document.querySelector('#factionCrimes-root, #faction-crimes-root') || document.body;
        observer = new MutationObserver(processOCs);
        observer.observe(rootNode, observerConfig);

        // Tab change handler
        document.querySelector('.buttonsContainer___aClaa')?.addEventListener('click', () => {
            log('OC tab changed - reprocessing...');
            document.querySelectorAll('.wrapper___U2Ap7.oc-processed').forEach(el => {
                el.classList.remove('oc-processed');
            });
            setTimeout(processOCs, 500);
        });

        // Initial processing
        processOCs();
    }

    // Start script
    if (document.readyState === 'complete') {
        initialize();
    } else {
        window.addEventListener('load', initialize);
    }
})();