Skip to main content
return {
    ----------------------------------------------
    --        🛠️ Setup the basics below
    ----------------------------------------------

    setup = {
        -- Use only if needed, directed by support or know what you're doing
        -- Notice: enabling debug features will significantly increase resmon
        -- And should always be disabled in production
        debug = false,
        -- Set your interaction system below
        -- Available options are: 'ox_target', 'qb-target', 'interact' & 'custom'
        -- 'custom' needs to be added to client/functions.lua
        interact = 'ox_target',
        -- Set your notification system below
        -- Available options are: 'lation_ui', 'ox_lib', 'esx', 'qb', 'okok', 'sd-notify', 'wasabi_notify' & 'custom'
        -- 'custom' needs to be added to client/functions.lua
        notify = 'ox_lib',
        -- Set your progress bar system below
        -- Available options are: 'lation_ui', 'ox_lib', 'qbcore' & 'custom'
        -- 'custom' needs to be added to client/functions.lua
        -- Any custom progress bar must also support animations
        progress = 'ox_lib',
        -- Set your minigame (skillcheck) system below
        -- Available options are: 'lation_ui', 'ox_lib' & 'custom'
        minigame = 'ox_lib',
        -- Set your context menu system below
        -- Available options are: 'lation_ui', 'ox_lib' & 'custom'
        menu = 'ox_lib',
        -- Set your alert & input dialog system below
        -- Available options are: 'lation_ui', 'ox_lib' & 'custom'
        dialogs = 'ox_lib',
        -- Do you want to be notified via server console if an update is available?
        -- True if yes, false if no
        version = true,
        -- 'request' option below is how long the client should wait for a model/anim to load
        -- Do not edit unless you know what you are doing or directed via support member
        request = 10000,
        -- Render is the number of units (distance) from the closest object a player
        -- Must be within in order for the prop to spawn and be visible
        -- (outside this number/distance the props are deleted until needed again)
        render = 100,
        -- Input all your police jobs below for things like dispatch notifications & more
        police = { 'police', 'sheriff' }
    },

    ----------------------------------------------
    --    🌱 Create, edit & manage coca farms
    ----------------------------------------------

    -- How long (in seconds) cooldown lasts on each plant after searching
    -- After the cooldown has expired, the plant can be searched again
    cooldown = 180,
    -- Limit the maximum amount of coca that can be harvested
    -- This is per player, per server session - which means once a player
    -- Has reached this limit, they cannot search for more until the server restarts
    -- Don't want a limit? Set a super high number!
    limit = 100,
    -- Manage your coca farms below
    farms = {
        [1] = {
            -- Do you need the script to spawn the plant props here?
            spawn = true,
            -- If spawn = true, what prop do you want to spawn?
            model = 'coca_growth_3_wild',
            -- The center-most coords of the zone here
            center = vec3(2139.1252, 5169.0010, 54.1451),
            -- The size (radius) of the zone (enable debug to see zone)
            size = 150,
            -- What level is required to search plants here?
            level = 1,
            -- The item(s) required & optionally removed when planting
            required = {
                { item = 'ls_shears', quantity = 1, remove = false },
                -- Add your own additional items here if desired
            },
            -- What item and quantity to reward player upon searching?
            reward = { item = 'ls_coca_leaf', quantity = { min = 1, max = 1 } },
            -- A second item to reward, based on a percentage chance
            -- These are the seeds required to grow & harvest personal coca plants
            seed = { item = 'ls_coca_seed', quantity = { min = 1, max = 1 }, chance = 5 },
            -- How much XP to reward upon a search? This is per leaf.
            -- So if a player finds x3 leaves they'll get 3xp
            xp = { min = 1, max = 1 },
            -- Optional blip settings for this farm
            blip = { enable = true, sprite = 89, color = 1, scale = 0.7, label = 'Coca Field' },
            -- The coords of each plant to apply an interaction point
            -- Or, if spawn = true - the coords to spawn each plant
            coca = {
                -- If you need help grabbing the exact coords of existing plants
                -- We recommend using dolu_tool: https://github.com/dolutattoo/dolu_tool/releases/latest
                [1] = vec3(2179.3621, 5155.1704, 53.9840),
                [2] = vec3(2172.1982, 5152.0825, 53.1408),
                [3] = vec3(2177.6487, 5162.5894, 55.0238),
                [4] = vec3(2172.4924, 5167.3901, 55.5249),
                [5] = vec3(2158.9534, 5170.7520, 55.4918),
                [6] = vec3(2153.8794, 5178.9380, 56.4784),
                [7] = vec3(2145.5286, 5184.4600, 56.7473),
                [8] = vec3(2147.3643, 5196.0039, 57.9572),
                [9] = vec3(2138.5225, 5197.4136, 57.4630),
                [10] = vec3(2134.8069, 5202.9419, 57.6294),
                [11] = vec3(2130.9080, 5207.7373, 57.7305),
                [12] = vec3(2170.6714, 5145.4829, 51.9476),
                [13] = vec3(2164.1550, 5154.3433, 53.0478),
                [14] = vec3(2154.0593, 5159.4741, 53.3424),
                [15] = vec3(2146.5908, 5168.2437, 54.4723),
                [16] = vec3(2139.6296, 5177.9883, 55.5299),
                [17] = vec3(2135.0847, 5186.2339, 56.3193),
                [18] = vec3(2129.0337, 5191.6782, 56.6006),
                [19] = vec3(2119.1389, 5195.7695, 56.5050),
                [20] = vec3(2116.1658, 5203.2280, 57.0254),
                [21] = vec3(2159.5923, 5138.2407, 49.8532),
                [22] = vec3(2154.9294, 5145.2588, 50.8309),
                [23] = vec3(2146.0554, 5151.8408, 51.5500),
                [24] = vec3(2140.2964, 5158.5825, 52.4179),
                [25] = vec3(2136.4829, 5166.3301, 53.4914),
                [26] = vec3(2133.7690, 5173.1504, 54.3835),
                [27] = vec3(2126.7668, 5180.8262, 55.0367),
                [28] = vec3(2120.1672, 5185.2935, 55.2166),
                [29] = vec3(2114.5471, 5193.8320, 56.1267),
                [30] = vec3(2102.3345, 5198.7134, 55.7128),
                [31] = vec3(2099.0806, 5191.6616, 55.1150),
                [32] = vec3(2108.1299, 5185.8960, 54.8048),
                [33] = vec3(2117.7590, 5176.4795, 53.9329),
                [34] = vec3(2124.3381, 5168.0464, 53.0896),
                [35] = vec3(2130.1248, 5158.8242, 52.1414),
                [36] = vec3(2135.4602, 5149.5005, 50.8720),
                [37] = vec3(2147.3152, 5144.6392, 50.3661),
                [38] = vec3(2152.6050, 5138.3320, 49.5138),
                [39] = vec3(2159.1841, 5130.0737, 48.8332),
                [40] = vec3(2148.3936, 5128.9341, 48.0955),
                [41] = vec3(2136.5923, 5140.8809, 49.5312),
                [42] = vec3(2125.2170, 5153.5503, 51.2402),
                [43] = vec3(2113.2061, 5164.1587, 52.3151),
                [44] = vec3(2104.4207, 5176.2031, 53.6669),
                [45] = vec3(2091.6428, 5186.8242, 54.4904),
                [46] = vec3(2133.9062, 5131.5181, 48.1057),
                [47] = vec3(2113.7305, 5151.1050, 50.3221),
                [48] = vec3(2097.6240, 5169.5703, 52.1555),
                [49] = vec3(2084.0840, 5183.9663, 53.8722)
            }
        },

        -- You can create as many or as few farms as you wish!
        -- Add more farms here if desired following the same format

        -- [2] = {
        --     spawn = false,
        --     model = 'h4_prop_bush_cocaplant_01',
        --     center = vec3(0.0, 0.0, 0.0),
        --     size = 100,
        --     level = 1,
        --     required = {
        --         { item = 'ls_shears', quantity = 1, remove = false },
        --     },
        --     reward = { item = 'ls_coca_leaf', quantity = { min = 1, max = 3 } },
        --     seed = { item = 'ls_coca_seed', quantity = { min = 1, max = 1 }, chance = 5 },
        --     xp = { min = 1, max = 1 },
        --     blip = { enable = true, sprite = 89, color = 1, scale = 0.7, label = 'Coca Field' },
        --     coca = {
        --         [1] = vec3(0.0, 0.0, 0.0),
        --         [2] = vec3(0.0, 0.0, 0.0),
        --         [3] = vec3(0.0, 0.0, 0.0)
        --     }
        -- }

    },

    ----------------------------------------------
    --     🌿 Customize your plant options
    ----------------------------------------------

    planting = {
        -- The seed item used to plant a coca plant
        seed = 'ls_coca_seed',
        -- The maximum number of plants a player can have at once
        max = 5,
        -- Do you want to check for collisions during the placement process?
        collision = true,
        -- The item(s) required & optionally removed when planting
        required = {
            { item = 'ls_plant_pot', quantity = 1, remove = true },
            -- Add your own additional items here if desired
        },
        -- How many police to require be online to plant?
        police = 0,
        -- Customize the soil checking feature
        soil = {
            -- Do you want to enable the soil checking feature?
            -- This will only allow the player to place plants
            -- On soil types matching the soil hashes below
            enable = true,
            -- If enable = true, these are the allowed soil types
            -- You can get a soil type by setting debug to true above
            -- And try placing a plant - it will print the soil hash in F8!
            types = {
                [2409420175] = true,
                [3008270349] = true,
                [3833216577] = true,
                [223086562] = true,
                [1333033863] = true,
                [4170197704] = true,
                [3594309083] = true,
                [2461440131] = true,
                [1109728704] = true,
                [2352068586] = true,
                [1144315879] = true,
                [581794674] = true,
                [2128369009] = true,
                [-461750719] = true,
                [-1286696947] = true,
                [-1885547121] = true,
                [-1907520769] = true
            },
            -- If you wish to keep the soil check feature active but want to allow planting
            -- In certain locations (such as warehouses, etc) you can put the location & the
            -- General size (radius) of the area here and the soil checking will be bypassed
            ignore = {
                [1] = { coords = vec3(0, 0, 0), radius = 25 },
                -- [2] = { coords = vec3(0, 0, 0), radius = 25 },
                -- Add more ignored locations here if desired
            }
        },
        -- If you want to disable planting in specific locations you can do that here
        blacklist = {
            [1] = { coords = vec3(0, 0, 0), radius = 25 },
            -- [2] = { coords = vec3(0, 0, 0), radius = 25 },
            -- Add more blacklisted locations here if desired
        },
        -- If you do not want to allow players to plant freely around the map
        -- You can restrict planting to whitelisted locations only and customize the
        -- Size (radius) of each location planting will be enabled at below
        restrict = {
            -- Do you want to enable the restricted option?
            enable = false,
            -- If enable = true, what locations are allowed to plant in?
            whitelist = {
                [1] = { coords = vec3(0, 0, 0), radius = 25 },
                -- [2] = { coords = vec3(0, 0, 0), radius = 25 },
                -- Add more whitelisted locations here if desired
            }
        },
        -- The props assigned to each stage
        stages = {
            [1] = 'coca_growth_1',
            [2] = 'coca_growth_2',
            [3] = 'coca_growth_3'
        }
    },

    growth = {
        -- 'update_interval' is roughly how often in milliseconds each plant is updated
        update_interval = 15000,
        -- 'starting' is the starting values (percentage) of each
        starting = { thirst = 85, hunger = 85, yield = 100, growth = 0 },
        -- 'growth_increase' is the percentage growth increases each update_interval
        -- By default, growth will increase between 0.1% & 0.3% roughly every 15 seconds
        growth_increase = { min = 0.15, max = 0.35 },
        -- 'stages' is the growth percentage threshold for advancing to that stage
        -- [2] = 40 means plants will change to stage 2 at 40% growth, etc
        stages = { [2] = 40, [3] = 90 },
        -- 'watering' is how much the plants thirst level will increase per watering action
        -- The 'item' section is the watering item spawn name
        -- By default, the plants thirst level will increase 8-13% per watering
        watering = { item = 'ls_watering_can', min = 8.0, max = 13.0 },
        -- 'fertilizing' is how much the plants hunger level will increase per fertilizing action
        -- The 'item' section is the fertilizer item spawn name
        -- By default, the plants hunger level will increase 10-15% per fertilizing
        fertilizing = { item = 'ls_fertilizer', min = 10.0, max = 15.0 },
        -- 'thirst' is how much the thirst will decrease per update_interval
        -- By default, thirst will decrease between 0.1% and 0.4% roughly every 15 seconds
        thirst = { min = 0.12, max = 0.3 },
        -- 'hunger' is how much the hunger will decrease per update_interval
        -- By default, hunger will decrease between 0.1% and 0.4% roughly every 15 seconds
        hunger = { min = 0.11, max = 0.3 },
        -- 'death' is when the thirst or hunger reaches this amount, the plant will die
        -- By default, a plant will die anytime thirst or hunger reaches 20% or lower
        -- When plants die, they are completely deleted & removed from the map
        death = 20,
        -- 'yield_threshold' is when a plants thirst or hunger reaches this number, the yield
        -- Of the plant starts to decrease. So if a plant has 100% hunger but 69% thirst, the yield
        -- Level will start going down every update_interval until the plants thirst & hunger are back
        -- Above this amount
        yield_threshold = 70,
        -- 'yield_decrease' is how much the yield percentage will decrease every update_interval
        -- When any plants thirst or hunger level is below the yield_threshold amount (70% by default)
        yield_decrease = { min = 0.4, max = 0.9 },
    },

    harvest = {
        -- The item(s) required & optionally removed when harvesting
        required = {
            { item = 'ls_shears', quantity = 1, remove = false },
            -- Add your own additional items here if desired
        },
        -- The items & quantity given to player upon harvest
        add = {
            -- The coca leaf item name
            leaf = 'ls_coca_leaf',
            -- The quantity of coca leaves given to player on harvest
            -- The number of leaves given to the player is based on the plants yield percentage
            -- By default, a player will get 3 leaves for every 10 percentage points
            -- If a plant is harvested at 100% yield, this will give the player 30 leaves
            -- If a plant is harvested at 50% yield, the player will get 15 leaves
            -- The yield percentage changes based on how well the plant was taken care of
            yield_per_10_percent = 3,
            -- When a coca plant is harvested, you get leaves and seed(s)
            -- However, the "required_yield" option allows you to only reward seeds
            -- If the plant is being harvested at a specific yield percentage or higher
            -- This way if the plant was poorly managed and has a yield below required_yield
            -- Then the player will not get any seeds back
            seed = { item = 'ls_coca_seed', quantity = { min = 1, max = 1 }, required_yield = 95, },
            -- How much XP to reward upon harvesting? This is per leaf
            -- So if a player harvests x15 leaves they'll get 15xp
            xp = { min = 1, max = 1 }
        }
    },

    ----------------------------------------------
    --      ⚙️ Setup your table options
    ----------------------------------------------

    table = {
        -- The item spawn name of the meth table
        item = 'ls_coke_table',
        -- The model/object used as the table prop
        -- More models: https://forge.plebmasters.de/objects
        model = 'bkr_prop_coke_table01a',
        -- Do you want to lock tables to be accessed by their owners only?
        -- If true, only the player who placed the table can interact with it
        -- If false, anyone can interact & pick up / take the table themselves
        locked = false,
        -- How many tables do you want to allow one player to be able to place?
        -- If you have locked = false, they can still use someone elses table
        -- Even if the limit to place their own is 1
        limit = 1,
        -- If desired, you can disable the collision checking feature when placing
        -- A table. It is recommended to keep true, but it can be disabled if needed
        collisions = true,
        -- How many police to require be online to place & use table?
        police = { place = 0, use = 0 },
        -- Restrict placing tables in these areas
        restricted = {
            [1] = { coords = vec3(0, 0, 0), radius = 25 },
            -- Add more here if needed, be sure to increment
            -- The numbers accordingly, [2], [3], etc
        },
    },

    ----------------------------------------------
    --       📈 Customize the XP system
    ----------------------------------------------

    experience = {
        -- The number in these [brackets] are the level
        -- The number after = is the exp required to reach that level
        -- Be sure levels *always* start at level 1 with 0 exp
        [1] = 0,
        [2] = 2500,
        [3] = 10000,
        [4] = 20000,
        [5] = 50000,
        [6] = 100000,
        -- You can add or remove levels as you wish
        -- Be sure to increment numbers correctly
    },

    ----------------------------------------------
    --      ⚒️ Customize cement gathering
    ----------------------------------------------

    cement = {
        -- Disable or enable cement gathering
        enable = true,
        -- Set a maximum number of cement that can be collected per player
        -- This is per server session, so this does not reset until the server restarts
        -- Don't want a limit? Set an unrealistically high number like 5000!
        limit = 25,
        -- How long (in seconds) cooldown lasts on each cement pallet after taking 1
        -- After the cooldown has expired, the cement pallet can be searched again
        cooldown = 180,
        -- Do you want police to be notified when cement is taken?
        -- If yes, chance is the percentage chance of a notification being sent
        -- If no, set chance to 0. Sprite, color & scale are dispatch blip settings
        dispatch = { chance = 5, sprite = 133, color = 1, scale = 1.0 },
        -- Create, edit or manage collecting zones
        zones = {
            [1] = {
                -- These zones are built with poly zones, input all the points below
                -- Need help building a poly zone? Check out https://skyrossm.github.io/PolyZoneCreator/
                -- Or you can use ox_libs /zone command to create a poly zone
                points = {
                    vec3(-436.36, -863.64, 38.0),
                    vec3(-434.85, -965.15, 38.0),
                    vec3(-431.06, -1001.52, 38.0),
                    vec3(-440.91, -1098.48, 38.0),
                    vec3(-521.97, -1065.91, 38.0),
                    vec3(-525.00, -1030.30, 38.0),
                    vec3(-529.55, -993.18, 38.0),
                    vec3(-526.52, -959.09, 38.0),
                    vec3(-489.39, -882.58, 38.0),
                    vec3(-485.61, -862.88, 38.0)
                },
                -- The height of the polygon
                thickness = 35,
                -- The item and quantity given when collecting
                reward = { item = 'ls_cement', quantity = { min = 1, max = 1 } },
                -- How much XP to reward upon collecting? This is per x1 cement item
                xp = { min = 0, max = 0 },
                -- The coordinates of each cement object you want to allow collection at
                coords = {
                    [1] = vec3(-486.745, -1051.958, 28.911),
                    [2] = vec3(-490.756, -1049.139, 28.911),
                    [3] = vec3(-492.023, -1048.076, 28.911),
                    [4] = vec3(-478.925, -1050.830, 28.912),
                    [5] = vec3(-478.925, -1049.176, 28.912),
                    [6] = vec3(-465.013, -1045.091, 28.911),
                    [7] = vec3(-464.057, -1046.464, 28.911),
                    [8] = vec3(-469.612, -1051.953, 28.911),
                    [9] = vec3(-468.091, -1052.831, 28.911),
                    [10] = vec3(-455.397, -1052.479, 28.919),
                    [11] = vec3(-454.051, -1053.609, 28.919),
                    [12] = vec3(-497.059, -1010.229, 28.918),
                    [13] = vec3(-497.059, -1008.576, 28.917),
                    [14] = vec3(-493.132, -996.690, 28.911),
                    [15] = vec3(-493.132, -994.933, 28.911),
                    [16] = vec3(-495.574, -992.676, 28.911),
                    [17] = vec3(-495.574, -994.433, 28.911),
                    [18] = vec3(-440.356, -1022.513, 25.784),
                    [19] = vec3(-440.380, -1021.142, 25.784),
                    [20] = vec3(-440.394, -1019.159, 25.784),
                    [21] = vec3(-440.041, -1017.833, 25.784),
                    [22] = vec3(-440.805, -967.684, 25.784),
                    [23] = vec3(-440.603, -966.006, 25.784),
                    [24] = vec3(-448.492, -947.273, 28.973),
                    [25] = vec3(-447.125, -947.166, 28.973),
                    [26] = vec3(-466.271, -899.449, 28.997),
                    [27] = vec3(-464.786, -899.297, 28.996),
                    [28] = vec3(-462.224, -895.241, 28.995),
                    [29] = vec3(-457.348, -881.329, 28.973),
                    [30] = vec3(-457.362, -879.957, 28.973),
                    [31] = vec3(-471.268, -925.761, 28.973),
                    [32] = vec3(-471.492, -927.114, 28.973),
                    [33] = vec3(-470.563, -956.971, 28.973),
                    [34] = vec3(-470.552, -955.599, 28.973),
                    [35] = vec3(-454.731, -959.882, 28.973),
                    [36] = vec3(-456.096, -959.752, 28.973),
                    -- Add or remove as desired
                }
            },

            -- You can create as many or as few zones as you wish for cement!
            -- Add more cement zones here if desired following the same format
            -- 🗒️ Note: it will NOT spawn cement, only apply targets to the set coords

            -- [2] = {
            --     points = {
            --         vec3(0.0, 0.0, 0.0),
            --         vec3(0.0, 0.0, 0.0),
            --         vec3(0.0, 0.0, 0.0),
            --         vec3(0.0, 0.0, 0.0),
            --     },
            --     thickness = 35,
            --     reward = { item = 'ls_cement', quantity = { min = 1, max = 1 } },
            --     xp = { min = 0, max = 0 },
            --     coords = {
            --         [1] = vec3(0.0, 0.0, 0.0),
            --         [2] = vec3(0.0, 0.0, 0.0),
            --         [3] = vec3(0.0, 0.0, 0.0),
            --         [4] = vec3(0.0, 0.0, 0.0),
            --         [5] = vec3(0.0, 0.0, 0.0),
            --     }
            -- },

        }
    },

    ----------------------------------------------
    --       🛒 Customize supply shop
    ----------------------------------------------

    shop = {
        -- Optionally disable this shop if you wish to grant access to
        -- Coke supplies via another method
        enable = true,
        -- If enabled = true, where is this shop located?
        location = vec4(1901.1145, 4924.4526, 48.8241, 207.5563),
        -- The ped model used
        -- More models: https://docs.fivem.net/docs/game-references/ped-models/
        model = 'a_m_m_farmer_01',
        -- The scenario assigned to the ped (or nil/false for no scenario)
        -- More scenarios: https://github.com/DioneB/gtav-scenarios
        scenario = 'WORLD_HUMAN_DRINKING',
        -- You can limit the hours at which the ped is available here
        -- By default, this ped is available 24/7
        -- Min is the earliest the shop is available (in 24hr format)
        -- Max is the latest the shop is available (in 24hr format)
        -- For example, if you want the ped only available during night set min to 21 and max to 5
        hours = { min = 0, max = 24 },
        -- Use cash or bank when purchasing here?
        account = 'cash',
        -- Items available for sale in this shop
        items = {
            -- item: item spawn name
            -- price: price of item
            -- icon: icon for item
            -- metadata: optional metadata for item
            [1] = { item = 'ls_watering_can', price = 15, icon = 'droplet', metadata = 100 },
            [2] = { item = 'ls_fertilizer', price = 20, icon = 'burger', metadata = 100 },
            [3] = { item = 'ls_plant_pot', price = 10, icon = 'plant-wilt' },
            [4] = { item = 'ls_shears', price = 150, icon = 'scissors' },
            [5] = { item = 'ls_baking_soda', price = 15, icon = 'box-archive' },
            [6] = { item = 'ls_empty_baggy', price = 5, icon = 'bag-shopping' },
            [7] = { item = 'ls_gasoline', price = 50, icon = 'gas-pump', metadata = 100 },
            [8] = { item = 'ls_coke_table', price = 5000, icon = 'snowflake' },
            -- Add or remove items as you wish
        },
        -- Manage blip settings if desired
        blip = {
            enable = true, -- Enable or disable the blip for this shop
            sprite = 280, -- Sprite ID (https://docs.fivem.net/docs/game-references/blips/)
            color = 0, -- Color (https://docs.fivem.net/docs/game-references/blips/#blip-colors)
            scale = 0.9, -- Size/scale
            label = 'Farmer' -- Label
        }
    },

    ----------------------------------------------
    --    ❄️ Build & customize your coke labs
    ----------------------------------------------

    -- Create as many or as few Coke Lab options you wish below
    -- Be sure to increment the numbers accordingly, [2], [3], etc
    labs = {
        [1] = {
            -- The entrance coords to this lab
            enter = vec4(499.6197, -651.9501, 24.9085, 262.6456),
            -- A brief description used in the menu
            description = 'A modest lab located in Textile City on Little Bighorn',
            -- The path to the image used to preview lab
            -- To disable lab preview images, just set image = nil,
            image = 'nui://lation_coke/labs/lab1.jpg',
            -- The icon used in the menu
            icon = 'fas fa-warehouse',
            -- The price to purchase this lab
            price = 100000,
            -- The level required to purchase this lab
            level = 1,
            -- "camrot" below is for camera rotation. It it used to assign a default
            -- Rotation value for a good default angle. To get the camrot for a warehouse
            -- Simply stand in front of the warehouse entrance, facing away from the door,
            -- Enable the debug option above and use the command "getrot" ingame
            camrot = vec3(-0.000001, -0.000000, -97.354431),
            -- The stashes available in this lab
            storage = {
                [1] = {
                    -- The ID of the stash, this must be unique for each and every stash
                    -- Recommended naming scheme is: lab#_stash#
                    identifier = 'lab1_stash1',
                    -- The label of the stash
                    label = 'Storage',
                    -- The coords of the stash
                    coords = vec3(1096.9021, -3192.4685, -38.8690),
                    -- The max size of the stash
                    slots = 50,
                    -- The max weight of the stash
                    weight = 50000
                },
                -- Add or remove more stashes as desired
            }
        },
        [2] = {
            enter = vec4(945.9252, -1138.3689, 26.5010, 0.7115),
            description = 'A conveniently located lab in Murrieta Heights',
            image = 'nui://lation_coke/labs/lab2.jpg',
            icon = 'fas fa-warehouse',
            price = 125000,
            level = 1,
            camrot = vec3(0.000000, 0.000000, 0.711507),
            storage = {
                [1] = {
                    identifier = 'lab2_stash1',
                    label = 'Storage',
                    coords = vec3(1096.9021, -3192.4685, -38.8690),
                    slots = 75,
                    weight = 75000
                },
            }
        },
        [3] = {
            enter = vec4(930.9764, -1963.8546, 30.4092, 271.7736),
            description = 'A lab located in Cypress Flats with extra storage',
            image = 'nui://lation_coke/labs/lab3.jpg',
            icon = 'fas fa-warehouse',
            price = 150000,
            level = 1,
            camrot = vec3(-0.019753, -0.034459, -88.226372),
            storage = {
                [1] = {
                    identifier = 'lab3_stash1',
                    label = 'Storage',
                    coords = vec3(1096.9021, -3192.4685, -38.8690),
                    slots = 75,
                    weight = 75000
                },
                [2] = {
                    identifier = 'lab3_stash2',
                    label = 'Storage',
                    coords = vec3(1093.5725, -3199.8215, -38.9873),
                    slots = 25,
                    weight = 25000
                },
            }
        },
        [4] = {
            enter = vec4(-1267.7716, -811.9169, 17.1077, 127.9849),
            description = 'A large lab located in the heart of Del Perro',
            image = 'nui://lation_coke/labs/lab4.jpg',
            icon = 'fas fa-warehouse',
            price = 175000,
            level = 1,
            camrot = vec3(-0.280802, 0.012579, 127.985153),
            storage = {
                [1] = {
                    identifier = 'lab4_stash1',
                    label = 'Storage',
                    coords = vec3(1096.9021, -3192.4685, -38.8690),
                    slots = 100,
                    weight = 100000
                },
            }
        },
        [5] = {
            enter = vec4(-1684.7803, -291.3889, 51.8901, 145.2175),
            description = 'A re-purposed church with lots of storage',
            image = 'nui://lation_coke/labs/lab5.jpg',
            icon = 'fas fa-warehouse',
            price = 250000,
            level = 1,
            camrot = vec3(0.025158, -0.001362, 145.217484),
            storage = {
                [1] = {
                    identifier = 'lab5_stash1',
                    label = 'Storage',
                    coords = vec3(1096.9021, -3192.4685, -38.8690),
                    slots = 75,
                    weight = 75000
                },
                [2] = {
                    identifier = 'lab5_stash2',
                    label = 'Storage',
                    coords = vec3(1093.5725, -3199.8215, -38.9873),
                    slots = 50,
                    weight = 50000
                },
                [3] = {
                    identifier = 'lab5_stash3',
                    label = 'Storage',
                    coords = vec3(1090.0062, -3199.6501, -38.8936),
                    slots = 50,
                    weight = 50000
                },
            }
        },
    },

    -- Various coke lab settings that apply to all labs
    settings = {
        -- The player account used for specific lab transactions
        -- Available options: 'cash' or 'bank'
        accounts = { purchase = 'cash', upgrade = 'cash', sell = 'cash' },
        -- When a player enters a lab, this is where they are teleported to
        enter = vec4(1088.7609, -3187.5291, -38.9934, 181.0739),
        -- When a player is inside the lab, this is where they exit at
        exit = vec4(1088.7609, -3187.5291, -38.9934, 181.0739),
        -- Where do you want the manage lab menu to be located?
        manage = vec3(1086.5208, -3194.2824, -39.1940),
        -- Where are the cooking stations location at?
        -- 'basic' is where the non-upgraded labs stations are located
        -- 'upgrade' is where the upgraded labs stations are located
        -- By default, an upgraded lab comes with more stations
        stations = {
            ['basic'] = {
                [1] = vec3(1090.3503, -3195.7060, -39.1919),
                [2] = vec3(1093.1036, -3195.7351, -39.1924),
                [3] = vec3(1095.3885, -3195.7077, -39.1919)
            },
            ['upgrade'] = {
                [1] = vec3(1090.3503, -3195.7060, -39.1919),
                [2] = vec3(1093.1036, -3195.7351, -39.1924),
                [3] = vec3(1095.3885, -3195.7077, -39.1919),
                [4] = vec3(1098.6831, -3194.3437, -39.1923),
                [5] = vec3(1101.8637, -3192.8369, -39.1920)
            }
        },
        -- What is the maximum amount of labs a single player can own?
        limit = 1,
        -- When a player purchases a lab, these are the default values assigned
        -- style options: 'basic', 'upgrade'
        -- security options: 'basic', 'upgrade'
        defaults = { style = 'basic', security = 'basic' },
        -- When a player adds an authorized user to their lab, these are the default
        -- Permissions applied to the newly added user
        permissions = {
            -- manageDoor is the ability to lock/unlock the lab door
            manageDoor = false,
            -- viewCamera is the ability to view the lab security camera
            viewCamera = false,
            -- manageUsers is the ability to add/remove authorized users
            manageUsers = false
        },
        -- Upgrades available for each lab (style & security only available if default = 'basic')
        -- Price is how much it costs | duration is how long (in minutes) it takes
        upgrades = {
            style = { price = 15000, duration = 60 },
            security = { price = 10000, duration = 30 }
        },
        -- Lab selling related options
        selling = {
            -- When a user sells their lab, they must type this to continue
            confirm = 'confirm',
            -- When selling a lab, this is the percentage of the purchase price refunded
            refund = 50,
        },
        -- If a player owns a warehouse this is the blip settings for it
        blip = {
            name = 'Coke Lab',
            -- Sprite ID: https://docs.fivem.net/docs/game-references/blips/
            sprite = 473,
            -- Color: https://docs.fivem.net/docs/game-references/blips/#blip-colors
            color = 0,
            -- Size of the blip on the map
            scale = 0.8
        }
    },

    -- Various coke lab raiding settings
    raids = {
        -- Do you want to allow police be able to raid coke labs?
        police = true,
        -- Do you want to allow players be able to raid coke labs?
        players = true,
        -- If raids are enabled, below is the skillcheck difficulties and inputs
        -- Learn more here: https://overextended.dev/ox_lib/Modules/Interface/Client/skillcheck
        skillcheck = {
            -- The skillcheck settings for entering a locked lab
            entry = {
                difficulty = {'easy', 'easy', 'easy', 'medium', 'medium', 'medium', 'hard'},
                inputs = {'W', 'A', 'S', 'D'}
            },
            -- The skillcheck settings for opening a locked stash
            stash = {
                difficulty = {'easy', 'easy', 'easy', 'medium', 'medium', 'medium', 'hard'},
                inputs = {'W', 'A', 'S', 'D'}
            }
        }
    },

    -- The ped that sells coke labs
    dealer = {
        -- Optionally disable this shop if you wish to grant access to the lab
        -- Shop via another method through the use of the available export (see docs)
        enable = true,
        -- If enabled = true, where is this shop located?
        location = vec4(-7.2472, 409.2823, 120.1271, 76.4763),
        -- If enabled = true, the ped model used
        -- More models: https://docs.fivem.net/docs/game-references/ped-models/
        model = 'a_m_m_eastsa_02',
        -- If enabled = true, the scenario assigned to the ped (or nil/false for no scenario)
        -- More scenarios: https://github.com/DioneB/gtav-scenarios
        scenario = 'WORLD_HUMAN_SMOKING',
        -- If enabled = true, what level does a player have to be to access the shop?
        level = 1,
        -- You can limit the hours at which the ped is available here
        -- By default, this ped is available 24/7
        -- Min is the earliest the shop is available (in 24hr format)
        -- Max is the latest the shop is available (in 24hr format)
        -- For example, if you want the ped only available during night set min to 21 and max to 5
        hours = { min = 0, max = 24 },
        -- Manage blip settings if desired
        blip = {
             -- Enable or disable the blip for this shop
            enable = true,
            -- Sprite ID (https://docs.fivem.net/docs/game-references/blips/)
            sprite = 280,
            -- Color (https://docs.fivem.net/docs/game-references/blips/#blip-colors)
            color = 0,
            -- Size of the blip on the map
            scale = 0.8,
            -- Label
            label = 'Lab Shop'
        }
    },

    ----------------------------------------------
    --      👩‍🍳 Customize cooking process
    ----------------------------------------------

    cooking = {

        -- Do you want each players cooking process to be unique?
        -- If true every player will have a different mixture of
        -- Gas & cement required to reach 100% purity coke/crack
        -- If false, all players will have the same mixture and you
        -- Can assign a server-wide range in the cooking steps below
        unique = true,

        -- Do you want police to be notified when a player is cooking?
        -- If yes, chance is the percentage chance of a notification being sent
        -- If no, set chance to 0. Sprite, color, scale & radius are dispatch blip settings
        dispatch = {
            -- Portable table cooking dispatch settings
            table = { chance = 5, sprite = 133, color = 1, scale = 1.0, radius = 50 },
            -- Lab cooking dispatch settings
            lab = { chance = 5, sprite = 133, color = 1, scale = 1.0, radius = 50 }
        },

        -- Create, edit & manage the cooking steps for tables
        table = {
            [1] = {
                -- Title and description of this step (edit in locales file)
                title = locale('table-menu.process-leaves-title'),
                description = locale('table-menu.process-leaves-desc'),
                -- Icon settings
                icon = 'fas fa-mortar-pestle',
                iconColor = '',
                -- Required items for this step
                required = {
                    { item = 'ls_coca_leaf', quantity = 20, remove = true },
                    -- Add your own additional items here if desired
                    -- Don't want to require anything? Leave this table empty
                },
                -- Items added when this step is completed
                add = {
                    { item = 'ls_coca_ground', quantity = 1 },
                    -- Add your own additional items here if desired
                },
                -- How much XP is given for this step?
                -- If you do not want to award XP you can remove this section
                -- Or assign the min/max to 0/0
                xp = { min = 1, max = 3 },
                -- If needing to trigger an event on the server-side
                -- Add server = true after event name
                event = { name = 'lation_coke:table:processLeaves' }
            },
            [2] = {
                title = locale('table-menu.add-gas-title'),
                description = locale('table-menu.add-gas-desc'),
                icon = 'fas fa-gas-pump',
                iconColor = '',
                -- The gasoline item that is used to add to the ground leaves
                -- This is where the base purity of the coke is first determined
                gasoline = 'ls_gasoline',
                -- ⚠️ If unique = true, you can ignore the range settings here
                -- ⚠️ If unique = false, you can set a range for all players here
                -- The ideal percentage/amount of gasoline to add
                -- Any amount outside these values will impact purity negatively
                range = { min = 5, max = 9 },
                -- The maximum penalty to purity if outside the range above
                -- For example if someone puts 100% gas, purity will come back 75% (25% max penalty)
                penalty = 25,
                required = {
                    { item = 'ls_coca_ground', quantity = 5, remove = true },
                },
                add = {
                    { item = 'ls_coca_base_unf', quantity = 1, metatype = 'purity' },
                },
                xp = { min = 1, max = 3 },
                event = { name = 'lation_coke:table:addGasoline' }
            },
            [3] = {
                title = locale('table-menu.add-cement-title'),
                description = locale('table-menu.add-cement-desc'),
                icon = 'fas fa-trowel-bricks',
                iconColor = '',
                -- The cement item that is used to add to the coca base
                -- The purity level of the coke can be further impacted here
                cement = 'ls_cement',
                -- ⚠️ If unique = true, you can ignore the range settings here
                -- ⚠️ If unique = false, you can set a range for all players here
                -- The ideal percentage/amount of cement to add
                -- Any amount outside these values will impact purity negatively
                range = { min = 15, max = 19 },
                -- The maximum penalty to purity if outside the range above
                penalty = 25,
                required = {
                    { item = 'ls_coca_base_unf', quantity = 1, remove = true, metatype = 'purity' },
                },
                add = {
                    { item = 'ls_coca_base', quantity = 1, metatype = 'purity' },
                },
                xp = { min = 1, max = 3 },
                event = { name = 'lation_coke:table:addCement' }
            },
            [4] = {
                title = locale('table-menu.start-heat-title'),
                description = locale('table-menu.start-heat-desc'),
                icon = 'fas fa-fire',
                iconColor = '',
                -- How long does this step take to complete?
                -- This duration is in minutes
                duration = 60,
                required = {
                    { item = 'ls_coca_base', quantity = 1, remove = true, metatype = 'purity' }
                },
                add = {
                    { item = 'ls_cocaine_brick', quantity = 1, metatype = 'purity' }
                },
                xp = { min = 10, max = 30 },
                event = { name = 'lation_coke:table:heatAndDry' }
            },
            [5] = {
                title = locale('table-menu.package-title'),
                description = locale('table-menu.package-desc'),
                icon = 'fas fa-hammer',
                iconColor = '',
                -- The empty baggy item required to package a brick into bags
                empty_baggy = 'ls_empty_baggy',
                required = {
                    { item = 'ls_cocaine_brick', quantity = 1, remove = true, metatype = 'purity' },
                    -- { item = 'ls_crack_brick', quantity = 1, remove = true, metatype = 'purity'}
                },
                add = {
                    { item = 'ls_cocaine_bag', min = 10, max = 20, metatype = 'purity' },
                    -- { item = 'ls_crack_bag', min = 10, max = 20, metatype = 'purity' }
                },
                xp = { min = 2, max = 5 },
                event = { name = 'lation_coke:table:packageBrick' }
            },
            -- -- OPTIONAL: Uncomment below to enable crack production on tables
            -- [6] = {
            --     title = locale('table-menu.cook-coke-title'),
            --     description = locale('table-menu.cook-coke-desc'),
            --     icon = 'fas fa-flask-vial',
            --     iconColor = '',
            --     -- How long does this step take to complete?
            --     -- This duration is in minutes
            --     duration = 60,
            --     required = {
            --         { item = 'ls_cocaine_brick', quantity = 1, remove = true, metatype = 'purity' },
            --         { item = 'ls_baking_soda', quantity = 1, remove = true }
            --     },
            --     add = {
            --         { item = 'ls_crack_brick', quantity = 1, metatype = 'purity' }
            --     },
            --     xp = { min = 5, max = 15 },
            --     event = { name = 'lation_coke:table:cookCoke' }
            -- },
            -- -- OPTIONAL: Uncomment below to enable brick cutting on tables
            -- [7] = {
            --     title = locale('table-menu.cut-brick-title'),
            --     description = locale('table-menu.cut-brick-desc'),
            --     icon = 'fas fa-scissors',
            --     iconColor = '',
            --     -- The cutting agent used to double the batch size
            --     cutting_agent = 'ls_baking_soda',
            --     -- The percentage purity is impacted when cutting
            --     -- e.g. If you cut x1 100% purity brick, it'll now be x2 65% bricks
            --     penalty = 50,
            --     required = {
            --         { item = 'ls_cocaine_brick', quantity = 1, remove = true, metatype = 'purity' },
            --         { item = 'ls_crack_brick', quantity = 1, remove = true, metatype = 'purity' }
            --     },
            --     add = {
            --         { item = 'ls_cocaine_brick', quantity = 2, metatype = 'purity' },
            --         { item = 'ls_crack_brick', quantity = 2, metatype = 'purity' }
            --     },
            --     xp = { min = 1, max = 3 },
            --     event = { name = 'lation_coke:table:cutBrick' }
            -- },
        },

        -- Create, edit & manage the cooking steps for lab stations
        lab = {
            [1] = {
                title = locale('table-menu.process-leaves-title'),
                description = locale('table-menu.process-leaves-desc'),
                icon = 'fas fa-mortar-pestle',
                iconColor = '',
                required = {
                    { item = 'ls_coca_leaf', quantity = 20, remove = true },
                },
                add = {
                    { item = 'ls_coca_ground', quantity = 1 },
                },
                xp = { min = 2, max = 4 },
                event = { name = 'lation_coke:lab:processLeaves' }
            },
            [2] = {
                title = locale('table-menu.add-gas-title'),
                description = locale('table-menu.add-gas-desc'),
                icon = 'fas fa-gas-pump',
                iconColor = '',
                gasoline = 'ls_gasoline',
                -- ⚠️ If unique = true, you can ignore the range settings here
                -- ⚠️ If unique = false, you can set a range for all players here
                -- The ideal percentage/amount of gasoline to add
                -- Any amount outside these values will impact purity negatively
                range = { min = 5, max = 9 },
                -- The maximum penalty to purity if outside the range above
                -- For example if someone puts 100% gas, purity will come back 75% (25% max penalty)
                penalty = 20,
                required = {
                    { item = 'ls_coca_ground', quantity = 5, remove = true },
                },
                add = {
                    { item = 'ls_coca_base_unf', quantity = 1, metatype = 'purity' },
                },
                xp = { min = 2, max = 4 },
                event = { name = 'lation_coke:lab:addGasoline' }
            },
            [3] = {
                title = locale('table-menu.add-cement-title'),
                description = locale('table-menu.add-cement-desc'),
                icon = 'fas fa-trowel-bricks',
                iconColor = '',
                cement = 'ls_cement',
                -- ⚠️ If unique = true, you can ignore the range settings here
                -- ⚠️ If unique = false, you can set a range for all players here
                -- The ideal percentage/amount of cement to add
                -- Any amount outside these values will impact purity negatively
                range = { min = 15, max = 19 },
                -- The maximum penalty to purity if outside the range above
                penalty = 20,
                required = {
                    { item = 'ls_coca_base_unf', quantity = 1, remove = true, metatype = 'purity' },
                },
                add = {
                    { item = 'ls_coca_base', quantity = 1, metatype = 'purity' },
                },
                xp = { min = 2, max = 4 },
                event = { name = 'lation_coke:lab:addCement' }
            },
            [4] = {
                title = locale('table-menu.start-heat-title'),
                description = locale('table-menu.start-heat-desc'),
                icon = 'fas fa-fire',
                iconColor = '',
                -- How long does this step take to complete?
                -- This duration is in minutes
                duration = 45,
                required = {
                    { item = 'ls_coca_base', quantity = 1, remove = true, metatype = 'purity' }
                },
                add = {
                    { item = 'ls_cocaine_brick', quantity = 1, metatype = 'purity' }
                },
                xp = { min = 25, max = 50 },
                event = { name = 'lation_coke:lab:heatAndDry' }
            },
            [5] = {
                title = locale('table-menu.cook-coke-title'),
                description = locale('table-menu.cook-coke-desc'),
                icon = 'fas fa-flask-vial',
                iconColor = '',
                -- How long does this step take to complete?
                -- This duration is in minutes
                duration = 30,
                required = {
                    { item = 'ls_cocaine_brick', quantity = 1, remove = true, metatype = 'purity' },
                    { item = 'ls_baking_soda', quantity = 1, remove = true }
                },
                add = {
                    { item = 'ls_crack_brick', quantity = 1, metatype = 'purity' }
                },
                xp = { min = 5, max = 15 },
                event = { name = 'lation_coke:lab:cookCoke' }
            },
            [6] = {
                title = locale('table-menu.cut-brick-title'),
                description = locale('table-menu.cut-brick-desc'),
                icon = 'fas fa-scissors',
                iconColor = '',
                -- The cutting agent used to double the batch size
                cutting_agent = 'ls_baking_soda',
                -- The percentage purity is impacted when cutting
                -- e.g. If you cut x1 100% purity brick, it'll now be x2 65% bricks
                penalty = 35,
                required = {
                    { item = 'ls_cocaine_brick', quantity = 1, remove = true, metatype = 'purity' },
                    { item = 'ls_crack_brick', quantity = 1, remove = true, metatype = 'purity' }
                },
                add = {
                    { item = 'ls_cocaine_brick', quantity = 2, metatype = 'purity' },
                    { item = 'ls_crack_brick', quantity = 2, metatype = 'purity' }
                },
                xp = { min = 1, max = 3 },
                event = { name = 'lation_coke:lab:cutBrick' }
            },
            [7] = {
                title = locale('table-menu.package-title'),
                description = locale('table-menu.package-desc'),
                icon = 'fas fa-hammer',
                iconColor = '',
                -- The empty baggy item required to package a brick into bags
                empty_baggy = 'ls_empty_baggy',
                -- A menu pops up to let you select which brick to package, it does not require both items
                -- Be sure if you add additional brick types, the bag is in the same order
                -- e.g. If cocaine brick is first, then cocaine bags should be first in the "add" section
                required = {
                    { item = 'ls_cocaine_brick', quantity = 1, remove = true, metatype = 'purity' },
                    { item = 'ls_crack_brick', quantity = 1, remove = true, metatype = 'purity' }
                },
                add = {
                    { item = 'ls_cocaine_bag', min = 10, max = 20, metatype = 'purity' },
                    { item = 'ls_crack_bag', min = 10, max = 20, metatype = 'purity' }
                },
                xp = { min = 3, max = 6 },
                event = { name = 'lation_coke:lab:packageBrick' }
            },
        }
    },

    ----------------------------------------------
    --        ❄️ Customize consumables
    ----------------------------------------------

    consumables = {
        -- ['item_spawn_name'] Consumable item name
        ['ls_cocaine_bag'] = {
            -- Do you want to make this item consumable?
            usable = true,
            -- What level must the player be to consume this?
            level = 1,
            -- Manage the effects this item has on the player
            effects = {
                -- enable: do you want to enable this effect?
                -- amount: if enabled, how much health to apply?
                health = { enable = false, amount = 50 },
                -- enable: do you want to enable this effect?
                -- amount: if enabled, how much armor to apply?
                -- max: if consuming multiple, what is the max armor a player can have?
                armor = { enable = true, amount = 100, max = 100 },
                -- enable: do you want to enable this effect?
                -- multiplier: if enabled, how much to increase player speed? (maximum is 1.49)
                -- duration: if enabled, how long (in milliseconds) to activate the effect
                speed = { enable = true, multiplier = 1.49, duration = 30000 },
                -- enable: do you want to enable this effect?
                -- effect: the timecycle modifier (more can be found at: https://forge.plebmasters.de/timecyclemods)
                -- duration: if enabled, how long (in milliseconds) to activate the effect
                screen = { enable = true, effect = 'stoned_monkeys', duration = 30000 },
                -- enable: do you want to enable this effect?
                -- clipset: the movement clipset to apply (more can be found at: https://github.com/DurtyFree/gta-v-data-dumps/blob/master/movementClipsetsCompact.json)
                -- duration: if enabled, how long (in milliseconds) to activate the effect
                walk = { enable = true, clipset = 'move_m@drunk@a', duration = 30000 },
                -- enable: do you want to enable this effect?
                -- name: the camera shake name (more can be found at: https://docs.fivem.net/natives/?_0xFD55E49555E017CF)
                -- intensity: the intensity of the camera shake (lower is less, higher is more)
                -- duration: if enabled, how long (in milliseconds) to activate the effect
                shake = { enable = true, name = 'DRUNK_SHAKE', intensity = 2.0, duration = 30000 }
            }
        },
        ['ls_crack_bag'] = {
            usable = true,
            level = 1,
            effects = {
                health = { enable = false, amount = 50 },
                armor = { enable = true, amount = 100, max = 100 },
                speed = { enable = true, multiplier = 1.49, duration = 30000 },
                screen = { enable = true, effect = 'stoned_monkeys', duration = 30000 },
                walk = { enable = true, clipset = 'move_m@drunk@a', duration = 30000 },
                shake = { enable = true, name = 'DRUNK_SHAKE', intensity = 2.0, duration = 30000 }
            }
        },
    }

}
-- Initialize global variables to store framework & inventory
Framework, Inventory = nil, nil

-- Initialize global player variables
PlayerLoaded, PlayerData = nil, {}

-- Get framework
local function InitializeFramework()
    if GetResourceState('es_extended') == 'started' then
        ESX = exports['es_extended']:getSharedObject()
        Framework = 'esx'

        RegisterNetEvent('esx:playerLoaded', function(xPlayer)
            PlayerData = xPlayer
            PlayerLoaded = true
            TriggerEvent('lation_coke:onPlayerLoaded')
        end)

        RegisterNetEvent('esx:onPlayerLogout', function()
            table.wipe(PlayerData)
            PlayerLoaded = false
        end)

        AddEventHandler('onResourceStart', function(resourceName)
            if GetCurrentResourceName() ~= resourceName then return end
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_coke:onPlayerLoaded')
        end)

    elseif GetResourceState('qbx_core') == 'started' then
        Framework = 'qbx'

        AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_coke:onPlayerLoaded')
        end)

        RegisterNetEvent('qbx_core:client:playerLoggedOut', function()
            table.wipe(PlayerData)
            PlayerLoaded = false
        end)

        AddEventHandler('onResourceStart', function(resourceName)
            if GetCurrentResourceName() ~= resourceName then return end
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_coke:onPlayerLoaded')
        end)
    elseif GetResourceState('qb-core') == 'started' then
        QBCore = exports['qb-core']:GetCoreObject()
        Framework = 'qb'

        AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_coke:onPlayerLoaded')
        end)

        RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
            table.wipe(PlayerData)
            PlayerLoaded = false
        end)

        AddEventHandler('onResourceStart', function(resourceName)
            if GetCurrentResourceName() ~= resourceName then return end
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_coke:onPlayerLoaded')
        end)
    elseif GetResourceState('ox_core') == 'started' then
        Ox = require '@ox_core.lib.init'
        Framework = 'ox'

        AddEventHandler('ox:playerLoaded', function()
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_coke:onPlayerLoaded')
        end)

        AddEventHandler('ox:playerLogout', function()
            table.wipe(PlayerData)
            PlayerLoaded = false
        end)

        AddEventHandler('onResourceStart', function(resourceName)
            if GetCurrentResourceName() ~= resourceName then return end
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_coke:onPlayerLoaded')
        end)
    else
        -- Add custom framework here
    end
end

-- Get inventory
local function InitializeInventory()
    if GetResourceState('ox_inventory') == 'started' then
        Inventory = 'ox_inventory'
    elseif GetResourceState('qb-inventory') == 'started' then
        Inventory = 'qb-inventory'
    elseif GetResourceState('qs-inventory') == 'started' then
        Inventory = 'qs-inventory'
    elseif GetResourceState('ps-inventory') == 'started' then
        Inventory = 'ps-inventory'
    elseif GetResourceState('origen_inventory') == 'started' then
        Inventory = 'origen_inventory'
    elseif GetResourceState('codem-inventory') == 'started' then
        Inventory = 'codem-inventory'
    elseif GetResourceState('core_inventory') == 'started' then
        Inventory = 'core_inventory'
    else
        -- Add custom inventory here
    end
end

-- Returns player data
function GetPlayerData()
    if Framework == 'esx' then
        return ESX.GetPlayerData()
    elseif Framework == 'qb' then
        return QBCore.Functions.GetPlayerData()
    elseif Framework == 'qbx' then
        return exports.qbx_core:GetPlayerData()
    elseif Framework == 'ox' then
        return Ox.GetPlayer()
    else
        -- Add custom framework here
    end
end

-- Returns players current inventory
--- @return any
function GetInventory()
    if Inventory then
        if Inventory == 'ox_inventory' then
            return exports[Inventory]:GetPlayerItems()
        elseif Inventory == 'qb-inventory' then
            return GetPlayerData().items
        elseif Inventory == 'qs-inventory' then
            return exports[Inventory]:getUserInventory()
        elseif Inventory == 'ps-inventory' then
            return GetPlayerData().items
        elseif Inventory == 'origen_inventory' then
            return exports[Inventory]:GetInventory()
        elseif Inventory == 'codem-inventory' then
            return exports[Inventory]:GetClientPlayerInventory()
        end
    else
        if Framework == 'esx' then
            return GetPlayerData().inventory
        elseif Framework == 'qb' then
            return GetPlayerData().items
        elseif Framework == 'qbx' then
            return print('Are you really not using ox_inventory? Contact support and say: "I\'m special"')
        elseif Framework == 'ox' then
            return print('It is confirmed your insane. Please contact support for mental health evaluation.')
        end
    end
end

-- Returns player job
function GetJob()
    local player = GetPlayerData()
    if not player then return end
    if Framework == 'esx' then
        return player.job.name
    elseif Framework == 'qb' then
        return player.job.name
    elseif Framework == 'qbx' then
        return player.job.name
    elseif Framework == 'ox' then
        return player.getGroupByType('job')
    else
        -- Add custom framework here
    end
end

-- Return data for item
--- @param item string
function GetItemData(item)
    if not item then return end
    if Inventory then
        if Inventory == 'ox_inventory' then
            return exports[Inventory]:Items(item)
        elseif Inventory == 'qb-inventory' or Inventory == 'ps-inventory' then
            return QBCore.Shared.Items[item]
        elseif Inventory == 'qs-inventory' then
            local items = exports[Inventory]:GetItemList()
            if not items then return end
            return items[item]
        elseif Inventory == 'origen_inventory' then
            local items = exports[Inventory]:GetItems()
            if not items then return end
            return items[item]
        elseif Inventory == 'codem-inventory' then
            local items = exports[Inventory]:GetItemList()
            if not items then return end
            return items[item]
        elseif Inventory == 'core_inventory' then
            -- No client-side export available to get item list
            if Framework == 'qb' then
                return QBCore.Shared.Items[item]
            end
        else
            -- Add custom inventory here
        end
    else
        if Framework == 'esx' then
            -- Unlikely to need anything here but.. just in case..
            print('An error has occured with lation_meth - please contact support')
        elseif Framework == 'qb' then
            return QBCore.Shared.Items[item]
        elseif Framework == 'qbx' then
            -- Unlikely to need anything here but.. just in case..
            print('Are you really not using ox_inventory? Contact support please lul.')
        end
    end
end

-- Returns boolean if player has specified amount of item
--- @param item string
--- @param amount number
--- @return boolean
function HasItem(item, amount)
    if not item or not amount then return false end
    if Inventory then
        if Inventory == 'ox_inventory' then
            return exports[Inventory]:Search('count', item) >= amount
        elseif Inventory == 'core_inventory' then
            return exports[Inventory]:hasItem(item, amount)
        else
            return exports[Inventory]:HasItem(item, amount)
        end
    else
        local playerData = GetPlayerData()
        if not playerData then return false end
        local inventory = Framework == 'esx' and playerData.inventory or playerData.items
        if not inventory then return false end
        for _, itemData in pairs(inventory) do
            if itemData and itemData.name == item then
                local count = itemData.amount or itemData.count or 0
                if count >= amount then
                    return true
                end
            end
        end
        return false
    end
end

-- Disables access to open/view inventory
function DisableInventory()
    if Inventory == 'ox_inventory' then
        LocalPlayer.state.invBusy = true
    elseif Inventory == 'qb-inventory' then
        LocalPlayer.state.inv_busy = true
    elseif Inventory == 'qs-inventory' then
        exports[Inventory]:setInventoryDisabled(true)
    elseif Inventory == 'core_inventory' then
        exports[Inventory]:lockInventory()
    else
        -- Add custom inventory here
    end
end

-- Enables access to open/view inventory
function EnableInventory()
    if Inventory == 'ox_inventory' then
        LocalPlayer.state.invBusy = false
    elseif Inventory == 'qb-inventory' then
        LocalPlayer.state.inv_busy = false
    elseif Inventory == 'qs-inventory' then
        exports[Inventory]:setInventoryDisabled(false)
    elseif Inventory == 'core_inventory' then
        exports[Inventory]:unlockInventory()
    else
        -- Add custom inventory here
    end
end

-- Initialize defaults
InitializeFramework()
InitializeInventory()
-- Initialize global variable to store dispatch
Dispatch = nil

-- Initialize config(s)
local shared = require 'config.shared'
local client = require 'config.client'

-- Localize export
local coke = exports.lation_coke

-- You can change the textUI script here
-- Options: 'lation_ui', 'ox_lib', 'jg-textui', 'okokTextUI', 'qbcore' & 'custom'
local textui = 'ox_lib'

-- Get dispatch
local function InitializeDispatch()
    if GetResourceState('ps-dispatch') == 'started' then
        Dispatch = 'ps-dispatch'
    elseif GetResourceState('cd_dispatch') == 'started' then
        Dispatch = 'cd_dispatch'
    elseif GetResourceState('qs-dispatch') == 'started' then
        Dispatch = 'qs-dispatch'
    elseif GetResourceState('redutzu-mdt') == 'started' then
        Dispatch = 'redutzu-mdt'
    elseif GetResourceState('emergencydispatch') == 'started' then
        Dispatch = 'emergencydispatch'
    elseif GetResourceState('rcore_dispatch') == 'started' then
        Dispatch = 'rcore_dispatch'
    elseif GetResourceState('codem-dispatch') == 'started' then
        Dispatch = 'codem-dispatch'
    elseif GetResourceState('tk_dispatch') == 'started' then
        Dispatch = 'tk_dispatch'
    elseif GetResourceState('core_dispatch') == 'started' then
        Dispatch = 'core_dispatch'
    elseif GetResourceState('aty_dispatch') == 'started' then
        Dispatch = 'aty_dispatch'
    elseif GetResourceState('op-dispatch') == 'started' then
        Dispatch = 'op-dispatch'
    else
        -- Add custom dispatch here
    end
end

-- Used to determine if a player can (true) or cannot (false) search a plant
--- @param farmId number local farm = shared.farms[farmId]
function CanSearchCoca(farmId)
    for _, req in pairs(shared.farms[farmId].required) do
        if not HasItem(req.item, req.quantity) then
            ShowNotification(locale('notify.no-shears'), 'error')
            return false
        end
    end
    local level = coke:getPlayerData('level')
    if not level or level < shared.farms[farmId].level then
        ShowNotification(locale('notify.not-experienced'), 'error')
        return false
    end
    return true
end

-- Used to determine if a player can (true) or cannot (false) use a coke table
--- @param tableId number local table = Tables[tableId]
function CanUseCokeTable(tableId)
    local police = shared.table.police.use > 0 and lib.callback.await('lation_coke:getpolicecount', false) or 0
    if police < shared.table.police.use then
        ShowNotification(locale('notify.no-police'), 'error')
        return false
    end
    return true
end

-- Used to determine if a player can (true) or cannot (false) take cement
--- @param zoneId number local zone = shared.cement.zones[zoneId]
function CanTakeCement(zoneId)
    return true
end

-- Used to verify all conditions are met before allowing entry to lab
---@param labId number Lab ID
---@return boolean
function CanEnterLab(labId)
    return true
end

-- Used to determine if a player can (true) or cannot (false) open supply shop
---@return boolean
function CanOpenSupplyShop()
    return true
end

-- Display a notification
--- @param message string
--- @param type string
function ShowNotification(message, type)
    if shared.setup.notify == 'lation_ui' then
        exports.lation_ui:notify({ message = message, type = type, icon = 'fas fa-leaf' })
    elseif shared.setup.notify == 'ox_lib' then
        lib.notify({ description = message, type = type, position = 'top', icon = 'fas fa-leaf' })
    elseif shared.setup.notify == 'esx' then
        ESX.ShowNotification(message)
    elseif shared.setup.notify == 'qb' then
        QBCore.Functions.Notify(message, type)
    elseif shared.setup.notify == 'okok' then
        exports['okokNotify']:Alert('Coke', message, 5000, type, false)
    elseif shared.setup.notify == 'sd-notify' then
        exports['sd-notify']:Notify('Coke', message, type)
    elseif shared.setup.notify == 'wasabi_notify' then
        exports.wasabi_notify:notify('Coke', message, 5000, type, false, 'fas fa-leaf')
    elseif shared.setup.notify == 'custom' then
        -- Add custom notification export/event here
    end
end

-- Display notifications from server
--- @param message string
--- @param type string
RegisterNetEvent('lation_coke:notify', function(message, type)
    ShowNotification(message, type)
end)

-- Triggered when object placement begins (tables, plants)
function StartedPlacement()
    DisableInventory()
end

-- Triggered when object placement ends (tables, plants)
function StoppedPlacement()
    EnableInventory()
end

-- Return an inventory's "durability/quality" types
function GetDurabilityType()
    if Inventory == 'ox_inventory' then
        return 'durability'
    else
        return 'quality'
    end
end

-- Display TextUI
--- @param text string 
--- @param icon string
function ShowTextUI(text, icon)
    if textui == 'lation_ui' then
        local isOpen, _ = exports.lation_ui:isOpen()
        if isOpen then return end
        exports.lation_ui:showText({
            description = text,
            icon = icon
        })
    elseif textui == 'ox_lib' then
        local isOpen, _ = lib.isTextUIOpen()
        if isOpen then return end
        lib.showTextUI(text, {
            position = 'left-center',
            icon = icon
        })
    elseif textui == 'jg-textui' then
        exports['jg-textui']:DrawText(text)
    elseif textui == 'okokTextUI' then
        exports['okokTextUI']:Open(text, 'lightblue ', 'left', false)
    elseif textui == 'qbcore' then
        exports['qb-core']:DrawText(text, 'left')
    else
        -- Add custom textUI here
    end
end

-- Hide TextUI
--- @param label string
function HideTextUI(label)
    if textui == 'lation_ui' then
        local isOpen, text = exports.lation_ui:isOpen()
        if isOpen and text == label then
            exports.lation_ui:hideText()
        end
    elseif textui == 'ox_lib' then
        local isOpen, text = lib.isTextUIOpen()
        if isOpen and text == label then
            lib.hideTextUI()
        end
    elseif textui == 'jg-textui' then
        exports['jg-textui']:HideText()
    elseif textui == 'okokTextUI' then
        exports['okokTextUI']:Close()
    elseif textui == 'qbcore' then
        exports['qb-core']:HideText()
    else
        -- Add custom textUI here
    end
end

-- Display a progress bar
--- @param data table
function ProgressBar(data)
    if shared.setup.progress == 'lation_ui' then
        if exports.lation_ui:progressBar({
            label = data.label,
            description = data.description or nil,
            icon = data.icon or nil,
            duration = data.duration,
            useWhileDead = data.useWhileDead,
            canCancel = data.canCancel,
            steps = data.steps or nil,
            disable = data.disable,
            anim = {
                dict = data.anim.dict or nil,
                clip = data.anim.clip or nil,
                flag = data.anim.flag or nil
            },
            prop = {
                model = data.prop.model or nil,
                bone = data.prop.bone or nil,
                pos = data.prop.pos or nil,
                rot = data.prop.rot or nil
            }
        }) then
            return true
        end
        return false
    elseif shared.setup.progress == 'ox_lib' then
        -- Want to use ox_lib's progress circle instead of bar?
        -- Change "progressBar" to "progressCircle" below & done!
        if lib.progressBar({
            label = data.label,
            duration = data.duration,
            position = data.position or 'bottom',
            useWhileDead = data.useWhileDead,
            canCancel = data.canCancel,
            disable = data.disable,
            anim = {
                dict = data.anim.dict or nil,
                clip = data.anim.clip or nil,
                flag = data.anim.flag or nil
            },
            prop = {
                model = data.prop.model or nil,
                bone = data.prop.bone or nil,
                pos = data.prop.pos or nil,
                rot = data.prop.rot or nil
            }
        }) then
            return true
        end
        return false
    elseif shared.setup.progress == 'qbcore' then
        local p = promise.new()
        QBCore.Functions.Progressbar(data.label, data.label, data.duration, data.useWhileDead, data.canCancel, {
            disableMovement = data.disable.move,
            disableCarMovement = data.disable.car,
            disableMouse = false,
            disableCombat = data.disable.combat
        }, {
            animDict = data.anim.dict or nil,
            anim = data.anim.clip or nil,
            flags = data.anim.flag or nil
        }, {
            model = data.prop.model or nil,
            bone = data.prop.bone or nil,
            coords = data.prop.pos or nil,
            rotation = data.prop.rot or nil
        }, {},
        function()
            ClearPedTasks(cache.ped)
            p:resolve(true)
        end,
        function()
            ClearPedTasks(cache.ped)
            p:resolve(false)
        end)
        return Citizen.Await(p)
    else
        -- Add 'custom' progress bar here
    end
end

-- Register menu
--- @param data table
function RegisterMenu(data)
    if shared.setup.menu == 'lation_ui' then
        exports.lation_ui:registerMenu(data)
    elseif shared.setup.menu == 'ox_lib' then
        lib.registerContext(data)
    elseif shared.setup.menu == 'custom' then
        -- Add 'custom' menu system here
    end
end

-- Show menu
--- @param menuId string
function ShowMenu(menuId)
    if shared.setup.menu == 'lation_ui' then
        exports.lation_ui:showMenu(menuId)
    elseif shared.setup.menu == 'ox_lib' then
        lib.showContext(menuId)
    elseif shared.setup.menu == 'custom' then
        -- Add 'custom' menu system here
    end
end

-- Display an alert dialog
--- @param data table
function ShowAlert(data)
    if shared.setup.dialogs == 'lation_ui' then
        return exports.lation_ui:alert(data)
    elseif shared.setup.dialogs == 'ox_lib' then
        return lib.alertDialog(data)
    elseif shared.setup.dialogs == 'custom' then
        -- Add your custom alert dialog here
    end
end

-- Display an input dialog
--- @param data table
function ShowInput(data)
    if shared.setup.dialogs == 'lation_ui' then
        return exports.lation_ui:input({ title = data.title, options = data.options })
    elseif shared.setup.dialogs == 'ox_lib' then
        return lib.inputDialog(data.title, data.options)
    elseif shared.setup.dialogss == 'custom' then
        -- Add your custom input dialog here
    end
end

-- Display a skillcheck
--- @param data table .difficulty, .inputs
function Skillcheck(data)
    if shared.setup.minigame == 'lation_ui' then
        if exports.lation_ui:skillCheck(nil, data.difficulty, data.inputs) then
            return true
        end
        return false
    elseif shared.setup.minigame == 'ox_lib' then
        if lib.skillCheck(data.difficulty, data.inputs) then
            return true
        end
        return false
    elseif shared.setup.minigame == 'custom' then
        -- Add your custom minigame here
    end
    return false
end

-- Open stash
--- @param labId number
--- @param stashId number
function OpenStash(labId, stashId)
    if not labId or not stashId then return end

    local lab = shared.labs[labId]
    if not lab then return end

    local stash = lab.storage[stashId]
    if not stash then return end

    local data = coke:getLabData(labId)
    if not data then return end

    if data.passcode and not canOpenStash(data) then return end

    if Inventory == 'ox_inventory' then
        exports[Inventory]:openInventory('stash', stash.identifier)
    elseif Inventory == 'qb-inventory' then
        TriggerServerEvent('lation_coke:openStash', labId, stashId)
    elseif Inventory == 'ps-inventory' then
        TriggerServerEvent('ps-inventory:server:OpenInventory', 'stash', stash.identifier, {
            label = stash.label,
            maxweight = stash.weight,
            slots = stash.slots
        })
        TriggerEvent('ps-inventory:client:SetCurrentStash', stash.identifier)
    else
        TriggerServerEvent('inventory:server:OpenInventory', 'stash', stash.identifier, {
            label = stash.label,
            maxweight = stash.weight,
            slots = stash.slots
        })
        TriggerEvent('inventory:client:SetCurrentStash', stash.identifier)
    end
end

-- Send a police dispatch
--- @param data table Dispatch data
-- data.title - Dispatch title
-- data.code - Dispatch code
-- data.message - Dispatch message
-- data.street - Dispatch street name
-- data.coords - Dispatch coordinates
-- data.blip - Blip data (data.blip.sprite, data.blip.scale, data.blip.colour, data.blip.radius)
function SendDispatch(data)
    if not Dispatch then print('^1[ERROR]: No police dispatch detected - unable to send alert^0') return end
    if not data or type(data) ~= 'table' then return end
    if Dispatch == 'ps-dispatch' then
        exports[Dispatch]:CustomAlert({
            coords = data.coords,
            message = data.message:format(data.street),
            dispatchCode = data.code,
            description = data.code .. ' | ' .. data.title,
            alert = { sprite = data.blip.sprite, color = data.blip.color, scale = data.blip.scale, length = 5, radius = data.blip.radius }
        })
    elseif Dispatch == 'cd_dispatch' then
        local cd_data = exports[Dispatch]:GetPlayerInfo()
        if not cd_data then return end
        TriggerServerEvent('cd_dispatch:AddNotification', {
            job_table = shared.setup.police,
            coords = data.coords,
            title = data.code .. ' | ' .. data.title,
            message = data.message:format(data.street),
            flash = 0,
            unique_id = cd_data.unique_id,
            sound = 1,
            blip = { sprite = data.blip.sprite, scale = data.blip.scale, colour = data.blip.color, text = data.code, time = 5, radius = data.blip.radius }
        })
    elseif Dispatch == 'qs-dispatch' then
        TriggerServerEvent('qs-dispatch:server:CreateDispatchCall', {
            job = shared.setup.police,
            callLocation = data.coords,
            callCode = { code = data.code, snippet = data.title },
            message = data.message:format(data.street),
            flashes = false,
            blip = { sprite = data.blip.sprite, scale = data.blip.scale, colour = data.blip.color, text = data.code, time = (5 * 60000), radius = data.blip.radius },
        })
    elseif Dispatch == 'redutzu-mdt' then
        TriggerServerEvent('redutzu-mdt:server:sendDispatchMessage', {
            code = data.code,
            coords = data.coords,
            street = data.street,
            gender = IsPedMale(cache.ped) and 'Male' or 'Female',
            duration = (5 * 60000)
        })
    elseif Dispatch == 'emergencydispatch' then
        TriggerServerEvent('emergencydispatch:emergencycall:new', 'police', data.code .. ' | ' .. data.title, data.coords, true)
    elseif Dispatch == 'rcore_dispatch' then
        TriggerServerEvent('rcore_dispatch:server:sendAlert', {
            code = data.code,
            default_priority = 'low',
            coords = data.coords,
            job = shared.setup.police,
            text = data.message:format(data.street),
            type = 'alerts',
            blip_time = 30,
            blip = { sprite = data.blip.sprite, scale = data.blip.scale, colour = data.blip.color, text = data.code, radius = data.blip.radius }
        })
    elseif Dispatch == 'codem-dispatch' then
        exports[Dispatch]:CustomDispatch({
            type = 'Robbery',
            header = data.title,
            text = data.message:format(data.street),
            code = data.code
        })
    elseif Dispatch == 'tk_dispatch' then
        exports[Dispatch]:addCall({
            title = data.title,
            code = data.code,
            priority = 'Priority 3',
            coords = data.coords,
            message = data.message:format(data.street),
            showGender = true,
            removeTime = (5 * 60000),
            showTime = (5 * 60000),
            blip = { sprite = data.blip.sprite, color = data.blip.color, scale = data.blip.scale, radius = data.blip.radius },
            jobs = shared.setup.police
        })
    elseif Dispatch == 'core_dispatch' then
        exports[Dispatch]:addCall(
            data.code,
            data.message:format(data.street),
            { data.coords.x, data.coords.y, data.coords.z },
            'police', (5* 60000), data.blip.sprite, data.blip.color, false
        )
    elseif Dispatch == 'aty_dispatch' then
        exports[Dispatch]:SendDispatch(data.title, data.code, data.blip.sprite, shared.setup.police)
    elseif Dispatch == 'op-dispatch' then
        TriggerServerEvent('Opto_dispatch:Server:SendAlert',
            'police',
            data.title,
            data.message:format(data.street),
            data.coords, false, cache.serverId
        )
    else
        -- Add custom dispatch here
    end
end

-- Play animation on consumables
lib.callback.register('lation_coke:consumeItem', function()
    if ProgressBar(client.animations.use_drug) then
        return true
    end
    return false
end)

-- Apply effects on consumables
--- @param item string
RegisterNetEvent('lation_coke:applyEffects', function(item)
    if not item then return end

    local valid = lib.callback.await('lation_coke:validateRequest', false)
    if not valid then return end

    local effects = shared.consumables[item].effects
    if not effects then return end

    if effects.health.enable then
        local health = GetEntityHealth(cache.ped)
        local add = math.min(health + effects.health.amount)
        if add > 200 then add = 200 end
        SetEntityHealth(cache.ped, add)
    end

    if effects.armor.enable then
        local armor = GetPedArmour(cache.ped)
        local add = math.min(armor + effects.armor.amount)
        if add > effects.armor.max then add = effects.armor.max end
        SetPedArmour(cache.ped, add)
    end

    if effects.speed.enable then
        local duration = effects.speed.duration
        -- Says SetRunSprintMultiplierForPlayer to be called "just one time" but
        -- Gets overridden by some scripts/frameworks - calling every second instead
        CreateThread(function()
            while duration > 0 do
                Wait(1000)
                duration = duration - 1000
                SetRunSprintMultiplierForPlayer(cache.playerId, effects.speed.multiplier)
            end
            SetRunSprintMultiplierForPlayer(cache.playerId, 1.0)
        end)
    end

    if effects.screen.enable then
        SetTimecycleModifier(effects.screen.effect)
        SetTimeout(effects.screen.duration, function()
            ClearTimecycleModifier()
        end)
    end

    if effects.walk.enable then
        lib.requestAnimSet(effects.walk.clipset, shared.setup.request)
        SetPedMovementClipset(cache.ped, effects.walk.clipset, 0)
        SetTimeout(effects.walk.duration, function()
            ResetPedMovementClipset(cache.ped, 0)
        end)
    end

    if effects.shake.enable then
        ShakeGameplayCam(effects.shake.name, effects.shake.intensity)
        SetTimeout(effects.shake.duration, function()
            StopGameplayCamShaking(true)
        end)
    end
end)

-- Add entity target
--- @param entity number Entity number
--- @param data table Options table
function AddTargetEntity(entity, data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:addLocalEntity(entity, data)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:AddTargetEntity(entity, {options = data, distance = 2})
    elseif shared.setup.interact == 'interact' then
        exports.interact:AddLocalEntityInteraction({
            entity = entity,
            interactDst = 2.0,
            offset = vec3(0.0, 0.0, 1.0),
            options = data
        })
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('No interaction system defined in the config file.')
    end
end

-- Add circle target zones
--- @param data table
function AddCircleZone(data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:addSphereZone(data)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:AddCircleZone(data.name, data.coords, data.radius, {
            name = data.name,
            debugPoly = shared.setup.debug}, {
            options = data.options,
            distance = 2,
        })
    elseif shared.setup.interact == 'interact' then
        exports.interact:AddInteraction({
            coords = data.coords,
            interactDst = 2.0,
            id = data.name,
            options = data.options
        })
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system defined in the config file^0')
    end
end

-- Add model target
--- @param model string
--- @param data table
function AddTargetModel(model, data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:addModel(model, data)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:AddTargetModel(model, {options = data, distance = 2})
    elseif shared.setup.interact == 'interact' then
        exports.interact:AddModelInteraction({
            model = model,
            offset = vec3(0.0, 0.0, 0.0),
            id = model,
            interactDst = 2.0,
            options = data
        })
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system defined in the config file^0')
    end
end

-- Function to remove circle zone
--- @param name string
function RemoveCircleZone(name)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:removeZone(name)
    elseif shared.setup.interact == 'qb-target' then
        exports['qb-target']:RemoveZone(name)
    elseif shared.setup.interact == 'interact' then
        exports.interact:RemoveInteraction(name)
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system defined in the shared file^0')
    end
end

-- Remove target from entity
--- @param entity any|number
--- @param data table|string
function RemoveTargetEntity(entity, data)
    if shared.setup.interact == 'ox_target' then
        exports.ox_target:removeLocalEntity(entity, data)
    elseif shared.setup.interact == 'qb-target' then
        exports.qtarget:RemoveTargetEntity(entity, data)
    elseif shared.setup.interact == 'interact' then
        exports.interact:RemoveLocalEntityInteraction(entity, data)
    elseif shared.setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('^1[ERROR]: No interaction system defined in the shared file^0')
    end
end

-- Triggered on PlayerLoaded event to display metadata for specific inventory(ies)
function DisplayMetadata()
    if not Inventory then return end
    if Inventory == 'ox_inventory' then
        exports[Inventory]:displayMetadata({
            purity = 'Purity'
        })
    end
end

-- Function to spawn NPCs
--- @param model string
--- @param position vector4
function SpawnPed(model, position)
    lib.requestModel(model, shared.setup.request)
    while not HasModelLoaded(model) do Wait(0) end
    local ped = CreatePed(0, model, position.x, position.y, position.z - 1.0, position.w, false, true)
    FreezeEntityPosition(ped, true)
    SetBlockingOfNonTemporaryEvents(ped, true)
    SetEntityInvincible(ped, true)
    return ped
end

-- Retreive player bucket from server
function GetPlayerBucket()
    return lib.callback.await('lation_coke:getplayerbucket', false)
end

-- Initialize default(s)
InitializeDispatch()