diff --git a/[esx_addons]/esx_shops/client/main.lua b/[esx_addons]/esx_shops/client/main.lua
index 59c5a1db..d0b8f6f2 100644
--- a/[esx_addons]/esx_shops/client/main.lua
+++ b/[esx_addons]/esx_shops/client/main.lua
@@ -1,121 +1,106 @@
-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])
-
- SetBlipSprite (blip, v.Type)
- SetBlipScale (blip, v.Size)
- SetBlipColour (blip, v.Color)
- SetBlipAsShortRange(blip, true)
-
- 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..4bd28e17 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 = {
- 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.Locale = GetConvar('esx:locale', 'en')
- 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
-},
+Config.ItemMaxQuantity = 100
- 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..e19eabfa 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',
+}
\ 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 = $(`
${c.label}
`);
+ if (!activeCategoryId && idx === 0) activeCategoryId = c.id;
+ if (c.id === activeCategoryId) $el.addClass("active");
+ $el.on("click", () => {
+ activeCategoryId = c.id;
+ renderCategories();
+ renderItems();
+ });
+ $categories.append($el);
+ });
+ }
+
+ function createItemCard(item) {
+ const $card = $(`
+
+
+
+
![]()
+
+
+
+ `);
+ $card.find(".Item-Label").text(item.label || item.name);
+ $card.find(".Item-Price").text(`$ ${item.price}`);
+ $card.find("img").attr("src", item.image || "");
+ $card
+ .find(".Item-Cart")
+ .on("click", () => addToCart(item, item.amount || 1));
+ return $card;
+ }
+
+ function renderItems() {
+ $items.empty();
+ const list = getFilteredItems();
+ list.forEach((item) => {
+ $items.append(createItemCard(item));
+ });
+ }
+
+ function recomputeTotal() {
+ let total = 0;
+ Object.values(cartByName).forEach((entry) => {
+ total += entry.def.price * entry.amount;
+ });
+ $totalPrice.text(formatPrice(total));
+ }
+
+ function renderBasket() {
+ $basket.find(".Basket-Item").remove();
+ const entries = Object.values(cartByName);
+ if (entries.length === 0) {
+ $basketEmpty.show();
+ } else {
+ $basketEmpty.hide();
+ }
+ entries.forEach((entry) => {
+ const { def, amount } = entry;
+ const $row = $(`
+
+ `);
+ $row.find("img").attr("src", def.image || "");
+ $row.find(".Basket-Item-Label").text(def.label || def.name);
+ $row.find(".Basket-Item-Price").text(`${def.price} $`);
+ const $count = $row.find("input.count");
+ $count.val(amount);
+ $row
+ .find("button.decrease")
+ .on("click", () =>
+ updateCart(def.name, cartByName[def.name].amount - 1)
+ );
+ $row
+ .find("button.increase")
+ .on("click", () =>
+ updateCart(def.name, cartByName[def.name].amount + 1)
+ );
+ $count.on("change", () =>
+ updateCart(def.name, parseInt($count.val(), 10) || 1)
+ );
+ $row.find(".Count-Remove").on("click", () => removeFromCart(def.name));
+ $basket.append($row);
+ });
+ recomputeTotal();
+ }
+
+ function addToCart(def, addAmount) {
+ const maxQty = 100;
+ const name = def.name;
+ if (!cartByName[name]) {
+ cartByName[name] = { def, amount: 0 };
+ }
+ const prevAmount = cartByName[name].amount;
+ cartByName[name].amount = Math.min(
+ maxQty,
+ cartByName[name].amount + (addAmount || 1)
+ );
+ const addedNow = cartByName[name].amount - prevAmount;
+ if (addedNow > 0) {
+ console.log(
+ `[Shop] Added to basket: ${
+ def.label || def.name
+ } x${addedNow} (total: ${cartByName[name].amount})`
+ );
+ } else {
+ console.warn(
+ `[Shop] Not added: ${def.label || def.name} (reached max ${maxQty})`
+ );
+ }
+ renderBasket();
+ }
+
+ function updateCart(name, newAmount) {
+ if (!cartByName[name]) return;
+ if (newAmount <= 0) {
+ delete cartByName[name];
+ } else {
+ const maxQty = 100;
+ cartByName[name].amount = Math.min(maxQty, newAmount);
+ }
+ renderBasket();
+ }
+
+ function removeFromCart(name) {
+ if (cartByName[name]) {
+ delete cartByName[name];
+ renderBasket();
+ }
+ }
+
+ function getCartPayload() {
+ const cart = [];
+ Object.values(cartByName).forEach((entry) => {
+ cart.push({ name: entry.def.name, amount: entry.amount });
+ });
+ return cart;
+ }
+
+ function checkout(method) {
+ const cart = getCartPayload();
+ if (cart.length === 0) return;
+ $.post(
+ `https://${resourceName}/purchase`,
+ JSON.stringify({ method, cart }),
+ function (resp) {
+ try {
+ const data = typeof resp === "string" ? JSON.parse(resp) : resp;
+ if (data && data.success) {
+ Object.keys(cartByName).forEach((k) => delete cartByName[k]);
+ renderBasket();
+ } else {
+ }
+ } catch (_) {}
+ }
+ );
+ }
+
+ window.addEventListener("message", function (event) {
+ const data = event.data || {};
+ switch (data.action) {
+ case "show":
+ $(".Container").stop(true, true).fadeIn(150);
+ $.post(
+ `https://${resourceName}/getShopData`,
+ JSON.stringify({}),
+ function (resp) {
+ try {
+ const payload =
+ typeof resp === "string" ? JSON.parse(resp) : resp;
+ categories = payload.categories || [];
+ items = payload.items || [];
+ activeCategoryId = (categories[0] && categories[0].id) || null;
+ renderCategories();
+ renderItems();
+ renderBasket();
+ } catch (_) {}
+ }
+ );
+ break;
+ case "hide":
+ $(".Container").stop(true, true).fadeOut(150);
+ break;
+ }
+ });
+
+ $(".Shop-close").on("click", function () {
+ nuiPost("close");
+ });
+
+ $(document).on("keydown", function (e) {
+ if (e.key === "Escape") {
+ nuiPost("close");
+ }
+ });
+
+ $(".Shop-search input[type=text]").on("input", function () {
+ searchTerm = ($(this).val() || "").toString().toLowerCase();
+ renderItems();
+ });
+
+ $(".Right-Shop-End-Button .Cash").on("click", function () {
+ checkout("cash");
+ });
+ $(".Right-Shop-End-Button .Bank").on("click", function () {
+ checkout("bank");
+ });
+});
diff --git a/[esx_addons]/esx_shops/html/index.html b/[esx_addons]/esx_shops/html/index.html
new file mode 100644
index 00000000..553778cd
--- /dev/null
+++ b/[esx_addons]/esx_shops/html/index.html
@@ -0,0 +1,668 @@
+
+
+
+
+
+
+ esx_shops
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+

+
+
+
+
+
+
+ Add to Cart
+
+
+
+
+
+
+
+
+ SHOPPING CART
+
+
+
+
+ Your basket is empty
+ Add something to checkout
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+ TOTAL PRICE:
+ 5000$
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/[esx_addons]/esx_shops/html/style.css b/[esx_addons]/esx_shops/html/style.css
new file mode 100644
index 00000000..95f04b5c
--- /dev/null
+++ b/[esx_addons]/esx_shops/html/style.css
@@ -0,0 +1,506 @@
+@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
+
+* {
+ margin: 0;
+ padding: 0;
+}
+
+.Container {
+ background: rgba(22, 22, 22, 1);
+ width: 80vw;
+ height: 80vh;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ display: none;
+}
+
+.Header-Shop {
+ display: flex;
+ width: 100%;
+ /* background-color: red; */
+ justify-content: space-between;
+ height: 5vh;
+ margin-top: 0.5vw;
+}
+
+.Shop-name {
+ display: flex;
+ align-items: center;
+ font-family: "Poppins", sans-serif;
+ margin-left: 1vw;
+ gap: 0.5vw;
+}
+
+.Shop-search {
+ display: flex;
+ align-items: center;
+ margin-right: 1vw;
+ gap: 0.5vw;
+}
+
+.Shop-title {
+ color: rgba(242, 242, 242, 1);
+ font-family: "Poppins", sans-serif;
+ font-weight: 600;
+ font-size: 1vw;
+}
+
+.Shop-icon {
+ color: rgba(242, 242, 242, 1);
+ font-size: 1vw;
+}
+
+.Shop-search-icon input {
+ border: none;
+ /* remove style */
+ background: rgba(242, 242, 242, 0.1);
+ color: rgba(242, 242, 242, 1);
+ font-family: "Poppins", sans-serif;
+ font-weight: 400;
+ font-size: 0.8vw;
+ border-radius: 0.2vw;
+ padding: 0 0.5vw;
+ height: 3.4vh;
+ width: 50vw;
+ padding-left: 2vw;
+}
+
+.Shop-search-icon input:focus {
+ outline: none;
+}
+
+.Shop-search-icon {
+ position: relative;
+}
+
+.Shop-search-icon i {
+ position: absolute;
+ left: 0.5vw;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #aaa;
+ pointer-events: none;
+ font-size: 0.8vw;
+}
+
+.Shop-close {
+ background-color: rgba(242, 242, 242, 0.1);
+ color: rgba(242, 242, 242, 1);
+ height: 3.4vh;
+ width: 2vw;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0.2vw;
+}
+
+.Shop-close i {
+ font-size: 1vw;
+}
+
+.Container-Shop {
+ margin-top: 0.5vw;
+ display: flex;
+ width: 100%;
+ /* background-color: red; */
+}
+
+.Left-Shop {
+ width: 70%;
+ /* background-color: blue; */
+ height: 2vw;
+}
+
+.Right-Shop {
+ width: 28%;
+ /* background-color: green; */
+ height: 10vw;
+}
+
+.Right-Shop-Title {
+ text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5vw;
+ /* background-color: red; */
+}
+
+.Right-Shop-Basket {
+ margin-top: 1.5vw;
+ /* background-color: red; */
+ height: 55vh;
+ width: 100%;
+ overflow-y: auto;
+ padding-right: 0.5vw;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+
+.Right-Shop-Title span {
+ color: white;
+ font-family: "Poppins", sans-serif;
+ font-weight: 600;
+ font-size: 0.9vw;
+}
+
+.Right-Shop-Title i {
+ color: rgba(250, 250, 250, 1);
+ font-size: 0.8vw;
+}
+
+.Basket-Item {
+ background-color: #212121;
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.5vw;
+ border-radius: 0.2vw;
+ gap: 0.5vw;
+ color: white;
+ justify-content: space-between;
+}
+
+.Basket-Item-LeftSection,
+.Basket-Item-RightSection {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.Basket-Item-Info {
+ color: white;
+ font-family: "Poppins", sans-serif;
+ font-weight: 500;
+}
+
+.Basket-Item-Label {
+ font-size: 0.6vw;
+}
+
+.Basket-Item-Price {
+ font-size: 0.7vw;
+}
+
+.Basket-Item-Img img {
+ width: 2vw;
+ height: 2vw;
+ border-radius: 0.2vw;
+ padding: 0.3vw;
+}
+
+.Shop-Categories {
+ margin-left: 1vw;
+ overflow-x: auto;
+ /* Important for scroll */
+ max-width: calc(15% * 6 + 0.5vw * 5);
+ /* Space for 6 items + 5 gaps */
+ scrollbar-width: none;
+}
+
+.Shop-Categories::-webkit-scrollbar {
+ display: none;
+ /* Chrome, Safari, Opera */
+}
+
+.Categories-Scroller {
+ display: flex;
+ gap: 0.5vw;
+ min-width: fit-content;
+ /* Make sure content does not shrink */
+}
+
+.Categories-Wrap {
+ background: rgba(242, 242, 242, 0.1);
+ color: rgba(242, 242, 242, 0.5);
+ font-family: Poppins;
+ font-weight: 500;
+ font-size: 0.8vw;
+ padding: 0.3vw 0.5vw;
+ width: 14%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0.3vw;
+ white-space: nowrap;
+ flex-shrink: 0;
+ /* Prevent shrinking in scroll */
+}
+
+.Categories-Wrap.active {
+ background: rgba(251, 155, 4, 1);
+ color: rgba(22, 22, 22, 1);
+}
+
+.Shop-Items {
+ margin-top: 1vw;
+ margin-left: 1vw;
+ width: 94.5%;
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 0.5vw;
+ max-height: 64vh;
+ /* Limit height for scrolling */
+ /* background-color: red; */
+ overflow-y: auto;
+ /* Enable vertical scroll */
+ overflow-x: hidden;
+ /* background-color: red; */
+ padding-right: 0.5vw;
+}
+
+.Item {
+ display: flex;
+ flex-direction: column;
+ background-color: rgba(242, 242, 242, 0.05);
+ border-radius: 0.3vw;
+ font-family: "Poppins", sans-serif;
+ width: 100%;
+ /* ahora se adapta al grid */
+}
+
+.Item-Info {
+ display: flex;
+ justify-content: space-between;
+ padding: 0.3vw;
+}
+
+.Item-Label {
+ color: rgba(242, 242, 242, 1);
+ font-weight: 500;
+ font-size: 0.8vw;
+}
+
+.Item-Price {
+ background: rgba(242, 242, 242, 0.1);
+ padding: 0.1vw 0.2vw;
+ font-weight: 600;
+ font-size: 0.6vw;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0.15vw;
+}
+
+.Item-Cart {
+ display: flex;
+ background: rgba(242, 242, 242, 0.1);
+ justify-content: center;
+ align-items: center;
+ padding: 0.2vw;
+ gap: 0.5vw;
+ border-bottom-left-radius: 0.3vw;
+ border-bottom-right-radius: 0.3vw;
+}
+
+.Item-Cart-Icon {
+ color: rgba(242, 242, 242, 1);
+ font-size: 0.8vw;
+}
+
+.Item-Cart-Label {
+ color: rgba(242, 242, 242, 1);
+ font-size: 0.7vw;
+}
+
+.Item-Image {
+ padding: 1vw 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.Item-Image img {
+ width: 4vw;
+ height: 4vw;
+}
+
+.Item:hover {
+ background: linear-gradient(180deg, rgba(251, 155, 4, 0.1) 0%, rgba(251, 155, 4, 0) 100%);
+ transition: background 0.3s ease-in-out;
+ box-shadow: 0px 0px 4px 0px rgba(251, 155, 4, 0.25) inset;
+ border: 1px solid rgba(251, 155, 4, 0.5);
+ cursor: pointer;
+}
+
+.Item:hover .Item-Cart {
+ background: rgba(251, 155, 4, 1);
+ transition: background 0.5s ease-in-out;
+}
+
+.Item:hover .Item-Cart-Icon,
+.Item:hover .Item-Cart-Label {
+ color: rgba(22, 22, 22, 1);
+ font-weight: 600;
+}
+
+.Right-Shop-End {
+ /* background: red; */
+ display: flex;
+ flex-direction: column;
+ height: 10vh;
+ justify-content: flex-end;
+}
+
+.Right-Shop-End-Button {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5vw;
+ /* background-color: red; */
+}
+
+.Cash,
+.Bank {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(242, 242, 242, 0.1);
+ width: 50%;
+ color: rgba(242, 242, 242, 0.349);
+ padding: 0.4vw 0.3vw;
+ border-radius: 0.3vw;
+ font-family: "Poppins", sans-serif;
+ gap: 0.5vw;
+ font-weight: 600;
+ font-size: 0.8vw;
+}
+
+#decrease,
+#increase {
+ background: none;
+ color: inherit;
+ border: none;
+ padding: 0;
+ font: inherit;
+ cursor: pointer;
+ outline: inherit;
+ background-color: rgba(242, 242, 242, 0.1);
+ height: 1.2vw;
+ width: 1.2vw;
+ border-radius: 0.1vw;
+ font-size: 0.8vw;
+}
+
+#count {
+ background: none;
+ color: inherit;
+ border: none;
+ padding: 0;
+ font: inherit;
+ cursor: pointer;
+ outline: inherit;
+ width: 2.5vw;
+ font-size: 0.8vw;
+ font-family: "Poppins", sans-serif;
+ text-align: center;
+}
+
+.Count-Remove {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: 1vw;
+ margin-right: 1vw;
+ background-color: rgba(244, 91, 105, 0.2);
+ height: 1.2vw;
+ width: 1.2vw;
+ border-radius: 0.1vw;
+}
+
+.Count-Remove i {
+ color: rgba(244, 91, 105, 1);
+ font-size: 0.8vw;
+}
+
+.Right-Shop-End-Price {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.4vw 0vw;
+}
+
+.Right-Shop-End-Price span {
+ font-family: "Poppins", sans-serif;
+ color: rgba(242, 242, 242, 1);
+ font-weight: 600;
+ font-size: 0.9vw;
+}
+
+.Basket-Empty {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.Basket-Empty i {
+ font-size: 5vw;
+ color: rgba(242, 242, 242, 0.281);
+}
+
+.Basket-Empty span {
+ text-align: center;
+ color: rgba(242, 242, 242, 0.281);
+ font-family: "Poppins", sans-serif;
+}
+
+.empty-basket-title {
+ margin-top: 0.4vw;
+ text-transform: uppercase;
+
+ font-size: 1vw;
+ font-weight: 600;
+}
+
+.empty-basket-subtitle {
+ font-size: 0.7vw;
+ margin-top: 0.2vw;
+ font-weight: 300;
+}
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+::-webkit-scrollbar {
+ width: 0.2vw;
+ height: 0.35vw;
+ border-radius: 100vw;
+}
+
+::-webkit-scrollbar-track {
+ background: rgba(251, 155, 4, 0.2);
+}
+
+::-webkit-scrollbar-thumb {
+ background: rgba(251, 155, 4, 1);
+ height: 0.3vw;
+ border-radius: 100vw;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.8);
+ height: 0.8vw;
+ border-radius: 100vw;
+}
+
+::-webkit-scrollbar-thumb:active {
+ background: rgba(255, 255, 255, 0.8);
+ height: 0.8vw;
+ border-radius: 100vw;
+}
+
+::-webkit-scrollbar-thumb:focus {
+ background: rgba(255, 255, 255, 0.8);
+ height: 0.3vw;
+ border-radius: 100vw;
+}
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..407c1442 100644
--- a/[esx_addons]/esx_shops/server/main.lua
+++ b/[esx_addons]/esx_shops/server/main.lua
@@ -1,53 +1,94 @@
-function GetItemFromShop(itemName, zone)
- local zoneItems = Config.Zones[zone].Items
- local item = nil
-
- for _, itemData in pairs(zoneItems) do
- if itemData.name == itemName then
- item = itemData
- break
- end
- end
-
- if not item then
- return false
- end
-
- return true,item.price, item.label
+---@diagnostic disable: undefined-global
+local ESX = exports['es_extended']:getSharedObject()
+
+local function buildItemIndex()
+ local index = {}
+ for _, item in ipairs(Config.Items or {}) do
+ index[item.name] = item
+ end
+ return index
+end
+
+local itemIndex = buildItemIndex()
+
+AddEventHandler('onResourceStart', function(resourceName)
+ if GetCurrentResourceName() ~= resourceName then return end
+ itemIndex = buildItemIndex()
+end)
+
+local function calculateCartTotal(cart)
+ local total = 0
+ for _, entry in ipairs(cart or {}) do
+ local def = itemIndex[entry.name]
+ if def then
+ local qty = tonumber(entry.amount) or 0
+ if qty > 0 then
+ total = total + (def.price * qty)
+ end
+ end
+ end
+ return total
end
-RegisterServerEvent('esx_shops:buyItem')
-AddEventHandler('esx_shops:buyItem', function(itemName, amount, zone)
- local source = source
- local xPlayer = ESX.Player(source)
- local Exists, price, label = GetItemFromShop(itemName, zone)
- amount = ESX.Math.Round(amount)
-
- if amount < 0 then
- print(('[^3WARNING^7] Player ^5%s^7 attempted to exploit the shop!'):format(source))
- return
- end
-
- if not Exists then
- print(('[^3WARNING^7] Player ^5%s^7 attempted to exploit the shop!'):format(source))
- 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
+ESX.RegisterServerCallback('esx_shops:purchase', function(source, cb, data)
+ local xPlayer = ESX.GetPlayerFromId(source)
+ if not xPlayer then
+ cb(false, _U('player_not_found'))
+ 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
+
+ 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)