From d52c66b397808ddb47da9614516e6e1235cad48e Mon Sep 17 00:00:00 2001 From: SyncOusli <142453413+SyncOusli@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:26:39 +0100 Subject: [PATCH 1/2] Refactor shop system to use NUI for UI interactions, update item configuration, and enhance localization support - Replaced the previous shop menu system with a NUI-based interface for better user experience. - Updated the configuration to include item categories and improved item data structure. - Enhanced localization files for multiple languages, adding new keys for better user feedback. - Removed deprecated marker and blip logic, simplifying the shop interaction process. - Added commands and key mappings for toggling the shop UI. --- [esx_addons]/esx_shops/client/main.lua | 205 +++++++++++------------ [esx_addons]/esx_shops/config.lua | 112 +++---------- [esx_addons]/esx_shops/fxmanifest.lua | 11 +- [esx_addons]/esx_shops/locales/de.lua | 27 ++- [esx_addons]/esx_shops/locales/en.lua | 27 ++- [esx_addons]/esx_shops/locales/es.lua | 27 ++- [esx_addons]/esx_shops/locales/fi.lua | 27 ++- [esx_addons]/esx_shops/locales/fr.lua | 27 ++- [esx_addons]/esx_shops/locales/hu.lua | 28 ++-- [esx_addons]/esx_shops/locales/it.lua | 27 ++- [esx_addons]/esx_shops/locales/nl.lua | 27 ++- [esx_addons]/esx_shops/locales/pl.lua | 27 ++- [esx_addons]/esx_shops/locales/sl.lua | 27 ++- [esx_addons]/esx_shops/locales/sr.lua | 27 ++- [esx_addons]/esx_shops/locales/sv.lua | 31 ++-- [esx_addons]/esx_shops/locales/tr.lua | 27 ++- [esx_addons]/esx_shops/locales/zh-cn.lua | 27 ++- [esx_addons]/esx_shops/server/main.lua | 76 ++++++--- 18 files changed, 356 insertions(+), 431 deletions(-) diff --git a/[esx_addons]/esx_shops/client/main.lua b/[esx_addons]/esx_shops/client/main.lua index 59c5a1db..3f1aa5fb 100644 --- a/[esx_addons]/esx_shops/client/main.lua +++ b/[esx_addons]/esx_shops/client/main.lua @@ -1,121 +1,116 @@ -local hasAlreadyEnteredMarker, lastZone -local currentAction, currentActionMsg, currentActionData = nil, nil, {} - -local function openShopMenu(zone) - local elements = { - {unselectable = true, icon = "fas fa-shopping-basket", title = TranslateCap('shop') } - } - - for i=1, #Config.Zones[zone].Items, 1 do - local item = Config.Zones[zone].Items[i] - - elements[#elements+1] = { - icon = "fas fa-shopping-basket", - title = ('%s - %s'):format(item.label, TranslateCap('shop_item', ESX.Math.GroupDigits(item.price))), - itemLabel = item.label, - item = item.name, - price = item.price - } - end - - ESX.OpenContext("right", elements, function(menu,element) - local elements2 = { - {unselectable = true, icon = "fas fa-shopping-basket", title = element.title}, - {icon = "fas fa-shopping-basket", title = TranslateCap('amount'), input = true, inputType = "number", inputPlaceholder = TranslateCap('amount_placeholder'), inputMin = 1, inputMax = 25}, - {icon = "fas fa-check-double", title = TranslateCap('confirm'), val = "confirm"} - } - - ESX.OpenContext("right", elements2, function(menu2,element2) - local amount = menu2.eles[2].inputValue - ESX.CloseContext() - TriggerServerEvent('esx_shops:buyItem', element.item, amount, zone) - end, function(menu) - currentAction = 'shop_menu' - currentActionMsg = TranslateCap('press_menu', ESX.GetInteractKey()) - currentActionData = {zone = zone} - end) - end, function(menu) - currentAction = 'shop_menu' - currentActionMsg = TranslateCap('press_menu', ESX.GetInteractKey()) - currentActionData = {zone = zone} - end) +---@diagnostic disable: undefined-global +local ESX = exports['es_extended']:getSharedObject() +local isShopUiOpen = false + +local function setShopUiVisible(visible) + isShopUiOpen = visible + SetNuiFocus(visible, visible) + SendNUIMessage({ action = visible and 'show' or 'hide' }) end -local function hasEnteredMarker(zone) - currentAction = 'shop_menu' - currentActionMsg = TranslateCap('press_menu', ESX.GetInteractKey()) - currentActionData = {zone = zone} +local function openShopUi() + if isShopUiOpen then return end + setShopUiVisible(true) end -local function hasExitedMarker(zone) - currentAction = nil - ESX.CloseContext() +local function closeShopUi() + if not isShopUiOpen then return end + setShopUiVisible(false) end --- Create Blips -CreateThread(function() - for k,v in pairs(Config.Zones) do - for i = 1, #v.Pos, 1 do - if not v.ShowBlip then return end - - local blip = AddBlipForCoord(v.Pos[i]) +RegisterCommand('shopui', function() + if isShopUiOpen then + closeShopUi() + else + openShopUi() + end +end, false) - SetBlipSprite (blip, v.Type) - SetBlipScale (blip, v.Size) - SetBlipColour (blip, v.Color) - SetBlipAsShortRange(blip, true) +RegisterKeyMapping('shopui', 'Toggle Shop UI', 'keyboard', 'F7') - BeginTextCommandSetBlipName('STRING') - AddTextComponentSubstringPlayerName(TranslateCap('shops')) - EndTextCommandSetBlipName(blip) - end - end +RegisterNUICallback('close', function(_, cb) + closeShopUi() + if cb then cb({}) end end) --- Enter / Exit marker events -CreateThread(function() - while true do - local sleep = 1500 +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + SetNuiFocus(false, false) +end) - local playerCoords = GetEntityCoords(ESX.PlayerData.ped) - local isInMarker, currentZone = false, nil - for k,v in pairs(Config.Zones) do - for i = 1, #v.Pos, 1 do - local distance = #(playerCoords - v.Pos[i]) +RegisterNUICallback('getShopData', function(_, cb) + cb({ + categories = Config.Categories or {}, + items = Config.Items or {} + }) +end) - if distance < Config.DrawDistance then - sleep = 0 - if v.ShowMarker then - DrawMarker(Config.MarkerType, v.Pos[i], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Config.MarkerSize.x, Config.MarkerSize.y, Config.MarkerSize.z, Config.MarkerColor.r, Config.MarkerColor.g, Config.MarkerColor.b, 100, false, true, 2, false, nil, nil, false) - end - if distance < 2.0 then - isInMarker = true - currentZone = k - lastZone = k - end - end - end - end +RegisterNUICallback('purchase', function(data, cb) + local payload = data or {} + ESX.TriggerServerCallback('esx_shops:purchase', function(success, message) + cb({ success = success, message = message }) + if success then + ESX.ShowNotification(message) + else + ESX.ShowNotification(message) + end + end, payload) +end) - if isInMarker and not hasAlreadyEnteredMarker then - hasAlreadyEnteredMarker = true - hasEnteredMarker(currentZone) - ESX.TextUI(currentActionMsg) - end +---@param label any +local function showHelpText(label) + BeginTextCommandDisplayHelp('STRING') + AddTextComponentSubstringPlayerName(label) + EndTextCommandDisplayHelp(0, false, true, 1) +end - if not isInMarker and hasAlreadyEnteredMarker then - hasAlreadyEnteredMarker = false - ESX.HideUI() - hasExitedMarker(lastZone) - end - - Wait(sleep) - end +CreateThread(function() + while true do + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + local nearby = false + local inInteractRange = false + + for _, loc in ipairs(Config.Locations or {}) do + local coords = loc.coords + local distance = #(playerCoords - coords) + if distance < 20.0 then + nearby = true + if loc.marker ~= false then + DrawMarker( + 2, + coords.x, coords.y, coords.z, + 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + 0.2, 0.2, 0.2, + 204, 153, 0, 200, + false, true, 2, true, nil, nil, false + ) + end + if distance < 1.8 then + inInteractRange = true + if not isShopUiOpen then + local helpText = 'Press ~INPUT_CONTEXT~ to open shop' + if type(_U) == 'function' then + local translated = _U('open_shop_help') + if type(translated) == 'string' and translated ~= '' then + helpText = translated + end + end + showHelpText(helpText) + end + if IsControlJustPressed(0, 38) then -- INPUT_PICKUP (E) + openShopUi() + end + end + end + end + + if nearby then + Wait(0) + else + Wait(500) + end + end end) - -ESX.RegisterInteraction("shop_menu", function() - openShopMenu(currentActionData.zone) -end, function() - return currentAction and currentAction == 'shop_menu' -end) \ No newline at end of file diff --git a/[esx_addons]/esx_shops/config.lua b/[esx_addons]/esx_shops/config.lua index 9468821b..39d54b68 100644 --- a/[esx_addons]/esx_shops/config.lua +++ b/[esx_addons]/esx_shops/config.lua @@ -1,98 +1,28 @@ Config = {} -Config.DrawDistance = 7.5 -Config.MarkerSize = {x = 1.1, y = 0.7, z = 1.1} -Config.MarkerType = 29 -Config.MarkerColor = {r = 50, g = 200, b = 50, a = 200} -Config.Locale = GetConvar('esx:locale', 'en') -Config.Zones = { +Config.Locale = GetConvar('esx:locale', 'fr') - TwentyFourSeven = { - Items = { - { - name = "bread", - label = TranslateCap('bread'), - price = 100 - }, - { - name = "water", - label = TranslateCap('water'), - price = 100 - } - }, - Pos = { - vector3(373.8, 325.8, 103.5), - vector3(2557.4, 382.2, 108.6), - vector3(-3038.9, 585.9, 7.9), - vector3(-3241.9, 1001.4, 12.8), - vector3(547.4, 2671.7, 42.1), - vector3(1961.4, 3740.6, 32.3), - vector3(2678.9, 3280.6, 55.2), - vector3(1729.2, 6414.1, 35.0) - }, - Size = 0.8, - Type = 59, - Color = 25, - ShowBlip = true, - ShowMarker = true -}, +Config.ItemMaxQuantity = 100 - RobsLiquor = { - Items = { - { - name = "bread", - label = TranslateCap('bread'), - price = 100 - }, - { - name = "water", - label = TranslateCap('water'), - price = 100 - } - }, - Pos = { - vector3(1135.8, -982.2, 46.4), - vector3(-1222.9, -906.9, 12.3), - vector3(-1487.5, -379.1, 40.1), - vector3(-2968.2, 390.9, 15.0), - vector3(1166.0, 2708.9, 38.1), - vector3(1392.5, 3604.6, 34.9), - vector3(127.8, -1284.7, 29.2), --StripClub - vector3(-1393.4, -606.6, 30.3), --Tequila la - vector3(-559.9, 287.0, 82.1) --Bahamamas - }, - Size = 0.8, - Type = 59, - Color = 25, - ShowBlip = true, - ShowMarker = true -}, - - LTDgasoline = { - Items = { - { - name = "bread", - label = TranslateCap('bread'), - price = 100 - }, - { - name = "water", - label = TranslateCap('water'), - price = 100 - } - }, - Pos = { - vector3(-48.5, -1757.5, 29.4), - vector3(1163.3, -323.8, 69.2), - vector3(-707.5, -914.2, 19.2), - vector3(-1820.5, 792.5, 138.1), - vector3(1698.3, 4924.4, 42.0) - }, - Size = 0.8, - Type = 59, - Color = 25, - ShowBlip = true, - ShowMarker = true +Config.Categories = { + { id = 'drinks', label = 'Drinks' }, + { id = 'food', label = 'Food' }, + { id = 'essentials', label = 'Essentials' }, + { id = 'others', label = 'Others' } } + +Config.Items = { + { name = 'water', label = 'Water', price = 200, category = 'drinks', amount = 1, image = 'https://r2.fivemanage.com/R92pivz8ZlXwjJjTvi3Oq/water.png' }, + { name = 'sandwich', label = 'Sandwich', price = 500, category = 'drinks', amount = 1, image = 'https://r2.fivemanage.com/R92pivz8ZlXwjJjTvi3Oq/sprunk.png' }, + { name = 'donut', label = 'Donut', price = 50, category = 'food', amount = 1, image = 'https://r2.fivemanage.com/R92pivz8ZlXwjJjTvi3Oq/donut.png' }, + { name = 'pizza', label = 'Pizza', price = 9900, category = 'food', amount = 1, image = 'https://r2.fivemanage.com/R92pivz8ZlXwjJjTvi3Oq/pizza_ham_slice.png' }, + { name = 'lockpick', label = 'Lockpick', price = 2500, category = 'essentials', amount = 1, image = 'https://r2.fivemanage.com/R92pivz8ZlXwjJjTvi3Oq/lockpick.png' } } +-- Shop locations. Add as many as you want. +-- Each entry: { coords = vector3(x, y, z), marker = true/false, text = 'Display name' } +Config.Locations = { + { coords = vector3(25.740662, -1347.652710, 29.482056), marker = true, text = 'Shop' }, + { coords = vector3(25.767035, -1345.173584, 29.482056), marker = true, text = 'Shop' }, + -- you can add more locations as you want +} diff --git a/[esx_addons]/esx_shops/fxmanifest.lua b/[esx_addons]/esx_shops/fxmanifest.lua index 5e26dd2b..30387906 100644 --- a/[esx_addons]/esx_shops/fxmanifest.lua +++ b/[esx_addons]/esx_shops/fxmanifest.lua @@ -5,7 +5,6 @@ game 'gta5' description 'A shop system for ESX Legacy, to allow players to buy items' lua54 'yes' version '1.2' -legacyversion '1.13.4' shared_script '@es_extended/imports.lua' @@ -25,3 +24,13 @@ server_scripts { } dependency 'es_extended' + +ui_page { + 'html/index.html', +} + +files { + 'html/index.html', + 'html/app.js', + 'html/style.css', +} diff --git a/[esx_addons]/esx_shops/locales/de.lua b/[esx_addons]/esx_shops/locales/de.lua index 570d3bc0..b5ffff8f 100644 --- a/[esx_addons]/esx_shops/locales/de.lua +++ b/[esx_addons]/esx_shops/locales/de.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['de'] = { ['shop'] = 'Shop', - ['shops'] = 'Shops', - ['press_menu'] = 'Drücke [%s] um auf den ~g~Shop~g~ zuzugreifen.', - ['shop_item'] = '%sEUR', - ['bought'] = 'Du kaufst ~b~%sx %s~s~ für ~b~%sEUR', - ['not_enough'] = 'Du hast ~r~nicht~s~ genügend Geld! Dir Fehlt ~b~%sEUR!', - ['player_cannot_hold'] = 'Du hast ~r~nicht~s~ genügend freien Platz in deinem Inventar!', - ['shop_confirm'] = 'Willst du %sx %s kaufen für %sEUR?', - ['no'] = 'Nein', - ['yes'] = 'Ja', - ['amount'] = 'Anzahl', - ['amount_placeholder'] = 'Anzahl die du kaufen möchtest', - ['confirm'] = 'Bestätigen', - ['purchase'] = 'Kaufen', - ['bread'] = 'Brot', - ['water'] = 'Wasser', + ['open_shop_help'] = 'Drücke ~INPUT_CONTEXT~, um den Shop zu öffnen', + ['player_not_found'] = 'Spieler nicht gefunden', + ['cart_empty'] = 'Warenkorb ist leer', + ['invalid_cart_entry'] = 'Ungültiger Warenkorbeintrag', + ['invalid_item_data'] = 'Ungültige Artikeldaten', + ['unknown_item'] = 'Unbekannter Artikel: %s', + ['invalid_quantity'] = 'Ungültige Menge', + ['not_enough_cash'] = 'Nicht genug Bargeld', + ['not_enough_bank'] = 'Unzureichendes Bankguthaben', + ['purchase_success'] = 'Kauf erfolgreich' + } diff --git a/[esx_addons]/esx_shops/locales/en.lua b/[esx_addons]/esx_shops/locales/en.lua index 2b623615..d16324d5 100644 --- a/[esx_addons]/esx_shops/locales/en.lua +++ b/[esx_addons]/esx_shops/locales/en.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['en'] = { ['shop'] = 'shop', - ['shops'] = 'shops', - ['press_menu'] = 'press [%s] to access the ~g~store.', - ['shop_item'] = '$%s', - ['bought'] = 'You Have Bought ~b~%sx %s~s~ for ~b~$%s', - ['not_enough'] = 'you do ~r~not~s~ have enough money, you\'re missing ~b~$%s!', - ['player_cannot_hold'] = 'you do ~r~not~s~ have enough free space in your inventory!', - ['shop_confirm'] = 'buy %sx %s for $%s?', - ['no'] = 'no', - ['yes'] = 'yes', - ['amount'] = 'Amount', - ['amount_placeholder'] = 'Amount you want to buy', - ['confirm'] = 'Confirm', - ['purchase'] = 'Purchase', - ['bread'] = 'Bread', - ['water'] = 'Water', + ['open_shop_help'] = 'Press ~INPUT_CONTEXT~ to open shop', + ['player_not_found'] = 'Player not found', + ['cart_empty'] = 'Cart is empty', + ['invalid_cart_entry'] = 'Invalid cart entry', + ['invalid_item_data'] = 'Invalid item data', + ['unknown_item'] = 'Unknown item: %s', + ['invalid_quantity'] = 'Invalid quantity', + ['not_enough_cash'] = 'Not enough cash', + ['not_enough_bank'] = 'Not enough bank balance', + ['purchase_success'] = 'Purchase successful' + } diff --git a/[esx_addons]/esx_shops/locales/es.lua b/[esx_addons]/esx_shops/locales/es.lua index 90865108..1b11a648 100644 --- a/[esx_addons]/esx_shops/locales/es.lua +++ b/[esx_addons]/esx_shops/locales/es.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['es'] = { ['shop'] = 'tienda', - ['shops'] = 'tiendas', - ['press_menu'] = 'pulsa [%s] para comprar en la tienda.', - ['shop_item'] = '%s€', - ['bought'] = 'has comprado %sx %s por ~r~%s€', - ['not_enough'] = 'no tienes ~r~suficiente dinero: %s', - ['player_cannot_hold'] = 'no tienes espacio libre en tu inventario...', - ['shop_confirm'] = '¿Comprar %sx %s por $%s?', - ['no'] = 'no', - ['yes'] = 'si', - ['amount'] = 'Amount', --not translated - ['amount_placeholder'] = 'Amount you want to buy', --not translated - ['confirm'] = 'Confirm', --not translated - ['purchase'] = 'Purchase', --not translated - ['bread'] = 'Bread', --not translated - ['water'] = 'Water', --not translated + ['open_shop_help'] = 'Pulsa ~INPUT_CONTEXT~ para abrir la tienda', + ['player_not_found'] = 'Jugador no encontrado', + ['cart_empty'] = 'El carrito está vacío', + ['invalid_cart_entry'] = 'Entrada de carrito inválida', + ['invalid_item_data'] = 'Datos de artículo inválidos', + ['unknown_item'] = 'Artículo desconocido: %s', + ['invalid_quantity'] = 'Cantidad inválida', + ['not_enough_cash'] = 'No tienes suficiente efectivo', + ['not_enough_bank'] = 'Saldo bancario insuficiente', + ['purchase_success'] = 'Compra exitosa' + } diff --git a/[esx_addons]/esx_shops/locales/fi.lua b/[esx_addons]/esx_shops/locales/fi.lua index 0db1e83f..71636bdb 100644 --- a/[esx_addons]/esx_shops/locales/fi.lua +++ b/[esx_addons]/esx_shops/locales/fi.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['fi'] = { ['shop'] = 'Kauppa', - ['shops'] = 'Kauppa', - ['press_menu'] = 'Paina [%s] avataksesi valikko.', - ['shop_item'] = '€%s', - ['bought'] = 'Sinä ostit juuri %sx %s. Summaksi tuli ~r~€%s', - ['not_enough'] = 'Sinulla ei ole ~r~tarpeeksi rahaa, sinulta puuttuu ~r~€%s!', - ['player_cannot_hold'] = 'Sinulla ~r~ei ole tarpeeksi tilaa repussasi!', - ['shop_confirm'] = 'Osta %sx %s hintaan €%s?', - ['no'] = 'Ei', - ['yes'] = 'Kyllä', - ['amount'] = 'Määrä', - ['amount_placeholder'] = 'Kuinka monta haluat ostaa?', - ['confirm'] = 'Vahvista', - ['purchase'] = 'Osta', - ['bread'] = 'Leipä', - ['water'] = 'Vesi', + ['open_shop_help'] = 'Paina ~INPUT_CONTEXT~ avataksesi kaupan', + ['player_not_found'] = 'Pelaajaa ei löydy', + ['cart_empty'] = 'Ostoskori on tyhjä', + ['invalid_cart_entry'] = 'Virheellinen ostoskori-merkintä', + ['invalid_item_data'] = 'Virheelliset tuotetiedot', + ['unknown_item'] = 'Tuntematon tuote: %s', + ['invalid_quantity'] = 'Virheellinen määrä', + ['not_enough_cash'] = 'Ei tarpeeksi käteistä', + ['not_enough_bank'] = 'Riittämätön pankkisaldo', + ['purchase_success'] = 'Osto onnistui' + } diff --git a/[esx_addons]/esx_shops/locales/fr.lua b/[esx_addons]/esx_shops/locales/fr.lua index 088998dc..f47280e0 100644 --- a/[esx_addons]/esx_shops/locales/fr.lua +++ b/[esx_addons]/esx_shops/locales/fr.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['fr'] = { ['shop'] = 'magasin', - ['shops'] = 'magasins', - ['press_menu'] = 'appuyez sur [%s] pour accéder au magasin.', - ['shop_item'] = '$%s', - ['bought'] = 'vous venez d\'acheter %sx %s pour ~r~$%s', - ['not_enough'] = 'vous n\'avez ~r~pas assez d\'argent: %s', - ['player_cannot_hold'] = 'vous n\'avez ~r~pas assez de place dans votre inventaire!', - ['shop_confirm'] = 'acheter %sx %s pour $%s?', - ['no'] = 'non', - ['yes'] = 'oui', - ['amount'] = 'Quantité', - ['amount_placeholder'] = 'Quantité que vous voulez acheter', - ['confirm'] = 'Confirmer', - ['purchase'] = 'Acheter', - ['bread'] = 'Pain', - ['water'] = 'Eau', + ['open_shop_help'] = 'Appuyez sur ~INPUT_CONTEXT~ pour ouvrir le magasin', + ['player_not_found'] = 'Joueur introuvable', + ['cart_empty'] = 'Le panier est vide', + ['invalid_cart_entry'] = 'Entrée de panier invalide', + ['invalid_item_data'] = 'Données d\'objet invalides', + ['unknown_item'] = 'Objet inconnu : %s', + ['invalid_quantity'] = 'Quantité invalide', + ['not_enough_cash'] = 'Pas assez d\'argent liquide', + ['not_enough_bank'] = 'Solde bancaire insuffisant', + ['purchase_success'] = 'Achat réussi' + } diff --git a/[esx_addons]/esx_shops/locales/hu.lua b/[esx_addons]/esx_shops/locales/hu.lua index 949a0838..679da344 100644 --- a/[esx_addons]/esx_shops/locales/hu.lua +++ b/[esx_addons]/esx_shops/locales/hu.lua @@ -1,18 +1,16 @@ +---@diagnostic disable: undefined-global Locales['hu'] = { ['shop'] = 'Bolt', - ['shops'] = 'Bolt', - ['press_menu'] = 'Nyomd meg a [%s] gombot hogy megnézd a kinálatot', - ['shop_item'] = '$%s', - ['bought'] = 'Vettél %sx %s ennyiért: ~r~$%s', - ['not_enough'] = 'Nincsen elég pénzed', - ['player_cannot_hold'] = 'Nincsen elég szabad helyed!', - ['shop_confirm'] = 'Veszel %sx %s ennyiért $%s?', - ['no'] = 'Nem', - ['yes'] = 'Igen', - ['amount'] = 'Mennyiség', - ['amount_placeholder'] = 'Amennyit szeretnél', - ['confirm'] = 'Megerősítés', - ['purchase'] = 'Vásárlás', - ['bread'] = 'Kenyér', - ['water'] = 'Palackos víz', + + ['open_shop_help'] = 'Nyomd meg a ~INPUT_CONTEXT~ gombot a bolt megnyitásához', + ['player_not_found'] = 'Játékos nem található', + ['cart_empty'] = 'A kosár üres', + ['invalid_cart_entry'] = 'Érvénytelen kosár bejegyzés', + ['invalid_item_data'] = 'Érvénytelen tárgy adatok', + ['unknown_item'] = 'Ismeretlen tárgy: %s', + ['invalid_quantity'] = 'Érvénytelen mennyiség', + ['not_enough_cash'] = 'Nincs elég készpénz', + ['not_enough_bank'] = 'Elégtelen bankszámla egyenleg', + ['purchase_success'] = 'Sikeres vásárlás' + } diff --git a/[esx_addons]/esx_shops/locales/it.lua b/[esx_addons]/esx_shops/locales/it.lua index d88bac6a..e8811a7a 100644 --- a/[esx_addons]/esx_shops/locales/it.lua +++ b/[esx_addons]/esx_shops/locales/it.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['it'] = { ['shop'] = 'negozio', - ['shops'] = 'negozi', - ['press_menu'] = 'premi [%s] per accedere al negozio.', - ['shop_item'] = '%s$', - ['bought'] = 'hai acquistato %sx %s per ~r~%s$', - ['not_enough'] = 'non hai ~r~abbastanza soldi: %s', - ['player_cannot_hold'] = 'non hai spazio libero nel tuo inventario', - ['shop_confirm'] = 'Acquista %sx %s per $%s?', - ['no'] = 'no', - ['yes'] = 'sì', - ['amount'] = 'Quantità', - ['amount_placeholder'] = 'Quantità che desideri acquistare', - ['confirm'] = 'Conferma', - ['purchase'] = 'Acquista', - ['bread'] = 'Pane', - ['water'] = 'Acqua', + ['open_shop_help'] = 'Premi ~INPUT_CONTEXT~ per aprire il negozio', + ['player_not_found'] = 'Giocatore non trovato', + ['cart_empty'] = 'Il carrello è vuoto', + ['invalid_cart_entry'] = 'Voce del carrello non valida', + ['invalid_item_data'] = 'Dati articolo non validi', + ['unknown_item'] = 'Articolo sconosciuto: %s', + ['invalid_quantity'] = 'Quantità non valida', + ['not_enough_cash'] = 'Contanti insufficienti', + ['not_enough_bank'] = 'Saldo bancario insufficiente', + ['purchase_success'] = 'Acquisto avvenuto con successo' + } diff --git a/[esx_addons]/esx_shops/locales/nl.lua b/[esx_addons]/esx_shops/locales/nl.lua index 81eaa172..16cc4b96 100644 --- a/[esx_addons]/esx_shops/locales/nl.lua +++ b/[esx_addons]/esx_shops/locales/nl.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['nl'] = { ['shop'] = 'winkel', - ['shops'] = 'winkels', - ['press_menu'] = 'klik op [%s] om de ~g~winkel~s~ te gebruiken.', - ['shop_item'] = '€%s', - ['bought'] = 'Je hebt ~b~%sx %s~s~ gekocht voor ~b~€%s', - ['not_enough'] = 'je hebt ~r~niet~s~ genoeg geld, je mist nog ~b~€%s!', - ['player_cannot_hold'] = 'je hebt ~r~niet~s~ genoeg ruimte in je inventaris!', - ['shop_confirm'] = 'Wil je %sx %s kopen voor €%s?', - ['no'] = 'nee', - ['yes'] = 'ja', - ['amount'] = 'Bedrag', - ['amount_placeholder'] = 'Hoeveelheid dat je wil kopen', - ['confirm'] = 'Bevestig', - ['purchase'] = 'Koop', - ['bread'] = 'Brood', - ['water'] = 'Water', + ['open_shop_help'] = 'Druk op ~INPUT_CONTEXT~ om de winkel te openen', + ['player_not_found'] = 'Speler niet gevonden', + ['cart_empty'] = 'Winkelwagen is leeg', + ['invalid_cart_entry'] = 'Ongeldige winkelwageninvoer', + ['invalid_item_data'] = 'Ongeldige artikelgegevens', + ['unknown_item'] = 'Onbekend item: %s', + ['invalid_quantity'] = 'Ongeldige hoeveelheid', + ['not_enough_cash'] = 'Niet genoeg contant geld', + ['not_enough_bank'] = 'Onvoldoende banksaldo', + ['purchase_success'] = 'Aankoop geslaagd' + } diff --git a/[esx_addons]/esx_shops/locales/pl.lua b/[esx_addons]/esx_shops/locales/pl.lua index b6a5abce..382e1070 100644 --- a/[esx_addons]/esx_shops/locales/pl.lua +++ b/[esx_addons]/esx_shops/locales/pl.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['pl'] = { ['shop'] = 'sklep', - ['shops'] = 'sklepy', - ['press_menu'] = 'naciśnij [%s] żeby wejść do sklepu.', - ['shop_item'] = '$%s', - ['bought'] = 'właśnie zakupiłeś %s x %s za %s $', - ['not_enough'] = 'nie masz ~r~wystarczjąco pięniędzy, Brakuje Ci ~r~$%s!', - ['player_cannot_hold'] = '~r~Nie masz wystarczająco wolnego miejsca w swoim ekwipunku!', - ['shop_confirm'] = 'chcesz kupić %sx %s za $%s?', - ['no'] = 'nie', - ['yes'] = 'tak', - ['amount'] = 'Amount', --not translated - ['amount_placeholder'] = 'Amount you want to buy', --not translated - ['confirm'] = 'Confirm', --not translated - ['purchase'] = 'Purchase', --not translated - ['bread'] = 'Bread', --not translated - ['water'] = 'Water', --not translated + ['open_shop_help'] = 'Naciśnij ~INPUT_CONTEXT~, aby otworzyć sklep', + ['player_not_found'] = 'Nie znaleziono gracza', + ['cart_empty'] = 'Koszyk jest pusty', + ['invalid_cart_entry'] = 'Nieprawidłowa pozycja koszyka', + ['invalid_item_data'] = 'Nieprawidłowe dane przedmiotu', + ['unknown_item'] = 'Nieznany przedmiot: %s', + ['invalid_quantity'] = 'Nieprawidłowa ilość', + ['not_enough_cash'] = 'Za mało gotówki', + ['not_enough_bank'] = 'Niewystarczające saldo bankowe', + ['purchase_success'] = 'Zakup udany' + } diff --git a/[esx_addons]/esx_shops/locales/sl.lua b/[esx_addons]/esx_shops/locales/sl.lua index 8e05d6ea..990f19ed 100644 --- a/[esx_addons]/esx_shops/locales/sl.lua +++ b/[esx_addons]/esx_shops/locales/sl.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['sl'] = { ['shop'] = 'Trgovina', - ['shops'] = 'Trgovine', - ['press_menu'] = 'pritisni [%s] da odpres ~g~Trgovino.', - ['shop_item'] = '$%s', - ['bought'] = 'Vi ste kupili ~b~%sx %s~s~ za ~b~$%s', - ['not_enough'] = 'Vi ~r~nimate~s~ dovolj denarja, manjka vam ~b~$%s!', - ['player_cannot_hold'] = 'Vi ~r~nimate~s~ dovolj prostora v vasi shrambi!', - ['shop_confirm'] = 'kupi %sx %s za $%s?', - ['no'] = 'ne', - ['yes'] = 'da', - ['amount'] = 'vsota', - ['amount_placeholder'] = 'Koliko kosov bi kupili?', - ['confirm'] = 'Potrdi', - ['purchase'] = 'Kupi', - ['bread'] = 'Krh', - ['water'] = 'Voda', + ['open_shop_help'] = 'Pritisnite ~INPUT_CONTEXT~, da odprete trgovino', + ['player_not_found'] = 'Igralec ni najden', + ['cart_empty'] = 'Košarica je prazna', + ['invalid_cart_entry'] = 'Neveljaven vnos v košarici', + ['invalid_item_data'] = 'Neveljavni podatki o izdelku', + ['unknown_item'] = 'Neznan izdelek: %s', + ['invalid_quantity'] = 'Neveljavna količina', + ['not_enough_cash'] = 'Premalo gotovine', + ['not_enough_bank'] = 'Premalo sredstev na računu', + ['purchase_success'] = 'Nakup uspešen' + } diff --git a/[esx_addons]/esx_shops/locales/sr.lua b/[esx_addons]/esx_shops/locales/sr.lua index 2831dd29..dc3c1b91 100644 --- a/[esx_addons]/esx_shops/locales/sr.lua +++ b/[esx_addons]/esx_shops/locales/sr.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['sr'] = { ['shop'] = 'Prodavnica', - ['shops'] = 'Prodavnice', - ['press_menu'] = 'Pritisni [%s] da pristupiš ~g~prodavnici.', - ['shop_item'] = '$%s', - ['bought'] = 'Kupili ste ~b~%sx %s~s~ za ~b~$%s', - ['not_enough'] = 'Vi ~r~nemate~s~ dovoljno novca, nedostaje vam ~b~$%s!', - ['player_cannot_hold'] = 'Vi ~r~nemate~s~ dovoljno mesta u vašem inventaru!', - ['shop_confirm'] = 'Kupi %sx %s za $%s?', - ['no'] = 'Ne', - ['yes'] = 'Da', - ['amount'] = 'Amount', --not translated - ['amount_placeholder'] = 'Amount you want to buy', --not translated - ['confirm'] = 'Confirm', --not translated - ['purchase'] = 'Purchase', --not translated - ['bread'] = 'Bread', --not translated - ['water'] = 'Water', --not translated + ['open_shop_help'] = 'Pritisnite ~INPUT_CONTEXT~ da otvorite prodavnicu', + ['player_not_found'] = 'Igrač nije pronađen', + ['cart_empty'] = 'Korpa je prazna', + ['invalid_cart_entry'] = 'Nevažeći unos u korpi', + ['invalid_item_data'] = 'Nevažeći podaci o artiklu', + ['unknown_item'] = 'Nepoznat artikal: %s', + ['invalid_quantity'] = 'Nevažeća količina', + ['not_enough_cash'] = 'Nedovoljno gotovine', + ['not_enough_bank'] = 'Nedovoljno sredstava u banci', + ['purchase_success'] = 'Kupovina uspešna' + } diff --git a/[esx_addons]/esx_shops/locales/sv.lua b/[esx_addons]/esx_shops/locales/sv.lua index 0d419f6e..26b91ff2 100644 --- a/[esx_addons]/esx_shops/locales/sv.lua +++ b/[esx_addons]/esx_shops/locales/sv.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['sv'] = { - ['shop'] = 'Affär', - ['shops'] = 'Affärer', - ['press_menu'] = 'Tryck [%s] för att öppna ~g~affären.', - ['shop_item'] = '%skr', - ['bought'] = 'Du har köpt ~b~%sx %s~s~ för ~b~%skr', - ['not_enough'] = 'Du har ~r~inte~s~ råd, det fattas ~b~%skr!', - ['player_cannot_hold'] = 'Du har ~r~inte~s~ plats i inventoryt för detta!', - ['shop_confirm'] = 'Köp %sx %s för %skr?', - ['no'] = 'Ja', - ['yes'] = 'Nej', - ['amount'] = 'Antal', - ['amount_placeholder'] = 'Antal du vill köpa', - ['confirm'] = 'Godkänn', - ['purchase'] = 'Köp', - ['bread'] = 'Bröd', - ['water'] = 'Vatten', - } + ['shop'] = 'Affär', + ['open_shop_help'] = 'Tryck ~INPUT_CONTEXT~ för att öppna affären', + ['player_not_found'] = 'Spelare hittades inte', + ['cart_empty'] = 'Kundvagnen är tom', + ['invalid_cart_entry'] = 'Ogiltig kundvagnspost', + ['invalid_item_data'] = 'Ogiltig artikeldata', + ['unknown_item'] = 'Okänt objekt: %s', + ['invalid_quantity'] = 'Ogiltig mängd', + ['not_enough_cash'] = 'Inte tillräckligt med kontanter', + ['not_enough_bank'] = 'Otillräckligt banksaldo', + ['purchase_success'] = 'Köpet lyckades' + +} diff --git a/[esx_addons]/esx_shops/locales/tr.lua b/[esx_addons]/esx_shops/locales/tr.lua index 78315a8d..bebf87bd 100644 --- a/[esx_addons]/esx_shops/locales/tr.lua +++ b/[esx_addons]/esx_shops/locales/tr.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['tr'] = { ['shop'] = 'Market', - ['shops'] = 'Marketler', - ['press_menu'] = 'Marketi açmak için ~b~[%s]~s~ tuşuna bas.', - ['shop_item'] = '$%s', - ['bought'] = '~b~%s~s~x ~b~%s~s~ satın aldın ve ~b~$%s ~s~ödedin.', - ['not_enough'] = 'Paranız yeterli ~r~değil~s~, ~b~$%s ~s~eksik!', - ['player_cannot_hold'] = 'Envanterinizde boş yer ~r~yok~s~!', - ['shop_confirm'] = '%sx %s için $%s satın almak istiyor musun?', - ['no'] = 'hayır', - ['yes'] = 'evet', - ['amount'] = 'Miktar', - ['amount_placeholder'] = 'Satın almak istediğin miktar', - ['confirm'] = 'Onayla', - ['purchase'] = 'Satın Al', - ['bread'] = 'Ekmek', - ['water'] = 'Su', + ['open_shop_help'] = 'Mağazayı açmak için ~INPUT_CONTEXT~ tuşuna basın', + ['player_not_found'] = 'Oyuncu bulunamadı', + ['cart_empty'] = 'Sepet boş', + ['invalid_cart_entry'] = 'Geçersiz sepet girdisi', + ['invalid_item_data'] = 'Geçersiz ürün verisi', + ['unknown_item'] = 'Bilinmeyen ürün: %s', + ['invalid_quantity'] = 'Geçersiz miktar', + ['not_enough_cash'] = 'Yeterli nakit yok', + ['not_enough_bank'] = 'Yetersiz banka bakiyesi', + ['purchase_success'] = 'Satın alma başarılı' + } diff --git a/[esx_addons]/esx_shops/locales/zh-cn.lua b/[esx_addons]/esx_shops/locales/zh-cn.lua index ac7e27cf..588218f9 100644 --- a/[esx_addons]/esx_shops/locales/zh-cn.lua +++ b/[esx_addons]/esx_shops/locales/zh-cn.lua @@ -1,18 +1,15 @@ +---@diagnostic disable: undefined-global Locales['zh-cn'] = { ['shop'] = '购物商店', - ['shops'] = '购物商店', - ['press_menu'] = '键下 [%s] 访问~g~购物商店.', - ['shop_item'] = '$%s', - ['bought'] = '已购 ~b~%sx %s~s~ -支付:~b~$%s', - ['not_enough'] = '暂无足够资金, 您还需要~b~$%s!', - ['player_cannot_hold'] = '背包尚无足够剩余空间!', - ['shop_confirm'] = '确认购买 %sX%s -支付:$%s?', - ['no'] = '取消', - ['yes'] = '确认', - ['amount'] = 'Amount', --not translated - ['amount_placeholder'] = 'Amount you want to buy', --not translated - ['confirm'] = 'Confirm', --not translated - ['purchase'] = 'Purchase', --not translated - ['bread'] = 'Bread', --not translated - ['water'] = 'Water', --not translated + ['open_shop_help'] = '按下 ~INPUT_CONTEXT~ 打开商店', + ['player_not_found'] = '未找到玩家', + ['cart_empty'] = '购物车为空', + ['invalid_cart_entry'] = '购物车条目无效', + ['invalid_item_data'] = '物品数据无效', + ['unknown_item'] = '未知物品:%s', + ['invalid_quantity'] = '数量无效', + ['not_enough_cash'] = '现金不足', + ['not_enough_bank'] = '银行余额不足', + ['purchase_success'] = '购买成功' + } diff --git a/[esx_addons]/esx_shops/server/main.lua b/[esx_addons]/esx_shops/server/main.lua index 895bf0ae..86a7c633 100644 --- a/[esx_addons]/esx_shops/server/main.lua +++ b/[esx_addons]/esx_shops/server/main.lua @@ -19,7 +19,7 @@ end RegisterServerEvent('esx_shops:buyItem') AddEventHandler('esx_shops:buyItem', function(itemName, amount, zone) local source = source - local xPlayer = ESX.Player(source) + local xPlayer = ESX.GetPlayerFromId(source) local Exists, price, label = GetItemFromShop(itemName, zone) amount = ESX.Math.Round(amount) @@ -28,26 +28,58 @@ AddEventHandler('esx_shops:buyItem', function(itemName, amount, zone) return end - if not Exists then - print(('[^3WARNING^7] Player ^5%s^7 attempted to exploit the shop!'):format(source)) - return - end + local method = data and data.method or 'cash' + local cart = data and data.cart or {} + if type(cart) ~= 'table' or #cart == 0 then + cb(false, _U('cart_empty')) + return + end - if Exists then - price = price * amount - -- can the player afford this item? - if xPlayer.getMoney() >= price then - -- can the player carry the said amount of x item? - if xPlayer.canCarryItem(itemName, amount) then - xPlayer.removeMoney(price, label .. " " .. TranslateCap('purchase')) - xPlayer.addInventoryItem(itemName, amount) - xPlayer.showNotification(TranslateCap('bought', amount, label, ESX.Math.GroupDigits(price))) - else - xPlayer.showNotification(TranslateCap('player_cannot_hold')) - end - else - local missingMoney = price - xPlayer.getMoney() - xPlayer.showNotification(TranslateCap('not_enough', ESX.Math.GroupDigits(missingMoney))) - end - end + local maxQty = Config.ItemMaxQuantity or 100 + + local total = 0 + local normalizedCart = {} + for _, entry in ipairs(cart) do + if type(entry) ~= 'table' then + cb(false, _U('invalid_cart_entry')) + return + end + local name = entry.name + local amount = tonumber(entry.amount) + if not name or not amount then + cb(false, _U('invalid_item_data')) + return + end + local def = itemIndex[name] + if not def then + cb(false, _U('unknown_item', name)) + return + end + if amount < 1 or amount > maxQty then + cb(false, _U('invalid_quantity')) + return + end + total = total + (def.price * amount) + normalizedCart[#normalizedCart + 1] = { name = name, amount = amount } + end + + if method == 'bank' then + if xPlayer.getAccount('bank').money < total then + cb(false, _U('not_enough_bank')) + return + end + xPlayer.removeAccountMoney('bank', total) + else + if xPlayer.getMoney() < total then + cb(false, _U('not_enough_cash')) + return + end + xPlayer.removeMoney(total) + end + + for _, entry in ipairs(normalizedCart) do + xPlayer.addInventoryItem(entry.name, entry.amount) + end + + cb(true, _U('purchase_success')) end) From 6f81ff4bdb92e267a4aaaf7ad13cc7301978b112 Mon Sep 17 00:00:00 2001 From: SyncOusli <142453413+SyncOusli@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:26:47 +0100 Subject: [PATCH 2/2] Add HTML and CSS for esx_shops interface - Created index.html for the shop layout including header, categories, items, and shopping cart. - Implemented style.css for responsive design and aesthetics, utilizing Flexbox and Grid for layout. - Integrated Font Awesome for icons and Google Fonts for typography. - Added placeholder items with prices and images for demonstration purposes. --- [esx_addons]/esx_shops/client/main.lua | 10 - [esx_addons]/esx_shops/config.lua | 2 +- [esx_addons]/esx_shops/fxmanifest.lua | 2 +- [esx_addons]/esx_shops/html/app.js | 270 ++++++++++ [esx_addons]/esx_shops/html/index.html | 668 +++++++++++++++++++++++++ [esx_addons]/esx_shops/html/style.css | 506 +++++++++++++++++++ [esx_addons]/esx_shops/server/main.lua | 57 ++- 7 files changed, 1479 insertions(+), 36 deletions(-) create mode 100644 [esx_addons]/esx_shops/html/app.js create mode 100644 [esx_addons]/esx_shops/html/index.html create mode 100644 [esx_addons]/esx_shops/html/style.css diff --git a/[esx_addons]/esx_shops/client/main.lua b/[esx_addons]/esx_shops/client/main.lua index 3f1aa5fb..d0b8f6f2 100644 --- a/[esx_addons]/esx_shops/client/main.lua +++ b/[esx_addons]/esx_shops/client/main.lua @@ -18,16 +18,6 @@ local function closeShopUi() setShopUiVisible(false) end -RegisterCommand('shopui', function() - if isShopUiOpen then - closeShopUi() - else - openShopUi() - end -end, false) - -RegisterKeyMapping('shopui', 'Toggle Shop UI', 'keyboard', 'F7') - RegisterNUICallback('close', function(_, cb) closeShopUi() if cb then cb({}) end diff --git a/[esx_addons]/esx_shops/config.lua b/[esx_addons]/esx_shops/config.lua index 39d54b68..4bd28e17 100644 --- a/[esx_addons]/esx_shops/config.lua +++ b/[esx_addons]/esx_shops/config.lua @@ -1,6 +1,6 @@ Config = {} -Config.Locale = GetConvar('esx:locale', 'fr') +Config.Locale = GetConvar('esx:locale', 'en') Config.ItemMaxQuantity = 100 diff --git a/[esx_addons]/esx_shops/fxmanifest.lua b/[esx_addons]/esx_shops/fxmanifest.lua index 30387906..e19eabfa 100644 --- a/[esx_addons]/esx_shops/fxmanifest.lua +++ b/[esx_addons]/esx_shops/fxmanifest.lua @@ -33,4 +33,4 @@ files { 'html/index.html', 'html/app.js', 'html/style.css', -} +} \ No newline at end of file diff --git a/[esx_addons]/esx_shops/html/app.js b/[esx_addons]/esx_shops/html/app.js new file mode 100644 index 00000000..5db5ca33 --- /dev/null +++ b/[esx_addons]/esx_shops/html/app.js @@ -0,0 +1,270 @@ +$(function () { + const resourceName = + typeof GetParentResourceName === "function" + ? GetParentResourceName() + : "esx_shops"; + + function nuiPost(action, payload) { + $.post(`https://${resourceName}/${action}`, JSON.stringify(payload || {})); + } + + $(".Container").hide(); + + // State + let categories = []; + let items = []; + let activeCategoryId = null; + let searchTerm = ""; + const cartByName = {}; // name -> { def, amount } + + const $categories = $(".Categories-Scroller"); + const $items = $(".Shop-Items"); + const $basket = $(".Right-Shop-Basket"); + const $basketEmpty = $(".Right-Shop-Basket .Basket-Empty"); + const $totalPrice = $(".Right-Shop-End-Price span").last(); + + function formatPrice(value) { + return `${value}$`; + } + + function getFilteredItems() { + return items.filter((i) => { + const matchesCat = !activeCategoryId || i.category === activeCategoryId; + const matchesSearch = + !searchTerm || (i.label || i.name).toLowerCase().includes(searchTerm); + return matchesCat && matchesSearch; + }); + } + + function renderCategories() { + $categories.empty(); + categories.forEach((c, idx) => { + const $el = $(`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+