Bt-target (forked, updated functionality)

Hello friends,

Over the past two months I’ve been using bt-target as my primary interaction method for a server I’ve been building and I’ve made many changes to bt-target that I feel improve upon the original functionality of it.

For those of you who haven’t checked out bt-target, I highly recommend doing so. It was made by BrentN5 over at GitHub - brentN5/bt-target and many people use it to this day. It allows you to hold a button to reveal interactions around you.

It got to a point where I decided I should at the very least release my fork publicly as you too can use these additional benefits and features of bt-target. It’s been optimised slightly and the way it works has been changed quite significantly, however there should be no “breaking” changes to this.

I would like to preface this release by stating that there are two important pre-requisites for this fork of bt-target:

Github:

Summary of my changes/additions:

  • All data from the original option is passed through to the event as a data table (see examples)
  • Vehicle bone support - I originally merged in a version from another pull request but didn’t like how it worked, so I rewrote most of it. It’s not perfect but it sort of works.
  • Built in disabling of the ability to attack/combat mode while holding the interact key.
  • Job checks have been made a per-option check, with built in grade checking (see examples)
  • Added support for networked Entity bt-targets. This basically allows you to attach a bt-target option to a spawned entity (such as a job vehicle) and it’ll remain until the entity is despawned.
  • Added support for EntityZones - they work similar to EntityTarget but with a zone around the object. Useful for tricky models or things that don’t move.
  • Pass any data through an option to the target event, including the entity you interacted with by default.
  • Added a required_item check that hooks into linden_inventory and checks if you have the item in your inventory before displaying the option.
  • The piece de la resistance - added a canInteract() function for you to do any code check you want at the time of interaction.

Installation instructions

Installation is very straightforward but as we use a few custom imports for performance you’ll want to include them yourself:

In es_extended\imports.lua Add the following to the bottom of the file (if it does not already exist):


------------------------------------------------------------------------

-- SHARED

------------------------------------------------------------------------

local Intervals = {}

local CreateInterval = function(name, interval, action, clear)

    local self = {interval = interval}

    CreateThread(function()

        local name, action, clear = name, action, clear

        repeat

            action()

            Citizen.Wait(self.interval)

        until self.interval == -1

        if clear then clear() end

        Intervals[name] = nil

    end)

    return self

end

SetInterval = function(name, interval, action, clear)

    if Intervals[name] and interval then Intervals[name].interval = interval

    else

        Intervals[name] = CreateInterval(name, interval, action, clear)

    end

end

ClearInterval = function(name)

    Intervals[name].interval = -1

end

Examples

Passing Item Data / AddTargetModel


local coffee = {

    690372739,

}

exports['bt-target']:AddTargetModel(coffee, {

    options = {

        {

            event = "coffee:buy",

            icon = "fas fa-coffee",

            label = "Coffee",

            item = "coffee",

            price = 5,

        },

    },

    distance = 2.5

})

RegisterNetEvent('coffee:buy')

AddEventHandler('coffee:buy',function(data)

    ESX.ShowNotification("You purchased a " .. data.item .. " for $" .. data.price .. ". Enjoy!")

    -- server event to buy the item here

end)

AddTargetModel requires a table of model hashes to function correctly. You can use the direct hash number: 690372739 or direct object name with backticks: prop_vend_coffe_01.

Define a table like:


local coffee = {

    `prop_vend_coffe_01`,

    `p_ld_coffee_vend_01`

}

Checking job / BoxZone


exports['bt-target']:AddBoxZone("MissionRowDutyClipboard", vector3(441.7989, -982.0529, 30.67834), 0.45, 0.35, {

    name="MissionRowDutyClipboard",

    heading=11.0,

    debugPoly=false,

    minZ=30.77834,

    maxZ=30.87834,

    }, {

        options = {

            {

                event = "qrp_duty:goOnDuty",

                icon = "fas fa-sign-in-alt",

                label = "Sign In",

                job = "police",

            },

            {

                event = "qrp_duty:goOffDuty",

                icon = "fas fa-sign-out-alt",

                label = "Sign Out",

                job = "police",

            },

        },

        distance = 3.5

})

In this example I’m defining police as a straight string to check if the player interacting has that job before showing the option. You can also define it as [key] = value pairs for grade, for example:


job = {

    ["police"] = 5, -- minimum grade required to see this option

    ["ambulance"] = 0,

}

Removing Zones

You can and SHOULD remove zones in various situations, as a rule I tend to remove a zone with the same name before creating one to prevent clutter, and it helps if you’re using debugPoly.


exports['bt-target']:RemoveZone("MissionRowDutyClipboard")

Just make sure you’re using the exact name of the zone you defined.

CanInteract / EntityZone

In this example we’re making a specifc zone around an entity that was created. This can help if you can’t interact with a particular entity, but also don’t have a static location to define.

The EntityZone is typically removed with the entity.

The canInteract() function defined here allows you to do any particular checks you want to be executed at the time of interaction, in this instance, it checks if the plant has a statebag defined for growth that is higher than or equal to 100.

You can do any check you want as long as you return true or false.


local playerPed = PlayerPedId()

local coords = GetEntityCoords(playerPed)

model = `prop_plant_fern_02a`

RequestModel(model)

while not HasModelLoaded(model) do

    Citizen.Wait(50)

end

local plant = CreateObject(model, coords.x, coords.y, coords.z, true, true)

Citizen.Wait(50)

PlaceObjectOnGroundProperly(plant)

SetEntityInvincible(plant, true)

exports['bt-target']:AddEntityZone("potato-growing-"..plant, plant, {

    name = "potato-growing-"..plant,

    heading=GetEntityHeading(plant),

    debugPoly=false,

}, {

    options = {

    {

        event = "farming:harvestPlant",

        icon = "fa-solid fa-scythe",

        label = "Harvest potato",

        plant = plant,

        job = "farmer",

        canInteract = function()

            if Entity(plant).state.growth >= 100 then 

                return true

            else 

                return false

            end 

        end,

    },

},

    distance = 3.5

})

AddTargetEntity

You can also define an entity target that only that entity will have the interaction. This is very simular to EntityZone except attached to the network entity instead of a static zone.


exports['bt-target']:AddTargetEntity(NetworkGetNetworkIdFromEntity(vehicle), {

    options = {

        {

            event = "postop:getPackage",

            icon = "fa-solid fa-box-circle-check",

            label = "Get Package",

            owner = NetworkGetNetworkIdFromEntity(PlayerPedId()),

            job = "postop",

        },

    },

    distance = 3.0

})

AddTargetBone

Use this in conjunction with the bone index in the config.lua file to define bones you would like to interact with on a vehicle.


    exports['bt-target']:AddTargetBone({"boot"}, {

    options = {

        {

            event = "qrp_vehicle:toggleVehicleDoor",

            icon = "fas fa-door-open",

            label = "Toggle Boot",

            door = 5,

        },

    },

    distance = 1.5

    })

13 Likes

vouch, noms’s version of bt-target is alot “better” than the original.

is it really mandatory to use this inventory and this es_extended? I already have my customized versions would it be possible and difficult to adapt your bt target to another inventory or es_extended?

1 Like

Anyone who knows what they’re doing can easily remove the requirement for linden_inventory, just look for the exports for linden_inventory and replace them with an equiv loop to check inventory.

hummm sounds simple, so linden’s only dependency is the item check export ?

Pretty much yeah. I’m not 100% sure what specifically is required from his version of ESX Legacy but I listed it as a dependency just to be safe, but you will need ESX Legacy as it uses the imports.

if (b.required_item == nil) or (b.required_item and exports['linden_inventory']:CountItems(b.required_item)[b.required_item] > 0) then
What this export is doing
InventorySearch = function(search, item, metadata)
	if item then
		if type(item) == 'string' then item = {item} end
		if type(metadata) == 'string' then metadata = {type=metadata} end
		local returnData = {}
		for i=1, #item do
			local item = string.lower(item[i])
			if item:find('weapon_') then item = string.upper(item) end
			if search == 1 then returnData[item] = {}
			elseif search == 2 then returnData[item] = 0 end
			for k, v in pairs(ESX.PlayerData.inventory) do
				if v.name == item then
					if not v.metadata then v.metadata = {} end
					if not metadata or func.tablecontains(v.metadata, metadata) then
						if search == 1 then table.insert(returnData[item], ESX.PlayerData.inventory[v.slot])
						elseif search == 2 then
							returnData[item] = returnData[item] + v.count
						end
					end
				end
			end
		end
		if next(returnData) then return returnData end
	end
	return false
end
Since you’re likely only looking for one item in most cases, a standard replacement for ESX is
if (b.required_item == nil) or (b.required_item and ItemCount(b.required_item) > 0) then
ItemCount = function(item)
	local item, count = 'water', 0
	for k, v in pairs(ESX.GetPlayerData().inventory) do
		if v.name == item then
			return v.count
		end
	end
	return 0
end
2 Likes

why not just make a pr to the main repo and improve it like that?

Because there’s been a pull-request on the main repository for the last two months that’s just been sat there, for a small change, and this is a lot of changes, so I’d rather just release the fork so people can use it.

this is happenng please help


i

1 Like

Installation instructions

Installation is very straightforward but as we use a few custom imports for performance you’ll want to include them yourself:

In es_extended\imports.lua Add the following to the bottom of the file (if it does not already exist):


------------------------------------------------------------------------

-- SHARED

------------------------------------------------------------------------

local Intervals = {}

local CreateInterval = function(name, interval, action, clear)

    local self = {interval = interval}

    CreateThread(function()

        local name, action, clear = name, action, clear

        repeat

            action()

            Citizen.Wait(self.interval)

        until self.interval == -1

        if clear then clear() end

        Intervals[name] = nil

    end)

    return self

end

SetInterval = function(name, interval, action, clear)

    if Intervals[name] and interval then Intervals[name].interval = interval

    else

        Intervals[name] = CreateInterval(name, interval, action, clear)

    end

end

ClearInterval = function(name)

    Intervals[name].interval = -1

end

Read. The. Instructions.

The only issue I have with all Bt-target is, when there are 2 overlapping zones, it always scuffs the target eye. And it won’t open again.

sounds resonable

can you do a job check on a AddCircleZone ? how does that work, couldn’t do it

1 Like

so add them to the same zone ?

How would i create a “AddTargetBone” on a defined vehicle, that will only work on that specific vehicle?
(delivery job, with a truck)

edit:
Maybe like AddTrargetEntity and AddTargetBone together

I am trying to use the canInteract function, but it seems something is wrong

if IsEntityAVehicle(entity) then 

                local bone, hitDistanceToBone, boneIndex, bonePos = FindNearestVehicleBoneToRayCast(entity,coords,hit)

                local distanceToBone = #(bonePos - plyCoords)

                if (bone ~= nil and Bones[bone] ~= nil and distanceToBone ~= nil) and distanceToBone <= Bones[bone]["distance"] then 

                    local options = Bones[bone]["options"]

                    local send_options = {}

                    for l,b in pairs(options) do 

                        if b.job == "all" or b.job == PlayerJob then    

                                if b.canInteract == nil or b.canInteract() then 

                                    local slot = #send_options + 1

                                    send_options[slot] = b

                                    send_options[slot].entity = entity

                                end

                        end

                    end

AddTargetBone(car, {–538.7338, -180.4917, 54.48687

        options = {

            {

                event = "fixcar",

                icon = "fas fa-wrench",

                label = "修車",

                job = "all",

                canInteract = function()

                    if yesyoucan then

                        return true 

                    end

                    return false

                end

            },

        {

            event = "refuelcar123",

            icon = "fas fa-gas-pump",

            label = "加油",

            job = "all",

    

        },

    },

    distance = 2

    })

end)
    yesyoucanp = {

        {538.7338, -180.4917, 54.48687},

    }


    Citizen.CreateThread(function ()

        yesyoucan = false

        while true do

            Citizen.Wait(100)                

                local dstchecked = 1000

    

                for i = 1, #yesyoucanp do

                    garageCoords2 = yesyoucanp[i]

    

                    local comparedst = #(GetEntityCoords(PlayerPedId()) - vector3( garageCoords2[1], garageCoords2[2], garageCoords2[3]))

                    if comparedst < dstchecked then

                        dstchecked = comparedst

                    end

                    if #(GetEntityCoords(PlayerPedId()) - vector3(garageCoords2[1], garageCoords2[2], garageCoords2[3])) < 15 then

                     yesyoucan = true

                    else

                    yesyoucan = false

                    end 

                end

    

                if dstchecked > 50 then

                    Citizen.Wait(math.ceil(dstchecked * 10))

                end

        end

    end)

it works for extended 1.1 and 1.2 just add the imports.lua yourself guys to your extended il upload the file and add it to your manifest
imports.lua (1.1 KB)

and this to your fxmanifest
files {
‘imports.lua’
}

1 Like

does anyone know what hospital check in script that is

This is dope!