您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.
// ==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); })();