// ==UserScript==
// @name MZ - Player Ratings on Transfer Page
// @namespace douglaskampl
// @version 1.3
// @description Displays player ratings on transfer page
// @author Douglas
// @match https://www.managerzone.com/?p=transfer
// @match https://www.managerzone.com/?p=players
// @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant GM_addStyle
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const ratings = {
"SPEED": { "K": 0.09, "D": 0.25, "A": 0.25, "M": 0.15, "W": 0.25, "F": 0.23 },
"STAMINA": { "K": 0.09, "D": 0.16, "A": 0.18, "M": 0.15, "W": 0.20, "F": 0.15 },
"PLAYINT": { "K": 0.09, "D": 0.07, "A": 0.05, "M": 0.10, "W": 0.06, "F": 0.05 },
"PASSING": { "K": 0.02, "D": 0.02, "A": 0.05, "M": 0.15, "W": 0.04, "F": 0.04 },
"SHOOTING": { "K": 0.00, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.05, "F": 0.28 },
"HEADING": { "K": 0.00, "D": 0.00, "A": 0.02, "M": 0.00, "W": 0.00, "F": 0.03 },
"GOALKEEPING": { "K": 0.55, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.00, "F": 0.00 },
"BALLCONTROL": { "K": 0.09, "D": 0.08, "A": 0.10, "M": 0.12, "W": 0.15, "F": 0.15 },
"TACKLING": { "K": 0.00, "D": 0.30, "A": 0.25, "M": 0.20, "W": 0.05, "F": 0.02 },
"CROSSING": { "K": 0.02, "D": 0.07, "A": 0.05, "M": 0.08, "W": 0.15, "F": 0.00 },
"SETPLAYS": { "K": 0.00, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.00, "F": 0.00 },
"EXPERIENCE": { "K": 0.05, "D": 0.05, "A": 0.05, "M": 0.05, "W": 0.05, "F": 0.05 }
};
const skillPositionMap = [
"SPEED",
"STAMINA",
"PLAYINT",
"PASSING",
"SHOOTING",
"HEADING",
"GOALKEEPING",
"BALLCONTROL",
"TACKLING",
"CROSSING",
"SETPLAYS",
"EXPERIENCE"
];
function calculateRatings(skills) {
const player = {
K: 0,
D: 0,
A: 0,
M: 0,
W: 0,
F: 0,
B: 0,
top: 0
};
Object.entries(skills).forEach(([skill, value]) => {
if (!ratings[skill]) return;
const num = parseInt(value, 10);
if (isNaN(num)) return;
if (skill !== "EXPERIENCE") {
player.B += num;
}
player.K += num * ratings[skill]["K"];
if (player.K > player.top) player.top = player.K;
player.D += num * ratings[skill]["D"];
if (player.D > player.top) player.top = player.D;
player.A += num * ratings[skill]["A"];
if (player.A > player.top) player.top = player.A;
player.M += num * ratings[skill]["M"];
if (player.M > player.top) player.top = player.M;
player.W += num * ratings[skill]["W"];
if (player.W > player.top) player.top = player.W;
player.F += num * ratings[skill]["F"];
if (player.F > player.top) player.top = player.F;
});
return {
K: player.K.toFixed(2),
D: player.D.toFixed(2),
A: player.A.toFixed(2),
M: player.M.toFixed(2),
W: player.W.toFixed(2),
F: player.F.toFixed(2),
B: player.B,
top: player.top.toFixed(2)
};
}
function extractPlayerSkills(playerElement) {
const skills = {};
const skillRows = playerElement.querySelectorAll('.player_skills tr');
skillRows.forEach((row, index) => {
if (index >= skillPositionMap.length) return;
const valueElem = row.querySelector('.skillval span');
if (valueElem) {
const skillType = skillPositionMap[index];
const value = valueElem.textContent.trim();
skills[skillType] = value;
}
});
return skills;
}
function createRatingDisplay(ratings) {
const positions = [
{ code: 'K', name: 'Goalkeeper', value: ratings.K },
{ code: 'D', name: 'Defender', value: ratings.D },
{ code: 'A', name: 'Anchorman', value: ratings.A },
{ code: 'M', name: 'Midfielder', value: ratings.M },
{ code: 'W', name: 'Winger', value: ratings.W },
{ code: 'F', name: 'Forward', value: ratings.F }
];
const container = document.createElement('div');
container.className = 'mz-rating-container';
const ratingsList = document.createElement('div');
ratingsList.className = 'mz-rating-list';
positions.forEach(pos => {
const row = document.createElement('div');
row.className = 'mz-rating-row';
const isTop = pos.value === ratings.top;
const posName = document.createElement('span');
posName.className = 'mz-pos-name' + (isTop ? ' mz-pos-top' : '');
posName.textContent = pos.name + ':';
const posValue = document.createElement('span');
posValue.className = 'mz-pos-value' + (isTop ? ' mz-pos-top' : '');
posValue.textContent = pos.value;
row.appendChild(posName);
row.appendChild(posValue);
ratingsList.appendChild(row);
});
container.appendChild(ratingsList);
return container;
}
function addRatingButton(playerElement) {
const idElement = playerElement.querySelector('.player_id_span');
if (!idElement) return;
if (idElement.nextElementSibling && idElement.nextElementSibling.classList.contains('mz-rating-btn')) {
return;
}
const btn = document.createElement('button');
btn.className = 'mz-rating-btn';
btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
btn.title = 'Show player ratings';
let ratingContainer = null;
let isVisible = false;
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (isVisible && ratingContainer) {
ratingContainer.classList.remove('mz-rating-visible');
setTimeout(() => {
if (ratingContainer && ratingContainer.parentNode) {
ratingContainer.parentNode.removeChild(ratingContainer);
}
ratingContainer = null;
}, 300);
isVisible = false;
btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
return;
}
const skills = extractPlayerSkills(playerElement);
const ratings = calculateRatings(skills);
ratingContainer = createRatingDisplay(ratings);
const playerHeader = playerElement.querySelector('.subheader');
if (playerHeader) {
playerHeader.parentNode.insertBefore(ratingContainer, playerHeader.nextSibling);
} else {
playerElement.appendChild(ratingContainer);
}
setTimeout(() => {
ratingContainer.classList.add('mz-rating-visible');
}, 10);
isVisible = true;
btn.innerHTML = '<i class="fa-solid fa-xmark"></i>';
});
idElement.parentNode.insertBefore(btn, idElement.nextSibling);
}
function processPlayerElements() {
const playerContainers = document.querySelectorAll('div[id^="thePlayers_"]');
playerContainers.forEach(addRatingButton);
}
function setUpObserver() {
const playerContainer = document.getElementById('players_container') || document.body;
const observer = new MutationObserver((mutations) => {
let shouldProcess = false;
mutations.forEach(mutation => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE &&
(node.id && node.id.startsWith('thePlayers_') ||
node.querySelector && node.querySelector('div[id^="thePlayers_"]'))) {
shouldProcess = true;
break;
}
}
}
});
if (shouldProcess) {
processPlayerElements();
}
});
observer.observe(playerContainer, { childList: true, subtree: true });
return observer;
}
function addStyles() {
GM_addStyle(`
.mz-rating-btn {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 8px;
width: 24px;
height: 24px;
border: none;
border-radius: 50%;
background: #1a73e8;
color: white;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.mz-rating-btn:hover {
background: #0d5bbb;
transform: translateY(-1px);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.25);
}
.mz-rating-container {
margin: 10px 0;
padding: 12px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-width: 300px;
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s ease;
}
.mz-rating-visible {
opacity: 1;
transform: translateY(0);
}
.mz-rating-header {
font-weight: bold;
margin-bottom: 8px;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
color: #333;
font-size: 14px;
}
.mz-rating-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-bottom: 10px;
}
.mz-rating-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 3px 5px;
}
.mz-pos-name {
font-size: 13px;
color: #555;
}
.mz-pos-value {
font-weight: bold;
font-size: 13px;
color: #333;
}
.mz-pos-top {
color: #1a73e8;
}
.mz-total-row {
margin-top: 5px;
padding-top: 8px;
border-top: 1px solid #eee;
}
`);
}
function init() {
addStyles();
processPlayerElements();
setUpObserver();
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(init, 500);
} else {
window.addEventListener('DOMContentLoaded', () => setTimeout(init, 500));
}
})();