UNPKG

@haworth/menu-store

Version:

A visual store and cart system for samp-node servers.

1,266 lines 51.4 kB
"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