Torn Pickpocketing Optimizer

You can pick your friends, but you can't pick your friend's pockets.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Torn Pickpocketing Optimizer
// @namespace    http://tampermonkey.net/
// @version      48.0
// @description  You can pick your friends, but you can't pick your friend's pockets.
// @author       Kia-Kaha (Updated with Math Fix)
// @match        https://www.torn.com/loader.php?sid=crimes*
// @match        https://www.torn.com/page.php?sid=crimes*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // ========================================================================
    // 1. DATA MATRICES 
    // ========================================================================
    // A weighting factor to tune how much skill mitigates difficulty
    const W_SKILL = 0.8; 

    const BASE_DIFFICULTY = {
        "drunk man": 10, "drunk woman": 10,
        "homeless person": 15, "junkie": 15,
        "elderly man": 25, "elderly woman": 25,
        "student": 40, "young man": 40, "young woman": 40,
        "laborer": 50, "postal worker": 50,
        "classy lady": 70, "businessman": 70, "businesswoman": 70,
        "jogger": 75, "rich kid": 80,
        "thug": 85, "gang member": 85, "sex worker": 85,
        "mobster": 110, "police officer": 110, "cyclist": 120
    };

    const BODY_MULTIPLIERS = {
        "skinny": 0.85, "scrawny": 0.85,
        "average": 1.0,
        "athletic": 1.1, "muscular": 1.2, "beefy": 1.25,
        "obese": 1.0
    };

    const STATUS_WEIGHTS = {
        'sleeping': 0.3,
        'passed out': 0.4,
        'drunk': 0.5,
        'stumbling': 0.5,
        'distracted': 0.7,
        'on the phone': 0.7,
        'reading': 0.7,
        'listening': 0.7,
        'music': 0.7,
        'daydreaming': 0.7,
        'begging': 0.7,
        'loitering': 1.0,
        'walking': 1.0,
        'jogging': 1.5,
        'cycling': 1.8,
        'running': 2.5,
        'sprinting': 3.0
    };

    const SPRITE_FALLBACK = {
        "-170": 0.7, "-102": 0.7, "-34": 0.7, "-340": 0.7,
        "-272": 0.4, "-238": 1.0, "-306": 1.0, "-136": 1.0,
        "-204": 2.5, "0": 2.5
    };

    const safeSetValue = (key, value) => {
        if (typeof GM_setValue === 'function') GM_setValue(key, value);
        else window.localStorage.setItem('tp_' + key, value);
    };

    const safeGetValue = (key, def) => {
        if (typeof GM_getValue === 'function') return GM_getValue(key, def);
        const val = window.localStorage.getItem('tp_' + key);
        return val !== null ? val : def;
    };

    // ========================================================================
    // 2. CRIME SKILL DETECTION
    // ========================================================================
    const getCrimeSkill = () => {
        const pref = safeGetValue('manual_level_pref', 'auto');
        if (pref.indexOf('auto') === -1) {
            let val = pref;
            if (val.indexOf('_') !== -1) val = val.split('_')[0];
            return parseInt(val);
        }
        try {
            const masteryNode = document.querySelector('div[class*="crime-mastery"]');
            if (masteryNode) {
                 const match = masteryNode.innerText.match(/Level\s*(\d{1,3})/i);
                 if (match) return parseInt(match[1]);
            }
            const mobileSkillNode = document.querySelector('button[aria-label^="Skill:"]');
            if (mobileSkillNode) {
                 const text = mobileSkillNode.getAttribute('aria-label');
                 const match = text.match(/Skill:\s*(\d+(\.\d+)?)/);
                 if (match) return Math.floor(parseFloat(match[1]));
            }
            const progressNode = document.querySelector('div[aria-label*="Crime skill:"]');
            if (progressNode) {
                 const text = progressNode.getAttribute('aria-label');
                 const match = text.match(/Crime skill:\s*(\d+)/);
                 if (match) return parseInt(match[1]);
            }
        } catch (e) {
            console.log("TP Optimizer: Auto-detect failed", e);
        }
        return 1;
    };

    // ========================================================================
    // 3. TARGET ANALYSIS
    // ========================================================================
    const calculateHeuristic = (row, playerCS) => {
        let text = (row.textContent || "").toLowerCase();
        
        let baseDiff = 50; 
        for (const [name, val] of Object.entries(BASE_DIFFICULTY)) {
            if (text.indexOf(name) !== -1) { baseDiff = val; break; }
        }

        let bodyMult = 1.0;
        for (const [type, val] of Object.entries(BODY_MULTIPLIERS)) {
            if (text.indexOf(type) !== -1) { bodyMult = val; break; }
        }

        let statusMult = 1.0;
        let activityDiv = row.querySelector('div[class*="activity"]');
        let activityText = activityDiv ? activityDiv.textContent.toLowerCase() : text;
        
        let foundStatus = false;
        for (const [state, mult] of Object.entries(STATUS_WEIGHTS)) {
            if (activityText.indexOf(state) !== -1) {
                statusMult = mult;
                foundStatus = true;
                break;
            }
        }

        if (!foundStatus && activityDiv) {
            const iconDiv = activityDiv.querySelector('div[style*="background-position"]');
            if (iconDiv) {
                const style = iconDiv.getAttribute('style');
                const match = style.match(/background-position-y:\s*(-?\d+)px/);
                let spriteID = match ? match[1] : (style.indexOf('0px') !== -1 ? "0" : null);
                if (spriteID && SPRITE_FALLBACK[spriteID]) {
                    statusMult = SPRITE_FALLBACK[spriteID];
                }
            }
        }

        // Apply dynamic weighting factor to player skill for accurate margin
        const finalDifficulty = baseDiff * statusMult * bodyMult;
        let safetyMargin = (playerCS * W_SKILL) - finalDifficulty;

        if (statusMult >= 2.0) safetyMargin = -999; 

        return {
            text: text,
            safetyMargin: safetyMargin,
            isDanger: safetyMargin < 0 || statusMult >= 2.0
        };
    };

    // ========================================================================
    // 4. MAIN LOOP
    // ========================================================================
    const processTargets = () => {
        const url = window.location.href;
        if (url.indexOf('pickpocketing') === -1 && window.location.hash.indexOf('pickpocketing') === -1) {
            const panel = document.getElementById('tp-control-panel');
            if (panel) panel.style.display = 'none';
            return;
        }

        createInterface();

        const rows = document.querySelectorAll('.crime-option');
        if (rows.length === 0) return;

        let maxScore = -9999;
        const processedList = [];
        const currentCS = getCrimeSkill();
        const rawPref = safeGetValue('manual_level_pref', 'auto');
        const isSkinnyMode = rawPref.indexOf('skinny') !== -1;
        const isProfitMode = (currentCS >= 100) || rawPref.indexOf('profit') !== -1 || rawPref === '100';

        const select = document.querySelector('#tp-control-panel select');
        if (select && rawPref.indexOf('auto') !== -1) {
            const option = select.options[select.selectedIndex];
            if (option && !option.innerText.includes('Lvl')) {
                Array.from(select.options).forEach(o => o.innerText = o.innerText.split(' (Lvl')[0]);
                option.innerText = option.innerText.split(' (Lvl')[0] + ' (Lvl ' + currentCS + ')';
            }
        }

        rows.forEach(row => {
            const btn = row.querySelector('.commit-button');
            const parentContext = row.closest('.virtual-item') || row.closest('li') || row;
            const fullText = (parentContext.innerText || "").toLowerCase();
            const ariaLabel = btn ? (btn.getAttribute('aria-label') || "").toLowerCase() : "";

            const isPicked = ariaLabel.includes('already picked') || fullText.indexOf('success') !== -1;
            const isLost = ariaLabel.includes('lost') || row.classList.contains('expired') || fullText.indexOf('failure') !== -1 || fullText.indexOf('hospitalized') !== -1;

            if (isPicked || isLost) {
                // Dim the row completely and add a subtle background pattern
                row.style.background = "repeating-linear-gradient(45deg, rgba(0,0,0,0.02), rgba(0,0,0,0.02) 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 20px)";
                
                // Hide the actual game data without disrupting React's node structure
                let innerContent = row.querySelector('.crime-option-sections');
                if (innerContent) {
                    innerContent.style.opacity = '0';
                    innerContent.style.pointerEvents = 'none';
                }

                // Inject absolute positioned overlay
                let lostOverlay = row.querySelector('.tp-lost-overlay');
                if (!lostOverlay) {
                    lostOverlay = document.createElement('div');
                    lostOverlay.className = 'tp-lost-overlay';
                    lostOverlay.style.cssText = 'position: absolute; top: 0; left: 0; height: 100%; width: 100%; display: flex; align-items: center; justify-content: center; font-style: italic; font-size: 13px; font-weight: 500; letter-spacing: 0.5px; z-index: 10; pointer-events: none;';
                    row.appendChild(lostOverlay);
                }
                
                // Change display text based on state
                if (isPicked) {
                    lostOverlay.style.color = '#5cb85c'; // Success green tint
                    lostOverlay.innerHTML = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 8px; opacity: 0.8;"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> Pocket picked`;
                } else {
                    lostOverlay.style.color = '#999'; // Default faded gray
                    lostOverlay.innerHTML = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 8px; opacity: 0.8;"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><line x1="17" y1="8" x2="23" y2="14"></line><line x1="23" y1="8" x2="17" y2="14"></line></svg> Target lost`;
                }

                return; 
            }

            // --- IMPORTANT: RESET ALIVE TARGETS ---
            row.style.background = "";
            row.style.opacity = "1";
            let innerContent = row.querySelector('.crime-option-sections');
            if (innerContent) {
                innerContent.style.opacity = '1';
                innerContent.style.pointerEvents = 'auto';
            }
            
            // Nuclear Cleanup: Destroy the node to prevent React flexbox artifacting
            let lostOverlay = row.querySelector('.tp-lost-overlay');
            if (lostOverlay) lostOverlay.remove();

            const data = calculateHeuristic(row, currentCS);

            if (isSkinnyMode && data.text.indexOf('skinny') === -1 && data.text.indexOf('scrawny') === -1) {
                row.style.opacity = '0.4'; return;
            }
            if (!isProfitMode && (data.text.indexOf('police') !== -1 || data.text.indexOf('mobster') !== -1)) {
                row.style.opacity = '0.4'; return;
            }

            processedList.push({ row, btn, data });
            if (data.safetyMargin > maxScore) maxScore = data.safetyMargin;
        });

        processedList.forEach(item => {
            const { row, btn, data } = item;

            row.style.background = "";
            row.style.borderLeft = "";
            if(btn) btn.style.boxShadow = "";

            const isSafest = (data.safetyMargin === maxScore && data.safetyMargin > 0);
            const isDanger = data.isDanger;

            const cGreen = "#32cd32";
            const cGold = "#FFD700";

            if (isSafest) {
                const color = isProfitMode ? cGold : cGreen;
                row.style.background = "linear-gradient(90deg, " + color + "22 0%, rgba(0,0,0,0) 100%)";
                row.style.borderLeft = "4px solid " + color;
                if(btn) btn.style.boxShadow = "0 0 5px " + color;
            } else if (isDanger) {
                row.style.opacity = '0.5';
            }

            injectStatusText(row, data, isSafest, isDanger);
        });
    };

    const injectStatusText = (row, data, isBest, isDanger) => {
        let container = row.querySelector('span[class*="physicalProps"]');
        
        if (!container) container = row.querySelector('div[class*="titleAndProps"]');
        if (!container) {
            const allDivs = row.querySelectorAll('div');
            if (allDivs.length > 2) container = allDivs[1];
            else container = row;
        }

        let reasonEl = container.querySelector('.tp-reason');
        if (!reasonEl) {
            reasonEl = document.createElement('span');
            reasonEl.className = 'tp-reason';
            reasonEl.style.fontSize = "10px";
            reasonEl.style.fontWeight = "bold";
            reasonEl.style.marginLeft = "6px";
            reasonEl.style.verticalAlign = "middle";
            reasonEl.style.whiteSpace = "nowrap"; 
            container.appendChild(reasonEl);
        }

        let idDisplay = "";
        let idColor = "#555";
        if (data.spriteID && data.type === 'unknown') {
            idDisplay = "&nbsp;[ID:" + data.spriteID + "]";
            idColor = "#D000FF";
        }

        let color = "#888";
        const marginVal = Math.abs(data.safetyMargin).toFixed(0); 
        const sign = data.safetyMargin >= 0 ? "+" : "-";
        
        if (isDanger || data.safetyMargin < 0) {
            color = "#ff4444"; 
        } else if (isBest) {
            color = "#32cd32"; 
        } else if (data.safetyMargin >= 10) {
            color = "#aaa";    
        } else {
            color = "#d2691e"; 
        }

        const text = "[Safety:&nbsp;" + sign + marginVal + "]";
        reasonEl.innerHTML = '<span style="color:' + color + '">' + text + '</span><span style="color:' + idColor + '; font-size:9px;">' + idDisplay + '</span>';
    };

    const createInterface = () => {
        let panel = document.getElementById('tp-control-panel');
        if (panel) { panel.style.display = 'block'; return; }

        panel = document.createElement('div');
        panel.id = 'tp-control-panel';
        Object.assign(panel.style, {
            position: 'fixed', top: '50px', right: '10px', zIndex: '999999', textAlign: 'right', display: 'flex', gap: '5px'
        });

        const select = document.createElement('select');
        Object.assign(select.style, {
            background: 'rgba(0,0,0,0.9)', color: '#fff', border: '1px solid #444',
            borderRadius: '4px', fontSize: '11px', padding: '4px'
        });

        let opts = [
            {v:'auto', t:'🕵️ [Auto] Max Success'},
            {v:'auto_skinny', t:'👙 [Auto] Skinny Hunt'},
            {v:'auto_profit', t:'🤑 [Auto] Profit/XP'},
            {v:'1', t:'Manual: Lvl 1-24'},
            {v:'25', t:'Manual: Lvl 25-59'},
            {v:'60', t:'Manual: Lvl 60-99'}
        ];

        opts.forEach(o => {
            const el = document.createElement('option');
            el.value = o.v; el.innerText = o.t;
            select.appendChild(el);
        });

        select.value = safeGetValue('manual_level_pref', 'auto');
        select.addEventListener('change', (e) => {
            safeSetValue('manual_level_pref', e.target.value);
            processTargets();
        });

        panel.appendChild(select);
        document.body.appendChild(panel);
    };

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