Torn Crimes Cracking Difficulty

Shows difficulty of cracking targets in cycles & nerve to be spent.

  1. // ==UserScript==
  2. // @name Torn Crimes Cracking Difficulty
  3. // @namespace https://github.com/SOLiNARY
  4. // @version 0.1.3
  5. // @description Shows difficulty of cracking targets in cycles & nerve to be spent.
  6. // @author Ramin Quluzade, Silmaril [2665762]
  7. // @license MIT License
  8. // @match https://www.torn.com/loader.php?sid=crimes*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
  10. // @grant GM_addStyle
  11. // @grant unsafeWindow
  12. // ==/UserScript==
  13.  
  14. (async function() {
  15. 'use strict';
  16.  
  17. const crackingHash = '#/cracking';
  18. const difficultyInfoTemplate = '{cycles} cycles, {attempts} attempts, {nerve} nerve needed';
  19. const isTampermonkeyEnabled = typeof unsafeWindow !== 'undefined';
  20. const isMobileView = window.innerWidth <= 784;
  21. const difficultyColorMap = {
  22. "OneAttempt": "#37b24d",
  23. "ThreeOrLess": "#74b816",
  24. "FiveOrLess": "#f59f00",
  25. "SevenOrLess": "#f76707",
  26. "TenOrLess": "#f03e3e",
  27. "MoreThanTen": "#7048e8",
  28. };
  29. let isOnCrackingPage = false;
  30. let bruteForceStrength = 0;
  31.  
  32. const styles = `
  33. @media only screen and (max-width: 784px) {
  34. span.silmaril-crimes-cracking-difficulty {
  35. font-size: xx-small;
  36. }
  37.  
  38. div.crime-root.cracking-root div[class^=crimeOptionWrapper___] div[class^=sections___] {
  39. height: 54px !important;
  40. }
  41. }
  42. `;
  43.  
  44. if (isTampermonkeyEnabled){
  45. GM_addStyle(styles);
  46. } else {
  47. let style = document.createElement("style");
  48. style.type = "text/css";
  49. style.innerHTML = styles;
  50. while (document.head == null){
  51. await sleep(50);
  52. }
  53. document.head.appendChild(style);
  54. }
  55.  
  56. checkURLChange();
  57.  
  58. setInterval(checkURLChange, 750);
  59.  
  60. const targetNode = document.querySelector("div.crimes-app");
  61. const observerConfig = { childList: true, subtree: true };
  62. const observer = new MutationObserver(async (mutationsList, observer) => {
  63. for (const mutation of mutationsList) {
  64. if (!isOnCrackingPage) {
  65. break;
  66. }
  67.  
  68. let mutationTarget = mutation.target;
  69. if (mutation.type === 'childList' && mutationTarget.className.indexOf('outcomeWrapper___') >= 0) {
  70. let outcomeDiv = mutationTarget.querySelector('div[class*=outcome___]');
  71. if (outcomeDiv == null || outcomeDiv.hasAttribute('data-cracking-difficulty-value-set')) {
  72. continue;
  73. }
  74. outcomeDiv.setAttribute('data-cracking-difficulty-value-set', '');
  75.  
  76. const crimeOption = mutationTarget.parentNode.querySelector('div.crime-option');
  77. if (crimeOption.classList.contains('crime-option')){
  78. setTimeout(function() {calculateDifficulty(crimeOption)}, 500);
  79. }
  80. break;
  81. }
  82. }
  83. });
  84. observer.observe(targetNode, observerConfig);
  85.  
  86. async function addDifficulty() {
  87. while (document.querySelector('div.crime-root.cracking-root div[class^=currentCrime___]') == null) {
  88. if (!isOnCrackingPage) {
  89. break;
  90. }
  91. await sleep(50);
  92. }
  93. if (!isOnCrackingPage) {
  94. return;
  95. }
  96.  
  97. const targets = document.querySelectorAll('div[class^=currentCrime___] div[class^=virtualList___] div[class^=crimeOptionWrapper___] div.crime-option');
  98. while (document.querySelector('div[class^=rig___]') == null){
  99. await sleep(50);
  100. }
  101. bruteForceStrength = parseFloat(document.querySelector('div[class^=rig___] div[class^=statistics___] div[class*=strength___] span[class^=value___]').innerText);
  102.  
  103. targets.forEach(target => calculateDifficulty(target));
  104. }
  105.  
  106. function calculateDifficulty(target) {
  107. const targetDescription = target.querySelector('div[class*=targetSection___]');
  108.  
  109. let cyclesNeeded = 0;
  110. const characters = target.querySelectorAll('div[class^=sections___] div[class^=charSlot___]');
  111. characters.forEach(character => {
  112. cyclesNeeded += parseDifficulty(character) + 1;
  113. });
  114. const attemptsNeeded = Math.ceil(cyclesNeeded / bruteForceStrength);
  115. const nerveNeeded = Math.round(attemptsNeeded * 7 + 5);
  116. updateDifficultyInfo(targetDescription, cyclesNeeded, attemptsNeeded, nerveNeeded);
  117. }
  118.  
  119. function updateDifficultyInfo(targetDescription, cyclesNeeded, attemptsNeeded, nerveNeeded) {
  120. const difficultyInfoSpan = targetDescription.querySelector('span.silmaril-crimes-cracking-difficulty') ?? document.createElement('span');
  121. difficultyInfoSpan.className = 'silmaril-crimes-cracking-difficulty';
  122. switch (attemptsNeeded) {
  123. case 0:
  124. case 1:
  125. difficultyInfoSpan.style.color = difficultyColorMap.OneAttempt;
  126. break;
  127. case 2:
  128. case 3:
  129. difficultyInfoSpan.style.color = difficultyColorMap.ThreeOrLess;
  130. break;
  131. case 4:
  132. case 5:
  133. difficultyInfoSpan.style.color = difficultyColorMap.FiveOrLess;
  134. break;
  135. case 6:
  136. case 7:
  137. difficultyInfoSpan.style.color = difficultyColorMap.SevenOrLess;
  138. break;
  139. case 8:
  140. case 9:
  141. case 10:
  142. difficultyInfoSpan.style.color = difficultyColorMap.TenOrLess;
  143. break;
  144. default:
  145. difficultyInfoSpan.style.color = difficultyColorMap.MoreThanTen;
  146. break;
  147. }
  148. difficultyInfoSpan.innerText = difficultyInfoTemplate.replace('{cycles}', cyclesNeeded).replace('{attempts}', attemptsNeeded).replace('{nerve}', nerveNeeded);
  149. if (isMobileView) {
  150. targetDescription.querySelector('div[class^=typeAndServiceWrapper___]').append(difficultyInfoSpan);
  151. } else {
  152. targetDescription.querySelector('div[class^=typeAndService___]').append(difficultyInfoSpan);
  153. }
  154.  
  155. }
  156.  
  157. function checkURLChange() {
  158. const currentURL = window.location.href;
  159. if (currentURL !== checkURLChange.previousURL) {
  160. if (window.location.href.includes(crackingHash)) {
  161. isOnCrackingPage = true;
  162. addDifficulty();
  163. } else {
  164. isOnCrackingPage = false;
  165. }
  166. }
  167. checkURLChange.previousURL = currentURL;
  168. }
  169.  
  170. function parseDifficulty(element) {
  171. const label = element.getAttribute('aria-label');
  172. if (label == null) {
  173. return 0;
  174. }
  175. if (element.querySelector('span[class^=discoveredChar___]') != null) {
  176. return -1;
  177. }
  178. const matchEncrypted = label.match(/, (\d+) encryption/);
  179. if (matchEncrypted && matchEncrypted[1]) {
  180. return parseInt(matchEncrypted[1]);
  181. }
  182. return null;
  183. }
  184.  
  185. function sleep(ms) {
  186. return new Promise(resolve => setTimeout(resolve, ms));
  187. }
  188. })();