Cybroria Loot Tracker with Timer and Value Calculator

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Cybroria Loot Tracker with Timer and Value Calculator
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Tracks loot, timer, income, and Cyber Components delta separately.
// @author       Skydragon + Edit
// @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';

    let timestamps = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
    let lootValues = JSON.parse(localStorage.getItem(VALUE_KEY) || '{}');
    let viewMode = 'hour'; // 'hour', 'day', 'session'

    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.style.position = 'fixed';
    trackerBox.style.top = localStorage.getItem(POS_KEY + '_top') || '10px';
    trackerBox.style.left = localStorage.getItem(POS_KEY + '_left') || '10px';
    trackerBox.style.background = '#1a214c';
    trackerBox.style.color = 'white';
    trackerBox.style.padding = '10px';
    trackerBox.style.fontSize = '13px';
    trackerBox.style.fontFamily = 'monospace';
    trackerBox.style.zIndex = 9999;
    trackerBox.style.cursor = 'move';
    trackerBox.style.minWidth = '220px';
    trackerBox.style.border = '1px solid #0f0';
    document.body.appendChild(trackerBox);

    const timer = document.createElement('div');
    timer.id = 'cybroria-timer';
    timer.style.marginTop = '8px';
    timer.style.fontSize = '11px';
    timer.style.color = '#8f8';
    trackerBox.appendChild(timer);

    makeDraggable(trackerBox);
    renderControls();
    updateTimer();
    setInterval(updateTimer, 1000);

    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;
        });
    }

    function renderControls() {
        const controls = document.createElement('div');
        controls.style.marginTop = '8px';

        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 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;

        controls.appendChild(resetBtn);
        controls.appendChild(modeSelect);
        controls.appendChild(settingsBtn);
        trackerBox.appendChild(controls);
    }

    function formatNumber(n) {
        return n.toLocaleString();
    }

    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>`);

        function renderGroup(title, items, calculateValue = false) {
            const group = [];
            let hasItems = false;
            items.forEach(item => {
                if (stats[item]) {
                    const amount = stats[item];
                    hasItems = true;
                    const displayAmount = formatNumber(amount);
                    if (calculateValue && lootValues[item]) {
                        const value = amount * lootValues[item];
                        totalIncome += value;
                    }
                    group.push(`${item}: ${displayAmount}`);
                }
            });
            return hasItems ? `<strong>${title}:</strong><br>${group.join('<br>')}<br><br>` : '';
        }

        html.push(renderGroup("Fighting Stats", ["Strength", "Agility", "Dexterity", "Vitality"]));
        html.push(renderGroup("Extraction Stats", ["Energy Tap", "System Breach", "Chemsynthesis", "Cyber Harvest"]));

        const currencyHtml = [];
        ["Credits", "Artifacts"].forEach(item => {
            if (stats[item]) {
                const val = lootValues[item] ? stats[item] * lootValues[item] : 0;
                if (val) totalIncome += val;
                currencyHtml.push(`${item}: ${formatNumber(stats[item])}`);
            }
        });

        const cyberGain = parseInt(localStorage.getItem(CYBER_GAIN_KEY), 10);
        if (cyberGain > 0) {
            const val = lootValues["Cyber Components"] ? cyberGain * lootValues["Cyber Components"] : 0;
            if (val) totalIncome += val;
            currencyHtml.push(`Cyber Components: ${formatNumber(cyberGain)}`);
        }

        if (currencyHtml.length) {
            html.push(`<strong>Currency:</strong><br>${currencyHtml.join('<br>')}<br><br>`);
        }

        html.push(renderGroup("Extraction Loot", ["Power Cells", "Logic Cores", "Cyber Implants", "Neuro Stims"], true));

        if (totalIncome > 0) {
            html.push(`<strong>Total Income:</strong> ${formatNumber(totalIncome)} credits<br>`);
        }

        const oldTimer = document.getElementById('cybroria-timer');
        trackerBox.innerHTML = html.join('');
        renderControls();
        if (oldTimer) trackerBox.appendChild(oldTimer);
    }

    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}`;
        }
    }

    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);
        });

        popup.appendChild(closeBtn);
        document.body.appendChild(popup);
    }

    function parseLootText(text) {
        text = text.replace(/\u00A0/g, ' ')
                   .replace(/\s+/g, ' ')
                   .replace(/\([\d,]+ for your syndicate\)/g, '')
                   .trim();

        const statValueMatch = text.match(/You have found ([\d,]+) ([A-Za-z ]+?) Stat Value/i);
        if (statValueMatch) {
            const amount = parseInt(statValueMatch[1].replace(/,/g, ''), 10);
            const statName = statValueMatch[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();
            }
        }
    }

    const trackedTypes = [
        "Strength", "Agility", "Dexterity", "Vitality",
        "Energy Tap", "System Breach", "Chemsynthesis", "Cyber Harvest",
        "Credits", "Power Cells", "Logic Cores", "Artifacts",
        "Neuro Stims", "Cyber Implants"
    ];

    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();
    });
})();