Flatmmo Keybinds Improved

remappable keybindings for flatmmo

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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);