Greasy Fork is available in English.
You can pick your friends, but you can't pick your friend's pockets.
// ==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();
}
})();