Show NPC Time Til Next Level at Loader

Shows the time til the next level for NPCs at the attack loader page. Uses https://api.lzpt.io/loot to get the data. Also modifies tab title, disable by modifying IF_UPDATE_TAB_TITLE to false. Offset differences of a few seconds may occur because of difference in time between the server and your device.

  1. // ==UserScript==
  2. // @name Show NPC Time Til Next Level at Loader
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.7
  5. // @description Shows the time til the next level for NPCs at the attack loader page. Uses https://api.lzpt.io/loot to get the data. Also modifies tab title, disable by modifying IF_UPDATE_TAB_TITLE to false. Offset differences of a few seconds may occur because of difference in time between the server and your device.
  6. // @author Hesper [2924630]
  7. // @match https://www.torn.com/loader.php?sid=attack&user2ID=*
  8. // @grant GM.xmlHttpRequest
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
  10.  
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Displays time remaining until the next level in the tab title.
  18. // Set to false to disable
  19. const IF_UPDATE_TAB_TITLE = true;
  20.  
  21. const validIDs = [10, 20, 21, 4, 19, 15, 17];
  22.  
  23. // Function to fetch JSON data
  24. function fetchData() {
  25.  
  26. return new Promise((resolve, reject) => {
  27. const request = GM.xmlHttpRequest || GM.xmlhttpRequest;
  28. request({
  29. method: 'GET',
  30. url: 'https://api.lzpt.io/loot',
  31. onload: function(response) {
  32. if (response.status >= 200 && response.status < 300) {
  33. try {
  34. const data = JSON.parse(response.responseText);
  35.  
  36. resolve(data);
  37. } catch (e) {
  38. reject(e);
  39. }
  40. } else {
  41.  
  42. reject(new Error(`HTTP error! status: ${response.status}`));
  43. }
  44. },
  45. onerror: function(err) {
  46. reject(err);
  47. }
  48. });
  49. });
  50. }
  51.  
  52. function calculateTimeRemaining(currentTime, hospOutTime) {
  53. const levelTimes = [0, 30, 90, 210, 450]; // Minutes after hosp_out for each level
  54. let nextLevel = 1;
  55. let timeRemaining = 0;
  56.  
  57. if (currentTime < hospOutTime) {
  58. timeRemaining = hospOutTime - currentTime;
  59. } else {
  60. for (let level = 2; level <= 5; level++) {
  61. const targetTime = hospOutTime + levelTimes[level - 1] * 60; // Convert minutes to seconds
  62. timeRemaining = targetTime - currentTime;
  63. // If the time remaining is positive, the current level is the previous level
  64. if (timeRemaining > 0) {
  65. nextLevel = level;
  66. break;
  67. }
  68. }
  69. }
  70.  
  71. return { nextLevel, timeRemaining: timeRemaining > 0 ? timeRemaining : 0 };
  72. }
  73.  
  74. function formatTime(seconds) {
  75. const minutes = Math.floor(seconds / 60);
  76. const hours = Math.floor(minutes / 60);
  77. const remainingMinutes = minutes % 60;
  78. const remainingSeconds = seconds % 60;
  79. if (hours === 0) {
  80. if (remainingMinutes === 0) {
  81. return `${remainingSeconds}s`;
  82. }
  83. return `${remainingMinutes}m ${remainingSeconds}s`;
  84. }
  85. return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`;
  86. }
  87.  
  88. function updateDiv(npc, currentTime) {
  89.  
  90. const hospOutTime = npc.hosp_out;
  91. const { nextLevel, timeRemaining } = calculateTimeRemaining(currentTime, hospOutTime);
  92. let level = nextLevel - 1;
  93.  
  94. const targetDiv = document.querySelector('.titleContainer___QrlWP');
  95.  
  96. if (targetDiv) {
  97.  
  98. let newDiv = document.querySelector('#npc-level-info');
  99. if (!newDiv) {
  100. newDiv = document.createElement('div');
  101. newDiv.id = 'npc-level-info';
  102. newDiv.style.marginTop = '12px';
  103. newDiv.style.marginRight = '10px';
  104. newDiv.style.textAlign = 'right';
  105. newDiv.style.fontSize = '1em';
  106. newDiv.style.lineHeight = '1.2em';
  107.  
  108. targetDiv.parentNode.insertBefore(newDiv, targetDiv.nextSibling);
  109. }
  110. if (level < 5) {
  111. newDiv.innerHTML = `Currently <b>Level ${level}</b><br />Time til Level ${level + 1}: <b>${formatTime(timeRemaining)}</b>`;
  112. if (IF_UPDATE_TAB_TITLE) document.title = `${formatTime(timeRemaining)} til Level ${level + 1} - ${npc.name}`;
  113. } else {
  114. newDiv.innerHTML = `Currently <b>Level ${level}</b>`;
  115. if (IF_UPDATE_TAB_TITLE) document.title = `Level ${level} - ${npc.name}`;
  116. }
  117. }
  118. }
  119.  
  120. async function startUpdating(user2ID) {
  121. // Check if the user2ID is valid
  122. const npcData = await fetchData();
  123.  
  124. const npc = npcData.npcs[user2ID];
  125. if (!npc) return;
  126.  
  127. // Start updating the timer every second
  128. setInterval(() => {
  129. const currentTime = Math.floor(Date.now() / 1000);
  130. updateDiv(npc, currentTime);
  131. }, 1000);
  132. }
  133.  
  134. // check if the user2ID is valid
  135. const user2ID = parseInt(new URLSearchParams(window.location.search).get('user2ID'), 10);
  136. if (validIDs.includes(user2ID)) {
  137. startUpdating(user2ID);
  138. }
  139.  
  140. })();