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