// ==UserScript==
// @name Cybroria Loot Tracker v4.3 (Complete)
// @namespace http://tampermonkey.net/
// @version 4.3
// @description Tracks loot, stats, Cyber Components, with theme switcher, keyboard shortcut, opacity control, click-through toggle, and collapsible controls.
// @author Skydragon
// @match https://cybroria.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'cybroria_loot_stats';
const VALUE_KEY = 'cybroria_loot_values';
const POS_KEY = 'cybroria_tracker_position';
const RESET_TIME_KEY = 'cybroria_reset_time';
const CYBER_BASE_KEY = 'cybroria_cyber_components_base';
const CYBER_GAIN_KEY = 'cybroria_cyber_components_gain';
const OPACITY_KEY = 'cybroria_tracker_opacity';
const THEME_KEY = 'cybroria_tracker_theme';
const CLICKTHRU_KEY = 'cybroria_tracker_clickthrough';
const UI_COLLAPSE_KEY = 'cybroria_tracker_controls_hidden';
let timestamps = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
let lootValues = JSON.parse(localStorage.getItem(VALUE_KEY) || '{}');
let viewMode = 'hour';
let panelOpacity = parseFloat(localStorage.getItem(OPACITY_KEY)) || 0.6;
let theme = localStorage.getItem(THEME_KEY) || 'dark';
let clickThrough = localStorage.getItem(CLICKTHRU_KEY) === 'true';
let controlsHidden = localStorage.getItem(UI_COLLAPSE_KEY) === 'true';
const trackedTypes = [
"Strength", "Agility", "Dexterity", "Vitality",
"Energy Tap", "System Breach", "Chemsynthesis", "Cyber Harvest",
"Credits", "Power Cells", "Logic Cores", "Artifacts",
"Neuro Stims", "Cyber Implants", "Cyber Components"
];
if (!localStorage.getItem(RESET_TIME_KEY)) {
localStorage.setItem(RESET_TIME_KEY, Date.now());
}
if (!localStorage.getItem(CYBER_GAIN_KEY)) {
localStorage.setItem(CYBER_GAIN_KEY, '0');
}
const trackerBox = document.createElement('div');
trackerBox.id = 'cybroria-tracker';
trackerBox.style.position = 'fixed';
trackerBox.style.top = localStorage.getItem(POS_KEY + '_top') || '10px';
trackerBox.style.left = localStorage.getItem(POS_KEY + '_left') || '10px';
applyPanelStyles();
function applyPanelStyles() {
const base = trackerBox.style;
const glow = '0 0 12px rgba(0,255,100,0.4)';
const light = '0 0 8px rgba(255,255,255,0.3)';
base.background = theme === 'dark' ? `rgba(0, 0, 0, ${panelOpacity})` : `rgba(255, 255, 255, ${panelOpacity})`;
base.color = theme === 'dark' ? '#cfc' : '#333';
base.borderRadius = '10px';
base.boxShadow = theme === 'dark' ? glow : light;
base.border = theme === 'dark' ? '1px solid rgba(0,255,100,0.3)' : '1px solid rgba(0,0,0,0.2)';
base.textShadow = theme === 'dark' ? '0 0 4px rgba(0,255,100,0.3)' : 'none';
base.fontFamily = 'monospace';
base.fontSize = '13px';
base.padding = '12px';
base.zIndex = 9999;
base.cursor = 'move';
base.minWidth = '240px';
base.pointerEvents = clickThrough ? 'none' : 'auto';
}
document.body.appendChild(trackerBox);
const timer = document.createElement('div');
timer.id = 'cybroria-timer';
timer.style.marginTop = '8px';
timer.style.fontSize = '11px';
timer.style.color = theme === 'dark' ? '#8f8' : '#555';
trackerBox.appendChild(timer);
function makeDraggable(el) {
let offsetX, offsetY, dragging = false;
el.addEventListener('mousedown', (e) => {
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'SELECT' || e.target.classList.contains('icon')) return;
dragging = true;
offsetX = e.clientX - el.offsetLeft;
offsetY = e.clientY - el.offsetTop;
});
document.addEventListener('mousemove', (e) => {
if (!dragging) return;
el.style.left = `${e.clientX - offsetX}px`;
el.style.top = `${e.clientY - offsetY}px`;
});
document.addEventListener('mouseup', () => {
if (dragging) {
localStorage.setItem(POS_KEY + '_top', el.style.top);
localStorage.setItem(POS_KEY + '_left', el.style.left);
}
dragging = false;
});
}
makeDraggable(trackerBox);
// Ctrl+L to toggle visibility
window.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key.toLowerCase() === 'l') {
trackerBox.style.display = trackerBox.style.display === 'none' ? 'block' : 'none';
}
});
function renderControls() {
const controlsWrapper = document.createElement('div');
controlsWrapper.id = 'tracker-controls';
const collapseToggle = document.createElement('div');
collapseToggle.textContent = controlsHidden ? '[+]' : '[–]';
collapseToggle.style.cursor = 'pointer';
collapseToggle.style.marginBottom = '6px';
collapseToggle.style.fontWeight = 'bold';
collapseToggle.onclick = () => {
controlsHidden = !controlsHidden;
localStorage.setItem(UI_COLLAPSE_KEY, controlsHidden);
updateBox();
};
trackerBox.appendChild(collapseToggle);
if (controlsHidden) return;
const resetBtn = document.createElement('button');
resetBtn.textContent = 'Reset';
resetBtn.onclick = () => {
if (confirm('Reset all loot stats?')) {
timestamps = [];
localStorage.removeItem(STORAGE_KEY);
localStorage.setItem(RESET_TIME_KEY, Date.now());
localStorage.setItem(CYBER_GAIN_KEY, '0');
localStorage.removeItem(CYBER_BASE_KEY);
updateBox();
}
};
const exportBtn = document.createElement('button');
exportBtn.textContent = 'Export CSV';
exportBtn.style.marginLeft = '6px';
exportBtn.onclick = exportCSV;
const modeSelect = document.createElement('select');
modeSelect.style.marginLeft = '6px';
['hour', 'day', 'session'].forEach(mode => {
const opt = document.createElement('option');
opt.value = mode;
opt.textContent = `Per ${mode}`;
modeSelect.appendChild(opt);
});
modeSelect.value = viewMode;
modeSelect.onchange = () => {
viewMode = modeSelect.value;
updateBox();
};
const settingsBtn = document.createElement('span');
settingsBtn.textContent = '⚙️';
settingsBtn.title = 'Set values';
settingsBtn.className = 'icon';
settingsBtn.style.marginLeft = '8px';
settingsBtn.style.cursor = 'pointer';
settingsBtn.onclick = showSettingsPopup;
controlsWrapper.appendChild(resetBtn);
controlsWrapper.appendChild(exportBtn);
controlsWrapper.appendChild(modeSelect);
controlsWrapper.appendChild(settingsBtn);
trackerBox.appendChild(controlsWrapper);
}
function updateTimer() {
const resetTime = parseInt(localStorage.getItem(RESET_TIME_KEY), 10);
const seconds = Math.floor((Date.now() - resetTime) / 1000);
const hrs = String(Math.floor(seconds / 3600)).padStart(2, '0');
const mins = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
const secs = String(seconds % 60).padStart(2, '0');
const timerEl = document.getElementById('cybroria-timer');
if (timerEl) {
timerEl.textContent = `⏱ since last reset: ${hrs}:${mins}:${secs}`;
}
}
setInterval(updateTimer, 1000);
function exportCSV() {
let csv = 'Item,Amount\n';
const totals = {};
timestamps.forEach(t => {
if (!totals[t.item]) totals[t.item] = 0;
totals[t.item] += t.amount;
});
for (const item in totals) {
csv += `${item},${totals[item]}\n`;
}
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'cybroria_loot.csv';
link.click();
URL.revokeObjectURL(url);
}
function showSettingsPopup() {
const popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.background = '#222';
popup.style.color = '#fff';
popup.style.padding = '20px';
popup.style.border = '2px solid #0f0';
popup.style.zIndex = 10000;
popup.style.maxHeight = '80vh';
popup.style.overflowY = 'auto';
const closeBtn = document.createElement('button');
closeBtn.textContent = 'Close';
closeBtn.style.marginTop = '10px';
closeBtn.onclick = () => popup.remove();
const inputItems = [
"Artifacts", "Power Cells", "Logic Cores", "Cyber Implants",
"Neuro Stims", "Cyber Components"
];
lootValues.Credits = 1;
inputItems.forEach(item => {
const label = document.createElement('label');
label.textContent = `${item} price: `;
label.style.display = 'block';
const input = document.createElement('input');
input.type = 'number';
input.value = lootValues[item] || '';
input.style.marginBottom = '6px';
input.onchange = () => {
lootValues[item] = parseFloat(input.value) || 0;
localStorage.setItem(VALUE_KEY, JSON.stringify(lootValues));
updateBox();
};
label.appendChild(input);
popup.appendChild(label);
});
// Opacity Slider
const opacityLabel = document.createElement('label');
opacityLabel.textContent = `Panel Opacity: ${Math.round(panelOpacity * 100)}%`;
opacityLabel.style.display = 'block';
const opacitySlider = document.createElement('input');
opacitySlider.type = 'range';
opacitySlider.min = '0.1';
opacitySlider.max = '1';
opacitySlider.step = '0.05';
opacitySlider.value = panelOpacity;
opacitySlider.oninput = () => {
panelOpacity = parseFloat(opacitySlider.value);
localStorage.setItem(OPACITY_KEY, panelOpacity);
opacityLabel.textContent = `Panel Opacity: ${Math.round(panelOpacity * 100)}%`;
applyPanelStyles();
};
opacityLabel.appendChild(opacitySlider);
popup.appendChild(opacityLabel);
// Theme Switch
const themeLabel = document.createElement('label');
themeLabel.textContent = `Theme: `;
themeLabel.style.display = 'block';
const themeSelect = document.createElement('select');
['dark', 'light'].forEach(t => {
const opt = document.createElement('option');
opt.value = t;
opt.textContent = t.charAt(0).toUpperCase() + t.slice(1);
themeSelect.appendChild(opt);
});
themeSelect.value = theme;
themeSelect.onchange = () => {
theme = themeSelect.value;
localStorage.setItem(THEME_KEY, theme);
applyPanelStyles();
updateBox();
};
themeLabel.appendChild(themeSelect);
popup.appendChild(themeLabel);
// Click-through toggle
const clickBox = document.createElement('input');
clickBox.type = 'checkbox';
clickBox.checked = clickThrough;
clickBox.onchange = () => {
clickThrough = clickBox.checked;
localStorage.setItem(CLICKTHRU_KEY, clickThrough);
applyPanelStyles();
};
const clickLabel = document.createElement('label');
clickLabel.textContent = ' Enable Click-Through Mode';
clickLabel.prepend(clickBox);
clickLabel.style.display = 'block';
popup.appendChild(clickLabel);
popup.appendChild(closeBtn);
document.body.appendChild(popup);
}
function updateBox() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(timestamps));
const now = Date.now();
const hourAgo = now - 3600000;
const dayAgo = now - 86400000;
const stats = {};
timestamps.forEach(entry => {
const show = viewMode === 'session' ||
(viewMode === 'hour' && entry.time >= hourAgo) ||
(viewMode === 'day' && entry.time >= dayAgo);
if (!show) return;
if (!stats[entry.item]) stats[entry.item] = 0;
stats[entry.item] += entry.amount;
});
let totalIncome = 0;
const html = [];
html.push('<strong>Cybroria Loot Tracker</strong><br><br>');
html.push(`<u>Per ${viewMode.charAt(0).toUpperCase() + viewMode.slice(1)}:</u><br><br>`);
const sections = {
"Fighting Stats": ["Strength", "Agility", "Dexterity", "Vitality"],
"Extraction Stats": ["Energy Tap", "System Breach", "Chemsynthesis", "Cyber Harvest"],
"Currency": ["Credits", "Artifacts", "Cyber Components"],
"Extraction Loot": ["Power Cells", "Logic Cores", "Cyber Implants", "Neuro Stims"]
};
for (const [title, items] of Object.entries(sections)) {
const group = items.map(item => {
if (!stats[item]) return null;
const amount = stats[item];
const value = lootValues[item] ? amount * lootValues[item] : 0;
totalIncome += value;
return `${item}: ${amount.toLocaleString()}`;
}).filter(Boolean);
if (group.length) {
html.push(`<strong>${title}:</strong><br>${group.join('<br>')}<br><br>`);
}
}
if (totalIncome > 0) {
html.push(`<strong>Total Income:</strong> ${totalIncome.toLocaleString()} credits<br>`);
}
trackerBox.innerHTML = html.join('');
trackerBox.appendChild(timer);
renderControls();
}
function parseLootText(text) {
text = text.replace(/\u00A0/g, ' ')
.replace(/\s+/g, ' ')
.replace(/\([\d,]+ for your syndicate\)/g, '')
.trim();
const statMatch = text.match(/You have found ([\\d,]+) ([A-Za-z ]+?) Stat Value/i);
if (statMatch) {
const amount = parseInt(statMatch[1].replace(/,/g, ''), 10);
const statName = statMatch[2].trim();
if (trackedTypes.includes(statName)) {
timestamps.push({ time: Date.now(), item: statName, amount });
updateBox();
}
return;
}
const lootMatch = text.match(/You have found ([\\d,]+) ([A-Za-z ]+)/);
if (lootMatch) {
const qty = parseInt(lootMatch[1].replace(/,/g, ''), 10);
const item = lootMatch[2].trim();
if (trackedTypes.includes(item)) {
timestamps.push({ time: Date.now(), item, amount: qty });
updateBox();
}
}
}
function observeLootLog() {
const seenLines = new Set();
setInterval(() => {
const logSpans = document.querySelectorAll('app-loot-log span.ng-star-inserted');
logSpans.forEach(span => {
const rawText = span.textContent.trim();
if (rawText.includes('You have found') && !seenLines.has(rawText)) {
seenLines.add(rawText);
parseLootText(rawText);
}
});
}, 1000);
}
function trackCyberComponentDelta() {
setInterval(() => {
const el = Array.from(document.querySelectorAll('*')).find(e =>
e.textContent && e.textContent.includes('Cyber Components')
);
if (el) {
const match = el.textContent.match(/Cyber Components\\s*:?\\s*([\\d,]+)/i);
if (match) {
const current = parseInt(match[1].replace(/,/g, ''), 10);
const base = parseInt(localStorage.getItem(CYBER_BASE_KEY) || current);
const gain = current - base;
localStorage.setItem(CYBER_GAIN_KEY, gain.toString());
localStorage.setItem(CYBER_BASE_KEY, base.toString());
updateBox();
}
}
}, 2000);
}
window.addEventListener('load', () => {
setTimeout(() => {
observeLootLog();
trackCyberComponentDelta();
}, 3000);
updateBox();
});
})();