Pickpocket Helper

color pick-pocket targets based on difficulty

目前為 2023-11-06 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Pickpocket Helper
// @namespace    http://tampermonkey.net/
// @version      0.6
// @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;
  }
 
  function getColorForCrimeChild(crimeChild) {
    const crimesDivAkaSections = findChildByClassStartingWith('sections', crimeChild);
    const mainSection = findChildByClassStartingWith('mainSection', crimesDivAkaSections);
 
    // e.g. 'Elderly woman'
    const types = [
      'Businessman',
      'Businesswoman',
      'Classy lady',
      'Elderly man',
      'Elderly woman',
      'Homeless person',
      'Jogger',
      'Junkie',
      'Police officer',
      'Postal Worker',
      'Rich kid',
      'Sex worker',
      'Thug',
      'Young man',
      'Young woman'
    ];
    let targetType = findChildByClassStartingWith('titleAndProps', mainSection).children[0].textContent;
    for (let type of types) {
      if (targetType.startsWith(type)) {
        // Handle mobile view e.g. "Police officer 5m 10s"
        targetType = type;
      }
    }
 
    // e.g. Average 5'0" 158 lbs
    const physicalProps = findChildByClassStartingWith('titleAndProps', mainSection).children[1].textContent;
 
    // Average
    const build = physicalProps.substring(0, physicalProps.indexOf(' '));
 
    // e.g. Begging0s
    const activity = findChildByClassStartingWith('activity', mainSection).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);
 
    return colors[getColorSemanticBasedOnAttributes(targetType, build, activityName)];
  }
 
  let colors = {
    ideal: '#65E037',
    easy: '#B4E0AD',
    'too-easy': '#C7DCC4',
    'too-hard': '#fa8e8e',
    uncategorized: '#DA85FF'
  };
 
  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 'too-hard', 'ideal', 'easy', 'too-easy'
   */
  function getMarkIdeality(mark) {
    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 'too-easy';
        }
      }
    };
    return 'too-hard';
  }
 
  /**
   *
   * @param markType  Elderly woman
   * @param build     Average
   * @param status    Begging
   *
   * @return green if ideal, red if too hard, yellow otherwise
   */
  function getColorSemanticBasedOnAttributes(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'],
      'Businesswoman': ['Walking'],
      'Drunk man': ['Distracted'],
      'Drunk woman': ['Distracted'],
      'Homeless person': ['Loitering'],
      'Junkie': ['Loitering'],
      'Laborer': ['Distracted'],
      'Police officer': ['Walking'],
      'Sex worker': ['Distracted'],
      'Thug': ['Loitering', 'Walking']
    };
 
    let colorSemantic = 'uncategorized';
    colorSemantic = getMarkIdeality(markType);
    if (buildsToAvoid[markType] && buildsToAvoid[markType].includes(build)) {
      colorSemantic = 'too-hard';
    }
    if (statusesToAvoid[markType] && statusesToAvoid[markType].includes(status)) {
      colorSemantic = 'too-hard';
    }
    return colorSemantic;
  }
 
  function setCrimeChildColor(crimeChild) {
    const crimesDivAkaSections = findChildByClassStartingWith('sections', crimeChild);
    const divContainingButton = findChildByClassStartingWith('commitButtonSection', crimesDivAkaSections)
    divContainingButton.style.backgroundColor = getColorForCrimeChild(crimeChild)
  }
 
  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() {
    if (!window.location.href.includes("#/pickpocketing")) {
      return;
    }
    setSkillLevel();
 
    for (let node of getCrimesContainer().children) {
      setCrimeChildColor(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) {
            setCrimeChildColor(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或关注我们的公众号极客氢云获取最新地址