MouseHunt - Hween 2022 Trick/Treat map colour coder

Color codes mice on trick/treat maps according to type. Max ML shown per group and AR shown individually.

  1. // ==UserScript==
  2. // @name MouseHunt - Hween 2022 Trick/Treat map colour coder
  3. // @author tsitu & Leppy & in59te & Warden Slayer
  4. // @namespace https://greasyfork.org/en/users/967077-maidenless
  5. // @version 1.1.6
  6. // @description Color codes mice on trick/treat maps according to type. Max ML shown per group and AR shown individually.
  7. // @match http://www.mousehuntgame.com/*
  8. // @match https://www.mousehuntgame.com/*
  9. // @include https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
  10. // ==/UserScript==
  11. // Credits:
  12. // tsitu - Provided the original code.
  13. // in59te - Improved the original code. We use his version as the starting point.
  14. // Warden Slayer - Implemented bait changes.
  15. // Kuhmann, Leppy and Neb - Maintenance and QA
  16. // tmrj2222 - Provided code to sort the mice by groups.
  17. // and anyone else we may have missed :peepolove:
  18.  
  19. const displayMinLuck = true; // Will display minluck for the group of mouse iff true.
  20. const displayAR = true; // Will display the AR for each uncaught mouse iff true.
  21. const displayHunterCheese = true; // Will display which group of mouse the hunter if attempting iff true.
  22. let assignBaitChange = true; // Avoid the bait change event being registered more than once.
  23.  
  24. // If the chest name contains any of hte following as a substring, enable the colour coder.
  25. const chestKeywords = [
  26. "Halloween",
  27. "Undead",
  28. ];
  29.  
  30. // name, AR
  31. const standardMice = [
  32. ["Candy Cat", "9.02%"],
  33. ["Candy Goblin", "9.23%"],
  34. ["Cobweb", "9.74%"],
  35. ["Grey Recluse", "9.88%"],
  36. ["Shortcut", "20.30%"],
  37. ["Sugar Rush", "7.60%"],
  38. ["Teenage Vampire", "8.79%"],
  39. ["Tricky Witch", "15.40%"],
  40. ["Zombot Unipire", "10.04%"],
  41. ];
  42. const jackoMice = [
  43. ["Gourdborg", "7.90%"],
  44. ["Maize Harvester", "19.94%"],
  45. ["Pumpkin Hoarder", "25.50%"],
  46. ["Spirit Light", "11.97%"],
  47. ["Treat", "14.84%"],
  48. ["Trick", "14.85%"],
  49. ["Wild Chainsaw", "5.00%"],
  50. ];
  51. const boneMice = [
  52. ["Creepy Marionette", "9.91%"],
  53. ["Dire Lycan", "14.96%"],
  54. ["Grave Robber", "4.99%"],
  55. ["Hollowhead", "19.68%"],
  56. ["Mousataur Priestess", "9.94%"],
  57. ["Sandmouse", "20.62%"],
  58. ["Titanic Brain-Taker", "14.78%"],
  59. ["Tomb Exhumer", "5.12%"],
  60. ];
  61. const pgMice = [
  62. ["Admiral Arrrgh", "4.92%"],
  63. ["Captain Cannonball", "22.63%"],
  64. ["Ghost Pirate Queen", "4.97%"],
  65. ["Gourd Ghoul", "10.06%"],
  66. ["Scorned Pirate", "21.66%"],
  67. ["Spectral Butler", "13.98%"],
  68. ["Spectral Swashbuckler", "21.78%"],
  69. ];
  70. const screamMice = [
  71. ["Baba Gaga", "15.15%"],
  72. ["Bonbon Gummy Globlin", "20.65%"],
  73. ["Hollowed", "19.65%"],
  74. ["Hollowed Minion", "24.90%"],
  75. ["Swamp Thang", "19.65%"],
  76. ];
  77.  
  78. // group name, mice, minimum luck, bait, bait ID, color
  79. const miceGroups = [
  80. ["Std", standardMice, 40, "", 114, "#D7BDE2"], // light purple
  81. ["Jack", jackoMice, 40, "Monterey Jack-O-Lantern", 3305, "#F8C471"], // orange
  82. ["Bone", boneMice, 45, "Bonefort Cheese", 3306, "#E6B0AA"], // red
  83. ["Polter", pgMice, 45, "Polter-Geitost", 3307, "#A9CCE3"], // blue
  84. ["Scream", screamMice, 45, "Scream Cheese", 3308, "#AED581"], // green
  85. ];
  86.  
  87. class Mouse {
  88. constructor(name, AR) {
  89. this.name = name;
  90. this.AR = AR;
  91. }
  92. }
  93.  
  94. class MiceGroup {
  95. constructor(name, minluck, cheese, baitId, color) {
  96. this.name = name;
  97. this.mice = [];
  98. this.minluck = minluck;
  99. this.cheese = cheese;
  100. this.baitId = baitId;
  101. this.color = color;
  102. this.count = 0;
  103. }
  104.  
  105. add(mouse) {
  106. this.mice.push(mouse);
  107. }
  108.  
  109. hasMouse(name) {
  110. for (let i = 0; i < this.mice.length; i++) {
  111. if (this.mice[i].name == name) {
  112. return true;
  113. }
  114. }
  115. return false;
  116. }
  117.  
  118. getAR(name) {
  119. for (let i = 0; i < this.mice.length; i++) {
  120. if (this.mice[i].name == name) {
  121. return this.mice[i].AR;
  122. }
  123. }
  124. return "0.00%";
  125. }
  126. }
  127.  
  128. let allMiceGroups = []; // This contains all info about the various group of mice.
  129. let miceNameDict = {}; // If displayAR == true, we are forced to modify the <span> element's text to mouse name + AR, so we need to be able to go back to the original mouse name.
  130.  
  131. function initialise() {
  132. // Avoid initialising more than once as the script can be called multiple times by other plug-in.
  133. if (allMiceGroups.length > 0) {
  134. //sortGoals();
  135. return;
  136. }
  137.  
  138. // Populate allMiceGroups from miceGroups
  139. for (let i = 0; i < miceGroups.length; i++) {
  140. let miceGroup = new MiceGroup(
  141. miceGroups[i][0],
  142. miceGroups[i][2],
  143. miceGroups[i][3],
  144. miceGroups[i][4],
  145. miceGroups[i][5]
  146. );
  147. for (let j = 0; j < miceGroups[i][1].length; j++) {
  148. miceGroup.add(new Mouse(miceGroups[i][1][j][0], miceGroups[i][1][j][1]));
  149. }
  150. allMiceGroups.push(miceGroup);
  151. }
  152. }
  153.  
  154. function addAr(mouseSpan, mouseName, miceGroup) {
  155. const mouseNameWithAr = mouseName + " (" + miceGroup.getAR(mouseName) + ")";
  156. //console.log("checking " + mouseNameWithAr + " in dict: " + (mouseNameWithAr in miceNameDict));
  157. if (!(mouseNameWithAr in miceNameDict)) {
  158. miceNameDict[mouseNameWithAr] = mouseName;
  159. }
  160. mouseSpan.querySelector(".treasureMapView-goals-group-goal-name").querySelector("span").firstChild .textContent = mouseNameWithAr;
  161. }
  162.  
  163. const defaultColor = miceGroups[0][5];
  164. const hunterColor = [defaultColor, defaultColor, defaultColor, defaultColor, defaultColor];
  165. var numHunters = 0;
  166.  
  167. function getCheeseColor(cheese) {
  168. for (let i = 0; i < allMiceGroups.length; i++) {
  169. if (allMiceGroups[i].cheese == cheese) {
  170. return allMiceGroups[i].color;
  171. }
  172. }
  173. return defaultColor; // return the default color if no matching cheese.
  174. }
  175.  
  176. function hunterColorize() {
  177. document.querySelectorAll(".treasureMapRootView-subTab[data-type='manage_allies']")[0].click(); //Click on Hunters tab
  178. let hunters = document.querySelectorAll(".treasureMapView-componentContainer");
  179. const list_of_cheese = [];
  180. for (let i = 0; i < hunters.length; i++) {
  181. list_of_cheese.push(hunters[i].children[2].title);
  182. }
  183. //console.log(list_of_cheese);
  184. numHunters = hunters.length;
  185. document.querySelectorAll(".treasureMapRootView-subTab[data-type='show_goals']")[0].click(); //Click on Goals tab
  186.  
  187.  
  188. for (let i = 0; i < numHunters; i++) {
  189. hunterColor[i] = getCheeseColor(list_of_cheese[i]);
  190. }
  191. //console.log(hunterColor);
  192. }
  193.  
  194. function colorize() {
  195. const greyColor = "#949494";
  196.  
  197. const isChecked =
  198. localStorage.getItem("highlightPref") === "uncaught-only" ? true : false;
  199. const isCheckedStr = isChecked ? "checked" : "";
  200.  
  201. if (
  202. document.querySelectorAll(".treasureMapView-goals-group-goal").length === 0
  203. ) {
  204. return;
  205. }
  206.  
  207. for (let i = 0; i < allMiceGroups.length; i++) {
  208. allMiceGroups[i].count = 0;
  209. }
  210.  
  211. /*
  212. for (const key of Object.keys(miceNameDict)) {
  213. console.log(key + ": " + miceNameDict[key])
  214. }
  215. */
  216.  
  217. document.querySelectorAll(".treasureMapView-goals-group-goal").forEach(el => {
  218. let mouseName = el.querySelector(".treasureMapView-goals-group-goal-name").querySelector("span").firstChild .textContent;
  219. // Fix up the mouse name if we added AR info in.
  220. if (mouseName in miceNameDict) {
  221. mouseName = miceNameDict[mouseName];
  222. }
  223. //console.log(mouseName);
  224.  
  225. for (let i = 0; i < allMiceGroups.length; i++) {
  226. if (allMiceGroups[i].hasMouse(mouseName)) {
  227. el.style.backgroundColor = allMiceGroups[i].color;
  228. if (el.className.indexOf(" complete ") < 0) {
  229. allMiceGroups[i].count++;
  230. if (displayAR) {
  231. addAr(el, mouseName, allMiceGroups[i]);
  232. }
  233. } else {
  234. if (isChecked) el.style.backgroundColor = "white";
  235. }
  236. }
  237. }
  238. });
  239.  
  240. /*
  241. for (let i = 0; i < allMiceGroups.length; i++) {
  242. console.log(allMiceGroups[i].name + " " + allMiceGroups[i].cheese + " " + allMiceGroups[i].count);
  243. }*/
  244.  
  245. // Remove existing tsitu-map-div related elements before proceeding
  246. document.querySelectorAll(".tsitu-map-div").forEach(el => el.remove());
  247.  
  248. const masterDiv = document.createElement("div");
  249. masterDiv.className = "tsitu-map-div";
  250. masterDiv.style =
  251. "display: inline-flex; margin-bottom: 10px; width: 100%; text-align: center; line-height: 1.5; overflow: hidden";
  252. const spanStyle =
  253. "; width: auto; padding: 5px; font-weight: bold; font-size: 12.75px; text-shadow: 0px 0px 11px white";
  254.  
  255. const spans = [];
  256.  
  257. for (let i = 0; i < allMiceGroups.length; i++) {
  258. const newSpan = document.createElement("span");
  259. newSpan.classList.add(allMiceGroups[i].name + "Span");
  260. if (allMiceGroups[i].count > 0) {
  261. newSpan.style = "background-color: " + allMiceGroups[i].color + spanStyle;
  262. }
  263. else {
  264. newSpan.style = "background-color: " + greyColor + spanStyle;
  265. }
  266. newSpan.innerHTML = allMiceGroups[i].name;
  267. if (displayMinLuck) {
  268. newSpan.innerHTML = newSpan.innerHTML + " (" + allMiceGroups[i].minluck + ")";
  269. }
  270. newSpan.innerHTML = newSpan.innerHTML + "<br>" + allMiceGroups[i].count;
  271. spans.push(newSpan);
  272. }
  273.  
  274. // Highlight uncaught only feature
  275. const highlightLabel = document.createElement("label");
  276. highlightLabel.htmlFor = "tsitu-highlight-box";
  277. highlightLabel.innerText = "Highlight uncaught mice only";
  278.  
  279. const highlightBox = document.createElement("input");
  280. highlightBox.type = "checkbox";
  281. highlightBox.name = "tsitu-highlight-box";
  282. highlightBox.style.verticalAlign = "middle";
  283. highlightBox.checked = isChecked;
  284. highlightBox.addEventListener("click", function () {
  285. if (highlightBox.checked) {
  286. localStorage.setItem("highlightPref", "uncaught-only");
  287. } else {
  288. localStorage.setItem("highlightPref", "all");
  289. }
  290. if (displayHunterCheese) {
  291. hunterColorize();
  292. }
  293. colorize();
  294.  
  295. });
  296.  
  297. const highlightDiv = document.createElement("div");
  298. highlightDiv.className = "tsitu-map-div";
  299. highlightDiv.style = "float: right; position: relative; z-index: 1";
  300. highlightDiv.appendChild(highlightBox);
  301. highlightDiv.appendChild(highlightLabel);
  302.  
  303. // Assemble masterDiv
  304. for (let i = 0; i < spans.length; i++) {
  305. masterDiv.appendChild(spans[i]);
  306. }
  307.  
  308. // Inject into DOM
  309. const insertEl = document.querySelector(
  310. ".treasureMapView-leftBlock .treasureMapView-block-content"
  311. );
  312. if (
  313. insertEl &&
  314. document.querySelector(
  315. ".treasureMapRootView-header-navigation-item.tasks.active" // On "Active Maps"
  316. )
  317. ) {
  318. insertEl.insertAdjacentElement("afterbegin", highlightDiv);
  319. insertEl.insertAdjacentElement("afterbegin", masterDiv);
  320. }
  321.  
  322. var canvas = [];
  323. var div = document.getElementsByClassName("treasureMapView-hunter-wrapper"); //MH improved removes mousehuntTooltipParent class??
  324.  
  325. if (displayHunterCheese) {
  326. for (var i=0; i<div.length; i++){
  327. canvas[i] = document.createElement('canvas');
  328. canvas[i].id = "hunter-canvas";
  329. canvas[i].style = "; bottom: 0px; left: 0px; position: absolute; width: 15px; height: 15px; background: " + hunterColor[i] + "; border: 1px solid black";
  330. div[i].appendChild(canvas[i]);
  331. }
  332. }
  333.  
  334. // "Goals" button
  335. document.querySelector("[data-type='show_goals']").onclick = function () {
  336. colorize();
  337. };
  338.  
  339. if (assignBaitChange) {
  340. // Avoid assigning the event more than once.
  341. assignBaitChange = false;
  342. for (let i = 0; i < allMiceGroups.length; i++) {
  343. //Warden added this (waves)
  344. $(document).on('click', '.' + allMiceGroups[i].name + 'Span', function() {
  345. hg.utils.TrapControl.setBait(allMiceGroups[i].baitId).go();
  346. });
  347. }
  348. }
  349.  
  350. sortGoals();
  351. }
  352.  
  353. // Credit to @tmrj2222 for the code
  354. function sortGoals() {
  355. //const regex = / \(\d+\.\d+%?\)$/;
  356. const parentGoals = document.querySelector(".treasureMapView-goals-group-goal-padding").parentElement.parentElement;
  357. //console.log(parentGoals.textContent);
  358.  
  359. const childrenArray = Array.from(parentGoals.children);
  360. //console.log("before sorting");
  361. //console.log(childrenArray);
  362. childrenArray.sort((a, b) => {
  363. let orderA = -1;
  364. let orderB = -1;
  365.  
  366. //const nameA = a.querySelector("span").firstChild.textContent.replace(regex, '');
  367. //const nameB = b.querySelector("span").firstChild.textContent.replace(regex, '');
  368. const nameA = displayAR ? miceNameDict[a.querySelector("span").firstChild.textContent] : a.querySelector("span").firstChild.textContent;
  369. const nameB = displayAR ? miceNameDict[b.querySelector("span").firstChild.textContent] : b.querySelector("span").firstChild.textContent;
  370.  
  371. //console.log('nameA == ' + nameA);
  372. //console.log('nameB == ' + nameB);
  373.  
  374. for (let i = 0; i < allMiceGroups.length; i++) {
  375. if (allMiceGroups[i].hasMouse(nameA)) {
  376. orderA = i;
  377. break;
  378. }
  379. }
  380. for (let i = 0; i < allMiceGroups.length; i++) {
  381. if (allMiceGroups[i].hasMouse(nameB)) {
  382. orderB = i;
  383. break;
  384. }
  385. }
  386. return orderA - orderB;
  387. });
  388. while (parentGoals.firstChild) {
  389. parentGoals.removeChild(parentGoals.firstChild);
  390. }
  391. childrenArray.forEach(child => parentGoals.appendChild(child));
  392. }
  393.  
  394.  
  395. // Listen to XHRs, opening a map always at least triggers board.php
  396. const originalOpen = XMLHttpRequest.prototype.open;
  397. XMLHttpRequest.prototype.open = function () {
  398. this.addEventListener("load", function () {
  399. const chestEl = document.querySelector(
  400. ".treasureMapView-mapMenu-rewardName"
  401. );
  402.  
  403. if (chestEl) {
  404. const chestName = chestEl.textContent;
  405. if (
  406. chestName && chestKeywords.some(v => chestName.includes(v))
  407. ) {
  408. initialise();
  409. if (displayHunterCheese) {
  410. hunterColorize();
  411. }
  412. colorize();
  413. }
  414. }
  415. });
  416. originalOpen.apply(this, arguments);
  417. };