TM Simple Routine + R5 (Stable Field Version) v3.1

Mostra Routine e R5 (cálculo completo) apenas para jogadores no campo. Versão estável com correção de sobreposição.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TM Simple Routine + R5 (Stable Field Version) v3.1
// @namespace    http://tampermonkey.net/
// @version      3.1
// @description  Mostra Routine e R5 (cálculo completo) apenas para jogadores no campo. Versão estável com correção de sobreposição.
// @author       Gemini (baseado no script de Borgo Cervaro e RatingR6)
// @match        https://trophymanager.com/*tactics/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // *** Constantes e Variáveis Globais ***
    var players_on_field = {};
    var player_r5_values = {};
    var updateTimeout;
    const SHARE_BONUS = 0.25;
    const ROUTINE_CAP = 40.0;
    const DEF_LINE_IDX = [0, 6];
    const MID_LINE_IDX = [6, 16];
    const OFF_LINE_IDX = [16, 24];
    const WEIGHT_R5 = [[0.41029304,0.18048062,0.56730138,1.06344654,1.02312672,0.40831256,0.58235457,0.12717479,0.05454137,0.09089830,0.42381693,0.04626272,0.02199046,0],[0.42126371,0.18293193,0.60567629,0.91904794,0.89070915,0.40038476,0.56146633,0.15053902,0.15955429,0.15682932,0.42109742,0.09460329,0.03589655,0],[0.23412419,0.32032289,0.62194779,0.63162534,0.63143081,0.45218831,0.47370658,0.55054737,0.17744915,0.39932519,0.26915814,0.16413124,0.07404301,0],[0.27276905,0.26814289,0.61104798,0.39865092,0.42862643,0.43582015,0.46617076,0.44931076,0.25175412,0.46446692,0.29986350,0.43843061,0.21494592,0],[0.25219260,0.25112993,0.56090649,0.18230261,0.18376490,0.45928749,0.53498118,0.59461481,0.09851189,0.61601950,0.31243959,0.65402884,0.29982016,0],[0.28155678,0.24090675,0.60680245,0.19068879,0.20018012,0.45148647,0.48230007,0.42982389,0.26268609,0.57933805,0.31712419,0.65824985,0.29885649,0],[0.22029884,0.29229690,0.63248227,0.09904394,0.10043602,0.47469498,0.52919791,0.77555880,0.10531819,0.71048302,0.27667115,0.56813972,0.21537826,0],[0.21151292,0.35804710,0.88688492,0.14391236,0.13769621,0.46586605,0.34446036,0.51377701,0.59723919,0.75126119,0.16550722,0.29966502,0.12417045,0],[0.35479780,0.14887553,0.43273380,0.00023928,0.00021111,0.46931131,0.57731335,0.41686333,0.05607604,0.62121195,0.45370457,1.03660702,0.43205492,0],[0.45462811,0.30278232,0.45462811,0.90925623,0.45462811,0.90925623,0.45462811,0.45462811,0.30278232,0.15139116,0.15139116]];

    function initialize() {
        if (typeof players_by_id !== 'undefined' && Object.keys(players_by_id).length > 0) {
            main();
        } else {
            setTimeout(initialize, 200);
        }
    }

    function main() {
        updateAndDisplay();
        setupMutationObserver();
    }

    function updateAndDisplay() {
        if (typeof players_by_id === 'undefined' || typeof formation_by_pos === 'undefined') return;
        updatePlayersRoutine();
        calculateFieldPlayersR5();
        displayOnFieldRatings();
    }

    function setupMutationObserver() {
        const observer = new MutationObserver(() => {
            clearTimeout(updateTimeout);
            updateTimeout = setTimeout(updateAndDisplay, 150);
        });
        const tacticsNode = document.getElementById('tactics_field');
        if (tacticsNode) {
            observer.observe(tacticsNode, { childList: true, subtree: true });
        }
    }

    function updatePlayersRoutine() {
        players_on_field = {};
        [DEF_LINE_IDX, MID_LINE_IDX, OFF_LINE_IDX].forEach(line => updateLineRoutine(line));
    }

    function updateLineRoutine(line_idx) {
        let players_ar = [];
        for (let i = line_idx[0]; i < line_idx[1]; i++) {
            const id = formation_by_pos[i];
            if (id && id !== "0") {
                players_ar.push({ "id": id, "routine": parseFloat(players_by_id[id].routine) });
            }
        }
        if (players_ar.length > 1) {
            players_ar.sort((a, b) => a.routine - b.routine);
            let min_player = players_ar[0];
            if (min_player.routine < ROUTINE_CAP) {
                let bonus = players_ar[players_ar.length - 1].routine * SHARE_BONUS;
                let new_routine = Math.min(min_player.routine + bonus, players_ar[1].routine, ROUTINE_CAP);
                min_player.routine = parseFloat(new_routine.toFixed(1));
            }
        }
        players_ar.forEach(p => { players_on_field[p.id] = { routine: p.routine }; });
    }

    function calculateFieldPlayersR5() {
        player_r5_values = {};
        $("div.field_player[player_set='true']").each(function() {
            const id = $(this).attr("player_id");
            const player = players_by_id[id];
            if (!player || !player.skills) return;

            let routine = players_on_field[id] ? players_on_field[id].routine : parseFloat(player.routine);
            const posNames = ["dc","dcl","dcr","dl","dr","dmc","dmcl","dmcr","dml","dmr","mc","mcl","mcr","ml","mr","omc","omcl","omcr","oml","omr","fc","fcl","fcr","gk"];
            const posMap = [0,0,0,1,1,2,2,2,3,3,4,4,4,5,5,6,6,6,7,7,8,8,8,9];
            const fp = posMap[posNames.indexOf($(this).attr('position'))] ?? player.fp;

            const is_gk = (fp === 9);
            const asi = parseFloat(player.skill_index.replace(",", ""));
            const skill_map = is_gk ? ["0","1","2","3","4","5","7","9","11","13","15"] : ["0","2","4","6","8","10","12","1","3","5","7","9","11","13"];
            const skills = skill_map.map(i => parseFloat(String(player.skills[i]?.value || '0').match(/\d+/)?.[0] || 0));
            const skillSum = skills.reduce((a, b) => a + b, 0);

            const weight = is_gk ? 48717927500 : 263533760000;
            const remainder = Math.max(0, Math.round((Math.pow(2, Math.log(weight * asi) / Math.log(Math.pow(2, 7))) - skillSum) * 10) / 10);
            const rou2 = (3 / 100) * (100 - (100) * Math.pow(Math.E, -routine * 0.035));

            let ratingR = 0, remainderWeight2 = 0;
            const not20 = skills.filter(s => s < 20).length;

            if (WEIGHT_R5[fp]) {
                for (let i = 0; i < skills.length; i++) {
                    if (i < WEIGHT_R5[fp].length) {
                        ratingR += skills[i] * WEIGHT_R5[fp][i];
                        if (skills[i] < 20) remainderWeight2 += WEIGHT_R5[fp][i];
                    }
                }
            }
            if (remainder / (not20 || 1) > 0.9 || not20 === 0) remainderWeight2 = 5;

            ratingR += remainder * remainderWeight2 / (not20 || 1);
            const base_r5 = ratingR + rou2 * 5;

            const goldstar = skills.filter(s => s === 20).length;
            const denom = (skills.length - goldstar) || 1;
            const skillsC = skills.map(s => (s !== 20) ? s + remainder / denom : 20);
            const skillsB = skillsC.map((s, i) => (is_gk || i === 1) ? s : s + rou2);

            let allBonus = 0, posGain = 0, posKeep = 0;
            if (!is_gk) {
                const sB = skillsB;
                const gainBase = ((sB[0]**2 + sB[1]**2*0.5 + sB[2]**2*0.5 + sB[3]**2 + sB[4]**2 + sB[5]**2 + sB[6]**2)/6/22.9**2);
                const keepBase = ((sB[0]**2*0.5 + sB[1]**2*0.5 + sB[2]**2 + sB[3]**2 + sB[4]**2 + sB[5]**2 + sB[6]**2)/6/22.9**2);
                const posGainTable = [gainBase*0.3, gainBase*0.3, gainBase*0.9, gainBase*0.6, gainBase*1.5, gainBase*0.9, gainBase*0.9, gainBase*0.6, gainBase*0.3];
                const posKeepTable = [keepBase*0.3, keepBase*0.3, keepBase*0.9, keepBase*0.6, keepBase*1.5, keepBase*0.9, keepBase*0.9, keepBase*0.6, keepBase*0.3];
                posGain = posGainTable[fp] || 0;
                posKeep = posKeepTable[fp] || 0;

                const headerBonus = sB[10] > 12 ? ((Math.pow(Math.E, (sB[10]-10)**3/1584.77)-1)*0.8 + Math.pow(Math.E, (sB[0]*sB[0]*0.007)/8.73021)*0.15 + Math.pow(Math.E, (sB[6]*sB[6]*0.007)/8.73021)*0.05) : 0;
                const fkBonus = (Math.pow(Math.E, Math.pow(sB[13]+sB[12]+sB[9]*0.5, 2)*0.002)/327.92526);
                const ckBonus = (Math.pow(Math.E, Math.pow(sB[13]+sB[8]+sB[9]*0.5, 2)*0.002)/983.65770);
                const pkBonus = (Math.pow(Math.E, Math.pow(sB[13]+sB[11]+sB[9]*0.5, 2)*0.002)/1967.31409);
                allBonus = headerBonus + fkBonus + ckBonus + pkBonus;
            }

            player_r5_values[id] = (base_r5 + allBonus + posGain + posKeep).toFixed(2);
        });
    }

    function displayOnFieldRatings() {
        $("div.field_player").each(function() {
            const $this = $(this);
            $this.find(".tm-r5-display").remove(); // Limpa a div antiga para garantir que não haja duplicatas
            if ($this.attr("player_set") === "true") {
                const id = $this.attr("player_id");
                const routine = players_on_field[id] ? players_on_field[id].routine.toFixed(1) : parseFloat(players_by_id[id]?.routine || 0).toFixed(1);
                const r5 = player_r5_values[id] || "N/A";
                const displayText = `Rou: ${routine} / R5: ${r5}`;

                // ** MUDANÇA PRINCIPAL AQUI **
                // Adiciona a div como um elemento normal, sem posicionamento absoluto, para que flua com o conteúdo.
                const displayDiv = `<div class="tm-r5-display" style="font-size: 10px; width: 100%; text-align: center; background-color: rgba(0,0,0,0.6); border-radius: 3px; padding: 1px 0; margin-top: 1px;">${displayText}</div>`;

                $this.append(displayDiv);
            }
        });
    }

    initialize();

})();