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:
-
Linden Inventory (Latest version): GitHub - thelindat/linden_inventory: Advanced Inventory for ESX Legacy
-
Linden ESX Legacy (es_extended) (Latest): GitHub - thelindat/es_extended: ESX Legacy for use with linden_inventory AND follow instructions
-
linden_inventory - If you’re not already using this, you should be. It’s probably one of the most actively updated inventories, it’s free, and the current developer is a lovely guy. We use some exports from his inventory resource to check item counts in your inventory.
-
ESX Legacy - This does not support 1.1 final or 1.2 - only ESX Legacy. Feel free to test and adapt it; but it makes use of a few helper functions provided by ESX Legacy.
-
a brain - I’m happy to provide support for this resource but I will not provide support if you haven’t done the bare minimum of searching the forums or other help resources for assistance, or if you haven’t read the examples provided on the github.
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
})