Multi-language FMP position analyzer and player comparison tool with custom charts
目前為
// ==UserScript==
// @name FMP Position Analyzer and Comparator
// @namespace http://tampermonkey.net/
// @version 4.2
// @description Multi-language FMP position analyzer and player comparison tool with custom charts
// @author FMP Assistant
// @match https://footballmanagerproject.com/Team/Player*
// @icon https://www.google.com/s2/favicons?sz=64&domain=footballmanagerproject.com
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @license MIT
// ==/UserScript==
/*
* FMP Position Analyzer and Comparator v4.2
* Multi-language FMP position analyzer with custom chart visualization
* Automatically detects game language (EN/TR/DE/ES/FR/IT)
* Provides professional player analysis and comparison tools with custom SVG charts
*
* Features:
* - Auto-language detection
* - Position-based skill highlighting
* - Player saving system (up to 5 players)
* - Advanced comparison modal with custom charts
* - Radar charts for skill visualization using pure SVG
* - Draggable popup windows
*
* Supported positions: GK, DC, DL/DR, DMC, MC, ML/MR, AMC, AML/AMR, FC/ST
*
* Author: FMP Assistant
* License: MIT
*/
(function() {
'use strict';
// ===== MULTI-LANGUAGE SUPPORT =====
const translations = {
en: {
savePlayer: '💾 Save Player',
compareWindow: '📊 Comparison Window',
modalTitle: '👥 Player Comparison',
clearList: 'Clear List',
savedCount: 'Saved players',
noPlayers: 'No players saved. Go to player profile and click "Save".',
confirmClear: 'Delete all saved players?',
playerSaved: ' saved successfully!',
maxPlayers: 'Maximum 5 players. Please clear list first.',
unknownPlayer: 'Unknown Player',
unknownPosition: 'Unknown',
age: 'Age',
salary: 'Salary',
rating: 'Rating',
quality: 'Quality',
points: 'POINTS',
difference: 'DIFF',
feature: 'FEATURE',
tableView: 'Table View',
chartView: 'Chart View',
skillsChart: 'Skills Radar Chart',
physical: 'Physical',
technical: 'Technical',
mental: 'Mental',
attacking: 'Attacking',
defending: 'Defending',
overallRating: 'Overall Rating'
},
tr: {
savePlayer: '💾 Oyuncuyu Kaydet',
compareWindow: '📊 Karşılaştırma Penceresi',
modalTitle: '👥 Oyuncu Karşılaştırma',
clearList: 'Listeyi Temizle',
savedCount: 'Kayıtlı oyuncular',
noPlayers: 'Henüz oyuncu kaydedilmedi. Oyuncu profiline gidip "Kaydet" butonuna basın.',
confirmClear: 'Tüm kayıtlı oyuncular silinsin mi?',
playerSaved: ' başarıyla kaydedildi!',
maxPlayers: 'Maksimum 5 oyuncu. Lütfen önce listeyi temizleyin.',
unknownPlayer: 'Bilinmeyen Oyuncu',
unknownPosition: 'Bilinmiyor',
age: 'Yaş',
salary: 'Maaş',
rating: 'Derece',
quality: 'Kalite',
points: 'PUAN',
difference: 'FARK',
feature: 'ÖZELLİK',
tableView: 'Tablo Görünümü',
chartView: 'Grafik Görünümü',
skillsChart: 'Yetenek Radar Grafiği',
physical: 'Fiziksel',
technical: 'Teknik',
mental: 'Mental',
attacking: 'Hücum',
defending: 'Savunma',
overallRating: 'Genel Değerlendirme'
}
};
// Auto-detect game language
function detectGameLanguage() {
const htmlLang = document.documentElement.lang;
if (htmlLang && translations[htmlLang]) {
return htmlLang;
}
const bodyText = document.body.innerText;
if (bodyText.includes('Yaş') || bodyText.includes('Maaş')) return 'tr';
return 'en';
}
const currentLang = detectGameLanguage();
const t = translations[currentLang] || translations.en;
// ===== POSITION DEFINITIONS =====
const positionSkills = {
'KL': { primary: ['Poz', '1e1', 'ElK'], secondary: ['Ref', 'HH', 'Sçr', 'Zıp'] },
'GK': { primary: ['Poz', '1e1', 'ElK'], secondary: ['Ref', 'HH', 'Sçr', 'Zıp'] },
'DC': { primary: ['Mrkj', 'TpK', 'Poz'], secondary: ['Kaf', 'Day', 'Hız'] },
'DL': { primary: ['TpK', 'Ort', 'Poz'], secondary: ['Pas', 'Tek', 'Hız'] },
'DR': { primary: ['TpK', 'Ort', 'Poz'], secondary: ['Pas', 'Tek', 'Hız'] },
'DMC': { primary: ['Mrkj', 'TpK', 'Poz'], secondary: ['Kaf', 'Pas', 'Day'] },
'MC': { primary: ['Pas', 'Tek', 'Poz'], secondary: ['TpK', 'Kaf', 'Day'] },
'ML': { primary: ['Ort', 'Pas', 'Poz'], secondary: ['Tek', 'Kaf', 'Hız'] },
'MR': { primary: ['Ort', 'Pas', 'Poz'], secondary: ['Tek', 'Kaf', 'Hız'] },
'AMC': { primary: ['Pas', 'Bit', 'Tek'], secondary: ['Ort', 'Uza', 'Poz'] },
'AML': { primary: ['Ort', 'Pas', 'Poz'], secondary: ['Tek', 'Bit', 'Hız'] },
'AMR': { primary: ['Ort', 'Pas', 'Poz'], secondary: ['Tek', 'Bit', 'Hız'] },
'FC': { primary: ['Bit', 'Kaf'], secondary: ['Uza', 'Poz', 'Hız'] },
'ST': { primary: ['Bit', 'Kaf'], secondary: ['Uza', 'Poz', 'Hız'] }
};
// ===== SKILL CATEGORIES FOR CHARTS =====
const skillCategories = {
technical: ['Pas', 'Tek', 'Ort', 'Şut', 'Bit', 'Kaf', 'Uza'],
physical: ['Hız', 'Çab', 'Day', 'Kuv', 'Zıp'],
mental: ['Mrkj', 'Poz', 'TpK', 'Ces', 'HH', 'Tak', 'Kar'],
goalkeeping: ['Poz', '1e1', 'ElK', 'Ref', 'HH', 'Sçr', 'Zıp']
};
// ===== CUSTOM CHART SYSTEM =====
function createCustomRadarChart(players, containerId) {
const playerIds = Object.keys(players);
if (playerIds.length === 0) return '';
const keySkills = ['Pas', 'Tek', 'Bit', 'Kaf', 'Hız', 'Day', 'Mrkj', 'TpK', 'Ort', 'Ref'];
const centerX = 150, centerY = 150, radius = 120;
const angleStep = (2 * Math.PI) / keySkills.length;
let svgHTML = `<svg width="300" height="300" viewBox="0 0 300 300" class="fmp-radar-svg">`;
// Draw grid circles
for (let i = 1; i <= 5; i++) {
const circleRadius = radius * (i / 5);
svgHTML += `<circle cx="${centerX}" cy="${centerY}" r="${circleRadius}" fill="none" stroke="#e0e0e0" stroke-width="1"/>`;
}
// Draw axis lines and labels
keySkills.forEach((skill, index) => {
const angle = index * angleStep - Math.PI / 2;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
// Axis line
svgHTML += `<line x1="${centerX}" y1="${centerY}" x2="${x}" y2="${y}" stroke="#e0e0e0" stroke-width="1"/>`;
// Skill label
const labelX = centerX + (radius + 15) * Math.cos(angle);
const labelY = centerY + (radius + 15) * Math.sin(angle);
const textAnchor = Math.cos(angle) > 0.1 ? 'start' : Math.cos(angle) < -0.1 ? 'end' : 'middle';
svgHTML += `<text x="${labelX}" y="${labelY}" text-anchor="${textAnchor}" dominant-baseline="middle" font-size="10" fill="#333">${skill}</text>`;
});
// Draw player data
const colors = ['#ff4444', '#4444ff', '#44ff44', '#ffff44', '#ff44ff'];
playerIds.forEach((id, playerIndex) => {
const player = players[id];
const color = colors[playerIndex] || '#888888';
let points = [];
keySkills.forEach((skill, skillIndex) => {
const angle = skillIndex * angleStep - Math.PI / 2;
const value = player.skills[skill] || 0;
const scaledRadius = radius * (value / 100);
const x = centerX + scaledRadius * Math.cos(angle);
const y = centerY + scaledRadius * Math.sin(angle);
points.push(`${x},${y}`);
});
// Draw polygon
svgHTML += `<polygon points="${points.join(' ')}" fill="${color}30" stroke="${color}" stroke-width="2"/>`;
// Draw data points
points.forEach(point => {
const [x, y] = point.split(',').map(Number);
svgHTML += `<circle cx="${x}" cy="${y}" r="3" fill="${color}"/>`;
});
});
svgHTML += `</svg>`;
// Legend
let legendHTML = '<div class="fmp-radar-legend">';
playerIds.forEach((id, index) => {
const player = players[id];
const color = colors[index] || '#888888';
legendHTML += `<div class="fmp-legend-item">
<span class="fmp-legend-color" style="background-color: ${color}"></span>
<span class="fmp-legend-name">${player.name}</span>
</div>`;
});
legendHTML += '</div>';
return `<div class="fmp-radar-container">${svgHTML}${legendHTML}</div>`;
}
function createCustomBarChart(players, containerId) {
const playerIds = Object.keys(players);
if (playerIds.length === 0) return '';
const categories = [t.technical, t.physical, t.mental];
const categorySkills = {
[t.technical]: skillCategories.technical,
[t.physical]: skillCategories.physical,
[t.mental]: skillCategories.mental
};
const colors = ['#ff4444', '#4444ff', '#44ff44', '#ffff44', '#ff44ff'];
let html = '<div class="fmp-bar-chart">';
categories.forEach(category => {
html += `<div class="fmp-bar-category">
<div class="fmp-bar-label">${category}</div>
<div class="fmp-bars-container">`;
playerIds.forEach((id, index) => {
const player = players[id];
const skills = categorySkills[category];
const total = skills.reduce((sum, skill) => sum + (player.skills[skill] || 0), 0);
const average = skills.length > 0 ? total / skills.length : 0;
const color = colors[index] || '#888888';
html += `<div class="fmp-bar-wrapper">
<div class="fmp-bar" style="width: ${average}%; background-color: ${color};">
<span class="fmp-bar-value">${Math.round(average)}</span>
</div>
<span class="fmp-bar-player">${player.name}</span>
</div>`;
});
html += `</div></div>`;
});
html += '</div>';
return html;
}
// ===== STYLES =====
GM_addStyle(`
.fmp-primary-skill { background-color: #ffd700 !important; color: #000 !important; font-weight: bold !important; border-radius: 4px; }
.fmp-secondary-skill { background-color: #b0e0e6 !important; color: #000 !important; font-weight: bold !important; border-radius: 4px; }
#playerdata .skilltable th, #playerdata .skilltable td { padding: 2px 4px !important; }
/* Modal Styles */
.fmp-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
visibility: hidden;
opacity: 0;
transition: opacity 0.3s, visibility 0.3s;
}
.fmp-modal-overlay.active {
visibility: visible;
opacity: 1;
}
.fmp-modal {
background-color: white;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
width: 95%;
max-width: 1400px;
max-height: 90vh;
overflow: hidden;
display: flex;
flex-direction: column;
transform: translateY(-20px);
transition: transform 0.3s;
}
.fmp-modal.active {
transform: translateY(0);
}
.fmp-modal-header {
background: linear-gradient(to right, #007bff, #0056b3);
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
user-select: none;
}
.fmp-modal-title {
font-size: 18px;
font-weight: bold;
margin: 0;
}
.fmp-modal-close {
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
line-height: 1;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.2s;
}
.fmp-modal-close:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.fmp-modal-content {
padding: 20px;
overflow-y: auto;
flex-grow: 1;
}
/* Tabs */
.fmp-tabs {
display: flex;
margin-bottom: 15px;
border-bottom: 1px solid #ddd;
}
.fmp-tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
border-radius: 5px 5px 0 0;
margin-right: 5px;
background: #f8f9fa;
transition: all 0.3s;
}
.fmp-tab.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.fmp-tab-content {
display: none;
}
.fmp-tab-content.active {
display: block;
}
/* Comparison Table */
#fmp-compare-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 0.95em;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
#fmp-compare-table th, #fmp-compare-table td {
border: 1px solid #b0c4de;
padding: 8px;
text-align: center;
}
#fmp-compare-table th {
background-color: #d8e6f7;
font-weight: bold;
text-transform: uppercase;
}
.fmp-diff-positive { color: green; font-weight: bold; background-color: #e6ffe6; }
.fmp-diff-negative { color: red; font-weight: bold; background-color: #ffe6e6; }
.fmp-clear-btn {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
margin-left: 10px;
font-size: 0.9em;
transition: background-color 0.2s;
}
.fmp-clear-btn:hover {
background-color: #c82333;
}
/* Custom Radar Chart */
.fmp-radar-container {
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 0;
}
.fmp-radar-svg {
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #fafafa;
}
.fmp-radar-legend {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
margin-top: 15px;
}
.fmp-legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.fmp-legend-color {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
.fmp-legend-name {
font-size: 12px;
font-weight: 500;
}
/* Custom Bar Chart */
.fmp-bar-chart {
margin: 20px 0;
}
.fmp-bar-category {
margin-bottom: 20px;
padding: 10px;
background: #f8f9fa;
border-radius: 5px;
}
.fmp-bar-label {
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.fmp-bars-container {
display: flex;
flex-direction: column;
gap: 8px;
}
.fmp-bar-wrapper {
display: flex;
align-items: center;
gap: 10px;
}
.fmp-bar {
height: 25px;
min-width: 30px;
border-radius: 3px;
position: relative;
transition: width 0.3s ease;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.fmp-bar-value {
color: white;
font-weight: bold;
font-size: 12px;
text-shadow: 1px 1px 1px rgba(0,0,0,0.3);
}
.fmp-bar-player {
min-width: 120px;
font-size: 12px;
font-weight: 500;
}
/* Chart Container */
.fmp-chart-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}
.fmp-chart-wrapper {
flex: 1;
min-width: 300px;
background: white;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
border: 1px solid #e0e0e0;
}
.fmp-chart-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
text-align: center;
color: #333;
}
/* Player Cards */
.fmp-player-cards {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
}
.fmp-player-card {
flex: 1;
min-width: 200px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.fmp-player-card h4 {
margin: 0 0 10px 0;
font-size: 16px;
}
.fmp-player-card p {
margin: 5px 0;
font-size: 14px;
}
/* Buttons */
#fmp-save-player-btn, #fmp-open-modal-btn {
color: white;
border: none;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
font-size: 14px;
margin-left: 10px;
white-space: nowrap;
transition: background-color 0.2s;
}
#fmp-save-player-btn {
background-color: #007bff;
}
#fmp-save-player-btn:hover {
background-color: #0056b3;
}
#fmp-open-modal-btn {
background-color: #28a745;
}
#fmp-open-modal-btn:hover {
background-color: #218838;
}
`);
// ===== CORE FUNCTIONS =====
function getPlayerMainPosition() {
const posElement = document.querySelector('.pitch-position');
if (posElement) return posElement.textContent.trim();
const positionElementB = document.querySelector('#playerdata .playerpos b');
if (positionElementB) {
const text = positionElementB.textContent.trim();
return text.split(' ')[0].replace(/[^a-zA-Z]/g, '').toUpperCase();
}
return t.unknownPosition;
}
function highlightSkills() {
const mainPosition = getPlayerMainPosition();
if (!positionSkills[mainPosition]) return;
const config = positionSkills[mainPosition];
const skillTable = document.querySelector('#playerdata .skilltable');
if (!skillTable) return;
const headers = skillTable.querySelectorAll('th');
headers.forEach((th, index) => {
const skillName = th.textContent.trim();
const isPrimary = config.primary.includes(skillName);
const isSecondary = config.secondary.includes(skillName);
if (isPrimary || isSecondary) {
if (isPrimary) th.classList.add('fmp-primary-skill');
if (isSecondary) th.classList.add('fmp-secondary-skill');
const parentRow = th.parentElement;
const valueRow = parentRow.nextElementSibling;
if (valueRow && valueRow.children[index]) {
const valueCell = valueRow.children[index];
const numSpan = valueCell.querySelector('.num');
const styleClass = isPrimary ? 'fmp-primary-skill' : 'fmp-secondary-skill';
if (numSpan) numSpan.classList.add(styleClass);
else valueCell.classList.add(styleClass);
}
}
});
}
function extractPlayerName() {
let nameElement = $('.lheader h3');
if (nameElement.length === 0) nameElement = $('h1');
if (nameElement.length === 0) nameElement = $('.lheader').find(':header').first();
if (nameElement.length > 0) {
let rawText = nameElement.contents().filter(function() {
return this.nodeType === 3;
}).text().trim();
if (!rawText) rawText = nameElement.text().trim();
let cleanName = rawText.replace(/^\d+\.\s*/, '').trim();
if (cleanName) return cleanName;
}
return t.unknownPlayer;
}
async function extractPlayerData() {
const player = {};
const urlParams = new URLSearchParams(window.location.search);
player.id = urlParams.get('id');
player.name = extractPlayerName();
player.position = getPlayerMainPosition();
if (!player.id) return null;
// Extract skills from table
player.skills = {};
const skillTable = $('#playerdata .skilltable');
const headers = skillTable.find('th');
const values = skillTable.find('td');
headers.each((index, th) => {
const skillName = $(th).text().trim();
const skillValueText = $(values[index]).text().trim();
const skillValue = parseInt(skillValueText.match(/(\d+)/)?.[0], 10) || 0;
if(skillName) {
player.skills[skillName] = skillValue;
}
});
// Extract additional data from JSON
try {
const response = await fetch(`/Team/Player?handler=PlayerData&playerId=${player.id}`);
if (response.ok) {
const json = await response.json();
if (json && json.player) {
if (json.player.age) player.age = `${json.player.age.years} ${t.age} ${json.player.age.months}M`;
if (json.player.wage) player.salary = json.player.wage.toLocaleString();
if (json.player.rating) {
player.rating = json.player.rating;
player.lastRating = json.player.rating;
}
if (json.player.qi) player.qi = json.player.qi;
}
}
} catch (e) {
console.error("JSON data fetch failed:", e);
}
// Fallback data extraction
const infoText = $('.infotable').text();
if (!player.age) {
const ageMatch = infoText.match(/(Yaş|Age|Alter|Edad|Âge|Età)\s*(\d+)[.,](\d+)/);
player.age = ageMatch ? `${ageMatch[2]} ${t.age} ${ageMatch[3]}M` : t.unknownPosition;
}
if (!player.salary || player.salary === t.unknownPosition) {
const wageMatch = infoText.match(/(Maaş|Wage|Gehalt|Salario|Salaire|Stipendio)\s*ⓕ\s*([\d,.]+)/);
player.salary = wageMatch ? wageMatch[2] : t.unknownPosition;
}
player.value = t.unknownPosition;
player.timestamp = new Date().toLocaleString();
player.lang = currentLang;
return player;
}
async function savePlayer() {
const player = await extractPlayerData();
if (!player) {
alert('Player data could not be fetched. Please refresh the page.');
return;
}
let savedPlayers = await GM_getValue('fmp_saved_players', {});
if (Object.keys(savedPlayers).length >= 5 && !savedPlayers[player.id]) {
alert(t.maxPlayers);
return;
}
savedPlayers[player.id] = player;
await GM_setValue('fmp_saved_players', savedPlayers);
alert(player.name + t.playerSaved);
await updateCompareModal();
}
// ===== MODAL FUNCTIONS =====
function createModal() {
if ($('#fmp-modal-overlay').length) return;
const modalHTML = `
<div class="fmp-modal-overlay" id="fmp-modal-overlay">
<div class="fmp-modal" id="fmp-modal">
<div class="fmp-modal-header" id="fmp-modal-header">
<h3 class="fmp-modal-title">${t.modalTitle}</h3>
<button class="fmp-modal-close" id="fmp-modal-close">×</button>
</div>
<div class="fmp-modal-content" id="fmp-modal-content">
<div class="fmp-tabs">
<div class="fmp-tab active" data-tab="table">${t.tableView}</div>
<div class="fmp-tab" data-tab="chart">${t.chartView}</div>
</div>
<div id="fmp-tab-table" class="fmp-tab-content active">
<p>${t.noPlayers}</p>
</div>
<div id="fmp-tab-chart" class="fmp-tab-content">
<p>${t.noPlayers}</p>
</div>
</div>
</div>
</div>
`;
$('body').append(modalHTML);
// Close modal functionality
$('#fmp-modal-close, #fmp-modal-overlay').on('click', function(e) {
if (e.target === this) {
$('#fmp-modal-overlay').removeClass('active');
$('#fmp-modal').removeClass('active');
}
});
// Tab functionality
$('.fmp-tab').on('click', function() {
const tabId = $(this).data('tab');
$('.fmp-tab').removeClass('active');
$('.fmp-tab-content').removeClass('active');
$(this).addClass('active');
$(`#fmp-tab-${tabId}`).addClass('active');
});
makeModalDraggable();
}
function makeModalDraggable() {
const modal = document.getElementById('fmp-modal');
const header = document.getElementById('fmp-modal-header');
let isDragging = false;
let currentX, currentY, initialX, initialY, xOffset = 0, yOffset = 0;
header.addEventListener("mousedown", dragStart);
document.addEventListener("mousemove", drag);
document.addEventListener("mouseup", dragEnd);
function dragStart(e) {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.target === header || header.contains(e.target)) {
isDragging = true;
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, modal);
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
isDragging = false;
}
}
async function updateCompareModal() {
const players = await GM_getValue('fmp_saved_players', {});
const playerIds = Object.keys(players);
let tableHTML = '';
let chartHTML = '';
if (playerIds.length === 0) {
tableHTML = `<p>${t.noPlayers}</p>`;
chartHTML = `<p>${t.noPlayers}</p>`;
} else {
tableHTML = `
<div style="margin-bottom: 15px;">
<button class="fmp-clear-btn" id="fmp-clear-btn">${t.clearList}</button>
<span style="margin-left: 10px; font-size: 0.9em; color: #666;">
${t.savedCount}: ${playerIds.length}/5
</span>
</div>
${createCompareTable(players)}
`;
chartHTML = `
<div style="margin-bottom: 15px;">
<button class="fmp-clear-btn" id="fmp-clear-btn-chart">${t.clearList}</button>
<span style="margin-left: 10px; font-size: 0.9em; color: #666;">
${t.savedCount}: ${playerIds.length}/5
</span>
</div>
${createPlayerCards(players)}
<div class="fmp-chart-container">
<div class="fmp-chart-wrapper">
<div class="fmp-chart-title">${t.skillsChart}</div>
${createCustomRadarChart(players, 'fmp-radar-chart')}
</div>
<div class="fmp-chart-wrapper">
<div class="fmp-chart-title">Skill Categories</div>
${createCustomBarChart(players, 'fmp-bar-chart')}
</div>
</div>
`;
}
$('#fmp-tab-table').html(tableHTML);
$('#fmp-tab-chart').html(chartHTML);
$('#fmp-clear-btn, #fmp-clear-btn-chart').on('click', async () => {
if (confirm(t.confirmClear)) {
await GM_setValue('fmp_saved_players', {});
await updateCompareModal();
}
});
}
function createPlayerCards(players) {
const playerIds = Object.keys(players);
if (playerIds.length === 0) return '';
let html = '<div class="fmp-player-cards">';
playerIds.forEach((id, index) => {
const player = players[id];
const colors = ['#667eea', '#764ba2', '#f093fb', '#4facfe', '#43e97b'];
html += `
<div class="fmp-player-card" style="background: linear-gradient(135deg, ${colors[index] || '#667eea'} 0%, ${darkenColor(colors[index] || '#667eea', 20)} 100%);">
<h4>${player.name}</h4>
<p><strong>${t.quality}:</strong> ${player.rating || 'N/A'}</p>
<p><strong>${t.position}:</strong> ${player.position}</p>
<p><strong>${t.age}:</strong> ${player.age || 'N/A'}</p>
</div>
`;
});
html += '</div>';
return html;
}
function darkenColor(color, percent) {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) - amt;
const G = (num >> 8 & 0x00FF) - amt;
const B = (num & 0x0000FF) - amt;
return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
}
function openModal() {
$('#fmp-modal-overlay').addClass('active');
$('#fmp-modal').addClass('active');
}
function createOpenModalButton() {
if ($('#fmp-open-modal-btn').length) return;
const $headerCell = $('.lheader h3').parent();
if ($headerCell.length) {
const $btn = $(`<button id="fmp-open-modal-btn">${t.compareWindow}</button>`);
$btn.on('click', openModal);
$('#fmp-save-player-btn').after($btn);
}
}
function createCompareTable(players) {
const playerIds = Object.keys(players);
const allSkills = new Set();
playerIds.forEach(id => {
if(players[id].skills) {
Object.keys(players[id].skills).forEach(skill => allSkills.add(skill));
}
});
const sortedSkills = Array.from(allSkills).sort();
let html = '<table id="fmp-compare-table"><thead><tr>';
html += `<th style="width:10%">${t.feature}</th>`;
playerIds.forEach(id => {
const displayName = (players[id].name && players[id].name !== t.unknownPlayer) ? players[id].name : `Player ${players[id].id}`;
html += `<th colspan="2" style="background-color:#2c5a8a; color:white;">${displayName.toUpperCase()} <br><small>(${players[id].position})</small></th>`;
});
html += '</tr><tr><th> </th>';
playerIds.forEach(() => { html += `<th>${t.points}</th><th>${t.difference}</th>`; });
html += '</tr></thead><tbody>';
const infoKeys = [
{ label: t.age, key: 'age' },
{ label: t.salary, key: 'salary' },
{ label: `${t.quality} (${t.rating})`, key: 'rating' },
{ label: 'QI', key: 'qi' }
];
infoKeys.forEach(info => {
html += `<tr class="fmp-key-info-row"><td><b>${info.label}</b></td>`;
playerIds.forEach(id => {
let val = players[id][info.key] || '-';
html += `<td colspan="2" style="font-weight:bold;">${val}</td>`;
});
html += '</tr>';
});
sortedSkills.forEach(skill => {
html += `<tr><td style="text-align:left;font-weight:bold;">${skill}</td>`;
const refVal = players[playerIds[0]].skills[skill] || 0;
playerIds.forEach((id, idx) => {
const val = players[id].skills[skill] || 0;
let diffHtml = '';
if (idx > 0) {
const diff = val - refVal;
if (diff > 0) diffHtml = `<span class="fmp-diff-positive">+${diff}</span>`;
else if (diff < 0) diffHtml = `<span class="fmp-diff-negative">${diff}</span>`;
else diffHtml = '<span style="color:gray">-</span>';
}
html += `<td>${val}</td><td>${diffHtml}</td>`;
});
html += '</tr>';
});
html += '</tbody></table>';
return html;
}
function createSaveButton() {
if ($('#fmp-save-player-btn').length) return;
const $headerCell = $('.lheader h3').parent();
if ($headerCell.length) {
const $btn = $(`<button id="fmp-save-player-btn">${t.savePlayer}</button>`);
$btn.on('click', savePlayer);
$('.lheader h3').after($btn);
}
}
// ===== INITIALIZATION =====
setTimeout(async () => {
createSaveButton();
createModal();
createOpenModalButton();
highlightSkills();
await updateCompareModal();
}, 1000);
})();