// ==UserScript==
// @name MZ Tactics Selector
// @namespace essenfc
// @version 4.4
// @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=*
// @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant GM_getValue
// @grant GM_setValue
// @license MIT
// ==/UserScript==
const fontLink = document.createElement("link");
fontLink.href =
"https://fonts.googleapis.com/css2?family=Montserrat&display=swap";
fontLink.rel = "stylesheet";
document.head.appendChild(fontLink);
let modal;
(function () {
"use strict";
let dropdownTactics = [];
const defaultTacticsDataUrl =
"https://raw.githubusercontent.com/douglasdotv/tactics-selector/main/tactics.json?callback=?";
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)";
window.addEventListener("load", function () {
const tacticsSelectorDiv = createTacSelDiv();
const dropdown = createDropdownMenu();
const dropdownDescription = createDropdownDescription();
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 hiBtn = createHiButton();
appendChildren(tacticsSelectorDiv, [
dropdownDescription,
dropdown,
addNewTacticBtn,
deleteTacticBtn,
renameTacticBtn,
updateTacticBtn,
clearTacticsBtn,
resetTacticsBtn,
importTacticsBtn,
exportTacticsBtn,
aboutBtn,
hiBtn,
]);
if (isSoccerTacticsPage()) {
insertAfterElement(
tacticsSelectorDiv,
document.getElementById("tactics_box")
);
}
modal = createInfoModal();
document.body.appendChild(modal);
document.addEventListener("click", function (event) {
if (modal.style.display === "block" && !modal.contains(event.target)) {
modal.style.display = "none";
}
});
fetchTacticsFromLocalStorage()
.then((data) => {
dropdownTactics = data.tactics;
dropdownTactics.sort((a, b) => {
return a.name.localeCompare(b.name);
});
addTacticsToDropdown(dropdown, dropdownTactics);
dropdown.addEventListener("change", function () {
handleTacticSelection(this.value);
});
})
.catch((err) => {
console.error("Couldn't fetch data from json: ", err);
});
});
function createTacSelDiv() {
const myDiv = document.createElement("div");
myDiv.id = "tactics_selector_div";
myDiv.style.width = "100%";
myDiv.style.display = "flex";
myDiv.style.flexWrap = "wrap";
myDiv.style.alignItems = "center";
myDiv.style.justifyContent = "flex-start";
myDiv.style.marginTop = "6px";
myDiv.style.marginLeft = "6px";
return myDiv;
}
// _____Dropdown Menu_____
function createDropdownMenu() {
const dropdown = document.createElement("select");
setupDropdownMenu(dropdown);
const placeholderOption = createPlaceholderOption();
appendChildren(dropdown, [placeholderOption]);
return dropdown;
}
function setupDropdownMenu(dropdown) {
dropdown.id = "tacticsDropdown";
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";
}
function createPlaceholderOption() {
const placeholderOption = document.createElement("option");
placeholderOption.value = "";
placeholderOption.text = "";
placeholderOption.disabled = true;
placeholderOption.selected = true;
return placeholderOption;
}
function createDropdownDescription() {
const description = document.createElement("span");
description.textContent = "Select a tactic: ";
description.style.fontFamily = "Montserrat, sans-serif";
description.style.fontSize = "12px";
description.style.color = "#000";
return description;
}
function createHiButton() {
const button = document.createElement("button");
button.id = "hiButton";
button.textContent = "";
button.style.visibility = "hidden";
button.addEventListener("click", function () {
const presetDropdown = document.getElementById("tactics_preset");
presetDropdown.value = "5-3-2";
presetDropdown.dispatchEvent(new Event("change"));
});
return button;
}
async function fetchTacticsFromLocalStorage() {
const storedTactics = GM_getValue("ls_tactics");
if (storedTactics) {
return storedTactics;
} else {
const jsonTactics = await fetchTacticsFromJson();
storeTacticsInLocalStorage(jsonTactics);
return jsonTactics;
}
}
async function fetchTacticsFromJson() {
const response = await fetch(defaultTacticsDataUrl);
return await response.json();
}
function storeTacticsInLocalStorage(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) {
let outfieldPlayers = Array.from(
document.querySelectorAll(outfieldPlayersSelector)
);
const selectedTactic = dropdownTactics.find(
(tacticData) => tacticData.name === tactic
);
if (selectedTactic) {
if (outfieldPlayers.length < 10) {
const hiButton = document.getElementById("hiButton");
hiButton.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";
checkForCollision(outfieldPlayers[i]);
}
}
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 checkForCollision(player) {
if (player.classList.contains("fieldpos-collision")) {
player.classList.remove("fieldpos-collision");
player.classList.add("fieldpos-ok");
}
}
// _____Add new tactic_____
function createAddNewTacticButton() {
const button = document.createElement("button");
button.id = "addNewTacticButton";
button.textContent = "Add current tactic";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
button.addEventListener("click", function () {
addNewTactic().catch(console.error);
});
return button;
}
async function addNewTactic() {
let dropdown = document.getElementById("tacticsDropdown");
let outfieldPlayers = Array.from(
document.querySelectorAll(outfieldPlayersSelector)
);
if (!validateTacticPlayerCount(outfieldPlayers)) {
return;
}
const tacticName = prompt("Please enter a name for the tactic: ");
const isValidName = await validateTacticName(tacticName);
if (!isValidName) {
return;
}
let coordinates = outfieldPlayers.map((player) => [
parseInt(player.style.left),
parseInt(player.style.top),
]);
let tactic = {
name: tacticName,
coordinates,
id: generateUniqueId(),
};
saveTacticToStorage(tactic).catch(console.error);
addTacticsToDropdown(dropdown, [tactic]);
dropdownTactics.push(tactic);
dropdown.value = tactic.name;
handleTacticSelection(tactic.name);
alert(`Tactic "${tactic.name}" has been added.`);
}
function validateTacticPlayerCount(outfieldPlayers) {
let isGoalkeeper = document.querySelector(
".fieldpos.fieldpos-ok.goalkeeper.ui-draggable"
);
outfieldPlayers = outfieldPlayers.filter(
(player) => !player.classList.contains("fieldpos-collision")
);
if (outfieldPlayers.length < 10 || !isGoalkeeper) {
alert(
"Error: invalid tactic. You must have 1 goalkeeper and 10 outfield players in valid positions."
);
return false;
}
return true;
}
async function validateTacticName(name) {
if (!name) {
alert("Error: you must provide a name for the tactic.");
return false;
}
const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
if (tacticsData.tactics.some((t) => t.name === name)) {
alert(
"Error: a tactic with this name already exists. Please choose a different name."
);
return false;
}
if (name.length > 50) {
alert("Error: tactic name must be less than 50 characters.");
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");
button.id = "deleteTacticButton";
button.textContent = "Delete tactic";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
button.addEventListener("click", function () {
deleteTactic().catch(console.error);
});
return button;
}
async function deleteTactic() {
let dropdown = document.getElementById("tacticsDropdown");
let selectedTactic = dropdownTactics.find(
(tactic) => tactic.name === dropdown.value
);
if (!selectedTactic) {
alert("Error: no tactic selected.");
return;
}
const confirmed = confirm(
`Are you sure you want to delete the tactic "${selectedTactic.name}"?`
);
if (!confirmed) {
return;
}
alert(`Tactic "${selectedTactic.name}" was successfully deleted!`);
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(dropdown.options).find(
(option) => option.value === selectedTactic.name
);
dropdown.remove(selectedOption.index);
if (dropdown.options[0]?.disabled) {
dropdown.selectedIndex = 0;
}
}
// _____Rename tactic_____
function createRenameTacticButton() {
const button = document.createElement("button");
button.id = "renameTacticButton";
button.textContent = "Rename tactic";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
button.addEventListener("click", function () {
renameTactic().catch(console.error);
});
return button;
}
async function renameTactic() {
let dropdown = document.getElementById("tacticsDropdown");
let selectedTactic = dropdownTactics.find(
(tactic) => tactic.name === dropdown.value
);
if (!selectedTactic) {
alert("Error: no tactic selected.");
return;
}
const oldName = selectedTactic.name;
const newName = prompt("Please enter a new name for this tactic: ");
const isValidName = await validateTacticName(newName);
if (!isValidName) {
return;
}
const selectedOption = Array.from(dropdown.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;
alert(`Tactic "${oldName}" has been renamed to "${newName}".`);
}
// _____Update tactic_____
function createUpdateTacticButton() {
const button = document.createElement("button");
button.id = "updateTacticButton";
button.textContent = "Update tactic";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
button.addEventListener("click", function () {
updateTactic().catch(console.error);
});
return button;
}
async function updateTactic() {
let dropdown = document.getElementById("tacticsDropdown");
let outfieldPlayers = Array.from(
document.querySelectorAll(outfieldPlayersSelector)
);
let selectedTactic = dropdownTactics.find(
(tactic) => tactic.name === dropdown.value
);
if (!selectedTactic) {
alert("Error: no tactic selected.");
return;
}
const confirmed = confirm(
`Are you sure you want to update "${selectedTactic.name}" coordinates?`
);
if (!confirmed) {
return;
}
alert(
`Tactic "${selectedTactic.name}" coordinates were successfully updated!`
);
let updatedCoordinates = outfieldPlayers.map((player) => [
parseInt(player.style.left),
parseInt(player.style.top),
]);
const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
for (let tactic of tacticsData.tactics) {
if (tactic.id === selectedTactic.id) {
tactic.coordinates = updatedCoordinates;
}
}
for (let tactic of dropdownTactics) {
if (tactic.id === selectedTactic.id) {
tactic.coordinates = updatedCoordinates;
}
}
await GM_setValue("ls_tactics", tacticsData);
}
// _____Clear tactics_____
function createClearTacticsButton() {
const button = document.createElement("button");
button.id = "clearButton";
button.textContent = "Clear tactics";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
button.addEventListener("click", function () {
clearTactics().catch(console.error);
});
return button;
}
async function clearTactics() {
const confirmed = confirm(
"Are you sure you want to clear all tactics? This action will delete all saved tactics and cannot be undone."
);
if (!confirmed) {
return;
}
await GM_setValue("ls_tactics", { tactics: [] });
dropdownTactics = [];
const dropdown = document.getElementById("tacticsDropdown");
dropdown.innerHTML = "";
dropdown.disabled = true;
alert("Tactics successfully cleared!");
}
// _____Reset default settings_____
function createResetTacticsButton() {
const button = document.createElement("button");
button.id = "resetButton";
button.textContent = "Reset tactics";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
button.addEventListener("click", function () {
resetTactics().catch(console.error);
});
return button;
}
async function resetTactics() {
const confirmed = confirm(
"Are you sure you want to reset? This action will overwrite all saved tactics and cannot be undone."
);
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 dropdown = document.getElementById("tacticsDropdown");
dropdown.innerHTML = "";
dropdown.appendChild(createPlaceholderOption());
addTacticsToDropdown(dropdown, dropdownTactics);
dropdown.disabled = false;
alert("Reset done!");
}
// _____Import/Export_____
function createImportTacticsButton() {
const button = document.createElement("button");
button.id = "importButton";
button.textContent = "Import tactics";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
button.addEventListener("click", function () {
importTactics().catch(console.error);
});
return button;
}
function createExportTacticsButton() {
const button = document.createElement("button");
button.id = "exportButton";
button.textContent = "Export tactics";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
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 dropdown = document.getElementById("tacticsDropdown");
dropdown.innerHTML = "";
dropdown.append(createPlaceholderOption());
addTacticsToDropdown(dropdown, dropdownTactics);
dropdown.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");
button.id = "aboutButton";
button.textContent = "About";
button.style.fontFamily = "Montserrat, sans-serif";
button.style.fontSize = "12px";
button.style.color = "#000";
button.style.marginLeft = "6px";
button.style.cursor = "pointer";
button.addEventListener("click", function (event) {
event.stopPropagation();
if (modal.style.display === "none" || modal.style.opacity === "0") {
showInfo();
}
});
return button;
}
function showInfo() {
modal.style.display = "block";
setTimeout(function () {
modal.style.opacity = "1";
}, 0);
}
function hideInfo() {
modal.style.opacity = "0";
setTimeout(function () {
modal.style.display = "none";
}, 500);
}
function createInfoModal() {
const modal = document.createElement("div");
setupInfoModal(modal);
const modalContent = createModalContent();
modal.appendChild(modalContent);
window.onclick = function (event) {
if (event.target == modal) {
hideInfo();
}
};
return modal;
}
function setupInfoModal(modal) {
modal.id = "infoModal";
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.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.innerHTML =
'For instructions, click <a href="https://greasyfork.org/pt-BR/scripts/467712-mz-tactics-selector" style="color: #007BFF;">here</a>.';
return infoText;
}
function createFeedbackText() {
const feedbackText = document.createElement("p");
feedbackText.innerHTML =
'If you run into any issues or have any suggestions, contact me here: <a href="https://www.managerzone.com/?p=guestbook&uid=8623925"><img src="https://www.managerzone.com/img/soccer/reply_guestbook.gif"></a>';
return feedbackText;
}
// _____Other_____
function appendChildren(element, children) {
children.forEach((ch) => {
element.appendChild(ch);
});
}
function insertAfterElement(toBeInserted, element) {
element.parentNode.insertBefore(toBeInserted, element.nextSibling);
}
function generateUniqueId() {
let currentDate = new Date();
return (
currentDate.getFullYear() +
"-" +
(currentDate.getMonth() + 1) +
"-" +
currentDate.getDate() +
"_" +
currentDate.getHours() +
"-" +
currentDate.getMinutes() +
"-" +
currentDate.getSeconds() +
"_" +
Math.random().toString(36).substring(2, 15)
);
}
function isSoccerTacticsPage() {
return document.getElementById("tactics_box").classList.contains("soccer");
}
})();