Cybroria Loot Tracker with Timer and Value Calculator

Tracks loot, timer, income, and Cyber Components delta separately.

  1. // ==UserScript==
  2. // @name Cybroria Loot Tracker with Timer and Value Calculator
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description Tracks loot, timer, income, and Cyber Components delta separately.
  6. // @author Skydragon + Edit
  7. // @match https://cybroria.com/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const STORAGE_KEY = 'cybroria_loot_stats';
  16. const VALUE_KEY = 'cybroria_loot_values';
  17. const POS_KEY = 'cybroria_tracker_position';
  18. const RESET_TIME_KEY = 'cybroria_reset_time';
  19. const CYBER_BASE_KEY = 'cybroria_cyber_components_base';
  20. const CYBER_GAIN_KEY = 'cybroria_cyber_components_gain';
  21.  
  22. let timestamps = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
  23. let lootValues = JSON.parse(localStorage.getItem(VALUE_KEY) || '{}');
  24. let viewMode = 'hour'; // 'hour', 'day', 'session'
  25.  
  26. if (!localStorage.getItem(RESET_TIME_KEY)) {
  27. localStorage.setItem(RESET_TIME_KEY, Date.now());
  28. }
  29.  
  30. if (!localStorage.getItem(CYBER_GAIN_KEY)) {
  31. localStorage.setItem(CYBER_GAIN_KEY, '0');
  32. }
  33.  
  34. const trackerBox = document.createElement('div');
  35. trackerBox.style.position = 'fixed';
  36. trackerBox.style.top = localStorage.getItem(POS_KEY + '_top') || '10px';
  37. trackerBox.style.left = localStorage.getItem(POS_KEY + '_left') || '10px';
  38. trackerBox.style.background = '#1a214c';
  39. trackerBox.style.color = 'white';
  40. trackerBox.style.padding = '10px';
  41. trackerBox.style.fontSize = '13px';
  42. trackerBox.style.fontFamily = 'monospace';
  43. trackerBox.style.zIndex = 9999;
  44. trackerBox.style.cursor = 'move';
  45. trackerBox.style.minWidth = '220px';
  46. trackerBox.style.border = '1px solid #0f0';
  47. document.body.appendChild(trackerBox);
  48.  
  49. const timer = document.createElement('div');
  50. timer.id = 'cybroria-timer';
  51. timer.style.marginTop = '8px';
  52. timer.style.fontSize = '11px';
  53. timer.style.color = '#8f8';
  54. trackerBox.appendChild(timer);
  55.  
  56. makeDraggable(trackerBox);
  57. renderControls();
  58. updateTimer();
  59. setInterval(updateTimer, 1000);
  60.  
  61. function makeDraggable(el) {
  62. let offsetX, offsetY, dragging = false;
  63. el.addEventListener('mousedown', (e) => {
  64. if (e.target.tagName === 'BUTTON' || e.target.tagName === 'SELECT' || e.target.classList.contains('icon')) return;
  65. dragging = true;
  66. offsetX = e.clientX - el.offsetLeft;
  67. offsetY = e.clientY - el.offsetTop;
  68. });
  69. document.addEventListener('mousemove', (e) => {
  70. if (!dragging) return;
  71. el.style.left = `${e.clientX - offsetX}px`;
  72. el.style.top = `${e.clientY - offsetY}px`;
  73. });
  74. document.addEventListener('mouseup', () => {
  75. if (dragging) {
  76. localStorage.setItem(POS_KEY + '_top', el.style.top);
  77. localStorage.setItem(POS_KEY + '_left', el.style.left);
  78. }
  79. dragging = false;
  80. });
  81. }
  82.  
  83. function renderControls() {
  84. const controls = document.createElement('div');
  85. controls.style.marginTop = '8px';
  86.  
  87. const resetBtn = document.createElement('button');
  88. resetBtn.textContent = 'Reset';
  89. resetBtn.onclick = () => {
  90. if (confirm("Reset all loot stats?")) {
  91. timestamps = [];
  92. localStorage.removeItem(STORAGE_KEY);
  93. localStorage.setItem(RESET_TIME_KEY, Date.now());
  94. localStorage.setItem(CYBER_GAIN_KEY, '0');
  95. localStorage.removeItem(CYBER_BASE_KEY);
  96. updateBox();
  97. }
  98. };
  99.  
  100. const modeSelect = document.createElement('select');
  101. modeSelect.style.marginLeft = '6px';
  102. ['hour', 'day', 'session'].forEach(mode => {
  103. const opt = document.createElement('option');
  104. opt.value = mode;
  105. opt.textContent = `Per ${mode}`;
  106. modeSelect.appendChild(opt);
  107. });
  108. modeSelect.value = viewMode;
  109. modeSelect.onchange = () => {
  110. viewMode = modeSelect.value;
  111. updateBox();
  112. };
  113.  
  114. const settingsBtn = document.createElement('span');
  115. settingsBtn.textContent = '⚙️';
  116. settingsBtn.title = 'Set values';
  117. settingsBtn.className = 'icon';
  118. settingsBtn.style.marginLeft = '8px';
  119. settingsBtn.style.cursor = 'pointer';
  120. settingsBtn.onclick = showSettingsPopup;
  121.  
  122. controls.appendChild(resetBtn);
  123. controls.appendChild(modeSelect);
  124. controls.appendChild(settingsBtn);
  125. trackerBox.appendChild(controls);
  126. }
  127.  
  128. function formatNumber(n) {
  129. return n.toLocaleString();
  130. }
  131.  
  132. function updateBox() {
  133. localStorage.setItem(STORAGE_KEY, JSON.stringify(timestamps));
  134. const now = Date.now();
  135. const hourAgo = now - 3600000;
  136. const dayAgo = now - 86400000;
  137.  
  138. const stats = {};
  139. timestamps.forEach(entry => {
  140. const show = viewMode === 'session' ||
  141. (viewMode === 'hour' && entry.time >= hourAgo) ||
  142. (viewMode === 'day' && entry.time >= dayAgo);
  143. if (!show) return;
  144. if (!stats[entry.item]) stats[entry.item] = 0;
  145. stats[entry.item] += entry.amount;
  146. });
  147.  
  148. let totalIncome = 0;
  149. const html = [];
  150. html.push('<strong>Cybroria Loot Tracker</strong><br><br>');
  151. html.push(`<u>Per ${viewMode.charAt(0).toUpperCase() + viewMode.slice(1)}:</u><br><br>`);
  152.  
  153. function renderGroup(title, items, calculateValue = false) {
  154. const group = [];
  155. let hasItems = false;
  156. items.forEach(item => {
  157. if (stats[item]) {
  158. const amount = stats[item];
  159. hasItems = true;
  160. const displayAmount = formatNumber(amount);
  161. if (calculateValue && lootValues[item]) {
  162. const value = amount * lootValues[item];
  163. totalIncome += value;
  164. }
  165. group.push(`${item}: ${displayAmount}`);
  166. }
  167. });
  168. return hasItems ? `<strong>${title}:</strong><br>${group.join('<br>')}<br><br>` : '';
  169. }
  170.  
  171. html.push(renderGroup("Fighting Stats", ["Strength", "Agility", "Dexterity", "Vitality"]));
  172. html.push(renderGroup("Extraction Stats", ["Energy Tap", "System Breach", "Chemsynthesis", "Cyber Harvest"]));
  173.  
  174. const currencyHtml = [];
  175. ["Credits", "Artifacts"].forEach(item => {
  176. if (stats[item]) {
  177. const val = lootValues[item] ? stats[item] * lootValues[item] : 0;
  178. if (val) totalIncome += val;
  179. currencyHtml.push(`${item}: ${formatNumber(stats[item])}`);
  180. }
  181. });
  182.  
  183. const cyberGain = parseInt(localStorage.getItem(CYBER_GAIN_KEY), 10);
  184. if (cyberGain > 0) {
  185. const val = lootValues["Cyber Components"] ? cyberGain * lootValues["Cyber Components"] : 0;
  186. if (val) totalIncome += val;
  187. currencyHtml.push(`Cyber Components: ${formatNumber(cyberGain)}`);
  188. }
  189.  
  190. if (currencyHtml.length) {
  191. html.push(`<strong>Currency:</strong><br>${currencyHtml.join('<br>')}<br><br>`);
  192. }
  193.  
  194. html.push(renderGroup("Extraction Loot", ["Power Cells", "Logic Cores", "Cyber Implants", "Neuro Stims"], true));
  195.  
  196. if (totalIncome > 0) {
  197. html.push(`<strong>Total Income:</strong> ${formatNumber(totalIncome)} credits<br>`);
  198. }
  199.  
  200. const oldTimer = document.getElementById('cybroria-timer');
  201. trackerBox.innerHTML = html.join('');
  202. renderControls();
  203. if (oldTimer) trackerBox.appendChild(oldTimer);
  204. }
  205.  
  206. function updateTimer() {
  207. const resetTime = parseInt(localStorage.getItem(RESET_TIME_KEY), 10);
  208. const seconds = Math.floor((Date.now() - resetTime) / 1000);
  209. const hrs = String(Math.floor(seconds / 3600)).padStart(2, '0');
  210. const mins = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
  211. const secs = String(seconds % 60).padStart(2, '0');
  212. const timerEl = document.getElementById('cybroria-timer');
  213. if (timerEl) {
  214. timerEl.textContent = `⏱ since last reset: ${hrs}:${mins}:${secs}`;
  215. }
  216. }
  217.  
  218. function showSettingsPopup() {
  219. const popup = document.createElement('div');
  220. popup.style.position = 'fixed';
  221. popup.style.top = '50%';
  222. popup.style.left = '50%';
  223. popup.style.transform = 'translate(-50%, -50%)';
  224. popup.style.background = '#222';
  225. popup.style.color = '#fff';
  226. popup.style.padding = '20px';
  227. popup.style.border = '2px solid #0f0';
  228. popup.style.zIndex = 10000;
  229. popup.style.maxHeight = '80vh';
  230. popup.style.overflowY = 'auto';
  231.  
  232. const closeBtn = document.createElement('button');
  233. closeBtn.textContent = 'Close';
  234. closeBtn.style.marginTop = '10px';
  235. closeBtn.onclick = () => popup.remove();
  236.  
  237. const inputItems = ["Artifacts", "Power Cells", "Logic Cores", "Cyber Implants", "Neuro Stims", "Cyber Components"];
  238. lootValues.Credits = 1;
  239. inputItems.forEach(item => {
  240. const label = document.createElement('label');
  241. label.textContent = `${item} price: `;
  242. label.style.display = 'block';
  243.  
  244. const input = document.createElement('input');
  245. input.type = 'number';
  246. input.value = lootValues[item] || '';
  247. input.style.marginBottom = '6px';
  248. input.onchange = () => {
  249. lootValues[item] = parseFloat(input.value) || 0;
  250. localStorage.setItem(VALUE_KEY, JSON.stringify(lootValues));
  251. updateBox();
  252. };
  253.  
  254. label.appendChild(input);
  255. popup.appendChild(label);
  256. });
  257.  
  258. popup.appendChild(closeBtn);
  259. document.body.appendChild(popup);
  260. }
  261.  
  262. function parseLootText(text) {
  263. text = text.replace(/\u00A0/g, ' ')
  264. .replace(/\s+/g, ' ')
  265. .replace(/\([\d,]+ for your syndicate\)/g, '')
  266. .trim();
  267.  
  268. const statValueMatch = text.match(/You have found ([\d,]+) ([A-Za-z ]+?) Stat Value/i);
  269. if (statValueMatch) {
  270. const amount = parseInt(statValueMatch[1].replace(/,/g, ''), 10);
  271. const statName = statValueMatch[2].trim();
  272. if (trackedTypes.includes(statName)) {
  273. timestamps.push({ time: Date.now(), item: statName, amount });
  274. updateBox();
  275. }
  276. return;
  277. }
  278.  
  279. const lootMatch = text.match(/You have found ([\d,]+) ([A-Za-z ]+)/);
  280. if (lootMatch) {
  281. const qty = parseInt(lootMatch[1].replace(/,/g, ''), 10);
  282. const item = lootMatch[2].trim();
  283. if (trackedTypes.includes(item)) {
  284. timestamps.push({ time: Date.now(), item, amount: qty });
  285. updateBox();
  286. }
  287. }
  288. }
  289.  
  290. const trackedTypes = [
  291. "Strength", "Agility", "Dexterity", "Vitality",
  292. "Energy Tap", "System Breach", "Chemsynthesis", "Cyber Harvest",
  293. "Credits", "Power Cells", "Logic Cores", "Artifacts",
  294. "Neuro Stims", "Cyber Implants"
  295. ];
  296.  
  297. function observeLootLog() {
  298. const seenLines = new Set();
  299. setInterval(() => {
  300. const logSpans = document.querySelectorAll('app-loot-log span.ng-star-inserted');
  301. logSpans.forEach(span => {
  302. const rawText = span.textContent.trim();
  303. if (rawText.includes("You have found") && !seenLines.has(rawText)) {
  304. seenLines.add(rawText);
  305. parseLootText(rawText);
  306. }
  307. });
  308. }, 1000);
  309. }
  310.  
  311. function trackCyberComponentDelta() {
  312. setInterval(() => {
  313. const el = Array.from(document.querySelectorAll('*')).find(e =>
  314. e.textContent && e.textContent.includes("Cyber Components")
  315. );
  316. if (el) {
  317. const match = el.textContent.match(/Cyber Components\s*:?[\s]*([\d,]+)/i);
  318. if (match) {
  319. const current = parseInt(match[1].replace(/,/g, ''), 10);
  320. const base = parseInt(localStorage.getItem(CYBER_BASE_KEY) || current);
  321. const gain = current - base;
  322. localStorage.setItem(CYBER_GAIN_KEY, gain.toString());
  323. localStorage.setItem(CYBER_BASE_KEY, base.toString());
  324. updateBox();
  325. }
  326. }
  327. }, 2000);
  328. }
  329.  
  330. window.addEventListener('load', () => {
  331. setTimeout(() => {
  332. observeLootLog();
  333. trackCyberComponentDelta();
  334. }, 3000);
  335. updateBox();
  336. });
  337. })();