[ESX] shop locations based on props not coordinates

I’m trying to mod esx_shops with esx_hotdog to create a prop based shop (food carts) script. I was able to convert esx_atm to be prop based fairly easily but having trouble with this. The use of zones and database call backs have proven a bit too complex for me. Any feedback or help would be greatly appreciated. This is what I have so far.

CONFIG

Config              = {}
Config.DrawDistance = 25
Config.Size         = {x = 1.5, y = 1.5, z = 1.5}
Config.Color        = {r = 0, g = 128, b = 255}
Config.Type         = 1
Config.Locale       = 'en'

Config.Zones = {

	CheapShotCoffeeCarts = {
		Items = {},
		Prop = {"p_ld_coffee_vend_01"}
	}

}

CLIENT

local Keys = {
	["ESC"] = 322, ["F1"] = 288, ["F2"] = 289, ["F3"] = 170, ["F5"] = 166, ["F6"] = 167, ["F7"] = 168, ["F8"] = 169, ["F9"] = 56, ["F10"] = 57,
	["~"] = 243, ["1"] = 157, ["2"] = 158, ["3"] = 160, ["4"] = 164, ["5"] = 165, ["6"] = 159, ["7"] = 161, ["8"] = 162, ["9"] = 163, ["-"] = 84, ["="] = 83, ["BACKSPACE"] = 177,
	["TAB"] = 37, ["Q"] = 44, ["W"] = 32, ["E"] = 38, ["R"] = 45, ["T"] = 245, ["Y"] = 246, ["U"] = 303, ["P"] = 199, ["["] = 39, ["]"] = 40, ["ENTER"] = 18,
	["CAPS"] = 137, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["F"] = 23, ["G"] = 47, ["H"] = 74, ["K"] = 311, ["L"] = 182,
	["LEFTSHIFT"] = 21, ["Z"] = 20, ["X"] = 73, ["C"] = 26, ["V"] = 0, ["B"] = 29, ["N"] = 249, ["M"] = 244, [","] = 82, ["."] = 81,
	["LEFTCTRL"] = 36, ["LEFTALT"] = 19, ["SPACE"] = 22, ["RIGHTCTRL"] = 70,
	["HOME"] = 213, ["PAGEUP"] = 10, ["PAGEDOWN"] = 11, ["DELETE"] = 178,
	["LEFT"] = 174, ["RIGHT"] = 175, ["TOP"] = 27, ["DOWN"] = 173,
	["NENTER"] = 201, ["N4"] = 108, ["N5"] = 60, ["N6"] = 107, ["N+"] = 96, ["N-"] = 97, ["N7"] = 117, ["N8"] = 61, ["N9"] = 118
}

ESX                           = nil

local HasAlreadyEnteredMarker = false
local LastZone                = nil
local CurrentAction           = nil
local CurrentActionMsg        = ''
local CurrentActionData       = {}

local cam					  = -1
local CurrentStand 			  = nil

Citizen.CreateThread(function()
	while ESX == nil do
		TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
		Citizen.Wait(0)
	end

	Citizen.Wait(5000)

	ESX.TriggerServerCallback('tresh_foodstands:requestDBItems', function(ShopItems)
		for k,v in pairs(ShopItems) do
			Config.Zones[k].Items = v
		end
	end)
end)

function OpenStandMenu(zone)
	local elements = {}
	for i=1, #Config.Zones[zone].Items, 1 do
		local item = Config.Zones[zone].Items[i]

		if item.limit == -1 then
			item.limit = 1
		end

		table.insert(elements, {
			label      = ('%s - <span style="color:green;">%s</span>'):format(item.label, _U('stand_item', ESX.Math.GroupDigits(item.price))),
			label_real = item.label,
			item       = item.item,
			price      = item.price,

			-- menu properties
			value      = 1,
			type       = 'slider',
			min        = 1,
			max        = item.limit
		})
	end

	ESX.UI.Menu.CloseAll()
	ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'stand', {
		title    = _U('stand'),
		align    = 'bottom-right',
		elements = elements
	}, function(data, menu)
		ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'stand_confirm', {
			title    = _U('stand_confirm', data.current.value, data.current.label_real, ESX.Math.GroupDigits(data.current.price * data.current.value)),
			align    = 'bottom-right',
			elements = {
				{label = _U('no'),  value = 'no'},
				{label = _U('yes'), value = 'yes'}
			}
		}, function(data2, menu2)
			if data2.current.value == 'yes' then
				TriggerServerEvent('tresh_foodstands:buyItem', data.current.item, data.current.value, zone)
			end

			menu2.close()
			menu.close()
		end, function(data2, menu2)
			menu2.close()
		end)
	end, function(data, menu)
		menu.close()

		CurrentAction     = 'stand_menu'
		CurrentActionMsg  = _U('press_menu')
		CurrentActionData = {zone = zone}
	end)
end

AddEventHandler('tresh_foodstands:hasEnteredMarker', function(zone)
	CurrentAction     = 'stand_menu'
	CurrentActionMsg  = _U('press_menu')
	CurrentActionData = {zone = zone}
end)

AddEventHandler('tresh_foodstands:hasExitedMarker', function(zone)
	CurrentAction = nil
	ESX.UI.Menu.CloseAll()
end)

-- Enter / Exit marker events
Citizen.CreateThread(function()
	while true do
		Citizen.Wait(0)
		local PlayerCoords      = GetEntityCoords(PlayerPedId())
		local isInMarker  		= false
		local currentZone 		= nil

		for k,v in pairs(Config.Zones) do
			
			local ClosestStand = GetClosestObjectOfType(PlayerCoords, 0.7, GetHashKey(v.Prop), false, false)
				
			if ClosestStand ~= 0 and ClosestStand ~= nil then
				CurrentStand = ClosestStand	
				
				isInMarker  = true
				ShopItems   = v.Items
				currentZone = k
				LastZone    = k
				break
			else
				CurrentStand = nil
			end
		end
		if isInMarker and not HasAlreadyEnteredMarker then
			HasAlreadyEnteredMarker = true
			TriggerEvent('tresh_foodstands:hasEnteredMarker', currentZone)
		end
		if not isInMarker and HasAlreadyEnteredMarker then
			HasAlreadyEnteredMarker = false
			TriggerEvent('tresh_foodstands:hasExitedMarker', LastZone)
		end
	end
end)

--Key Controls--
Citizen.CreateThread(function()
	while true do
		Citizen.Wait(5)

		if CurrentStand ~= nil and IsControlJustReleased(0, 38) then
			if CurrentAction == 'stand_menu' then
				OpenStandMenu(CurrentActionData.zone)
			end
			CurrentAction = nil

		end
	end
end)

--Items--
RegisterNetEvent('tresh_coffee_stand:onDrink')
AddEventHandler('tresh_coffee_stand:onDrink', function(prop_name)
	if not IsAnimated then
		prop_name = prop_name or 'p_ing_coffeecup_01'
		IsAnimated = true

		Citizen.CreateThread(function()
			local playerPed = PlayerPedId()
			local x,y,z = table.unpack(GetEntityCoords(playerPed))
			local prop = CreateObject(GetHashKey(prop_name), x, y, z + 0.2, true, true, true)
			local boneIndex = GetPedBoneIndex(playerPed, 18905)
			AttachEntityToEntity(prop, playerPed, boneIndex, 0.12, 0.028, 0.001, 10.0, 175.0, 0.0, true, true, false, true, 1, true)

			ESX.Streaming.RequestAnimDict('mp_player_intdrink', function()
				TaskPlayAnim(playerPed, 'mp_player_intdrink', 'loop_bottle', 1.0, -1.0, 2000, 0, 1, true, true, true)

				Citizen.Wait(3000)
				IsAnimated = false
				ClearPedSecondaryTask(playerPed)
				DeleteObject(prop)
			end)
		end)

	end
end)

SERVER

ESX             = nil
local ShopItems = {}

TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)

MySQL.ready(function()
	MySQL.Async.fetchAll('SELECT * FROM food_stands LEFT JOIN items ON items.name = food_stands.item', {}, function(shopResult)
		for i=1, #shopResult, 1 do
			if shopResult[i].name then
				if ShopItems[shopResult[i].stand] == nil then
					ShopItems[shopResult[i].stand] = {}
				end

				if shopResult[i].limit == -1 then
					shopResult[i].limit = 1
				end

				table.insert(ShopItems[shopResult[i].stand], {
					label = shopResult[i].label,
					item  = shopResult[i].item,
					price = shopResult[i].price,
					limit = shopResult[i].limit
				})
			else
				print(('tresh_foodstands: invalid item "%s" found!'):format(shopResult[i].item))
			end
		end
	end)
end)

ESX.RegisterServerCallback('tresh_foodstands:requestDBItems', function(source, cb)
	cb(ShopItems)
end)

RegisterServerEvent('tresh_foodstands:buyItem')
AddEventHandler('tresh_foodstands:buyItem', function(itemName, amount, zone)
	local _source = source
	local xPlayer = ESX.GetPlayerFromId(_source)
	local sourceItem = xPlayer.getInventoryItem(itemName)

	amount = ESX.Math.Round(amount)

	-- is the player trying to exploit?
	if amount < 0 then
		print('tresh_foodstands: ' .. xPlayer.identifier .. ' attempted to exploit the stand!')
		return
	end

	-- get price
	local price = 0
	local itemLabel = ''

	for i=1, #ShopItems[zone], 1 do
		if ShopItems[zone][i].item == itemName then
			price = ShopItems[zone][i].price
			itemLabel = ShopItems[zone][i].label
			break
		end
	end

	price = price * amount

	-- can the player afford this item?
	if xPlayer.getMoney() >= price then
		-- can the player carry the said amount of x item?
		if sourceItem.limit ~= -1 and (sourceItem.count + amount) > sourceItem.limit then
			TriggerClientEvent('esx:showNotification', _source, _U('player_cannot_hold'))
		else
			xPlayer.removeMoney(price)
			xPlayer.addInventoryItem(itemName, amount)
			TriggerClientEvent('esx:showNotification', _source, _U('bought', amount, itemLabel, ESX.Math.GroupDigits(price)))
		end
	else
		local missingMoney = price - xPlayer.getMoney()
		TriggerClientEvent('esx:showNotification', _source, _U('not_enough', ESX.Math.GroupDigits(missingMoney)))
	end
end)

--items--

ESX.RegisterUsableItem('cafeaulait', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('cafeaulait', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_cafeaulait'))
end)

ESX.RegisterUsableItem('latte', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('latte', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_latte'))
end)

ESX.RegisterUsableItem('cappucino', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('cappucino', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_cappucino'))
end)

ESX.RegisterUsableItem('espresso', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('espresso', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_espresso'))
end)

ESX.RegisterUsableItem('americano', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('americano', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_americano'))
end)

ESX.RegisterUsableItem('chocamocha', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('chocamocha', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_chocamocha'))
end)

ESX.RegisterUsableItem('macchiato', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('macchiato', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_macchiato'))
end)

ESX.RegisterUsableItem('cocoa', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('cocoa', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_cocoa'))
end)

ESX.RegisterUsableItem('hottea', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('hottea', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_hottea'))
end)

ESX.RegisterUsableItem('chaitea', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('chaitea', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_chaitea'))
end)

ESX.RegisterUsableItem('fruitjuice', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('fruitjuice', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_fruitjuice'))
end)

ESX.RegisterUsableItem('milkshake', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('milkshake', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_milkshake'))
end)

ESX.RegisterUsableItem('smoothie', function(source)
	local xPlayer = ESX.GetPlayerFromId(source)

	xPlayer.removeInventoryItem('smoothie', 1)

	TriggerClientEvent('esx_status:add', source, 'thirst', 300000)
	TriggerClientEvent('tresh_coffee_stand:onDrink', source)
	TriggerClientEvent('esx:showNotification', source, _U('use_smoothie'))
end)```

I’m still in struggle town with this bad boy. Any assistance would be appreciated :slight_smile:

some progress, but the items aren’t coming up in the menu…

since the server script is basically esx_shops i figure the issue is in the client. i’m also confident esx_menu isnt the issue either. here is the client script if anyone wants to have a look.

local Keys = {
	["ESC"] = 322, ["F1"] = 288, ["F2"] = 289, ["F3"] = 170, ["F5"] = 166, ["F6"] = 167, ["F7"] = 168, ["F8"] = 169, ["F9"] = 56, ["F10"] = 57,
	["~"] = 243, ["1"] = 157, ["2"] = 158, ["3"] = 160, ["4"] = 164, ["5"] = 165, ["6"] = 159, ["7"] = 161, ["8"] = 162, ["9"] = 163, ["-"] = 84, ["="] = 83, ["BACKSPACE"] = 177,
	["TAB"] = 37, ["Q"] = 44, ["W"] = 32, ["E"] = 38, ["R"] = 45, ["T"] = 245, ["Y"] = 246, ["U"] = 303, ["P"] = 199, ["["] = 39, ["]"] = 40, ["ENTER"] = 18,
	["CAPS"] = 137, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["F"] = 23, ["G"] = 47, ["H"] = 74, ["K"] = 311, ["L"] = 182,
	["LEFTSHIFT"] = 21, ["Z"] = 20, ["X"] = 73, ["C"] = 26, ["V"] = 0, ["B"] = 29, ["N"] = 249, ["M"] = 244, [","] = 82, ["."] = 81,
	["LEFTCTRL"] = 36, ["LEFTALT"] = 19, ["SPACE"] = 22, ["RIGHTCTRL"] = 70,
	["HOME"] = 213, ["PAGEUP"] = 10, ["PAGEDOWN"] = 11, ["DELETE"] = 178,
	["LEFT"] = 174, ["RIGHT"] = 175, ["TOP"] = 27, ["DOWN"] = 173,
	["NENTER"] = 201, ["N4"] = 108, ["N5"] = 60, ["N6"] = 107, ["N+"] = 96, ["N-"] = 97, ["N7"] = 117, ["N8"] = 61, ["N9"] = 118
}

ESX								= nil
local cam						= -1
local HasAlreadyEnteredMarker 	= false
local CurrentStand 				= nil
local CurrentAction          	= nil
local CurrentActionData     	= {}
local LastZone                	= nil

Citizen.CreateThread(function()
	while ESX == nil do
		TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
		Citizen.Wait(0)
	end

	Citizen.Wait(5000)

	ESX.TriggerServerCallback('tresh_fstands_N_vmachines:requestDBItems', function(ShopItems)
		for k,v in pairs(ShopItems) do
			Config.Zones[k].Items = v
		end
	end)
end)

function OpenStandMenu(zone)
	local elements = {}
	for i=1, #Config.Zones[zone].Items, 1 do
		local item = Config.Zones[zone].Items[i]

		if item.limit == -1 then
			item.limit = 1
		end

		table.insert(elements, {
			label      = ('%s - <span style="color:green;">%s</span>'):format(item.label, _U('stand_item', ESX.Math.GroupDigits(item.price))),
			label_real = item.label,
			item       = item.item,
			price      = item.price,

			-- menu properties
			value      = 1,
			type       = 'slider',
			min        = 1,
			max        = item.limit
		})
	end

	ESX.UI.Menu.CloseAll()
	ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'stand', {
		title    = _U('stand'),
		align    = 'bottom-right',
		elements = elements
	}, function(data, menu)
		ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'stand_confirm', {
			title    = _U('stand_confirm', data.current.value, data.current.label_real, ESX.Math.GroupDigits(data.current.price * data.current.value)),
			align    = 'bottom-right',
			elements = {
				{label = _U('no'),  value = 'no'},
				{label = _U('yes'), value = 'yes'}
			}
		}, function(data2, menu2)
			if data2.current.value == 'yes' then
				TriggerServerEvent('tresh_fstands_N_vmachines:buyItem', data.current.item, data.current.value, zone)
			end

			menu2.close()
			menu.close()
		end, function(data2, menu2)
			menu2.close()
		end)
	end, function(data, menu)
		menu.close()

		CurrentActionData = {zone = zone}
	end)
end

AddEventHandler('tresh_fstands_N_vmachines:hasEnteredMarker', function(zone)
	CurrentAction     = 'stand_menu'
	CurrentActionData = {zone = zone}
end)

AddEventHandler('tresh_fstands_N_vmachines:hasExitedMarker', function(zone)
	CurrentAction = nil
	ESX.UI.Menu.CloseAll()
end)



Citizen.CreateThread(function() --Detects if he is near a booth
	while true do
		Citizen.Wait(1000)

			local PlayerPed = PlayerPedId()
			local PlayerCoords = GetEntityCoords(PlayerPed)
			local currentZone = nil
			local isInMarker  = false

			for k,v in pairs(Config.Zones) do
				for i = 1, #v.Stand, 1 do
					local ClosestStand = GetClosestObjectOfType(PlayerCoords, 1.5, GetHashKey(v.Stand[i]), false, false)

					if ClosestStand ~= 0 and ClosestStand ~= nil then
						CurrentStand = ClosestStand
						isInMarker  = true
						currentZone = k
						LastZone    = k
						ShopItems = v.Items
						break
					else
						CurrentStand = nil
					end
				end
			end
			if isInMarker and not HasAlreadyEnteredMarker then
				HasAlreadyEnteredMarker = true
				TriggerEvent('tresh_fstands_N_vmachines:hasEnteredMarker', currentZone)
			end
			if not isInMarker and HasAlreadyEnteredMarker then
				HasAlreadyEnteredMarker = false
				TriggerEvent('tresh_fstands_N_vmachines:hasExitedMarker', LastZone)
			end
	end
end)

Citizen.CreateThread(function() -- if he's near a booth then we put him the notif
	while true do
		Citizen.Wait(5)

		if CurrentStand ~= nil then
			
			local StandCoord = GetEntityCoords(CurrentStand)

			ESX.ShowHelpNotification(_U('stand_prompt'))
		end
	end
end)

-- Key Controls
Citizen.CreateThread(function()
	while true do
		Citizen.Wait(5)
		if CurrentAction ~= nil then
			if CurrentStand ~= nil and IsControlJustReleased(0, 38) then
				if CurrentAction == 'stand_menu' then
					OpenStandMenu(CurrentActionData.zone)
				end

				CurrentAction = nil
			end
		else
			Citizen.Wait(500)
		end
	end
end)

-- Function Message en bas
function DrawMissionText(msg, time)
  	ClearPrints()
  	SetTextEntry_2('STRING')
  	AddTextComponentString(msg)
  	DrawSubtitleTimed(time, 1)
end

RegisterNetEvent('tresh_fstands_N_vmachines:onDrink')
AddEventHandler('tresh_fstands_N_vmachines:onDrink', function(prop_name)
	if not IsAnimated then
		prop_name = prop_name or 'p_ing_coffeecup_01'
		IsAnimated = true

		Citizen.CreateThread(function()
			local playerPed = PlayerPedId()
			local x,y,z = table.unpack(GetEntityCoords(playerPed))
			local prop = CreateObject(GetHashKey(prop_name), x, y, z + 0.2, true, true, true)
			local boneIndex = GetPedBoneIndex(playerPed, 18905)
			AttachEntityToEntity(prop, playerPed, boneIndex, 0.12, 0.028, 0.001, 10.0, 175.0, 0.0, true, true, false, true, 1, true)

			ESX.Streaming.RequestAnimDict('mp_player_intdrink', function()
				TaskPlayAnim(playerPed, 'mp_player_intdrink', 'loop_bottle', 1.0, -1.0, 2000, 0, 1, true, true, true)

				Citizen.Wait(3000)
				IsAnimated = false
				ClearPedSecondaryTask(playerPed)
				DeleteObject(prop)
			end)
		end)

	end
end)

I added a print to console when script tries to populate the menu with items.


Also edited the client script some more. There was some issues with how it was detecting props.

local Keys = {
	["ESC"] = 322, ["F1"] = 288, ["F2"] = 289, ["F3"] = 170, ["F5"] = 166, ["F6"] = 167, ["F7"] = 168, ["F8"] = 169, ["F9"] = 56, ["F10"] = 57,
	["~"] = 243, ["1"] = 157, ["2"] = 158, ["3"] = 160, ["4"] = 164, ["5"] = 165, ["6"] = 159, ["7"] = 161, ["8"] = 162, ["9"] = 163, ["-"] = 84, ["="] = 83, ["BACKSPACE"] = 177,
	["TAB"] = 37, ["Q"] = 44, ["W"] = 32, ["E"] = 38, ["R"] = 45, ["T"] = 245, ["Y"] = 246, ["U"] = 303, ["P"] = 199, ["["] = 39, ["]"] = 40, ["ENTER"] = 18,
	["CAPS"] = 137, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["F"] = 23, ["G"] = 47, ["H"] = 74, ["K"] = 311, ["L"] = 182,
	["LEFTSHIFT"] = 21, ["Z"] = 20, ["X"] = 73, ["C"] = 26, ["V"] = 0, ["B"] = 29, ["N"] = 249, ["M"] = 244, [","] = 82, ["."] = 81,
	["LEFTCTRL"] = 36, ["LEFTALT"] = 19, ["SPACE"] = 22, ["RIGHTCTRL"] = 70,
	["HOME"] = 213, ["PAGEUP"] = 10, ["PAGEDOWN"] = 11, ["DELETE"] = 178,
	["LEFT"] = 174, ["RIGHT"] = 175, ["TOP"] = 27, ["DOWN"] = 173,
	["NENTER"] = 201, ["N4"] = 108, ["N5"] = 60, ["N6"] = 107, ["N+"] = 96, ["N-"] = 97, ["N7"] = 117, ["N8"] = 61, ["N9"] = 118
}

ESX								= nil
local cam						= -1
local HasAlreadyEnteredMarker 	= false
local CurrentStand 				= nil
local CurrentAction          	= nil
local CurrentActionData     	= {}
local LastZone                	= nil

Citizen.CreateThread(function()
	while ESX == nil do
		TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
		Citizen.Wait(0)
	end

	Citizen.Wait(5000)

	ESX.TriggerServerCallback('tresh_fstands_N_vmachines:requestDBItems', function(ShopItems)
		for k,v in pairs(ShopItems) do
			Config.Zones[k].Items = v
			print(Items)
		end
	end)
end)

function OpenStandMenu(zone)
	local elements = {}
	for i=1, #Config.Zones[zone].Items, 1 do
		local item = Config.Zones[zone].Items[i]

		if item.limit == -1 then
			item.limit = 1
		end

		table.insert(elements, {
			label      = ('%s - <span style="color:green;">%s</span>'):format(item.label, _U('stand_item', ESX.Math.GroupDigits(item.price))),
			label_real = item.label,
			item       = item.item,
			price      = item.price,

			-- menu properties
			value      = 1,
			type       = 'slider',
			min        = 1,
			max        = item.limit
		})
	end

	ESX.UI.Menu.CloseAll()
	ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'stand', {
		title    = _U('stand'),
		align    = 'bottom-right',
		elements = elements
	}, function(data, menu)
		ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'stand_confirm', {
			title    = _U('stand_confirm', data.current.value, data.current.label_real, ESX.Math.GroupDigits(data.current.price * data.current.value)),
			align    = 'bottom-right',
			elements = {
				{label = _U('no'),  value = 'no'},
				{label = _U('yes'), value = 'yes'}
			}
		}, function(data2, menu2)
			if data2.current.value == 'yes' then
				TriggerServerEvent('tresh_fstands_N_vmachines:buyItem', data.current.item, data.current.value, zone)
			end

			menu2.close()
			menu.close()
		end, function(data2, menu2)
			menu2.close()
		end)
	end, function(data, menu)
		menu.close()

		CurrentAction     = 'stand_menu'
		CurrentActionData = {zone = zone}
	end)
end

AddEventHandler('tresh_fstands_N_vmachines:hasEnteredMarker', function(zone)
	CurrentAction     = 'stand_menu'
	CurrentActionData = {zone = zone}
end)

AddEventHandler('tresh_fstands_N_vmachines:hasExitedMarker', function(zone)
	CurrentAction = nil
	ESX.UI.Menu.CloseAll()
end)



Citizen.CreateThread(function() --Detects if he is near a booth
	while true do
		Citizen.Wait(1000)

			local PlayerPed = PlayerPedId()
			local PlayerCoords = GetEntityCoords(PlayerPed)
			local currentZone = nil
			local isInMarker  = false

			for k,v in pairs(Config.Zones) do
				local ClosestStand = GetClosestObjectOfType(PlayerCoords, 1.5, GetHashKey(v.Stand), false, false)

				if ClosestStand ~= 0 and ClosestStand ~= nil then
					CurrentStand = ClosestStand
					isInMarker  = true
					ShopItems 	= v.Items
					currentZone = k
					LastZone    = k
					break
				else
					CurrentStand = nil
				end
			end
			if isInMarker and not HasAlreadyEnteredMarker then
				HasAlreadyEnteredMarker = true
				TriggerEvent('tresh_fstands_N_vmachines:hasEnteredMarker', currentZone)
			end
			if not isInMarker and HasAlreadyEnteredMarker then
				HasAlreadyEnteredMarker = false
				TriggerEvent('tresh_fstands_N_vmachines:hasExitedMarker', LastZone)
			end
	end
end)

Citizen.CreateThread(function() -- if he's near a booth then we put him the notif
	while true do
		Citizen.Wait(5)

		if CurrentStand ~= nil then
			
			local StandCoord = GetEntityCoords(CurrentStand)

			ESX.ShowHelpNotification(_U('stand_prompt'))
		end
	end
end)

-- Key Controls
Citizen.CreateThread(function()
	while true do
		Citizen.Wait(5)
		if CurrentAction ~= nil then
			if CurrentStand ~= nil and IsControlJustReleased(0, 38) then
				if CurrentAction == 'stand_menu' then
					OpenStandMenu(CurrentActionData.zone)
				end

				CurrentAction = nil
			end
		else
			Citizen.Wait(500)
		end
	end
end)

-- Function Message en bas
function DrawMissionText(msg, time)
  	ClearPrints()
  	SetTextEntry_2('STRING')
  	AddTextComponentString(msg)
  	DrawSubtitleTimed(time, 1)
end

RegisterNetEvent('tresh_fstands_N_vmachines:onDrink')
AddEventHandler('tresh_fstands_N_vmachines:onDrink', function(prop_name)
	if not IsAnimated then
		prop_name = prop_name or 'p_ing_coffeecup_01'
		IsAnimated = true

		Citizen.CreateThread(function()
			local playerPed = PlayerPedId()
			local x,y,z = table.unpack(GetEntityCoords(playerPed))
			local prop = CreateObject(GetHashKey(prop_name), x, y, z + 0.2, true, true, true)
			local boneIndex = GetPedBoneIndex(playerPed, 18905)
			AttachEntityToEntity(prop, playerPed, boneIndex, 0.12, 0.028, 0.001, 10.0, 175.0, 0.0, true, true, false, true, 1, true)

			ESX.Streaming.RequestAnimDict('mp_player_intdrink', function()
				TaskPlayAnim(playerPed, 'mp_player_intdrink', 'loop_bottle', 1.0, -1.0, 2000, 0, 1, true, true, true)

				Citizen.Wait(3000)
				IsAnimated = false
				ClearPedSecondaryTask(playerPed)
				DeleteObject(prop)
			end)
		end)

	end
end)

I just don’t understand why this script is having problems while others are fine communicating with esx_menu_default

I mean, I’ve been wondering why you’re using a zone to identify if they player can open the menu.

You could just use the prop name vs ped distance to validate if the player can open the menu.

Because all the old esx scrpits use pre-defined logic of a static zone, building on a more dynamic approach is what I have been doing with prop entity distance vs player ped entity distance.

Have a look at esx_solpare or what ever it is called. The dumpster diving one.

1 Like

Ok thanks, will definitely have a look.

The zones are so I can have multiple shops within the same script. I have about 5 different stand props that I want to sell different items.

I’m new to scripting, so I found scripts that did what I wanted to do and used them as templates.

Figured it out! Turns out that the resource file is pretty important too lol.

Forgot to add ‘@mysql-async/lib/MySQL.lua’ to the server scripts. I’m new :shushing_face:


This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.