250 lines
7.3 KiB
JavaScript
250 lines
7.3 KiB
JavaScript
// 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();
|
||
}
|
||
});
|
||
})();
|