// 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 (they’ll 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();
}
});
})();