MZ - Player Ratings on Transfer Page

Displays player ratings on transfer page

目前为 2025-02-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MZ - Player Ratings on Transfer Page
  3. // @namespace douglaskampl
  4. // @version 1.0
  5. // @description Displays player ratings on transfer page
  6. // @author Douglas
  7. // @match https://www.managerzone.com/?p=transfer*
  8. // @match https://www.managerzone.com/?p=players*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
  10. // @grant GM_addStyle
  11. // @license MIT
  12. // @run-at document-idle
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. const ratings = {
  19. "SPEED": { "K": 0.09, "D": 0.25, "A": 0.25, "M": 0.15, "W": 0.25, "F": 0.23 },
  20. "STAMINA": { "K": 0.09, "D": 0.16, "A": 0.18, "M": 0.15, "W": 0.20, "F": 0.15 },
  21. "PLAYINT": { "K": 0.09, "D": 0.07, "A": 0.05, "M": 0.10, "W": 0.06, "F": 0.05 },
  22. "PASSING": { "K": 0.02, "D": 0.02, "A": 0.05, "M": 0.15, "W": 0.04, "F": 0.04 },
  23. "SHOOTING": { "K": 0.00, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.05, "F": 0.28 },
  24. "HEADING": { "K": 0.00, "D": 0.00, "A": 0.02, "M": 0.00, "W": 0.00, "F": 0.03 },
  25. "GOALKEEPING": { "K": 0.55, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.00, "F": 0.00 },
  26. "BALLCONTROL": { "K": 0.09, "D": 0.08, "A": 0.10, "M": 0.12, "W": 0.15, "F": 0.15 },
  27. "TACKLING": { "K": 0.00, "D": 0.30, "A": 0.25, "M": 0.20, "W": 0.05, "F": 0.02 },
  28. "CROSSING": { "K": 0.02, "D": 0.07, "A": 0.05, "M": 0.08, "W": 0.15, "F": 0.00 },
  29. "SETPLAYS": { "K": 0.00, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.00, "F": 0.00 },
  30. "EXPERIENCE": { "K": 0.05, "D": 0.05, "A": 0.05, "M": 0.05, "W": 0.05, "F": 0.05 }
  31. };
  32.  
  33. const skillPositionMap = [
  34. "SPEED",
  35. "STAMINA",
  36. "PLAYINT",
  37. "PASSING",
  38. "SHOOTING",
  39. "HEADING",
  40. "GOALKEEPING",
  41. "BALLCONTROL",
  42. "TACKLING",
  43. "CROSSING",
  44. "SETPLAYS",
  45. "EXPERIENCE"
  46. ];
  47.  
  48. function calculateRatings(skills) {
  49. const player = {
  50. K: 0,
  51. D: 0,
  52. A: 0,
  53. M: 0,
  54. W: 0,
  55. F: 0,
  56. B: 0,
  57. top: 0
  58. };
  59.  
  60. Object.entries(skills).forEach(([skill, value]) => {
  61. if (!ratings[skill]) return;
  62.  
  63. const num = parseInt(value, 10);
  64. if (isNaN(num)) return;
  65.  
  66. if (skill !== "EXPERIENCE") {
  67. player.B += num;
  68. }
  69.  
  70. player.K += num * ratings[skill]["K"];
  71. if (player.K > player.top) player.top = player.K;
  72.  
  73. player.D += num * ratings[skill]["D"];
  74. if (player.D > player.top) player.top = player.D;
  75.  
  76. player.A += num * ratings[skill]["A"];
  77. if (player.A > player.top) player.top = player.A;
  78.  
  79. player.M += num * ratings[skill]["M"];
  80. if (player.M > player.top) player.top = player.M;
  81.  
  82. player.W += num * ratings[skill]["W"];
  83. if (player.W > player.top) player.top = player.W;
  84.  
  85. player.F += num * ratings[skill]["F"];
  86. if (player.F > player.top) player.top = player.F;
  87. });
  88.  
  89. return {
  90. K: player.K.toFixed(2),
  91. D: player.D.toFixed(2),
  92. A: player.A.toFixed(2),
  93. M: player.M.toFixed(2),
  94. W: player.W.toFixed(2),
  95. F: player.F.toFixed(2),
  96. B: player.B,
  97. top: player.top.toFixed(2)
  98. };
  99. }
  100.  
  101. function extractPlayerSkills(playerElement) {
  102. const skills = {};
  103. const skillRows = playerElement.querySelectorAll('.player_skills tr');
  104.  
  105. skillRows.forEach((row, index) => {
  106. if (index >= skillPositionMap.length) return;
  107.  
  108. const valueElem = row.querySelector('.skillval span');
  109. if (valueElem) {
  110. const skillType = skillPositionMap[index];
  111. const value = valueElem.textContent.trim();
  112. skills[skillType] = value;
  113. }
  114. });
  115.  
  116. return skills;
  117. }
  118.  
  119. function createRatingDisplay(ratings) {
  120. const positions = [
  121. { code: 'K', name: 'Goalkeeper', value: ratings.K },
  122. { code: 'D', name: 'Defender', value: ratings.D },
  123. { code: 'A', name: 'Anchorman', value: ratings.A },
  124. { code: 'M', name: 'Midfielder', value: ratings.M },
  125. { code: 'W', name: 'Winger', value: ratings.W },
  126. { code: 'F', name: 'Forward', value: ratings.F }
  127. ];
  128.  
  129. const container = document.createElement('div');
  130. container.className = 'mz-rating-container';
  131.  
  132. const ratingsList = document.createElement('div');
  133. ratingsList.className = 'mz-rating-list';
  134.  
  135. positions.forEach(pos => {
  136. const row = document.createElement('div');
  137. row.className = 'mz-rating-row';
  138.  
  139. const isTop = pos.value === ratings.top;
  140.  
  141. const posName = document.createElement('span');
  142. posName.className = 'mz-pos-name' + (isTop ? ' mz-pos-top' : '');
  143. posName.textContent = pos.name + ':';
  144.  
  145. const posValue = document.createElement('span');
  146. posValue.className = 'mz-pos-value' + (isTop ? ' mz-pos-top' : '');
  147. posValue.textContent = pos.value;
  148.  
  149. row.appendChild(posName);
  150. row.appendChild(posValue);
  151. ratingsList.appendChild(row);
  152. });
  153.  
  154. container.appendChild(ratingsList);
  155.  
  156. return container;
  157. }
  158.  
  159. function addRatingButton(playerElement) {
  160. const idElement = playerElement.querySelector('.player_id_span');
  161. if (!idElement) return;
  162.  
  163. if (idElement.nextElementSibling && idElement.nextElementSibling.classList.contains('mz-rating-btn')) {
  164. return;
  165. }
  166.  
  167. const btn = document.createElement('button');
  168. btn.className = 'mz-rating-btn';
  169. btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
  170. btn.title = 'Show player ratings';
  171.  
  172. let ratingContainer = null;
  173. let isVisible = false;
  174.  
  175. btn.addEventListener('click', (e) => {
  176. e.preventDefault();
  177. e.stopPropagation();
  178.  
  179. if (isVisible && ratingContainer) {
  180. ratingContainer.classList.remove('mz-rating-visible');
  181. setTimeout(() => {
  182. if (ratingContainer && ratingContainer.parentNode) {
  183. ratingContainer.parentNode.removeChild(ratingContainer);
  184. }
  185. ratingContainer = null;
  186. }, 300);
  187. isVisible = false;
  188.  
  189. btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
  190. return;
  191. }
  192.  
  193. const skills = extractPlayerSkills(playerElement);
  194. const ratings = calculateRatings(skills);
  195.  
  196. ratingContainer = createRatingDisplay(ratings);
  197.  
  198. const playerHeader = playerElement.querySelector('.subheader');
  199. if (playerHeader) {
  200. playerHeader.parentNode.insertBefore(ratingContainer, playerHeader.nextSibling);
  201. } else {
  202. playerElement.appendChild(ratingContainer);
  203. }
  204.  
  205. setTimeout(() => {
  206. ratingContainer.classList.add('mz-rating-visible');
  207. }, 10);
  208.  
  209. isVisible = true;
  210. btn.innerHTML = '<i class="fa-solid fa-xmark"></i>';
  211. });
  212.  
  213. idElement.parentNode.insertBefore(btn, idElement.nextSibling);
  214. }
  215.  
  216. function processPlayerElements() {
  217. const playerContainers = document.querySelectorAll('div[id^="thePlayers_"]');
  218. playerContainers.forEach(addRatingButton);
  219. }
  220.  
  221. function setupObserver() {
  222. const playerContainer = document.getElementById('players_container') || document.body;
  223.  
  224. const observer = new MutationObserver((mutations) => {
  225. let shouldProcess = false;
  226.  
  227. mutations.forEach(mutation => {
  228. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  229. for (const node of mutation.addedNodes) {
  230. if (node.nodeType === Node.ELEMENT_NODE &&
  231. (node.id && node.id.startsWith('thePlayers_') ||
  232. node.querySelector && node.querySelector('div[id^="thePlayers_"]'))) {
  233. shouldProcess = true;
  234. break;
  235. }
  236. }
  237. }
  238. });
  239.  
  240. if (shouldProcess) {
  241. processPlayerElements();
  242. }
  243. });
  244.  
  245. observer.observe(playerContainer, { childList: true, subtree: true });
  246.  
  247. return observer;
  248. }
  249.  
  250. function addStyles() {
  251. GM_addStyle(`
  252. .mz-rating-btn {
  253. display: inline-flex;
  254. align-items: center;
  255. justify-content: center;
  256. margin-left: 8px;
  257. width: 24px;
  258. height: 24px;
  259. border: none;
  260. border-radius: 50%;
  261. background: #1a73e8;
  262. color: white;
  263. cursor: pointer;
  264. font-size: 12px;
  265. transition: all 0.2s ease;
  266. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  267. }
  268.  
  269. .mz-rating-btn:hover {
  270. background: #0d5bbb;
  271. transform: translateY(-1px);
  272. box-shadow: 0 3px 6px rgba(0, 0, 0, 0.25);
  273. }
  274.  
  275. .mz-rating-container {
  276. margin: 10px 0;
  277. padding: 12px;
  278. background: white;
  279. border-radius: 8px;
  280. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  281. max-width: 300px;
  282. opacity: 0;
  283. transform: translateY(-10px);
  284. transition: all 0.3s ease;
  285. }
  286.  
  287. .mz-rating-visible {
  288. opacity: 1;
  289. transform: translateY(0);
  290. }
  291.  
  292. .mz-rating-header {
  293. font-weight: bold;
  294. margin-bottom: 8px;
  295. padding-bottom: 5px;
  296. border-bottom: 1px solid #eee;
  297. color: #333;
  298. font-size: 14px;
  299. }
  300.  
  301. .mz-rating-list {
  302. display: grid;
  303. grid-template-columns: repeat(2, 1fr);
  304. gap: 8px;
  305. margin-bottom: 10px;
  306. }
  307.  
  308. .mz-rating-row {
  309. display: flex;
  310. justify-content: space-between;
  311. align-items: center;
  312. padding: 3px 5px;
  313. }
  314.  
  315. .mz-pos-name {
  316. font-size: 13px;
  317. color: #555;
  318. }
  319.  
  320. .mz-pos-value {
  321. font-weight: bold;
  322. font-size: 13px;
  323. color: #333;
  324. }
  325.  
  326. .mz-pos-top {
  327. color: #1a73e8;
  328. }
  329.  
  330. .mz-total-row {
  331. margin-top: 5px;
  332. padding-top: 8px;
  333. border-top: 1px solid #eee;
  334. }
  335. `);
  336. }
  337.  
  338. function init() {
  339. addStyles();
  340. processPlayerElements();
  341. setupObserver();
  342. }
  343.  
  344. if (document.readyState === 'complete' || document.readyState === 'interactive') {
  345. setTimeout(init, 500);
  346. } else {
  347. window.addEventListener('DOMContentLoaded', () => setTimeout(init, 500));
  348. }
  349. })();