Enhances the ManagerZone tactics interface: copy/paste formations, display rival formations with additional player data, and adjust player positions in real time (mouse + touchscreen), and now you can save your tactics.
// ==UserScript==
// @name MZ Axis
// @namespace http://tampermonkey.net/
// @version 0.4.1
// @description Enhances the ManagerZone tactics interface: copy/paste formations, display rival formations with additional player data, and adjust player positions in real time (mouse + touchscreen), and now you can save your tactics.
// @author jrcl
// @match https://www.managerzone.com/?p=tactics*
// @grant GM_xmlhttpRequest
// @grant GM_log
// @grant GM_addStyle
// @require https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.4/pako.min.js
// @license MIT
// ==/UserScript==
GM_addStyle(`
.fieldpos::after {
content: attr(data-x) " " attr(data-y);
position: absolute;
bottom: -14px;
left: 50%;
transform: translateX(-50%);
font-size: 8px;
color: #fff;
pointer-events: none;
white-space: nowrap;
text-align: center;
font-family: monospace;
}
/* error input */
.mz-error-input{
background-color:#ffd6d6;
transition: background-color 0.2s ease;
}
`);
(function() {
'use strict';
let _x = '--';
let _y = '--';
let inputX;
let inputY;
let formation = [];
let rivalFormation = [];
let rivalTeam = null;
const conversionRatesToUSD = {USD: 1,R$: 2.827003,SEK: 7.4234,EUR: 0.80887,GBP: 0.555957,DKK: 6.00978,NOK: 6.921908,CHF: 1.265201,CAD: 1.3003,AUD: 1.309244,ILS: 4.378812,MXN: 10.82507,ARS: 2.807162,BOB: 7.905644,PYG: 5671.047,UYU: 28.88898,RUB: 28.21191,PLN: 3.801452,ISK: 71.15307,BGN: 1.576971,ZAR: 5.999531,THB: 43.46507,SIT: 190.539,SKK: 29.75788,JPY: 123.7233,INR: 43.66706,MZ: 7.4234,MM: 7.4234,点: 7.4234,};
if(isSoccer()) {
createRivalButton();
createCoordinatesPanel();
updateCoordinatesPanel();
initPlayers();
observePitchChanges();
document.addEventListener("keydown", setKeys);
}
// Función para crear y configurar el botón de rival
function createRivalButton() {
// Obtener el primer span dentro de 'formation-container'
const firstSpanOnFContainer = document.querySelector('#formation-container span');
if (!firstSpanOnFContainer) return;
// Crear y configurar el botón para la táctica del rival
const toAddRivalBtn = document.createElement('button');
toAddRivalBtn.id = "rivalBtn";
toAddRivalBtn.textContent = "xmlRival";
toAddRivalBtn.style.color = "blue";
// Agregar el botón al contenedor
firstSpanOnFContainer.appendChild(toAddRivalBtn);
// Agregar eventos
toAddRivalBtn.addEventListener('click', cfgRivalFormation);
toAddRivalBtn.addEventListener('mouseover', showAdditionalInfoRivalPlayers);
toAddRivalBtn.addEventListener('touchstart', e => { e.preventDefault(); showAdditionalInfoRivalPlayers(); });
}
function observePitchChanges() {
const pitch = document.getElementById("pitch");
if (!pitch) return;
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
// Nuevos jugadores añadidos
if (m.type === "childList") {
for (const node of m.addedNodes) {
if (!(node instanceof HTMLElement)) continue;
if (node.classList?.contains("fieldpos")) {
setupPlayers();
return;
}
}
}
// Cambios en style (movimiento)
if (m.type === "attributes" && m.attributeName === "style") {
const el = m.target;
if (el.classList?.contains("fieldpos")) {
updatePlayerCoords(el);
const active = getActivePlayer();
if (active === el) {
setCoordsLabel({ currentTarget: active });
}
}
}
// Cambios en class (selección)
if (m.type === "attributes" && m.attributeName === "class") {
const el = m.target;
if (el.classList?.contains("fieldpos")) {
const active = getActivePlayer();
if (!active) {
_x = '--';
_y = '--';
} else {
setCoordsLabel({ currentTarget: active });
attachArrowControlListeners();
}
updateCoordinatesPanel();
}
}
}
});
observer.observe(pitch, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["style", "class"]
});
}
function waitForElement(selector){
return new Promise(resolve => {
const el = document.querySelector(selector);
if(el) return resolve(el);
const observer = new MutationObserver(()=>{
const el = document.querySelector(selector);
if(el){
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body,{
childList:true,
subtree:true
});
});
}
function attachArrowControlListeners() {
const arrowControls = document.querySelectorAll('.arrow-controls');
arrowControls.forEach(control => {
const buttons = control.querySelectorAll('.left-button, .right-button, .up-button, .down-button');
buttons.forEach(button => {
if (!button.dataset.listenerAttached) {
button.addEventListener('click', () => {
const activePlayer = getActivePlayer();
if (activePlayer) {
setCoordsLabel({ currentTarget: activePlayer });
}
});
button.dataset.listenerAttached = "true";
}
});
});
}
function showAdditionalInfoRivalPlayers() {
const addInfo = document.getElementsByClassName('additionalInfoRivalPlayer');
for (let i = addInfo.length - 1; i >= 0; i--) {
addInfo[i].classList.toggle('hidden');
}
}
function initPlayers() {
const container = document.getElementById('pitch');
if (!container) return;
if (container.dataset.mzAxisInit) return;
container.dataset.mzAxisInit = "true";
container.addEventListener('click', handlePlayerEvent);
container.addEventListener('keydown', handlePlayerEvent);
function handlePlayerEvent(e) {
const player = e.target.closest('.fieldpos.fieldpos-ok.ui-draggable');
if (!player) return;
setCoordsLabel({ currentTarget: player });
const validX = isInRange(inputX.value, 'x');
const validY = isInRange(inputY.value, 'y');
// manejar warnings
validX ? removeWarning(inputX) : addWarning(inputX);
validY ? removeWarning(inputY) : addWarning(inputY);
}
setupPlayers();
}
function getActivePlayer() {
return document.querySelector('.fieldpos.ui-selected');
}
function getFieldPlayers() {
return document.querySelectorAll('.fieldpos.fieldpos-ok.ui-draggable:not(.substitute):not(.goalkeeper)');
}
function setupPlayers() {
const players = document.querySelectorAll('.fieldpos.ui-draggable');
players.forEach(player => {
// evitar reinicializar
if (player.dataset.initialized) return;
player.dataset.initialized = "true";
updatePlayerCoords(player);
$(player).draggable({
drag: function(event, ui) {
const player = this;
const pos = pitchToCoords(ui.position.left, ui.position.top);
player.dataset.x = pos.x;
player.dataset.y = pos.y;
_x = pos.x;
_y = pos.y;
updateCoordinatesPanel();
}
});
let originalStop = $(player).draggable("option", "stop");
$(player).draggable("option", "stop", function(event, ui) {
if (originalStop) originalStop.call(this, event, ui);
updatePlayerCoords(player);
_x = player.dataset.x;
_y = player.dataset.y;
updateCoordinatesPanel();
});
});
}
function setCoordsLabel(event) {
const player = event.currentTarget;
getOffset(player);
updateCoordinatesPanel();
}
function getOffset( el ) {
const pos = pitchToCoords(el.offsetLeft, el.offsetTop);
_x = pos.x;
_y = pos.y;
}
function updateCoordinatesPanel(){
inputX.value = _x;
inputY.value = _y;
const disabled = (_x === '--');
inputX.disabled = disabled;
inputY.disabled = disabled;
}
function copyFormation() {
const coords = getCurrentCoords();
if(!coords) return;
const txtCoord = coords.map(p => `${p.x},${p.y}`).join("\n");
navigator.clipboard.writeText(txtCoord)
.then(() => {
console.log('Formation copied to the clipboard');
alert('Formation copied to the clipboard');
})
.catch(err => {
console.error('Error copying to the clipboard:', err);
})
//console.log(txtCoord);
}
function getCurrentCoords(){
const players = [...getFieldPlayers()];
if(players.length !== 10){
alert("Not Valid Formation");
return null;
}
const coords = players.map(p => ({
x: Number(p.dataset.x),
y: Number(p.dataset.y)
}));
// ordenar por Y (arriba→abajo) y luego por X (izquierda→derecha)
coords.sort((a,b)=> a.y - b.y || a.x - b.x);
return coords;
}
function saveTactic(){
const coords = getCurrentCoords();
if(!coords) return;
const name = prompt("Tactic name:");
if(!name) return;
const text = name + "\n" + coords.map(p => `${p.x},${p.y}`).join("\n");
showNote(2026,'01','01',1);
setTimeout(()=>{
const textarea = document.querySelector("textarea[name='cal_message']");
if(!textarea) return;
textarea.value = text;
$("#calendar_add_not_btn").triggerHandler("click");
},500);
}
async function loadTactics(){
$("#calendar-new").datepicker("setDate", new Date(2026,0,1));
setTimeout(async ()=>{
[...document.querySelectorAll("#calendar-new td[data-handler='selectDay']")]
.find(td => td.textContent.trim() === "1")
.click();
await waitForElement("textarea[id^='note']");
const notes = [...document.querySelectorAll("textarea[id^='note']")];
const tactics = [];
notes.forEach(n => {
const noteID = n.id.replace("note","");
const lines = n.value
.split("\n")
.map(l=>l.trim())
.filter(Boolean);
if(lines.length >= 11){
const name = lines[0];
const coords = lines.slice(1,11);
tactics.push({ name, coords, noteID });
}
});
powerboxClose('calendar-administration');
showTacticsModal(tactics);
},300);
}
function showTacticsModal(tactics){
let modal = document.getElementById("mzTacticModal");
if(modal) modal.remove();
modal = document.createElement("div");
modal.id = "mzTacticModal";
modal.style.position = "fixed";
modal.style.top = "120px";
modal.style.left = "50%";
modal.style.transform = "translateX(-50%)";
modal.style.background = "#f2f2f2";
modal.style.border = "1px solid #666";
modal.style.padding = "10px";
modal.style.zIndex = "9999";
modal.style.minWidth = "220px";
modal.style.maxHeight = "70vh";
modal.style.overflowY = "auto";
let html = `<div style="font-weight:bold;margin-bottom:8px;">Tactics Library</div>`;
tactics.forEach((t,i)=>{
html += `
<div style="display:flex;justify-content:space-between;
padding:4px;border-bottom:1px solid #ddd;">
<span class="mzTacticItem"
data-index="${i}"
style="cursor:pointer;">
${t.name}
</span>
<span class="mzDeleteTactic"
data-index="${i}"
style="cursor:pointer;color:red;">
🗑
</span>
</div>`;
});
html += `
<div style="text-align:center;margin-top:8px;">
<button id="mzCloseTacticModal">Close</button>
</div>
`;
modal.innerHTML = html;
document.body.appendChild(modal);
modal.querySelectorAll(".mzTacticItem").forEach(el=>{
el.onclick = function(){
const t = tactics[this.dataset.index];
loadTactic(t);
modal.remove();
};
});
modal.querySelectorAll(".mzDeleteTactic").forEach(el=>{
el.onclick = function(){
const t = tactics[this.dataset.index];
if(!confirm("Delete tactic: "+t.name+" ?")) return;
confirm_delete_note(
t.noteID,
'2026-01-01 00:00:00',
2026,
'01',
1
);
modal.remove();
setTimeout(()=>{
powerboxClose('calendar-administration');
loadTactics(); // recargar lista
},400);
};
});
document.getElementById("mzCloseTacticModal")
.onclick = ()=> modal.remove();
}
function loadTactic(tactic){
formation = [];
tactic.coords.forEach(c => {
let [x,y] = c.split(",");
const pos = coordsToPitch(Number(x), Number(y));
formation.push([ pos.left, pos.top ]);
});
// Optional because it’s already saved in sorted order
formation = sortFormation(formation);
setFormation();
}
function sortFormation(arr){
return arr.sort((a,b) => b[1] - a[1] || a[0] - b[0]);
}
function pasteFormation() {
navigator.clipboard.readText()
.then(text => {
console.log('Clipboard text:', text)
// is xml content?
if (text.search("xml") != -1) {
// parse clipboard to xml file
console.log('Cartesian coordinate system from XML file');
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(text,"text/xml");
formation = [];
let pos = xmlDoc.getElementsByTagName("Pos")
for (let i = 1; i < pos.length; i++) {
formation.push([Number(pos[i].getAttribute("x")) - 7, Number(pos[i].getAttribute("y")) - 9]);
}
formation = sortFormation(formation);
// formation.sort(function(a,b) { if (b[1] == a[1]) return a[0] - b[0]; else return b[1] - a[1]; })
setFormation();
} else if (text.trim().split('\n').length == 10) { // is there 10 coord?
console.log('Cartesian coordinate system from Clipboard');
let coorXY = text.trim().split('\n')
formation = [];
for (let pair of coorXY) {
let [xAxis,yAxis] = pair.split(',');
const pos = coordsToPitch( Number(xAxis), Number(yAxis) );
formation.push([ pos.left, pos.top]);
// formation.push([Number(xAxis) + 97, 155 - Number(yAxis)]);
}
formation = sortFormation(formation);
//formation.sort(function(a,b) { if (b[1] == a[1]) return a[0] - b[0]; else return b[1] - a[1]; })
setFormation();
} else if (formation.length == 10) {
console.log('Cartesian coordinate system from internal var');
setFormation();
} else {
console.log("Nothing to do");
}
})
.catch(err => {
console.error('Error reading from the clipboard:', err)
})
}
function setFormation() {
let player = getFieldPlayers();
if (formation.length == player.length) {
// get an order
let oldFormation = [];
for (let i = 0; i < player.length; i++) {
oldFormation.push([Number((player[i].style.left).slice(0,-2)), Number((player[i].style.top).slice(0,-2)), i]);
}
oldFormation = sortFormation(oldFormation);
//oldFormation.sort(function(a,b) { if (b[1] == a[1]) return a[0] - b[0]; else return b[1] - a[1]; })
for (let i = 0; i < formation.length; i++) {
player[oldFormation[i][2]].style.left = formation[i][0] + 'px';
player[oldFormation[i][2]].style.top = formation[i][1] + 'px';
}
alert('Formation successfully copied');
formation = []; // empty formation
}
}
function cfgRivalFormation() {
let elBoton = document.getElementById('rivalBtn');
if(elBoton.innerHTML == "xmlRival") {
pasteRivalFormation();
} else {
elBoton.style.color = "blue";
elBoton.innerHTML = "xmlRival";
let rivalPlayers = document.getElementById('rivalPlayers');
if(rivalPlayers) {
rivalPlayers.parentElement.removeChild(rivalPlayers);
}
navigator.clipboard.writeText('')
.then(() => {
console.log('Clipboard Clear');
})
.catch(err => {
console.error('Error clearing the clipboard:', err);
})
}
}
function pasteRivalFormation(){
navigator.clipboard.readText()
.then(text => {
// is xml content?
if (text.search("xml") != -1) {
console.log('Clipboard text:', text)
// parse clipboard to xml file
console.log('Cartesian coordinate system from XML file');
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(text,"text/xml");
rivalFormation = [];
let pos = xmlDoc.getElementsByTagName("Pos")
// x, y, shirtno, value, height, weight, age, countryShortname
for (let i = 0; i < pos.length; i++) {
rivalFormation.push([Number(pos[i].getAttribute("x")) - 7, Number(pos[i].getAttribute("y")) - 9, '9', '', '', '', '', '' ]);
}
rivalFormation = sortFormation(rivalFormation);
// rivalFormation.sort(function(a,b) { if (b[1] == a[1]) return a[0] - b[0]; else return b[1] - a[1]; })
rivalTeam = xmlDoc.querySelector("Team");
rivalTeam.setAttribute("tactic", rivalTeam.getAttribute('tactics'));
setRivalFormation();
} else if (text != null && text != "") {
// is valid id number
let matchId = prompt("Please enter your Match ID [mid]:", (text.trim().length < 14 && !isNaN(text.trim()))? text.trim():"No IDea");
if (matchId == null) {
console.log("User cancel this");
} else if(matchId == "" || matchId == "No IDea" || matchId.length < 5 || matchId.length > 14 || isNaN(matchId.trim()) ) {
console.log("User dont have any idea about this or not valid match id");
} else {
matchId = matchId.trim();
console.log(`Your matchId is:${matchId}`);
getDataFromXML(matchId);
}
} else if (rivalFormation.length > 0) {
console.log('Formation from system on internal var');
setRivalFormation();
} else {
console.log("Nothing to do");
}
})
.catch(err => {
console.error('Error reading from the clipboard:', err)
})
}
function getDataFromXML(matchId) {
let statsUrl = `http://download06.managerzone.com/data/soccer/${matchId.slice(-1)}/${matchId.charAt(matchId.length-2)}/stats${matchId}.xml.gz`;
GM_xmlhttpRequest({
method: 'GET',
// url: 'http://download06.managerzone.com/data/soccer/7/1/stats1486585817.xml.gz',
url: statsUrl,
responseType: 'arraybuffer', // We want to handle the binary data (arraybuffer)
onload: function(response) {
if (response.status === 200) {
try {
// Decompress the .gz file using pako
const decompressed = pako.ungzip(new Uint8Array(response.response), { to: 'string' });
// Parse the XML data
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(decompressed, 'text/xml');
let teams = xmlDoc.documentElement.getElementsByTagName("Team");
let ishome = 0;
if (confirm(`Press [ OK ] if your rival is:\t${teams[0].getAttribute('name')}\nPress [ Cancel ] if your rival is:\t${teams[1].getAttribute('name')}`)) {
ishome = 1;
} else {
ishome = 0;
}
rivalTeam = ishome ? teams[0] : teams[1];
console.log(rivalTeam.getAttribute('name'));
rivalFormation = [];
// x, y, shirtno, value, height, weight, age, countryShortname
let players = xmlDoc.querySelectorAll(`Player[teamId="${rivalTeam.getAttribute('id')}"][origin*=","]`);
for (let i = 0; i < players.length; i++) {
let origin = players[i].getAttribute('origin');
let xy = origin.split(",");
rivalFormation.push([StatsToPos_X(xy[0], ishome) - 7, StatsToPos_Y(xy[1], ishome) - 9, players[i].getAttribute('shirtno'), '', '', '', '', '' ]);
}
// Additional Data
let squadTeamUrl = `http://www.managerzone.com/xml/team_playerlist.php?sport_id=1&team_id=${rivalTeam.getAttribute('id')}`
GM_xmlhttpRequest({
method: 'GET',
url: squadTeamUrl,
onload: function(response) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(response.responseText, "text/xml");
let teamPlayers = xmlDoc.querySelector('TeamPlayers');
for (let i = 0; i < players.length; i++) {
let player = xmlDoc.querySelector(`Player[id="${players[i].getAttribute('id')}"]`);
if (player) {
let value = Number(player.getAttribute('value')) / (conversionRatesToUSD[teamPlayers.getAttribute('teamCurrency')] || 1)
rivalFormation[i][3] = '' + value.toFixed(0);
//player.getAttribute('value').replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
rivalFormation[i][4] = player.getAttribute('height');
rivalFormation[i][5] = player.getAttribute('weight');
rivalFormation[i][6] = player.getAttribute('age');
rivalFormation[i][7] = player.getAttribute('countryShortname');
}
}
setRivalFormation();
},
onerror: function(error) {
console.log('Error:', error);
}
});
} catch (error) {
console.error('Error decompressing or parsing XML:', error);
}
} else {
console.error('Failed to fetch the file:', response.statusText);
alert('The match has probably not been viewed in 2D yet, therefore we cannot obtain the statistics, or try again later.');
}
},
onerror: function(error) {
console.error('Error with GM_xmlhttpRequest:', error);
}
});
}
function StatsToPos_X (i, isHome) {
let ret = isHome ? Math.round(-.255800462 * i + 199.8228530689) : Math.round(.2555000556 * i + 8.3741302936);
return ret;
}
function StatsToPos_Y (i, isHome) {
let ret = isHome ? Math.round(-.3073207154 * i + 315.9278777381) : Math.round(.3070644902 * i + 9.2794889414);
return ret;
}
function setRivalFormation () {
let rivalPlayers = document.createElement('div');
rivalPlayers.id = "rivalPlayers";
let formationText = document.getElementById('formation_text');
formationText.parentNode.insertBefore(rivalPlayers, formationText.nextSibling);
console.log('players on the pitch');
let contentRival = '';
if (rivalTeam.hasAttribute("name")) {
contentRival += `<div style="position:absolute; left: 1px; top: 3px; width: 215px;">`+
`<div style="display: flex; justify-content: space-between; font-size:0.7em; color:black"><div style="display: flex; flex-direction: column; justify-content: space-around; max-width: 90px; text-align: center;"><span>${rivalTeam.getAttribute('name')}</span><span><img src="https://www.managerzone.com/nocache-910/img/flags/64/${rivalTeam.getAttribute('country').toLowerCase()}.png" width="14" alt=""></span></div>` +
`<span><img src="https://www.managerzone.com/dynimg/badge.php?team_id=${rivalTeam.getAttribute('id')}" width="25" alt=""></span></div></div>`;
}
contentRival += `<div style="position:absolute; left: 1px; top: 44px; width: 215px">` +
`<div style="display: flex; justify-content: space-around; font-size:0.7em;"><span>${rivalTeam.getAttribute('tactic')}</span><span>${rivalTeam.getAttribute('playstyle')}</span><span>${rivalTeam.getAttribute('aggression')}</span></div></div>`;
for (let i = 0; i < rivalFormation.length; i++) {
contentRival += "<div style=\"position:absolute; left: " + (194 - rivalFormation[i][0]) + "px; top: " + (310 - rivalFormation[i][1]) + "px;\">" +
"<div style=\"border-radius: 50%; width: 18px; height: 18px; border: 2px solid #FFE42B; color: black; display: flex; align-items: center; justify-content: center; font-size:0.9em; font-weight: bold;\">"+rivalFormation[i][2]+"</div>" +
"<div style=\"position:absolute; top: -6px; width:22px; display: flex; justify-content: space-between; font-size:0.45em; color:black\"><span>" + (rivalFormation[i][0] - 97) + "</span><span>" + (155 - rivalFormation[i][1]) + "</span></div>" +
`<div class="additionalInfoRivalPlayer hidden"><div style="position: absolute; white-space: nowrap; font-size:0.45em; writing-mode: vertical-rl; text-align: right; color:black; left: 22px; top: -6px; height:37px"><span>${rivalFormation[i][3]?rivalFormation[i][3].replace(/\B(?=(\d{3})+(?!\d))/g, ' ')+" USD":''}</span></div>` +
`<div style="display: flex; justify-content: space-between; font-size:0.3em; color:black"><span>${rivalFormation[i][4]?rivalFormation[i][4]+" cm":''}</span><span>${rivalFormation[i][6]?rivalFormation[i][6]+" y":''}</span></div>` +
`<div style="display: flex; justify-content: space-between; font-size:0.3em; color:black"><span>${rivalFormation[i][5]?rivalFormation[i][5]+" kg":''}</span><span>${rivalFormation[i][7]?`<img src="https://www.managerzone.com/nocache-910/img/flags/s_${rivalFormation[i][7]}.gif" width="7" alt="">`:''}</span></div></div></div>`;
}
rivalPlayers.innerHTML = contentRival;
let elBoton = document.getElementById('rivalBtn');
elBoton.style.color = '#FF4500';
elBoton.innerHTML = "clearRival";
}
function createCoordinatesPanel(){
if (document.getElementById("divCoords")) return;
const coordsContainer = document.getElementById('formation-container');
if (!coordsContainer) return;
const div = document.createElement("div");
div.id = "divCoords";
div.innerHTML = `
<span style="font-weight:600">
Coords:
<span style="color:green">X</span>
<input id="inputX" type="text" style="width:24px">
<span style="color:blue">Y</span>
<input id="inputY" type="text" style="width:24px">
</span>
<button id="copyBtn">Copy</button>
<button id="pasteBtn">Paste</button>
<button id="saveBtn">Save</button>
<button id="loadBtn">Load</button>
`;
coordsContainer.appendChild(div);
const copyBtn = div.querySelector("#copyBtn");
const pasteBtn = div.querySelector("#pasteBtn");
const saveBtn = div.querySelector("#saveBtn");
saveBtn.style.color = "white";
saveBtn.style.background = "#4CAF50";
const loadBtn = div.querySelector("#loadBtn");
loadBtn.style.color = "white";
loadBtn.style.background = "#007bff";
inputX = div.querySelector("#inputX");
inputY = div.querySelector("#inputY");
inputX.inputMode = "numeric";
inputY.inputMode = "numeric";
inputX.addEventListener("input", setPlayerPosition);
inputY.addEventListener("input", setPlayerPosition);
const selectAll = e => e.target.select();
inputX.addEventListener("focus", selectAll);
inputY.addEventListener("focus", selectAll);
copyBtn.addEventListener("click", copyFormation);
pasteBtn.addEventListener("click", pasteFormation);
saveBtn.addEventListener("click", saveTactic);
loadBtn.addEventListener("click", loadTactics);
[copyBtn, pasteBtn, saveBtn, loadBtn].forEach(btn => {
btn.style.display = "inline-block";
btn.style.margin = "0 0px";
btn.style.padding = "2px 3px"; // más pequeño en móviles
btn.style.boxSizing = "border-box";
});
}
function setPlayerPosition() {
const x = Number(inputX.value);
const y = Number(inputY.value);
const player = getActivePlayer();
if(!player) return;
const validX = isInRange(x, 'x');
const validY = isInRange(y, 'y');
validX ? removeWarning(inputX) : addWarning(inputX);
validY ? removeWarning(inputY) : addWarning(inputY);
if(validX && validY){
const pos = coordsToPitch(x, y);
player.style.left = pos.left + "px";
player.style.top = pos.top + "px";
}
}
function isInRange(number, coordinate) {
const n = Number(number);
if (Number.isNaN(n)) return false;
if (coordinate === 'x') {
return n >= -96 && n <= 96;
}
if (coordinate === 'y') {
return n >= -157 && n <= 101;
}
return false;
}
function setKeys(key) {
if((key.keyCode === 37 || key.keyCode === 38 || key.keyCode === 39 || key.keyCode === 40)
&& (key.currentTarget.activeElement.localName != 'input')) {
const players = document.querySelectorAll('.fieldpos.fieldpos-ok.ui-draggable.ui-selected, .fieldpos.ui-selected.fieldpos-collision');
if(players.length) {
const pos = pitchToCoords(players[0].offsetLeft, players[0].offsetTop);
_x = pos.x;
_y = pos.y;
} else {
_y = '--';
_x = '--';
}
updateCoordinatesPanel();
}
}
function updatePlayerCoords(player){
const pos = pitchToCoords(player.offsetLeft, player.offsetTop);
player.dataset.x = pos.x;
player.dataset.y = pos.y;
}
function coordsToPitch(x, y){
return {
left: x + 97,
top: 155 - y
};
}
function pitchToCoords(left, top){
return {
x: Math.round(left) - 97,
y: 155 - Math.round(top)
};
}
function addWarning(input) {
input.classList.add("mz-error-input");
}
function removeWarning(input) {
input.classList.remove("mz-error-input");
}
function isSoccer() {
const sport = document.getElementById('tactics_box');
return sport?.classList.contains('soccer') ?? false;
}
})();