MouseHunt - Mapping Helper

Invite players and send SB+ directly from the map interface

当前为 2019-06-13 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         MouseHunt - Mapping Helper
// @author       Tran Situ (tsitu)
// @namespace    https://greasyfork.org/en/users/232363-tsitu
// @version      1.2
// @description  Invite players and send SB+ directly from the map interface
// @match        http://www.mousehuntgame.com/*
// @match        https://www.mousehuntgame.com/*
// ==/UserScript==

(function() {
  // Valid Queso map variants
  const quesoMaps = [
    "Queso Canyoneer Treasure Map",
    "Queso Geyser Treasure Map",
    "Queso Canyon Grand Tour Treasure Map"
  ];

  // Queso cheese image icons
  const quesoImg = {
    Standard:
      "https://www.mousehuntgame.com/images/items/bait/7e0daa548364166c46c0804e6cb122c6.gif?cv=243",
    "SB+":
      "https://www.mousehuntgame.com/images/items/bait/d3bb758c09c44c926736bbdaf22ee219.gif?cv=243",
    Bland:
      "https://www.mousehuntgame.com/images/items/bait/4752dbfdce202c0d7ad60ce0bacbebae.gif?cv=243",
    Mild:
      "https://www.mousehuntgame.com/images/items/bait/7193159aa90c85ba67cbe02d209e565f.gif?cv=243",
    Medium:
      "https://www.mousehuntgame.com/images/items/bait/be747798c5e6a7747ba117e9c32a8a1f.gif?cv=243",
    Hot:
      "https://www.mousehuntgame.com/images/items/bait/11d1170bc85f37d67e26b0a05902bc3f.gif?cv=243",
    "Flamin'":
      "https://www.mousehuntgame.com/images/items/bait/5a69c1ea617ba622bd1dd227afb69a68.gif?cv=243",
    Wildfire:
      "https://www.mousehuntgame.com/images/items/bait/73891a065f1548e474177165734ce78d.gif?cv=243"
  };

  // Location -> Cheese -> Mouse
  const quesoData = {
    "Queso River": {
      Standard: [
        "Tiny Saboteur",
        "Pump Raider",
        "Croquet Crusher",
        "Queso Extractor"
      ],
      "SB+": ["Sleepy Merchant"],
      Wildfire: ["Queen Quesada"]
    },
    "Prickly Plains": {
      Bland: ["Spice Seer", "Old Spice Collector"],
      Mild: ["Spice Farmer", "Granny Spice"],
      Medium: ["Spice Sovereign", "Spice Finder"],
      Hot: ["Spice Raider", "Spice Reaper"],
      "Flamin'": ["Inferna, The Engulfed"]
    },
    "Cantera Quarry": {
      Bland: ["Chip Chiseler", "Tiny Toppler"],
      Mild: ["Ore Chipper", "Rubble Rummager"],
      Medium: ["Nachore Golem", "Rubble Rouser"],
      Hot: ["Grampa Golem", "Fiery Crusher"],
      "Flamin'": ["Nachous, The Molten"]
    },
    "Cork Collecting": {
      Bland: ["Fuzzy Drake"],
      Mild: ["Cork Defender"],
      Medium: ["Burly Bruiser"],
      Hot: ["Horned Cork Hoarder"],
      "Flamin'": ["Rambunctious Rain Rumbler", "Corky, the Collector"],
      Wildfire: ["Corkataur"]
    },
    "Pressure Building": {
      Mild: ["Steam Sailor"],
      Medium: ["Warming Wyvern"],
      Hot: ["Vaporior"],
      "Flamin'": ["Pyrehyde"],
      Wildfire: ["Emberstone Scaled"]
    },
    "Small Eruption": {
      Mild: ["Sizzle Pup"],
      Medium: ["Sizzle Pup"],
      Hot: ["Sizzle Pup"]
      // Mild: ["Mild Spicekin", "Sizzle Pup"],
      // Medium: ["Sizzle Pup", "Smoldersnap", "Mild Spicekin"],
      // Hot: ["Sizzle Pup", "Ignatia"]
    },
    "Medium Eruption": {
      Medium: ["Bearded Elder"],
      Hot: ["Bearded Elder"],
      "Flamin'": ["Bearded Elder"]
      // Mild: ["Mild Spicekin"],
      // Medium: ["Bearded Elder", "Smoldersnap"],
      // Hot: ["Bearded Elder", "Ignatia"],
      // "Flamin'": ["Bearded Elder", "Bruticus, the Blazing"]
    },
    "Large Eruption": {
      Hot: ["Cinderstorm"],
      "Flamin'": ["Cinderstorm"]
      // Medium: ["Smoldersnap"],
      // Hot: ["Cinderstorm", "Ignatia"],
      // "Flamin'": ["Cinderstorm", "Bruticus, the Blazing"]
    },
    "Epic Eruption": {
      "Flamin'": ["Stormsurge, the Vile Tempest"],
      Wildfire: ["Kalor'ignis of the Geyser"]
      // Hot: ["Ignatia", "Stormsurge, the Vile Tempest"],
      // "Flamin'": ["Stormsurge, the Vile Tempest", "Bruticus, the Blazing"],
    },
    "Any Eruption": {
      Mild: ["Mild Spicekin"],
      Medium: ["Smoldersnap"],
      Hot: ["Ignatia"],
      "Flamin'": ["Bruticus, the Blazing"]
    }
  };

  // Alternate representation: Mouse -> Location -> Cheese
  const quesoMice = {};
  for (let location in quesoData) {
    for (let cheese in quesoData[location]) {
      const arr = quesoData[location][cheese];
      for (let mouse of arr) {
        if (quesoMice[mouse] === undefined) {
          quesoMice[mouse] = [[location, cheese]];
        } else {
          quesoMice[mouse].push([location, cheese]);
        }
      }
    }
  }

  // RH endpoint listener - caches maps (which come in one at a time)
  const originalOpen = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function() {
    this.addEventListener("load", function() {
      if (
        this.responseURL ===
        "https://www.mousehuntgame.com/managers/ajax/users/relichunter.php"
        // && document.querySelector(".treasureMapPopup")
        // TODO: Add ".treasureMapPopup" existence check or not?
        // Caught error is probably from render()
        // hg.showMap() will trigger it
      ) {
        try {
          const map = JSON.parse(this.responseText).treasure_map;
          if (map) {
            const obj = {};
            const condensed = {};

            const condensedGroups = [];
            map.groups.forEach(el => {
              // TODO: Compress goals array for individual mice/items in the future
              const innerObj = {};
              innerObj.name = el.name;
              innerObj.profile_pic = el.profile_pic;
              innerObj.snuid = el.snuid;
              condensedGroups.push(innerObj);
            });
            condensed.groups = condensedGroups;

            condensed.hunters = map.hunters;
            condensed.invited_hunters = map.invited_hunters;
            condensed.is_complete = map.is_complete;
            condensed.is_owner = map.is_owner;
            condensed.is_scavenger_hunt = map.is_scavenger_hunt;
            condensed.is_wanted_poster = map.is_wanted_poster;
            condensed.map_class = map.map_class;
            condensed.map_id = map.map_id;
            condensed.timestamp = Date.now();
            obj[map.name] = condensed;
            // console.log(obj);

            const mapCacheRaw = localStorage.getItem("tsitu-mapping-cache");
            if (mapCacheRaw) {
              const mapCache = JSON.parse(mapCacheRaw);
              mapCache[map.name] = condensed;
              localStorage.setItem(
                "tsitu-mapping-cache",
                JSON.stringify(mapCache)
              );
            } else {
              localStorage.setItem("tsitu-mapping-cache", JSON.stringify(obj));
            }

            render();
          }
        } catch (error) {
          console.log("Server response doesn't contain a valid treasure map");
          console.error(error.stack);
        }
      }
    });
    originalOpen.apply(this, arguments);
  };

  // Renders custom UI elements onto the DOM
  function render() {
    // Clear out existing custom elements
    // Uses static collection instead of live one from getElementsByClassName
    document.querySelectorAll(".tsitu-mapping").forEach(el => el.remove());
    document.querySelectorAll(".tsitu-queso-mapper").forEach(el => el.remove());

    /**
     * Refresh button
     * Iterate thru QRH.maps array for element matching current map and set its hash to empty string
     * This forces a hard refresh via hasCachedMap, which is called in show/showMap
     */
    const refreshSpan = document.createElement("span");
    refreshSpan.className = "tsitu-mapping tsitu-refresh-span";
    const refreshButton = document.createElement("button");
    refreshButton.innerText = "Refresh";
    refreshButton.className = "treasureMapPopup-action-button tsitu-mapping";
    refreshButton.style.cursor = "pointer";
    refreshButton.style.fontSize = "9px";
    refreshButton.style.padding = "2px";
    refreshButton.style.margin = "3px 5px 0px 0px";
    refreshButton.style.textShadow = "none";
    refreshButton.style.display = "inline-block";
    refreshButton.addEventListener("click", function() {
      const mapName = document.querySelector(
        ".treasureMapPopup-header-title.mapName"
      ).textContent;

      user.quests.QuestRelicHunter.maps.forEach(el => {
        if (el.name === mapName) {
          // Reset hash to bust cache
          el.hash = "";
        }
      });

      // Close map dialog and re-open either with current map, default, or overview
      const mapIdEl = document.querySelector("[data-map-id].active");
      const mapId = mapIdEl ? mapIdEl.getAttribute("data-map-id") : -1;
      document.getElementById("jsDialogClose").click();
      mapId === -1
        ? hg.views.TreasureMapView.show()
        : hg.views.TreasureMapView.show(mapId);
    });

    refreshSpan.appendChild(refreshButton);
    document
      .querySelector(
        ".treasureMapPopup-state.viewMap .treasureMapPopup-header-subtitle"
      )
      .insertAdjacentElement("afterend", refreshSpan);

    // Utility handler that opens supply transfer page and selects SB+
    function transferSB(snuid) {
      const newWindow = window.open(
        `https://www.mousehuntgame.com/supplytransfer.php?fid=${snuid}`
      );
      newWindow.addEventListener("load", function() {
        if (newWindow.supplyTransfer1) {
          newWindow.supplyTransfer1.setSelectedItemType("super_brie_cheese");
          newWindow.supplyTransfer1.renderTabMenu();
          newWindow.supplyTransfer1.render();
        }
      });
      return false;
    }

    // Corkboard image click handling
    document.querySelectorAll("[data-message-id]").forEach(msg => {
      const snuid = msg
        .querySelector(".messageBoardView-message-name")
        .href.split("snuid=")[1];
      const img = msg.querySelector(".messageBoardView-message-image");
      img.href = "#";
      img.onclick = function() {
        transferSB(snuid);
      };
    });

    // Hunter container image click handling
    document
      .querySelectorAll(".treasureMapPopup-hunter:not(.empty)")
      .forEach(el => {
        const img = el.querySelector(".treasureMapPopup-hunter-image");
        const snuid = el.getAttribute("data-snuid");
        img.style.cursor = "pointer";
        img.onclick = function() {
          transferSB(snuid);
        };
      });

    // Check for valid Queso Canyon map name
    const mapNameSelector = document.querySelector(
      ".treasureMapPopup-header-title.mapName"
    );
    if (mapNameSelector) {
      const split = mapNameSelector.textContent.split("Rare ");
      const mapName = split.length === 2 ? split[1] : split[0];

      if (quesoMaps.indexOf(mapName) >= 0) {
        // Queso Mapper toggling
        const quesoToggle = document.createElement("input");
        quesoToggle.type = "checkbox";
        quesoToggle.className = "tsitu-mapping";
        quesoToggle.name = "tsitu-queso-toggle";
        quesoToggle.addEventListener("click", function() {
          localStorage.setItem("tsitu-queso-toggle", quesoToggle.checked);
          render();
        });

        const quesoToggleLabel = document.createElement("label");
        quesoToggleLabel.className = "tsitu-mapping";
        quesoToggleLabel.htmlFor = "tsitu-queso-toggle";
        quesoToggleLabel.innerText = "Toggle Queso Mapper functionality";

        const qtChecked = localStorage.getItem("tsitu-queso-toggle") || "false";
        quesoToggle.checked = qtChecked === "true";
        if (quesoToggle.checked) {
          quesoRender();
        }

        const quesoToggleDiv = document.createElement("div");
        quesoToggleDiv.className = "tsitu-queso-mapper";
        if (!quesoToggle.checked) quesoToggleDiv.style.marginBottom = "10px";
        quesoToggleDiv.appendChild(quesoToggle);
        quesoToggleDiv.appendChild(quesoToggleLabel);

        document
          .querySelector(".treasureMapPopup-hunterContainer")
          .insertAdjacentElement("afterend", quesoToggleDiv);
      }
    }

    // Features that require cache checking
    const cacheRaw = localStorage.getItem("tsitu-mapping-cache");
    if (cacheRaw) {
      const cache = JSON.parse(cacheRaw);
      const mapName = document.querySelector(
        ".treasureMapPopup-header-title.mapName"
      ).textContent;

      if (cache[mapName] !== undefined) {
        // Must specify <a> because favorite button <div> also matches the selector
        const mapIdEl = document.querySelector("a[data-map-id].active");
        if (mapIdEl) {
          // Abstract equality comparison because map ID can be number or string
          const mapId = mapIdEl.getAttribute("data-map-id");
          if (mapId == cache[mapName].map_id) {
            // "Last refreshed" timestamp
            const refreshSpan = document.querySelector(".tsitu-refresh-span");
            if (refreshSpan && cache[mapName].timestamp) {
              const timeSpan = document.createElement("span");
              timeSpan.innerText = `(This map was last refreshed on: ${new Date(
                parseInt(cache[mapName].timestamp)
              ).toLocaleString()})`;
              refreshSpan.appendChild(timeSpan);
            }

            // Invite via Hunter ID (only for map captains)
            if (cache[mapName].is_owner) {
              const inputLabel = document.createElement("label");
              inputLabel.innerText = "Hunter ID: ";
              inputLabel.htmlFor = "tsitu-mapping-id-input";

              const inputField = document.createElement("input");
              inputField.setAttribute("type", "number");
              inputField.setAttribute("name", "tsitu-mapping-id-input");
              inputField.setAttribute("data-lpignore", "true"); // Get rid of LastPass Autofill
              inputField.setAttribute("min", 1);
              inputField.setAttribute("max", 9999999);
              inputField.setAttribute("placeholder", "e.g. 1234567");
              inputField.setAttribute("required", true);
              inputField.addEventListener("keyup", function(e) {
                if (e.keyCode === 13) {
                  inviteButton.click(); // 'Enter' pressed
                }
              });

              const overrideStyle =
                "input[name='tsitu-mapping-id-input'] { -webkit-appearance:textfield; -moz-appearance:textfield; appearance:textfield; } input[name='tsitu-mapping-id-input']::-webkit-outer-spin-button, input[name='tsitu-mapping-id-input']::-webkit-inner-spin-button { display:none; -webkit-appearance:none; margin:0; }";
              let stylePresent = false;
              document.querySelectorAll("style").forEach(style => {
                if (style.textContent === overrideStyle) {
                  stylePresent = true;
                }
              });
              if (!stylePresent) {
                const spinOverride = document.createElement("style");
                spinOverride.innerHTML = overrideStyle;
                document.body.appendChild(spinOverride);
              }

              const inviteButton = document.createElement("button");
              inviteButton.innerText = "Invite";
              inviteButton.addEventListener("click", function() {
                const rawText = inputField.value;
                if (rawText.length > 0) {
                  const hunterId = parseInt(rawText);
                  if (typeof hunterId === "number" && !isNaN(hunterId)) {
                    if (hunterId > 0 && hunterId < 9999999) {
                      postReq(
                        "https://www.mousehuntgame.com/managers/ajax/pages/friends.php",
                        `sn=Hitgrab&hg_is_ajax=1&action=community_search_by_id&user_id=${hunterId}&uh=${
                          user.unique_hash
                        }`
                      ).then(res => {
                        let response = null;
                        try {
                          if (res) {
                            response = JSON.parse(res.responseText);
                            // console.log(response);
                            const data = response.friend;
                            if (data.has_invitable_map) {
                              if (
                                confirm(
                                  `Are you sure you'd like to invite this hunter?\n\nName: ${
                                    data.name
                                  }\nTitle: ${data.title_name} (${
                                    data.title_percent
                                  }%)\nLocation: ${
                                    data.environment_name
                                  }\nLast Active: ${
                                    data.last_active_formatted
                                  } ago`
                                )
                              ) {
                                postReq(
                                  "https://www.mousehuntgame.com/managers/ajax/users/relichunter.php",
                                  `sn=Hitgrab&hg_is_ajax=1&action=send_invites&map_id=${mapId}&snuids%5B%5D=${
                                    data.snuid
                                  }&uh=${user.unique_hash}`
                                ).then(res2 => {
                                  let inviteRes = null;
                                  try {
                                    if (res2) {
                                      inviteRes = JSON.parse(res2.responseText);
                                      if (inviteRes.success === 1) {
                                        refreshButton.click();
                                      } else {
                                        alert(
                                          "Map invite unsuccessful - may be because map is full"
                                        );
                                      }
                                    }
                                  } catch (error2) {
                                    alert("Error while inviting hunter to map");
                                    console.error(error2.stack);
                                  }
                                });
                              }
                            } else {
                              if (data.name) {
                                alert(
                                  `${
                                    data.name
                                  } cannot to be invited to a map at this time`
                                );
                              } else {
                                alert("Invalid hunter information");
                              }
                            }
                          }
                        } catch (error) {
                          alert("Error while requesting hunter information");
                          console.error(error.stack);
                        }
                      });
                    }
                  }
                }
              });

              // Invited hunters aka pending invites
              const invitedArr = cache[mapName].invited_hunters;
              const invitedSpan = document.createElement("span");
              invitedSpan.style.marginLeft = "5px";
              invitedSpan.innerText =
                invitedArr.length > 0
                  ? "Pending Invites:"
                  : "Pending Invites: None";

              if (invitedArr.length > 0) {
                let count = 1;
                invitedArr.forEach(snuid => {
                  const link = document.createElement("a");
                  link.innerText = `[${count}]`;
                  link.href = `https://www.mousehuntgame.com/profile.php?snuid=${snuid}`;
                  link.target = "_blank";
                  invitedSpan.appendChild(document.createTextNode("\u00A0"));
                  invitedSpan.appendChild(link);

                  // Prevent text from running past width of dialog (fails when >999)
                  if (count < 100) {
                    if (count === 20) {
                      invitedSpan.appendChild(document.createElement("br"));
                    } else if ((count - 20) % 30 === 0) {
                      invitedSpan.appendChild(document.createElement("br"));
                    }
                  } else {
                    if (count === 108) {
                      invitedSpan.appendChild(document.createElement("br"));
                    } else if ((count - 108) % 24 === 0) {
                      invitedSpan.appendChild(document.createElement("br"));
                    }
                  }
                  count += 1;
                });

                // -- Text debugging --
                // for (let i = 0; i < 1337; i++) {
                // for (let i = 0; i < 80; i++) {
                //   const link = document.createElement("a");
                //   link.innerText = `[${i + 1}]`;
                //   link.href = `#`;
                //   invitedSpan.appendChild(document.createTextNode("\u00A0"));
                //   invitedSpan.appendChild(link);
                //   // Prevent text from running past width of dialog (formatting breaks past 1k but meh)
                //   if (i + 1 < 100) {
                //     if (i + 1 === 20) {
                //       invitedSpan.appendChild(document.createElement("br"));
                //     } else if ((i + 1 - 20) % 30 === 0) {
                //       invitedSpan.appendChild(document.createElement("br"));
                //     }
                //   } else {
                //     if (i + 1 === 108) {
                //       invitedSpan.appendChild(document.createElement("br"));
                //     } else if ((i + 1 - 108) % 24 === 0) {
                //       invitedSpan.appendChild(document.createElement("br"));
                //     }
                //   }
                // }
              }

              const span = document.createElement("span");
              span.className = "tsitu-mapping";
              span.style.display = "inline-block";
              span.style.marginBottom = "10px";
              span.appendChild(inputLabel);
              span.appendChild(inputField);
              span.appendChild(inviteButton);
              span.appendChild(invitedSpan);

              document
                .querySelector(".treasureMapPopup-hunterContainer")
                .insertAdjacentElement("afterend", span);
            }
          }
        }

        // "x caught these mice" image click handling
        const groups = cache[mapName].groups;
        const format = {};

        groups.forEach(el => {
          if (el.profile_pic !== null) {
            format[el.profile_pic] = [el.name, el.snuid];
          }
        });

        document
          .querySelectorAll(".treasureMapPopup-goals-group-header")
          .forEach(group => {
            const text = group.textContent.split(":(")[0] + ":";
            if (text !== "Uncaught mice in other locations:") {
              const img = group.querySelector(
                ".treasureMapPopup-goals-group-header-image"
              );
              if (img) {
                const pic = img.style.backgroundImage
                  .split('url("')[1]
                  .split('")')[0];
                if (format[pic] !== undefined) {
                  if (format[pic][0] === text) {
                    img.style.cursor = "pointer";
                    img.onclick = function() {
                      const snuid = format[pic][1];
                      transferSB(snuid);
                    };
                  }
                }
              }
            }
          });
      }
    }
  }

  // POST to specified endpoint URL with desired form data
  function postReq(url, form) {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open("POST", url, true);
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      xhr.onreadystatechange = function() {
        if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
          resolve(this);
        }
      };
      xhr.onerror = function() {
        reject(this);
      };
      xhr.send(form);
    });
  }

  // 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() {
      // Callback

      // Render if treasure map popup is available
      const mapTab = observerTarget.querySelector("[data-tab=map_mice]");
      const groupLen = document.querySelectorAll(
        ".treasureMapPopup-goals-groups"
      ).length;

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

      if (
        mapTab &&
        mapTab.className.indexOf("active") >= 0 &&
        groupLen > 0 &&
        !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
    });
  }

  // Queso Mapper functionality
  function quesoRender() {
    const mapMice = document.querySelectorAll(
      "div.treasureMapPopup-goals-group-goal.treasureMapPopup-searchIndex.mouse"
    );
    if (mapMice.length > 0) {
      // Generate DOM elements
      const displayDiv = document.createElement("div");
      displayDiv.className = "tsitu-queso-mapper";
      displayDiv.style.fontSize = "14px";
      displayDiv.style.marginBottom = "10px";
      displayDiv.innerText = "Preferred Location & Cheese -> ";

      const cacheSel = localStorage.getItem("tsitu-queso-mapper-sel");
      if (cacheSel) {
        const cache = JSON.parse(cacheSel);
        for (let location in quesoData) {
          const locSel = `${classBuilder(location, "loc")}`;
          if (cache[locSel] !== undefined) {
            const locationSpan = document.createElement("span");
            locationSpan.innerText = `${location}: `;
            let cheeseCount = 0;
            for (let cheese in quesoData[location]) {
              const cheeseSel = `${classBuilder(location, cheese)}`;
              if (cache[locSel].indexOf(cheeseSel) >= 0) {
                const cheeseSpan = document.createElement("span");
                let prependStr = "";
                if (cheeseCount > 0) prependStr = ", ";

                const imgSpan = document.createElement("span");
                imgSpan.setAttribute(
                  "style",
                  `background-image:url('${
                    quesoImg[cheese]
                  }');width:20px;height:20px;display:inline-block;background-size:contain;background-repeat:no-repeat;position:relative;top:4px;`
                );

                let appendStr = "";
                if (cheese !== "Standard" && cheese !== "SB+") {
                  appendStr += " Queso";
                }

                cheeseSpan.innerText = `${prependStr + cheese + appendStr}`;
                locationSpan.append(cheeseSpan);
                locationSpan.append(document.createTextNode("\u00A0"));
                locationSpan.append(imgSpan);
                cheeseCount += 1;
              }
            }
            displayDiv.appendChild(locationSpan);
          }
        }
      } else {
        displayDiv.style.marginTop = "5px";
        displayDiv.innerText = "Preferred Location & Cheese -> N/A";
      }

      const target = document.querySelector(
        ".treasureMapPopup-map-stateContainer.viewGoals"
      );
      if (target) target.insertAdjacentElement("beforebegin", displayDiv);

      mapMice.forEach(el => {
        if (el.className.indexOf("tsitu-queso-mapper-mouse") < 0) {
          function listener() {
            const span = el.querySelector("span");
            if (span) {
              const mouse = span.textContent;
              const mouseData = quesoMice[mouse];
              if (mouseData) {
                const toCache = {};
                for (let arr of mouseData) {
                  const locSel = classBuilder(arr[0], "loc");
                  const cheeseSel = classBuilder(arr[0], arr[1]);
                  if (toCache[locSel] === undefined) {
                    toCache[locSel] = [cheeseSel];
                  } else {
                    toCache[locSel].push(cheeseSel);
                  }
                  localStorage.setItem(
                    "tsitu-queso-mapper-sel",
                    JSON.stringify(toCache)
                  );
                  render();
                }
              }
            }
          }

          el.addEventListener("mouseover", function() {
            listener();
          });

          el.addEventListener("click", function() {
            listener();
          });
        }
        el.classList.add("tsitu-queso-mapper-mouse");
      });
    }

    function classBuilder(location, cheese) {
      let retVal = "";

      switch (location) {
        case "Queso River":
          retVal += "river-";
          break;
        case "Prickly Plains":
          retVal += "plains-";
          break;
        case "Cantera Quarry":
          retVal += "quarry-";
          break;
        case "Cork Collecting":
          retVal += "cork-";
          break;
        case "Pressure Building":
          retVal += "pressure-";
          break;
        case "Small Eruption":
          retVal += "small-";
          break;
        case "Medium Eruption":
          retVal += "medium-";
          break;
        case "Large Eruption":
          retVal += "large-";
          break;
        case "Epic Eruption":
          retVal += "epic-";
          break;
        default:
          retVal += location;
      }

      switch (cheese) {
        case "Standard":
          retVal += "standard";
          break;
        case "SB+":
          retVal += "super";
          break;
        case "Bland":
          retVal += "bland";
          break;
        case "Mild":
          retVal += "mild";
          break;
        case "Medium":
          retVal += "medium";
          break;
        case "Hot":
          retVal += "hot";
          break;
        case "Flamin'":
          retVal += "flamin";
          break;
        case "Wildfire":
          retVal += "wildfire";
          break;
        default:
          retVal += cheese;
      }

      return retVal;
    }
  }
})();