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 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();

})();