MZ Tactics Selector

Adds a dropdown menu with overused tactics.

As of 2023-07-04. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         MZ Tactics Selector
// @namespace    essenfc
// @version      6.1
// @description  Adds a dropdown menu with overused tactics.
// @author       Douglas Vieira
// @match        https://www.managerzone.com/?p=tactics
// @match        https://www.managerzone.com/?p=national_teams&sub=tactics&type=*
// @match        https://www.managerzone.com/?p=players
// @icon         https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @require      https://unpkg.com/[email protected]/dist/sha256.js
// @require      https://unpkg.com/[email protected]/i18next.min.js
// @license      MIT
// ==/UserScript==

GM_addStyle(
  "@import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap');"
);

(function () {
  "use strict";

  let dropdownTactics = [];

  const defaultTacticsDataUrl =
    "https://raw.githubusercontent.com/douglasdotv/tactics-selector/main/json/tactics.json?callback=?";

  let activeLanguage;

  const flagsDataUrl = {
    gb: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/gb.svg",
    br: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/br.svg",
    cn: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/cn.svg",
    se: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/se.svg",
    no: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/no.svg",
    dk: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/dk.svg",
    ar: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/ar.svg",
    pl: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/pl.svg",
    nl: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/nl.svg",
    id: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/id.svg",
    de: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/de.svg",
    it: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/it.svg",
    fr: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/fr.svg",
    ro: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/ro.svg",
    tr: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/tr.svg",
    kr: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/kr.svg",
    ru: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/ru.svg",
    sa: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/sa.svg",
  };

  const languages = [
    { code: "en", name: "English", flag: flagsDataUrl.gb },
    { code: "pt", name: "Português", flag: flagsDataUrl.br },
    { code: "zh", name: "中文", flag: flagsDataUrl.cn },
    { code: "sv", name: "Svenska", flag: flagsDataUrl.se },
    { code: "no", name: "Norsk", flag: flagsDataUrl.no },
    { code: "da", name: "Dansk", flag: flagsDataUrl.dk },
    { code: "es", name: "Español", flag: flagsDataUrl.ar },
    { code: "pl", name: "Polski", flag: flagsDataUrl.pl },
    { code: "nl", name: "Nederlands", flag: flagsDataUrl.nl },
    { code: "id", name: "Bahasa Indonesia", flag: flagsDataUrl.id },
    { code: "de", name: "Deutsch", flag: flagsDataUrl.de },
    { code: "it", name: "Italiano", flag: flagsDataUrl.it },
    { code: "fr", name: "Français", flag: flagsDataUrl.fr },
    { code: "ro", name: "Română", flag: flagsDataUrl.ro },
    { code: "tr", name: "Türkçe", flag: flagsDataUrl.tr },
    { code: "ko", name: "한국어", flag: flagsDataUrl.kr },
    { code: "ru", name: "Русский", flag: flagsDataUrl.ru },
    { code: "ar", name: "العربية", flag: flagsDataUrl.sa },
  ];

  const strings = {
    addButton: "",
    deleteButton: "",
    renameButton: "",
    updateButton: "",
    aboutButton: "",
    clearButton: "",
    resetButton: "",
    importButton: "",
    exportButton: "",
    tacticNamePrompt: "",
    addAlert: "",
    deleteAlert: "",
    renameAlert: "",
    updateAlert: "",
    clearAlert: "",
    resetAlert: "",
    deleteConfirmation: "",
    updateConfirmation: "",
    clearConfirmation: "",
    resetConfirmation: "",
    invalidTacticError: "",
    noTacticNameProvidedError: "",
    alreadyExistingTacticNameError: "",
    tacticNameLengthError: "",
    noTacticSelectedError: "",
    duplicateTacticError: "",
    modalContentInfoText: "",
    modalContentFeedbackText: "",
    tacticsDropdownMenuLabel: "",
    languageDropdownMenuLabel: "",
  };

  let infoModal;

  const tacticsBox = document.getElementById("tactics_box");

  const tacticsPreset = document.getElementById("tactics_preset");

  const isFootball = function () {
    return tacticsBox.classList.contains("soccer");
  };

  const outfieldPlayersSelector =
    ".fieldpos.fieldpos-ok.ui-draggable:not(.substitute):not(.goalkeeper):not(.substitute.goalkeeper), .fieldpos.fieldpos-collision.ui-draggable:not(.substitute):not(.goalkeeper):not(.substitute.goalkeeper)";

  const goalkeeperSelector = ".fieldpos.fieldpos-ok.goalkeeper.ui-draggable";

  const formationTextSelector = "#formation_text";

  const tacticSlotSelector =
    ".ui-state-default.ui-corner-top.ui-tabs-selected.ui-state-active.invalid";

  const minOutfieldPlayers = 10;

  const maxTacticNameLength = 50;

  async function initialize() {
    if (tacticsBox) {
      activeLanguage = getActiveLanguage();
      i18next
        .init({
          lng: activeLanguage,
          resources: {
            [activeLanguage]: {
              translation: await (
                await fetch(
                  `https://raw.githubusercontent.com/douglasdotv/tactics-selector/main/json/lang/${activeLanguage}.json?callback=?`
                )
              ).json(),
            },
          },
        })
        .then(() => {
          const tacticsSelectorDiv = createMainDiv();
          const firstRow = createMainDivFirstRow();
          const secondRow = createMainDivSecondRow();

          const tacticsDropdownMenuLabel = createTacticsDropdownMenuLabel();
          const tacticsDropdownMenu = createTacticsDropdownMenu();
          const tacticsDropdownGroup = createLabelDropdownMenuGroup(
            tacticsDropdownMenuLabel,
            tacticsDropdownMenu
          );

          const languageDropdownMenuLabel = createLanguageDropdownMenuLabel();
          const languageDropdownMenu = createLanguageDropdownMenu();
          const languageDropdownGroup = createLabelDropdownMenuGroup(
            languageDropdownMenuLabel,
            languageDropdownMenu
          );
          const flagImage = createFlagImage();
          languageDropdownGroup.appendChild(flagImage);

          appendChildren(firstRow, [
            tacticsDropdownGroup,
            languageDropdownGroup,
          ]);

          const addNewTacticBtn = createAddNewTacticButton();
          const deleteTacticBtn = createDeleteTacticButton();
          const renameTacticBtn = createRenameTacticButton();
          const updateTacticBtn = createUpdateTacticButton();
          const clearTacticsBtn = createClearTacticsButton();
          const resetTacticsBtn = createResetTacticsButton();
          const importTacticsBtn = createImportTacticsButton();
          const exportTacticsBtn = createExportTacticsButton();
          const aboutBtn = createAboutButton();
          const hiddenTriggerBtn = createHiddenTriggerButton();

          appendChildren(secondRow, [
            addNewTacticBtn,
            deleteTacticBtn,
            renameTacticBtn,
            updateTacticBtn,
            clearTacticsBtn,
            resetTacticsBtn,
            importTacticsBtn,
            exportTacticsBtn,
            aboutBtn,
          ]);

          appendChildren(tacticsSelectorDiv, [
            firstRow,
            secondRow,
            hiddenTriggerBtn,
          ]);

          if (isFootball) {
            insertAfterElement(tacticsSelectorDiv, tacticsBox);
          }

          fetchTacticsFromGMStorage()
            .then((data) => {
              dropdownTactics = data.tactics;

              dropdownTactics.sort((a, b) => {
                return a.name.localeCompare(b.name);
              });

              addTacticsToDropdown(tacticsDropdownMenu, dropdownTactics);

              tacticsDropdownMenu.addEventListener("change", function () {
                handleTacticSelection(this.value);
              });
            })
            .catch((err) => {
              console.error("Couldn't fetch data from json: ", err);
            });
          setInfoModal();
          updateTranslation();
        });
    }
    applyUxxFilter();
  }

  window.addEventListener("load", function () {
    initialize().catch((err) => {
      console.error("Init error: ", err);
    });
  });

  // _____Tactics Dropdown Menu_____

  function createTacticsDropdownMenu() {
    const dropdown = document.createElement("select");
    setupDropdownMenu(dropdown, "tactics_dropdown_menu");
    appendChildren(dropdown, [createPlaceholderOption()]);
    return dropdown;
  }

  function createTacticsDropdownMenuLabel() {
    const label = document.createElement("span");
    setupDropdownMenuLabel(
      label,
      "tactics_dropdown_menu_label",
      strings.tacticsDropdownMenuLabel
    );
    return label;
  }

  function createHiddenTriggerButton() {
    const button = document.createElement("button");
    button.id = "hidden_trigger_button";
    button.textContent = "";
    button.style.visibility = "hidden";

    button.addEventListener("click", function () {
      tacticsPreset.value = "5-3-2";
      tacticsPreset.dispatchEvent(new Event("change"));
    });

    return button;
  }

  async function fetchTacticsFromGMStorage() {
    const storedTactics = GM_getValue("ls_tactics");
    if (storedTactics) {
      return storedTactics;
    } else {
      const jsonTactics = await fetchTacticsFromJson();
      storeTacticsInGMStorage(jsonTactics);
      return jsonTactics;
    }
  }

  async function fetchTacticsFromJson() {
    const response = await fetch(defaultTacticsDataUrl);
    return await response.json();
  }

  function storeTacticsInGMStorage(data) {
    GM_setValue("ls_tactics", data);
  }

  function addTacticsToDropdown(dropdown, tactics) {
    for (const tactic of tactics) {
      const option = document.createElement("option");
      option.value = tactic.name;
      option.text = tactic.name;
      dropdown.appendChild(option);
    }
  }

  function handleTacticSelection(tactic) {
    const outfieldPlayers = Array.from(
      document.querySelectorAll(outfieldPlayersSelector)
    );

    const selectedTactic = dropdownTactics.find(
      (tacticData) => tacticData.name === tactic
    );

    if (selectedTactic) {
      if (outfieldPlayers.length < minOutfieldPlayers) {
        const hiddenTriggerButton = document.getElementById(
          "hidden_trigger_button"
        );
        hiddenTriggerButton.click();
        setTimeout(() => rearrangePlayers(selectedTactic.coordinates), 1);
      } else {
        rearrangePlayers(selectedTactic.coordinates);
      }
    }
  }

  function rearrangePlayers(coordinates) {
    const outfieldPlayers = Array.from(
      document.querySelectorAll(outfieldPlayersSelector)
    );

    findBestPositions(outfieldPlayers, coordinates);

    for (let i = 0; i < outfieldPlayers.length; ++i) {
      outfieldPlayers[i].style.left = coordinates[i][0] + "px";
      outfieldPlayers[i].style.top = coordinates[i][1] + "px";
      removeCollision(outfieldPlayers[i]);
    }

    removeTacticSlotInvalidStatus();
    updateFormationText(getFormation(coordinates));
  }

  function findBestPositions(players, coordinates) {
    players.sort((a, b) => parseInt(a.style.top) - parseInt(b.style.top));
    coordinates.sort((a, b) => a[1] - b[1]);
  }

  function removeCollision(player) {
    if (player.classList.contains("fieldpos-collision")) {
      player.classList.remove("fieldpos-collision");
      player.classList.add("fieldpos-ok");
    }
  }

  function removeTacticSlotInvalidStatus() {
    const slot = document.querySelector(tacticSlotSelector);
    if (slot) {
      slot.classList.remove("invalid");
    }
  }

  function updateFormationText(formation) {
    const formationTextElement = document.querySelector(formationTextSelector);
    formationTextElement.querySelector(".defs").textContent =
      formation.defenders;
    formationTextElement.querySelector(".mids").textContent =
      formation.midfielders;
    formationTextElement.querySelector(".atts").textContent =
      formation.strikers;
  }

  function getFormation(coordinates) {
    let strikers = 0;
    let midfielders = 0;
    let defenders = 0;

    for (const coo of coordinates) {
      const y = coo[1];
      if (y < 103) {
        strikers++;
      } else if (y <= 204) {
        midfielders++;
      } else {
        defenders++;
      }
    }

    return { strikers, midfielders, defenders };
  }

  // _____Add new tactic_____

  function createAddNewTacticButton() {
    const button = document.createElement("button");
    setupButton(button, "add_tactic_button", strings.addButton);

    button.addEventListener("click", function () {
      addNewTactic().catch(console.error);
    });

    return button;
  }

  async function addNewTactic() {
    const outfieldPlayers = Array.from(
      document.querySelectorAll(outfieldPlayersSelector)
    );

    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );

    const tacticCoordinates = outfieldPlayers.map((player) => [
      parseInt(player.style.left),
      parseInt(player.style.top),
    ]);

    if (!validateTacticPlayerCount(outfieldPlayers)) {
      return;
    }

    const tacticId = generateUniqueId(tacticCoordinates);
    const isDuplicate = await validateDuplicateTactic(tacticId);
    if (isDuplicate) {
      alert(strings.duplicateTacticError);
      return;
    }

    const tacticName = prompt(strings.tacticNamePrompt);
    const isValidName = await validateTacticName(tacticName);
    if (!isValidName) {
      return;
    }

    const tactic = {
      name: tacticName,
      coordinates: tacticCoordinates,
      id: tacticId,
    };

    saveTacticToStorage(tactic).catch(console.error);
    addTacticsToDropdown(tacticsDropdownMenu, [tactic]);
    dropdownTactics.push(tactic);

    tacticsDropdownMenu.value = tactic.name;
    handleTacticSelection(tactic.name);

    alert(strings.addAlert.replace("{}", tactic.name));
  }

  function validateTacticPlayerCount(outfieldPlayers) {
    const isGoalkeeper = document.querySelector(goalkeeperSelector);

    outfieldPlayers = outfieldPlayers.filter(
      (player) => !player.classList.contains("fieldpos-collision")
    );

    if (outfieldPlayers.length < minOutfieldPlayers || !isGoalkeeper) {
      alert(strings.invalidTacticError);
      return false;
    }

    return true;
  }

  async function validateDuplicateTactic(id) {
    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    return tacticsData.tactics.some((tactic) => tactic.id === id);
  }

  async function validateTacticName(name) {
    if (!name) {
      alert(strings.noTacticNameProvidedError);
      return false;
    }

    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    if (tacticsData.tactics.some((t) => t.name === name)) {
      alert(strings.alreadyExistingTacticNameError);
      return false;
    }

    if (name.length > maxTacticNameLength) {
      alert(strings.tacticNameLengthError);
      return false;
    }

    return true;
  }

  async function saveTacticToStorage(tactic) {
    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    tacticsData.tactics.push(tactic);
    await GM_setValue("ls_tactics", tacticsData);
  }

  // _____Delete tactic_____

  function createDeleteTacticButton() {
    const button = document.createElement("button");
    setupButton(button, "delete_tactic_button", strings.deleteButton);

    button.addEventListener("click", function () {
      deleteTactic().catch(console.error);
    });

    return button;
  }

  async function deleteTactic() {
    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );

    const selectedTactic = dropdownTactics.find(
      (tactic) => tactic.name === tacticsDropdownMenu.value
    );

    if (!selectedTactic) {
      alert(strings.noTacticSelectedError);
      return;
    }

    const confirmed = confirm(
      strings.deleteConfirmation.replace("{}", selectedTactic.name)
    );

    if (!confirmed) {
      return;
    }

    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    tacticsData.tactics = tacticsData.tactics.filter(
      (tactic) => tactic.id !== selectedTactic.id
    );

    await GM_setValue("ls_tactics", tacticsData);

    dropdownTactics = dropdownTactics.filter(
      (tactic) => tactic.id !== selectedTactic.id
    );

    const selectedOption = Array.from(tacticsDropdownMenu.options).find(
      (option) => option.value === selectedTactic.name
    );
    tacticsDropdownMenu.remove(selectedOption.index);

    if (tacticsDropdownMenu.options[0]?.disabled) {
      tacticsDropdownMenu.selectedIndex = 0;
    }

    alert(strings.deleteAlert.replace("{}", selectedTactic.name));
  }

  // _____Rename tactic_____

  function createRenameTacticButton() {
    const button = document.createElement("button");
    setupButton(button, "rename_tactic_button", strings.renameButton);

    button.addEventListener("click", function () {
      renameTactic().catch(console.error);
    });

    return button;
  }

  async function renameTactic() {
    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );

    const selectedTactic = dropdownTactics.find(
      (tactic) => tactic.name === tacticsDropdownMenu.value
    );

    if (!selectedTactic) {
      alert(strings.noTacticSelectedError);
      return;
    }

    const oldName = selectedTactic.name;

    const newName = prompt(strings.tacticNamePrompt);
    const isValidName = await validateTacticName(newName);
    if (!isValidName) {
      return;
    }

    const selectedOption = Array.from(tacticsDropdownMenu.options).find(
      (option) => option.value === selectedTactic.name
    );

    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    tacticsData.tactics = tacticsData.tactics.map((tactic) => {
      if (tactic.id === selectedTactic.id) {
        tactic.name = newName;
      }
      return tactic;
    });

    await GM_setValue("ls_tactics", tacticsData);

    dropdownTactics = dropdownTactics.map((tactic) => {
      if (tactic.id === selectedTactic.id) {
        tactic.name = newName;
      }
      return tactic;
    });

    selectedOption.value = newName;
    selectedOption.textContent = newName;

    const replacements = [oldName, newName];
    alert(strings.renameAlert.replace(/\{\}/g, () => replacements.shift()));
  }

  // _____Update tactic_____

  function createUpdateTacticButton() {
    const button = document.createElement("button");
    setupButton(button, "update_tactic_button", strings.updateButton);

    button.addEventListener("click", function () {
      updateTactic().catch(console.error);
    });

    return button;
  }

  async function updateTactic() {
    const outfieldPlayers = Array.from(
      document.querySelectorAll(outfieldPlayersSelector)
    );

    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );

    const selectedTactic = dropdownTactics.find(
      (tactic) => tactic.name === tacticsDropdownMenu.value
    );

    if (!selectedTactic) {
      alert(strings.noTacticSelectedError);
      return;
    }

    const updatedCoordinates = outfieldPlayers.map((player) => [
      parseInt(player.style.left),
      parseInt(player.style.top),
    ]);

    const newId = generateUniqueId(updatedCoordinates);

    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    const validationOutcome = await validateDuplicateTacticWithUpdatedCoord(
      newId,
      selectedTactic,
      tacticsData
    );

    switch (validationOutcome) {
      case "unchanged":
        return;
      case "duplicate":
        alert(strings.duplicateTacticError);
        return;
      case "unique":
        break;
      default:
        return;
    }

    const confirmed = confirm(
      strings.updateConfirmation.replace("{}", selectedTactic.name)
    );

    if (!confirmed) {
      return;
    }

    for (const tactic of tacticsData.tactics) {
      if (tactic.id === selectedTactic.id) {
        tactic.coordinates = updatedCoordinates;
        tactic.id = newId;
      }
    }

    for (const tactic of dropdownTactics) {
      if (tactic.id === selectedTactic.id) {
        tactic.coordinates = updatedCoordinates;
        tactic.id = newId;
      }
    }

    await GM_setValue("ls_tactics", tacticsData);

    alert(strings.updateAlert.replace("{}", selectedTactic.name));
  }

  async function validateDuplicateTacticWithUpdatedCoord(
    newId,
    selectedTac,
    tacticsData
  ) {
    if (newId === selectedTac.id) {
      return "unchanged";
    } else if (tacticsData.tactics.some((tac) => tac.id === newId)) {
      return "duplicate";
    } else {
      return "unique";
    }
  }

  // _____Clear tactics_____

  function createClearTacticsButton() {
    const button = document.createElement("button");
    setupButton(button, "clear_tactics_button", strings.clearButton);

    button.addEventListener("click", function () {
      clearTactics().catch(console.error);
    });

    return button;
  }

  async function clearTactics() {
    const confirmed = confirm(strings.clearConfirmation);

    if (!confirmed) {
      return;
    }

    await GM_setValue("ls_tactics", { tactics: [] });
    dropdownTactics = [];

    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );
    tacticsDropdownMenu.innerHTML = "";
    tacticsDropdownMenu.disabled = true;

    alert(strings.clearAlert);
  }

  // _____Reset default settings_____

  function createResetTacticsButton() {
    const button = document.createElement("button");
    setupButton(button, "reset_tactics_button", strings.resetButton);

    button.addEventListener("click", function () {
      resetTactics().catch(console.error);
    });

    return button;
  }

  async function resetTactics() {
    const confirmed = confirm(strings.resetConfirmation);

    if (!confirmed) {
      return;
    }

    const response = await fetch(defaultTacticsDataUrl);
    const data = await response.json();
    const defaultTactics = data.tactics;

    await GM_setValue("ls_tactics", { tactics: defaultTactics });
    dropdownTactics = defaultTactics;

    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );
    tacticsDropdownMenu.innerHTML = "";
    tacticsDropdownMenu.appendChild(createPlaceholderOption());
    addTacticsToDropdown(tacticsDropdownMenu, dropdownTactics);
    tacticsDropdownMenu.disabled = false;

    alert(strings.resetAlert);
  }

  // _____Import/Export_____

  function createImportTacticsButton() {
    const button = document.createElement("button");
    setupButton(button, "import_tactics_button", strings.importButton);

    button.addEventListener("click", function () {
      importTactics().catch(console.error);
    });

    return button;
  }

  function createExportTacticsButton() {
    const button = document.createElement("button");
    setupButton(button, "export_tactics_button", strings.exportButton);
    button.addEventListener("click", exportTactics);
    return button;
  }

  async function importTactics() {
    const input = document.createElement("input");
    input.type = "file";
    input.accept = ".json";

    input.onchange = async function (event) {
      const file = event.target.files[0];
      const reader = new FileReader();

      reader.onload = async function (event) {
        const importedTactics = JSON.parse(event.target.result).tactics;

        let existingTactics = await GM_getValue("ls_tactics", { tactics: [] });
        existingTactics = existingTactics.tactics;

        const mergedTactics = [...existingTactics];
        for (const importedTactic of importedTactics) {
          if (
            !existingTactics.some((tactic) => tactic.id === importedTactic.id)
          ) {
            mergedTactics.push(importedTactic);
          }
        }

        await GM_setValue("ls_tactics", { tactics: mergedTactics });

        mergedTactics.sort((a, b) => a.name.localeCompare(b.name));
        dropdownTactics = mergedTactics;

        const tacticsDropdownMenu = document.getElementById(
          "tactics_dropdown_menu"
        );
        tacticsDropdownMenu.innerHTML = "";
        tacticsDropdownMenu.append(createPlaceholderOption());
        addTacticsToDropdown(tacticsDropdownMenu, dropdownTactics);
        tacticsDropdownMenu.disabled = false;
      };

      reader.readAsText(file);
    };

    input.click();
  }

  function exportTactics() {
    const tactics = GM_getValue("ls_tactics", { tactics: [] });
    const tacticsJson = JSON.stringify(tactics);
    const blob = new Blob([tacticsJson], { type: "application/json" });
    const url = URL.createObjectURL(blob);

    const link = document.createElement("a");
    link.href = url;
    link.download = "tactics.json";
    link.click();

    URL.revokeObjectURL(url);
  }

  // _____About button_____

  function createAboutButton() {
    const button = document.createElement("button");
    setupButton(button, "about_button", strings.aboutButton);

    button.addEventListener("click", function (event) {
      event.stopPropagation();
      if (
        infoModal.style.display === "none" ||
        infoModal.style.opacity === "0"
      ) {
        showInfo();
      }
    });

    return button;
  }

  function showInfo() {
    infoModal.style.display = "block";
    setTimeout(function () {
      infoModal.style.opacity = "1";
    }, 0);
  }

  function hideInfo() {
    infoModal.style.opacity = "0";
    setTimeout(function () {
      infoModal.style.display = "none";
    }, 500);
  }

  function createInfoModal() {
    const modal = document.createElement("div");
    setupModal(modal, "info_modal");

    const modalContent = createModalContent();
    modal.appendChild(modalContent);

    window.onclick = function (event) {
      if (event.target == modal) {
        hideInfo();
      }
    };

    return modal;
  }

  function setupModal(modal, id) {
    modal.id = id;
    modal.style.display = "none";
    modal.style.position = "fixed";
    modal.style.zIndex = "1";
    modal.style.left = "50%";
    modal.style.top = "50%";
    modal.style.transform = "translate(-50%, -50%)";
    modal.style.opacity = "0";
    modal.style.transition = "opacity 0.5s ease-in-out";
  }

  function createModalContent() {
    const modalContent = document.createElement("div");
    styleModalContent(modalContent);

    const title = createTitle();
    const infoText = createInfoText();
    const feedbackText = createFeedbackText();

    modalContent.appendChild(title);
    modalContent.appendChild(infoText);
    modalContent.appendChild(feedbackText);

    return modalContent;
  }

  function styleModalContent(content) {
    content.style.backgroundColor = "#fefefe";
    content.style.margin = "auto";
    content.style.padding = "20px";
    content.style.border = "1px solid #888";
    content.style.width = "80%";
    content.style.maxWidth = "500px";
    content.style.borderRadius = "10px";
    content.style.fontFamily = "Montserrat, sans-serif";
    content.style.textAlign = "center";
    content.style.color = "#000";
    content.style.fontSize = "16px";
    content.style.lineHeight = "1.5";
  }

  function createTitle() {
    const title = document.createElement("h2");
    title.id = "info_modal_title";
    title.textContent = "MZ Tactics Selector";
    title.style.fontSize = "24px";
    title.style.fontWeight = "bold";
    title.style.marginBottom = "20px";
    return title;
  }

  function createInfoText() {
    const infoText = document.createElement("p");
    infoText.id = "info_modal_info_text";
    infoText.innerHTML = strings.modalContentInfoText;
    return infoText;
  }

  function createFeedbackText() {
    const feedbackText = document.createElement("p");
    feedbackText.id = "info_modal_feedback_text";
    feedbackText.innerHTML = strings.modalContentFeedbackText;
    return feedbackText;
  }

  function setInfoModal() {
    infoModal = createInfoModal();
    document.body.appendChild(infoModal);
    document.addEventListener("click", function (event) {
      if (
        infoModal.style.display === "block" &&
        !infoModal.contains(event.target)
      ) {
        infoModal.style.display = "none";
      }
    });
  }

  // _____Language Dropdown Menu_____

  function createLanguageDropdownMenu() {
    const dropdown = document.createElement("select");
    setupDropdownMenu(dropdown, "language_dropdown_menu");

    for (const lang of languages) {
      const option = document.createElement("option");
      option.value = lang.code;
      option.textContent = lang.name;
      if (lang.code === activeLanguage) {
        option.selected = true;
      }
      dropdown.appendChild(option);
    }

    dropdown.addEventListener("change", function () {
      changeLanguage(this.value).catch(console.error);
    });

    return dropdown;
  }

  function createLanguageDropdownMenuLabel() {
    const label = document.createElement("span");
    setupDropdownMenuLabel(
      label,
      "language_dropdown_menu_label",
      strings.languageDropdownMenuLabel
    );
    return label;
  }

  async function changeLanguage(languageCode) {
    try {
      const translationDataUrl = `https://raw.githubusercontent.com/douglasdotv/tactics-selector/main/json/lang/${languageCode}.json?callback=?`;
      const translations = await (await fetch(translationDataUrl)).json();

      i18next.changeLanguage(languageCode);
      i18next.addResourceBundle(languageCode, "translation", translations);

      GM_setValue("language", languageCode);

      updateTranslation();

      const language = languages.find((lang) => lang.code === languageCode);
      if (language) {
        const flagImage = document.getElementById("language_flag");
        flagImage.src = language.flag;
      }
    } catch (err) {
      console.error(err);
    }
  }

  function updateTranslation() {
    for (let key in strings) {
      strings[key] = i18next.t(key);
    }

    document.getElementById("add_tactic_button").textContent =
      strings.addButton;
    document.getElementById("delete_tactic_button").textContent =
      strings.deleteButton;
    document.getElementById("rename_tactic_button").textContent =
      strings.renameButton;
    document.getElementById("update_tactic_button").textContent =
      strings.updateButton;
    document.getElementById("clear_tactics_button").textContent =
      strings.clearButton;
    document.getElementById("reset_tactics_button").textContent =
      strings.resetButton;
    document.getElementById("import_tactics_button").textContent =
      strings.importButton;
    document.getElementById("export_tactics_button").textContent =
      strings.exportButton;
    document.getElementById("tactics_dropdown_menu_label").textContent =
      strings.tacticsDropdownMenuLabel;
    document.getElementById("language_dropdown_menu_label").textContent =
      strings.languageDropdownMenuLabel;
    document.getElementById("info_modal_info_text").innerHTML =
      strings.modalContentInfoText;
    document.getElementById("info_modal_feedback_text").innerHTML =
      strings.modalContentFeedbackText;
    document.getElementById("about_button").textContent = strings.aboutButton;
  }

  function getActiveLanguage() {
    let language = GM_getValue("language");
    if (!language) {
      let browserLanguage = navigator.language || "en";
      browserLanguage = browserLanguage.split("-")[0];
      const languageExists = languages.some(
        (lang) => lang.code === browserLanguage
      );
      language = languageExists ? browserLanguage : "en";
    }
    return language;
  }

  // _____Other_____

  function applyUxxFilter() {
    const minAge = 16;
    const maxAge = 21;
    let links = "";

    for (let i = minAge; i <= maxAge; ++i) {
      if (i !== minAge) {
        links += " ";
      }
      links += '<a href="#">' + i + "</a>";
    }

    $(".age-wrapper label").append(" " + links);

    let last = null;
    $(".age-wrapper label a").click(function () {
      const current = $(this).text().trim();

      if (last === current) {
        $("#age_from").val(current);
        $("#age_from").change();
      }

      $("#age_to").val(current);
      $("#age_to").change();

      if (parseInt($("#age_from").val()) > parseInt($("#age_to").val())) {
        $("#age_from").val($("#age_to").val());
        $("#age_from").change();
      }

      $("#filterSubmit").click();
      last = current;
    });
  }

  function appendChildren(parent, children) {
    children.forEach((ch) => {
      parent.appendChild(ch);
    });
  }

  function insertAfterElement(something, element) {
    element.parentNode.insertBefore(something, element.nextSibling);
  }

  function createMainDiv() {
    const div = document.createElement("div");
    setupMainDiv(div);
    return div;
  }

  function setupMainDiv(div) {
    div.id = "tactics_selector_div";
    div.style.width = "100%";
    div.style.display = "flex";
    div.style.flexDirection = "column";
    div.style.alignItems = "stretch";
    div.style.marginTop = "6px";
    div.style.marginLeft = "6px";
  }

  function createMainDivFirstRow() {
    const row = document.createElement("div");
    row.id = "tactics_selector_div_first_row";
    row.style.display = "flex";
    row.style.justifyContent = "space-between";
    row.style.flexWrap = "wrap";
    row.style.width = "75%";
    return row;
  }

  function createMainDivSecondRow() {
    const row = document.createElement("div");
    row.id = "tactics_selector_div_second_row";
    row.style.display = "flex";
    row.style.justifyContent = "flex-start";
    row.style.flexWrap = "wrap";
    return row;
  }

  function createLabelDropdownMenuGroup(label, dropdown) {
    const group = document.createElement("div");
    group.style.display = "flex";
    group.appendChild(label);
    group.appendChild(dropdown);
    return group;
  }

  function setupDropdownMenu(dropdown, id) {
    dropdown.id = id;
    dropdown.style.fontSize = "12px";
    dropdown.style.fontFamily = "Montserrat, sans-serif";
    dropdown.style.border = "2px solid #000";
    dropdown.style.borderRadius = "2px";
    dropdown.style.background = "linear-gradient(to right, #add8e6, #e6f7ff)";
    dropdown.style.color = "#000";
    dropdown.style.boxShadow = "3px 3px 5px rgba(0, 0, 0, 0.2)";
    dropdown.style.cursor = "pointer";
    dropdown.style.outline = "none";
    dropdown.style.margin = "6px 0 6px 6px";
  }

  function setupDropdownMenuLabel(description, id, textContent) {
    description.id = id;
    description.textContent = textContent;
    description.style.fontFamily = "Montserrat, sans-serif";
    description.style.fontSize = "12px";
    description.style.color = "#000";
    description.style.margin = "6px 0 6px 6px";
  }

  function setupButton(button, id, textContent) {
    button.id = id;
    button.textContent = textContent;
    button.style.fontFamily = "Montserrat, sans-serif";
    button.style.fontSize = "12px";
    button.style.color = "#000";
    button.style.marginLeft = "6px";
    button.style.marginTop = "6px";
    button.style.cursor = "pointer";
    button.style.boxShadow = "3px 3px 5px rgba(0, 0, 0, 0.2)";
  }

  function createPlaceholderOption() {
    const placeholderOption = document.createElement("option");
    placeholderOption.value = "";
    placeholderOption.text = "";
    placeholderOption.disabled = true;
    placeholderOption.selected = true;
    return placeholderOption;
  }

  function createFlagImage() {
    const img = document.createElement("img");
    img.id = "language_flag";
    img.style.height = "15px";
    img.style.width = "25px";
    img.style.margin = "9px 0 6px 6px";
    const activeLang = languages.find((lang) => lang.code === activeLanguage);
    if (activeLang) {
      img.src = activeLang.flag;
    }
    return img;
  }

  function generateUniqueId(coordinates) {
    const sortedCoordinates = coordinates.sort(
      (a, b) => a[1] - b[1] || a[0] - b[0]
    );

    const coordString = sortedCoordinates
      .map((coord) => `${coord[1]}_${coord[0]}`)
      .join("_");

    return sha256Hash(coordString);
  }

  function sha256Hash(str) {
    const shaObj = new jsSHA("SHA-256", "TEXT");
    shaObj.update(str);
    const hash = shaObj.getHash("HEX");
    return hash;
  }
})();