BW WAR Tracker

Chain Queue & Enemy Faction Tracker

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         BW WAR Tracker
// @namespace    http://tampermonkey.net/
// @version      11
// @description  Chain Queue & Enemy Faction Tracker
// @author       Tu
// @match        https://www.torn.com/*
// @icon         https://www.torn.com/favicon.ico
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// @connect      bw-war-tracker-default-rtdb.europe-west1.firebasedatabase.app
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    const ADMIN_ID = 3946434; // ID-ul tau
    const DB_URL = "https://bw-war-tracker-default-rtdb.europe-west1.firebasedatabase.app";

    let currentUser = null;
    let targetsList = [];
    let queueList = [];
    let queueActive = false;
    let lastTargetsJson = "";
    let lastQueueJson = "";
    let isPolling = false;
    let pollingInterval = null;
    let statusInterval = null;

    function initScript() {
        if (document.body) {
            try { setupScript(); } catch (e) { console.error("Init Error:", e); }
        } else { setTimeout(initScript, 100); }
    }

    function setupScript() {
        GM_addStyle(`
            #bw-war-button { position: fixed !important; bottom: 20px !important; left: 20px !important; background-color: #000 !important; color: #fff !important; border: 2px solid #fff !important; padding: 12px 24px !important; font-size: 14px !important; font-weight: bold; cursor: pointer; border-radius: 5px; z-index: 2147483647 !important; box-shadow: 0 2px 10px rgba(0,0,0,0.5) !important; transition: all 0.3s !important; }
            #bw-war-button:hover { background-color: #333 !important; transform: translateY(-2px) !important; }
            #bw-war-modal, #bw-settings-modal { display: none !important; position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 90% !important; max-width: 1200px !important; height: 85% !important; background-color: rgba(20, 20, 20, 0.98) !important; border: 2px solid #444 !important; border-radius: 10px !important; z-index: 2147483647 !important; box-shadow: 0 10px 40px rgba(0,0,0,0.9) !important; color: #fff !important; font-family: Arial, sans-serif !important; }
            #bw-war-modal.active, #bw-settings-modal.active { display: flex !important; flex-direction: column !important; overflow: hidden !important; }
            #bw-war-overlay { display: none !important; position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background-color: rgba(0,0,0,0.85) !important; z-index: 2147483646 !important; backdrop-filter: blur(2px) !important; }
            #bw-war-overlay.active { display: block !important; }
            .bw-modal-header { background: #000 !important; padding: 15px !important; border-bottom: 1px solid #333 !important; display: flex !important; flex-direction: column !important; align-items: center !important; position: relative !important; }
            .bw-modal-title { font-size: 22px !important; font-weight: 900 !important; text-transform: uppercase !important; letter-spacing: 1px !important; margin-top: 10px !important; }
            .bw-header-buttons { position: absolute !important; top: 15px !important; right: 15px !important; display: flex !important; gap: 10px !important; }
            .bw-settings-button, .bw-close-button, .bw-back-button { background: transparent !important; border: 1px solid #fff !important; color: #fff !important; width: 30px !important; height: 30px !important; border-radius: 50% !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; font-size: 14px !important; }
            .bw-back-button { position: absolute !important; left: 15px !important; top: 15px !important; font-size: 18px !important; border: none !important; width: auto !important; }
            .bw-user-info { position: absolute !important; left: 15px !important; top: 50% !important; transform: translateY(-50%) !important; display: flex !important; align-items: center !important; gap: 15px !important; width: 100%; }
            .bw-user-details { display: flex; flex-direction: column; }
            .bw-user-stats { font-size: 11px !important; color: #888 !important; }
            .bw-user-name { color: #fff !important; font-weight: bold !important; text-decoration: none !important; }
            .bw-header-queue { color: #2196F3; font-weight: bold; border: 1px solid #2196F3; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
            .bw-header-queue.full { color: #ff4444; border-color: #ff4444; }
            .bw-tabs { display: flex !important; background: #111 !important; border-bottom: 1px solid #333 !important; }
            .bw-tab { flex: 1 !important; padding: 15px !important; background: #111 !important; color: #666 !important; border: none !important; cursor: pointer !important; font-weight: bold !important; text-transform: uppercase !important; transition: 0.2s !important; }
            .bw-tab:hover { color: #fff !important; background: #222 !important; }
            .bw-tab.active { background: #000 !important; color: #fff !important; border-bottom: 3px solid #fff !important; }
            .bw-tab-content { display: none !important; padding: 20px !important; overflow-y: auto !important; flex: 1 !important; }
            .bw-tab-content.active { display: block !important; }
            .bw-target-item, .bw-queue-item { background: #0a0a0a !important; border: 1px solid #333 !important; padding: 8px 15px !important; margin-bottom: 6px !important; border-radius: 4px !important; display: flex !important; justify-content: space-between !important; align-items: center !important; }
            .bw-target-name { color: #fff !important; font-weight: bold !important; text-decoration: none !important; font-size: 15px !important; pointer-events: auto !important; }
            .bw-target-name:hover { text-decoration: underline !important; color: #ccc !important; }
            .bw-input-section { display: flex !important; gap: 10px !important; margin-bottom: 10px !important; align-items: center !important; }
            .bw-input-field { flex: 1 !important; padding: 10px !important; background: #000 !important; border: 1px solid #444 !important; color: #fff !important; border-radius: 4px !important; }
            .bw-btn { background: #000 !important; color: #fff !important; border: 1px solid #fff !important; padding: 6px 15px !important; border-radius: 3px !important; cursor: pointer !important; font-weight: bold !important; font-size: 11px !important; text-transform: uppercase !important; }
            .bw-btn:hover { background: #fff !important; color: #000 !important; }
            .bw-btn-disabled { background: #333 !important; color: #888 !important; border-color: #555 !important; cursor: not-allowed !important; }
            .bw-btn-error { background: #ff0000 !important; border-color: #ff0000 !important; color: white !important; }
            .stat-red { color: #ff4444 !important; } .stat-orange { color: #ffbb33 !important; } .stat-yellow { color: #ffeb3b !important; } .stat-green { color: #00c851 !important; } .stat-lightgreen { color: #69f0ae !important; } .stat-blue { color: #33b5e5 !important; } .stat-white { color: #fff !important; }
            .bw-status-text { font-size: 10px !important; font-weight: bold !important; margin-left: 5px !important; }
            .bw-loading { color: #666 !important; text-align: center !important; padding: 40px !important; font-style: italic !important; }
            .bw-queue-toggle { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #333; }
            .bw-switch { position: relative; display: inline-block; width: 40px; height: 20px; }
            .bw-switch input { opacity: 0; width: 0; height: 0; }
            .bw-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #333; transition: .4s; border-radius: 20px; }
            .bw-slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
            input:checked + .bw-slider { background-color: #2196F3; }
            input:checked + .bw-slider:before { transform: translateX(20px); }
            .bw-queue-count { font-size: 12px; color: #888; }
            .bw-queue-info { font-size: 11px; color: #888; margin-top: 5px; }
            .bw-participants-list { position: absolute; right: 20px; top: 20px; width: 200px; background: #0a0a0a; border: 1px solid #333; border-radius: 5px; padding: 15px; max-height: calc(100% - 40px); overflow-y: auto; }
            .bw-participants-title { color: #fff; font-weight: bold; margin-bottom: 10px; text-align: center; border-bottom: 1px solid #333; padding-bottom: 5px; font-size: 12px; }
            .bw-participant-item { color: #aaa; font-size: 12px; padding: 5px 0; border-bottom: 1px solid #222; }
            .bw-stats-input { background: #222; border: 1px solid #444; color: #fff; padding: 3px 5px; font-size: 11px; width: 80px; margin-right: 5px; border-radius: 3px; }
            .bw-icon-btn { background: transparent; border: none; cursor: pointer; color: #888; padding: 0 4px; font-size: 14px; transition: color 0.2s; }
            .bw-icon-btn:hover { color: #fff; }
            .bw-stats-wrapper { font-size: 12px; margin-top: 3px; display: flex; align-items: center; height: 20px; }
        `);

        const button = document.createElement('button');
        button.id = 'bw-war-button';
        button.textContent = 'BW WAR';
        document.body.appendChild(button);

        const overlay = document.createElement('div');
        overlay.id = 'bw-war-overlay';
        document.body.appendChild(overlay);

        const modal = document.createElement('div');
        modal.id = 'bw-war-modal';
        modal.innerHTML = `
            <div class="bw-modal-header">
                <div class="bw-user-info">
                    <div class="bw-user-details">
                        <a href="#" target="_blank" class="bw-user-name" id="user-name-link">Loading...</a>
                        <span class="bw-user-stats" id="user-total-stats">Loading...</span>
                    </div>
                    <span id="header-queue-status" class="bw-header-queue">Q: 0/15</span>
                </div>
                <div class="bw-header-buttons"><button class="bw-settings-button">⚙️</button><button class="bw-close-button">✕</button></div>
                <img src="https://factionimages.torn.com/08099e78-0a69-4424-8ccd-5219338250ad-40039.png" style="max-width: 200px; height: 50px; margin-bottom: 10px;">
                <div class="bw-modal-title">Black & White War Manager</div>
            </div>
            <div class="bw-tabs">
                <button class="bw-tab active" data-tab="targets">Enemy Faction</button>
                <button class="bw-tab" data-tab="queue">Chain Queue</button>
            </div>
            <div class="bw-tab-content active" id="targets-content">
                <div class="bw-input-section" id="admin-import-section" style="display:none;">
                    <button id="clear-faction-btn" class="bw-btn">Clear</button>
                    <input type="text" id="faction-import-input" class="bw-input-field" placeholder="Faction ID">
                    <button id="import-faction-btn" class="bw-btn">Add</button>
                </div>
                <div id="targets-list"></div>
            </div>
            <div class="bw-tab-content" id="queue-content" style="position:relative;">
                <div class="bw-queue-toggle">
                    <span style="color:#fff;font-weight:bold;">CHAIN QUEUE</span>
                    <label class="bw-switch">
                        <input type="checkbox" id="queue-master-switch">
                        <span class="bw-slider"></span>
                    </label>
                    <span class="bw-queue-count" id="queue-count" style="display:none;">0/15</span>
                </div>
                <div id="queue-active-area" style="display:none;">
                    <div id="queue-current-display"></div>
                    <div style="color:#888;font-size:12px;margin:10px 0 5px;">Upcoming:</div>
                    <div id="queue-list"></div>
                </div>
                <div id="queue-closed-msg" style="text-align:center;color:#666;margin-top:40px;">QUEUE CLOSED</div>
                <div class="bw-participants-list" id="participants-panel">
                    <div class="bw-participants-title">PARTICIPANTS</div>
                    <div id="participants-list"></div>
                </div>
            </div>
        `;
        document.body.appendChild(modal);

        const settingsModal = document.createElement('div');
        settingsModal.id = 'bw-settings-modal';
        settingsModal.innerHTML = `
            <div class="bw-modal-header">
                <button class="bw-back-button">←</button>
                <div class="bw-header-buttons"><button class="bw-close-button settings-close">✕</button></div>
                <div class="bw-modal-title">Settings</div>
            </div>
            <div style="padding: 40px; display: flex; flex-direction: column; align-items: center;">
                <label style="color: #fff; margin-bottom: 10px; font-weight: bold;">Torn API Key (Limited/Full)</label>
                <div style="display: flex; width: 100%; max-width: 300px; gap: 10px;">
                    <input type="text" id="api-key-input" class="bw-input-field" placeholder="Enter Key" style="text-align: center;">
                    <button id="save-api-key" class="bw-btn">Save</button>
                </div>
                <div style="color:#666;font-size:10px;margin-top:10px;">Requires Limited Access for Stats</div>
            </div>
        `;
        document.body.appendChild(settingsModal);

        // HELPERS
        function getApiKey() { return GM_getValue('torn_api_key', ''); }
        function formatBigNumber(num) { if (!num) return '0'; if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B'; if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K'; return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); }
        function convertToK(num) { return Math.round(num / 1000).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); }
        function formatStatsDisplay(valInK) { if (!valInK || valInK === "N/A") return "???"; let num = parseFloat(valInK.toString().replace(/,/g, '')); if (num >= 1000000) return (num / 1000000).toFixed(1).replace('.0', '') + 'B'; if (num >= 1000) return (num / 1000).toFixed(1).replace('.0', '') + 'M'; return Math.round(num) + 'k'; }
        function getStatusHtml(status) { if (!status) return ''; let color = '#fff'; let text = status.state; if (status.state === 'Okay') { color = '#00ff00'; text = 'OK'; } else if (status.state === 'Hospital') { color = '#ff4444'; if (status.until) { let remaining = status.until - (Date.now() / 1000); let mins = Math.max(0, Math.floor(remaining / 60)); text = `H (${mins}m)`; } else text = 'Hosp'; } else if (status.state === 'Traveling' || status.state === 'Abroad') { color = '#00ccff'; text = 'Abroad'; } return `<span class="bw-status-text" style="color:${color}">[${text}]</span>`; }
        function tornApiCall(endpoint) { return new Promise((resolve, reject) => { const key = getApiKey(); if (!key) { reject('No API Key'); return; } GM_xmlhttpRequest({ method: 'GET', url: `https://api.torn.com/${endpoint}&key=${key}`, onload: (res) => { try { const d = JSON.parse(res.responseText); if (d.error) reject(d.error.error); else resolve(d); } catch (e) { reject(e); } }, onerror: () => reject('Network Error') }); }); }

        // DATA
        function fetchData() {
            if (isPolling) return;
            isPolling = true;
            GM_xmlhttpRequest({
                method: "GET", url: `${DB_URL}/targets.json`, onload: (res) => {
                    if (res.status === 200) {
                        const newData = res.responseText ? Object.entries(JSON.parse(res.responseText) || {}).map(([k, v]) => ({ ...v, firebaseId: k })) : [];
                        if (JSON.stringify(newData) !== lastTargetsJson) { targetsList = newData; lastTargetsJson = JSON.stringify(newData); renderTargets(); }
                    }
                    GM_xmlhttpRequest({
                        method: "GET", url: `${DB_URL}/queue.json`, onload: (res2) => {
                            isPolling = false;
                            if (res2.status === 200) {
                                const qData = JSON.parse(res2.responseText) || {};
                                queueActive = qData.active || false;
                                const list = qData.list ? Object.values(qData.list).sort((a, b) => a.timestamp - b.timestamp) : [];
                                
                                const headerQ = document.getElementById('header-queue-status');
                                if(headerQ) {
                                    headerQ.textContent = `Q: ${list.length}/15`;
                                    if(list.length >= 15) headerQ.classList.add('full');
                                    else headerQ.classList.remove('full');
                                }

                                if (JSON.stringify(list) !== lastQueueJson) { queueList = list; lastQueueJson = JSON.stringify(list); renderQueue(); renderParticipantsList(); }
                                const switchEl = document.getElementById('queue-master-switch');
                                if (switchEl && switchEl.checked !== queueActive) { switchEl.checked = queueActive; renderQueue(); }
                            }
                        }, onerror: () => isPolling = false
                    });
                }, onerror: () => isPolling = false
            });
        }

        function startPolling() { if (pollingInterval) clearInterval(pollingInterval); if (statusInterval) clearInterval(statusInterval); fetchData(); pollingInterval = setInterval(fetchData, 3000); if (currentUser && currentUser.player_id === ADMIN_ID) statusInterval = setInterval(refreshFactionStatus, 60000); }
        function bulkWriteDB(dataObj) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: "PUT", url: `${DB_URL}/targets.json`, data: JSON.stringify(dataObj), headers: { "Content-Type": "application/json" }, onload: () => { fetchData(); resolve(); } }); }); }
        function clearNodeDB(node) { return new Promise(resolve => GM_xmlhttpRequest({ method: "DELETE", url: `${DB_URL}/${node}.json`, onload: () => { fetchData(); resolve(); } })); }
        function updateQueueStatus(status) { GM_xmlhttpRequest({ method: "PATCH", url: `${DB_URL}/queue.json`, data: JSON.stringify({ active: status }), onload: () => fetchData() }); }
        function joinQueueDB(entry) { return new Promise((resolve) => GM_xmlhttpRequest({ method: "POST", url: `${DB_URL}/queue/list.json`, data: JSON.stringify(entry), headers: { "Content-Type": "application/json" }, onload: () => { fetchData(); resolve(); } })); }
        function removeFromQueueDB(id) { GM_xmlhttpRequest({ method: "GET", url: `${DB_URL}/queue/list.json`, onload: (res) => { const data = JSON.parse(res.responseText); for (let k in data) { if (data[k].timestamp == id) { GM_xmlhttpRequest({ method: "DELETE", url: `${DB_URL}/queue/list/${k}.json`, onload: () => fetchData() }); break; } } } }); }
        function updateTargetStatsDB(firebaseId, newStats) { GM_xmlhttpRequest({ method: "PATCH", url: `${DB_URL}/targets/${firebaseId}.json`, data: JSON.stringify({ customStats: newStats }), headers: { "Content-Type": "application/json" } }); }

        let trackedFactionId = GM_getValue('bw_tracked_faction_id', null);
        async function refreshFactionStatus() { if (!trackedFactionId || !currentUser || currentUser.player_id !== ADMIN_ID) return; try { const d = await tornApiCall(`faction/${trackedFactionId}?selections=basic`); if (!d.members) return; const updates = {}; let hasUpdates = false; for (let target of targetsList) { const pid = target.player_id || target.id; if (d.members[pid]) { const newStatus = d.members[pid].status; if (!target.status || target.status.state !== newStatus.state || Math.abs((target.status.until || 0) - (newStatus.until || 0)) > 60) { updates[`${target.firebaseId}/status`] = newStatus; hasUpdates = true; } } } if (hasUpdates) GM_xmlhttpRequest({ method: "PATCH", url: `${DB_URL}/targets.json`, data: JSON.stringify(updates), headers: { "Content-Type": "application/json" } }); } catch (e) { } }

        function renderParticipantsList() {
            const container = document.getElementById('participants-list');
            if (!container) return;
            if (queueList.length === 0) { container.innerHTML = '<div style="color:#666;text-align:center;padding:20px;font-size:11px;">No participants</div>'; return; }
            container.innerHTML = queueList.map((item, idx) => `<div class="bw-participant-item">${idx + 1}. ${item.attackerName}</div>`).join('');
        }

        function renderQueue() {
            const controls = document.getElementById('queue-active-area');
            const closedMsg = document.getElementById('queue-closed-msg');
            const countEl = document.getElementById('queue-count');
            const switchInput = document.getElementById('queue-master-switch');
            if (queueActive) { controls.style.display = 'block'; closedMsg.style.display = 'none'; countEl.style.display = 'inline'; countEl.textContent = `${queueList.length}/15`; }
            else { controls.style.display = 'none'; closedMsg.style.display = 'block'; countEl.style.display = 'none'; }
            if (switchInput && !switchInput.hasAttribute('data-bound')) {
                if (currentUser && currentUser.player_id !== ADMIN_ID) switchInput.disabled = true;
                switchInput.checked = queueActive;
                switchInput.addEventListener('change', (e) => {
                    const isActive = e.target.checked;
                    updateQueueStatus(isActive);
                    if (!isActive) { clearNodeDB('queue/list'); queueList = []; lastQueueJson = ""; }
                    queueActive = isActive; renderQueue(); renderParticipantsList();
                });
                switchInput.setAttribute('data-bound', 'true');
            }
            const current = queueList.length > 0 ? queueList[0] : null;
            const nextList = queueList.slice(1);
            const currDisplay = document.getElementById('queue-current-display');
            if (currDisplay) {
                if (current) {
                    const isMe = currentUser && current.attackerId === currentUser.player_id;
                    let attackBtnHtml = '';
                    if (isMe) attackBtnHtml = `<button class="bw-btn bw-queue-attack-btn" data-ts="${current.timestamp}" data-tid="${current.targetId}">ATTACK</button>`;
                    let atkStats = current.attackerStats ? `<div style="font-size:11px;color:#fff;">${current.attackerStats}</div>` : '';
                    let defStats = current.targetStats ? `<div class="stat-white" style="font-size:11px;">${formatStatsDisplay(current.targetStats)} Stats</div>` : '';
                    currDisplay.innerHTML = `<div style="background:#111; border:2px solid #fff; padding:15px; margin-bottom:20px; text-align:center; border-radius:5px;"><div style="font-size:16px;font-weight:bold;color:#fff;">CURRENT ATTACKER</div>${atkStats}<div style="font-size:14px;color:#00ff00;margin:5px 0;">${current.attackerName}</div><div style="color:#888;">vs</div><div style="font-size:14px;color:#ff4444;margin-bottom:10px;">${current.targetName}</div>${defStats}<div style="margin-top:10px;">${attackBtnHtml}</div></div>`;
                } else { currDisplay.innerHTML = '<div style="text-align:center;color:#444;padding:20px;">Queue Empty</div>'; }
            }
            const listContainer = document.getElementById('queue-list');
            if (listContainer) { listContainer.innerHTML = nextList.map((item, idx) => `<div class="bw-queue-item"><div><span style="color:#888;">${idx + 1}.</span> ${item.attackerName} <span style="color:#666;">vs</span> ${item.targetName}</div></div>`).join(''); }
        }

        function renderTargets() {
            const container = document.getElementById('targets-list');
            if (!container || targetsList.length === 0) { if (container) container.innerHTML = '<div class="bw-loading">No targets</div>'; return; }
            targetsList.sort((a, b) => (parseFloat((b.customStats || "0").replace(/,/g, '')) - parseFloat((a.customStats || "0").replace(/,/g, ''))));
            const isAdmin = currentUser && currentUser.player_id === ADMIN_ID;
            container.innerHTML = targetsList.map(item => {
                const targetId = item.player_id || item.id || "Unknown";
                const displayStats = item.customStats && item.customStats !== "N/A" ? formatStatsDisplay(item.customStats) : "???";
                const rawStats = item.customStats || "";
                let statsHtml = "";
                if (isAdmin) {
                    statsHtml = `<div class="bw-stats-wrapper" id="stats-wrapper-${item.firebaseId}"><span class="stats-display-mode" style="color:#fff;">${displayStats} Stats <button class="bw-icon-btn bw-edit-mode-btn" data-fid="${item.firebaseId}">✎</button></span><span class="stats-edit-mode" style="display:none;"><input type="text" class="bw-stats-input" id="input-${item.firebaseId}" value="${rawStats}"><button class="bw-icon-btn bw-save-stats" data-fid="${item.firebaseId}">💾</button><button class="bw-icon-btn bw-cancel-stats" data-fid="${item.firebaseId}">✕</button></span></div>`;
                } else {
                    statsHtml = `<div style="color:#fff;font-size:12px;margin-top:3px;">${displayStats} Stats</div>`;
                }
                let statusHtml = getStatusHtml(item.status);
                return `<div class="bw-target-item"><div><a href="https://www.torn.com/profiles.php?XID=${targetId}" target="_blank" class="bw-target-name">${item.name} [${targetId}] <span style="color:#444;font-size:10px;">Lvl ${item.level || '?'}</span></a>${statusHtml}${statsHtml}</div><div style="display:flex;align-items:center;"><button class="bw-btn bw-attack-and-join" data-tid="${targetId}" data-tname="${item.name}" data-tstats="${item.customStats || 'N/A'}">Join Queue</button></div></div>`;
            }).join('');
        }

        function attachGlobalListeners() {
            document.body.addEventListener('click', async (e) => {
                if (e.target.classList.contains('bw-attack-and-join')) {
                    const btn = e.target;
                    const tid = btn.dataset.tid; const tname = btn.dataset.tname; const tstats = btn.dataset.tstats;
                    if (queueList.length >= 15) {
                        const originalText = btn.textContent; btn.classList.add('bw-btn-error'); btn.textContent = "Queue Full!!!";
                        setTimeout(() => { btn.classList.remove('bw-btn-error'); btn.textContent = originalText; }, 2000); return;
                    }
                    const myStats = formatStatsDisplay(convertToK(currentUser.totalStats));
                    await joinQueueDB({ attackerName: currentUser.name, attackerId: currentUser.player_id, attackerStats: myStats, targetName: tname, targetId: tid, targetStats: tstats, timestamp: Date.now() });
                    const queueTabBtn = document.querySelector('.bw-tab[data-tab="queue"]'); if(queueTabBtn) queueTabBtn.click();
                }
                if (e.target.classList.contains('bw-edit-mode-btn')) { const fid = e.target.dataset.fid; const wrapper = document.getElementById(`stats-wrapper-${fid}`); if (wrapper) { wrapper.querySelector('.stats-display-mode').style.display = 'none'; wrapper.querySelector('.stats-edit-mode').style.display = 'flex'; wrapper.querySelector('.bw-stats-input').focus(); } }
                if (e.target.classList.contains('bw-save-stats')) { const fid = e.target.dataset.fid; const wrapper = document.getElementById(`stats-wrapper-${fid}`); const input = document.getElementById(`input-${fid}`); if (wrapper && input) { const newVal = input.value.trim(); updateTargetStatsDB(fid, newVal); const displayEl = wrapper.querySelector('.stats-display-mode'); displayEl.innerHTML = `${formatStatsDisplay(newVal)} Stats <button class="bw-icon-btn bw-edit-mode-btn" data-fid="${fid}">✎</button>`; wrapper.querySelector('.stats-display-mode').style.display = 'block'; wrapper.querySelector('.stats-edit-mode').style.display = 'none'; } }
                if (e.target.classList.contains('bw-cancel-stats')) { const fid = e.target.dataset.fid; const wrapper = document.getElementById(`stats-wrapper-${fid}`); if (wrapper) { wrapper.querySelector('.stats-display-mode').style.display = 'block'; wrapper.querySelector('.stats-edit-mode').style.display = 'none'; } }
                if (e.target.classList.contains('bw-queue-attack-btn')) { const btn = e.target; window.open(`https://www.torn.com/loader.php?sid=attack&user2ID=${btn.dataset.tid}`, '_blank'); btn.disabled = true; let countdown = 10; btn.textContent = `${countdown}s...`; const timer = setInterval(() => { countdown--; btn.textContent = `${countdown}s...`; if (countdown <= 0) { clearInterval(timer); removeFromQueueDB(btn.dataset.ts); } }, 1000); }
            });
        }

        const importBtn = document.getElementById('import-faction-btn'); if (importBtn) importBtn.addEventListener('click', async () => { const fid = document.getElementById('faction-import-input').value.trim(); if (!fid) return; GM_setValue('bw_tracked_faction_id', fid); trackedFactionId = fid; try { const d = await tornApiCall(`faction/${fid}?selections=basic`); if (!d.members) throw "Empty"; const bulkData = {}; for (const [id, details] of Object.entries(d.members)) { if (details.name) { bulkData[id] = { player_id: id, name: details.name, level: details.level, status: details.status, customStats: "" }; } } await bulkWriteDB(bulkData); document.getElementById('faction-import-input').value = ''; } catch (e) { alert('Error: ' + e); } });
        const clearBtn = document.getElementById('clear-faction-btn'); if (clearBtn) clearBtn.addEventListener('click', async () => { await clearNodeDB('targets'); });

        function openModal() { document.getElementById('bw-war-modal').classList.add('active'); document.getElementById('bw-war-overlay').classList.add('active'); loadUserInfo(); startPolling(); }
        function closeModals() { document.querySelectorAll('.active').forEach(el => el.classList.remove('active')); }
        document.querySelector('.bw-settings-button').addEventListener('click', () => { document.getElementById('bw-war-modal').classList.remove('active'); document.getElementById('bw-settings-modal').classList.add('active'); document.getElementById('api-key-input').value = getApiKey(); });
        document.querySelector('.bw-back-button').addEventListener('click', () => { document.getElementById('bw-settings-modal').classList.remove('active'); document.getElementById('bw-war-modal').classList.add('active'); });
        document.getElementById('bw-war-button').addEventListener('click', openModal);
        document.getElementById('bw-war-overlay').addEventListener('click', closeModals);
        document.querySelectorAll('.bw-close-button').forEach(b => b.addEventListener('click', closeModals));
        document.getElementById('save-api-key').addEventListener('click', () => { const k = document.getElementById('api-key-input').value.trim(); if (k) { GM_setValue('torn_api_key', k); alert('Saved'); } });
        const tabs = document.querySelectorAll('.bw-tab'); tabs.forEach(t => t.addEventListener('click', () => { document.querySelectorAll('.active').forEach(e => { if (e.classList.contains('bw-tab') || e.classList.contains('bw-tab-content')) e.classList.remove('active'); }); t.classList.add('active'); document.getElementById(`${t.dataset.tab}-content`).classList.add('active'); }));

        attachGlobalListeners();

        // CACHED USER INFO
        async function loadUserInfo() {
            // 1. Incarca imediat din cache daca exista
            const cachedUser = GM_getValue('bw_cached_user', null);
            if (cachedUser) {
                currentUser = cachedUser;
                const adminDiv = document.getElementById('admin-import-section'); if (adminDiv) adminDiv.style.display = (cachedUser.player_id === ADMIN_ID) ? 'flex' : 'none';
                document.getElementById('user-total-stats').textContent = formatBigNumber(cachedUser.totalStats);
                const userLink = document.getElementById('user-name-link'); userLink.href = `https://www.torn.com/profiles.php?XID=${cachedUser.player_id}`; userLink.textContent = `${cachedUser.name} [${cachedUser.player_id}]`;
            }

            // 2. Incearca sa actualizezi de pe API
            try {
                const data = await tornApiCall('user/?selections=profile,battlestats');
                // Calculeaza stats
                const total = (data.strength || 0) + (data.defense || 0) + (data.speed || 0) + (data.dexterity || 0);
                
                // Daca api key e public, total o sa fie 0 (sau undefined) -> Pastram cache-ul vechi daca exista
                if (total <= 0 && cachedUser) {
                    console.log("BW Tracker: API didn't return stats (Limited Access required), using cache.");
                    return;
                }

                data.totalStats = total;
                currentUser = data;

                // Salveaza in cache
                GM_setValue('bw_cached_user', currentUser);

                // Update UI
                const adminDiv = document.getElementById('admin-import-section'); if (adminDiv) adminDiv.style.display = (data.player_id === ADMIN_ID) ? 'flex' : 'none';
                document.getElementById('user-total-stats').textContent = formatBigNumber(total);
                const userLink = document.getElementById('user-name-link'); userLink.href = `https://www.torn.com/profiles.php?XID=${data.player_id}`; userLink.textContent = `${data.name} [${data.player_id}]`;

            } catch (e) {
                if (!cachedUser) {
                    document.getElementById('user-total-stats').textContent = 'Stats Error (Check Key)';
                }
            }
        }
    }
    initScript();
})();