@haworth/menu-store
Version:
A visual store and cart system for samp-node servers.
1,266 lines • 51.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.menuStoreManager = void 0;
exports.MenuStore_InitializeGameMode = MenuStore_InitializeGameMode;
exports.MenuStore_InitializePlayerEvents = MenuStore_InitializePlayerEvents;
exports.MenuStore_InitializePlayer = MenuStore_InitializePlayer;
exports.MenuStore_AddItem = MenuStore_AddItem;
exports.MenuStore_Open = MenuStore_Open;
exports.MenuStore_Close = MenuStore_Close;
exports.MenuStore_IsOpen = MenuStore_IsOpen;
exports.MenuStore_CleanupPlayer = MenuStore_CleanupPlayer;
const core_1 = require("@infernus/core");
/**
* MenuStore v5.0 - TypeScript Edition
*
* A comprehensive shopping cart system for SA-MP servers using Infernus framework.
* Features include:
* - Dynamic item management with 3D model previews
* - Shopping cart with pagination and quantity selection
* - Customizable store themes and callbacks
* - Responsive TextDraw-based UI with hover effects
* - Multi-page navigation for both store items and cart
*
* @author Original: CaioTJF | TypeScript Port: cha5e369
* @version 0.0.1
* @framework Infernus Core
* @license MIT
*/
// Configuration constants (immutable)
const MS_CONFIG = {
MAX_ITEMS_PER_PAGE: 6,
MAX_ITEMS_CART_PER_PAGE: 4,
MAX_ITEMS_CART: 20,
MAX_ITEMS: 30,
MAX_SIZE_DESCRIPTION: 7.5,
INTERVAL_DOUBLECLICK: 300,
DIALOG_ID: 23132,
// Default texts
DEFAULT_MONEY_SIGN: "$",
DEFAULT_CONFIRM: "Buy",
// Messages
MSG_TITLE_DIALOG: "Choose quantity",
MSG_DIALOG: "Enter the quantity you want to buy",
WORD_LIMIT: "Limit",
WORD_CLOSE: "Close",
WORD_TOTAL: "Total",
WORD_GO: "Go",
WORD_CANCEL: "Cancel",
WORD_CART: "Cart"
};
// Colors configuration
const MS_COLORS = {
SELECT_TD: 0xFFFFFFFF,
TEXT_MAIN: 0xFFFFFFFF,
BACKGROUND_1: 0x00000080,
BACKGROUND_2: 0x2C2C2CAA,
BACKGROUND_ITEM: 0x3E3E3EFF,
BACKGROUND_ITEM_SELECTED: 0xFFFFFF50,
BACKGROUND_CONFIRM: 0x5EAF3AFF,
BACKGROUND_MODEL: 0xFFFFFF50,
CART_REMOVE_ITEM: 0xCC30A0FF,
DESCRIPTION_NAME_ITEM: 0xFFFFFFFF,
DESCRIPTION_TEXT: 0xFFFFFFAA,
DESCRIPTION_BACKGROUND_MODEL: 0xFFFFFF50
};
const MSTORE_INVALID_ITEM_ID = -1;
class MenuStoreManager {
playerItems = new Map();
playerInfo = new Map();
playerCart = new Map();
playerTextDraws = new Map();
globalTextDraws = null;
storeCallbacks = new Map();
constructor() {
// Do not auto-initialize events
}
// Method to initialize GameMode events
initializeGameModeEvents() {
this.createGlobalTextDraws();
}
// Method to initialize Player events
initializePlayerEvents() {
core_1.TextDrawEvent.onPlayerClickGlobal(({ player, textDraw }) => {
console.log(`[MenuStore Debug] Global TextDraw clicked by ${player.getName()}, textDraw: ${textDraw || 'null'}`);
// Handle ESC (when textDraw is null)
if (!textDraw) {
const playerStoreInfo = this.playerInfo.get(player.id);
if (playerStoreInfo && playerStoreInfo.isOpen) {
console.log(`[MenuStore Debug] ESC detected for ${player.getName()}`);
this.handlePlayerESC(player);
}
return;
}
// Handle clicks on global textdraws
this.handleGlobalTextDrawClick(player, textDraw);
});
core_1.TextDrawEvent.onPlayerClickPlayer(({ player, textDraw }) => {
console.log(`[MenuStore Debug] Player TextDraw clicked by ${player.getName()}, textDraw: ${textDraw || 'null'}`);
this.handlePlayerTextDrawClick(player, textDraw);
});
}
// Method to initialize a specific player
initializePlayerData(player) {
this.initializePlayer(player);
}
// Method to clean up a specific player's data
cleanupPlayerData(player) {
this.closeStore(player);
// Remove all player data
this.playerItems.delete(player.id);
this.playerInfo.delete(player.id);
this.playerCart.delete(player.id);
this.playerTextDraws.delete(player.id);
}
initializePlayer(player) {
// Initialize items array
this.playerItems.set(player.id, Array(MS_CONFIG.MAX_ITEMS).fill(null).map(() => ({
itemID: MSTORE_INVALID_ITEM_ID,
itemModel: 0,
itemPrice: 0,
itemStack: 0,
itemName: "",
itemDescription: "",
itemDescriptionBonus: 0.0,
itemLineJump: true,
itemPreviewRot: [0.0, 0.0, 0.0, 1.0]
})));
// Initialize info
this.playerInfo.set(player.id, {
totalItems: 0,
currentPage: 1,
currentCartPage: 1, // Inicializar página del carrito
isOpen: false,
storeID: "",
moneySign: "",
doubleClick: 0,
doubleClickRow: -1,
selected: -1,
toggleControll: true
});
// Initialize cart - aumentar capacidad
this.playerCart.set(player.id, Array(MS_CONFIG.MAX_ITEMS_CART).fill(null).map(() => ({
itemID: MSTORE_INVALID_ITEM_ID,
itemPrice: 0,
itemAmount: 0
})));
// Initialize TextDraws
this.playerTextDraws.set(player.id, {
backgrounds: [null, null],
next: null,
previous: null,
cartBackgrounds: [null, null],
cartBackgroundConfirm: null,
cartCartName: null,
cartNext: null, // Inicializar botones del carrito
cartPrevious: null,
itemName: Array(MS_CONFIG.MAX_ITEMS_PER_PAGE).fill(null),
itemPrice: Array(MS_CONFIG.MAX_ITEMS_PER_PAGE).fill(null),
itemModel: Array(MS_CONFIG.MAX_ITEMS_PER_PAGE).fill(null),
itemBackground: Array(MS_CONFIG.MAX_ITEMS_PER_PAGE).fill(null),
storeName: null,
pagination: null,
cartPagination: null, // Inicializar paginación del carrito
descriptionItemName: null,
descriptionItemText: null,
descriptionItemModel: null,
descriptionBackground: [null, null],
cartItemBackground: Array(MS_CONFIG.MAX_ITEMS_CART_PER_PAGE).fill(null),
cartItemName: Array(MS_CONFIG.MAX_ITEMS_CART_PER_PAGE).fill(null),
cartItemClose: Array(MS_CONFIG.MAX_ITEMS_CART_PER_PAGE).fill(null),
cartBuyName: null,
cartTotal: null
});
}
createGlobalTextDraws() {
if (this.globalTextDraws)
return;
this.globalTextDraws = {
backgrounds: [null, null],
next: null,
previous: null,
cartBackgrounds: [null, null],
cartBackgroundConfirm: null,
cartCartName: null,
cartNext: null,
cartPrevious: null,
itemName: [],
itemPrice: [],
itemModel: [],
itemBackground: [],
storeName: null,
pagination: null,
cartPagination: null,
descriptionItemName: null,
descriptionItemText: null,
descriptionItemModel: null,
descriptionBackground: [null, null],
cartItemBackground: [],
cartItemName: [],
cartItemClose: [],
cartBuyName: null,
cartTotal: null
};
// Background 1
this.globalTextDraws.backgrounds[0] = new core_1.TextDraw({
x: 145.5,
y: 130.499969,
text: "box"
}).create()
.setLetterSize(0.0, 30.198539)
.setTextSize(322.0, 0.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.useBox(true)
.setBoxColors(MS_COLORS.BACKGROUND_1)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true);
// Background 2
this.globalTextDraws.backgrounds[1] = new core_1.TextDraw({
x: 153.0,
y: 149.383575,
text: "box"
}).create()
.setLetterSize(0.0, 26.226503)
.setTextSize(315.0, 0.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.useBox(true)
.setBoxColors(MS_COLORS.BACKGROUND_2)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true);
// Next button
this.globalTextDraws.next = new core_1.TextDraw({
x: 251.485229,
y: 390.033355,
text: "ld_beat:right"
}).create()
.setLetterSize(0.0, 0.0)
.setTextSize(11.0, 13.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.setShadow(0)
.setOutline(0)
.setFont(4)
.setProportional(true)
.setSelectable(true);
// Previous button
this.globalTextDraws.previous = new core_1.TextDraw({
x: 201.94522,
y: 390.033355,
text: "ld_beat:left"
}).create()
.setLetterSize(0.0, 0.0)
.setTextSize(11.0, 13.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.setShadow(0)
.setOutline(0)
.setFont(4)
.setProportional(true)
.setSelectable(true);
// Cart Confirm Button - EL MÁS IMPORTANTE
this.globalTextDraws.cartBackgroundConfirm = new core_1.TextDraw({
x: 444.89923,
y: 390.233489,
text: "box"
}).create()
.setLetterSize(0.0, 0.850001)
.setTextSize(507.70169, 15.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.useBox(true)
.setBoxColors(MS_COLORS.BACKGROUND_CONFIRM)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true)
.setSelectable(true);
this.createCartGlobalElements();
}
createCartGlobalElements() {
if (!this.globalTextDraws)
return;
// Cart Background 1
this.globalTextDraws.cartBackgrounds[0] = new core_1.TextDraw({
x: 333.201141,
y: 271.666656,
text: "box"
}).create()
.setLetterSize(0.0, 14.450012)
.setTextSize(516.0, 0.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.useBox(true)
.setBoxColors(MS_COLORS.BACKGROUND_1)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true);
// Cart Background 2
this.globalTextDraws.cartBackgrounds[1] = new core_1.TextDraw({
x: 341.0,
y: 290.133575,
text: "box"
}).create()
.setLetterSize(0.0, 10.134258)
.setTextSize(508.0, 0.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.useBox(true)
.setBoxColors(MS_COLORS.BACKGROUND_2)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true);
// Cart Name
this.globalTextDraws.cartCartName = new core_1.TextDraw({
x: 339.143798,
y: 270.73352,
text: MS_CONFIG.WORD_CART
}).create()
.setLetterSize(0.310357, 1.434998)
.setTextSize(508.0, 0.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.useBox(true)
.setBoxColors(0xFFFFFF00)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true);
// Cart Next button
this.globalTextDraws.cartNext = new core_1.TextDraw({
x: 395.0,
y: 371.0,
text: "ld_beat:right"
}).create()
.setLetterSize(0.0, 0.0)
.setTextSize(8.0, 10.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.setShadow(0)
.setOutline(0)
.setFont(4)
.setProportional(true)
.setSelectable(true);
// Cart Previous button
this.globalTextDraws.cartPrevious = new core_1.TextDraw({
x: 355.0,
y: 371.0,
text: "ld_beat:left"
}).create()
.setLetterSize(0.0, 0.0)
.setTextSize(8.0, 10.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.setShadow(0)
.setOutline(0)
.setFont(4)
.setProportional(true)
.setSelectable(true);
}
handleGlobalTextDrawClick(player, textDraw) {
const playerStoreInfo = this.playerInfo.get(player.id);
if (!playerStoreInfo || !playerStoreInfo.toggleControll)
return;
if (textDraw === this.globalTextDraws?.next) {
if (playerStoreInfo.currentPage < this.getNumberOfPages(player.id)) {
playerStoreInfo.currentPage++;
this.setPage(player, playerStoreInfo.currentPage);
this.unselectRow(player);
}
}
else if (textDraw === this.globalTextDraws?.previous) {
if (playerStoreInfo.currentPage > 1) {
playerStoreInfo.currentPage--;
this.setPage(player, playerStoreInfo.currentPage);
this.unselectRow(player);
}
}
else if (textDraw === this.globalTextDraws?.cartNext) {
if (playerStoreInfo.currentCartPage < this.getCartNumberOfPages(player.id)) {
playerStoreInfo.currentCartPage++;
this.setCartPage(player, playerStoreInfo.currentCartPage);
}
}
else if (textDraw === this.globalTextDraws?.cartPrevious) {
if (playerStoreInfo.currentCartPage > 1) {
playerStoreInfo.currentCartPage--;
this.setCartPage(player, playerStoreInfo.currentCartPage);
}
}
else if (textDraw === this.globalTextDraws?.cartBackgroundConfirm) {
const itemsInCart = this.getTotalItemsInCart(player.id);
if (itemsInCart > 0) {
this.processPurchase(player);
this.closeStore(player);
}
else {
player.sendClientMessage(0xFF0000FF, "The cart is empty. Double-click an item to add it.");
}
}
}
handlePlayerTextDrawClick(player, textDraw) {
const playerStoreInfo = this.playerInfo.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
if (!playerStoreInfo || !textDraws || !playerStoreInfo.toggleControll)
return;
// Check item clicks
for (let i = 0; i < MS_CONFIG.MAX_ITEMS_PER_PAGE; i++) {
if (textDraw === textDraws.itemBackground[i]) {
this.handleItemClick(player, i);
return;
}
}
// Check cart item clicks
for (let row = 0; row < MS_CONFIG.MAX_ITEMS_CART_PER_PAGE; row++) {
if (textDraw === textDraws.cartItemBackground[row]) {
const realIndex = row + (MS_CONFIG.MAX_ITEMS_CART_PER_PAGE * (playerStoreInfo.currentCartPage - 1));
this.removeFromCart(player, realIndex);
return;
}
}
}
handleItemClick(player, row) {
const playerStoreInfo = this.playerInfo.get(player.id);
const items = this.playerItems.get(player.id);
if (!playerStoreInfo || !items)
return;
const index = row + (MS_CONFIG.MAX_ITEMS_PER_PAGE * (playerStoreInfo.currentPage - 1));
const now = Date.now();
// Handle double click
if ((now - playerStoreInfo.doubleClick) < MS_CONFIG.INTERVAL_DOUBLECLICK && playerStoreInfo.doubleClickRow === row) {
if (items[index].itemStack > 1) {
this.showQuantityDialog(player, index);
}
else {
this.addToCart(player, items[index].itemID, items[index].itemPrice, items[index].itemStack);
this.unselectRow(player);
this.closeDescription(player);
}
playerStoreInfo.doubleClickRow = -1;
playerStoreInfo.doubleClick = 0;
return;
}
else {
playerStoreInfo.doubleClickRow = row;
playerStoreInfo.doubleClick = now;
}
// Handle selection
if (playerStoreInfo.selected === row) {
this.unselectRow(player);
this.closeDescription(player);
return;
}
else if (playerStoreInfo.selected !== -1) {
this.unselectRow(player);
}
this.selectRow(player, row);
this.openDescription(player, index);
}
handlePlayerESC(player) {
const playerStoreInfo = this.playerInfo.get(player.id);
if (!playerStoreInfo || !playerStoreInfo.isOpen)
return;
console.log(`[MenuStore Debug] ESC handled for ${player.getName()}`);
const callback = this.storeCallbacks.get(playerStoreInfo.storeID);
if (callback) {
callback(player, false, MSTORE_INVALID_ITEM_ID, 0, 0, 0, "CANCELLED");
}
this.closeStore(player);
}
// Helper methods
getNumberOfPages(playerId) {
const playerStoreInfo = this.playerInfo.get(playerId);
if (!playerStoreInfo)
return 1;
const totalItemsCount = this.getTotalValidItems(playerId);
if (totalItemsCount <= MS_CONFIG.MAX_ITEMS_PER_PAGE)
return 1;
return Math.ceil(totalItemsCount / MS_CONFIG.MAX_ITEMS_PER_PAGE);
}
getCartNumberOfPages(playerId) {
const totalCartItems = this.getTotalItemsInCart(playerId);
if (totalCartItems <= MS_CONFIG.MAX_ITEMS_CART_PER_PAGE)
return 1;
return Math.ceil(totalCartItems / MS_CONFIG.MAX_ITEMS_CART_PER_PAGE);
}
getTotalValidItems(playerId) {
const items = this.playerItems.get(playerId);
if (!items)
return 0;
return items.filter(item => item.itemID !== MSTORE_INVALID_ITEM_ID).length;
}
getTotalItemsInCart(playerId) {
const cart = this.playerCart.get(playerId);
if (!cart)
return 0;
return cart.filter(item => item.itemID !== MSTORE_INVALID_ITEM_ID).length;
}
getItemNameByID(playerId, id) {
const items = this.playerItems.get(playerId);
if (!items)
return "N/A";
for (const item of items) {
if (item.itemID === id) {
return item.itemName;
}
}
return "N/A";
}
getItemModelByID(playerId, id) {
const items = this.playerItems.get(playerId);
if (!items)
return 0;
for (const item of items) {
if (item.itemID === id) {
return item.itemModel;
}
}
return 0;
}
addToCart(player, itemID, price, amount) {
const cart = this.playerCart.get(player.id);
if (!cart)
return;
// Find empty slot in cart
for (let i = 0; i < cart.length; i++) {
const item = cart[i];
if (item.itemID === MSTORE_INVALID_ITEM_ID) {
item.itemID = itemID;
item.itemPrice = price;
item.itemAmount = amount;
console.log(`[MenuStore Debug] Added to cart: ${this.getItemNameByID(player.id, itemID)} (${amount}x) at slot ${i}`);
// Update cart visuals
this.setCartPage(player, this.playerInfo.get(player.id)?.currentCartPage || 1);
this.updateCartTotalValue(player);
// Notify player
const itemName = this.getItemNameByID(player.id, itemID);
player.sendClientMessage(0x00FF00FF, `Added to cart: ${amount}x ${itemName}`);
break;
}
}
}
removeFromCart(player, row) {
const cart = this.playerCart.get(player.id);
if (!cart || !cart[row])
return;
cart[row].itemID = MSTORE_INVALID_ITEM_ID;
cart[row].itemPrice = 0;
cart[row].itemAmount = 0;
this.updateCartTotalValue(player);
this.sortCart(player);
}
sortCart(player) {
const cart = this.playerCart.get(player.id);
if (!cart)
return;
// Compact cart: move valid items to the front, fill the rest with empty slots
const validItems = cart.filter(item => item.itemID !== MSTORE_INVALID_ITEM_ID);
const emptySlots = Array(MS_CONFIG.MAX_ITEMS_CART - validItems.length).fill({ itemID: MSTORE_INVALID_ITEM_ID, itemPrice: 0, itemAmount: 0 });
const newCart = validItems.concat(emptySlots);
for (let i = 0; i < cart.length; i++) {
cart[i] = { ...newCart[i] };
}
this.updateCart(player);
}
updateCart(player) {
const playerStoreInfo = this.playerInfo.get(player.id);
if (!playerStoreInfo)
return;
const totalCartPages = this.getCartNumberOfPages(player.id);
if (playerStoreInfo.currentCartPage > totalCartPages && totalCartPages > 0) {
playerStoreInfo.currentCartPage = totalCartPages;
}
else if (totalCartPages === 0) {
playerStoreInfo.currentCartPage = 1;
}
this.setCartPage(player, playerStoreInfo.currentCartPage);
this.updateCartPagination(player, playerStoreInfo.currentCartPage);
// Actualizar visibilidad de botones de navegación del carrito
if (totalCartPages > 1) {
this.globalTextDraws?.cartPrevious?.show(player);
this.globalTextDraws?.cartNext?.show(player);
}
else {
this.globalTextDraws?.cartPrevious?.hide(player);
this.globalTextDraws?.cartNext?.hide(player);
}
}
updateCartTotalValue(player) {
const cart = this.playerCart.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
const playerStoreInfo = this.playerInfo.get(player.id);
if (!cart || !textDraws || !playerStoreInfo)
return;
let totalValue = 0;
for (const item of cart) {
if (item.itemID !== MSTORE_INVALID_ITEM_ID) {
totalValue += item.itemPrice * item.itemAmount;
}
}
// Destruir textdraw existente del total
textDraws.cartTotal?.destroy();
textDraws.cartTotal = null;
// Crear nuevo textdraw del total
textDraws.cartTotal = new core_1.TextDraw({
player: player,
x: 339.200012,
y: 386.949951,
text: `${MS_CONFIG.WORD_TOTAL}: ${playerStoreInfo.moneySign}${totalValue}`,
charset: player.charset
});
textDraws.cartTotal
.create()
.setLetterSize(0.219999, 1.331665)
.setTextSize(437.000000, 0.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
}
showQuantityDialog(player, index) {
const playerStoreInfo = this.playerInfo.get(player.id);
const items = this.playerItems.get(player.id);
if (!playerStoreInfo || !items)
return;
playerStoreInfo.toggleControll = false;
this.selectRow(player, playerStoreInfo.selected);
const dialog = new core_1.Dialog({
style: core_1.DialogStylesEnum.INPUT,
caption: MS_CONFIG.MSG_TITLE_DIALOG,
info: `${MS_CONFIG.MSG_DIALOG} (${MS_CONFIG.WORD_LIMIT}: ${items[index].itemStack})`,
button1: MS_CONFIG.WORD_GO,
button2: MS_CONFIG.WORD_CANCEL
});
dialog.show(player).then(({ response, inputText }) => {
playerStoreInfo.toggleControll = true;
this.closeDescription(player);
if (!response || playerStoreInfo.selected === -1 || !inputText) {
this.unselectRow(player);
// Reactivate TextDraw selection mode if store is still open
if (playerStoreInfo.isOpen) {
player.selectTextDraw(MS_COLORS.SELECT_TD);
}
return;
}
const quantity = parseInt(inputText);
if (isNaN(quantity) || quantity < 1 || quantity > items[index].itemStack) {
this.unselectRow(player);
// Reactivate TextDraw selection mode if store is still open
if (playerStoreInfo.isOpen) {
player.selectTextDraw(MS_COLORS.SELECT_TD);
}
return;
}
this.addToCart(player, items[index].itemID, items[index].itemPrice, quantity);
this.unselectRow(player);
// Reactivate TextDraw selection mode if store is still open
if (playerStoreInfo.isOpen) {
player.selectTextDraw(MS_COLORS.SELECT_TD);
}
});
}
setPage(player, pageId) {
const items = this.playerItems.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
const playerStoreInfo = this.playerInfo.get(player.id);
if (!items || !textDraws || !playerStoreInfo)
return;
const startIndex = MS_CONFIG.MAX_ITEMS_PER_PAGE * (pageId - 1);
let currentRow = 0;
// Remove existing item textdraws
for (let i = 0; i < MS_CONFIG.MAX_ITEMS_PER_PAGE; i++) {
textDraws.itemBackground[i]?.destroy();
textDraws.itemName[i]?.destroy();
textDraws.itemPrice[i]?.destroy();
textDraws.itemModel[i]?.destroy();
textDraws.itemBackground[i] = null;
textDraws.itemName[i] = null;
textDraws.itemPrice[i] = null;
textDraws.itemModel[i] = null;
}
// Create new item textdraws for actual items
for (let i = startIndex; i < Math.min(startIndex + MS_CONFIG.MAX_ITEMS_PER_PAGE, MS_CONFIG.MAX_ITEMS); i++) {
const item = items[i];
if (item.itemID === MSTORE_INVALID_ITEM_ID || currentRow >= MS_CONFIG.MAX_ITEMS_PER_PAGE)
continue;
const baseY = 155.2 + (currentRow * 39.0);
// Item background
textDraws.itemBackground[currentRow] = new core_1.TextDraw({
player: player,
x: 158.0,
y: baseY,
text: "box",
charset: player.charset
});
textDraws.itemBackground[currentRow]
.create()
.setLetterSize(0.0, 3.3)
.setTextSize(311.0, 22.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.useBox(true)
.setBoxColors(MS_COLORS.BACKGROUND_ITEM)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true)
.setSelectable(true)
.show(player);
// Item name
textDraws.itemName[currentRow] = new core_1.TextDraw({
player: player,
x: 190.0,
y: baseY + 3.0,
text: item.itemName,
charset: player.charset
});
textDraws.itemName[currentRow]
.create()
.setLetterSize(0.231, 1.198)
.setTextSize(310.0, 0.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.useBox(true)
.setBoxColors(0xFFFFFF00)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
// Item price
textDraws.itemPrice[currentRow] = new core_1.TextDraw({
player: player,
x: 311.1,
y: baseY + 18.0,
text: `${playerStoreInfo.moneySign}${item.itemPrice}`,
charset: player.charset
});
textDraws.itemPrice[currentRow]
.create()
.setLetterSize(0.284, 1.227)
.setTextSize(0.0, 0.0)
.setAlignment(3)
.setColor(MS_COLORS.TEXT_MAIN)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
// Item model (3D preview)
textDraws.itemModel[currentRow] = new core_1.TextDraw({
player: player,
x: 158.7,
y: baseY + 1.0,
text: "",
charset: player.charset
});
textDraws.itemModel[currentRow]
.create()
.setLetterSize(0.0, 0.0)
.setTextSize(29.0, 28.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.setShadow(0)
.setOutline(0)
.setBackgroundColors(MS_COLORS.BACKGROUND_MODEL)
.setFont(5)
.setProportional(false)
.setPreviewModel(item.itemModel)
.setPreviewRot(item.itemPreviewRot[0], item.itemPreviewRot[1], item.itemPreviewRot[2], item.itemPreviewRot[3])
.show(player);
currentRow++;
}
// Update pagination
textDraws.pagination?.destroy();
textDraws.pagination = null;
if (playerStoreInfo.totalItems > MS_CONFIG.MAX_ITEMS_PER_PAGE) {
const totalPages = this.getNumberOfPages(player.id);
textDraws.pagination = new core_1.TextDraw({
player: player,
x: 232.25,
y: 388.98,
text: `${pageId}/${totalPages}`,
charset: player.charset
});
textDraws.pagination
.create()
.setLetterSize(0.318, 1.454)
.setTextSize(0.0, 0.0)
.setAlignment(2)
.setColor(MS_COLORS.TEXT_MAIN)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
}
}
selectRow(player, row) {
const playerStoreInfo = this.playerInfo.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
if (!playerStoreInfo || !textDraws)
return;
playerStoreInfo.selected = row;
if (textDraws.itemBackground[row]) {
textDraws.itemBackground[row].setBoxColors(MS_COLORS.BACKGROUND_ITEM_SELECTED);
textDraws.itemBackground[row].hide(player);
textDraws.itemBackground[row].show(player);
}
}
unselectRow(player) {
const playerStoreInfo = this.playerInfo.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
if (!playerStoreInfo || !textDraws || playerStoreInfo.selected === -1)
return;
const row = playerStoreInfo.selected;
if (textDraws.itemBackground[row]) {
textDraws.itemBackground[row].setBoxColors(MS_COLORS.BACKGROUND_ITEM);
textDraws.itemBackground[row].hide(player);
textDraws.itemBackground[row].show(player);
}
playerStoreInfo.selected = -1;
}
openDescription(player, index) {
console.log(`Opening description ${index} for ${player.getName()}`);
// Basic implementation - can be expanded later
}
closeDescription(player) {
console.log(`Closing description for ${player.getName()}`);
// Basic implementation - can be expanded later
}
createCartRow(player, row) {
const cart = this.playerCart.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
const playerStoreInfo = this.playerInfo.get(player.id);
if (!cart || !textDraws || !playerStoreInfo)
return;
const cartItem = cart[row];
if (cartItem.itemID === MSTORE_INVALID_ITEM_ID)
return;
// Calcular la posición visual en la página actual del carrito
const pageStartIndex = (playerStoreInfo.currentCartPage - 1) * MS_CONFIG.MAX_ITEMS_CART_PER_PAGE;
const visualRow = row - pageStartIndex;
// Solo crear si está en la página actual
if (visualRow < 0 || visualRow >= MS_CONFIG.MAX_ITEMS_CART_PER_PAGE)
return;
const baseY = 293.88 + (visualRow * 23.0);
// Destruir textdraws existentes para esta fila visual
textDraws.cartItemBackground[visualRow]?.destroy();
textDraws.cartItemName[visualRow]?.destroy();
textDraws.cartItemClose[visualRow]?.destroy();
// Cart item background
textDraws.cartItemBackground[visualRow] = new core_1.TextDraw({
player: player,
x: 344.0,
y: baseY,
text: "box",
charset: player.charset
});
textDraws.cartItemBackground[visualRow]
.create()
.setLetterSize(0.0, 1.55)
.setTextSize(505.0, 15.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.useBox(true)
.setBoxColors(MS_COLORS.BACKGROUND_ITEM)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true)
.setSelectable(true)
.show(player);
// Cart item name y cantidad
const itemName = this.getItemNameByID(player.id, cartItem.itemID);
const itemTotal = cartItem.itemPrice * cartItem.itemAmount;
textDraws.cartItemName[visualRow] = new core_1.TextDraw({
player: player,
x: 345.2,
y: baseY + 0.7,
text: `${cartItem.itemAmount}x ${itemName} - ${playerStoreInfo.moneySign}${itemTotal}`,
charset: player.charset
});
textDraws.cartItemName[visualRow]
.create()
.setLetterSize(0.18, 1.0)
.setTextSize(500.0, 0.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.useBox(true)
.setBoxColors(0xFFFFFF00)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
// Cart item close button (X)
textDraws.cartItemClose[visualRow] = new core_1.TextDraw({
player: player,
x: 502.0,
y: baseY + 0.7,
text: "X",
charset: player.charset
});
textDraws.cartItemClose[visualRow]
.create()
.setLetterSize(0.25, 1.2)
.setTextSize(0.0, 0.0)
.setAlignment(3)
.setColor(MS_COLORS.CART_REMOVE_ITEM)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true)
.show(player);
console.log(`[MenuStore Debug] Cart row ${visualRow} created for item ${itemName} (${cartItem.itemAmount}x)`);
}
setCartPage(player, pageId) {
const cart = this.playerCart.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
const playerStoreInfo = this.playerInfo.get(player.id);
if (!cart || !textDraws || !playerStoreInfo)
return;
// Limpiar todos los textdraws del carrito
for (let i = 0; i < MS_CONFIG.MAX_ITEMS_CART_PER_PAGE; i++) {
textDraws.cartItemBackground[i]?.destroy();
textDraws.cartItemName[i]?.destroy();
textDraws.cartItemClose[i]?.destroy();
textDraws.cartItemBackground[i] = null;
textDraws.cartItemName[i] = null;
textDraws.cartItemClose[i] = null;
}
// Calcular índices para esta página
const startIndex = (pageId - 1) * MS_CONFIG.MAX_ITEMS_CART_PER_PAGE;
const endIndex = Math.min(startIndex + MS_CONFIG.MAX_ITEMS_CART_PER_PAGE, MS_CONFIG.MAX_ITEMS_CART);
let visualRow = 0;
// Mostrar items de esta página
for (let i = startIndex; i < endIndex && visualRow < MS_CONFIG.MAX_ITEMS_CART_PER_PAGE; i++) {
const cartItem = cart[i];
if (cartItem.itemID !== MSTORE_INVALID_ITEM_ID) {
const baseY = 293.88 + (visualRow * 23.0);
// Cart item background
textDraws.cartItemBackground[visualRow] = new core_1.TextDraw({
player: player,
x: 344.0,
y: baseY,
text: "box",
charset: player.charset
});
textDraws.cartItemBackground[visualRow]
.create()
.setLetterSize(0.0, 1.55)
.setTextSize(505.0, 15.0)
.setAlignment(1)
.setColor(0xFFFFFFFF)
.useBox(true)
.setBoxColors(MS_COLORS.BACKGROUND_ITEM)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true)
.setSelectable(true)
.show(player);
// Cart item name y información
const itemName = this.getItemNameByID(player.id, cartItem.itemID);
const itemTotal = cartItem.itemPrice * cartItem.itemAmount;
textDraws.cartItemName[visualRow] = new core_1.TextDraw({
player: player,
x: 345.2,
y: baseY + 0.7,
text: `${cartItem.itemAmount}x ${itemName} - ${playerStoreInfo.moneySign}${itemTotal}`,
charset: player.charset
});
textDraws.cartItemName[visualRow]
.create()
.setLetterSize(0.18, 1.0)
.setTextSize(500.0, 0.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.useBox(true)
.setBoxColors(0xFFFFFF00)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
// Cart item close button (X)
textDraws.cartItemClose[visualRow] = new core_1.TextDraw({
player: player,
x: 502.0,
y: baseY + 0.7,
text: "X",
charset: player.charset
});
textDraws.cartItemClose[visualRow]
.create()
.setLetterSize(0.25, 1.2)
.setTextSize(0.0, 0.0)
.setAlignment(3)
.setColor(MS_COLORS.CART_REMOVE_ITEM)
.setShadow(0)
.setOutline(0)
.setFont(1)
.setProportional(true)
.show(player);
visualRow++;
}
}
console.log(`[MenuStore Debug] Cart page ${pageId} set with ${visualRow} visible items`);
}
updateCartPagination(player, pageId) {
// Only destroy if exists, but DO NOT create a new one
const textDraws = this.playerTextDraws.get(player.id);
if (!textDraws)
return;
textDraws.cartPagination?.destroy();
textDraws.cartPagination = null;
}
processPurchase(player) {
const cart = this.playerCart.get(player.id);
const playerStoreInfo = this.playerInfo.get(player.id);
if (!cart || !playerStoreInfo)
return;
let totalPrice = 0;
const purchasedItems = [];
// Calculate total and prepare purchase data
for (const item of cart) {
if (item.itemID !== MSTORE_INVALID_ITEM_ID) {
const itemTotal = item.itemPrice * item.itemAmount;
totalPrice += itemTotal;
purchasedItems.push({
itemID: item.itemID,
model: this.getItemModelByID(player.id, item.itemID),
amount: item.itemAmount,
price: itemTotal,
name: this.getItemNameByID(player.id, item.itemID)
});
}
}
// Execute callback for each purchased item
const callback = this.storeCallbacks.get(playerStoreInfo.storeID);
if (callback) {
for (const item of purchasedItems) {
callback(player, true, item.itemID, item.model, item.price, item.amount, item.name);
}
}
// Clear cart
this.clearCart(player);
}
clearCart(player) {
const cart = this.playerCart.get(player.id);
if (!cart)
return;
for (let i = 0; i < cart.length; i++) {
cart[i].itemID = MSTORE_INVALID_ITEM_ID;
cart[i].itemPrice = 0;
cart[i].itemAmount = 0;
}
}
closeStore(player) {
const playerStoreInfo = this.playerInfo.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
if (!playerStoreInfo || !textDraws)
return;
playerStoreInfo.isOpen = false;
playerStoreInfo.toggleControll = false;
// Hide and destroy all player textdraws
for (let i = 0; i < MS_CONFIG.MAX_ITEMS_PER_PAGE; i++) {
textDraws.itemBackground[i]?.hide(player);
textDraws.itemBackground[i]?.destroy();
textDraws.itemName[i]?.hide(player);
textDraws.itemName[i]?.destroy();
textDraws.itemPrice[i]?.hide(player);
textDraws.itemPrice[i]?.destroy();
textDraws.itemModel[i]?.hide(player);
textDraws.itemModel[i]?.destroy();
textDraws.itemBackground[i] = null;
textDraws.itemName[i] = null;
textDraws.itemPrice[i] = null;
textDraws.itemModel[i] = null;
}
for (let i = 0; i < MS_CONFIG.MAX_ITEMS_CART_PER_PAGE; i++) {
textDraws.cartItemBackground[i]?.hide(player);
textDraws.cartItemBackground[i]?.destroy();
textDraws.cartItemName[i]?.hide(player);
textDraws.cartItemName[i]?.destroy();
textDraws.cartItemClose[i]?.hide(player);
textDraws.cartItemClose[i]?.destroy();
textDraws.cartItemBackground[i] = null;
textDraws.cartItemName[i] = null;
textDraws.cartItemClose[i] = null;
}
// Hide and destroy other player textdraws
textDraws.storeName?.hide(player);
textDraws.storeName?.destroy();
textDraws.pagination?.hide(player);
textDraws.pagination?.destroy();
textDraws.cartPagination?.hide(player);
textDraws.cartPagination?.destroy();
textDraws.descriptionItemName?.hide(player);
textDraws.descriptionItemName?.destroy();
textDraws.descriptionItemText?.hide(player);
textDraws.descriptionItemText?.destroy();
textDraws.descriptionItemModel?.hide(player);
textDraws.descriptionItemModel?.destroy();
textDraws.cartBuyName?.hide(player);
textDraws.cartBuyName?.destroy();
textDraws.cartTotal?.hide(player);
textDraws.cartTotal?.destroy();
for (let i = 0; i < 2; i++) {
textDraws.descriptionBackground[i]?.hide(player);
textDraws.descriptionBackground[i]?.destroy();
textDraws.descriptionBackground[i] = null;
}
// Hide global textdraws
this.globalTextDraws?.backgrounds[0]?.hide(player);
this.globalTextDraws?.backgrounds[1]?.hide(player);
this.globalTextDraws?.next?.hide(player);
this.globalTextDraws?.previous?.hide(player);
this.globalTextDraws?.cartBackgrounds[0]?.hide(player);
this.globalTextDraws?.cartBackgrounds[1]?.hide(player);
this.globalTextDraws?.cartBackgroundConfirm?.hide(player);
this.globalTextDraws?.cartCartName?.hide(player);
this.globalTextDraws?.cartNext?.hide(player);
this.globalTextDraws?.cartPrevious?.hide(player);
// Reset player state
playerStoreInfo.currentPage = 1;
playerStoreInfo.currentCartPage = 1;
playerStoreInfo.selected = -1;
playerStoreInfo.doubleClick = 0;
playerStoreInfo.doubleClickRow = -1;
playerStoreInfo.toggleControll = true;
// Clear cart
this.clearCart(player);
// Cancelar modo de selección de TextDraw
player.cancelSelectTextDraw();
}
addItem(player, itemID, modelID, name, price, description = "", descriptionSize = MS_CONFIG.MAX_SIZE_DESCRIPTION, descriptionLineJump = true, stack = 1, rotX = 0.0, rotY = 0.0, rotZ = 0.0, zoom = 1.0) {
const items = this.playerItems.get(player.id);
const playerStoreInfo = this.playerInfo.get(player.id);
if (!items || !playerStoreInfo)
return false;
// Find empty slot
for (let i = 0; i < MS_CONFIG.MAX_ITEMS; i++) {
if (items[i].itemID === MSTORE_INVALID_ITEM_ID) {
items[i].itemID = itemID;
items[i].itemModel = modelID;
items[i].itemName = name;
items[i].itemPrice = price;
items[i].itemDescription = description;
items[i].itemDescriptionBonus = descriptionSize;
items[i].itemLineJump = descriptionLineJump;
items[i].itemStack = stack;
items[i].itemPreviewRot = [rotX, rotY, rotZ, zoom];
playerStoreInfo.totalItems++;
return true;
}
}
return false;
}
openStore(player, storeID, storeName, callback, moneySign = MS_CONFIG.DEFAULT_MONEY_SIGN, buttonConfirm = MS_CONFIG.DEFAULT_CONFIRM) {
const playerStoreInfo = this.playerInfo.get(player.id);
const textDraws = this.playerTextDraws.get(player.id);
if (!playerStoreInfo || !textDraws || playerStoreInfo.isOpen)
return false;
// Store callback
this.storeCallbacks.set(storeID, callback);
// Set store info
playerStoreInfo.isOpen = true;
playerStoreInfo.storeID = storeID;
playerStoreInfo.moneySign = moneySign;
playerStoreInfo.currentPage = 1;
playerStoreInfo.currentCartPage = 1;
playerStoreInfo.selected = -1;
playerStoreInfo.toggleControll = true;
// Show global textdraws
this.globalTextDraws?.backgrounds[0]?.show(player);
this.globalTextDraws?.backgrounds[1]?.show(player);
this.globalTextDraws?.next?.show(player);
this.globalTextDraws?.previous?.show(player);
this.globalTextDraws?.cartBackgrounds[0]?.show(player);
this.globalTextDraws?.cartBackgrounds[1]?.show(player);
this.globalTextDraws?.cartBackgroundConfirm?.show(player);
this.globalTextDraws?.cartCartName?.show(player);
// --- Restaurar posición y estilo originales de los textos clave ---
// Título de la tienda
textDraws.storeName = new core_1.TextDraw({
player: player,
x: 150.84,
y: 129.85,
text: storeName,
charset: player.charset
});
textDraws.storeName
.create()
.setLetterSize(0.31, 1.435)
.setTextSize(315.0, 0.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.useBox(true)
.setBoxColors(0x00000000)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
// Texto del total
textDraws.cartTotal = new core_1.TextDraw({
player: player,
x: 339.200012,
y: 386.949951,
text: `${MS_CONFIG.WORD_TOTAL}: ${playerStoreInfo.moneySign}0`,
charset: player.charset
});
textDraws.cartTotal
.create()
.setLetterSize(0.219999, 1.331665)
.setTextSize(437.000000, 0.0)
.setAlignment(1)
.setColor(MS_COLORS.TEXT_MAIN)
.useBox(true)
.setBoxColors(0xFFFFFF00)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
// Botón "comprar"
textDraws.cartBuyName = new core_1.TextDraw({
player: player,
x: 476.0,
y: 388.5,
text: buttonConfirm,
charset: player.charset
});
textDraws.cartBuyName
.create()
.setLetterSize(0.282, 1.2)
.setTextSize(0.0, 61.0)
.setAlignment(2)
.setColor(MS_COLORS.TEXT_MAIN)
.useBox(false)
.setShadow(0)
.setOutline(0)
.setFont(2)
.setProportional(true)
.show(player);
// Set initial page and cart
this.setPage(player, 1);
this.setCartPage(player, 1);
this.updateCartTotalValue(player);
// Activar modo de selección de TextDraw
player.selectTextDraw(MS_COLORS.SELECT_TD);
return true;
}
isStoreOpen(player) {
const playerStoreInfo = this.playerInfo.get(player.i