您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Exibe, em cada atributo do jogador, a porcentagem estimada até o próximo ganho de ponto (bolinha). A informação é mostrada em uma coluna extra, no final da linha da tabela de habilidades, com destaque por cores de acordo com a faixa de progresso. Script baseado em: van.mz.playerAdvanced
// ==UserScript== // @name ManagerZone - Treino % na Tabela // @namespace https://greasyfork.org/users/1520478-emanuel-neves // troque pelo seu ID no GreasyFork // @version 0.0.6 // @description Exibe, em cada atributo do jogador, a porcentagem estimada até o próximo ganho de ponto (bolinha). A informação é mostrada em uma coluna extra, no final da linha da tabela de habilidades, com destaque por cores de acordo com a faixa de progresso. Script baseado em: van.mz.playerAdvanced // @author Emanuel Neves (emanuelsn) // @match https://www.managerzone.com/* // @icon https://www.managerzone.com/favicon.ico // @run-at document-idle // @grant GM_xmlhttpRequest // @connect managerzone.com // @license MIT // ==/UserScript== (function(){ 'use strict'; if (window.__MZ_TRAIN_PUBLISHED__) return; window.__MZ_TRAIN_PUBLISHED__ = true; /** ========================================================================= * CONFIGURAÇÕES BÁSICAS * ========================================================================= */ const SPORT = 'soccer'; const TIMEOUT = 12000; // Pesos por nível de treino (mantidos conforme metodologia original) const WEIGHTS = { 1: 0.645*1, 2: 0.55*2, 3: 0.7*3, 4: 0.85*4, 5: 0.96*5, 6: 1.111*6, 7: 1.3*7, 8: 1.6*8, 9: 2.02*9, 10: 2.4*10 }; // Mapeamento de atributos → skill_id usados nos gráficos do ManagerZone const SKILL_NAME_TO_ID = { 'velocidade': 2, 'resistência': 3, 'inteligência': 4, 'passe curto': 5, 'chute': 6, 'cabeceio': 7, 'defesa a gol': 8, 'controle de bola': 9, 'desarme': 10, 'passe longo': 11, 'bola parada': 12 }; /** ========================================================================= * FUNÇÕES AUXILIARES * ========================================================================= */ const fmtDate = ts => { try { return new Date(ts).toLocaleDateString('pt-BR'); } catch(_) { return String(ts); } }; function sumEff(pos, neg){ let sPos = 0, sNeg = 0; for (let i=1;i<=10;i++){ const w = WEIGHTS[i]||0; sPos += w * (pos[i]||0); sNeg += w * (neg[i]||0); } let s = sPos - sNeg; if (s < 0) s = 0; if (s >= 100) s = 99.99; return +s.toFixed(2); } function isBall(url){ return /trainingicon\.php\?icon=bar_pos_\d+_ball/i.test(String(url||'')); } function parseIcon(url){ const m = String(url||'').match(/trainingicon\.php\?icon=bar_(pos|neg)_(\d+)(?:_ball)?(?:&t=([a-z_]+))?/i); if (!m) return null; return { sign: m[1].toLowerCase(), t: Math.max(1, Math.min(10, +m[2])) }; } /** ========================================================================= * REQUISIÇÕES AOS ENDPOINTS DE TREINO * ========================================================================= */ async function http(url){ try{ const r = await fetch(url, {credentials:'include'}); return { ok:true, data: await r.text() }; }catch(_){} return new Promise((resolve,reject)=>{ GM_xmlhttpRequest({ method:'GET', url, timeout: TIMEOUT, onload: r => resolve({ ok:true, data:r.responseText }), onerror: reject, ontimeout: () => reject(new Error('timeout')) }); }); } async function fetchTrainingData(pid, skillId){ const tries = [ `/ajax.php?p=trainingGraph&sub=getJsonTrainingHistory&sport=${SPORT}&player_id=${pid}&skill_id=${skillId}`, `/ajax.php?p=trainingGraph&sub=getJsonTrainingHistory&sport=${SPORT}&player_id=${pid}`, `/ajax.php?p=players&sub=training_graphs&pid=${pid}&sport=${SPORT}&skill=${skillId}`, `/ajax.php?p=players&sub=training_graphs&pid=${pid}&skill=${skillId}` ]; for (const url of tries){ try{ const res = await http(url); if (res.ok && res.data && res.data.length > 20) return { url, data: res.data }; }catch(_){} } return { url:null, data:null }; } /** ========================================================================= * PARSE E EXTRAÇÃO DE PONTOS DO HISTÓRICO * ========================================================================= */ function extractSeries(data){ let series; try{ eval(data); if (Array.isArray(series)) return series; }catch(_){} try{ const j = JSON.parse(data); if (j && Array.isArray(j.series)) return j.series; }catch(_){} try{ const m = data.match(/series\s*=\s*(\[[\s\S]*?\]);/); if (m){ const tmp = eval(m[1]); if (Array.isArray(tmp)) return tmp; } }catch(_){} return []; } function collectPointsFromSeries(seriesArr){ const pts = []; for (const s of seriesArr){ const data = (s && (s.data || s.points)) || []; for (const g of data){ const x = g && g.x; const marker = g && g.marker && (g.marker.symbol || g.marker.url); if (!Number.isFinite(x) || !marker) continue; const info = parseIcon(marker); if (!info) continue; pts.push({x, marker}); } } return pts; } /** ========================================================================= * CÁLCULO DO % DE TREINO * ========================================================================= */ async function getPercentForSkill(pid, skillId){ const net = await fetchTrainingData(pid, skillId); if (!net.data) return 0.00; const allSeries = extractSeries(net.data); const points = collectPointsFromSeries(allSeries); if (!points.length) return 0.00; let cutoff = -Infinity; for (const p of points){ if (isBall(p.marker) && p.x > cutoff) cutoff = p.x; } if (!Number.isFinite(cutoff)) cutoff = Math.min(...points.map(p=>p.x)); const pos = {}, neg = {}; for (const p of points){ if (p.x <= cutoff) continue; const info = parseIcon(p.marker); if (!info) continue; if (info.sign === 'pos') pos[info.t] = (pos[info.t]||0) + 1; else neg[info.t] = (neg[info.t]||0) + 1; } return sumEff(pos, neg); } /** ========================================================================= * UI: INSERÇÃO DA COLUNA E ESTILOS * ========================================================================= */ function injectCSS(){ if (document.getElementById('mz-train-css')) return; const st = document.createElement('style'); st.id = 'mz-train-css'; st.textContent = ` .mz-train-td{ font-size:11px; white-space:nowrap; padding-left:6px; text-align:right; min-width:56px; } .mz-train-td--placeholder{ color:transparent; } .mz-badge{ display:inline-block; padding:0 6px; border-radius:10px; font-weight:600; line-height:1.6; } .mz-badge--g0 { background: rgba(120,120,120,.15); color:#444; } .mz-badge--g20{ background: rgba(255,193,7,.18); color:#8a6d00; } .mz-badge--g50{ background: rgba(33,150,243,.18); color:#0f4c81; } .mz-badge--g80{ background: rgba(76,175,80,.18); color:#1b5e20; } `; document.head.appendChild(st); } function getSkillRows(){ const table = document.querySelector('.player_skills.player_skills_responsive'); if (!table) return []; const rows = []; table.querySelectorAll('tr').forEach(tr=>{ const td = tr.querySelector('td'); if (!td) return; const nameText = (td.textContent||'').trim().toLowerCase(); for (const key in SKILL_NAME_TO_ID){ if (nameText.includes(key)){ rows.push({tr, key, skillId: SKILL_NAME_TO_ID[key]}); break; } } }); return rows; } function clsForPercent(p){ if (p >= 80) return 'mz-badge--g80'; if (p >= 50) return 'mz-badge--g50'; if (p >= 20) return 'mz-badge--g20'; return 'mz-badge--g0'; } async function runOnce(){ const url = new URL(location.href); const pid = url.searchParams.get('pid'); if (!pid) return; injectCSS(); const table = document.querySelector('.player_skills.player_skills_responsive'); if (!table) return; // Garante coluna final em todas as linhas table.querySelectorAll('tr').forEach(tr=>{ if (tr.dataset.mzColAdded) return; tr.dataset.mzColAdded = '1'; const tdPercent = document.createElement('td'); tdPercent.className = 'mz-train-td mz-train-td--placeholder'; tdPercent.innerHTML = ' '; tr.appendChild(tdPercent); }); // Preenche apenas para as skills reconhecidas const rows = getSkillRows(); for (const {tr, skillId} of rows){ const tdPercent = tr.querySelector('.mz-train-td'); if (!tdPercent) continue; tdPercent.classList.remove('mz-train-td--placeholder'); tdPercent.textContent = ''; const badge = document.createElement('span'); badge.className = 'mz-badge mz-badge--g0'; badge.textContent = '(…)'; tdPercent.appendChild(badge); try{ const pct = await getPercentForSkill(pid, skillId); badge.className = `mz-badge ${clsForPercent(pct)}`; badge.textContent = `(${pct.toFixed(1)}%)`; }catch(_){ badge.className = 'mz-badge mz-badge--g0'; badge.textContent = '(0.0%)'; } } } /** ========================================================================= * OBSERVAÇÃO DE MUDANÇAS NA PÁGINA * ========================================================================= */ function isPlayerPage(){ return /[?&]p=players\b/.test(location.href) || /players\.php\b/.test(location.href); } let lastPid=null, t=null; const obs = new MutationObserver(()=>{ if (!isPlayerPage()) return; const pid = new URL(location.href).searchParams.get('pid'); if (pid && pid!==lastPid){ lastPid = pid; clearTimeout(t); t = setTimeout(runOnce, 600); } }); obs.observe(document.documentElement, {subtree:true, childList:true}); if (isPlayerPage()) runOnce(); })();