您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Uses API to fetch full recharge times & track XP progress manually, accounting for membership bonuses.
// ==UserScript== // @name Zed City Better Bars // @namespace http://tampermonkey.net/ // @version 6.2 // @description Uses API to fetch full recharge times & track XP progress manually, accounting for membership bonuses. // @author You // @license MIT // @match https://www.zed.city/* // @icon https://www.s2.favicons?sz=64&domain=zed.city // @icon https://www.google.com/s2/favicons?sz=64&domain=zed.city // @grant none // ==/UserScript== (function () { "use strict"; console.log("📢 Zed City Userscript Loaded!"); let userData = null; let towerData = null; let raidCooldownElement = null; let boosterCooldownElement = null; // Fix: Define boosterCooldownElement let radioTowerElement = null; async function fetchUserData() { try { let response = await fetch("https://api.zed.city/getStats", { method: "GET", credentials: "include", }); let data = await response.json(); if (data.error) { console.warn(`⚠️ API Error: ${data.error}`); return; } userData = data; console.log("📡 User Data Fetched:", userData); updateLevelElement(); updateRaidCooldown(); updateBoosterCooldown(); } catch (error) { console.error("❌ Error fetching user data:", error); } } function fetchRadioTowerData() { console.log("🔄 Fetching Radio Tower timer on page load..."); fetch("https://api.zed.city/getRadioTower", { method: "GET", credentials: "include", // Ensures authentication cookies are sent }) .then(response => response.json()) .then(data => { if (data.error) { console.warn(`⚠️ API Error: ${data.error}`); return; } if (data.expire !== undefined) { towerData = data.expire; logRadioTowerTimer(); } else { console.warn("⚠️ No Radio Tower timer found in response."); } }) .catch(error => console.error("❌ Error fetching Radio Tower data:", error)); } function calculateFullRechargeTime(currentValue, maxValue, regenRate, regenInterval, nextTick) { let statRemaining = maxValue - currentValue; if (statRemaining <= 0) return { fullTime: "Full", timeUntilFull: "00:00" }; let firstTickTime = nextTick * 1000; // Convert seconds to milliseconds let fullTicksNeeded = Math.ceil(statRemaining / regenRate); let additionalTime = (fullTicksNeeded - 1) * regenInterval * 60 * 1000; let totalTimeNeeded = firstTickTime + additionalTime; let fullRechargeDate = new Date(Date.now() + totalTimeNeeded); let totalMinutesRemaining = Math.floor(totalTimeNeeded / 60000); let hoursRemaining = Math.floor(totalMinutesRemaining / 60); let minutesRemaining = totalMinutesRemaining % 60; let timeUntilFull = `${String(hoursRemaining).padStart(2, "0")}:${String(minutesRemaining).padStart(2, "0")}`; return { fullTime: fullRechargeDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false }), timeUntilFull, }; } async function addStatDisplay(statKey, label, baseRegenRate, regenInterval, barClass) { try { let statBar = await waitForElement(`.q-linear-progress.${barClass}`); if (!statBar) return; console.log(`✅ Found ${label} progress bar`); let infoElement = document.createElement("div"); Object.assign(infoElement.style, { padding: "5px", marginTop: "5px", background: "rgba(0, 0, 0, 0.7)", color: "white", borderRadius: "5px", fontSize: "12px", textAlign: "center", cursor: "pointer" }); statBar.parentNode.insertBefore(infoElement, statBar.nextSibling); let lastDisplayedText = ""; setInterval(() => { if (userData) { let isMember = userData?.membership ? true : false; let adjustedRegenRate = baseRegenRate; let adjustedMaxValue = userData.skills[`max_${statKey}`] || 0; let currentValue = userData[statKey] || 0; let nextTick = userData[`${statKey}_regen`] || 0; if (statKey === "energy" && isMember) { adjustedRegenRate = 5; adjustedMaxValue = 150; } let { fullTime, timeUntilFull } = calculateFullRechargeTime( currentValue, adjustedMaxValue, adjustedRegenRate, regenInterval, nextTick ); let newDisplayText = `${label} ${fullTime} (${timeUntilFull})`; if (newDisplayText !== lastDisplayedText) { infoElement.textContent = newDisplayText; lastDisplayedText = newDisplayText; } } }, 1000); } catch (error) { console.error(`❌ Error modifying ${label} bar:`, error); } } async function addCooldownDisplays() { try { let numberRow = await waitForElement(".currency-stats"); if (!numberRow) return; // Raid Cooldown Element raidCooldownElement = document.createElement("div"); Object.assign(raidCooldownElement.style, { color: "white", borderRadius: "5px", fontSize: "12px", textAlign: "center", cursor: "pointer" }); raidCooldownElement.innerText = "⚔️ Raid: Loading..."; numberRow.prepend(raidCooldownElement); // Booster Cooldown Element boosterCooldownElement = document.createElement("div"); Object.assign(boosterCooldownElement.style, { color: "white", borderRadius: "5px", fontSize: "12px", textAlign: "center", cursor: "pointer" }); boosterCooldownElement.innerText = "Booster: Loading..."; numberRow.appendChild(boosterCooldownElement); // Radio Tower Display Element (Appended at the end) radioTowerElement = document.createElement("div"); Object.assign(radioTowerElement.style, { color: "white", borderRadius: "5px", fontSize: "12px", textAlign: "center", cursor: "pointer" }); radioTowerElement.innerText = "📡 Radio Tower: Coming Soon"; numberRow.appendChild(radioTowerElement); } catch (error) { console.error("❌ Error adding cooldown displays:", error); } } function updateRaidCooldown() { if (userData && userData.raid_cooldown !== undefined && raidCooldownElement) { let remainingSeconds = userData.raid_cooldown; let minutes = Math.floor(remainingSeconds / 60); let seconds = remainingSeconds % 60; raidCooldownElement.innerText = `⚔️ Raid: ${minutes}m ${seconds}s`; } } function updateBoosterCooldown() { if (userData && userData.booster_cooldown !== undefined && boosterCooldownElement) { let remainingSeconds = userData.booster_cooldown; let minutes = Math.floor(remainingSeconds / 60); let seconds = remainingSeconds % 60; boosterCooldownElement.innerText = `Booster: ${minutes}m ${seconds}s`; } else{ boosterCooldownElement.innerText = `Booster: None`; } } /** * Logs the Radio Tower timer */ function logRadioTowerTimer() { if(towerData != undefined & radioTowerElement){ let timeLeft = towerData; let hours = Math.floor(timeLeft / 3600); let minutes = Math.floor((timeLeft % 3600) / 60); let seconds = timeLeft % 60; console.log(`📡 Radio Tower Timer: ${timeLeft} seconds`); console.log(`📡 Radio Tower Timer (formatted): ${hours}h ${minutes}m ${seconds}s`); radioTowerElement.innerText = `📡${hours}h ${minutes}m ${seconds}s`; } } async function updateLevelElement() { await waitForElement(".stat-level"); let elem = document.querySelector(".stat-level"); if (!elem || !userData) return; let levelText = elem.querySelector(".row.items-center.no-wrap div:last-child"); if (!levelText) return; let currentXP = Math.round(userData.experience - userData.xp_start); let xpToNextLevel = Math.round(userData.xp_end - userData.xp_start); levelText.innerText = `${userData.rank} (${currentXP}/${xpToNextLevel})`; } async function waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { const observer = new MutationObserver(() => { let element = document.querySelector(selector); if (element) { observer.disconnect(); resolve(element); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); reject(`❌ Timeout: Element '${selector}' not found`); }, timeout); }); } // Run the script addStatDisplay("energy", "Energy", 5, 10, "energy"); addStatDisplay("rad", "Radiation", 1, 5, "rad"); addStatDisplay("morale", "Morale", 5, 5, "morale"); addStatDisplay("life", "Life", 10, 15, "life"); addCooldownDisplays(); fetchRadioTowerData(); setInterval(fetchUserData, 5000); setInterval(fetchRadioTowerData, 5000); })();