您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
color pick-pocket targets based on difficulty
当前为
// ==UserScript== // @name Pickpocket Helper // @namespace http://tampermonkey.net/ // @version 0.65 // @description color pick-pocket targets based on difficulty // @author Terekhov // @match https://www.torn.com/loader.php?sid=crimes* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant none // ==/UserScript== (function () { 'use strict'; // // Based on guide here https://www.torn.com/forums.php#/p=threads&f=61&t=16358739&b=0&a=0 // Thanks Emforus [2535044]! // // This script is triggered down at the bottom; see formatCrimesContainerOnce and startListeningToFormatNewCrimes // const lsKey = 'pickpocketSkillLevel'; // Need to wait for page to initialize, before we know this. Assume 1, until then let currentSkillLevel = 1; function findChildByClassStartingWith(name, parentEle) { for (let child of parentEle.children) { for (let childClass of child.classList) { if (!!childClass && childClass.startsWith(name)) { return child; } } } return null; } const colors = { ideal: '#65E037', easy: '#B4E0AD', tooEasy: '#C7DCC4', tooHard: '#fa8e8e', uncategorized: '#DA85FF' }; const ALL_MARK_TYPES = [ 'Businessman', 'Businesswoman', 'Classy lady', 'Cyclist', 'Drunk man', 'Drunk woman', 'Elderly man', 'Elderly woman', 'Gang member', 'Homeless person', 'Jogger', 'Junkie', 'Laborer', 'Mobster', 'Police officer', 'Postal Worker', 'Rich kid', 'Sex worker', 'Student', 'Thug', 'Young man', 'Young woman' ]; const MARK_CS_LEVELS_MAP = { 'Drunk man': '100', 'Drunk woman': '100', 'Elderly man': '100', 'Elderly woman': '100', 'Homeless person': '100', 'Junkie': '100', 'Classy lady': '150', 'Laborer': '150', 'Postal Worker': '150', 'Young man': '150', 'Young woman': '150', 'Student': '150', 'Rich kid': '200', 'Sex worker': '200', 'Thug': '200', 'Businessman': '250', 'Businesswoman': '250', 'Jogger': '250', 'Gang member': '250', 'Mobster': '250', 'Cyclist': '300', 'Police officer': '350' }; /** * * @return { * csSemantic: 'tooHard', // Police officer when you're lvl 1 * activitySemantic: 'ideal', // Businesswoman 'on phone' * buildSemantic: 'tooHard' // Skinny businessman, * finalSemantic: 'tooHard' * } */ function setColorsForCrimeTarget(crimeChild) { const crimesDivAkaSections = findChildByClassStartingWith('sections', crimeChild); const mainSection = findChildByClassStartingWith('mainSection', crimesDivAkaSections); let targetTypeEle = findChildByClassStartingWith('titleAndProps', mainSection).children[0]; let targetType; for (let type of ALL_MARK_TYPES) { if (targetTypeEle.textContent.startsWith(type)) { // Handle mobile view e.g. "Police officer 5m 10s" targetType = type; } } // e.g. Average 5'0" 158 lbs const physicalPropsEle = findChildByClassStartingWith('titleAndProps', mainSection).children[1]; const physicalProps = physicalPropsEle.textContent; // Average const build = physicalProps.substring(0, physicalProps.indexOf(' ')); // e.g. Begging0s const activityEle = findChildByClassStartingWith('activity', mainSection); const activity = activityEle.textContent; // e.g. Begging // The ternary handles mobile - in mobile we don't get the status like "Begging" so we can't do optimize there. const activityName = activity.match(/^\D+/) ? activity.match(/^\D+/)[0] : ''; // e.g. 0s // const activityTime = activity.substring(activityName.length); // // type DifficultySemantic = 'ideal' | 'easy' | 'tooEasy' | 'tooHard' | 'uncategorized' // interface Difficulties: { [key]: DifficultySemantic } = { // csSemantic: 'tooHard', // Police officer when you're lvl 1 // activitySemantic: 'ideal', // Businesswoman 'on phone' // buildSemantic: 'tooHard' // Skinny businessman // finalSemantic: 'tooHard', // Based on all the above // } // const difficulties = getDifficulties(targetType, build, activityName); // // Now Set all the colors // if (difficulties.buildSemantic) { physicalPropsEle.style.color = colors[difficulties.buildSemantic]; } if (difficulties.activitySemantic) { activityEle.style.color = colors[difficulties.activitySemantic]; } for (let type of ALL_MARK_TYPES) { if (targetTypeEle.textContent.startsWith(type)) { // Handle mobile view e.g. "Police officer 5m 10s" if (targetTypeEle.textContent.indexOf('%)') === -1) { targetTypeEle.textContent = targetTypeEle.textContent + ` (${MARK_CS_LEVELS_MAP[type]}%)`; } targetTypeEle.style.color = colors[difficulties.csSemantic]; } } // Set 'Pickpocket' button color const divContainingButton = findChildByClassStartingWith('commitButtonSection', crimesDivAkaSections); divContainingButton.style.backgroundColor = colors[difficulties.finalSemantic]; } let skillCats = ['Safe', 'Moderately Unsafe', 'Unsafe', 'Risky', 'Dangerous', 'Very Dangerous']; let skillStarts = [1, 10, 35, 65, 90, 100]; function getMaxSkillIndex() { let idx = 0; skillStarts.forEach((ele, currentIdx) => { if (Math.floor(currentSkillLevel) >= ele) { idx = currentIdx; } }); return idx; } function getAllSafeSkillCats() { let maxIndex = getMaxSkillIndex(); if (maxIndex >= skillCats.length) { return skillCats.slice(); } else { return skillCats.slice(0, maxIndex + 1); } } const markGroups = { // CS 1-20 'Safe': ['Drunk man', 'Drunk woman', 'Homeless person', 'Junkie', 'Elderly man', 'Elderly woman'], // CS 10-70 'Moderately Unsafe': ['Laborer', 'Postal worker', 'Young man', 'Young woman', 'Student'], // CS 35-90 'Unsafe': ['Classy lady', 'Rich kid', 'Sex worker'], // CS 65+ 'Risky': ['Thug', 'Jogger', 'Businessman', 'Businesswoman', 'Gang member'], // CS 90+ 'Dangerous': ['Cyclist'], // ??? 'Very Dangerous': ['Mobster', 'Police officer'], }; /** * @param mark e.g. 'Rich Kid' * * @return 'ideal' | 'easy' | 'tooEasy' | 'tooHard' | 'uncategorized' */ function getMarkIdealityBasedOnCS(mark) { // type colorSemantic = 'ideal' | 'easy' | 'tooEasy' | 'tooHard' | 'uncategorized' let safeSkillCats = getAllSafeSkillCats(); for (let idx = 0; idx < safeSkillCats.length; idx++) { let safeSkillCat = safeSkillCats[idx]; if (markGroups[safeSkillCat].includes(mark)) { if (idx === safeSkillCats.length - 1) { return 'ideal'; } else if (idx === safeSkillCats.length - 2) { return 'easy'; } else { return 'tooEasy'; } } }; return 'tooHard'; } /** * * @param markType Elderly woman * @param build Average * @param status Begging * * @return { * csSemantic: 'tooHard', // Police officer when you're lvl 1 * activitySemantic: 'ideal', // Businesswoman 'on phone' * buildSemantic: 'tooHard' // Skinny businessman, * finalSemantic: 'tooHard' * } */ function getDifficulties(markType, build, status) { // TODO builds and statuses to favor. Too much for now const buildsToAvoid = { 'Businessman': ['Skinny'], 'Drunk man': ['Muscular'], 'Gang member': ['Muscular'], 'Sex worker': ['Muscular'], 'Student': ['Athletic'], 'Thug': ['Muscular'] }; const statusesToAvoid = { 'Businessman': ['Walking'], 'Drunk man': ['Distracted'], 'Drunk woman': ['Distracted'], 'Homeless person': ['Loitering'], 'Junkie': ['Loitering'], 'Laborer': ['Distracted'], 'Police officer': ['Walking'], 'Sex worker': ['Distracted'], 'Thug': ['Loitering', 'Walking'] }; const difficulties = { csSemantic: 'uncategorized', activitySemantic: undefined, buildSemantic: undefined, finalSemantic: 'uncategorized' } // type colorSemantic = 'ideal' | 'easy' | 'tooEasy' | 'tooHard' | 'uncategorized' difficulties.csSemantic = getMarkIdealityBasedOnCS(markType); // We use csSemantic as baseline; activity and build can override. difficulties.finalSemantic = difficulties.csSemantic; if (buildsToAvoid[markType] && buildsToAvoid[markType].includes(build)) { difficulties.finalSemantic = 'tooHard'; difficulties.buildSemantic = 'tooHard' } if (statusesToAvoid[markType] && statusesToAvoid[markType].includes(status)) { difficulties.finalSemantic = 'tooHard'; difficulties.activitySemantic = 'tooHard'; } return difficulties; } function getCrimesContainer() { let crimesContainerName = document.querySelectorAll('[class^="crimeOptionGroup"]')[0].classList[0]; return document.getElementsByClassName(crimesContainerName)[0]; } function setSkillLevel() { currentSkillLevel = +document.getElementsByClassName('slick-slide')[0].children[0].children[0].children[0].children[2].textContent; } function formatCrimesContainerOnce() { console.error('got to formatCrimesContainerOnce'); if (!window.location.href.includes("#/pickpocketing")) { return; } setSkillLevel(); for (let node of getCrimesContainer().children) { setColorsForCrimeTarget(node); } } let observer; let alreadyListening = false; function startListeningToFormatNewCrimes() { if (!window.location.href.includes("#/pickpocketing")) { if (observer) { observer.disconnect(); observer = undefined; } alreadyListening = false; return; } if (alreadyListening) { return; } setSkillLevel(); // Select the node that will be observed for mutations const targetNode = getCrimesContainer(); // Options for the observer (which mutations to observe) const config = { attributes: false, childList: true, subtree: false }; // Callback function to execute when mutations are observed const callback = (mutationList, observer) => { for (const mutation of mutationList) { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { for (let node of targetNode.children) { setColorsForCrimeTarget(node); } } } }; // Create an observer instance linked to the callback function observer = new MutationObserver(callback); // Start observing the target node for configured mutations observer.observe(targetNode, config); alreadyListening = true; } // If we land directly on pickpocket page, these handle it correctly. setTimeout(formatCrimesContainerOnce, 650); setTimeout(startListeningToFormatNewCrimes, 650); // // GreaseMonkey can't listen for Pickpocket page directly, so we run this on all crimes pages. // however if we navigate away from Pickpocket, we stop listening with our observer // window.onpopstate = function(event) { setTimeout(formatCrimesContainerOnce, 650); setTimeout(startListeningToFormatNewCrimes, 650); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址