Skip to main content
config.lua
Config = {} -- Do not touch

----------------------------------------------
--        🛠️ Setup the basics below
----------------------------------------------

Config.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,
    -- Do you want the script to use item metadata?
    -- This will give items such as watering cans & fertilizer metadata
    -- As well as give buds, joints & bagged items strain & purity metadata
    metadata = true,
    -- Set your interaction system below
    -- Available options are: 'ox_target', 'qb-target', 'interact', 'textui' & 'custom'
    -- 'custom' needs to be added to client/functions.lua
    -- We also provide support for various textUI: ox_lib, jg-textui, okokTextUI & qbcore
    -- Go to client/functions.lua line 3 to choose which textUI to use
    interact = 'textui',
    -- 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 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,
    -- The below is only used if interact = 'textui'
    -- This will be the key used for interactions, default is E
    -- More options here: https://docs.fivem.net/docs/game-references/controls/
    control = 38,
    -- '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 plant 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,
    -- Put all admin player identifiers' here you wish to give access to /plants command
    -- The /plants command is the admins plant management menu
    -- ESX: use default identifier: char1:abcdefghijklmnopqrstuv123456789
    -- QBCore & QBox: use citizen ID: ABC12345
    -- Ox: use charId
    admins = {
        ['identifier'] = true,
        -- Add more admins here as needed
    },
}

----------------------------------------------
--        👮 Setup police options
----------------------------------------------

Config.Police = {
    -- List all your police jobs below
    jobs = { 'police', 'sheriff' },
    -- Do you want to require police be online to plant seeds?
    require = false,
    -- If require = true, how many should be online?
    count = 3,
    -- Allow police access to the lab ignoring all requirements?
    labAccess = true
}

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

Config.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
}

----------------------------------------------
--     🌿 Setup your planting options
----------------------------------------------

Config.Planting = {
    -- 'max' is the maximum number of plants a player can have
    max = 12,
    -- Customize items required for planting a new plant
    items = {
        plant_pot = {
            -- Item spawn name
            item = 'ls_plant_pot',
            -- Do you want to require a plant pot when planting?
            require = true,
            -- Do you want to remove this item when planting?
            remove = true,
            -- If remove = true, how many to remove?
            consume = 1,
            -- When you harvest a plant, do you want a chance to get a plant pot back?
            return_pot = true,
            -- If return_pot is true, whats the percent chance to return?
            return_chance = 70
        },
        shovel = {
            -- Item spawn name
            item = 'ls_shovel',
            -- Do you want to require a shovel when planting?
            require = false,
            -- Do you want to remove this item when planting?
            remove = false,
            -- If remove = true, how many to remove?
            consume = 1
        }
    },
    -- Do you want to check for collisions when placing a plant?
    -- This will not allow the player to place a plant if its colliding
    -- With anything else during the placement process
    collision = true,
    -- 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 = 5 },
            [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 = 5 },
        [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 = 5 },
            [2] = { coords = vec3(0, 0, 0), radius = 25 },
            -- Add more whitelisted locations here if desired
        }
    },
    -- Every plant by default has a "destroy" option that anybody can use
    -- when interacting with a plant. If you only want the plants owner to
    -- See the destroy option then set can_destroy to false
    -- (police will always have a destroy option regardless of this option)
    anyone_destroy = true,
}

----------------------------------------------
--    🧬 Create, edit & manage strains
----------------------------------------------

-- Create, edit & manage various types of weed strains here
-- By default the props set are our custom weed props! We have more options
-- available and/or if you wish to not use custom props, follow the link below:
-- https://docs.lationscripts.com/premium-resources/advanced-weed-growing/custom-props
Config.Strains = {
    -- A unique identifier for this strain
    ['plain_jane'] = {
        -- What is the name of this strain?
        label = 'Plain Jane',
        -- The seed item for this strain
        seed = 'ls_plain_jane_seed',
        -- The bud (harvested) item for this strain
        bud = 'ls_plain_jane_bud',
        -- The bagged item name of this strain received after "bagging"
        bag = 'ls_plain_jane_bag',
        -- The joint item name of this strain received after "rolling"
        joint = 'ls_plain_jane_joint',
        -- What level is required to grow/roll/bag this strain?
        level = 1,
        -- How much experience is the player rewarded per bud harvested?
        -- If a player harvested x5 buds, they would get XP * 5
        exp = { min = 4, max = 8 },
        -- When a player harvests this plant, what are the yield amounts?
        yields = {
            buds = { min = 4, max = 18 },
            seeds = { min = 1, max = 3 }
        },
        -- The props/models this strain will use at each stage
        props = {
            -- Stage 1
            [1] = 'shoe_shuffler_prop_weed_01_small_green_01a',
            -- Stage 2
            [2] = 'shoe_shuffler_prop_weed_med_green_01a',
            -- Stage 3
            [3] = 'shoe_shuffler_prop_weed_lrg_green_01a'
        }
    },
    ['banana_kush'] = {
        label = 'Banana Kush',
        seed = 'ls_banana_kush_seed',
        bud = 'ls_banana_kush_bud',
        bag = 'ls_banana_kush_bag',
        joint = 'ls_banana_kush_joint',
        level = 2,
        exp = { min = 5, max = 10 },
        yields = {
            buds = { min = 4, max = 18 },
            seeds = { min = 1, max = 3 }
        },
        props = {
            [1] = 'shoe_shuffler_prop_weed_01_small_yellow_01a',
            [2] = 'shoe_shuffler_prop_weed_med_yellow_01a',
            [3] = 'shoe_shuffler_prop_weed_lrg_yellow_01a'
        }
    },
    ['blue_dream'] = {
        label = 'Blue Dream',
        seed = 'ls_blue_dream_seed',
        bud = 'ls_blue_dream_bud',
        bag = 'ls_blue_dream_bag',
        joint = 'ls_blue_dream_joint',
        level = 3,
        exp = { min = 6, max = 12 },
        yields = {
            buds = { min = 4, max = 18 },
            seeds = { min = 1, max = 3 }
        },
        props = {
            [1] = 'shoe_shuffler_prop_weed_01_small_cyan_01a',
            [2] = 'shoe_shuffler_prop_weed_med_cyan_01a',
            [3] = 'shoe_shuffler_prop_weed_lrg_cyan_01a'
        }
    },
    ['purple_haze'] = {
        label = 'Purple Haze',
        seed = 'ls_purple_haze_seed',
        bud = 'ls_purple_haze_bud',
        bag = 'ls_purple_haze_bag',
        joint = 'ls_purple_haze_joint',
        level = 4,
        exp = { min = 7, max = 14 },
        yields = {
            buds = { min = 4, max = 18 },
            seeds = { min = 1, max = 3 }
        },
        props = {
            [1] = 'shoe_shuffler_prop_weed_01_small_purple_01a',
            [2] = 'shoe_shuffler_prop_weed_med_purple_01a',
            [3] = 'shoe_shuffler_prop_weed_lrg_purple_01a'
        }
    },
    ['orange_crush'] = {
        label = 'Orange Crush',
        seed = 'ls_orange_crush_seed',
        bud = 'ls_orange_crush_bud',
        bag = 'ls_orange_crush_bag',
        joint = 'ls_orange_crush_joint',
        level = 5,
        exp = { min = 8, max = 16 },
        yields = {
            buds = { min = 4, max = 18 },
            seeds = { min = 1, max = 3 }
        },
        props = {
            [1] = 'shoe_shuffler_prop_weed_01_small_sunkissed_01a',
            [2] = 'shoe_shuffler_prop_weed_med_sunkissed_01a',
            [3] = 'shoe_shuffler_prop_weed_lrg_sunkissed_01a'
        }
    },
    ['cosmic_kush'] = {
        label = 'Cosmic Kush',
        seed = 'ls_cosmic_kush_seed',
        bud = 'ls_cosmic_kush_bud',
        bag = 'ls_cosmic_kush_bag',
        joint = 'ls_cosmic_kush_joint',
        level = 6,
        exp = { min = 9, max = 18 },
        yields = {
            buds = { min = 4, max = 18 },
            seeds = { min = 1, max = 3 }
        },
        props = {
            [1] = 'shoe_shuffler_prop_weed_01_small_haze_01a',
            [2] = 'shoe_shuffler_prop_weed_med_haze_01a',
            [3] = 'shoe_shuffler_prop_weed_lrg_haze_01a'
        }
    },
    -- Add more strains here as desired following the same format as above
    -- Don't want one of the strains above? Just remove it from the list!
}

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

Config.Cooldown = 45 -- How long (in seconds) the cooldown lasts on each plant once searched
Config.Farms = {
    [1] = {
        -- Do you need the script to spawn the plant props here?
        spawn = false,
        -- If spawn = true, what prop do you want to spawn?
        model = 'prop_weed_01',
        -- The center-most coords of the zone here
        center = vec3(2225.805, 5576.971, 53.857),
        -- The size (radius) of the zone (Config.Setup.debug to see zone)
        size = 15,
        -- The player level required to search plants at this farm
        level = 1,
        -- The location of each plant here (or location to spawn each plant)
        coords = {
            -- If you need help grabbing the exact coords of existing plants
            -- We recommend using dolu_tool: https://github.com/dolutattoo/dolu_tool/releases/latest
            -- It is also recommended to increase the Z coord (3rd coord) + 1 for accuracy
            [1] = vec3(2216.251, 5577.534, 53.739),
            [2] = vec3(2215.845, 5575.269, 53.599),
            [3] = vec3(2218.534, 5577.353, 53.757),
            [4] = vec3(2218.279, 5575.158, 53.720),
            [5] = vec3(2218.917, 5579.656, 53.855),
            [6] = vec3(2220.535, 5577.247, 53.750),
            [7] = vec3(2221.015, 5574.937, 53.621),
            [8] = vec3(2222.687, 5574.870, 53.623),
            [9] = vec3(2223.059, 5577.105, 53.742),
            [10] = vec3(2223.790, 5579.327, 53.831),
            [11] = vec3(2225.323, 5576.916, 53.759),
            [12] = vec3(2225.409, 5579.195, 53.837),
            [13] = vec3(2227.682, 5576.773, 53.775),
            [14] = vec3(2227.326, 5574.560, 53.719),
            [15] = vec3(2230.170, 5576.593, 53.856),
            [16] = vec3(2230.674, 5574.298, 53.815),
            [17] = vec3(2232.641, 5576.405, 53.936),
            [18] = vec3(2233.875, 5578.694, 54.022)
        },
        -- The seeds that can be found at this location
        seeds = {
            -- ['strain_name'] = The unique strain identifier from Config.Strains
            ['plain_jane'] = {
                -- The level player must be to get a chance to get this seed
                level = 1,
                -- The "chance" a player finds this seed
                chance = 60,
                -- The minimum amount to reward when found
                min = 1,
                -- The maximum amount to reward when found
                max = 4,
                -- The minimum and maximum amount of exp to reward per seed found
                exp = { min = 1, max = 1 }
            },
            ['banana_kush'] = {
                level = 2,
                chance = 40,
                min = 1,
                max = 3,
                exp = { min = 1, max = 2 }
            },
            ['blue_dream'] = {
                level = 3,
                chance = 30,
                min = 1,
                max = 2,
                exp = { min = 1, max = 2 }
            },
            ['purple_haze'] = {
                level = 4,
                chance = 20,
                min = 1,
                max = 2,
                exp = { min = 1, max = 2 }
            },
            ['orange_crush'] = {
                level = 5,
                chance = 10,
                min = 1,
                max = 1,
                exp = { min = 1, max = 3 }
            },
            ['cosmic_kush'] = {
                level = 6,
                chance = 5,
                min = 1,
                max = 1,
                exp = { min = 1, max = 4 }
            },
            -- Add or remove seeds here following the same format as above
        },
        -- The percentage chance to find nothing when searching here
        nothing = 15
    },
    -- Add more farms here following the same format as above
    -- Notice: ensure no zones overlap each other
    -- You can double check & verify by setting Config.Setup.debug = true
}

----------------------------------------------
--       🏪 Customize supply shop
----------------------------------------------

Config.Shop = {
    -- Do you want to enable this shop?
    enable = true,
    -- Where is this shop located?
    location = vec4(-1171.0409, -1571.1124, 4.6636, 122.0094),
    -- What ped model should we spawn here?
    model = 'a_m_y_beach_02',
    -- You can limit the hours at which the supply shop is available here
    -- Min is the earliest the shop is available (default 6:00AM)
    -- Max is the latest the shop is available (detault 21:00 aka 9PM)
    -- If you want it available 24/7, set min to 1 and max to 24
    hour = { min = 6, max = 21 },
    -- Use cash or bank when purchasing here?
    account = 'cash',
    -- Customize the items available here
    items = {
        -- item: item spawn name
        -- price: how much it costs
        -- label: display name
        -- icon: icon for this item or nil
        -- min: minimum amount the player must purchase at once
        -- max: maximum amount the player can purchase at once or nil
        [1] = { item = 'ls_watering_can', price = 15, label = 'Water', icon = 'droplet', min = 1, max = 1 },
        [2] = { item = 'ls_fertilizer', price = 20, label = 'Fertilizer', icon = 'burger', min = 1, max = 1 },
        [3] = { item = 'ls_plant_pot', price = 10, label = 'Plant Pot', icon = 'plant-wilt', min = 1, max = 50 },
        [4] = { item = 'ls_shovel', price = 275, label = 'Shovel', icon = 'trowel', min = 1, max = 1 },
        [5] = { item = 'ls_shears', price = 150, label = 'Shears', icon = 'scissors', min = 1, max = 1 },
        [6] = { item = 'ls_rolling_paper', price = 5, label = 'Rolling Paper', icon = 'joint', min = 1, max = 50 },
        [7] = { item = 'ls_empty_baggy', price = 5, label = 'Empty Baggy', icon = 'bag-shopping', min = 1, max = 50 },
        [8] = { item = 'ls_weed_table', price = 5000, label = 'Weed Table', icon = 'cannabis', min = 1, max = 1 },
        -- Add more items if desired here following the same format as above
        -- Or remove items from this list if you want - just be sure to update the number scheme [1], [2], etc
    },
    blip = {
        -- Enable or disable the blip for this shop
        enabled = true,
        -- Sprite ID (https://docs.fivem.net/docs/game-references/blips/)
        sprite = 140,
        -- Color (https://docs.fivem.net/docs/game-references/blips/#blip-colors)
        color = 2,
        -- Size/scale
        scale = 0.8,
        -- Label
        label = 'Smoke on the Water'
    }
}

----------------------------------------------
--       🌿 Customize plant growth
----------------------------------------------

-- ⚠️ WARNING: PLEASE READ CAREFULLY
-- All the configuration options below have significant impacts on the
-- Plant growth system. It is NOT recommend to alter or change any of
-- The settings below unless your willing to test & debug. Doing so can 
-- Have unintended consequences. However, of course if you wish to change
-- How the growth system works, these options are available to you.

Config.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, quality = 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.15, max = 0.4 },
    -- '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.15, max = 0.4 },
    -- '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,
    -- 'good_quality' is the "quality" percentage or above a plant is considered "good quality"
    -- Plants quality levels that are good_quality or higher when harvested, get max yields
    good_quality = 94,
    -- 'quality_threshold' is when a plants thirst or hunger reaches this number, the quality
    -- Of the plant starts to decrease. So if a plant has 100% hunger but 69% thirst, the quality
    -- Level will start going down every update_interval until the plants thirst & hunger are back
    -- Above this amount
    quality_threshold = 70,
    -- 'quality_decrease' is how much the quality percentage will decrease every update_interval
    -- When any plants thirst or hunger level is below the quality_threshold amount (70% by default)
    quality_decrease = { min = 0.4, max = 0.9 },
    -- 'shears' is an optional item requirement to harvest a plant
    shears = {
        -- Item spawn name
        item = 'ls_shears',
        -- Do you want to require shears when harvesting?
        require = false,
        -- Do you want to remove this item when harvesting?
        remove = false,
        -- If remove = true, how many do you want to remove?
        consume = 1
    }
}

----------------------------------------------
--       📊 Customize stats menu
----------------------------------------------

Config.Stats = {
    -- Do you want to show the Plants Searched stat?
    plants_searched = true,
    -- Do you want to show the Plants Grown stat?
    plants_grown = true,
    -- Do you want to show the Bud Harvested stat?
    bud_harvested = true,
    -- Do you want to show the Joints Rolled stats?
    joints_rolled = true,
    -- Do you want to show the Weed Bagged stat?
    weed_bagged = true

    -- 🗒️ Note: if all stats are set to false the
    -- "View Lifetime Statistics" menu will not show at all
}

----------------------------------------------
--        📋 Setup your weed lab
----------------------------------------------

Config.Lab = {
    -- Do you want to enable the weed lab?
    -- This is where players can roll & bag weed
    enable = true,
    -- Where do players enter the lab?
    enter = vec4(416.1885, 6520.8638, 27.7308, 86.4887),
    -- Where do players exit the lab?
    exit = vec4(1066.3086, -3183.4487, -39.1636, 272.1406),
    -- Optional lab entry requirements
    requirements = {
        -- Player level requirement
        level = 1,
        -- Item requirement
        item = {
            -- Do you want to require an item to enter?
            enable = true,
            -- If enable = true, what item?
            name = 'ls_access_card'
        },
        -- Police requirement
        police = {
            -- Do you want to require police be online to enter?
            enable = false,
            -- If enable = true, how many?
            count = 3
        }
    },
    rolling = {
        -- Add, remove and manage rolling table(s) here
        tables = {
            [1] = {
                -- Do you need the script to spawn this table?
                -- If one already exists at these coords, then keep false
                spawn = false,
                -- If spawn = true, what model table?
                model = 'bkr_prop_weed_table_01a',
                -- The coordinates of this rolling table
                coords = vec4(1038.3824, -3205.8552, -39.1231, 85.0345),
                -- The player level required to use this table
                level = 1,
                -- The type of strains that can be rolled here
                buds = {
                    -- ['uniqueStrainIdentifier']: the unique strain identifier from Config.Strains above
                    -- To disable a strain from being rolled here, simply remove it from the list
                    ['plain_jane'] = {
                        -- When rolling this bud, how many to remove per roll?
                        remove_bud = 1,
                        -- When rolling this bud, do you want to require an additional item?
                        -- By default, the required item is "rolling paper" and removes 1 per roll
                        -- If you don't want to require an additional item set remove_item = nil
                        -- item: item spawn name & quantity: amount to remove per roll
                        remove_item = { item = 'ls_rolling_paper', quantity = 1 },
                        -- When rolling this bud, how many joints to reward per roll?
                        add_joint = 1,
                        -- How long does it take per 1 rolling action (in milliseconds)?
                        duration = 2500,
                        -- Maximum amount you can roll at once before having to start again
                        -- This allows you to make this action AFK or not
                        max_roll = 15,
                        -- The amount of exp rewarded to player per joint rolled
                        exp = { min = 1, max = 1 },
                        -- The icon to represent this strain in the menu
                        icon = 'fas fa-cannabis',
                        -- The icon color for this strain (leave '' for none)
                        iconColor = ''
                    },
                    ['banana_kush'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_rolling_paper', quantity = 1 },
                        add_joint = 1,
                        duration = 2500,
                        max_roll = 15,
                        exp = { min = 1, max = 2 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    },
                    ['blue_dream'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_rolling_paper', quantity = 1 },
                        add_joint = 1,
                        duration = 2500,
                        max_roll = 15,
                        exp = { min = 1, max = 2 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    },
                    ['purple_haze'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_rolling_paper', quantity = 1 },
                        add_joint = 1,
                        duration = 2500,
                        max_roll = 15,
                        exp = { min = 1, max = 2 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    },
                    ['orange_crush'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_rolling_paper', quantity = 1 },
                        add_joint = 1,
                        duration = 2500,
                        max_roll = 15,
                        exp = { min = 1, max = 3 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    },
                    ['cosmic_kush'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_rolling_paper', quantity = 1 },
                        add_joint = 1,
                        duration = 2500,
                        max_roll = 15,
                        exp = { min = 1, max = 4 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    }
                }
            },
            -- Add or remove more tables here as desired
            -- Be sure to follow the same format as above
            -- Increment the numbers correctly, [2], [3], etc
        }
    },
    bagging = {
        -- Add, remove and manage bagging table(s) here
        tables = {
            [1] = {
                -- Do you need the script to spawn this table?
                -- If one already exists at these coords, then keep false
                spawn = false,
                -- If spawn = true, what model table?
                model = 'bkr_prop_weed_table_01a',
                -- The coordinates of this bagging table
                coords = vec4(1033.7921, -3206.0498, -39.1231, 85.0345),
                -- The player level required to use this table
                level = 1,
                -- The type of strains that can be bagged here
                buds = {
                    -- ['uniqueStrainIdentifier']: the unique strain identifier from Config.Strains above
                    -- To disable a strain from being bagged here, simply remove it from the list
                    ['plain_jane'] = {
                        -- When bagging this bud, how many buds to remove per bag?
                        remove_bud = 1,
                        -- When bagging this bud, do you want to require an additional item?
                        -- By default, the required item is "empty baggy" and removes 1 per bag
                        -- If you don't want to require an additional item set remove_item = nil
                        -- item: item spawn name & quantity: amount to remove per bag
                        remove_item = { item = 'ls_empty_baggy', quantity = 1 },
                        -- When bagging this bud, how many bags to reward per bagging?
                        add_bag = 1,
                        -- How long does it take per 1 bagging action (in milliseconds)?
                        duration = 2500,
                        -- Maximum amount you can bag at once before having to start again
                        -- This allows you to make this action AFK or not
                        max_bag = 15,
                        -- The amount of exp rewarded to the player per bag
                        exp = { min = 1, max = 1 },
                        -- The icon to represent this strain in the menu
                        icon = 'fas fa-cannabis',
                        -- The icon color for this strain (leave '' for none)
                        iconColor = ''
                    },
                    ['banana_kush'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_empty_baggy', quantity = 1 },
                        add_bag = 1,
                        duration = 2500,
                        max_bag = 15,
                        exp = { min = 1, max = 2 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    },
                    ['blue_dream'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_empty_baggy', quantity = 1 },
                        add_bag = 1,
                        duration = 2500,
                        max_bag = 15,
                        exp = { min = 1, max = 2 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    },
                    ['purple_haze'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_empty_baggy', quantity = 1 },
                        add_bag = 1,
                        duration = 2500,
                        max_bag = 15,
                        exp = { min = 1, max = 2 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    },
                    ['orange_crush'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_empty_baggy', quantity = 1 },
                        add_bag = 1,
                        duration = 2500,
                        max_bag = 15,
                        exp = { min = 1, max = 3 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    },
                    ['cosmic_kush'] = {
                        remove_bud = 1,
                        remove_item = { item = 'ls_empty_baggy', quantity = 1 },
                        add_bag = 1,
                        duration = 2500,
                        max_bag = 15,
                        exp = { min = 1, max = 4 },
                        icon = 'fas fa-cannabis',
                        iconColor = ''
                    }
                }
            },
            -- Add or remove more tables here as desired
            -- Be sure to follow the same format as above
            -- Increment the numbers correctly, [2], [3], etc
        }
    }
}

----------------------------------------------
--      🔄 Customize portable tables
----------------------------------------------

Config.Table = {
    -- Do you want to enable portable weed tables?
    -- Players can place these tables anywhere and use them
    -- For rolling & bagging weed - they are persistent (saved)
    -- through server restarts, logouts & all
    enable = true,
    -- The item spawn name of the weed table
    item = 'ls_weed_table',
    -- The model/object used as the table prop
    -- More models: https://forge.plebmasters.de/objects
    model = 'bkr_prop_weed_table_01a',
    -- 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,
    -- What level must the player be to use portable weed tables?
    -- This option only impacts the ability to use a table, not what the
    -- Player can roll/bag. That comes from Config.Strains.
    level = 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,
    -- Select which actions you want to allow on portable tables
    allow = { rolling = true, bagging = true },
    -- Various requirements to check before allowing table be placed
    requirements = {
        -- Police requirement
        police = {
            -- Do you want to require police be online to place & use tables?
            enable = false,
            -- If enable = true, how many police must be online?
            count = 3,
        }
    },
    -- 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
    },
    -- The type of buds that can be rolled or bagged on these tables
    buds = {
        -- ['uniqueStrainIdentifier']: the unique strain identifier from Config.Strains above
        -- To disable a strain from being rolled or bagged here, simply remove it from the list
        ['plain_jane'] = {
            -- When rolling & bagging this bud, how many to remove per action?
            remove_bud = { rolling = 1, bagging = 1},
            -- When rolling or bagging this bud, do you want to require additional item(s)?
            -- If you don't want to require an additional item set item = nil
            -- item: item spawn name & quantity: amount to remove per roll or bag
            remove_item = {
                rolling = { item = 'ls_rolling_paper', quantity = 1 },
                bagging = { item = 'ls_empty_baggy', quantity = 1 }
            },
            -- When rolling or bagging this bud, how many joints/bags to reward per roll?
            add_item = { rolling = 1, bagging = 1 },
            -- How long does it take per 1 rolling/bagging action (in milliseconds)?
            duration = { rolling = 2500, bagging = 2500 },
            -- Maximum amount you can roll or bag at once before having to start again
            -- This allows you to make this action AFK or not
            max_actions = { rolling = 15, bagging = 15 },
            -- The amount of exp rewarded to player per roll or bag
            exp = {
                rolling = { min = 1, max = 1 },
                bagging = { min = 1, max = 1 }
            },
            -- The icon to represent this strain in the menu
            icon = 'fas fa-cannabis',
            -- The icon color for this strain (leave '' for none)
            iconColor = ''
        },
        ['banana_kush'] = {
            remove_bud = { rolling = 1, bagging = 1},
            remove_item = {
                rolling = { item = 'ls_rolling_paper', quantity = 1 },
                bagging = { item = 'ls_empty_baggy', quantity = 1 }
            },
            add_item = { rolling = 1, bagging = 1 },
            duration = { rolling = 2500, bagging = 2500 },
            max_actions = { rolling = 15, bagging = 15 },
            exp = {
                rolling = { min = 1, max = 1 },
                bagging = { min = 1, max = 1 }
            },
            icon = 'fas fa-cannabis',
            iconColor = ''
        },
        ['blue_dream'] = {
            remove_bud = { rolling = 1, bagging = 1},
            remove_item = {
                rolling = { item = 'ls_rolling_paper', quantity = 1 },
                bagging = { item = 'ls_empty_baggy', quantity = 1 }
            },
            add_item = { rolling = 1, bagging = 1 },
            duration = { rolling = 2500, bagging = 2500 },
            max_actions = { rolling = 15, bagging = 15 },
            exp = {
                rolling = { min = 1, max = 1 },
                bagging = { min = 1, max = 1 }
            },
            icon = 'fas fa-cannabis',
            iconColor = ''
        },
        ['purple_haze'] = {
            remove_bud = { rolling = 1, bagging = 1},
            remove_item = {
                rolling = { item = 'ls_rolling_paper', quantity = 1 },
                bagging = { item = 'ls_empty_baggy', quantity = 1 }
            },
            add_item = { rolling = 1, bagging = 1 },
            duration = { rolling = 2500, bagging = 2500 },
            max_actions = { rolling = 15, bagging = 15 },
            exp = {
                rolling = { min = 1, max = 1 },
                bagging = { min = 1, max = 1 }
            },
            icon = 'fas fa-cannabis',
            iconColor = ''
        },
        ['orange_crush'] = {
            remove_bud = { rolling = 1, bagging = 1},
            remove_item = {
                rolling = { item = 'ls_rolling_paper', quantity = 1 },
                bagging = { item = 'ls_empty_baggy', quantity = 1 }
            },
            add_item = { rolling = 1, bagging = 1 },
            duration = { rolling = 2500, bagging = 2500 },
            max_actions = { rolling = 15, bagging = 15 },
            exp = {
                rolling = { min = 1, max = 1 },
                bagging = { min = 1, max = 1 }
            },
            icon = 'fas fa-cannabis',
            iconColor = ''
        },
        ['cosmic_kush'] = {
            remove_bud = { rolling = 1, bagging = 1},
            remove_item = {
                rolling = { item = 'ls_rolling_paper', quantity = 1 },
                bagging = { item = 'ls_empty_baggy', quantity = 1 }
            },
            add_item = { rolling = 1, bagging = 1 },
            duration = { rolling = 2500, bagging = 2500 },
            max_actions = { rolling = 15, bagging = 15 },
            exp = {
                rolling = { min = 1, max = 1 },
                bagging = { min = 1, max = 1 }
            },
            icon = 'fas fa-cannabis',
            iconColor = ''
        }
    }
}

----------------------------------------------
--      🚬 Customize joint effects
----------------------------------------------

Config.Joints = {
    -- ['item_spawn_name'] Joint item name
    ['ls_plain_jane_joint'] = {
        -- Do you want to make this joint usable/smokable?
        usable = true,
        -- What level must the player be to smoke this joint?
        level = 1,
        -- Manage the effects this joint has on the player
        effects = {
            -- enable: do you want to enable this effect?
            -- amount: if enabled, how much health to apply?
            health = { enable = true, 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 = 20, 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 = false, 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_banana_kush_joint'] = {
        usable = true,
        level = 1,
        effects = {
            health = { enable = true, amount = 50 },
            armor = { enable = true, amount = 20, max = 100 },
            speed = { enable = false, 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 }
        }
    },
    ['ls_blue_dream_joint'] = {
        usable = true,
        level = 1,
        effects = {
            health = { enable = true, amount = 50 },
            armor = { enable = true, amount = 20, max = 100 },
            speed = { enable = false, 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 }
        }
    },
    ['ls_purple_haze_joint'] = {
        usable = true,
        level = 1,
        effects = {
            health = { enable = true, amount = 50 },
            armor = { enable = true, amount = 20, max = 100 },
            speed = { enable = false, 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 }
        }
    },
    ['ls_orange_crush_joint'] = {
        usable = true,
        level = 1,
        effects = {
            health = { enable = true, amount = 50 },
            armor = { enable = true, amount = 20, max = 100 },
            speed = { enable = false, 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 }
        }
    },
    ['ls_cosmic_kush_joint'] = {
        usable = true,
        level = 1,
        effects = {
            health = { enable = true, amount = 50 },
            armor = { enable = true, amount = 20, max = 100 },
            speed = { enable = false, 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 }
        }
    }
}

----------------------------------------------
--    📍 Customize placement controls
----------------------------------------------

Config.Controls = {
    -- How fast the prop placement system moves the object
    speed = 0.025,
    -- The controls used to move objects during placement
    movement = {
        rotateLeft = 44, -- Q
        rotateRight = 38, -- E
        moveForward = 32, -- W
        moveBackward = 33, -- S
        moveLeft = 34, -- A
        moveRight = 35, -- D
        cancel = 73, -- X
        confirm = 22 -- Space
    },
    -- The controls disabled during placement
    disable = {
        30, -- Disables left/right movement
        31, -- Disables forward/backward movement
        44, -- Disables Q (crouch)
        22, -- Disables Spacebar (jump)
        200, -- Disables Escape
        -- Add more here if needed
    }
}

----------------------------------------------
--    💃 Customize animations and props
----------------------------------------------

Config.Animations = {
    placingPot = { -- Animation(s) used during initial start of plant placement
        part1 = { -- Played when player first "uses" a seed
            label = 'Placing pot..',
            duration = 1200,
            position = 'bottom',
            useWhileDead = false,
            canCancel = true,
            disable = { move = true, car = true, combat = true },
            anim = { dict = 'pickup_object', clip = 'pickup_low' },
            prop = { }
        },
        part2 = { -- Played after part1, and continues until confirmation/cancellation of placement
            anim = { dict = 'missheist_jewelleadinout', clip = 'jh_int_outro_loop_a', flag = 51 }
        }
    },
    watering = { -- Animation(s) used when watering a plant
        part1 = { -- Played when player waters the plant
            label = 'Watering..',
            duration = 4000,
            position = 'bottom',
            useWhileDead = false,
            canCancel = false,
            disable = { move = true, car = true, combat = true },
            anim = { dict = 'weapon@w_sp_jerrycan', clip = 'fire', flag = 1 },
            prop = { }
        },
        part2 = { -- Played concurrently with part1 and handles the prop & effects
            prop = { model = 'prop_wateringcan', bone = 28422, pos = vec3(0.4, 0.125, -0.05), rot = vec3(90.0, 180.0, 0.0) },
            fx = { dict = 'core', name = 'ent_sht_water', offset = vec3(0.35, 0.0, 0.25), rot = vec3(0.0, 0.0, 0.0), scale = 2.0 }
        }
    },
    fertilizing = { -- Animation(s) used when fertilizing a plant
        part1 = { -- Played when a player fertilizes a plant
            label = 'Fertilizing..',
            duration = 4000,
            position = 'bottom',
            useWhileDead = false,
            canCancel = false,
            disable = { move = true, car = true, combat = true },
            anim = { dict = 'weapon@w_sp_jerrycan', clip = 'fire', flag = 1 },
            prop = { }
        },
        part2 = { -- Played concurrently with part1 and handles the prop & effects
            prop = { model = 'p_cs_sack_01_s', bone = 28422, pos = vec3(0.3239, -0.0328, 0.1253), rot = vec3(49.4678, -18.1732, -79.2577) },
            fx = { dict = 'scr_fbi3', name = 'scr_fbi3_dirty_water_pour', offset = vec3(0.0, 0.0, 0.0), rot = vec3(0.0, 0.0, 0.0), scale = 2.0 }
        }
    },
    destroying = { -- Animation(s) used when deleting a plant
        label = 'Destroying..',
        duration = 4000,
        position = 'bottom',
        useWhileDead = false,
        canCancel = false,
        disable = { move = true, car = true, combat = true },
        anim = { dict = 'amb@prop_human_bum_bin@base', clip = 'base' },
        prop = { }
    },
    harvesting = { -- Animation(s) used when harvesting a plant
        label = 'Harvesting..',
        duration = 4000,
        position = 'bottom',
        useWhileDead = false,
        canCancel = false,
        disable = { move = true, car = true, combat = true },
        anim = { dict = 'amb@prop_human_bum_bin@base', clip = 'base' },
        prop = { }
    },
    searching = { -- Animation(s) used when searching a plant
        label = 'Searching..',
        duration = 6000,
        position = 'bottom',
        useWhileDead = false,
        canCancel = false,
        disable = { move = true, car = true, combat = true },
        anim = { dict = 'amb@prop_human_bum_bin@base', clip = 'base' },
        prop = { }
    },
    rolling = { -- Animation(s) used when rolling joints
        label = 'Rolling..',
        duration = nil,
        position = 'bottom',
        useWhileDead = false,
        canCancel = true,
        disable = { car = true, move = true, combat = true },
        anim = { dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', clip = 'machinic_loop_mechandplayer' },
        prop = { }
    },
    bagging = { -- Animation(s) used when bagging buds
        label = 'Bagging..',
        duration = nil,
        position = 'bottom',
        useWhileDead = false,
        canCancel = true,
        disable = { car = true, move = true, combat = true },
        anim = { dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', clip = 'machinic_loop_mechandplayer' },
        prop = { }
    },
    smoking = { -- Animation(s) used when smoking joints
        label = 'Smoking..',
        duration = 10000,
        position = 'bottom',
        useWhileDead = false,
        canCancel = true,
        disable = { car = false, move = false, combat = true },
        anim = { dict = 'amb@world_human_aa_smoke@male@idle_a', clip = 'idle_c' },
        prop = { model = 'p_cs_joint_01', bone = 28422, pos = vec3(0.0, 0.0, 0.0), rot = vec3(-0.07, 0.0, 1.0) },
    },
    place_table = { -- Animation(s) used when placing a table
        label = 'Placing table..',
        duration = 1500,
        position = 'bottom',
        useWhileDead = false,
        canCancel = true,
        disable = { move = true, car = true, combat = true },
        anim = { dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', clip = 'machinic_loop_mechandplayer', flag = 0 },
        prop = {}
    },
    pickup_table = { -- Animation(s) used when picking up a table
        label = 'Picking up table..',
        duration = 1500,
        position = 'bottom',
        useWhileDead = false,
        canCancel = true,
        disable = { move = true, car = true, combat = true },
        anim = { dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', clip = 'machinic_loop_mechandplayer', flag = 0 },
        prop = {}
    },
}
-- 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_weed: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_weed:onPlayerLoaded')
        end)

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

        AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
            PlayerData = GetPlayerData()
            PlayerLoaded = true
            TriggerEvent('lation_weed: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_weed: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_weed: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_weed: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_weed: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_weed: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 data
--- @return any
function GetPlayerInventory()
    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 GetPlayerJob()
    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.getGroups()
    else
        -- Add custom framework here
    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()
-- You can change the textUI script here
-- Options: 'lation_ui', 'ox_lib', 'jg-textui', 'okokTextUI', 'qbcore' & 'custom'
-- We have pre-formatted text for use with jg & okok textUI, visit our docs:
-- https://docs.lationscripts.com/premium-resources/advanced-weed-growing/guides/textui
local textui = 'ox_lib'

-- Used to determine if a player can (true) or cannot(false) search a plant for seeds
--- @param id number farmId, can be accessed via Config.Farms[id]
function CanSearchPlant(id)
    return true
end

-- Empty function used to allow or reject a user from placing a plant
-- You can add custom requirements here if you have the knowledge to do so
-- To restrict planting return false. To allow return true.
--- @param strain string Unique strain identifier: Config.Strains[strain]
function CanPlant(strain)
    local inVehicle = IsPedInAnyVehicle(cache.ped, true)
    if inVehicle then
        ShowNotification(locale('notify.no-vehicle'), 'error')
        return false
    end
    return true
end

-- Function used to allow or reject a user from entering the lab
-- You can add custom requirements here if you have the knowledge to do so
-- To restrict access return false. To allow access return true.
function CanEnterLab()
    local result, isPolice = true, false
    if Config.Police.labAccess then
        local playerJob = GetPlayerJob()
        for _, job in pairs(Config.Police.jobs) do
            if playerJob == job then
                isPolice = true
            end
        end
    end
    local requirements = Config.Lab.requirements
    if not requirements then
        EventLog('[functions.lua]: CanEnterLab: missing config data')
        result = false
    end
    local playerLevel = exports.lation_weed:GetPlayerData('level')
    if playerLevel < requirements.level and not isPolice then
        EventLog('[functions.lua]: CanEnterLab: player does not meet level requirement')
        result = false
    end
    if requirements.item.enable then
        local hasItem = HasItem(requirements.item.name, 1)
        if not hasItem and not isPolice then
            EventLog('[functions.lua]: CanEnterLab: player does not have required item')
            result = false
        end
    end
    if requirements.police.enable then
        local police = lib.callback.await('lation_weed:PoliceCount', false)
        if police < requirements.police.count and not isPolice then
            EventLog('[functions.lua]: CanEnterLab: not enough police available')
            result = false
        end
    end
    if not result then
        ShowNotification(locale('notify.not-authorized'), 'error')
    end
    -- If you want to add custom requirements for entering the lab
    -- This is where you would make that happen
    return result
end

-- Empty function used to allow or reject a player from starting to roll a joint(s)
-- To reject a roll return false. To allow a roll return true.
function CanRoll(strain)
    return true
end

-- Empty function used to allow or reject a player from starting to bag bud(s)
-- To reject a bag return false. To allow a bag return true.
function CanBag(strain)
    return true
end

-- Used to determine if a player can (true) or cannot (false) smoke a joint
-- Can be used to make custom edits, requirements, etc
function CanSmoke()
    return true
end

-- Function that's triggered when planting process has started
-- This function can be used for anything, but by default
-- It disables the ability to open your inventory during
-- The plant placement process
function StartedPlanting()
    DisableInventory()
end

-- Function that's triggered when finished planting process
-- As mentioned above, this function can be used for anything
-- But by default, after a player has finished plant placement
-- It will re-enable access to their inventory
function StoppedPlanting()
    EnableInventory()
end

-- Function that's triggered when a player is in-range of a plant
-- And it is spawned on their client
--- @param entity number
function PlantSpawned(entity)

end

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

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

-- Display TextUI
--- @param text string 
--- @param icon string
function ShowTextUI(text, icon)
    if textui == 'lation_ui' then
        exports.lation_ui:showText({
            description = text,
            icon = icon,
        })
    elseif textui == 'ox_lib' then
        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 Config.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,
            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 Config.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 Config.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 Config.Setup.menu == 'lation_ui' then
        exports.lation_ui:registerMenu(data)
    elseif Config.Setup.menu == 'ox_lib' then
        lib.registerContext(data)
    elseif Config.Setup.menu == 'custom' then
        -- Add 'custom' menu system here
    end
end

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

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

-- Display an input dialog
--- @param data table
function ShowInput(data)
    if Config.Setup.dialogs == 'lation_ui' then
        return exports.lation_ui:input({
            title = data.title,
            subtitle = data.subtitle,
            submitText = data.submitText,
            cancelText = data.cancelText,
            type = data.type,
            options = data.options
        })
    elseif Config.Setup.dialogs == 'ox_lib' then
        return lib.inputDialog(data.title, data.options)
    elseif Config.Setup.dialogss == 'custom' then
        -- Add your custom input dialog here
    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

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

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

-- Function to apply target options to model
--- @param model string Model
--- @param data table Options table
function AddTargetModel(model, data)
    if Config.Setup.interact == 'ox_target' then
        exports.ox_target:addModel(model, data)
    elseif Config.Setup.interact == 'qb-target' then
        exports['qb-target']:AddTargetModel(model, {options = data})
    elseif Config.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 Config.Setup.interact == 'textui' then
        -- TextUI is being used
    elseif Config.Setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('No interaction system defined in the config file.')
    end
end

-- Function to add circle target zones
--- @param data table
function AddCircleZone(data)
    if Config.Setup.interact == 'ox_target' then
        exports.ox_target:addSphereZone(data)
    elseif Config.Setup.interact == 'qb-target' then
        exports['qb-target']:AddCircleZone(data.name, data.coords, data.radius, {
            name = data.name,
            debugPoly = Config.Setup.debug}, {
            options = data.options,
            distance = 2,
        })
    elseif Config.Setup.interact == 'interact' then
        exports.interact:AddInteraction({
            coords = data.coords,
            interactDst = 2.0,
            id = data.name,
            options = data.options
        })
    elseif Config.Setup.interact == 'textui' then
        -- TextUI is being used
    elseif Config.Setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('No interaction system defined in the config file.')
    end
end

-- Remove target from entity
--- @param entity any|number Entity number
--- @param data table|string Options table or table name
function RemoveTargetEntity(entity, data)
    if Config.Setup.interact == 'ox_target' then
        exports.ox_target:removeLocalEntity(entity, data)
    elseif Config.Setup.interact == 'qb-target' then
        exports.qtarget:RemoveTargetEntity(entity, data)
    elseif Config.Setup.interact == 'interact' then
        exports.interact:RemoveLocalEntityInteraction(entity, data)
    elseif Config.Setup.interact == 'textui' then
        -- TextUI is being used
    elseif Config.Setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('No interaction system defined in the config file.')
    end
end

-- Function to remove target options
--- @param model string Model
function RemoveTargetModel(model)
    if Config.Setup.interact == 'ox_target' then
        exports.ox_target:removeModel(model, nil)
    elseif Config.Setup.interact == 'qb-target' then
        exports['qb-target']:RemoveTargetModel(model, nil)
    elseif Config.Setup.interact == 'interact' then
        exports.interact:RemoveModelInteraction(model, model)
    elseif Config.Setup.interact == 'textui' then
        -- TextUI is being used
    elseif Config.Setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('No interaction system defined in the config file.')
    end
end

-- Function to remove circle zone
--- @param name string
function RemoveCircleZone(name)
    if Config.Setup.interact == 'ox_target' then
        exports.ox_target:removeZone(name)
    elseif Config.Setup.interact == 'qb-target' then
        exports['qb-target']:RemoveZone(name)
    elseif Config.Setup.interact == 'interact' then
        exports.interact:RemoveInteraction(name)
    elseif Config.Setup.interact == 'textui' then
        -- TextUI is being used
    elseif Config.Setup.interact == 'custom' then
        -- Add support for a custom target system here
    else
        print('No interaction system defined in the config file.')
    end
end

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

-- Callback used to play animation before applying effects
lib.callback.register('lation_weed:SmokeJoint', function()
    if not CanSmoke() then return false end
    if ProgressBar(Config.Animations.smoking) then
        return true
    end
    return false
end)

-- Event handler to apply health upon smoking
--- @param joint string Joint item name
RegisterNetEvent('lation_weed:ApplyHealth', function(joint)
    if not joint then return end
    local data = Config.Joints[joint].effects.health
    if not data then return end
    local request = lib.callback.await('lation_weed:ValidateRequest', false, 'health')
    if not request then return end
    local current = GetEntityHealth(cache.ped)
    local new = math.min(current + data.amount)
    if new > 200 then new = 200 end
    SetEntityHealth(cache.ped, new)
end)

-- Event handler to apply armor upon smoking
--- @param joint string Joint item name
RegisterNetEvent('lation_weed:ApplyArmor', function(joint)
    if not joint then return end
    local data = Config.Joints[joint].effects.armor
    if not data then return end
    local request = lib.callback.await('lation_weed:ValidateRequest', false, 'armor')
    if not request then return end
    local current = GetPedArmour(cache.ped)
    local new = math.min(current + data.amount, data.max)
    SetPedArmour(cache.ped, new)
end)

-- Event handler to apply speed multiplier upon smoking
--- @param joint string Joint item name
RegisterNetEvent('lation_weed:ApplySpeed', function(joint)
    if not joint then return end
    local data = Config.Joints[joint].effects.speed
    if not data then return end
    local request = lib.callback.await('lation_weed:ValidateRequest', false, 'speed')
    if not request then return end
    local duration = data.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, data.multiplier)
        end
        SetRunSprintMultiplierForPlayer(cache.playerId, 1.0)
    end)
end)

-- Event handler to apply scren effects upon smoking
--- @param joint string Joint item name
RegisterNetEvent('lation_weed:ApplyScreen', function(joint)
    if not joint then return end
    local data = Config.Joints[joint].effects.screen
    if not data then return end
    SetTimecycleModifier(data.effect)
    SetTimeout(data.duration, function()
        ClearTimecycleModifier()
    end)
end)

-- Event handler to apply walk effects upon smoking
--- @param joint string Joint item name
RegisterNetEvent('lation_weed:ApplyWalk', function(joint)
    if not joint then return end
    local data = Config.Joints[joint].effects.walk
    if not data then return end
    lib.requestAnimSet(data.clipset, Config.Setup.request)
    while not HasAnimSetLoaded(data.clipset) do Wait(0) end
    SetPedMovementClipset(cache.ped, data.clipset, 0)
    SetTimeout(data.duration, function()
        ResetPedMovementClipset(cache.ped, 0)
    end)
end)

-- Event handler to apply screen shake upon smoking
--- @param joint string Joint item name
RegisterNetEvent('lation_weed:ApplyShake', function(joint)
    if not joint then return end
    local data = Config.Joints[joint].effects.shake
    if not data then return end
    ShakeGameplayCam(data.name, data.intensity)
    SetTimeout(data.duration, function()
        StopGameplayCamShaking(true)
    end)
end)

-- Function used to print events if Config.Setup.debug is enabled
--- @param string string Event message
function EventLog(string)
    if not string or not Config.Setup.debug then return end
    print(string)
end