FMP Player Analyzer v1.0

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.

当前为 2025-11-22 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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;
        }
    }

})();