console.log("[ATM UI] atm.js loaded"); console.log("[NUI] loaded"); // ===================== // DOM refs // ===================== const wrap = document.getElementById("wrap"); const cashEl = document.getElementById("cash"); const bankEl = document.getElementById("bank"); const amountEl = document.getElementById("amount"); const btnDeposit = document.getElementById("btnDeposit"); const btnWithdraw = document.getElementById("btnWithdraw"); const btnClose = document.getElementById("btnClose"); // Gang Bank HUD const gbHud = document.getElementById("gangbankHud"); const gbAmount = document.getElementById("gbAmount"); const gbLabel = document.getElementById("gbLabel"); // Capture HUD const capHud = document.getElementById("captureHud"); const capTitle = document.getElementById("capTitle"); const capFill = document.getElementById("capFill"); const capSub = document.getElementById("capSub"); const capBar = document.getElementById("capBar"); // Leaderboard HUD const lbHud = document.getElementById("leaderboardHud"); const lbTitle = document.getElementById("lbTitle"); const lbRows = document.getElementById("lbRows"); const lbError = document.getElementById("lbError"); // ===================== // State // ===================== let lastGangBank = null; // ===================== // Helpers // ===================== function fmt(n) { n = Number(n || 0); if (!Number.isFinite(n)) n = 0; return "$" + Math.floor(n).toLocaleString(); } function post(name, data = {}) { try { fetch(`https://${GetParentResourceName()}/${name}`, { method: "POST", headers: { "Content-Type": "application/json; charset=UTF-8" }, body: JSON.stringify(data) }).catch(() => {}); } catch (e) { console.log("[ATM UI] post failed:", name, e); } } function getAmount() { const v = Number(amountEl?.value || 0); if (!Number.isFinite(v)) return 0; return Math.floor(v); } // ===================== // ATM UI // ===================== function hideATM() { wrap?.classList.add("hidden"); } function showATM() { wrap?.classList.remove("hidden"); setTimeout(() => amountEl?.focus(), 0); } // ======================== // Turf Capture HUD (fixed) // ======================== function capShow() { capHud?.classList.remove("cap-hidden"); } function capHide() { capHud?.classList.add("cap-hidden"); if (capFill) capFill.style.width = "0%"; if (capSub) capSub.textContent = "0%"; } function capStart(titleText) { capShow(); if (capTitle) capTitle.textContent = titleText || "Capturing…"; capSet(0); } function capSet(t) { t = Math.max(0, Math.min(1, Number(t || 0))); const pct = Math.round(t * 100); if (capFill) capFill.style.width = pct + "%"; if (capSub) capSub.textContent = pct + "%"; } /** * Capture styling: * - fillCss = contesting/capturing gang * - bgCss = outgoing/current owner gang */ function capStyle(fillCss, bgCss) { if (capFill && fillCss) capFill.style.background = fillCss; if (capBar && bgCss) capBar.style.background = bgCss; } function capPaused(isPaused) { if (!capSub) return; if (isPaused) capSub.textContent = "PAUSED"; } // ======================== // Leaderboard HUD // ======================== function lbShow() { lbHud?.classList.remove("lb-hidden"); } function lbHide() { lbHud?.classList.add("lb-hidden"); } function lbRender(payload) { if (!payload) return; lbShow(); if (lbTitle) lbTitle.textContent = String(payload.title || "Most influence"); // Error display (optional) if (payload.error) { if (lbError) { lbError.textContent = String(payload.error); lbError.classList.remove("lb-hidden"); } } else { lbError?.classList.add("lb-hidden"); if (lbError) lbError.textContent = ""; } if (!lbRows) return; lbRows.innerHTML = ""; const rows = Array.isArray(payload.rows) ? payload.rows : []; rows.forEach((r) => { const gangId = Number(r.gangId); const name = String(r.name ?? "Unknown"); const val = Number(r.value ?? 0); // 🚫 Skip Police if (gangId === 3) return; // --- Color handling --- const rgb = Array.isArray(r.rgb) ? r.rgb : [255, 255, 255]; let R = Number(rgb[0]); let G = Number(rgb[1]); let B = Number(rgb[2]); // Fallback safety if (!Number.isFinite(R)) R = 255; if (!Number.isFinite(G)) G = 255; if (!Number.isFinite(B)) B = 255; // Brighten very dark colors only (Lost Gang) if (R < 80 && G < 80 && B < 80) { R = Math.min(255, R + 80); G = Math.min(255, G + 80); B = Math.min(255, B + 80); } const div = document.createElement("div"); div.className = "lb-row"; div.innerHTML = ` ${escapeHtml(name)} ${Number.isFinite(val) ? val : 0} `; lbRows.appendChild(div); }); } function escapeHtml(s) { return String(s) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function setGangBankColor(rgb) { if (!gbLabel) return; const arr = Array.isArray(rgb) ? rgb : [255, 255, 255]; let R = Number(arr[0]); let G = Number(arr[1]); let B = Number(arr[2]); if (!Number.isFinite(R)) R = 255; if (!Number.isFinite(G)) G = 255; if (!Number.isFinite(B)) B = 255; // Brighten very dark colors (e.g. Lost Gang) if (R < 80 && G < 80 && B < 80) { R = Math.min(255, R + 80); G = Math.min(255, G + 80); B = Math.min(255, B + 80); } const css = `rgb(${R}, ${G}, ${B})`; // Apply to label (and optionally amount if you want later) gbLabel.style.setProperty("--gang-color", css); gbLabel.style.color = "var(--gang-color)"; // ensures it shows even if CSS missing } // ===================== // Boot // ===================== document.addEventListener("DOMContentLoaded", () => { hideATM(); capHide(); // ✅ was capStop() in your file (but didn’t exist) lbHide(); // start hidden until first update console.log("[ATM UI] DOMContentLoaded -> posting atm:ready"); post("atm:ready"); }); // ===================== // Button wiring // ===================== btnDeposit?.addEventListener("click", () => { const amount = getAmount(); if (amount > 0) post("atm:deposit", { amount }); }); btnWithdraw?.addEventListener("click", () => { const amount = getAmount(); if (amount > 0) post("atm:withdraw", { amount }); }); btnClose?.addEventListener("click", () => post("atm:close")); document.addEventListener("keydown", (e) => { if (e.key === "Escape") post("atm:close"); }); // ===================== // NUI messages (single listener) // ===================== window.addEventListener("message", (event) => { const data = event.data || {}; const type = data.type; if (!type) return; // Capture debug (optional) if (type.startsWith("capture:")) { // console.log("[NUI] capture msg", data); } switch (type) { // -------- ATM -------- case "atm:open": showATM(); break; case "atm:close": hideATM(); break; case "atm:balances": if (cashEl) cashEl.textContent = fmt(data.cash); if (bankEl) bankEl.textContent = fmt(data.bank); break; // ---- Gang Bank HUD ---- case "gangbank:show": gbHud?.classList.remove("gb-hidden"); break; case "gangbank:hide": gbHud?.classList.add("gb-hidden"); break; case "gangbank:label": { if (gbLabel) gbLabel.textContent = String(data.label || "Gang Bank"); // ✅ set color if provided if (data.rgb) setGangBankColor(data.rgb); break; } case "gangbank:update": { if (!gbAmount) break; const amt = Number(data.balance ?? data.amount ?? 0); gbAmount.textContent = fmt(amt); // ✅ also accept rgb here (in case update is the only message carrying it) if (data.rgb) setGangBankColor(data.rgb); // pulse only if changed if (lastGangBank !== null && amt !== lastGangBank) { gbAmount.classList.remove("gb-pulse"); void gbAmount.offsetWidth; // restart animation gbAmount.classList.add("gb-pulse"); } lastGangBank = amt; break; } // ---- Turf Capture HUD ---- case "capture:start": capStart(data.turfName || data.title || "Capturing…"); if (data.t !== undefined) capSet(data.t); if (data.fill || data.bg) capStyle(data.fill, data.bg); break; case "capture:set": capShow(); capSet(data.t); break; case "capture:style": capStyle(data.fill, data.bg); break; case "capture:paused": capPaused(!!data.paused); break; case "capture:hint": if (capSub && data.text) { capSub.textContent = String(data.text); setTimeout(() => { // only clear if still showing the same hint-like text if (capSub && capSub.textContent === String(data.text)) { capSub.textContent = ""; } }, 1500); } break; case "capture:stop": capHide(); break; // ---- ✅ Leaderboard ---- // We accept either name: // - "leaderboard:update" (client might send this) // - "turfwar:leaderboard:update" (recommended) case "leaderboard:update": case "turfwar:leaderboard:update": lbRender(data.payload || data); break; case "leaderboard:hide": lbHide(); break; case "leaderboard:show": lbShow(); break; default: break; } });