# SLEEK SHOP SYSTEM

{% embed url="<https://youtu.be/d5wOS6ay_ZA?si=BZgqV6bSrlF47dU7>" %}

**INSTALLATION GUIDE**

1. Download from [KEYMASTER ](https://keymaster.fivem.net/login?return_url=/asset-grants)and Unzip the **`forge-shops.pack.zip`** and place this folder in your server's resource folder.
2. Add the resource to your server start config: **`ensure forge-shops`**,the name of the folder must not be changed or the resource will not function correctly.
3. Clear the cache of your server and also of your own FiveM.
4. Reboot the entire server with the forge script well ensured in your server.cfg.
5. <mark style="color:red;">**This script does not have any SQL to install.**</mark>
6. <mark style="color:red;">**Do not rename this script, this may cause it to fail when opening the interface.**</mark>

**CONFIG**

The following explains all the settings. Take a few minutes to understand them to provide your users with the best experience.

* **Store Customization:**\
  Each store is unique. Edit the item list in each store to add, remove, or modify items as you wish. You can use the default items from the config or add your own.
* **Job Restrictions:**\
  To restrict a store so that only specific jobs can open it, add a variable like this:

  ```lua
  allowedJobs = {"police"}
  ```

  This example is used in Store 1, but feel free to add or remove it in any store to limit access to one or more jobs.

***

{% tabs %}
{% tab title="CONFIG" %}
Fill all the **CONFIG** very carefully. &#x20;

{% code lineNumbers="true" %}

```lua
Config, Lang, Noti = {}, {}, {}

--  _____ _____ _   _ ______ _____ _____ _   _______  ___ _____ _____ _____ _   _ 
-- /  __ \  _  | \ | ||  ___|_   _|  __ \ | | | ___ \/ _ \_   _|_   _|  _  | \ | |
-- | /  \/ | | |  \| || |_    | | | |  \/ | | | |_/ / /_\ \| |   | | | | | |  \| |
-- | |   | | | | . ` ||  _|   | | | | __| | | |    /|  _  || |   | | | | | | . ` |
-- | \__/\ \_/ / |\  || |    _| |_| |_\ \ |_| | |\ \| | | || |  _| |_\ \_/ / |\  |
--  \____/\___/\_| \_/\_|    \___/ \____/\___/\_| \_\_| |_/\_/  \___/ \___/\_| \_/

-- Use "esx" or "qb"
Config.Framework = "esx"
-- If you are using one of the most recent versions of ESX, set the script name. Default = "es_extended"
Config.ESXExport = "es_extended"
-- Default ESX: "esx:getSharedObject" | Default QB: "qb-core"
Config.Core = "esx:getSharedObject"
-- Font for alerts. If you use Chinese language set to 0
Config.alertTextFont = 4
-- Account you use in esx/qb to cash at the bank
Config.bankaccount = "bank"
-- Account you use in esx/qb to collect cash
Config.cashaccount = "cash"
-- Choose if you want to use target (put the name of the one you have, it can be qb-target, ox-target...)
Config.UseTarget = {false,"ox-target","OPEN SHOP"}
-- The distance from the player at which the peds (if you decide to put shop NPCs) will spawn. By spawning based on proximity, it makes everything run smoother since there won't be many PEDs spawned at once
Config.PedSpawnRadius = 50
-- Maximum quantity that can be selected in the shop UI popup (does not override server-side stack limits)
Config.UIShopMaxQuantity = 20

--  _   _ _____ _____ ___________ _____ _____   ___ _____ _____ _____ _   _  _____ 
-- | \ | |  _  |_   _|_   _|  ___|_   _/  __ \ / _ \_   _|_   _|  _  | \ | |/  ___|
-- |  \| | | | | | |   | | | |_    | | | /  \// /_\ \| |   | | | | | |  \| |\ `--. 
-- | . ` | | | | | |   | | |  _|   | | | |    |  _  || |   | | | | | | . ` | `--. \
-- | |\  \ \_/ / | |  _| |_| |    _| |_| \__/\| | | || |  _| |_\ \_/ / |\  |/\__/ /
-- \_| \_/\___/  \_/  \___/\_|    \___/ \____/\_| |_/\_/  \___/ \___/\_| \_/\____/

function notifications(notitype, message, time)
    --Change this trigger for your notification system keeping the variables
    exports['forge-notify']:ShowNotification(notitype, message, time)
end

--Notifications types:
Noti.info = 'info'
Noti.check = 'success'
Noti.error = 'error'

--Notification time:
Noti.time = 5000

--  _       ___   _   _ _____ _   _  ___  _____  _____ 
-- | |     / _ \ | \ | |  __ \ | | |/ _ \|  __ \|  ___|
-- | |    / /_\ \|  \| | |  \/ | | / /_\ \ |  \/| |__  
-- | |    |  _  || . ` | | __| | | |  _  | | __ |  __| 
-- | |____| | | || |\  | |_\ \ |_| | | | | |_\ \| |___ 
-- \_____/\_| |_/\_| \_/\____/\___/\_| |_/\____/\____/

Lang.pressE = "Press ~b~[~w~E~b~]~w~ to access shop"
Lang.noMoney = "You don't have enough money"
Lang.addedItems  = "Products have been added to your inventory"
Lang.cart = "CART"
Lang.totalPrice = "TOTAL PRICE"
Lang.PayByBank = "PAY BY BANK"
Lang.PayByCash = "PAY BY CASH"
Lang.noJobAccess = "You don't have the required job to access this shop"
-- Popup texts
Lang.PopupTitle = "Select Quantity"
Lang.PopupAddButton = "Add"
Lang.PopupCloseButton = "Cancel"
Lang.PopupUpdateButton = "Update" 

--  _____ _   _ ___________  _____ 
-- /  ___| | | |  _  | ___ \/  ___|
-- \ `--.| |_| | | | | |_/ /\ `--. 
--  `--. \  _  | | | |  __/  `--. \
-- /\__/ / | | \ \_/ / |    /\__/ /
-- \____/\_| |_/\___/\_|    \____/

Config.animations = { -- Add as many animations as you want. Each NPC shopkeeper will do one of these animations.
    {
        name = "air_wave",
        dict = "anim@amb@waving@female"
    },
    {
        name = "ground_wave",
        dict = "anim@amb@waving@male"
    },
    {
        name = "base",
        dict = "amb@world_human_hang_out_street@female_arms_crossed@base"
    },
    {
        name = "rcmme_amanda1_stand_loop_cop",
        dict = "anim@amb@nightclub@peds@"
    },
    {
        name = "idle_reject_loop_a",
        dict = "mini@hookers_spvanilla"
    },
    {
        name = "friends@fra@ig_1",
        dict = "amb@world_human_hang_out_street@male_b@idle_a"
    },
    {
        name = "idle",
        dict = "mp_move@prostitute@m@french"
    },
    {
        name = "celebration_idle_f_a",
        dict = "anim@mp_celebration@idles@female"
    },
    {
        name = "idle_a",
        dict = "anim@mp_corona_idles@male_d@idle_a"
    },
    {
        name = "idle_a",
        dict = "amb@world_human_hang_out_street@female_hold_arm@idle_a"
    },
    {
        name = "drunk_driver_stand_loop_dd2",
        dict = "random@drunk_driver_1"
    },
    {
        name = "idle_a",
        dict = "amb@world_human_hang_out_street@Female_arm_side@idle_a"
    },
    {
        name = "idle",
        dict = "rcmjosh1"
    },
    {
        name = "ig_3_base_tracy",
        dict = "timetable@amanda@ig_3"
    },
    {
        name = "knuckle_crunch",
        dict = "anim@mp_player_intcelebrationfemale@knuckle_crunch"
    },
    {
        name = "fidget_sniff_fingers",
        dict = "move_p_m_two_idles@generic"
    },
    {
        name = "idle_ped06",
        dict = "anim@miss@low@fin@vagos@"
    },
    {
        name = "rehearsal_base_idle_director",
        dict = "misscarsteal4@aliens"
    },
  --{
      --name = "ground_wave",
      --dict = "anim@amb@waving@male"
  --}
}

Shops = {

    -- SUPER-MARKETS
    
    ["Supermarket 24/7 Hollywood"] = { --[1]
        label = "Shop", -- Add this to any shop where you need it to have a different label than the shop name.
        ped = { use = true, model = "CS_Josh", coords = vector4(24.5, -1346.19, 28.5, 266.78), useAnimations = true }, -- This will be the ped that will appear in the store, as shopkeeper. You can set false if you don't want there to be a ped.
        coord = vector3(4.5, -1346.19, 29.5),
        marker = {use = true, style = 23, r = 51, g = 175, b = 213},
        blip = {use = true, style = 52, color = 3, scale = 0.8, text = "Shop"},
        allowedJobs = {"police"}, -- If you want to restrict access to the shop to certain jobs, add the job name here. If you want everyone to have access, delete this line.
        items = {
            ["Water"] = {
                itemName = "water_bottle",
                itemImage = "water.png",
                itemPrice = 10,
                special = false
            },
            ["Coffee"] = {
                itemName = "coffee",
                itemImage = "coffee.png",
                itemPrice = 10,
                special = false
            },
            ["Cola"] = {
                itemName = "kurkakola",
                itemImage = "cola.png",
                itemPrice = 10,
                special = false
            },
            ["Bandage"] = {
                itemName = "bandage",
                itemImage = "bandage.png",
                itemPrice = 10,
                special = false
            },
            ["Lighter"] = {
                itemName = "lighter",
                itemImage = "lighter.png",
                itemPrice = 10,
                special = false
            },
            ["Snickers"] = {
                itemName = "snickers",
                itemImage = "snickers.png",
                itemPrice = 10,
                special = false
            },
            ["Twix"] = {
                itemName = "twix",
                itemImage = "twix.png",
                itemPrice = 10,
                special = false
            },
            ["Vodka"] = {
                itemName = "vodka",
                itemImage = "vodka.png",
                itemPrice = 10,
                special = false
            },
            ["Whiskey"] = {
                itemName = "whiskey",
                itemImage = "whiskey.png",
                itemPrice = 10,
                special = false
            },
            ["Sandwich"] = {
                itemName = "sandwich",
                itemImage = "sandwich.png",
                itemPrice = 10,
                special = false
            },
            -- specials
            ["Advanced Kit"] = {
                itemName = "advancedkit",
                itemImage = "advancedkit.png",
                itemPrice = 10,
                special = true
            },
            ["Binoculars"] = {
                itemName = "binoculars",
                itemImage = "binoculars.png",
                itemPrice = 10,
                special = true
            },
            ["Cleaning Kit"] = {
                itemName = "cleaningkit",
                itemImage = "cleaningkit.png",
                itemPrice = 10,
                special = true
            },
            ["Diving Gear"] = {
                itemName = "divinggear",
                itemImage = "divinggear.png",
                itemPrice = 10,
                special = true
            },
            ["First Aid"] = {
                itemName = "firstaid",
                itemImage = "firstaid.png",
                itemPrice = 10,
                special = true
            },
        }
    },
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
```

{% endcode %}
{% endtab %}

{% tab title="OPEN\_FUNCTIONS" %}
Here is everything related to open functions, such as target functions, in case you use a target script other than OX or QB.

{% code lineNumbers="true" %}

```lua
Functions = Functions or {}

if Config.Framework == "esx" then
    if Config.ESXExport ~= "" then
        ESX = exports[Config.ESXExport]:getSharedObject()
    else
        Citizen.CreateThread(function()
            while ESX == nil do
                TriggerEvent(Config.Core, function(obj) ESX = obj end)
                Citizen.Wait(0)
            end
        end)
    end
elseif Config.Framework == "qb" then
    QBCore = exports[Config.Core]:GetCoreObject()
else
    print("Unrecognized framework")
end

Functions.CreateTarget = function()
    if Config.UseTarget[1] then
        for shopName, shop in pairs(Shops) do
            local _options = {
                {
                    name = 'shop' .. shopName,
                    event = 'forge:1',
                    icon = 'fa-solid fa-shop',
                    distance = 2.5,
                    label = Config.UseTarget[3],
                    shopName = shopName 
                }
            }
            if Config.UseTarget[2] == "qb-target" then
                _options = {
                    {
                        name = 'shop' .. shopName,
                        event = 'forge:1',
                        icon = 'fa-solid fa-shop',
                        label = Config.UseTarget[3],
                        shopName = shopName 
                    }
                }
            end
            local data = {
                targetType = 'AddBoxZone',
                identifier = 'shop' .. shopName,
                coords = vec3(shop.coord.x, shop.coord.y, shop.coord.z),
                heading = 10.0,
                width = 2.0,
                length = 1.0,
                minZ = shop.coord.z - 0.9,
                maxZ = shop.coord.z + 0.9,
                options = _options
            }  
            CreateTarget(data)
        end

        AddEventHandler('forge:1', function(data)
            local shopName = data.shopName
            local playerJob = GetPlayerJob()
            local allowed = true
            if Shops[shopName].allowedJobs and #Shops[shopName].allowedJobs > 0 then
                allowed = false
                for _, job in ipairs(Shops[shopName].allowedJobs) do
                    if job == playerJob then
                        allowed = true
                        break
                    end
                end
            end
            if allowed then
                actualShop = shopName
                visibleAlert = false
                items = ""
                specialitems = ""
                cartitems = ""
                totalamount = 0
                shopcartlist = {}
                for name, itemdata in pairs(Shops[shopName].items) do
                    if itemdata.special then
                        specialitems = specialitems..'<div class="forge-shops-specialitem" data-value="'..name..'"><div class="forge-shops-box-background1"></div><div class="forge-shops-text-background"></div><span class="forge-shops-text16"><span>'..name..'</span></span><img src="public/items/'..itemdata.itemImage..'" alt="IMGSPECIALITEM1634" class="forge-shops-i-m-g-s-p-e-c-i-a-l-i-t-e-m"/></div>'
                    else
                        items = items..'<div class="forge-shops-item" data-value="'..name..'"><div class="forge-shops-box-background2"></div><div class="forge-shops-itemname"><div class="forge-shops-text-background1"></div><span class="forge-shops-text18"><span>'..name..'</span></span></div><img src="public/items/'..itemdata.itemImage..'" alt="IMGITEM325" class="forge-shops-i-m-g-i-t-e-m1"/><img src="public/playground_assets/addbackground1746-zw1-200h.png" alt="AddBackground1746" class="forge-shops-add-background"/><img src="public/playground_assets/addvector1119-bsxo.svg" alt="AddVector1119" class="forge-shops-add-vector"/></div>'
                    end
                end
                StartScreenEffect(blur, 1, true)
                SetDisplay(not display, items, specialitems, Shops[shopName].label ~= nil and Shops[shopName].label or shopName)
            else
                TriggerEvent("forge-shops:notifications", Noti.error, Lang.noJobAccess)
            end
        end)

    else
        local shopsRendered = {}
        while true do
            local playerCoords = GetEntityCoords(PlayerPedId())
            for shopName, shop in pairs(Shops) do
                if #(playerCoords - shop.coord) < 15 then
                    if not shopsRendered[shopName] then
                        shopsRendered[shopName] = true
                        Citizen.CreateThread(function()
                            while true do
                                local pedcoords = GetEntityCoords(PlayerPedId())
                                local distance = #(pedcoords - shop.coord)
                                if distance > 15 then
                                    shopsRendered[shopName] = false
                                    break
                                end

                                if shop.marker.use then
                                    DrawMarker(shop.marker.style, shop.coord.x, shop.coord.y, shop.coord.z, 0.0, 0.0, 0.0, 0.0, 180.0, 0.0, 2.0, 2.0, 2.0, shop.marker.r, shop.marker.g, shop.marker.b, 50, false, true, 2, nil, nil, false)
                                end

                                if distance < 2 then
                                    if visibleAlert then
                                        drawTxt(Lang.pressE, Config.alertTextFont, 1, 0.5, 0.8, 0.6, 255, 255, 255, 255)
                                    end
                                    if IsControlJustPressed(0, 38) then
                                        local playerJob = GetPlayerJob()
                                        local allowed = true
                                        if shop.allowedJobs and #shop.allowedJobs > 0 then
                                            allowed = false
                                            for _, job in ipairs(shop.allowedJobs) do
                                                if job == playerJob then
                                                    allowed = true
                                                    break
                                                end
                                            end
                                        end
                                        if allowed then
                                            actualShop = shopName
                                            visibleAlert = false
                                            items = ""
                                            specialitems = ""
                                            cartitems = ""
                                            totalamount = 0
                                            shopcartlist = {}
                                            for name, itemdata in pairs(shop.items) do
                                                if itemdata.special then
                                                    specialitems = specialitems..'<div class="forge-shops-specialitem" data-value="'..name..'"><div class="forge-shops-box-background1"></div><div class="forge-shops-text-background"></div><span class="forge-shops-text16"><span>'..name..'</span></span><img src="public/items/'..itemdata.itemImage..'" alt="IMGSPECIALITEM1634" class="forge-shops-i-m-g-s-p-e-c-i-a-l-i-t-e-m"/></div>'
                                                else
                                                    items = items..'<div class="forge-shops-item" data-value="'..name..'"><div class="forge-shops-box-background2"></div><div class="forge-shops-itemname"><div class="forge-shops-text-background1"></div><span class="forge-shops-text18"><span>'..name..'</span></span></div><img src="public/items/'..itemdata.itemImage..'" alt="IMGITEM325" class="forge-shops-i-m-g-i-t-e-m1"/><img src="public/playground_assets/addbackground1746-zw1-200h.png" alt="AddBackground1746" class="forge-shops-add-background"/><img src="public/playground_assets/addvector1119-bsxo.svg" alt="AddVector1119" class="forge-shops-add-vector"/></div>'
                                                end
                                            end
                                            StartScreenEffect(blur, 1, true)
                                            SetDisplay(not display, items, specialitems, shop.label ~= nil and shop.label or shopName)
                                            shopsRendered[shopName] = false
                                            break
                                        else
                                            notifications(Noti.error, Lang.noJobAccess, Noti.time)
                                        end
                                    end
                                    Citizen.Wait(0) -- comprobación rápida cuando está muy cerca
                                else
                                    Citizen.Wait(500) -- reduce consumo si no está tan cerca
                                end
                            end
                        end)
                    end
                end
            end
            Citizen.Wait(1000)
        end
    end
end

function CreateTarget(data)
    if Config.UseTarget[2] == "qb-target" then
        if data.targetType == 'AddBoxZone' then
            exports.qtarget:AddBoxZone(data.identifier, data.coords, data.length, data.width, {
                name = data.identifier,
                heading = data.heading,
                debugPoly = false,
                minZ = data.coords.z - 0.7,
                maxZ = data.coords.z + 1.5,
            }, {
                options = data.options,
                distance = 3.0,
            })
        elseif data.targetType == 'entity' then
            exports.qtarget:AddTargetEntity(data.entitys, {
                options = data.options,
                label = data.identifier,
                distance = 2.5
            })
        end
    elseif Config.UseTarget[2] == "ox-target" then
        if data.targetType == 'AddBoxZone' then
            exports.ox_target:addBoxZone({ coords = data.coords, options = data.options })
        elseif data.targetType == 'entity' then
            exports.ox_target:addLocalEntity(data.entitys, data.options)
        end
    end
end

function GetPlayerJob()
    if Config.Framework == "esx" then
        local playerData = ESX.GetPlayerData()
        return playerData.job.name
    elseif Config.Framework == "qb" then
        local playerData = QBCore.Functions.GetPlayerData()
        return playerData.job.name
    end
    return nil
end
```

{% endcode %}
{% endtab %}

{% tab title="SERVER\_OPEN" %}

```lua
function CanCarryItem(source, item, amount)

    -- ESX

    -- local esxPlayer = ESX.GetPlayerFromId(source)

    -- return esxPlayer.canCarryItem(item, amount)

    -- QBCore

    -- local canAdd, reason = exports['qb-inventory']:CanAddItem(source, item, amount)

    -- return canAdd

    -- Ox

    return exports['ox_inventory']:CanCarryItem(source, item, amount)

    -- Quasar

    -- return exports['qs-inventory']:CanCarryItem(source, item, amount)

end
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
**If you want to edit the aesthetics or design. You have the HTML open so you can modify the style and everything as you want.**

The script is **RESPONSIVE** for all resolutions as well.
{% endhint %}
