您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Transfer List: Show Routine (always) and Training Intensity (from Tuesday to Saturday)
// ==UserScript== // @name TI/XP Transfer List Modified // @namespace http://tampermonkey.net/ // @version 0.3.2 // @description Transfer List: Show Routine (always) and Training Intensity (from Tuesday to Saturday) // @match https://trophymanager.com/transfer/ // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const CONST = { WRAP: 'div#transfer_list', TI_COL: 6, DIFF_COL: 9, XP_COL: 12, CURRENT_PRICE_COL_BASE: 7, TI_HEADER: 'TI', XP_HEADER: 'XP', DIFF_HEADER: 'Price diff', TI_PREC: 0, XP_PREC: 1, DIFF_PREC: 0, CONCURRENCY: 8 }; const GK = 'GK'; let initialized = false; let columnsAdded = false; const currentPriceById = Object.create(null); const num = (t) => Number(String(t || '').replace(/[^\d.-]/g, '')); const formatCoin = (n, prec = 0) => { if (!isFinite(n)) return '-'; const s = Number(n).toFixed(prec); const [i, d] = s.split('.'); const withSep = i.replace(/\B(?=(\d{3})+(?!\d))/g, ','); return `<span class='coin'>${d ? `${withSep}.${d}` : withSep}</span>`; }; const getRows = () => Array.from(document.querySelectorAll(`${CONST.WRAP} tr[id^=player_row]`)); const mapRows = () => getRows().map(row => ({ id: row.id.split('_')[2], row })); const getOldASI = (row) => { const cells = row.querySelectorAll('td,th'); const cell = cells[5]; if (!cell) return NaN; const m = cell.innerHTML.match(/[0-9,\.]+/); return m ? Number(m[0].replace(/[^\d.-]/g, '')) : NaN; }; const addColumn = (colIndex, headerName) => { const headerRow = document.querySelector(`${CONST.WRAP} tr.header`); if (!headerRow) return; const ths = headerRow.querySelectorAll('th'); const th = document.createElement('th'); th.style.width = '80px'; th.textContent = headerName; if (ths.length > colIndex + 1) headerRow.insertBefore(th, ths[colIndex]); else headerRow.appendChild(th); const rows = getRows(); rows.forEach(row => { if (!row.children.length) return; const td = document.createElement('td'); td.className = 'align_center'; td.textContent = '-'; const tds = row.querySelectorAll('td'); if (tds.length > colIndex && tds[colIndex]) row.insertBefore(td, tds[colIndex]); else row.appendChild(td); }); }; const writeCell = (rowIndex, colIndex, html) => { const rows = getRows(); const row = rows[rowIndex]; if (!row) return; const cells = row.querySelectorAll('td,th'); if (cells[colIndex]) cells[colIndex].innerHTML = html; }; const TI = { compute(asiNew, asiOld, fp) { const P = Math.pow; if (fp === GK) { return ( (P(asiNew * P(2, 9) * P(5, 4) * P(7, 7), 1 / 7) - P(asiOld * P(2, 9) * P(5, 4) * P(7, 7), 1 / 7)) / 14 * 11 * 10 ); } return ( (P(asiNew * P(2, 9) * P(5, 4) * P(7, 7), 1 / 7) - P(asiOld * P(2, 9) * P(5, 4) * P(7, 7), 1 / 7)) * 10 ); } }; const priceDiff = (asiNew, ageYears, ageMonths, currentPrice, fp) => { const age = Number(ageYears) + Number(ageMonths) / 12; if (!asiNew || !age || age <= 0 || !isFinite(currentPrice)) return NaN; const est = asiNew * 500 * Math.pow(25 / age, 2.5); if (fp === GK) return est * 3 / 4 - currentPrice; return est - currentPrice; }; const fetchPlayer = (id) => new Promise((resolve, reject) => { $.ajax({ url: '/ajax/tooltip.ajax.php', method: 'POST', dataType: 'json', data: { player_id: id, minigame: undefined } }) .done((data) => { const p = data.player || {}; resolve({ id: p.player_id, fp: p.fp, asi: Number(String(p.skill_index || '0').replace(/,/g, '')), xp: Number(String(p.routine || '0').replace(',', '.')), age: Number(p.age || 0), months: Number(p.months || 0) }); }) .fail(reject); }); const runPool = async (items, worker, concurrency) => { const out = Array(items.length); let i = 0, active = 0; return new Promise((resolve) => { const next = () => { while (active < concurrency && i < items.length) { const cur = i++; active++; worker(items[cur], cur) .then((res) => (out[cur] = res)) .catch(() => (out[cur] = null)) .finally(() => { active--; if (i >= items.length && active === 0) resolve(out); else next(); }); } }; next(); }); }; const readCurrentPricesBeforeInsert = () => { const rows = mapRows(); rows.forEach(({ id, row }) => { const tds = row.querySelectorAll('td'); // cột gốc chưa bị chèn const cell = tds[CONST.CURRENT_PRICE_COL_BASE]; const val = cell ? num(cell.textContent) : NaN; currentPriceById[id] = isFinite(val) ? val : NaN; }); }; const init = async () => { if (initialized) return; const table = document.querySelector(`${CONST.WRAP} table`); if (!table) return; initialized = true; readCurrentPricesBeforeInsert(); if (!columnsAdded) { addColumn(CONST.TI_COL, CONST.TI_HEADER); addColumn(CONST.DIFF_COL, CONST.DIFF_HEADER); addColumn(CONST.XP_COL, CONST.XP_HEADER); columnsAdded = true; } const items = mapRows(); if (!items.length) return; await runPool( items, async ({ id, row }, idx) => { try { const data = await fetchPlayer(id); const oldAsi = getOldASI(row); const tiVal = TI.compute(data.asi, oldAsi, data.fp); const curPrice = currentPriceById[id]; // ✅ dùng giá đã đọc trước const diffVal = priceDiff(data.asi, data.age, data.months, curPrice, data.fp); requestAnimationFrame(() => { writeCell(idx, CONST.TI_COL, isFinite(tiVal) ? tiVal.toFixed(CONST.TI_PREC) : '-'); writeCell(idx, CONST.DIFF_COL, isFinite(diffVal) ? formatCoin(diffVal, CONST.DIFF_PREC) : '-'); writeCell(idx, CONST.XP_COL, isFinite(data.xp) ? data.xp.toFixed(CONST.XP_PREC) : '-'); }); } catch { requestAnimationFrame(() => { writeCell(idx, CONST.TI_COL, 'Error'); writeCell(idx, CONST.DIFF_COL, 'Error'); writeCell(idx, CONST.XP_COL, 'Error'); }); } }, CONST.CONCURRENCY ); }; const host = document.querySelector(CONST.WRAP); if (!host) return; const observer = new MutationObserver(() => init()); observer.observe(host, { childList: true }); init(); })();