Torn Pickpocketing Optimizer

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

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Torn Pickpocketing Optimizer
// @namespace    http://tampermonkey.net/
// @version      35.0
// @description  You can pick your friends, but you can't pick your friend's pockets.
// @author       Kia-Kaha
// @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
    // ========================================================================
    const SPRITE_MAP = {
        "-170": { name: "Phone",       mult: 0.7, type: "safe" },
        "-102": { name: "Music",       mult: 0.7, type: "safe" },
        "-34":  { name: "Daydreaming", mult: 0.7, type: "safe" },
        "-340": { name: "Begging",     mult: 0.7, type: "safe" },
        "-272": { name: "Impaired",    mult: 0.5, type: "safe" },
        "-238": { name: "Loitering",   mult: 1.0, type: "neutral" },
        "-306": { name: "Walking",     mult: 1.0, type: "risky" },
        "-136": { name: "Walking",     mult: 1.0, type: "risky" },
        "-204": { name: "Running",     mult: 2.5, type: "danger" },
        "0":    { name: "Cycling",     mult: 2.5, type: "danger" },
        "-68":  { name: "Reading?",    mult: 0.7, type: "unknown" }
    };

    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 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 analyzeTarget = (row) => {
        let text = (row.textContent || "").toLowerCase();
        let spriteID = null;
        let detectedStatus = null;

        const activityDiv = row.querySelector('div[class*="activity"]');
        if (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/);
                if (match) spriteID = match[1];
                else if (style.indexOf('background-position-y: 0px') !== -1 || style.indexOf('background-position-y:0px') !== -1) spriteID = "0";
            }
        } else {
             const iconWrap = row.querySelector('span[class*="iconWrap"]');
             if (iconWrap) spriteID = "-170";
        }

        if (spriteID && SPRITE_MAP[spriteID]) {
            detectedStatus = SPRITE_MAP[spriteID];
        } else if (spriteID) {
            detectedStatus = { name: "Unknown", mult: 1.0, type: "unknown" };
        } else {
            detectedStatus = { name: "Moving", mult: 1.0, type: "risky" };
        }

        return { text, spriteID, detectedStatus };
    };

    const calculateHeuristic = (row, playerCS) => {
        const { text, spriteID, detectedStatus } = analyzeTarget(row);

        let baseDiff = 50;
        for (const [name, val] of Object.entries(BASE_DIFFICULTY)) {
            if (text.indexOf(name) !== -1) { baseDiff = val; break; }
        }

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

        const modifiedDifficulty = baseDiff * detectedStatus.mult * bodyMult;
        const volatilityPenalty = playerCS > 75 ? (playerCS - 75) * 0.5 : 0;
        let safetyMargin = (playerCS * 1.2) - modifiedDifficulty - volatilityPenalty;

        if (detectedStatus.mult >= 2.0) safetyMargin = -999;

        return {
            text: text,
            diff: modifiedDifficulty,
            status: detectedStatus.name,
            type: detectedStatus.type,
            safetyMargin: safetyMargin,
            spriteID: spriteID,
            bodyType: bodyType
        };
    };

    // ========================================================================
    // 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('li[class*="crime-option"], div[class*="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('button');

            // --- CONTEXT AWARE DEAD CHECK ---
            // We check the PARENT containers (virtual-item) because the "Success" message
            // is often a sibling to the row, not a child.
            const parentContext = row.closest('.virtual-item') || row.closest('li') || row;
            const fullText = (parentContext.innerText || "").toLowerCase();

            const isDead =
                !btn ||
                btn.disabled ||
                btn.classList.contains('disabled') ||
                btn.getAttribute('aria-disabled') === 'true' ||
                row.classList.contains('expired') ||
                fullText.indexOf('success') !== -1 ||
                fullText.indexOf('failure') !== -1 ||
                fullText.indexOf('hospitalized') !== -1;

            if (isDead) {
                // Instantly strip styles
                row.style.background = "";
                row.style.borderLeft = "";
                row.style.opacity = "0.5";
                if (btn) btn.style.boxShadow = "";

                const reasonEl = row.querySelector('.tp-reason');
                if (reasonEl) reasonEl.innerHTML = "";

                return; // SKIP processing
            }

            // Reset alive targets
            row.style.opacity = "1";

            const data = calculateHeuristic(row, currentCS);

            if (isSkinnyMode && data.text.indexOf('skinny') === -1 && data.text.indexOf('scrawny') === -1) {
                row.style.opacity = '0.3'; return;
            }
            if (!isProfitMode && (data.text.indexOf('police') !== -1 || data.text.indexOf('mobster') !== -1)) {
                row.style.opacity = '0.3'; 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 = "";
            btn.style.boxShadow = "";

            const isSafest = (data.safetyMargin === maxScore && data.safetyMargin > 0);
            const isDanger = (data.safetyMargin < -20) || (data.type === 'danger');

            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;
                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('div[class*="title"]');
        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('div');
            reasonEl.className = 'tp-reason';
            reasonEl.style.fontSize = "10px";
            reasonEl.style.fontWeight = "bold";
            reasonEl.style.marginTop = "2px";
            container.appendChild(reasonEl);
        }

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

        let text = "";
        let color = "#888";
        const marginVal = data.safetyMargin.toFixed(0);
        const sign = data.safetyMargin > 0 ? "+" : "";
        const bodyTag = (data.bodyType === 'skinny' || data.bodyType === 'scrawny') ? " [Skinny]" : "";

        if (isDanger) {
            text = "[DANGER] " + data.status + idDisplay;
            color = "#ff4444";
        } else if (isBest) {
            text = "[BEST] [Margin: " + sign + marginVal + "] " + data.status + bodyTag + idDisplay;
            color = "#32cd32";
        } else if (data.safetyMargin > 0) {
            text = "[SAFE] [Margin: " + sign + marginVal + "] " + data.status + bodyTag + idDisplay;
            color = "#aaa";
        } else {
            text = "[RISKY] [Margin: " + sign + marginVal + "] " + data.status + bodyTag + idDisplay;
            color = "#d2691e";
        }

        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'
        });

        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();
    }
})();