Turfwar/html/shop.js
2026-02-12 04:17:15 +00:00

250 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// html/shop.js
(() => {
const wrap = document.getElementById("shop-wrap");
const title = document.getElementById("shop-title");
const balanceNum = document.getElementById("shop-balance-num");
const tabBtns = {
weapons: document.getElementById("shop-tab-weapons"),
ammo: document.getElementById("shop-tab-ammo"),
vehicles: document.getElementById("shop-tab-vehicles"),
};
const list = document.getElementById("shop-list");
const closeBtn = document.getElementById("shop-close");
console.log("[turfwar] shop.js loaded");
// Guard against missing markup (prevents silent failures)
if (
!wrap || !title || !balanceNum || !list || !closeBtn ||
!tabBtns.weapons || !tabBtns.ammo || !tabBtns.vehicles
) {
console.error("[turfwar] shop.js missing DOM elements. Check atm.html includes #shop-wrap and tab ids.");
return;
}
const ALL_TABS = ["weapons", "ammo", "vehicles"];
let state = {
open: false,
tab: "weapons",
gangId: 0,
balance: 0,
payload: { weapons: {}, ammo: {}, vehicles: {}, gangs: {} },
// NEW: which tabs are allowed for this open instance
allowedTabs: ["weapons", "ammo", "vehicles"],
};
function fmtMoney(n) {
n = Number(n) || 0;
return "$" + n.toLocaleString();
}
function normalizeAllowedTabs(arr) {
// If not provided, allow all
if (!Array.isArray(arr) || arr.length === 0) return [...ALL_TABS];
// Keep only known tabs, unique, in ALL_TABS order
const set = new Set(arr.map(String));
return ALL_TABS.filter(t => set.has(t));
}
function firstAllowedTab() {
return (state.allowedTabs && state.allowedTabs[0]) ? state.allowedTabs[0] : "weapons";
}
function applyAllowedTabsUI() {
// Hide / show tab buttons
for (const t of ALL_TABS) {
const btn = tabBtns[t];
const ok = state.allowedTabs.includes(t);
btn.classList.toggle("hidden", !ok);
// Also prevent focusing hidden buttons
btn.tabIndex = ok ? 0 : -1;
}
// If current tab not allowed, force to first allowed
if (!state.allowedTabs.includes(state.tab)) {
state.tab = firstAllowedTab();
}
}
function setActiveTab(tab) {
tab = String(tab || "");
// Enforce allowed tabs
if (!state.allowedTabs.includes(tab)) {
tab = firstAllowedTab();
}
state.tab = tab;
Object.keys(tabBtns).forEach((k) => tabBtns[k].classList.toggle("active", k === tab));
renderList();
}
function renderList() {
list.innerHTML = "";
// Enforce allowed tab again (in case something changed)
if (!state.allowedTabs.includes(state.tab)) {
setActiveTab(firstAllowedTab());
return;
}
const catalog = state.payload[state.tab] || {};
const entries = Object.entries(catalog);
if (entries.length === 0) {
const empty = document.createElement("div");
empty.style.padding = "10px 0";
empty.style.opacity = "0.8";
empty.textContent = "No items configured.";
list.appendChild(empty);
return;
}
for (const [itemId, it] of entries) {
const row = document.createElement("div");
row.className = "shop-row";
const left = document.createElement("div");
const name = document.createElement("div");
name.className = "shop-item-name";
name.textContent = it.label || itemId;
const sub = document.createElement("div");
sub.className = "shop-item-sub";
if (state.tab === "vehicles" && itemId === "gangveh") {
const gName =
(state.payload.gangs &&
state.payload.gangs[state.gangId] &&
state.payload.gangs[state.gangId].name) ||
"Your Gang";
sub.textContent = `Free gang vehicle for ${gName}`;
} else {
sub.textContent = `ID: ${itemId}`;
}
left.appendChild(name);
left.appendChild(sub);
const right = document.createElement("div");
right.style.display = "flex";
right.style.alignItems = "center";
const price = document.createElement("div");
price.className = "shop-price";
price.textContent = fmtMoney(it.price || 0);
const buy = document.createElement("button");
buy.className = "shop-buy";
buy.type = "button";
buy.textContent = "Buy";
buy.onclick = () => doBuy(state.tab, itemId);
right.appendChild(price);
right.appendChild(buy);
row.appendChild(left);
row.appendChild(right);
list.appendChild(row);
}
}
function post(name, data) {
return fetch(`https://${GetParentResourceName()}/${name}`, {
method: "POST",
headers: { "Content-Type": "application/json; charset=UTF-8" },
body: JSON.stringify(data || {}),
}).catch((err) => {
console.error("[turfwar] NUI post failed:", name, err);
});
}
function doBuy(tab, itemId) {
// Hard enforce allowed tabs (security-ish)
if (!state.allowedTabs.includes(tab)) {
console.warn("[turfwar] blocked buy from disallowed tab:", tab, itemId);
return;
}
if (tab === "weapons") return post("shop:buyWeapon", { itemId });
if (tab === "ammo") return post("shop:buyAmmo", { itemId });
if (tab === "vehicles") return post("shop:buyVehicle", { itemId });
}
function open(msg) {
state.open = true;
state.gangId = Number(msg.gangId) || 0;
state.payload = msg.payload || state.payload;
// NEW: allowed tabs
state.allowedTabs = normalizeAllowedTabs(msg.allowedTabs);
// requested tab (will be forced to allowed)
state.tab = String(msg.tab || "weapons");
title.textContent = "Gang Shop";
balanceNum.textContent = fmtMoney(state.balance);
// apply allowed tab UI before setting active tab
applyAllowedTabsUI();
wrap.classList.remove("hidden");
setActiveTab(state.tab);
}
function close() {
state.open = false;
wrap.classList.add("hidden");
post("shop:close", {});
}
// Buttons (theyll be hidden when not allowed)
tabBtns.weapons.addEventListener("click", () => setActiveTab("weapons"));
tabBtns.ammo.addEventListener("click", () => setActiveTab("ammo"));
tabBtns.vehicles.addEventListener("click", () => setActiveTab("vehicles"));
closeBtn.addEventListener("click", close);
// ESC closes
window.addEventListener("message", (e) => {
if (e.data.type === "shop:open") {
const rgb = e.data.gangRGB || { r: 10, g: 10, b: 10 }
document.documentElement.style.setProperty(
"--gang-rgb",
`${rgb.r}, ${rgb.g}, ${rgb.b}`
)
}
})
// Receive messages from Lua
window.addEventListener("message", (event) => {
const msg = event.data;
if (!msg || !msg.type) return;
if (msg.type === "shop:open") {
console.log("[turfwar] shop:open", msg);
open(msg);
} else if (msg.type === "shop:close") {
wrap.classList.add("hidden");
state.open = false;
} else if (msg.type === "shop:balance") {
state.balance = Number(msg.balance) || 0;
state.gangId = Number(msg.gangId) || state.gangId;
balanceNum.textContent = fmtMoney(state.balance);
} else if (msg.type === "shop:gang") {
state.gangId = Number(msg.gangId) || 0;
renderList();
}
});
})();