Reads all scout reports for observed players. Calculates the most accurate estimate for unscouted players by averaging Potential skill scores, and provides a role suitability rating.
当前为
// ==UserScript==
// @name FMP Player Analyzer v1.0
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Reads all scout reports for observed players. Calculates the most accurate estimate for unscouted players by averaging Potential skill scores, and provides a role suitability rating.
// @author FMP Assistant
// @match https://footballmanagerproject.com/Team/Player*
// @icon https://www.google.com/s2/favicons?sz=64&domain=footballmanagerproject.com
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- 1. BUTTON AND STYLE ---
const analyzeBtn = document.createElement("button");
analyzeBtn.innerText = "📊 FULL ANALYSIS";
analyzeBtn.style.position = "fixed";
analyzeBtn.style.top = "130px";
analyzeBtn.style.right = "20px";
analyzeBtn.style.zIndex = "9999";
analyzeBtn.style.padding = "10px 20px";
analyzeBtn.style.backgroundColor = "#2c3e50";
analyzeBtn.style.color = "#f1c40f";
analyzeBtn.style.border = "2px solid #f1c40f";
analyzeBtn.style.borderRadius = "5px";
analyzeBtn.style.cursor = "pointer";
analyzeBtn.style.fontWeight = "bold";
analyzeBtn.style.boxShadow = "0px 0px 10px rgba(0,0,0,0.5)";
document.body.appendChild(analyzeBtn);
// --- 2. ROLE DEFINITIONS ---
// Key skills for each role based on their index in the skill table (0: Handling, 1: Stamina, 2: Pace, etc.)
const ROLES = [
// Defensive Roles
{ name_en: "Central Defender (CD)", keySkills: [3, 4, 5, 9] }, // Marking, Tackling, Positioning, Heading
// Midfield Roles
{ name_en: "Deep Playmaker (DLP)", keySkills: [6, 8, 7, 1] }, // Passing, Technique, Crossing, Stamina
// Attacking Roles
{ name_en: "Advanced Forward (AF)", keySkills: [10, 2, 9, 11] } // Finishing, Pace, Heading, Longshots
];
// --- 3. ANALYSIS LOGIC ---
analyzeBtn.addEventListener("click", function() {
// --- A) CURRENT STRENGTH CALCULATION ---
const currentSkills = document.querySelectorAll('.skilltable span.num');
let totalCurrentScore = 0;
let ca_phy = 0, ca_def = 0, ca_mid = 0, ca_att = 0;
const getVal = (index) => parseFloat(currentSkills[index] ? currentSkills[index].innerText : 0);
if (currentSkills.length >= 12) {
// Current Strength Average Calculation
ca_phy = (getVal(1) + getVal(2)) / 2; // Stamina + Pace
ca_def = (getVal(3) + getVal(4) + getVal(5)) / 3; // Marking + Tackling + Positioning
ca_mid = (getVal(6) + getVal(8) + getVal(7)) / 3; // Passing + Technique + Crossing
ca_att = (getVal(10) + getVal(9) + getVal(11)) / 3; // Finishing + Heading + Longshots
totalCurrentScore = (ca_phy + ca_def + ca_mid + ca_att) / 4;
} else {
alert("Skill table not found.");
return;
}
// --- B) ROLE SUITABILITY CALCULATION ---
let roleScores = [];
ROLES.forEach(role => {
let sum = 0;
role.keySkills.forEach(index => {
sum += getVal(index);
});
let average = sum / role.keySkills.length;
roleScores.push({
name_en: role.name_en,
score: average
});
});
// Sort scores from highest to lowest
roleScores.sort((a, b) => b.score - a.score);
// --- C) HTML DATA READING AND REPORT MODE DETERMINATION ---
let reportData = {
summary: "---",
physical: "---",
defense: "---",
midfield: "---",
attack: "---",
blooming: "❓",
pro: "❓",
lead: "❓",
pers: "❓",
fit: "❓"
};
let isScouted = false;
let estimatedTotalPotential = 0;
// 1. Find Summary Score (div.rec) - Scout Report Check
const recDiv = document.querySelector('div.rec');
if (recDiv) {
reportData.summary = recDiv.innerText.trim();
isScouted = true;
}
// 2. Scan Texts and Titles (Scouted Mode)
if (isScouted) {
const skillDevTds = document.querySelectorAll('td.skilldev');
skillDevTds.forEach(td => {
let fullText = td.innerText;
const spans = td.querySelectorAll('span');
spans.forEach(span => {
if (span.title) fullText += "\n" + span.title;
});
const lines = fullText.split('\n');
lines.forEach(line => {
line = line.trim();
if (!line) return;
// Support reading from both Turkish and English game settings
if (line.includes("Fizik:") || line.includes("Physical:")) reportData.physical = line.split(":")[1].trim();
if (line.includes("Savunma:") || line.includes("Defense:")) reportData.defense = line.split(":")[1].trim();
if (line.includes("Orta Saha:") || line.includes("Midfield:")) reportData.midfield = line.split(":")[1].trim();
if (line.includes("Hücum:") || line.includes("Attack:")) reportData.attack = line.split(":")[1].trim();
if (line.includes("Patlama:") || line.includes("Blooming:")) reportData.blooming = line.split(":")[1].trim();
if (line.includes("Profesyonellik: ") || line.includes("Professionalism: ")) reportData.pro = line.replace(/Profesyonellik: |Professionalism: /, "").trim();
if (line.includes("Liderlik: ") || line.includes("Leadership: ")) reportData.lead = line.replace(/Liderlik: |Leadership: /, "").trim();
if (line.includes("Kişilik: ") || line.includes("Personality: ")) reportData.pers = line.replace(/Kişilik: |Personality: /, "").trim();
if (line.includes("Fitness: ") || line.includes("Fitness: ")) reportData.fit = line.replace(/Fitness: |Fitness: /, "").trim();
});
});
estimatedTotalPotential = parseFloat(reportData.summary.split('/')[0]) * (20/25);
}
// C) ESTIMATION MODE (If unscouted)
if (!isScouted) {
const potSkills = document.querySelectorAll('td.skilldev span');
let potValues = {};
let potNames = ['Handling', 'Stamina', 'Pace', 'Marking', 'Tackling', 'Positioning', 'Passing', 'Crossing', 'Technique', 'Heading', 'Finishing', 'Longshots'];
let currentPotIndex = 0;
potSkills.forEach(span => {
const text = span.textContent.trim();
if (text.startsWith("Pot:") && currentPotIndex < potNames.length) {
const value = parseFloat(text.replace('Pot:', '').trim());
potValues[potNames[currentPotIndex]] = value;
currentPotIndex++;
}
});
if (Object.keys(potValues).length >= 12) {
const getPotVal = (name) => potValues[name] || 0;
let pot_phy = (getPotVal('Stamina') + getPotVal('Pace')) / 2;
let pot_def = (getPotVal('Marking') + getPotVal('Tackling') + getPotVal('Positioning')) / 3;
let pot_mid = (getPotVal('Passing') + getPotVal('Technique') + getPotVal('Crossing')) / 3;
let pot_att = (getPotVal('Finishing') + getPotVal('Heading') + getPotVal('Longshots')) / 3;
estimatedTotalPotential = (pot_phy + pot_def + pot_mid + pot_att) / 4;
reportData.physical = `${getEstimatedText(pot_phy)} (${pot_phy.toFixed(1)})`;
reportData.defense = `${getEstimatedText(pot_def)} (${pot_def.toFixed(1)})`;
reportData.midfield = `${getEstimatedText(pot_mid)} (${pot_mid.toFixed(1)})`;
reportData.attack = `${getEstimatedText(pot_att)} (${pot_att.toFixed(1)})`;
} else {
reportData.physical = `${getEstimatedText(ca_phy)} (${ca_phy.toFixed(1)})`;
reportData.defense = `${getEstimatedText(ca_def)} (${ca_def.toFixed(1)})`;
reportData.midfield = `${getEstimatedText(ca_mid)} (${ca_mid.toFixed(1)})`;
reportData.attack = `${getEstimatedText(ca_att)} (${ca_att.toFixed(1)})`;
estimatedTotalPotential = totalCurrentScore;
}
reportData.summary = "❓ Scouting Required";
}
// --- D) REPORT BOX ---
let oldBox = document.getElementById("customReportBox");
if (oldBox) oldBox.remove();
const reportBox = document.createElement("div");
reportBox.id = "customReportBox";
reportBox.style.position = "fixed";
reportBox.style.top = "15%";
reportBox.style.left = "35%";
reportBox.style.backgroundColor = "rgba(20, 30, 20, 0.98)";
reportBox.style.border = `2px solid ${isScouted ? '#a6f704' : '#00d2d3'}`;
reportBox.style.color = "white";
reportBox.style.zIndex = "10000";
reportBox.style.borderRadius = "10px";
reportBox.style.minWidth = "380px";
reportBox.style.fontFamily = "Arial, sans-serif";
reportBox.style.boxShadow = "0 0 25px black";
const titleText = isScouted ? '📋 FMP FULL REPORT (Scouted)' : '⚠️ ESTIMATED POTENTIAL ANALYSIS (V1.0)';
const titleColor = isScouted ? '#f1c40f' : '#00d2d3';
const potScoreDisplay = isScouted ? reportData.summary : estimatedTotalPotential.toFixed(1) + ' / 20';
const potScoreTitle = isScouted ? 'SCOUT RATING (Total Potential):' : 'ESTIMATED POTENTIAL AVERAGE:';
// Role score HTML table (Completely English)
let roleScoreHtml = `
<h4 style="margin:10px 0 5px 0; color:#f1c40f; border-bottom:1px solid #555;">🏆 ROLE SUITABILITY SCORES (CA / 20)</h4>
<table style="width:100%; color:#ddd; font-size:12px;">
${roleScores.map(role => `
<tr>
<td>${role.name_en}</td>
<td style="text-align:right; color:${getTextColor(getEstimatedText(role.score))}"><b>${role.score.toFixed(1)}</b></td>
</tr>
`).join('')}
</table>
`;
reportBox.innerHTML = `
<div id="reportHeader" style="cursor: move; background-color: #1a2500; padding: 12px; border-radius: 8px 8px 0 0; border-bottom: 1px solid #a6f704; display: flex; justify-content: space-between;">
<span style="color:${titleColor}; font-weight:bold;">${titleText}</span>
<span style="color:#ccc; font-size:11px;">(Current Strength Avg.: ${totalCurrentScore.toFixed(1)})</span>
</div>
<div style="padding: 15px; font-size: 13px;">
<div style="background:rgba(255,255,255,0.05); padding:8px; border-radius:5px; margin-bottom:10px;">
<div style="display:flex; justify-content:space-between; border-bottom:1px solid #555; padding-bottom:5px; margin-bottom:5px;">
<span style="color:#a6f704; font-weight:bold;">${potScoreTitle}</span>
<span style="font-size:16px; font-weight:bold; color:white;">${potScoreDisplay}</span>
</div>
${!isScouted ? '<p style="font-size:11px; color:#00d2d3; margin: 5px 0 10px 0;">*This score is the average of the **Visible Potential Skill Scores (Pot:)**.</p>' : ''}
<table style="width:100%; color:#ddd;">
<tr><td>💪 Physical:</td> <td style="text-align:right; color:${getTextColor(reportData.physical)}"><b>${reportData.physical}</b></td></tr>
<tr><td>🛡️ Defense:</td> <td style="text-align:right; color:${getTextColor(reportData.defense)}"><b>${reportData.defense}</b></td></tr>
<tr><td>🎯 Midfield:</td> <td style="text-align:right; color:${getTextColor(reportData.midfield)}"><b>${reportData.midfield}</b></td></tr>
<tr><td>⚽ Attack:</td> <td style="text-align:right; color:${getTextColor(reportData.attack)}"><b>${reportData.attack}</b></td></tr>
</table>
</div>
${roleScoreHtml}
<div style="background:rgba(255,255,255,0.05); padding:8px; border-radius:5px; margin-bottom:10px;">
<strong style="color:#00d2d3;">🚀 BLOOMING:</strong>
<span style="float:right; font-weight:bold;">${reportData.blooming}</span>
</div>
<div style="background:rgba(255,255,255,0.05); padding:8px; border-radius:5px;">
<h4 style="margin:0 0 5px 0; color:#f1c40f; border-bottom:1px solid #555;">HIDDEN ATTRIBUTES</h4>
<div style="line-height:1.6;">
🧠 Prof: <span style="float:right; color:white;">${reportData.pro}</span><br>
📢 Leader: <span style="float:right; color:white;">${reportData.lead}</span><br>
🎭 Personality: <span style="float:right; color:white;">${reportData.pers}</span><br>
❤️ Fitness: <span style="float:right; color:white;">${reportData.fit}</span>
</div>
${!isScouted ? '<p style="margin:5px 0 0; font-size:11px; color:red;">*Hidden attributes and Blooming Status cannot be seen without a Scout!</p>' : ''}
</div>
<div style="text-align:center; margin-top:15px;">
<button id="closeReport" style="padding:6px 20px; cursor:pointer; background:#b32020; color:white; border:none; border-radius:4px;">CLOSE</button>
</div>
</div>
`;
document.body.appendChild(reportBox);
dragElement(reportBox);
document.getElementById("closeReport").addEventListener("click", function(){
reportBox.remove();
});
});
// Converts the numerical value (out of 20) to its estimated text equivalent
function getEstimatedText(value) {
if (value >= 17) return "VERY HIGH";
if (value >= 14) return "HIGH";
if (value >= 12) return "GOOD";
if (value >= 10) return "AVERAGE";
if (value >= 8) return "LOW";
return "VERY LOW";
}
// Function that gives color based on the text
function getTextColor(text) {
if (text.includes("VERY HIGH") || text.includes("Excellent") || text.includes("Outstanding")) return "#a6f704"; // Green
if (text.includes("HIGH") || text.includes("Very Good")) return "#e1d919"; // Light Yellow
if (text.includes("GOOD") || text.includes("Normal")) return "yellow"; // Yellow
if (text.includes("AVERAGE") || text.includes("Normal")) return "orange";
if (text.includes("LOW") || text.includes("Bad") || text.includes("Terrible")) return "#ff4444"; // Red
return "#ccc";
}
function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById("reportHeader")) {
document.getElementById("reportHeader").onmousedown = dragMouseDown;
} else {
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
}
})();