您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Highlight role restrictions and best roles in OC 2.0 (modified copy of "Torn OC Role Evaluator"). Well paired with https://greasyfork.org/en/scripts/526834-oc-success-chance-2-0.
// ==UserScript== // @name Torn OC Role Restrictions // @namespace https://xentac.github.io // @version 0.3 // @description Highlight role restrictions and best roles in OC 2.0 (modified copy of "Torn OC Role Evaluator"). Well paired with https://greasyfork.org/en/scripts/526834-oc-success-chance-2-0. // @author underko[3362751], xentac[3354782] // @match https://www.torn.com/factions.php* // @grant GM_xmlhttpRequest // @connect raw.githubusercontent.com // @license MIT // ==/UserScript== (function () { "use strict"; let ocRoleInfluence = { "Pet Project": [ { role: "Kidnapper", influence: 41.14, lower: 70 }, { role: "Muscle", influence: 26.83, lower: 70 }, { role: "Picklock", influence: 32.03, lower: 70 }, ], "Mob Mentality": [ { role: "Looter #1", influence: 34.83, lower: 70 }, { role: "Looter #2", influence: 25.97, lower: 70 }, { role: "Looter #3", influence: 19.87, lower: 60 }, { role: "Looter #4", influence: 19.33, lower: 67 }, ], "Cash Me if You Can": [ { role: "Thief #1", influence: 46.67, lower: 70 }, { role: "Thief #2", influence: 21.87, lower: 65 }, { role: "Lookout", influence: 31.46, lower: 70 }, ], "Best of the Lot": [ { role: "Picklock", influence: 23.65, lower: 70 }, { role: "Car Thief", influence: 21.06, lower: 70 }, { role: "Muscle", influence: 36.43, lower: 75 }, { role: "Imitator", influence: 18.85, lower: 60 }, ], "Market Forces": [ { role: "Enforcer", influence: 27.56, lower: 70 }, { role: "Negotiator", influence: 25.59, lower: 70 }, { role: "Lookout", influence: 19.05, lower: 68 }, { role: "Arsonist", influence: 4.12, lower: 40 }, { role: "Muscle", influence: 23.68, lower: 70 }, ], "Smoke and Wing Mirrors": [ { role: "Car Thief", influence: 48.2, lower: 74 }, { role: "Imitator", influence: 26.3, lower: 70 }, { role: "Hustler #1", influence: 7.7, lower: 60 }, { role: "Hustler #2", influence: 17.81, lower: 65 }, ], "Gaslight the Way": [ { role: "Imitator #1", influence: 7.54, lower: 70 }, { role: "Imitator #2", influence: 34.85, lower: 72 }, { role: "Imitator #3", influence: 40.25, lower: 72 }, { role: "Looter #1", influence: 7.54, lower: 60 }, { role: "Looter #2", influence: 0.0, lower: 40 }, { role: "Looter #3", influence: 9.83, lower: 65 }, ], "Stage Fright": [ { role: "Enforcer", influence: 16.89, lower: 70 }, { role: "Muscle #1", influence: 21.92, lower: 72 }, { role: "Muscle #2", influence: 2.09, lower: 50 }, { role: "Muscle #3", influence: 9.49, lower: 70 }, { role: "Lookout", influence: 7.68, lower: 60 }, { role: "Sniper", influence: 41.92, lower: 75 }, ], "Snow Blind": [ { role: "Hustler", influence: 51.4, lower: 74 }, { role: "Imitator", influence: 30.44, lower: 70 }, { role: "Muscle #1", influence: 9.08, lower: 70 }, { role: "Muscle #2", influence: 9.08, lower: 50 }, ], "Leave No Trace": [ { role: "Techie", influence: 24.4, lower: 60 }, { role: "Negotiator", influence: 29.07, lower: 70 }, { role: "Imitator", influence: 46.54, lower: 73 }, ], "No Reserve": [ { role: "Car Thief", influence: 30.86, lower: 67 }, { role: "Techie", influence: 37.88, lower: 75 }, { role: "Engineer", influence: 31.27, lower: 67 }, ], "Counter Offer": [ { role: "Robber", influence: 33.29, lower: 62 }, { role: "Looter", influence: 4.69, lower: 42 }, { role: "Hacker", influence: 16.72, lower: 60 }, { role: "Picklock", influence: 17.1, lower: 60 }, { role: "Engineer", influence: 28.21, lower: 62 }, ], "Honey Trap": [ { role: "Enforcer", influence: 20.21, lower: 60 }, { role: "Muscle #1", influence: 34.32, lower: 70 }, { role: "Muscle #2", influence: 45.47, lower: 75 }, ], "Bidding War": [ { role: "Robber #1", influence: 6.82, lower: 60 }, { role: "Driver", influence: 21.93, lower: 70 }, { role: "Robber #2", influence: 19.63, lower: 75 }, { role: "Robber #3", influence: 25.65, lower: 70 }, { role: "Bomber #1", influence: 10.96, lower: 70 }, { role: "Bomber #2", influence: 15.0, lower: 63 }, ], "Blast from the Past": [ { role: "Picklock #1", influence: 9.81, lower: 70 }, { role: "Hacker", influence: 6.18, lower: 65 }, { role: "Engineer", influence: 25.29, lower: 75 }, { role: "Bomber", influence: 20.4, lower: 70 }, { role: "Muscle", influence: 36.75, lower: 75 }, { role: "Picklock #2", influence: 1.56, lower: 40 }, ], "Break the Bank": [ { role: "Robber", influence: 10.84, lower: 63 }, { role: "Muscle #1", influence: 10.27, lower: 63 }, { role: "Muscle #2", influence: 7.78, lower: 60 }, { role: "Thief #1", influence: 3.55, lower: 60 }, { role: "Muscle #3", influence: 33.54, lower: 72 }, { role: "Thief #2", influence: 34.03, lower: 72 }, ], "Stacking the Deck": [ { role: "Cat Burglar", influence: 31.99, lower: 75 }, { role: "Driver", influence: 3.86, lower: 68 }, { role: "Hacker", influence: 25.64, lower: 63 }, { role: "Imitator", influence: 38.52, lower: 70 }, ], "Clinical Precision": [ { role: "Imitator", influence: 41.51, lower: 75 }, { role: "Cat Burglar", influence: 22.21, lower: 70 }, { role: "Assassin", influence: 14.56, lower: 60 }, { role: "Cleaner", influence: 21.71, lower: 70 }, ], "Ace in the Hole": [ { role: "Imitator", influence: 13.73, lower: 65 }, { role: "Muscle #1", influence: 18.55, lower: 65 }, { role: "Muscle #2", influence: 18.88, lower: 72 }, { role: "Hacker", influence: 37.49, lower: 75 }, { role: "Driver", influence: 11.35, lower: 60 }, ], }; let crimeData = {}; let previousTab = "none"; function classifyOcRoleInfluence(ocName, roleName) { const ocInfo = ocRoleInfluence[ocName]; const roleData = ocInfo?.find((r) => r.role === roleName); const influence = roleData ? roleData.influence : 0; const lower = roleData ? roleData.lower : 70; let upper = lower + 10; const roleLowers = ocInfo .map((role) => { return role.lower; }) .sort(); // If our role is a low influence role, set the upper bound to the next highest lower bound if (roleLowers[0] == lower) { upper = roleLowers[1]; } return { influence, lower, upper }; } function getFactionId() { let factionId = ""; try { document .querySelector(".forum-thread") .href.split("#")[1] .split("&") .forEach((elem) => { if (elem[0] == "a") { factionId = elem.split("=")[1]; } }); } catch (e) { console.log("[OCRoleRestrictions] Couldn't extract faction id:", e); } return factionId; } function updateFactionRoleRestrictions(factionId, cb) { try { GM_xmlhttpRequest({ method: "GET", url: `https://raw.githubusercontent.com/xentac/oc_role_restrictions/refs/heads/main/${factionId}.json`, headers: { "Content-Type": "application/json", }, onload: async function (response) { console.log(response); if (response.status != 200) { console.error( "[OCRoleRestrictions] Bad response fetching faction restrictions:", response.status, ); return cb(); } try { const result = JSON.parse(response.responseText); ocRoleInfluence = result; } catch (error) { console.error( "[OCRoleRestrictions] Failed to parse faction restrictions:", error.message, ); } return cb(); }, }); } catch (error) { console.error( "[OCRoleRestrictions] Failed fetching faction restrictions:", error.message, ); } } function processCrime(wrapper) { const ocId = wrapper.getAttribute("data-oc-id"); if (!ocId || crimeData[ocId]) return; const titleEl = wrapper.querySelector("p.panelTitle___aoGuV"); if (!titleEl) return; const crimeTitle = titleEl.textContent.trim(); const roles = []; const roleEls = wrapper.querySelectorAll(".title___UqFNy"); roleEls.forEach((roleEl) => { const roleName = roleEl.textContent.trim(); const successEl = roleEl.nextElementSibling; const chance = successEl ? parseInt(successEl.textContent.trim(), 10) : null; const evaluation = chance !== null ? classifyOcRoleInfluence(crimeTitle, roleName) : { influence: null, lower: 70, upper: 80 }; roles.push({ role: roleName, chance, evaluation }); if (successEl && evaluation.influence !== null) { successEl.textContent = `${chance}/${evaluation.lower}`; } const slotHeader = roleEl.closest("button.slotHeader___K2BS_"); if (slotHeader) { if (chance >= evaluation.upper) { //slotHeader.style.backgroundColor = "#ca6f1e"; } else if (chance >= evaluation.lower) { slotHeader.style.backgroundColor = "#239b56"; } else { slotHeader.style.backgroundColor = "#a93226"; } } }); crimeData[ocId] = { id: ocId, title: crimeTitle, roles }; } function setupMutationObserver(root) { const observer = new MutationObserver(() => { const tabTitle = document .querySelector("button.active___ImR61 span.tabName___DdwH3") ?.textContent.trim(); if (tabTitle !== "Recruiting" && tabTitle !== "Planning") return; if (previousTab !== tabTitle) { crimeData = {}; previousTab = tabTitle; } const allCrimes = document.querySelectorAll(".wrapper___U2Ap7"); allCrimes.forEach((crimeNode) => { processCrime(crimeNode); }); }); observer.observe(root, { childList: true, subtree: true }); } const factionId = getFactionId(); const cb = () => { waitForKeyElements("#faction-crimes-root", (root) => { setupMutationObserver(root); }); }; if (factionId) { updateFactionRoleRestrictions(factionId, cb); } else { console.log( "[OCRoleRestrictions] Couldn't find faction id, going with defaults.", ); cb(); } // Inserting dependency because Torn PDA can't handle @require // ==UserScript== // @version 1.3.0 // @name waitForKeyElements.js (CoeJoder fork) // @description A utility function for userscripts that detects and handles AJAXed content. // @namespace https://github.com/CoeJoder/waitForKeyElements.js // @author CoeJoder // @homepage https://github.com/CoeJoder/waitForKeyElements.js // @source https://raw.githubusercontent.com/CoeJoder/waitForKeyElements.js/master/waitForKeyElements.js // // ==/UserScript== /** * A utility function for userscripts that detects and handles AJAXed content. * * @example * waitForKeyElements("div.comments", (element) => { * element.innerHTML = "This text inserted by waitForKeyElements()."; * }); * * waitForKeyElements(() => { * const iframe = document.querySelector('iframe'); * if (iframe) { * const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; * return iframeDoc.querySelectorAll("div.comments"); * } * return null; * }, callbackFunc); * * @param {(string|function)} selectorOrFunction - The selector string or function. * @param {function} callback - The callback function; takes a single DOM element as parameter. * If returns true, element will be processed again on subsequent iterations. * @param {boolean} [waitOnce=true] - Whether to stop after the first elements are found. * @param {number} [interval=300] - The time (ms) to wait between iterations. * @param {number} [maxIntervals=-1] - The max number of intervals to run (negative number for unlimited). */ function waitForKeyElements( selectorOrFunction, callback, waitOnce, interval, maxIntervals, ) { if (typeof waitOnce === "undefined") { waitOnce = true; } if (typeof interval === "undefined") { interval = 300; } if (typeof maxIntervals === "undefined") { maxIntervals = -1; } if (typeof waitForKeyElements.namespace === "undefined") { waitForKeyElements.namespace = Date.now().toString(); } var targetNodes = typeof selectorOrFunction === "function" ? selectorOrFunction() : document.querySelectorAll(selectorOrFunction); var targetsFound = targetNodes && targetNodes.length > 0; if (targetsFound) { targetNodes.forEach(function (targetNode) { var attrAlreadyFound = `data-userscript-${waitForKeyElements.namespace}-alreadyFound`; var alreadyFound = targetNode.getAttribute(attrAlreadyFound) || false; if (!alreadyFound) { var cancelFound = callback(targetNode); if (cancelFound) { targetsFound = false; } else { targetNode.setAttribute(attrAlreadyFound, true); } } }); } if (maxIntervals !== 0 && !(targetsFound && waitOnce)) { maxIntervals -= 1; setTimeout(function () { waitForKeyElements( selectorOrFunction, callback, waitOnce, interval, maxIntervals, ); }, interval); } } })();