Disposal J.A.R.V.I.S.

color disposal options based on safety

目前为 2023-12-15 提交的版本。查看 最新版本

// ==UserScript==
// @name        Disposal J.A.R.V.I.S.
// @namespace   http://tampermonkey.net/
// @version     1.0
// @description color disposal options based on safety
// @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';

  const colors = {
    safe: '#40Ab24',
    moderatelySafe: '#A4D497',
    caution: '#D6BBA2',
    unsafe: '#B51B1B'
  };
  const JOB_METHOD_DIFFICULTIES_MAP = {
    'Biological Waste': {
      safe: ['Sink'],
      moderatelySafe: [],
      caution: ['Burn'],
      unsafe: ['Bury']
    },
    'Body Part': {
      safe: ['Dissolve'],
      moderatelySafe: [],
      caution: [],
      unsafe: []
    },
    'Broken Appliance': {
      safe: ['Sink'],
      moderatelySafe: [],
      caution: ['Abandon', 'Bury'],
      unsafe: ['Dissolve']
    },
    'Building Debris': {
      safe: ['Sink'],
      moderatelySafe: [],
      caution: ['Abandon', 'Bury'],
      unsafe: []
    },
    'Dead Body': {
      safe: ['Dissolve'],
      moderatelySafe: [],
      caution: [],
      unsafe: []
    },
    Documents: {
      safe: ['Burn'],
      moderatelySafe: [],
      caution: ['Abandon', 'Bury'],
      unsafe: ['Dissolve', 'Sink']
    },
    Firearm: {
      safe: ['Sink'],
      moderatelySafe: ['Bury'],
      caution: [],
      unsafe: ['Dissolve']
    },
    'General Waste': {
      safe: ['Burn'],
      moderatelySafe: ['Bury'],
      caution: ['Abandon', 'Sink'],
      unsafe: ['Dissolve']
    },
    'Industrial Waste': {
      safe: ['Sink'],
      moderatelySafe: [],
      caution: ['Abandon', 'Bury'],
      unsafe: []
    },
    'Murder Weapon': {
      safe: ['Sink'],
      moderatelySafe: [],
      caution: [],
      unsafe: ['Dissolve']
    },
    'Old Furniture': {
      safe: ['Burn'],
      moderatelySafe: [],
      caution: ['Abandon', 'Bury', 'Sink'],
      unsafe: ['Dissolve']
    },
    Vehicle: {
      safe: ['Sink'],
      moderatelySafe: ['Burn'],
      caution: ['Abandon'],
      unsafe: []
    }
  };
  const NERVE_COST_BY_METHOD = {
    Abandon: 6,
    Bury: 8,
    Burn: 10,
    Sink: 12,
    Dissolve: 14
  };

//
// Based on guide here https://www.torn.com/forums.php#/p=threads&f=61&t=16367936&b=0&a=0
// Thanks Emforus [2535044]!
//
// The script start is triggered by formatPageOnce and/or startCheckingPageToFormat
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// This section of the script listens for page load and calls the main crime script
// Note -- these functions are different depending on the crime; Pickpocketing, for example, is much more
//         complex to check for than Disposal.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  setTimeout(formatPageOnce, 650);
  const pageLandingInterval = setInterval(startCheckingPageToFormat, 650);

//
// GreaseMonkey can't listen for disposal page directly, so we run this on all crimes pages.
// however if we navigate away from disposal, we stop listening with our observer
//
  let pagePopInterval;
  window.addEventListener('popstate', function () {
    setTimeout(formatPageOnce, 650);
    pagePopInterval = setInterval(startCheckingPageToFormat, 650);
  });
  function formatPageOnce() {
    if (!window.location.href.includes('#/disposal')) {
      return;
    }
    executeCrimeScript();
  }

  /**
   * This function clears intervals checking the page, as the page has already been formatted
   */
  function clearPageCheckingIntervals(reason) {
    if (pageLandingInterval) {
      console.warn('clearing pageLandingInterval: ' + reason);
      clearInterval(pageLandingInterval);
    }
    if (pagePopInterval) {
      console.warn('clearing pagePopInterval: ' + reason);
      clearInterval(pagePopInterval);
    }
  }
  let alreadyListening = false;

  /**
   * This function is called on an interval to see if the page needs formatting.
   * If it does, it calls {@link #formatPageOnce} and stops checking after that; otherwise, it keeps going.
   */
  function startCheckingPageToFormat() {
    if (!window.location.href.includes('#/disposal')) {
      alreadyListening = false;
      clearPageCheckingIntervals('not disposal page');
      return;
    }
    if (alreadyListening) {
      clearPageCheckingIntervals('already listening');
      return;
    }
    formatPageOnce();
    alreadyListening = true;
  }

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Global variables - functions and variables that can be re-used across all crimes
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * @return the div containing the list of crime targets (for pickpocketing and disposal, at least)
   */
  function getCrimesContainer() {
    const crimesContainerName = document.querySelectorAll('[class^="crimeOptionGroup"]')[0].classList[0];
    return document.getElementsByClassName(crimesContainerName)[0];
  }

  /**
   * Utility for inspecting children of an element
   *
   * @return child which has a class starting with {@param name}
   */
  function findChildByClassStartingWith(name, parentEle) {
    for (const child of parentEle.children) {
      for (const childClass of child.classList) {
        if (!!childClass && childClass.startsWith(name)) {
          return child;
        }
      }
    }
    return null;
  }

  /**
   * Utility for inspecting children of an element
   *
   * @return child which has a class starting with {@param name}
   */
  function findChildByClassContaining(name, parentEle) {
    for (const child of parentEle.children) {
      for (const childClass of child.classList) {
        if (!!childClass && childClass.indexOf(name) !== -1) {
          return child;
        }
      }
    }
    return null;
  }

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MAIN SCRIPT - The code below is specific to this crime
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  let totalNerveHasBeenCalculated = false;

  /**
   * One purpose - execute the main crime script
   */
  function executeCrimeScript() {
    let totalNerveNeeded = 0;
    for (const jobNode of getCrimesContainer().children) {
      totalNerveNeeded += getNerveNeededForJob(jobNode);

      // Format each cell
      formatJob(jobNode);
    }
    if (totalNerveHasBeenCalculated) {
      return;
    } else {
      totalNerveHasBeenCalculated = true;
    }
    // Set total nerve at top
    const titleDiv = document.querySelectorAll('[class^="crimeHeading"]')[1].children[0];
    titleDiv.textContent = `${titleDiv.textContent} (Max Nerve needed: ${totalNerveNeeded})`;
  }
  function getNerveNeededForJob(jobNode) {
    const jobSections = findChildByClassStartingWith('sections', jobNode);

    // TODO not sure if jobName works for all views
    const jobName = jobSections.children[1].textContent;
    const methodDifficulties = JOB_METHOD_DIFFICULTIES_MAP[jobName];
    if (methodDifficulties && methodDifficulties.safe.length) {
      const highestNerveMethod = methodDifficulties.safe[methodDifficulties.safe.length - 1];
      return NERVE_COST_BY_METHOD[highestNerveMethod];
    } else {
      return 0;
    }
  }
  function formatJob(jobNode) {
    const jobSections = findChildByClassStartingWith('sections', jobNode);

    // TODO not sure if jobName works for all views
    const jobName = jobSections.children[1].textContent;
    let disposalMethodsContainer = findChildByClassContaining('desktopMethodsSection', jobSections);
    if (!disposalMethodsContainer) {
      disposalMethodsContainer = findChildByClassContaining('tabletMethodsSection', jobSections);

      // Have to go one deeper to get the methods container
      // However they remain the same options
      disposalMethodsContainer = findChildByClassStartingWith('methodPicker', disposalMethodsContainer);
    }
    const methodDifficulties = JOB_METHOD_DIFFICULTIES_MAP[jobName];
    if (methodDifficulties) {
      for (const safeMethod of methodDifficulties.safe) {
        const node = findChildByClassStartingWith(safeMethod.toLowerCase(), disposalMethodsContainer);
        if (node) {
          node.style.border = '3px solid ' + colors.safe;
        }
      }
      for (const moderatelySafeMethod of methodDifficulties.moderatelySafe) {
        const node = findChildByClassStartingWith(moderatelySafeMethod.toLowerCase(), disposalMethodsContainer);
        if (node) {
          node.style.border = '2px solid ' + colors.moderatelySafe;
        }
      }
      for (const cautionMethod of methodDifficulties.caution) {
        const node = findChildByClassStartingWith(cautionMethod.toLowerCase(), disposalMethodsContainer);
        if (node) {
          node.style.border = '2px solid ' + colors.caution;
        }
      }
      for (const unsafeMethod of methodDifficulties.unsafe) {
        const node = findChildByClassStartingWith(unsafeMethod.toLowerCase(), disposalMethodsContainer);
        if (node) {
          node.style.border = '3px solid ' + colors.unsafe;
        }
      }
    }
  }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址