Greasy Fork

Greasy Fork is available in English.

MouseHunt - QoL Utilities

Miscellaneous utilities to turbo-charge your MH experience

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MouseHunt - QoL Utilities
// @author       Tran Situ (tsitu)
// @namespace    http://greasyfork.icu/en/users/232363-tsitu
// @version      1.1.7
// @description  Miscellaneous utilities to turbo-charge your MH experience
// @match        http://www.mousehuntgame.com/*
// @match        https://www.mousehuntgame.com/*
// ==/UserScript==

(function () {
  /**
   * IDEA BANK
   * TODO: Alert when sounding horn after unseen trap check (user set 00, 15, 30, 45) to avoid wasted hunt if TC'd something
   * TODO: Track outbound supply transfers
   */

  // Adds direct hunter ID / snuid navigation popup via button on 'Friends' dropdown
  (function hunterIdNav() {
    document
      .querySelectorAll(".tsitu-hunter-id-nav")
      .forEach(el => el.remove());

    const target = document.querySelector("li .friend_list");
    if (target) {
      const li = document.createElement("li");
      li.className = "tsitu-hunter-id-nav";
      const button = document.createElement("a");
      button.href = "#";
      button.innerText = "ID Navigation";
      button.onclick = function () {
        const existing = document.querySelector("#tsitu-hunter-id-nav-ui");
        if (existing) existing.remove();
        else render();
        return false;
      };
      const icon = document.createElement("div");
      icon.className = "icon";
      button.appendChild(icon);
      li.appendChild(button);
      target.insertAdjacentElement("afterend", li);

      function render() {
        document
          .querySelectorAll("#tsitu-hunter-id-nav-ui")
          .forEach(el => el.remove());

        const div = document.createElement("div");
        div.id = "tsitu-hunter-id-nav-ui";
        div.style.backgroundColor = "#F5F5F5";
        div.style.position = "fixed";
        div.style.zIndex = "9999";
        div.style.left = "22.3vw";
        div.style.top = "28vh";
        div.style.border = "solid 3px #696969";
        div.style.borderRadius = "20px";
        div.style.padding = "10px";
        div.style.textAlign = "center";

        const closeButton = document.createElement("button", {
          id: "close-button"
        });
        closeButton.textContent = "x";
        closeButton.onclick = function () {
          document.body.removeChild(div);
        };

        const table = document.createElement("table");
        table.style.textAlign = "left";
        table.style.borderSpacing = "1em 0";
        const rowHid = document.createElement("tr");
        const rowSnuid = document.createElement("tr");

        const hidRadio = document.createElement("input");
        hidRadio.type = "radio";
        hidRadio.name = "tsitu-hunter-id";
        hidRadio.id = "tsitu-radio-hid";
        hidRadio.defaultChecked = true;
        hidRadio.onchange = function () {
          processRadio();
        };

        const hidRadioLabel = document.createElement("label");
        hidRadioLabel.innerText = "Hunter ID: ";
        hidRadioLabel.htmlFor = "tsitu-radio-hid";

        const hidInput = document.createElement("input");
        hidInput.type = "text";
        hidInput.id = "tsitu-input-hid";
        hidInput.placeholder = "e.g. 3795351";
        hidInput.onkeyup = function (event) {
          if (event.keyCode === 13) {
            goButton.click();
          }
        };

        const colHid = document.createElement("td");
        const colHidInput = document.createElement("td");
        colHid.appendChild(hidRadio);
        colHid.appendChild(document.createTextNode("\u00A0"));
        colHid.appendChild(hidRadioLabel);
        colHidInput.appendChild(hidInput);
        rowHid.appendChild(colHid);
        rowHid.appendChild(colHidInput);

        const snuidRadio = document.createElement("input");
        snuidRadio.type = "radio";
        snuidRadio.name = "tsitu-hunter-id";
        snuidRadio.id = "tsitu-radio-snuid";
        snuidRadio.onchange = function () {
          processRadio();
        };

        const snuidRadioLabel = document.createElement("label");
        snuidRadioLabel.innerText = "SN User ID: ";
        snuidRadioLabel.htmlFor = "tsitu-radio-snuid";

        const snuidInput = document.createElement("input");
        snuidInput.type = "text";
        snuidInput.id = "tsitu-input-snuid";
        snuidInput.placeholder = "e.g. 1062432650";
        snuidInput.onkeyup = function (event) {
          if (event.keyCode === 13) {
            goButton.click();
          }
        };

        const colSnuid = document.createElement("td");
        const colSnuidInput = document.createElement("td");
        colSnuid.appendChild(snuidRadio);
        colSnuid.appendChild(document.createTextNode("\u00A0"));
        colSnuid.appendChild(snuidRadioLabel);
        colSnuidInput.appendChild(snuidInput);
        rowSnuid.appendChild(colSnuid);
        rowSnuid.appendChild(colSnuidInput);

        function processRadio() {
          if (hidRadio.checked) {
            hidInput.disabled = false;
            snuidInput.disabled = true;
            localStorage.setItem("tsitu-id-radio", "hid");
          } else if (snuidRadio.checked) {
            hidInput.disabled = true;
            snuidInput.disabled = false;
            localStorage.setItem("tsitu-id-radio", "snuid");
          }
        }

        const goButton = document.createElement("button");
        goButton.style.fontWeight = "bold";
        goButton.innerText = "Go";
        goButton.onclick = function () {
          if (hidRadio.checked) {
            const val = hidInput.value;
            if (
              val.length > 0 &&
              val.length === parseInt(val).toString().length
            ) {
              const newWindow = window.open(
                `https://www.mousehuntgame.com/profile.php?id=${val}`
              );
            }
          } else if (snuidRadio.checked) {
            const val = snuidInput.value;
            if (
              val.length > 0 &&
              val.length === parseInt(val).toString().length
            ) {
              const newWindow = window.open(
                `https://www.mousehuntgame.com/profile.php?snuid=${val}`
              );
            }
          }
        };

        table.appendChild(rowHid);
        table.appendChild(rowSnuid);
        div.appendChild(closeButton);
        div.appendChild(document.createElement("br"));
        div.appendChild(document.createElement("br"));
        div.appendChild(table);
        div.appendChild(document.createElement("br"));
        div.appendChild(goButton);
        document.body.appendChild(div);

        // Apply cached radio selection
        const radioCache = localStorage.getItem("tsitu-id-radio");
        if (radioCache) {
          if (radioCache === "hid") {
            hidRadio.checked = true;
          } else if (radioCache === "snuid") {
            snuidRadio.checked = true;
          }
        }
        processRadio();
      }
    }
  })();

  // TODO: Claim-time map participants/duster tracker (move to mapping helper?)
  (function mapTracker() {
    const map = temp1;
    if (map.can_claim_reward && map.is_complete) {
      //
    }

    const data = {};
    map.hunters.forEach(hunter => {
      if (hunter.is_active) {
        data[hunter.user_id] = {
          name: hunter.name
        };

        const completedItems = hunter.completed_goal_ids.item;
        const completedMice = hunter.completed_goal_ids.mouse;
        if (completedItems.length > 0) {
          data[hunter.user_id].i = completedItems.length;
        }
        if (completedMice.length > 0) {
          data[hunter.user_id].m = completedMice.length;
        }

        if (hunter.upgrader) {
          data[hunter.user_id].d = true;
        }
      }
    });
    console.log(data);
  });

  (function lockConvertibleButtons() {
    // Observe for 'Special' tab of Inventory
    const observerTarget = document.querySelector(".mousehuntPage-content");
    if (observerTarget) {
      MutationObserver =
        window.MutationObserver ||
        window.WebKitMutationObserver ||
        window.MozMutationObserver;

      const observer = new MutationObserver(function () {
        const isSpecial = observerTarget.querySelector(
          ".mousehuntHud-page-tabContent.special.active"
        );
        if (isSpecial) {
          // Disconnect and reconnect later to prevent infinite mutation loop
          observer.disconnect();

          render();

          observer.observe(observerTarget, {
            childList: true,
            subtree: true
          });
        }
      });

      observer.observe(observerTarget, {
        childList: true,
        subtree: true
      });
    }

    function render() {
      document
        .querySelectorAll(".tsitu-lock-convertible")
        .forEach(el => el.remove());

      const convertibles = document.querySelectorAll(
        ".inventoryPage-item.full.convertible"
      );
      if (convertibles.length > 0) {
        // Apply cached locks on page load
        const cacheRaw = localStorage.getItem("tsitu-convertible-locks");
        if (cacheRaw) {
          const cache = JSON.parse(cacheRaw);
          convertibles.forEach(el => {
            const id = el.getAttribute("data-item-id");
            if (cache.indexOf(id) >= 0) {
              el.querySelectorAll(
                ".inventoryPage-item-content-action .button"
              ).forEach(button => {
                if (!button.classList.contains("disabled")) {
                  button.classList.toggle("disabled");
                }
              });
            }
          });
        }

        // Generate individual item lock buttons
        convertibles.forEach(el => {
          const a = document.createElement("a");
          a.href = "#";
          a.className =
            "inventoryPage-item-larryLexicon tsitu-lock-convertible";
          a.style.right = "22px";
          a.style.height = "15px";
          a.innerText = "🔒";
          a.onclick = function () {
            el.querySelectorAll(
              ".inventoryPage-item-content-action .button"
            ).forEach(button => {
              button.classList.toggle("disabled");
            });
            updateLockCache();
            return false;
          };
          const target = el.querySelector(".inventoryPage-item-name");
          if (target) target.insertAdjacentElement("afterend", a);
        });

        function updateLockCache() {
          const cacheArr = [];
          convertibles.forEach(el => {
            const button = el.querySelector(
              ".inventoryPage-item-content-action .button"
            );
            if (button.classList.contains("disabled")) {
              cacheArr.push(el.getAttribute("data-item-id"));
            }
          });
          localStorage.setItem(
            "tsitu-convertible-locks",
            JSON.stringify(cacheArr)
          );
        }

        // Add buttons for [un]locking entire tabs
        const lockableTabs = [
          "Baskets & Kits",
          "Scrolls, Posters, Assignments",
          "Spring Egg Hunt",
          "Treasure Chests"
        ];
        observerTarget
          .querySelectorAll(".inventoryPage-tagContent-tagGroup.clear-block")
          .forEach(tab => {
            const tabType = tab.getAttribute("data-name");
            if (lockableTabs.indexOf(tabType) >= 0) {
              const span = document.createElement("span");
              span.className =
                "inventoryPage-tagContent-tagTitle tsitu-lock-convertible";
              span.style.margin = "5px 0 5px 0";

              const lockAll = document.createElement("button");
              lockAll.innerText = "Lock Tab";
              lockAll.onclick = function () {
                if (
                  confirm(
                    `Are you sure you'd like to lock all convertibles on this tab?\n\n- ${tabType}`
                  )
                ) {
                  tab
                    .querySelectorAll(
                      ".inventoryPage-item-content-action .button"
                    )
                    .forEach(button => {
                      if (!button.classList.contains("disabled")) {
                        button.classList.toggle("disabled");
                      }
                    });
                  updateLockCache();
                }
              };

              const unlockAll = document.createElement("button");
              unlockAll.innerText = "Unlock Tab";
              unlockAll.onclick = function () {
                if (
                  confirm(
                    `Are you sure you'd like to unlock all convertibles on this tab?\n\n- ${tabType}`
                  )
                ) {
                  tab
                    .querySelectorAll(
                      ".inventoryPage-item-content-action .button"
                    )
                    .forEach(button => {
                      if (button.classList.contains("disabled")) {
                        button.classList.toggle("disabled");
                      }
                    });
                  updateLockCache();
                }
              };

              span.appendChild(lockAll);
              span.appendChild(document.createTextNode("\u00A0\u00A0"));
              span.appendChild(unlockAll);
              const target = tab.querySelector(
                ".inventoryPage-tagContent-tagTitle"
              );
              if (target) target.appendChild(span);
            }
          });
      }
    }
  })();

  // Adds RH location and 'Travel' button directly underneath clue
  (function relicHunterHintTravel() {
    const hintMap = {
      "Standing on the other side of a green and purple portal.":
        "Acolyte Realm",
      "Inside an elaborate one-way trap designed by Plankrun.": "Acolyte Realm",
      "Outside a smoky purple tower.": "Acolyte Realm",
      "Roaming amongst the most powerful of Lich mice.": "Balack's Cove",
      "Lurking in a damp and darkened grotto.": "Balack's Cove",
      "Searching for the best deals in the Burroughs.": "Bazaar",
      "Ducking between stalls and tents and loud merchants.": "Bazaar",
      "Under the pointiest tent in all the Kingdom!": "Bazaar",
      "Hobnobbing with giants.": "Bountiful Beanstalk",
      "Infiltrating a lofty castle.": "Bountiful Beanstalk",
      "Taking a relaxing hike through a forested area.": "Calm Clearing",
      "By a peaceful rock in a grassy clearing.": "Calm Clearing",
      "Tucked behind dense trees where it's quiet and peaceful.":
        "Calm Clearing",
      "Watching the peaceful gathering of tribal mice.": "Cape Clawed",
      "On a small bit of land near a volcano.": "Cape Clawed",
      "Listening for sinister secrets deep underground.": "Catacombs",
      "Walking through dark hallways in search of a Keeper's Candle.":
        "Catacombs",
      "Keeping an eye on the long-arm of the law.": "Claw Shot City",
      "Spitting in a spittoon! Yuck!": "Claw Shot City",
      "Leafing through ancient tomes of knowledge.": "Crystal Library",
      "Expanding knowledge and climbing endless ladders.": "Crystal Library",
      "Ankle deep in rocky, tropical sand.": "Derr Dunes",
      "Tumbling down hills of rreD sand.": "Derr Dunes",
      "Practicing an ancient art with fledgling warriors.": "Dojo",
      "Safely inside the bottom floor of a bamboo building.": "Dojo",
      "Carefully watching the training activities of advanced students.":
        "Dojo",
      "Near the bluE waters of the island.": "Elub Shore",
      "Watching the calm waters of Rodentia while remaining safely ashore.":
        "Elub Shore",
      "Marching through the Sandtail Desert.": "Fiery Warpath",
      "Dodging arrows, spears, swords, and spells!": "Fiery Warpath",
      "Investigating what can be built in a workshop.": "Floating Islands",
      "Searching the skies for treasure.": "Floating Islands",
      "Peering through an oculus.": "Floating Islands",
      "In the clouds above Hollow Heights.": "Floating Islands",
      "Watching hunters' dirigibles fly by.": "Floating Islands",
      "Avoiding falling victim to Sky Pirates.": "Floating Islands",
      "Trapped between two planes of existence.": "Forbidden Grove",
      "Behind heavy stone gates.": "Forbidden Grove",
      "Reaping what she sowed...": "Foreword Farm",
      "Cultivating a hearty yield...": "Foreword Farm",
      "Toiling away in fertile fields...": "Foreword Farm",
      "Carefully navigating a subterranean and humid environment.":
        "Fungal Cavern",
      "Deep inside of an infested, glowing, twisting, unending cave of untold riches...":
        "Fungal Cavern",
      "Tracing the deep patterns of bark growing on ancient towers.":
        "Great Gnarled Tree",
      "By a tree older than Gnawnia itself.": "Great Gnarled Tree",
      "Finding shade in the largest tree in the Kingdom.": "Great Gnarled Tree",
      "Near the loud and low horns and the dinging of bells.": "Harbour",
      "Visiting where many new hunters seek out seafaring mice.": "Harbour",
      "By the sea where there's plenty of fresh air and sunshine.": "Harbour",
      "Where royal strength rewards hunting prowess.": "King's Arms",
      "Under a circular roof atop arm-shared paths.": "King's Arms",
      "Browsing wares available with a most royal currency.": "King's Arms",
      "Climbing up spiralling, menacing stairs.": "King's Gauntlet",
      "Trekking up a massive tower in Valour.": "King's Gauntlet",
      "Atop a tall tower with the perfect view of an Eclipse.":
        "King's Gauntlet",
      "Performing bizarre experiments and chemical reactions.": "Laboratory",
      "Where the powerful and strange breeds of mice first arose.":
        "Laboratory",
      "Amongst brightly glowing potions.": "Laboratory",
      "Waist-deep in a shallow, sparkling pond.": "Lagoon",
      "Amongst sparkling, still water.": "Lagoon",
      "Climbing jagged rocks and slick moss.": "Lagoon",
      "Tending to a most troublesome and dangerous garden.": "Living Garden",
      "Enjoying a drink on the overgrown rooftop patio.": "Living Garden",
      "Looking across vast landscapes and the many horizons of the land.":
        "Mountain",
      "Cutting through the pass to reach the town on the other side.":
        "Mountain",
      "In a treacherous environment where only the toughest of mice survive.":
        "Mountain",
      "Investigating the spirits of slain mice.": "Mousoleum",
      "Surveying where scientists harvest 'spare parts'.": "Mousoleum",
      "Studying the spooky remains of Zombie Mice.": "Mousoleum",
      "Climbing and exploring some long lost ruins.": "Moussu Picchu",
      "Up high upon a weather changing plateau.": "Moussu Picchu",
      "Visiting a walled city that is no stranger to sieges.": "Muridae Market",
      "Keeping a close eye on would-be thieves...": "Muridae Market",
      "Investigating a well-seasoned Gumbo Cheese.": "Nerg Plains",
      "Running through flat fields of greeN.": "Nerg Plains",
      "Reeling it in...": "Prologue Pond",
      "Choosing the right tackle...": "Prologue Pond",
      "Enjoying a quick dip in a cheesy bath.": "Queso River",
      "Sipping delicious liquid cheese from a river.": "Queso River",
      "Protecting her ears from the sound of loud pumps.": "Queso River",
      "Testing out balance on the high seas.": "S.S. Huntington IV",
      "Looking a bit queasy...": "S.S. Huntington IV",
      "Watching the sky and wondering what the weather will bring.":
        "Seasonal Garden",
      "Braving the ever-changing elements.": "Seasonal Garden",
      "Walking along the coldest waters in Gnawnia.": "Slushy Shoreline",
      "At the beachside site of an invasion force!": "Slushy Shoreline",
      "Shivering near the edges of the mainland.": "Slushy Shoreline",
      "Walking along the bottom of the Rodentia Ocean.": "Sunken City",
      "Investigating powerful diving equipment.": "Sunken City",
      "Sitting down at a wooden table...": "Table of Contents",
      "Creating clever characters...": "Table of Contents",
      "Amongst the triumphant trumpets of master hunters of old.":
        "Tournament Hall",
      "Browsing the rewards of competitive champions.": "Tournament Hall",
      "Competing for the limelight amongst the finest champions.":
        "Tournament Hall",
      "Exploring the deep and winding caverns near a technologically-advanced underground city.":
        "Town of Digby",
      "Amongst powerful drills and excavation equipment.": "Town of Digby",
      "Hiding in the shadows while standing in the limelight.": "Town of Digby",
      "Hiding within the hustle and bustle in the city of the crown.":
        "Town of Gnawnia",
      "Trying to spot the King himself.": "Town of Gnawnia",
      "In a town with a dense population.": "Town of Gnawnia",
      "Standing among the ranks of new students out in the field.":
        "Training Grounds",
      "Watching the careful training of artful students.": "Training Grounds",
      "Relaxing in the shade of tall engraved rock.": "Training Grounds",
      "Observing the churning and grinding of the new harvest.": "Windmill",
      "By an agricultural structure once owned by one of Gnawnia's most prosperous farmers.":
        "Windmill",
      "Grinding up hundreds of tiny seeds from a stalky, golden plant.":
        "Windmill"
    };

    // MutationObserver logic for map UI
    // Observers are attached to a *specific* element (will DC if removed from DOM)
    const observerTarget = document.getElementById("overlayPopup");
    if (observerTarget) {
      MutationObserver =
        window.MutationObserver ||
        window.WebKitMutationObserver ||
        window.MozMutationObserver;

      const observer = new MutationObserver(function () {
        // Render if Relic Hunter hint is available
        const rhHint = observerTarget.querySelector(
          ".treasureMapInventoryView-relicHunter-hint"
        );

        // Prevent conflict with 'Mapping Helper'
        const mapTab = observerTarget.querySelector(
          ".treasureMapManagerView-header-navigation-item.tasks.active"
        );

        // Prevent conflict with 'Bulk Map Invites'
        const inviteHeader = document.querySelector(
          // ".treasureMapPopup-inviteFriend-header"
          ".treasureMapManagerDialogView-userSelector"
        );

        if (rhHint && !mapTab && !inviteHeader) {
          // Disconnect and reconnect later to prevent infinite mutation loop
          observer.disconnect();

          render();

          observer.observe(observerTarget, {
            childList: true,
            subtree: true
          });
        }
      });

      observer.observe(observerTarget, {
        childList: true,
        subtree: true
      });
    }

    function render() {
      document.querySelectorAll(".tsitu-rh-helper").forEach(el => el.remove());

      const div = document.createElement("div");
      div.style.textAlign = "center";
      div.className = "tsitu-rh-helper";
      const button = document.createElement("a");
      button.href = "#";
      button.className = "mousehuntActionButton small";
      const locSpan = document.createElement("span");
      locSpan.style.fontSize = "12px";
      locSpan.innerText = "N/A";

      const rhHint = observerTarget.querySelector(
        ".treasureMapInventoryView-relicHunter-hint"
      );
      const hint = rhHint.textContent;
      Object.keys(hintMap).forEach(key => {
        const loc = hintMap[key];
        if (hint == key) {
          locSpan.innerText = `Location: ${loc}`;
          button.onclick = function () {
            const newWindow = window.open(
              "https://www.mousehuntgame.com/travel.php?tab=map"
            );
            newWindow.addEventListener("load", function () {
              const hud = newWindow.document.querySelector(
                ".mousehuntHud-page-tabContent[data-tab='map']"
              );
              if (hud && hud.classList.contains("full")) {
                newWindow.document
                  .querySelectorAll(
                    ".travelPage-map-region-environment-link-name"
                  )
                  .forEach(el => {
                    if (el.textContent == loc) {
                      el.click();
                    }
                  });

                const scrollTo = newWindow.document.querySelector(
                  ".travelPage-map-simpleToggle.full"
                );
                if (scrollTo) {
                  scrollTo.scrollIntoView({
                    behavior: "auto",
                    block: "nearest",
                    inline: "nearest"
                  });
                }
              }
            });
            return false;
          };
        }
      });

      const buttonText = document.createElement("span");
      buttonText.innerText = "Travel";
      button.appendChild(buttonText);

      div.appendChild(button);
      div.appendChild(locSpan);
      rhHint.insertAdjacentElement("afterend", div);
    }
  })();
})();