Flatmmo Keybinds Improved

remappable keybindings for flatmmo

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Flatmmo Keybinds Improved
// @namespace    Joshu FlatMMO Scripts
// @description  remappable keybindings for flatmmo
// @license      MIT 
// @match        https://flatmmo.com/play.php*
// @version      1773961479750
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM.setValue
// @grant        GM.registerMenuCommand
// ==/UserScript==


// packages/userscripts/keybinds-improved/ACTIONS.ts
var ACTIONS = {
  run: {
    originalKey: "F1",
    description: "Run",
    socketCommand: "SHORTCUT_KEY=F1"
  },
  eat: {
    originalKey: "F2",
    description: "Consumes a piece of food",
    socketCommand: "SHORTCUT_KEY=F2"
  },
  lightFire: {
    originalKey: "F3",
    description: "Lights a fire",
    socketCommand: "SHORTCUT_KEY=F3"
  },
  equip1: {
    originalKey: "F6",
    description: "Equipment Auto equips items that you've configured",
    socketCommand: "SHORTCUT_KEY=F6"
  },
  equip2: {
    originalKey: "F7",
    description: "Equipment - Auto equips items that you've configured",
    socketCommand: "SHORTCUT_KEY=F7"
  },
  equip3: {
    originalKey: "F8",
    description: "Equipment - Auto equips items that you've configured",
    socketCommand: "SHORTCUT_KEY=F8"
  },
  badge1: {
    originalKey: "F9",
    description: "Badge - Right click a badge and click the 'set key binding'",
    socketCommand: "SHORTCUT_KEY=F9"
  },
  badge2: {
    originalKey: "F10",
    description: "Badge - Right click a badge and click the 'set key binding'",
    socketCommand: "SHORTCUT_KEY=F10"
  },
  badge3: {
    originalKey: "F11",
    description: "Badge - Right click a badge and click the 'set key binding'",
    socketCommand: "SHORTCUT_KEY=F11"
  },
  badge4: {
    originalKey: "F12",
    description: "Badge - Right click a badge and click the 'set key binding'",
    socketCommand: "SHORTCUT_KEY=F12"
  },
  teleport_everbrook: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_everbrook"
  },
  remote_sell: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=remote_sell"
  },
  dig: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=dig"
  },
  teleport_mysticvale: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_mysticvale"
  },
  timers: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=timers"
  },
  teleport_omboko: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_omboko"
  },
  teleport_dock_haven: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_dock_haven"
  },
  auto_hell_burying: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=auto_hell_burying"
  },
  teleport_jafa_outpost: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_jafa_outpost"
  },
  teleport_frostvale: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=teleport_frostvale"
  },
  hunting_contact: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=hunting_contact"
  },
  mass_pickup: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=mass_pickup"
  },
  focus: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=focus"
  },
  clarity: {
    originalKey: "N/A",
    description: "Worship skill, originally no hotkey",
    socketCommand: "USE_WORSHIP=clarity"
  }
};

// packages/userscripts/keybinds-improved/DEFAULT_HOTKEYS.ts
var DEFAULT_HOTKEYS = {
  run: {
    key: "r",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  eat: {
    key: "f",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  lightFire: {
    key: "4",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  equip1: {
    key: "1",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  equip2: {
    key: "2",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  equip3: {
    key: "3",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  badge1: {
    key: "a",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  badge2: {
    key: "s",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  badge3: {
    key: "d",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  badge4: {
    key: "v",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  teleport_everbrook: {
    key: "e",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  remote_sell: {
    key: "s",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  dig: {
    key: "l",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  teleport_mysticvale: {
    key: "m",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  timers: {
    key: "0",
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false
  },
  teleport_omboko: {
    key: "o",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  teleport_dock_haven: {
    key: "d",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  auto_hell_burying: {
    key: "b",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  teleport_jafa_outpost: {
    key: "j",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  teleport_frostvale: {
    key: "f",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  hunting_contact: {
    key: "h",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  mass_pickup: {
    key: "p",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  focus: {
    key: "k",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  },
  clarity: {
    key: "c",
    altKey: false,
    ctrlKey: true,
    metaKey: false,
    shiftKey: false
  }
};

// packages/userscripts/keybinds-improved/hotkeys.ts
var keypressToHashableString = (keypress) => {
  return `${keypress.key}-${keypress.altKey}-${keypress.ctrlKey}-${keypress.metaKey}-${keypress.shiftKey}`.toLowerCase();
};
if (GM_getValue("hotkeys", null) === null) {
  GM_setValue("hotkeys", {});
}
var usersHotkeys = GM_getValue("hotkeys", {});
var mergeHotkeys = () => {
  return { ...DEFAULT_HOTKEYS, ...usersHotkeys };
};
var hashKeymap = () => {
  return Object.entries(mergeHotkeys()).reduce((result, [action, kp]) => {
    const hashed = keypressToHashableString(kp);
    result[hashed] = { action, hotkey: kp };
    return result;
  }, {});
};
var hashedHotkeyMap = hashKeymap();
var setHotkeys = (updatedHotkeys) => {
  usersHotkeys = { ...usersHotkeys, ...updatedHotkeys };
  GM.setValue("hotkeys", usersHotkeys);
  hashedHotkeyMap = hashKeymap();
};

// packages/userscripts/keybinds-improved/settings.ts
var formatKeypress = (kp) => {
  const parts = [];
  if (kp.ctrlKey)
    parts.push("Ctrl");
  if (kp.altKey)
    parts.push("Alt");
  if (kp.shiftKey)
    parts.push("Shift");
  if (kp.metaKey)
    parts.push("Meta");
  parts.push(kp.key.toUpperCase());
  return parts.join(" + ");
};
var formatKeypressFromEvent = (e) => {
  const parts = [];
  if (e.ctrlKey)
    parts.push("Ctrl");
  if (e.altKey)
    parts.push("Alt");
  if (e.shiftKey)
    parts.push("Shift");
  if (e.metaKey)
    parts.push("Meta");
  parts.push(e.key.toUpperCase());
  return parts.join(" + ");
};
var formatActionName = (action) => {
  return action.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
};
var CATEGORIES = {
  "Basic Actions": ["run", "eat", "lightFire"],
  Equipment: ["equip1", "equip2", "equip3"],
  Badges: ["badge1", "badge2", "badge3", "badge4"],
  Teleports: [
    "teleport_everbrook",
    "teleport_mysticvale",
    "teleport_omboko",
    "teleport_dock_haven",
    "teleport_jafa_outpost",
    "teleport_frostvale"
  ],
  "Worship Skills": [
    "remote_sell",
    "dig",
    "timers",
    "auto_hell_burying",
    "hunting_contact",
    "mass_pickup",
    "focus",
    "clarity"
  ]
};
var createModalStyles = () => {
  const style = document.createElement("style");
  style.textContent = `
		#hotkeys-modal-overlay {
			position: fixed;
			top: 0;
			left: 0;
			width: 100vw;
			height: 100vh;
			background: rgba(0, 0, 0, 0.7);
			display: none;
			justify-content: center;
			z-index: 10000;
			padding-top: 5vh;
		}

		#hotkeys-modal-overlay.visible {
			display: flex;
		}

		#hotkeys-modal {
			background: #1a1a2e;
			border: 2px solid #4a4a6a;
			border-radius: 8px;
			max-width: 1000px;
			max-height: 80vh;
			width: 90%;
			overflow: hidden;
			box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
			height: fit-content;
			padding-bottom: 20px;
		}

		#hotkeys-modal-header {
			display: flex;
			justify-content: space-between;
			align-items: center;
			padding: 12px 16px;
			background: #252540;
			border-bottom: 1px solid #4a4a6a;
		}

		#hotkeys-modal-header h2 {
			margin: 0;
			color: #e0e0e0;
			font-size: 18px;
		}

		#hotkeys-modal-close {
			background: none;
			border: none;
			color: #888;
			font-size: 24px;
			cursor: pointer;
			padding: 0;
			line-height: 1;
		}

		#hotkeys-modal-close:hover {
			color: #fff;
		}

		#hotkeys-modal-content {
			overflow-y: auto;
			max-height: calc(80vh - 60px);
			padding: 16px;
		}

		.hotkey-category {
			font-size: 16px;
			font-weight: 600;
			color: #e0e0e0;
			padding: 12px 0 8px 0;
			border-bottom: 2px solid #4a4a6a;
			margin-bottom: 8px;
		}

		#hotkeys-grid {
			display: flex;
			flex-direction: column;
			gap: 16px;
			color: #e0e0e0;
		}

		.category-section {
			display: contents;
		}

		.category-section.full-width {
			display: flex;
			flex-direction: column;
			gap: 8px;
		}

		.small-categories-row {
			display: grid;
			grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
			gap: 16px;
		}

		.small-category-container {
			display: flex;
			flex-direction: column;
			gap: 8px;
		}

		.full-width .items-grid {
			display: grid;
			grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
			gap: 8px;
		}

		.small-category-container .items-grid {
			display: flex;
			flex-direction: column;
			gap: 8px;
		}

		.hotkey-item {
			display: grid;
			grid-template-columns: 1fr auto;
			align-items: center;
			gap: 12px;
			padding: 8px 12px;
			border: 1px solid #333;
			border-radius: 4px;
			transition: background 0.15s ease;
		}

		.hotkey-item:hover {
			background: #252540;
		}

		.hotkey-key {
			display: inline-block;
			background: #333;
			border: 1px solid #555;
			border-radius: 4px;
			padding: 4px 12px;
			font-family: monospace;
			font-size: 14px;
			cursor: pointer;
			transition: all 0.15s ease;
		}

		.hotkey-key:hover {
			background: #444;
			border-color: #777;
		}

		.hotkey-key.recording {
			background: #4a3a2e;
			border-color: #f0a050;
		}

		.hotkey-key.conflict {
			background: #4a2a2a;
			border-color: #c55;
		}
	`;
  return style;
};
var currentlyRecording = null;
var recordingAction = null;
var stopRecording = (restoreText = false) => {
  if (currentlyRecording) {
    if (restoreText && recordingAction) {
      const currentHotkeys = mergeHotkeys();
      currentlyRecording.textContent = formatKeypress(currentHotkeys[recordingAction]);
    }
    currentlyRecording.classList.remove("recording");
    currentlyRecording = null;
    recordingAction = null;
  }
};
var allSpans = [];
var updateConflicts = () => {
  const currentHotkeys = mergeHotkeys();
  const hashCounts = Object.values(currentHotkeys).map(keypressToHashableString).reduce((acc, hash) => {
    acc[hash] = (acc[hash] || 0) + 1;
    return acc;
  }, {});
  for (const span of allSpans) {
    const action = span.dataset.action;
    const kp = currentHotkeys[action];
    const hash = keypressToHashableString(kp);
    const count = hashCounts[hash];
    if (count > 1) {
      span.classList.add("conflict");
    } else {
      span.classList.remove("conflict");
    }
  }
};
var startRecording = (element, action) => {
  if (currentlyRecording === element) {
    stopRecording(true);
    return;
  }
  if (currentlyRecording) {
    stopRecording(true);
  }
  currentlyRecording = element;
  recordingAction = action;
  element.classList.add("recording");
  element.textContent = "Press a key...";
};
var getModifierText = (e) => {
  const parts = [];
  if (e.ctrlKey)
    parts.push("Ctrl");
  if (e.altKey)
    parts.push("Alt");
  if (e.shiftKey)
    parts.push("Shift");
  if (e.metaKey)
    parts.push("Meta");
  return parts.length > 0 ? `${parts.join(" + ")} + ...` : "Press a key...";
};
var updateRecordingDisplay = (e) => {
  if (!currentlyRecording)
    return;
  currentlyRecording.textContent = getModifierText(e);
};
var handleRecordingKeypress = (e) => {
  if (!currentlyRecording || !recordingAction)
    return;
  if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) {
    updateRecordingDisplay(e);
    return;
  }
  e.preventDefault();
  e.stopPropagation();
  const newKeypress = {
    key: e.key,
    altKey: e.altKey,
    ctrlKey: e.ctrlKey,
    metaKey: e.metaKey,
    shiftKey: e.shiftKey
  };
  setHotkeys({ [recordingAction]: newKeypress });
  currentlyRecording.textContent = formatKeypressFromEvent(e);
  currentlyRecording.classList.remove("recording");
  updateConflicts();
  stopRecording();
};
var handleRecordingKeyup = (e) => {
  if (!currentlyRecording)
    return;
  if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) {
    updateRecordingDisplay(e);
  }
};
var createModal = () => {
  const overlay = document.createElement("div");
  overlay.id = "hotkeys-modal-overlay";
  const modal = document.createElement("div");
  modal.id = "hotkeys-modal";
  const header = document.createElement("div");
  header.id = "hotkeys-modal-header";
  const title = document.createElement("h2");
  title.textContent = "Hotkey Bindings";
  const closeBtn = document.createElement("button");
  closeBtn.id = "hotkeys-modal-close";
  closeBtn.textContent = "×";
  closeBtn.addEventListener("click", () => hideModal());
  header.appendChild(title);
  header.appendChild(closeBtn);
  const content = document.createElement("div");
  content.id = "hotkeys-modal-content";
  const grid = document.createElement("div");
  grid.id = "hotkeys-grid";
  const hotkeys = mergeHotkeys();
  const smallCategories = [];
  const largeCategories = [];
  for (const [categoryName, categoryActions] of Object.entries(CATEGORIES)) {
    if (categoryActions.length <= 4) {
      smallCategories.push([categoryName, categoryActions]);
    } else {
      largeCategories.push([categoryName, categoryActions]);
    }
  }
  const createCategoryItems = (categoryActions) => {
    const items = [];
    for (const action of categoryActions) {
      const kp = hotkeys[action];
      const actionInfo = ACTIONS[action];
      if (!actionInfo || !kp)
        continue;
      const item = document.createElement("div");
      item.className = "hotkey-item";
      const actionName = document.createElement("span");
      actionName.className = "hotkey-action";
      actionName.textContent = formatActionName(action);
      const hotkeySpan = document.createElement("span");
      hotkeySpan.className = "hotkey-key";
      hotkeySpan.textContent = formatKeypress(kp);
      hotkeySpan.dataset.action = action;
      allSpans.push(hotkeySpan);
      hotkeySpan.addEventListener("click", () => {
        startRecording(hotkeySpan, action);
      });
      item.appendChild(actionName);
      item.appendChild(hotkeySpan);
      items.push(item);
    }
    return items;
  };
  if (smallCategories.length > 0) {
    const smallCategoriesRow = document.createElement("div");
    smallCategoriesRow.className = "small-categories-row";
    for (const [categoryName, categoryActions] of smallCategories) {
      const container = document.createElement("div");
      container.className = "small-category-container";
      const categoryHeader = document.createElement("div");
      categoryHeader.className = "hotkey-category";
      categoryHeader.textContent = categoryName;
      container.appendChild(categoryHeader);
      const itemsGrid = document.createElement("div");
      itemsGrid.className = "items-grid";
      const items = createCategoryItems(categoryActions);
      items.forEach((item) => itemsGrid.appendChild(item));
      container.appendChild(itemsGrid);
      smallCategoriesRow.appendChild(container);
    }
    grid.appendChild(smallCategoriesRow);
  }
  for (const [categoryName, categoryActions] of largeCategories) {
    const section = document.createElement("div");
    section.className = "category-section full-width";
    const categoryHeader = document.createElement("div");
    categoryHeader.className = "hotkey-category";
    categoryHeader.textContent = categoryName;
    section.appendChild(categoryHeader);
    const itemsGrid = document.createElement("div");
    itemsGrid.className = "items-grid";
    const items = createCategoryItems(categoryActions);
    items.forEach((item) => itemsGrid.appendChild(item));
    section.appendChild(itemsGrid);
    grid.appendChild(section);
  }
  updateConflicts();
  content.appendChild(grid);
  modal.appendChild(header);
  modal.appendChild(content);
  overlay.appendChild(modal);
  overlay.addEventListener("click", (e) => {
    if (e.target === overlay) {
      hideModal();
    }
  });
  modal.addEventListener("click", (e) => {
    const target = e.target;
    if (!target.classList.contains("hotkey-key") && currentlyRecording) {
      stopRecording(true);
    }
  });
  return overlay;
};
var modalElement = null;
var initModal = () => {
  if (modalElement)
    return;
  document.head.appendChild(createModalStyles());
  modalElement = createModal();
  document.body.appendChild(modalElement);
};
var showModal = () => {
  initModal();
  modalElement?.classList.add("visible");
};
var hideModal = () => {
  modalElement?.classList.remove("visible");
};
var toggleModal = () => {
  initModal();
  if (modalElement?.classList.contains("visible")) {
    hideModal();
  } else {
    showModal();
  }
};
document.addEventListener("keydown", (e) => {
  if (!modalElement?.classList.contains("visible"))
    return;
  if (currentlyRecording) {
    if (e.key === "Escape") {
      stopRecording(true);
      e.preventDefault();
      return;
    }
    handleRecordingKeypress(e);
    return;
  }
  if (e.key === "Escape") {
    hideModal();
    e.preventDefault();
  }
});
document.addEventListener("keyup", (e) => {
  if (!modalElement?.classList.contains("visible"))
    return;
  handleRecordingKeyup(e);
});

// packages/userscripts/keybinds-improved/index.ts
var focusOrSendChat = () => {
  const value = chat_ele.value.trim();
  if (document.activeElement !== chat_ele) {
    request_focus_chatbox();
    return;
  }
  if (value !== "") {
    Globals.websocket.send(`CHAT=${value}`);
    chat_ele.value = "";
  }
  request_unfocus_chatbox();
};
var handleNpcChatModal = (e) => {
  const keyCode = e.keyCode;
  if (has_npc_chat_message_modal_open()) {
    if (keyCode === 32) {
      document.getElementById("npc-chat-message-modal-continue-btn")?.click();
      e.preventDefault();
    }
    return;
  }
  if (has_npc_chat_options_modal_open()) {
    switch (keyCode) {
      case 49:
        {
          const wrapper = document.getElementById("npc-chat-options-modal-content");
          const options = wrapper?.getElementsByTagName("div");
          if (options && options[0].style.display !== "none") {
            options[0].click();
          }
        }
        break;
      case 50:
        {
          const wrapper = document.getElementById("npc-chat-options-modal-content");
          const options = wrapper?.getElementsByTagName("div");
          if (options && options[1].style.display !== "none") {
            options[1].click();
          }
        }
        break;
      case 51:
        {
          const wrapper = document.getElementById("npc-chat-options-modal-content");
          const options = wrapper?.getElementsByTagName("div");
          if (options && options[2].style.display !== "none") {
            options[2].click();
          }
        }
        break;
      case 52:
        {
          const wrapper = document.getElementById("npc-chat-options-modal-content");
          const options = wrapper?.getElementsByTagName("div");
          if (options && options[3].style.display !== "none") {
            options[3].click();
          }
        }
        break;
    }
  }
};
var hotkeyListener = (e) => {
  if (e.repeat)
    return;
  if (Globals.local_username == null)
    return;
  if (has_npc_chat_message_modal_open()) {
    handleNpcChatModal(e);
    return;
  }
  if (has_modal_open())
    return;
  if (e.key === "Enter") {
    focusOrSendChat();
    e.preventDefault();
  }
  if (document.activeElement?.id !== "body") {
    return;
  }
  if (e.key === "/") {
    chat_ele.value = "/";
    request_focus_chatbox();
    e.preventDefault();
  }
  const keypressString = keypressToHashableString(e);
  if (keypressString in hashedHotkeyMap) {
    const pressedHotkey = hashedHotkeyMap[keypressString];
    const action = ACTIONS[pressedHotkey.action];
    if (action) {
      Globals.websocket.send(action.socketCommand);
      e.preventDefault();
    }
  }
};
GM.registerMenuCommand("Toggle Settings menu", toggleModal);
window.removeEventListener("keypress", keypress_listener);
window.addEventListener("keydown", hotkeyListener, false);