Skip to content

Commit

Permalink
feat: hotwiring with lockpick
Browse files Browse the repository at this point in the history
  • Loading branch information
artur-michalak committed Jun 12, 2024
1 parent 90d76b3 commit c9c3023
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 62 deletions.
111 changes: 78 additions & 33 deletions client/functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,31 +109,38 @@ local function isVehicleInRange(vehicle, maxDistance)
end
end

---Will be execuded when the opening of the lock succeeds.
---@param vehicle number The entity number of the vehicle.
---@param plate string The plate number of the vehicle.
local function lockpickSuccessCallback(vehicle, plate)
TriggerServerEvent('hud:server:GainStress', math.random(1, 4))

if cache.seat == -1 then
TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys', plate)
---Chance to destroy lockpick
---@param isAdvancedLockedpick any
---@param vehicleClass any
local function breakLockpick(isAdvancedLockedpick, vehicleClass)
local chance = math.random()
if isAdvancedLockedpick then -- there is no benefit to using an advanced tool in the default configuration.
if chance <= config.removeAdvancedLockpickChance[vehicleClass] then
TriggerServerEvent("qb-vehiclekeys:server:breakLockpick", "advancedlockpick")
end
else
exports.qbx_core:Notify(locale("notify.vehicle_lockedpick"), 'success')
TriggerServerEvent('qb-vehiclekeys:server:setVehLockState', NetworkGetNetworkIdFromEntity(vehicle), 1)
Entity(vehicle).state.isOpen = true
if chance <= config.removeNormalLockpickChance[vehicleClass] then
TriggerServerEvent("qb-vehiclekeys:server:breakLockpick", "lockpick")
end
end
end

---Will be executed when the lock opening is successful.
---@param vehicle number The entity number of the vehicle.
local function lockpickSuccessCallback(vehicle)
TriggerServerEvent('hud:server:GainStress', math.random(1, 4))
exports.qbx_core:Notify(locale("notify.vehicle_lockedpick"), 'success')
TriggerServerEvent('qb-vehiclekeys:server:setVehLockState', NetworkGetNetworkIdFromEntity(vehicle), 1)
Entity(vehicle).state.isOpen = true
end

---Operations done after the LockpickDoor quickevent done.
---@param vehicle number The entity number of the vehicle.
---@param plate string The plate number of the vehicle.
---@param isAdvancedLockedpick boolean Determines whether an advanced lockpick was used.
---@param maxDistance number The max distance to check.
---@param isSuccess boolean? Determines whether the lock has been successfully opened.
local function lockpickCallback(vehicle, plate, isAdvancedLockedpick, maxDistance, isSuccess)
if not isVehicleInRange(vehicle, maxDistance) then return end -- the action will be aborted if the opened vehicle is too far.
local function lockpickCallback(vehicle, isAdvancedLockedpick, isSuccess)
if isSuccess then
lockpickSuccessCallback(vehicle, plate)
lockpickSuccessCallback(vehicle)
else -- if player fails quickevent
public.attemptPoliceAlert('carjack')
SetVehicleAlarm(vehicle, false)
Expand All @@ -142,20 +149,10 @@ local function lockpickCallback(vehicle, plate, isAdvancedLockedpick, maxDistanc
exports.qbx_core:Notify(locale('notify.failed_lockedpick'), 'error')
end

local chance = math.random()
if isAdvancedLockedpick then -- there is no benefit to using an advanced tool at this moment.
if chance <= config.removeAdvancedLockpickChance[GetVehicleClass(vehicle)] then
TriggerServerEvent("qb-vehiclekeys:server:breakLockpick", "advancedlockpick")
end
else
if chance <= config.removeNormalLockpickChance[GetVehicleClass(vehicle)] then
TriggerServerEvent("qb-vehiclekeys:server:breakLockpick", "lockpick")
end
end
breakLockpick(isAdvancedLockedpick, GetVehicleClass(vehicle))
end

local islockpickingProcessLocked = false -- lock flag

---Lockpicking quickevent.
---@param isAdvancedLockedpick boolean Determines whether an advanced lockpick was used
---@param maxDistance number? The max distance to check.
Expand All @@ -167,6 +164,7 @@ function public.lockpickDoor(isAdvancedLockedpick, maxDistance, customChallenge)

if not vehicle then return end

local class = GetVehicleClass(vehicle)
local plate = qbx.getVehiclePlate(vehicle)
local isDriverSeatFree = IsVehicleSeatFree(vehicle, -1)

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

if islockpickingProcessLocked then return end -- start of the critical section

islockpickingProcessLocked = true -- one call per player at a time

CreateThread(function()
-- lock opening animation
lib.requestAnimDict('veh@break_in@0h@p_m_one@')
TaskPlayAnim(cache.ped, 'veh@break_in@0h@p_m_one@', "low_force_entry_ds", 3.0, 3.0, -1, 16, 0, false, false, false)

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
local isSuccess = customChallenge or lib.skillCheck({ 'easy', 'easy', { areaSize = 60, speedMultiplier = 1 }, 'medium' }, { '1', '2', '3', '4' })
lockpickCallback(vehicle, plate, isAdvancedLockedpick, maxDistance, isSuccess)

if isVehicleInRange(vehicle, maxDistance) then -- the action will be aborted if the opened vehicle is too far.
lockpickCallback(vehicle, isAdvancedLockedpick, isSuccess)
end

Wait(config.lockpickCooldown)
end)

islockpickingProcessLocked = false -- end of the critical section
end

---Will be executed when the lock opening is successful.
---@param vehicle number The entity number of the vehicle.
local function hotwireSuccessCallback(vehicle)
local plate = qbx.getVehiclePlate(vehicle)
TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys', plate)
end

---Operations done after the LockpickDoor quickevent done.
---@param vehicle number The entity number of the vehicle.
---@param isAdvancedLockedpick boolean Determines whether an advanced lockpick was used.
---@param isSuccess boolean? Determines whether the lock has been successfully opened.
local function hotwireCallback(vehicle, isAdvancedLockedpick, isSuccess)
if isSuccess then
hotwireSuccessCallback(vehicle)
else -- if player fails quickevent
public.attemptPoliceAlert('carjack')
TriggerServerEvent('hud:server:GainStress', math.random(1, 4))
exports.qbx_core:Notify(locale('notify.failed_lockedpick'), 'error')
end

breakLockpick(isAdvancedLockedpick, GetVehicleClass(vehicle))
end

local isHotwiringProcessLocked = false -- lock flag
---Hotwiring with a tool quickevent.
---@param isAdvancedLockedpick boolean Determines whether an advanced lockpick was used
---@param customChallenge boolean? lockpick challenge
function public.hotwire(isAdvancedLockedpick, customChallenge)
if not(cache.vehicle
and cache.seat
and cache.seat == -1)
then return end

if isHotwiringProcessLocked then return end -- start of the critical section
isHotwiringProcessLocked = true -- one call per player at a time

CreateThread(function()
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
local isSuccess = customChallenge or lib.skillCheck({ 'easy', 'easy', { areaSize = 60, speedMultiplier = 1 }, 'medium' }, { '1', '2', '3', '4' })
hotwireCallback(cache.vehicle, isAdvancedLockedpick, isSuccess)
Wait(config.lockpickCooldown)
end)

isHotwiringProcessLocked = false -- end of the critical section
end

---Get a vehicle in the players scope by the plate
---@param plate string
---@return integer?
Expand Down
43 changes: 27 additions & 16 deletions client/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local config = require 'config.client'
local functions = require 'client.functions'

local hasKeys = functions.hasKeys
local hotwire = functions.hotwire
local lockpickDoor = functions.lockpickDoor
local attemptPoliceAlert = functions.attemptPoliceAlert
local isBlacklistedWeapon = functions.isBlacklistedWeapon
Expand All @@ -29,7 +30,11 @@ local canCarjack = true
local function giveKeys(id, plate)
local distance = #(GetEntityCoords(cache.ped) - GetEntityCoords(GetPlayerPed(GetPlayerFromServerId(id))))
if distance < 3 then
if not hasKeys(plate) then
return exports.qbx_core:Notify(locale('notify.no_keys'))
end
TriggerServerEvent('qb-vehiclekeys:server:GiveVehicleKeys', id, plate)
exports.qbx_core:Notify(locale('notify.gave_keys'))
else
exports.qbx_core:Notify(locale('notify.not_near'), 'error')
end
Expand Down Expand Up @@ -137,7 +142,7 @@ local function makePedFlee(ped)
TaskReactAndFleePed(ped, cache.ped)
end

local function hotwire(vehicle, plate)
local function findKeys(vehicle, plate)
local hotwireTime = math.random(config.minHotwireTime, config.maxHotwireTime)
isHotwiring = true

Expand Down Expand Up @@ -251,16 +256,15 @@ local function carjackVehicle(target)
isCarjacking = false
Wait(2000)
attemptPoliceAlert('carjack')
Wait(config.delayBetweenCarjackingsInMs)
canCarjack = true
end
else
ClearPedTasksImmediately(target)
makePedFlee(target)
isCarjacking = false
Wait(config.delayBetweenCarjackingsInMs)
canCarjack = true
end

Wait(config.delayBetweenCarjackingsInMs)
canCarjack = true
end

-----------------------
Expand Down Expand Up @@ -341,7 +345,7 @@ CreateThread(function()
SetVehicleEngineOn(cache.vehicle, false, false, true)

if IsControlJustPressed(0, 74) then
hotwire(cache.vehicle, plate)
findKeys(cache.vehicle, plate)
end
end
end
Expand Down Expand Up @@ -406,22 +410,29 @@ RegisterNetEvent('qb-vehiclekeys:client:GiveKeys', function(id, plate)

if id and type(id) == 'number' then -- Give keys to specific ID
giveKeys(id, targetPlate)
else
if IsPedSittingInVehicle(cache.ped, targetVehicle) then -- Give keys to everyone in vehicle
local otherOccupants = getOtherPlayersInVehicle(targetVehicle)
for p = 1, #otherOccupants do
TriggerServerEvent('qb-vehiclekeys:server:GiveVehicleKeys', GetPlayerServerId(NetworkGetPlayerIndexFromPed(otherOccupants[p])), targetPlate)
end
else -- Give keys to closest player
local playerId = lib.getClosestPlayer(GetEntityCoords(cache.ped), 3, false)
giveKeys(playerId, targetPlate)
elseif IsPedSittingInVehicle(cache.ped, targetVehicle) then -- Give keys to everyone in vehicle
local otherOccupants = getOtherPlayersInVehicle(targetVehicle)
if not hasKeys(qbx.getVehiclePlate(targetVehicle)) then
return exports.qbx_core:Notify(locale('notify.no_keys'))
end

for p = 1, #otherOccupants do
TriggerServerEvent('qb-vehiclekeys:server:GiveVehicleKeys', GetPlayerServerId(NetworkGetPlayerIndexFromPed(otherOccupants[p])), targetPlate)
end
exports.qbx_core:Notify(locale('notify.gave_keys'))
else -- Give keys to closest player
local playerId = lib.getClosestPlayer(GetEntityCoords(cache.ped), 3, false)
giveKeys(playerId, targetPlate)
end
end
end)

RegisterNetEvent('lockpicks:UseLockpick', function(isAdvanced)
lockpickDoor(isAdvanced)
if cache.vehicle then
hotwire(isAdvanced)
else
lockpickDoor(isAdvanced)
end
end)

--#region Backwards Compatibility ONLY -- Remove at some point --
Expand Down
5 changes: 5 additions & 0 deletions config/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ return {
[VehicleClasses.OPEN_WHEEL] = 0.5
},

advancedLockpickVehicleClasses = { -- The vehicle classes can only be opened with an advanced lockpick
[VehicleClasses.HELICOPTERS] = true,
[VehicleClasses.MILITARY] = true
},

-- Carjack Settings
carjackEnable = true, -- Enables the ability to carjack pedestrian vehicles, stealing them by pointing a weapon at them
carjackingTimeInMs = 7500, -- Time it takes to successfully carjack in miliseconds
Expand Down
4 changes: 0 additions & 4 deletions server/functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ function public.removeKeys(source, plate)
return true
end

function public.hasKeys(source, plate)
return Player(source).state.keysList[plate]
end

---Gives the user the keys to the vehicle
---@param source number ID of the player
---@param plate string The plate number of the vehicle.
Expand Down
9 changes: 0 additions & 9 deletions server/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

local functions = require 'server.functions'

local hasKeys = functions.hasKeys
local giveKeys = functions.giveKeys
local addPlayer = functions.addPlayer
local removePlayer = functions.removePlayer
Expand All @@ -16,21 +15,13 @@ local removePlayer = functions.removePlayer
-- Event to give keys. receiver can either be a single id, or a table of ids.
-- Must already have keys to the vehicle, trigger the event from the server, or pass forcegive paramter as true.
RegisterNetEvent('qb-vehiclekeys:server:GiveVehicleKeys', function(receiver, plate)
local giver = source

if not hasKeys(giver, plate) then
return exports.qbx_core:Notify(giver, locale('notify.no_keys'))
end

if type(receiver) == 'table' then
for i = 1, receiver do
giveKeys(receiver[i], plate)
end
else
giveKeys(receiver, plate)
end

exports.qbx_core:Notify(giver, locale('notify.gave_keys'))
end)

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

0 comments on commit c9c3023

Please sign in to comment.