// ==UserScript==
// @name FMP Yetenek Analizcisi (Geliştirilmiş)
// @namespace kalenderadam
// @version 1.4
// @description FMP oyuncu sayfasını tam analiz eder: Birden çok mevki için puanlama, gizli yeteneklere göre revize edilmiş potansiyel tahmini ve antrenman tavsiyesi sunar. Yıldız derecelendirmesi ve hata bildirim özelliği içerir.
// @description:tr FMP oyuncu sayfasını tam analiz eder: Birden çok mevki için puanlama, gizli yeteneklere göre revize edilmiş potansiyel tahmini ve antrenman tavsiyesi sunar. Yıldız derecelendirmesi ve hata bildirim özelliği içerir.
// @author Kalenderadam (ve AI Asistanı)
// @match https://*.footballmanagerproject.com/Team/Player?id=*
// @icon https://www.google.com/s2/favicons?sz=64&domain=footballmanagerproject.com
// @grant none
// @license MIT
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// --- AYARLAR ---
const YOUR_FMP_USER_ID = "16531";
const SCRIPT_VERSION = "1.4"; // Merkezi sürüm yönetimi
const MAX_SCORE = 25.0; // Maksimum puan
const skillWeights = {
DC: { "Markaj": 1.0, "Top Kapma": 1.0, "Kafa": 1.0, "Pozisyon Alma": 0.9, "Dayanıklılık": 0.8, "Uzaktan Şut": 0.6, "Teknik": 0.5, "Pas": 0.5, "Hız": 0.5, "Orta": 0.3, "Bitiricilik": 0.2 },
DL: { "Top Kapma": 1.0, "Orta": 1.0, "Dayanıklılık": 0.8, "Markaj": 0.6, "Teknik": 0.6, "Pas": 0.6, "Pozisyon Alma": 0.6, "Uzaktan Şut": 0.6, "Kafa": 0.6, "Hız": 0.5, "Bitiricilik": 0.4 },
DR: { "Top Kapma": 1.0, "Orta": 1.0, "Dayanıklılık": 0.8, "Markaj": 0.6, "Teknik": 0.6, "Pas": 0.6, "Pozisyon Alma": 0.6, "Uzaktan Şut": 0.6, "Kafa": 0.6, "Hız": 0.5, "Bitiricilik": 0.4 },
DMC: { "Top Kapma": 0.9, "Kafa": 0.9, "Pozisyon Alma": 0.8, "Dayanıklılık": 0.8, "Markaj": 0.7, "Pas": 0.7, "Uzaktan Şut": 0.7, "Teknik": 0.6, "Hız": 0.5, "Bitiricilik": 0.4, "Orta": 0.3 },
DML: { "Top Kapma": 1.0, "Orta": 1.0, "Dayanıklılık": 0.8, "Pas": 0.7, "Teknik": 0.6, "Pozisyon Alma": 0.6, "Uzaktan Şut": 0.6, "Kafa": 0.6, "Hız": 0.5, "Markaj": 0.5, "Bitiricilik": 0.4 },
DMR: { "Top Kapma": 1.0, "Orta": 1.0, "Dayanıklılık": 0.8, "Pas": 0.7, "Teknik": 0.6, "Pozisyon Alma": 0.6, "Uzaktan Şut": 0.6, "Kafa": 0.6, "Hız": 0.5, "Markaj": 0.5, "Bitiricilik": 0.4 },
MC: { "Pas": 1.0, "Teknik": 0.8, "Pozisyon Alma": 0.8, "Dayanıklılık": 0.8, "Top Kapma": 0.7, "Uzaktan Şut": 0.7, "Kafa": 0.6, "Markaj": 0.5, "Hız": 0.5, "Bitiricilik": 0.5, "Orta": 0.4 },
ML: { "Orta": 1.0, "Pas": 0.8, "Dayanıklılık": 0.8, "Pozisyon Alma": 0.7, "Uzaktan Şut": 0.7, "Teknik": 0.6, "Top Kapma": 0.6, "Kafa": 0.6, "Bitiricilik": 0.6, "Hız": 0.5, "Markaj": 0.4 },
MR: { "Orta": 1.0, "Pas": 0.8, "Dayanıklılık": 0.8, "Pozisyon Alma": 0.7, "Uzaktan Şut": 0.7, "Teknik": 0.6, "Top Kapma": 0.6, "Kafa": 0.6, "Bitiricilik": 0.6, "Hız": 0.5, "Markaj": 0.4 },
AMC: { "Pas": 1.0, "Uzaktan Şut": 1.0, "Bitiricilik": 0.9, "Dayanıklılık": 0.8, "Teknik": 0.7, "Pozisyon Alma": 0.7, "Kafa": 0.7, "Hız": 0.5, "Orta": 0.4, "Markaj": 0.3, "Top Kapma": 0.3 },
AML: { "Orta": 1.0, "Dayanıklılık": 0.8, "Pas": 0.8, "Pozisyon Alma": 0.8, "Teknik": 0.7, "Uzaktan Şut": 0.7, "Bitiricilik": 0.6, "Kafa": 0.6, "Top Kapma": 0.5, "Hız": 0.5, "Markaj": 0.3 },
AMR: { "Orta": 1.0, "Dayanıklılık": 0.8, "Pas": 0.8, "Pozisyon Alma": 0.8, "Teknik": 0.7, "Uzaktan Şut": 0.7, "Bitiricilik": 0.6, "Kafa": 0.6, "Top Kapma": 0.5, "Hız": 0.5, "Markaj": 0.3 },
FC: { "Bitiricilik": 1.0, "Kafa": 1.0, "Uzaktan Şut": 0.8, "Dayanıklılık": 0.8, "Teknik": 0.7, "Pas": 0.7, "Pozisyon Alma": 0.7, "Top Kapma": 0.5, "Hız": 0.5, "Orta": 0.4, "Markaj": 0.2 },
GK: { "Elle Kontrol": 1.0, "Refleks": 1.0, "Bire Bir": 0.8, "Pozisyon Alma": 0.7, "Hava Hakimiyeti": 0.6, "Zıplama": 0.6, "Hız": 0.6, "Sıçrama": 0.5, "Degaj": 0.5, "Elle Pas": 0.5, "Dayanıklılık": 0.5 }
};
const skillAbbrToFullName = {
Tec: 'Tecrübe', Day: 'Dayanıklılık', Hız: 'Hız', Mrkj: 'Markaj', TpK: 'Top Kapma', Poz: 'Pozisyon Alma',
Pas: 'Pas', Ort: 'Orta', Tek: 'Teknik', Kaf: 'Kafa', Bit: 'Bitiricilik', Uza: 'Uzaktan Şut',
ElK: 'Elle Kontrol', '1e1': 'Bire Bir', Ref: 'Refleks', HH: 'Hava Hakimiyeti', Sçr: 'Sıçrama',
Deg: 'Degaj', Zıp: 'Zıplama', ElP: 'Elle Pas'
};
const positionMap = {
'KL': 'GK', 'SĞB': 'DR', 'SLB': 'DL', 'STP': 'DC', 'OS': 'DMC', 'SĞO': 'MR', 'SLO': 'ML',
'MO': 'MC', 'OOS': 'AMC', 'SĞF': 'AMR', 'SLF': 'AML', 'F': 'FC'
};
function safeQuerySelector(selector) { return document.querySelector(selector); }
function safeQuerySelectorAll(selector) { return Array.from(document.querySelectorAll(selector)); }
function scoreToStars(score) {
const fullStar = '★'; const halfStar = '½'; const emptyStar = '☆';
let stars = score / 5;
let fullStars = Math.floor(stars);
let halfStars = (stars - fullStars >= 0.5) ? 1 : 0;
let emptyStars = 5 - fullStars - halfStars;
return fullStar.repeat(fullStars) + halfStar.repeat(halfStars) + emptyStar.repeat(emptyStars);
}
// --- GÜÇLENDİRİLMİŞ YETENEK OKUMA FONKSİYONU ---
function getSkillsFromPage() {
const skills = {};
const skillTable = safeQuerySelector('.skilltable');
if (!skillTable) return skills;
// Yetenek kısaltmalarını ve değerlerini içeren tüm hücreleri bulun
const cells = safeQuerySelectorAll('.skilltable td:not(:first-child)');
cells.forEach(cell => {
const abbrEl = cell.querySelector('th'); // Yetenek kısaltması
const valueEl = cell.querySelector('span.num'); // Yetenek değeri
if (abbrEl && valueEl) {
const abbr = abbrEl.textContent.trim();
const fullName = skillAbbrToFullName[abbr];
const value = parseInt(valueEl.textContent, 10);
if (fullName && !isNaN(value)) {
skills[fullName] = value;
}
}
});
if (Object.keys(skills).length === 0) {
console.warn("Yetenekler '.skilltable td:not(:first-child)' ile okunamadı. Alternatif yöntem denenecek.");
// Alternatif, daha genel bir arama (eski betikteki gibi)
const allHeaders = safeQuerySelectorAll('.skilltable th');
const allValues = safeQuerySelectorAll('.skilltable td > span.num');
allHeaders.forEach((th, index) => {
const abbr = th.textContent.trim();
const fullName = skillAbbrToFullName[abbr];
if (fullName && allValues[index]) {
skills[fullName] = parseInt(allValues[index].textContent, 10);
}
});
}
return skills;
}
// --- MEVKİ PUANINI HESAPLAYAN YARDIMCI FONKSİYON ---
function calculatePositionalScore(position, skills) {
const weights = skillWeights[position];
if (!weights) return { score25: 0, skillDetails: [] };
let weightedSum = 0, totalWeight = 0;
const skillDetails = [];
for (const skillName in weights) {
if (skills.hasOwnProperty(skillName)) {
weightedSum += skills[skillName] * weights[skillName];
totalWeight += weights[skillName];
skillDetails.push({ name: skillName, value: skills[skillName], weight: weights[skillName] });
}
}
if (totalWeight === 0) return { score25: 0, skillDetails: [] };
const positionalScore25 = (weightedSum / totalWeight / 20) * MAX_SCORE;
return { score25: positionalScore25, skillDetails };
}
function analyzePlayer() {
try {
const ageEl = safeQuerySelector('.infotable .age_year');
const positionEls = safeQuerySelectorAll('.infotable .pitch-position'); // Tüm pozisyonları çek
if (!positionEls || positionEls.length === 0 || !ageEl) throw new Error("Temel oyuncu bilgileri (yaş/pozisyon) bulunamadı.");
const age = parseInt(ageEl.textContent.trim(), 10);
const skills = getSkillsFromPage();
if (Object.keys(skills).length === 0) throw new Error("Oyuncu yetenekleri okunamadı.");
// 1. Pozisyon ve Puanlama Analizi (Çoklu Mevki Desteği)
const allPositions = positionEls.map(el => positionMap[el.textContent.trim()] || el.textContent.trim().replace('KL', 'GK'));
const uniquePositions = [...new Set(allPositions)].filter(p => skillWeights[p]); // Tekil ve ağırlığı olan mevkiler
if (uniquePositions.length === 0) throw new Error("Tanımlı ağırlık formülü olan mevki bulunamadı.");
const positionalScores = uniquePositions.map(pos => {
const result = calculatePositionalScore(pos, skills);
return { position: pos, ...result };
}).sort((a, b) => b.score25 - a.score25);
const primaryPositionData = positionalScores[0];
const primaryPosition = primaryPositionData.position;
const positionalScore25 = primaryPositionData.score25;
// 2. Gizli Yetenekler ve Antrenman Analizi
let hiddenTalents = { professionalism: null, fitness: null };
let trainingAnalysis = { text: "Antrenman bilgisi bulunamadı.", color: "#ccc" };
const reportContainer = safeQuerySelector('#ScoutInfo');
if (reportContainer) {
const hiddenTalentContainer = Array.from(reportContainer.querySelectorAll('.skilldev')).pop();
if (hiddenTalentContainer) {
hiddenTalentContainer.querySelectorAll('div > span').forEach(span => {
const match = span.textContent.match(/\((\d)\/8\)/);
if (match && match[1]) {
if (span.textContent.includes('Profesyonellik')) hiddenTalents.professionalism = parseInt(match[1]);
else if (span.textContent.includes('Fitness')) hiddenTalents.fitness = parseInt(match[1]);
}
});
}
}
const trainingElement = safeQuerySelector('#TrainingBoard table tr:nth-child(2)');
if (trainingElement?.cells?.[0]) {
const trainingText = trainingElement.cells[0].textContent;
const importantSkills = primaryPositionData.skillDetails.sort((a, b) => b.weight - a.weight).slice(0, 3).map(s => s.name);
let matchCount = importantSkills.filter(skill => trainingText.toLowerCase().includes(skill.toLowerCase().substring(0, 3))).length;
trainingAnalysis = matchCount >= 2 ? { text: "Doğru antrenmanı alıyor.", color: "#a6f704" } : { text: "Mevkisi için daha uygun bir antrenman seçilebilir.", color: "#ff7422" };
}
// 3. Potansiyel Tahmini (Gizli Yetenek Revizyonu)
let potentialEstimate25, potentialSource = "";
const latestReportElement = safeQuerySelector('#ScoutInfo .rec');
if (latestReportElement) {
potentialEstimate25 = parseFloat(latestReportElement.textContent);
potentialSource = "Gözlemci Raporu";
if (hiddenTalents.professionalism !== null) {
const professionalismBonus = (hiddenTalents.professionalism - 4) * 0.3;
const fitnessBonus = (hiddenTalents.fitness !== null) ? (hiddenTalents.fitness - 4) * 0.2 : 0;
// Potansiyeli 0 ile MAX_SCORE (25.0) arasında tut
potentialEstimate25 = Math.min(Math.max(potentialEstimate25 + professionalismBonus + fitnessBonus, 0), MAX_SCORE);
potentialSource = "Revize Edilmiş Rapor";
}
} else {
// Yaş çarpanları daha kademeli hale getirilebilir. Şimdilik mevcut mantık korundu.
let ageMultiplier = age < 18 ? 1.35 : age < 21 ? 1.25 : age < 24 ? 1.15 : age < 28 ? 1.05 : 1.0;
potentialEstimate25 = Math.min(positionalScore25 * ageMultiplier, MAX_SCORE);
potentialSource = "Yaş Tahmini";
}
displayReport({ primaryPosition, positionalScore25, potentialEstimate25, age, primarySkillDetails: primaryPositionData.skillDetails, potentialSource, trainingAnalysis, hiddenTalents, positionalScores });
} catch (error) {
console.error("FMP Analiz Betiği Hatası:", error);
handleError(error);
}
}
// --- HATA VE BİLDİRİM FONKSİYONLARI ---
function createReportLink(error) {
const url = new URL(window.location.href);
const playerId = url.searchParams.get('id');
const subject = encodeURIComponent(`Yetenek Analizcisi Hata Raporu (Oyuncu ID: ${playerId})`);
const body = encodeURIComponent(
`Merhaba,\n\nFMP Yetenek Analizcisi betiği bir hata ile karşılaştı. Hata detayları aşağıdadır:\n\n` +
`Hata Mesajı: ${error.message}\n` +
`Oyuncu Sayfası: ${window.location.href}\n` +
`Betiğin Sürümü: ${SCRIPT_VERSION}\n\n` + // Merkezi sürüm kullanıldı
`Stack Trace:\n${error.stack}`
);
const mailUrl = `/Team/Mail?mode=New&recipientId=${YOUR_FMP_USER_ID}&subj=${subject}&body=${body}`;
const reportLink = document.createElement('a');
reportLink.href = mailUrl;
reportLink.textContent = "Hata Raporu Gönder";
reportLink.target = "_blank";
reportLink.style.cssText = `display: block; text-align: center; margin-top: 10px; padding: 8px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px;`;
return reportLink;
}
function handleError(error) {
let oldReport = document.getElementById('aiScoutReport');
if (oldReport) oldReport.remove();
// Stiller doğrudan JS yerine burada tanımlandı
const errorPanel = document.createElement('div');
errorPanel.id = 'aiScoutReport';
errorPanel.style.cssText = `position: fixed; top: 100px; right: 20px; width: 300px; background-color: #4d2121; border: 2px solid #dc3545; border-radius: 8px; padding: 15px; z-index: 10000; color: #fff; font-family: 'Roboto', sans-serif; box-shadow: 0 0 15px rgba(0,0,0,0.5);`;
const closeButton = document.createElement('button');
closeButton.innerHTML = '×';
closeButton.style.cssText = `position: absolute; top: 5px; right: 10px; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer;`;
closeButton.onclick = () => errorPanel.remove();
const title = document.createElement('h3');
title.textContent = 'Analiz Hatası!';
title.style.cssText = `color: #ffc107; margin: 0 0 10px 0; border-bottom: 1px solid #dc3545; padding-bottom: 5px;`;
const errorMessage = document.createElement('p');
errorMessage.textContent = `Hata Mesajı: ${error.message}`;
errorMessage.style.margin = '10px 0';
const reportLink = createReportLink(error);
const linkParagraph = document.createElement('p');
linkParagraph.textContent = 'Bu hatayı geliştiriciye bildirmek için aşağıdaki linke tıklayın.';
linkParagraph.style.marginTop = '15px';
errorPanel.appendChild(closeButton);
errorPanel.appendChild(title);
errorPanel.appendChild(errorMessage);
errorPanel.appendChild(linkParagraph);
errorPanel.appendChild(reportLink);
document.body.appendChild(errorPanel);
}
// --- RAPOR GÖSTERİMİ ---
function displayReport({ primaryPosition, positionalScore25, potentialEstimate25, potentialSource, trainingAnalysis, hiddenTalents, positionalScores, primarySkillDetails }) {
let oldReport = document.getElementById('aiScoutReport');
if (oldReport) oldReport.remove();
// Rapor Paneli Oluşturma
const reportPanel = document.createElement('div');
reportPanel.id = 'aiScoutReport';
reportPanel.style.cssText = `position: fixed; top: 100px; right: 20px; width: 300px; background-color: #2a3128; border: 2px solid #53a12c; border-radius: 8px; padding: 15px; z-index: 10000; color: #fff; font-family: 'Roboto', sans-serif; box-shadow: 0 0 15px rgba(0,0,0,0.5); font-size: 0.95em;`;
const closeButton = document.createElement('button');
closeButton.innerHTML = '×';
closeButton.style.cssText = `position: absolute; top: 5px; right: 10px; background: none; border: none; color: #fff; font-size: 24px; cursor: pointer;`;
closeButton.onclick = () => reportPanel.remove();
const title = document.createElement('h3');
title.textContent = `AI Gözlemci Raporu (v${SCRIPT_VERSION})`;
title.style.cssText = `color: #a6f704; margin: 0 0 10px 0; border-bottom: 1px solid #53a12c; padding-bottom: 5px;`;
// 1. Puanlar ve Potansiyel
const scoresDiv = document.createElement('div');
scoresDiv.innerHTML = `
<p style="margin: 5px 0; display: flex; justify-content: space-between; align-items: center;">
<strong>Ana Mevki Puanı (${primaryPosition.replace('GK', 'KL')}):</strong>
<span style="color: #a6f704; font-size: 1.2em;">
${scoreToStars(positionalScore25)} (${positionalScore25.toFixed(1)})
</span>
</p>
<p style="margin: 5px 0; display: flex; justify-content: space-between; align-items: center;">
<strong>Potansiyel Tahmini (${potentialSource}):</strong>
<span style="color: #e1d919; font-size: 1.2em;">
${scoreToStars(potentialEstimate25)} (${potentialEstimate25.toFixed(1)})
</span>
</p>
`;
// 2. Çoklu Mevki Puanları
const otherPositionsHtml = positionalScores.slice(1).map(item => {
const posName = item.position.replace('GK', 'KL');
return `<p style="margin: 3px 0; font-size: 0.9em; display: flex; justify-content: space-between;">
${posName}: <span>${item.score25.toFixed(1)}</span>
</p>`;
}).join('');
const multiPositionDiv = document.createElement('div');
if (otherPositionsHtml) {
multiPositionDiv.innerHTML = `
<h4 style="margin-top: 10px; color: #ccc; border-bottom: 1px dashed #444; padding-bottom: 3px; font-size: 1em;">Diğer Mevki Skorları:</h4>
${otherPositionsHtml}
`;
}
// 3. Yetenek Detayları
primarySkillDetails.sort((a, b) => b.weight - a.weight);
const keySkills = primarySkillDetails.slice(0, 3).map(s => `<li style="color: #a6f704;">${s.name} (${s.value}) <span style="font-size: 0.8em; color: #ccc;">(x${s.weight})</span></li>`).join('');
const weakSkills = primarySkillDetails.slice(-3).map(s => `<li style="color: #ff7422;">${s.name} (${s.value}) <span style="font-size: 0.8em; color: #ccc;">(x${s.weight})</span></li>`).join('');
const detailsDiv = document.createElement('div');
detailsDiv.innerHTML = `
<h4 style="margin-top: 15px; color: #fff; border-bottom: 1px solid #53a12c; padding-bottom: 3px;">${primaryPosition.replace('GK', 'KL')} İçin Yetenek Analizi:</h4>
<h5 style="margin-top: 10px; color: #fff;">En Önemli Yetenekler:</h5>
<ul style="margin: 0; padding-left: 20px; list-style-type: none;">${keySkills}</ul>
<h5 style="margin-top: 10px; color: #fff;">En Az Önemli Yetenekler:</h5>
<ul style="margin: 0; padding-left: 20px; list-style-type: none;">${weakSkills}</ul>
<h4 style="margin-top: 15px; color: #fff; border-bottom: 1px solid #53a12c; padding-bottom: 3px;">Gizli Yetenekler & Antrenman:</h4>
<p style="margin: 5px 0; color: #ccc;">
Profesyonellik: ${hiddenTalents.professionalism !== null ? `<span style="color: #e1d919;">${hiddenTalents.professionalism}/8</span>` : 'Bilinmiyor'}
</p>
<p style="margin: 5px 0; color: #ccc;">
Fitness: ${hiddenTalents.fitness !== null ? `<span style="color: #e1d919;">${hiddenTalents.fitness}/8</span>` : 'Bilinmiyor'}
</p>
<p style="margin: 10px 0 0 0; color: ${trainingAnalysis.color}; font-weight: bold;">${trainingAnalysis.text}</p>
`;
reportPanel.appendChild(closeButton);
reportPanel.appendChild(title);
reportPanel.appendChild(scoresDiv);
reportPanel.appendChild(multiPositionDiv);
reportPanel.appendChild(detailsDiv);
document.body.appendChild(reportPanel);
}
// --- BUTON VE GÖZLEMCİ ---
function addButton() {
if (document.getElementById('aiScoutButton')) return;
const actionsContainer = document.querySelector('#ActionsBoard table');
if (actionsContainer) {
const newRow = document.createElement('tr');
const newHeader = document.createElement('th');
const newCell = document.createElement('td');
newCell.className = 'td-cell';
const button = document.createElement('div');
button.id = 'aiScoutButton';
button.textContent = 'AI Analizi Çalıştır';
button.className = 'fmp-btn btn-yellow';
button.style.width = '120px'; // Butonu biraz genişlettik
button.onclick = analyzePlayer;
const descriptionDl = document.createElement('dl');
const dt = document.createElement('dt');
dt.textContent = 'Yetenek Analizi';
const dd = document.createElement('dd');
dd.textContent = 'Oyuncunun mevcut ve diğer mevkilerdeki skorlarını, revize potansiyelini ve antrenman önerisini listeler.';
descriptionDl.appendChild(dt);
descriptionDl.appendChild(dd);
newHeader.appendChild(button);
newCell.appendChild(descriptionDl);
newRow.appendChild(newHeader);
newRow.appendChild(newCell);
actionsContainer.prepend(newRow);
}
}
// Mutasyon Gözlemcisi (Sayfa yüklendikten sonra veya içerik değiştiğinde butonu eklemek için)
const observer = new MutationObserver((mutationsList, observer) => {
const actionsContainer = document.querySelector('#ActionsBoard table');
if (actionsContainer && !document.getElementById('aiScoutButton')) {
addButton();
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Sayfa yüklendiğinde analizi otomatik başlatma seçeneği (İsteğe bağlı, şimdilik butonla bıraktım)
// window.addEventListener('load', analyzePlayer);
})();