// ==UserScript== // @name MZ - Training History // @namespace douglaskampl // @version 2.0 // @description Fetches player training history and counts skills gained across seasons // @author Douglas // @match https://www.managerzone.com/?p=players // @match https://www.managerzone.com/?p=transfer // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com // @grant GM_addStyle // @grant GM_getResourceText // @require https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js // @resource trainingHistoryStyles https://u18mz.vercel.app/mz/userscript/other/vTrainingHistory.css // @run-at document-idle // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; GM_addStyle(GM_getResourceText('trainingHistoryStyles')); const SKILL_MAP = { '1': "Speed", '2': "Stamina", '3': "Play Intelligence", '4': "Passing", '5': "Shooting", '6': "Heading", '7': "Keeping", '8': "Ball Control", '9': "Tackling", '10': "Aerial Passing", '11': "Set Plays" }; function getCurrentSeasonInfo() { const header = document.querySelector('#header-stats-wrapper h5.flex-grow-1.textCenter.linked'); const dateNode = document.querySelector('#header-stats-wrapper h5.flex-grow-1.textCenter'); if (!header || !dateNode) return null; const sm = header.textContent.match(/Season\s+(\d+).*Day\s+(\d+)/); const dm = dateNode.textContent.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/); if (!sm || !dm) return null; const s = parseInt(sm[1], 10); const d = parseInt(sm[2], 10); const c = new Date(`${dm[2]}/${dm[1]}/${dm[3]}`); return { season: s, day: d, currentDate: c }; } function createModal(content, showSpinner) { const overlay = document.createElement('div'); overlay.className = 'mz-training-overlay'; const modal = document.createElement('div'); modal.className = 'mz-training-modal'; const body = document.createElement('div'); body.className = 'mz-training-modal-content'; const spinnerEl = document.createElement('div'); spinnerEl.style.height = '60px'; spinnerEl.style.display = showSpinner ? 'block' : 'none'; body.appendChild(spinnerEl); if (content) body.innerHTML += content; const closeBtn = document.createElement('div'); closeBtn.className = 'mz-training-modal-close'; closeBtn.innerHTML = '×'; closeBtn.onclick = () => overlay.remove(); modal.appendChild(closeBtn); modal.appendChild(body); overlay.appendChild(modal); document.body.appendChild(overlay); overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); }); requestAnimationFrame(() => { overlay.classList.add('show'); modal.classList.add('show'); }); let spinnerInstance = null; if (showSpinner) { spinnerInstance = new Spinner({ color: '#ffa500', lines: 12 }); spinnerInstance.spin(spinnerEl); } return { modal, spinnerEl, spinnerInstance, overlay }; } function parseSeriesData(txt) { const m = txt.match(/var series = (\[.*?\]);/); return m ? JSON.parse(m[1]) : null; } function getSeasonCalculator(cs) { if (!cs) return () => 0; const baseSeason = cs.season; const baseDate = cs.currentDate; const dayOffset = cs.day; const seasonStart = new Date(baseDate); seasonStart.setDate(seasonStart.getDate() - (dayOffset - 1)); return (date) => { let s = baseSeason; let ref = seasonStart.getTime(); let diff = Math.floor((date.getTime() - ref) / 86400000); while (diff < 0) { s--; diff += 91; } while (diff >= 91) { s++; diff -= 91; } return s; }; } function getAgeForSeason(ageNow, currentSeason, targetSeason) { return ageNow - (currentSeason - targetSeason); } function getPlayerAge(container) { const strongs = container.querySelectorAll('strong'); for (const s of strongs) { const val = parseInt(s.textContent.trim(), 10); if (val >= 14 && val <= 55) return val; } return 18; } function generateReportHTML(playerName, bySeason, total, skillTotals, minAgePerSeason, currentSeason, ageNow) { let html = `