Skip to content

Commit 1fabd0b

Browse files
feat: hotwiring with lockpick (#62)
* feat: hotwiring with lockpick * feat: resolve qb-vehiclekeys:client:GiveKeys conflict
1 parent 8d6cab9 commit 1fabd0b

File tree

5 files changed

+110
-62
lines changed

5 files changed

+110
-62
lines changed

client/functions.lua

+78-33
Original file line numberDiff line numberDiff line change
@@ -109,31 +109,38 @@ local function isVehicleInRange(vehicle, maxDistance)
109109
end
110110
end
111111

112-
---Will be execuded when the opening of the lock succeeds.
113-
---@param vehicle number The entity number of the vehicle.
114-
---@param plate string The plate number of the vehicle.
115-
local function lockpickSuccessCallback(vehicle, plate)
116-
TriggerServerEvent('hud:server:GainStress', math.random(1, 4))
117-
118-
if cache.seat == -1 then
119-
TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys', plate)
112+
---Chance to destroy lockpick
113+
---@param isAdvancedLockedpick any
114+
---@param vehicleClass any
115+
local function breakLockpick(isAdvancedLockedpick, vehicleClass)
116+
local chance = math.random()
117+
if isAdvancedLockedpick then -- there is no benefit to using an advanced tool in the default configuration.
118+
if chance <= config.removeAdvancedLockpickChance[vehicleClass] then
119+
TriggerServerEvent("qb-vehiclekeys:server:breakLockpick", "advancedlockpick")
120+
end
120121
else
121-
exports.qbx_core:Notify(locale("notify.vehicle_lockedpick"), 'success')
122-
TriggerServerEvent('qb-vehiclekeys:server:setVehLockState', NetworkGetNetworkIdFromEntity(vehicle), 1)
123-
Entity(vehicle).state.isOpen = true
122+
if chance <= config.removeNormalLockpickChance[vehicleClass] then
123+
TriggerServerEvent("qb-vehiclekeys:server:breakLockpick", "lockpick")
124+
end
124125
end
125126
end
126127

128+
---Will be executed when the lock opening is successful.
129+
---@param vehicle number The entity number of the vehicle.
130+
local function lockpickSuccessCallback(vehicle)
131+
TriggerServerEvent('hud:server:GainStress', math.random(1, 4))
132+
exports.qbx_core:Notify(locale("notify.vehicle_lockedpick"), 'success')
133+
TriggerServerEvent('qb-vehiclekeys:server:setVehLockState', NetworkGetNetworkIdFromEntity(vehicle), 1)
134+
Entity(vehicle).state.isOpen = true
135+
end
136+
127137
---Operations done after the LockpickDoor quickevent done.
128138
---@param vehicle number The entity number of the vehicle.
129-
---@param plate string The plate number of the vehicle.
130139
---@param isAdvancedLockedpick boolean Determines whether an advanced lockpick was used.
131-
---@param maxDistance number The max distance to check.
132140
---@param isSuccess boolean? Determines whether the lock has been successfully opened.
133-
local function lockpickCallback(vehicle, plate, isAdvancedLockedpick, maxDistance, isSuccess)
134-
if not isVehicleInRange(vehicle, maxDistance) then return end -- the action will be aborted if the opened vehicle is too far.
141+
local function lockpickCallback(vehicle, isAdvancedLockedpick, isSuccess)
135142
if isSuccess then
136-
lockpickSuccessCallback(vehicle, plate)
143+
lockpickSuccessCallback(vehicle)
137144
else -- if player fails quickevent
138145
public.attemptPoliceAlert('carjack')
139146
SetVehicleAlarm(vehicle, false)
@@ -142,20 +149,10 @@ local function lockpickCallback(vehicle, plate, isAdvancedLockedpick, maxDistanc
142149
exports.qbx_core:Notify(locale('notify.failed_lockedpick'), 'error')
143150
end
144151

145-
local chance = math.random()
146-
if isAdvancedLockedpick then -- there is no benefit to using an advanced tool at this moment.
147-
if chance <= config.removeAdvancedLockpickChance[GetVehicleClass(vehicle)] then
148-
TriggerServerEvent("qb-vehiclekeys:server:breakLockpick", "advancedlockpick")
149-
end
150-
else
151-
if chance <= config.removeNormalLockpickChance[GetVehicleClass(vehicle)] then
152-
TriggerServerEvent("qb-vehiclekeys:server:breakLockpick", "lockpick")
153-
end
154-
end
152+
breakLockpick(isAdvancedLockedpick, GetVehicleClass(vehicle))
155153
end
156154

157155
local islockpickingProcessLocked = false -- lock flag
158-
159156
---Lockpicking quickevent.
160157
---@param isAdvancedLockedpick boolean Determines whether an advanced lockpick was used
161158
---@param maxDistance number? The max distance to check.
@@ -167,6 +164,7 @@ function public.lockpickDoor(isAdvancedLockedpick, maxDistance, customChallenge)
167164

168165
if not vehicle then return end
169166

167+
local class = GetVehicleClass(vehicle)
170168
local plate = qbx.getVehiclePlate(vehicle)
171169
local isDriverSeatFree = IsVehicleSeatFree(vehicle, -1)
172170

@@ -178,25 +176,72 @@ function public.lockpickDoor(isAdvancedLockedpick, maxDistance, customChallenge)
178176
or Entity(vehicle).state.isOpen -- the lock is locked
179177
or not isCloseToAnyBone(pedCoords, vehicle, doorBones, maxDistance) -- the player's ped is close enough to the driver's door
180178
or GetVehicleDoorLockStatus(vehicle) < 2 -- the vehicle is locked
179+
or (not isAdvancedLockedpick and config.advancedLockpickVehicleClasses[class])
181180
then return end
182181

183182
if islockpickingProcessLocked then return end -- start of the critical section
184-
185183
islockpickingProcessLocked = true -- one call per player at a time
186184

187185
CreateThread(function()
188-
-- lock opening animation
189-
lib.requestAnimDict('veh@break_in@0h@p_m_one@')
190-
TaskPlayAnim(cache.ped, 'veh@break_in@0h@p_m_one@', "low_force_entry_ds", 3.0, 3.0, -1, 16, 0, false, false, false)
191-
186+
lib.playAnim(cache.ped, 'veh@break_in@0h@p_m_one@', "low_force_entry_ds", 3.0, 3.0, -1, 16, 0, false, false, false) -- lock opening animation
192187
local isSuccess = customChallenge or lib.skillCheck({ 'easy', 'easy', { areaSize = 60, speedMultiplier = 1 }, 'medium' }, { '1', '2', '3', '4' })
193-
lockpickCallback(vehicle, plate, isAdvancedLockedpick, maxDistance, isSuccess)
188+
189+
if isVehicleInRange(vehicle, maxDistance) then -- the action will be aborted if the opened vehicle is too far.
190+
lockpickCallback(vehicle, isAdvancedLockedpick, isSuccess)
191+
end
192+
194193
Wait(config.lockpickCooldown)
195194
end)
196195

197196
islockpickingProcessLocked = false -- end of the critical section
198197
end
199198

199+
---Will be executed when the lock opening is successful.
200+
---@param vehicle number The entity number of the vehicle.
201+
local function hotwireSuccessCallback(vehicle)
202+
local plate = qbx.getVehiclePlate(vehicle)
203+
TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys', plate)
204+
end
205+
206+
---Operations done after the LockpickDoor quickevent done.
207+
---@param vehicle number The entity number of the vehicle.
208+
---@param isAdvancedLockedpick boolean Determines whether an advanced lockpick was used.
209+
---@param isSuccess boolean? Determines whether the lock has been successfully opened.
210+
local function hotwireCallback(vehicle, isAdvancedLockedpick, isSuccess)
211+
if isSuccess then
212+
hotwireSuccessCallback(vehicle)
213+
else -- if player fails quickevent
214+
public.attemptPoliceAlert('carjack')
215+
TriggerServerEvent('hud:server:GainStress', math.random(1, 4))
216+
exports.qbx_core:Notify(locale('notify.failed_lockedpick'), 'error')
217+
end
218+
219+
breakLockpick(isAdvancedLockedpick, GetVehicleClass(vehicle))
220+
end
221+
222+
local isHotwiringProcessLocked = false -- lock flag
223+
---Hotwiring with a tool quickevent.
224+
---@param isAdvancedLockedpick boolean Determines whether an advanced lockpick was used
225+
---@param customChallenge boolean? lockpick challenge
226+
function public.hotwire(isAdvancedLockedpick, customChallenge)
227+
if not(cache.vehicle
228+
and cache.seat
229+
and cache.seat == -1)
230+
then return end
231+
232+
if isHotwiringProcessLocked then return end -- start of the critical section
233+
isHotwiringProcessLocked = true -- one call per player at a time
234+
235+
CreateThread(function()
236+
lib.playAnim(cache.ped, 'veh@break_in@0h@p_m_one@', "low_force_entry_ds", 3.0, 3.0, -1, 16, 0, false, false, false) -- lock opening animation
237+
local isSuccess = customChallenge or lib.skillCheck({ 'easy', 'easy', { areaSize = 60, speedMultiplier = 1 }, 'medium' }, { '1', '2', '3', '4' })
238+
hotwireCallback(cache.vehicle, isAdvancedLockedpick, isSuccess)
239+
Wait(config.lockpickCooldown)
240+
end)
241+
242+
isHotwiringProcessLocked = false -- end of the critical section
243+
end
244+
200245
---Get a vehicle in the players scope by the plate
201246
---@param plate string
202247
---@return integer?

client/main.lua

+27-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local config = require 'config.client'
66
local functions = require 'client.functions'
77

88
local hasKeys = functions.hasKeys
9+
local hotwire = functions.hotwire
910
local lockpickDoor = functions.lockpickDoor
1011
local attemptPoliceAlert = functions.attemptPoliceAlert
1112
local isBlacklistedWeapon = functions.isBlacklistedWeapon
@@ -29,7 +30,11 @@ local canCarjack = true
2930
local function giveKeys(id, plate)
3031
local distance = #(GetEntityCoords(cache.ped) - GetEntityCoords(GetPlayerPed(GetPlayerFromServerId(id))))
3132
if distance < 3 then
33+
if not hasKeys(plate) then
34+
return exports.qbx_core:Notify(locale('notify.no_keys'))
35+
end
3236
TriggerServerEvent('qb-vehiclekeys:server:GiveVehicleKeys', id, plate)
37+
exports.qbx_core:Notify(locale('notify.gave_keys'))
3338
else
3439
exports.qbx_core:Notify(locale('notify.not_near'), 'error')
3540
end
@@ -137,7 +142,7 @@ local function makePedFlee(ped)
137142
TaskReactAndFleePed(ped, cache.ped)
138143
end
139144

140-
local function hotwire(vehicle, plate)
145+
local function findKeys(vehicle, plate)
141146
local hotwireTime = math.random(config.minHotwireTime, config.maxHotwireTime)
142147
isHotwiring = true
143148

@@ -251,16 +256,15 @@ local function carjackVehicle(target)
251256
isCarjacking = false
252257
Wait(2000)
253258
attemptPoliceAlert('carjack')
254-
Wait(config.delayBetweenCarjackingsInMs)
255-
canCarjack = true
256259
end
257260
else
258261
ClearPedTasksImmediately(target)
259262
makePedFlee(target)
260263
isCarjacking = false
261-
Wait(config.delayBetweenCarjackingsInMs)
262-
canCarjack = true
263264
end
265+
266+
Wait(config.delayBetweenCarjackingsInMs)
267+
canCarjack = true
264268
end
265269

266270
-----------------------
@@ -341,7 +345,7 @@ CreateThread(function()
341345
SetVehicleEngineOn(cache.vehicle, false, false, true)
342346

343347
if IsControlJustPressed(0, 74) then
344-
hotwire(cache.vehicle, plate)
348+
findKeys(cache.vehicle, plate)
345349
end
346350
end
347351
end
@@ -406,22 +410,29 @@ RegisterNetEvent('qb-vehiclekeys:client:GiveKeys', function(id, plate)
406410

407411
if id and type(id) == 'number' then -- Give keys to specific ID
408412
giveKeys(id, targetPlate)
409-
else
410-
if IsPedSittingInVehicle(cache.ped, targetVehicle) then -- Give keys to everyone in vehicle
411-
local otherOccupants = getOtherPlayersInVehicle(targetVehicle)
412-
for p = 1, #otherOccupants do
413-
TriggerServerEvent('qb-vehiclekeys:server:GiveVehicleKeys', otherOccupants[p], targetPlate)
414-
end
415-
else -- Give keys to closest player
416-
local playerId = lib.getClosestPlayer(GetEntityCoords(cache.ped), 3, false)
417-
giveKeys(playerId, targetPlate)
413+
elseif IsPedSittingInVehicle(cache.ped, targetVehicle) then -- Give keys to everyone in vehicle
414+
local otherOccupants = getOtherPlayersInVehicle(targetVehicle)
415+
if not hasKeys(qbx.getVehiclePlate(targetVehicle)) then
416+
return exports.qbx_core:Notify(locale('notify.no_keys'))
418417
end
418+
419+
for p = 1, #otherOccupants do
420+
TriggerServerEvent('qb-vehiclekeys:server:GiveVehicleKeys', otherOccupants[p], targetPlate)
421+
end
422+
exports.qbx_core:Notify(locale('notify.gave_keys'))
423+
else -- Give keys to closest player
424+
local playerId = lib.getClosestPlayer(GetEntityCoords(cache.ped), 3, false)
425+
giveKeys(playerId, targetPlate)
419426
end
420427
end
421428
end)
422429

423430
RegisterNetEvent('lockpicks:UseLockpick', function(isAdvanced)
424-
lockpickDoor(isAdvanced)
431+
if cache.vehicle then
432+
hotwire(isAdvanced)
433+
else
434+
lockpickDoor(isAdvanced)
435+
end
425436
end)
426437

427438
--#region Backwards Compatibility ONLY -- Remove at some point --

config/client.lua

+5
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ return {
101101
[VehicleClasses.OPEN_WHEEL] = 0.5
102102
},
103103

104+
advancedLockpickVehicleClasses = { -- The vehicle classes can only be opened with an advanced lockpick
105+
[VehicleClasses.HELICOPTERS] = true,
106+
[VehicleClasses.MILITARY] = true
107+
},
108+
104109
-- Carjack Settings
105110
carjackEnable = true, -- Enables the ability to carjack pedestrian vehicles, stealing them by pointing a weapon at them
106111
carjackingTimeInMs = 7500, -- Time it takes to successfully carjack in miliseconds

server/functions.lua

-4
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,6 @@ function public.removeKeys(source, plate)
106106
return true
107107
end
108108

109-
function public.hasKeys(source, plate)
110-
return Player(source).state.keysList[plate]
111-
end
112-
113109
---Gives the user the keys to the vehicle
114110
---@param source number ID of the player
115111
---@param plate string The plate number of the vehicle.

server/main.lua

-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
local functions = require 'server.functions'
66

7-
local hasKeys = functions.hasKeys
87
local giveKeys = functions.giveKeys
98
local addPlayer = functions.addPlayer
109
local removePlayer = functions.removePlayer
@@ -16,21 +15,13 @@ local removePlayer = functions.removePlayer
1615
-- Event to give keys. receiver can either be a single id, or a table of ids.
1716
-- Must already have keys to the vehicle, trigger the event from the server, or pass forcegive paramter as true.
1817
RegisterNetEvent('qb-vehiclekeys:server:GiveVehicleKeys', function(receiver, plate)
19-
local giver = source
20-
21-
if not hasKeys(giver, plate) then
22-
return exports.qbx_core:Notify(giver, locale('notify.no_keys'))
23-
end
24-
2518
if type(receiver) == 'table' then
2619
for i = 1, receiver do
2720
giveKeys(receiver[i], plate)
2821
end
2922
else
3023
giveKeys(receiver, plate)
3124
end
32-
33-
exports.qbx_core:Notify(giver, locale('notify.gave_keys'))
3425
end)
3526

3627
RegisterNetEvent('qb-vehiclekeys:server:AcquireVehicleKeys', function(plate)

0 commit comments

Comments
 (0)