refactor(vehicles): move garages to separate resource keep api

- Moved traffic locking to client
- removed garages (moved to ND_Garages resource)
- optimized and improved the api
- fixed smaller bugs
This commit is contained in:
Andyyy7666
2026-03-16 08:07:43 +01:00
parent b7222a1f8f
commit b837771300
7 changed files with 248 additions and 742 deletions

View File

@@ -242,30 +242,36 @@ local function getVehicleBlipSprite(entity)
end end
local function getVehFromNetId(netId) local function getVehFromNetId(netId)
local time = GetCloudTimeAsInt() local startTime = GetCloudTimeAsInt()
while not NetworkDoesNetworkIdExist(netId) or not NetworkDoesEntityExistWithNetworkId(netId) and time-GetCloudTimeAsInt() < 5 do while (not NetworkDoesNetworkIdExist(netId) or not NetworkDoesEntityExistWithNetworkId(netId)) and (GetCloudTimeAsInt() - startTime < 5) do
Wait(100) Wait(100)
end end
return NetToVeh(netId) return NetToVeh(netId)
end end
RegisterNetEvent("ND_Vehicles:blip", function(netId, status)
local veh = getVehFromNetId(netId) local function setVehicleBlip(veh, status, groupVehicle)
if not veh then return end local currentBlip = GetBlipFromEntity(veh)
if not status then if currentBlip and DoesBlipExist(currentBlip) then
local blip = GetBlipFromEntity(veh) RemoveBlip(currentBlip)
if not blip or not DoesBlipExist(blip) then return end
return RemoveBlip(blip)
end end
if not status then return end
local blip = AddBlipForEntity(veh) local blip = AddBlipForEntity(veh)
SetBlipSprite(blip, getVehicleBlipSprite(veh)) SetBlipSprite(blip, getVehicleBlipSprite(veh))
SetBlipColour(blip, 0) SetBlipColour(blip, 0)
SetBlipScale(blip, 0.8) SetBlipScale(blip, 0.8)
SetBlipAsShortRange(blip, true) SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName("STRING") BeginTextCommandSetBlipName("STRING")
AddTextComponentSubstringPlayerName(locale("personal_vehicle")) AddTextComponentSubstringPlayerName(groupVehicle and locale("group_vehicle") or locale("personal_vehicle"))
EndTextCommandSetBlipName(blip) EndTextCommandSetBlipName(blip)
end
RegisterNetEvent("ND_Vehicles:blip", function(netId, status, groupVehicle)
local veh = getVehFromNetId(netId)
if not veh then return end
setVehicleBlip(veh, status, groupVehicle)
end) end)
RegisterNetEvent("ND_Vehicles:syncAlarm", function(netId) RegisterNetEvent("ND_Vehicles:syncAlarm", function(netId)
@@ -284,13 +290,39 @@ RegisterNetEvent("ND_VehicleSystem:setOwnedIfNot", function(netId)
end) end)
AddStateBagChangeHandler("props", nil, function(bagName, key, value, reserved, replicated) AddStateBagChangeHandler("props", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName) if not value then return end
if not value or not DoesEntityExist(entity) or NetworkGetEntityOwner(entity) ~= cache.playerId then return end
local props = value local props = value
if type(value) == "string" then if type(value) == "string" then
props = json.decode(value) props = json.decode(value)
end end
if not props or type(props) ~= "table" then return end
local entity = GetEntityFromStateBagName(bagName)
if not DoesEntityExist(entity) or NetworkGetEntityOwner(entity) ~= cache.playerId then return end
lib.setVehicleProperties(entity, props) lib.setVehicleProperties(entity, props)
TriggerServerEvent("ND_Vehicles:propsApplied", DoesEntityExist(entity) and NetworkGetNetworkIdFromEntity(entity))
end)
local function hasBlipGroup(player, groups)
groups = groups or {}
for i=1, #groups do
local group = groups[i]
if player.groups[group] then
return true
end
end
end
AddStateBagChangeHandler("blipGroups", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if not value or not DoesEntityExist(entity) then return end
local player = NDCore.getPlayer()
if not player then return end
setVehicleBlip(entity, hasBlipGroup(player, value), true)
end) end)
local playingKey = 0 local playingKey = 0
@@ -411,6 +443,17 @@ local function hasVehicleKeys(veh, checkEngine)
return hasKey or checkEngine and state.hotwired return hasKey or checkEngine and state.hotwired
end end
AddStateBagChangeHandler("owner", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if not value or not DoesEntityExist(entity) or GetEntityType(entity) ~= 2 then return end
local player = NDCore.getPlayer()
if not player or player.id ~= value then return end
local hasKey = hasVehicleKeys(entity)
setVehicleBlip(entity, hasKey)
end)
local function hasVehicleKeysCheck(veh) local function hasVehicleKeysCheck(veh)
local time = GetCloudTimeAsInt() local time = GetCloudTimeAsInt()
if time-keyCheckTime.lastCheck < 5 then if time-keyCheckTime.lastCheck < 5 then
@@ -526,6 +569,33 @@ CreateThread(function()
end end
end) end)
CreateThread(function()
while true do
Wait(100)
local veh = cache.vehicle
if veh then goto skip end
local ped = cache.ped
local vehEntering = GetVehiclePedIsEntering(ped)
if not DoesEntityExist(vehEntering) or NetworkGetEntityOwner(vehEntering) == cache.serverId then goto skip end
local state = Entity(vehEntering).state
if state.owner or state.locked ~= nil then goto skip end
local driver = GetPedInVehicleSeat(vehEntering, -1)
if DoesEntityExist(driver) and IsPedAPlayer(driver) then
state.locked = false
state.hotwired = true
goto skip
end
if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end
state.locked = true
::skip::
end
end)
local function hotwireVehicle() local function hotwireVehicle()
local state = playerVehicle and Entity(playerVehicle).state local state = playerVehicle and Entity(playerVehicle).state
if not playerVehicle or state.hotwired then return end if not playerVehicle or state.hotwired then return end
@@ -597,6 +667,17 @@ local function lockpickVehicle()
local veh = lib.getClosestVehicle(pos, 2.5, false) local veh = lib.getClosestVehicle(pos, 2.5, false)
if not veh then return end if not veh then return end
local lockPickResource = GetResourceState("lockpick") == "started" and "lockpick" or GetResourceState("t3_lockpick") == "started" and "t3_lockpick"
if lockPickResource then
local success = exports[lockPickResource]:startLockpick("lockpick_vehicle", 4, Config.lockpickTries)
if not success or not DoesEntityExist(veh) or #(pos-GetEntityCoords(veh)) > 2.5 then
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), false)
if success == nil then -- nil would mean the minigame was cancelled
return false, false
end
return false, true
end
else
local dificulties = { local dificulties = {
"easy", "easy",
"medium", "medium",
@@ -619,13 +700,19 @@ local function lockpickVehicle()
end end
Wait(dificultyTime[dificulty]) Wait(dificultyTime[dificulty])
end end
end
veh = lib.getClosestVehicle(pos, 2.5, false) veh = lib.getClosestVehicle(pos, 2.5, false)
if not veh then return false, true end if not veh then return false, true end
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), true) TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), true)
PlaySoundFromEntity(-1, "Remote_Control_Fob", cache.ped, "PI_Menu_Sounds", true, 0) PlaySoundFromEntity(-1, "Remote_Control_Fob", cache.ped, "PI_Menu_Sounds", true, 0)
if lockPickResource then
return true, false
else
return true, true return true, true
end end
end
exports("lockpick", function(data, slot) exports("lockpick", function(data, slot)
local _, used = lockpickVehicle() local _, used = lockpickVehicle()

View File

@@ -1,301 +0,0 @@
return {
{
garageType = "land",
groups = {"lsfd"},
ped = vector4(370.9027, -593.8090, 28.8681, 73.6009),
vehicleSpawns = {
vec4(366.7840, -591.4883, 28.7261, 339.6823),
vec4(365.0753, -582.8641, 28.7156, 135.0064),
vec4(357.4239, -604.7719, 28.6767, 177.1355),
}
},
{
garageType = "land",
groups = {"sahp", "lspd", "bcso"},
ped = vector4(452.83, -1027.79, 28.54, 2.49),
vehicleSpawns = {
vector4(446.19, -1025.47, 28.24, 185.96),
vector4(438.75, -1026.09, 28.38, 184.29),
vector4(434.98, -1026.61, 28.46, 185.99),
vector4(431.26, -1027.31, 28.53, 185.59),
vector4(427.52, -1026.84, 28.58, 184.25)
}
},
{
garageType = "land",
impound = true,
ped = vector4(407.99, -1624.74, 29.29, 229.93),
vehicleSpawns = {
vector4(396.34, -1644.28, 28.86, 319.10),
vector4(398.56, -1646.42, 28.8616, 319.18),
vector4(400.68, -1648.86, 28.86, 140.78),
vector4(403.32, -1650.54, 28.86, 319.92),
vector4(403.21, -1650.68, 28.86, 139.40)
}
},
{
garageType = "plane",
ped = vector4(-941.05, -2966.04, 13.95, 136.06),
vehicleSpawns = {
vector4(-974.98, -2977.90, 14.55, 59.91)
}
},
{
garageType = "heli",
ped = vector4(-731.00, -1394.54, 5.00, 245.52),
vehicleSpawns = {
vector4(-746.01, -1469.39, 5.68, 139.27),
vector4(-725.26, -1444.72, 5.68, 139.58)
}
},
{
garageType = "plane",
ped = vector4(1742.77, 3298.25, 41.22, 132.91),
vehicleSpawns = {
vector4(1734.26, 3250.98, 41.96, 81.11),
vector4(1729.32, 3270.69, 41.74, 145.39)
}
},
{
garageType = "plane",
ped = vector4(-1241.26, -3391.48, 13.94, 35.03),
vehicleSpawns = {
vector4(-1254.71, -3388.15, 14.54, 330.04),
vector4(-1270.36, -3378.80, 14.54, 330.20),
vector4(-1286.05, -3369.44, 14.54, 329.99)
}
},
{
garageType = "plane",
ped = vector4(-1621.10, -3151.63, 13.99, 41.26),
vehicleSpawns = {
vector4(-1634.04, -3147.84, 14.60, 330.28),
vector4(-1649.75, -3138.34, 14.60, 329.20),
vector4(-1665.42, -3129.26, 14.60, 330.10)
}
},
{
garageType = "heli",
ped = vector4(-1121.87, -2839.88, 13.95, 150.58),
vehicleSpawns = {
vector4(-1178.71, -2846.51, 14.62, 150.16),
vector4(-1146.40, -2865.22, 14.62, 151.08),
vector4(-1112.85, -2884.66, 14.62, 149.70)
}
},
{
garageType = "water",
ped = vector4(-831.03, -1359.53, 5.00, 299.80),
vehicleSpawns = {
vector4(-846.16, -1362.07, 0.39, 110.54),
vector4(-842.53, -1372.00, 0.39, 111.54),
vector4(-849.29, -1353.26, 0.38, 109.53),
vector4(-852.26, -1345.09, 0.41, 110.52),
vector4(-855.49, -1336.60, 0.40, 107.43),
vector4(-858.63, -1328.30, 0.40, 109.82),
vector4(-839.35, -1380.30, 0.39, 109.27),
vector4(-836.38, -1388.93, 0.41, 110.11),
vector4(-833.29, -1397.32, 0.40, 110.80),
vector4(-830.34, -1405.65, 0.37, 110.16)
}
},
{
garageType = "land",
ped = vector4(-280.32, -888.42, 31.32, 250.68),
vehicleSpawns = {
vector4(-282.36, -915.11, 30.38, 68.81),
vector4(-284.27, -918.37, 30.38, 70.58),
vector4(-285.44, -921.82, 30.38, 250.22),
vector4(-285.70, -887.62, 30.38, 167.04),
vector4(-292.70, -885.93, 30.38, 167.09),
vector4(-285.76, -887.47, 30.38, 169.59),
vector4(-300.44, -885.14, 30.38, 347.05),
vector4(-303.59, -883.71, 30.38, 167.69),
vector4(-309.38, -896.88, 30.38, 167.15),
vector4(-312.98, -896.30, 30.38, 166.46),
vector4(-316.68, -896.26, 30.37, 347.94),
vector4(-311.06, -881.94, 30.38, 168.21),
vector4(-314.78, -881.86, 30.37, 348.45)
}
},
{
garageType = "land",
ped = vector4(597.53, 91.08, 93.13, 250.54),
vehicleSpawns = {
vector4(598.53, 98.37, 92.27, 69.47),
vector4(599.79, 102.00, 92.27, 249.38),
vector4(608.21, 103.90, 92.18, 248.98),
vector4(600.60, 111.38, 92.27, 73.07),
vector4(609.91, 107.59, 92.23, 68.77),
vector4(601.36, 114.99, 92.27, 250.19),
vector4(611.07, 111.39, 92.29, 249.69),
vector4(603.52, 118.52, 92.26, 67.62),
vector4(612.74, 114.94, 92.28, 69.01),
vector4(604.00, 122.58, 92.27, 249.76),
vector4(613.78, 118.83, 92.29, 248.82),
vector4(622.43, 115.48, 91.99, 70.39),
vector4(628.56, 110.25, 91.47, 249.30),
vector4(620.67, 111.89, 92.03, 250.24),
vector4(618.41, 104.51, 91.97, 72.22),
vector4(624.87, 99.25, 91.36, 63.32),
vector4(616.59, 100.82, 91.97, 249.00)
}
},
{
garageType = "land",
ped = vector4(100.64, -1072.88, 29.37, 341.55),
vehicleSpawns = {
vector4(106.13, -1063.24, 28.51, 66.86),
vector4(107.82, -1059.73, 28.51, 66.99),
vector4(112.39, -1049.71, 28.52, 67.40),
vector4(110.76, -1053.09, 28.51, 68.13),
vector4(109.06, -1056.38, 28.51, 66.45),
vector4(117.53, -1081.03, 28.50, 181.40),
vector4(119.09, -1069.44, 28.50, 181.12),
vector4(121.22, -1081.42, 28.50, 0.57),
vector4(122.32, -1070.46, 28.50, 0.67),
vector4(125.62, -1069.54, 28.50, 179.25),
vector4(124.83, -1081.17, 28.50, 180.37),
vector4(128.62, -1081.71, 28.50, 1.36),
vector4(128.93, -1070.62, 28.50, 359.89),
vector4(132.24, -1069.35, 28.50, 181.54),
vector4(132.31, -1081.45, 28.50, 180.86),
vector4(135.93, -1081.35, 28.50, 358.82),
vector4(135.64, -1070.75, 28.50, 0.43),
vector4(139.75, -1081.91, 28.50, 182.18),
vector4(138.86, -1070.37, 28.50, 181.90),
vector4(143.55, -1081.41, 28.50, 0.89),
vector4(147.13, -1081.48, 28.50, 179.02),
vector4(150.95, -1081.49, 28.50, 358.79),
vector4(154.64, -1081.32, 28.50, 179.51),
vector4(158.38, -1082.03, 28.50, 1.57),
vector4(162.10, -1081.32, 28.50, 179.88)
}
},
{
garageType = "land",
ped = vector4(214.84, -806.24, 30.81, 342.23),
vehicleSpawns = {
vector4(251.26, -774.63, 29.98, 67.34),
vector4(245.36, -772.34, 30.02, 66.81),
vector4(219.19, -765.93, 30.14, 249.78),
vector4(228.11, -768.94, 30.10, 249.66),
vector4(233.68, -771.18, 30.07, 249.82),
vector4(244.01, -775.06, 29.99, 249.41),
vector4(249.53, -777.07, 29.95, 251.09),
vector4(248.74, -779.52, 29.92, 69.14),
vector4(243.64, -777.63, 29.96, 69.23),
vector4(233.47, -773.91, 30.05, 69.88),
vector4(218.62, -768.58, 30.14, 69.81),
vector4(217.02, -770.83, 30.16, 248.11),
vector4(226.09, -773.95, 30.09, 249.30),
vector4(231.82, -776.14, 30.04, 249.17),
vector4(242.05, -779.91, 29.93, 246.99),
vector4(247.72, -782.02, 29.88, 251.04),
vector4(216.19, -773.65, 30.16, 67.90),
vector4(225.82, -776.85, 30.08, 65.64),
vector4(230.77, -778.89, 30.03, 69.40),
vector4(241.72, -782.84, 29.90, 69.13),
vector4(247.12, -784.93, 29.84, 67.56),
vector4(246.23, -787.13, 29.82, 247.56),
vector4(239.40, -784.68, 29.90, 248.06),
vector4(230.42, -781.41, 30.01, 247.48),
vector4(224.14, -779.01, 30.07, 247.39),
vector4(215.32, -775.95, 30.17, 248.99),
vector4(243.81, -792.19, 29.77, 246.23),
vector4(238.08, -789.95, 29.85, 250.62),
vector4(228.14, -786.30, 30.01, 249.07),
vector4(221.98, -783.92, 30.08, 248.25),
vector4(213.41, -781.01, 30.19, 249.21),
vector4(214.77, -778.70, 30.17, 67.86),
vector4(224.02, -781.91, 30.07, 68.82),
vector4(229.34, -784.05, 30.01, 66.57),
vector4(239.87, -787.83, 29.86, 69.37),
vector4(245.18, -789.87, 29.79, 68.00),
vector4(241.85, -797.20, 29.72, 245.51),
vector4(236.57, -795.07, 29.82, 247.54),
vector4(226.12, -791.34, 29.99, 247.84),
vector4(220.61, -789.19, 30.08, 248.09),
vector4(212.00, -786.22, 30.21, 248.93),
vector4(212.91, -783.71, 30.19, 68.61),
vector4(222.11, -786.89, 30.07, 70.10),
vector4(227.84, -789.16, 29.99, 67.55),
vector4(237.73, -792.73, 29.82, 69.40),
vector4(243.54, -794.88, 29.73, 68.74),
vector4(237.03, -812.64, 29.59, 243.63),
vector4(207.66, -798.70, 30.29, 70.06),
vector4(216.56, -801.92, 30.10, 70.31),
vector4(222.28, -804.17, 29.98, 69.97),
vector4(232.41, -807.95, 29.74, 69.42),
vector4(238.16, -810.17, 29.60, 68.95),
vector4(238.32, -807.43, 29.63, 248.28),
vector4(233.32, -805.44, 29.75, 248.13),
vector4(222.46, -801.41, 29.98, 248.50),
vector4(216.85, -799.11, 30.10, 248.54),
vector4(207.60, -795.90, 30.29, 248.64),
vector4(209.28, -793.74, 30.25, 69.82),
vector4(218.31, -796.92, 30.08, 68.55),
vector4(224.50, -799.34, 29.96, 68.63),
vector4(234.40, -803.02, 29.76, 66.67),
vector4(240.09, -805.27, 29.64, 69.02),
vector4(240.53, -802.44, 29.67, 248.95),
vector4(234.46, -800.13, 29.79, 249.40),
vector4(224.30, -796.51, 29.98, 248.31),
vector4(218.23, -794.00, 30.09, 249.09),
vector4(209.84, -791.10, 30.24, 251.56),
vector4(211.09, -788.68, 30.22, 69.53),
vector4(220.17, -791.73, 30.06, 70.56),
vector4(225.84, -794.12, 29.98, 67.53),
vector4(236.38, -797.85, 29.79, 69.39),
vector4(216.56, -801.90, 30.79, 69.39)
}
},
{
garageType = "land",
ped = vector4(587.5941, 2744.2844, 42.0691, 181.0514),
vehicleSpawns = {
vector4(584.3921, 2721.3599, 41.6483, 184.3260),
vector4(577.1069, 2736.2322, 41.6097, 184.6912),
vector4(577.9459, 2721.0010, 41.6481, 185.1414),
vector4(574.1685, 2735.7288, 41.6397, 184.5562)
}
},
{
garageType = "land",
ped = vector4(2746.1726, 3458.7378, 55.8272, 245.1009),
vehicleSpawns = {
vector4(2768.2136, 3456.2710, 55.2840, 67.7897),
vector4(2759.3218, 3451.1448, 55.4786, 248.0150),
vector4(2763.6526, 3445.0544, 55.4741, 66.9754),
vector4(2787.2947, 3467.1001, 54.9407, 247.4356),
vector4(2773.9514, 3471.6238, 55.0407, 246.8264)
}
},
{
garageType = "land",
ped = vector4(120.4042, 6623.5190, 31.9593, 228.8283),
vehicleSpawns = {
vector4(155.9047, 6593.3252, 31.4343, 359.3789),
vector4(155.7900, 6602.6978, 31.4463, 358.8232),
vector4(145.8267, 6614.0996, 31.4007, 178.7948),
vector4(145.6977, 6602.8950, 31.4397, 181.0616),
vector4(132.4042, 6585.2139, 31.5501, 91.0906)
}
},
{
garageType = "heli",
groups = {"lspd"},
ped = vector4(465.6194, -985.4730, 43.6918, 0.8591),
vehicleSpawns = {
vector4(449.3105, -981.1057, 44.0788, 359.6042)
}
},
{
garageType = "heli",
groups = {"lsfd"},
ped = vector4(339.6271, -581.0325, 74.1656, 267.8232),
vehicleSpawns = {
vector4(351.3463, -588.1304, 74.1698, 108.5617)
}
}
}

View File

@@ -1,291 +0,0 @@
local locations = require "client.vehicle.data"
local sprite = {
["water"] = 356,
["heli"] = 360,
["plane"] = 359,
["land"] = 357
}
local garageTypes = {
["water"] = 14,
["heli"] = 15,
["plane"] = 16
}
local clothing = {
{
face = {
drawable = 1,
texture = 1
},
undershirt = {
drawable = 0,
texture = 0
},
torso = {
drawable = 1,
texture = 1
},
leg = {
drawable = 0,
texture = 0
},
glasses = {
drawable = 1,
texture = 0
},
hat = {
drawable = -1,
texture = -1
},
},
{
leg = {
drawable = 0,
texture = 1
},
undershirt = {
drawable = 0,
texture = 0
},
face = {
drawable = 0,
texture = 0
},
torso = {
drawable = 0,
texture = 2
},
glasses = {
drawable = -1,
texture = -1
},
hat = {
drawable = 0,
texture = 0
},
},
{
face = {
drawable = 0,
texture = 2
},
undershirt = {
drawable = 0,
texture = 0
},
torso = {
drawable = 1,
texture = 2
},
leg = {
drawable = 0,
texture = 0
},
hat = {
drawable = -1,
texture = -1
},
glasses = {
drawable = -1,
texture = -1
},
}
}
local function getClosestOwnedVehicle()
local coords = GetEntityCoords(cache.ped)
local vehicles = lib.getNearbyVehicles(coords, 50.0, true)
local nearestVeh = {}
local function setNearestVehicle(veh)
local state = Entity(veh.vehicle).state
if not state.owner or state.owner ~= NDCore.player?.id then return end
local nearestDist = nearestVeh.dist
local dist = #(coords-veh.coords)
if not nearestDist or dist < nearestDist then
nearestVeh.dist = dist
nearestVeh.coords = veh.coords
nearestVeh.entity = veh.vehicle
end
end
for i=1, #vehicles do
setNearestVehicle(vehicles[i])
end
return nearestVeh.entity, nearestVeh.coords, nearestVeh.dist
end
local function parkVehicle(veh)
if not veh or not DoesEntityExist(veh) then
return NDCore.notify({
title = locale("garage"),
description = locale("no_owned_veh_nearby"),
type = "error",
position = "bottom",
duration = 3000
})
end
if GetPedInVehicleSeat(veh, -1) ~= 0 then
NDCore.notify({
title = locale("garage"),
description = locale("player_in_veh"),
type = "error",
position = "bottom",
duration = 3000
})
return
end
local properties = lib.getVehicleProperties(veh)
properties.class = GetVehicleClass(veh)
TriggerServerEvent("ND_Vehicles:storeVehicle", VehToNet(veh))
end
local function isVehicleAvailable(vehicle, garageType, impound)
local class = vehicle.properties.class
local available = vehicle.available and not impound or vehicle.impounded and impound
if available and not garageTypes[garageType] then return true end
for garType, garClass in pairs(garageTypes) do
if available and garType == garageType and garClass == class then
return true
end
end
end
local function getEngineStatus(health)
if health > 950 then
return locale("perfect")
elseif health > 750 then
return locale("good")
elseif health > 500 then
return locale("bad")
end
return locale("very_bad")
end
local function createMenuOptions(vehicle, vehicleSpawns)
local props = vehicle.properties
local makeName = GetLabelText(GetMakeNameFromVehicleModel(props.model))
local modelName = GetLabelText(GetDisplayNameFromVehicleModel(props.model))
local metadata = {}
if not makeName or makeName == "NULL" then
makeName = ""
else
metadata[#metadata+1] = {label = locale("veh_make_brand"), value = makeName}
makeName = makeName .. " "
end
if not modelName or modelName == "NULL" then
modelName = ""
else
metadata[#metadata+1] = {label = locale("veh_model"), value = modelName}
end
if props?.plate then
metadata[#metadata+1] = {label = locale("veh_plate"), value = props.plate}
end
if props?.engineHealth then
metadata[#metadata+1] = {
label = locale("engine_status"),
value = getEngineStatus(props.engineHealth),
progress = props.engineHealth/10,
colorScheme = "blue"
}
end
if props?.fuelLevel then
metadata[#metadata+1] = {
label = "Fuel",
value = ("%d%s"):format(props.fuelLevel, "%"),
progress = props.fuelLevel,
colorScheme = "yellow"
}
end
return {
title = ("%s: %s%s\n%s: %s"):format(locale("vehicle"), makeName, modelName, locale("veh_plate"), props?.plate or locale("not_found")),
metadata = metadata,
onSelect = function(args)
TriggerServerEvent("ND_Vehicles:takeVehicle", vehicle.id, vehicleSpawns)
end,
}
end
local function createMenu(vehicles, garageType, vehicleSpawns, impound)
local options = {}
if not impound then
options[#options+1] = {
title = locale("park_veh"),
onSelect = function(args)
local veh = getClosestOwnedVehicle()
parkVehicle(veh)
end
}
end
for _, vehicle in ipairs(vehicles) do
if isVehicleAvailable(vehicle, garageType, impound) then
options[#options+1] = createMenuOptions(vehicle, vehicleSpawns)
end
end
if impound and #options == 0 then
options[#options+1] = {
title = locale("no_vehs_found"),
readOnly = true
}
end
return {
id = ("garage_%s"):format(garageType),
title = impound and locale("vehicle_impound") or locale("parking_garage"),
options = options,
onExit = function()
garageOpen = false
end
}
end
for i=1, #locations do
local location = locations[i]
NDCore.createAiPed({
model = `s_m_y_airworker`,
coords = location.ped,
distance = 45.0,
clothing = clothing[math.random(1, #clothing)],
blip = {
label = location.impound and locale("impound_w_location", location.garageType) or locale("garage_w_location", location.garageType),
sprite = location.impound and 285 or sprite[location.garageType],
scale = 0.7,
color = 3,
groups = location.groups
},
anim = {
dict = "anim@amb@casino@valet_scenario@pose_d@",
clip = "base_a_m_y_vinewood_01"
},
options = {
{
name = "nd_core:garagePed",
icon = "fa-solid fa-warehouse",
label = location.impound and locale("view_impounded_vehs") or locale("view_garage"),
distance = 2.0,
canInteract = function(entity, distance, coords, name, bone)
if not location.groups then return true end
local groups = location.groups
local playerGroups = NDCore.player?.groups
for i=1, #groups do
if playerGroups?[groups[i]] then
return true
end
end
end,
onSelect = function(data)
local vehicles = lib.callback.await("ND_Vehicles:getOwnedVehicles") or {}
local menu = createMenu(vehicles, location.garageType, location.vehicleSpawns, location.impound)
lib.registerContext(menu)
lib.showContext(menu.id)
end
}
},
})
end

View File

@@ -8,16 +8,25 @@ fx_version "cerulean"
game "gta5" game "gta5"
lua54 "yes" lua54 "yes"
files {
"init.lua",
"compatibility/**/locale.lua",
"locales/*.json",
"ui/**"
}
ui_page "ui/index.html"
shared_script "@ox_lib/init.lua" shared_script "@ox_lib/init.lua"
client_scripts { client_scripts {
"client/main.lua", "client/main.lua",
"shared/functions.lua", "shared/functions.lua",
"client/peds.lua", "client/peds.lua",
"client/vehicle/main.lua", "client/vehicle.lua",
"client/vehicle/garages.lua",
"client/functions.lua", "client/functions.lua",
"client/events.lua", "client/events.lua",
"client/death.lua", "client/death.lua",
"client/groupadmin.lua",
"compatibility/**/client.lua" "compatibility/**/client.lua"
} }
server_scripts { server_scripts {
@@ -27,17 +36,12 @@ server_scripts {
"server/player.lua", "server/player.lua",
"server/vehicle.lua", "server/vehicle.lua",
"server/functions.lua", "server/functions.lua",
"server/groups.lua",
"server/groupadmin.lua",
"compatibility/**/server.lua", "compatibility/**/server.lua",
"server/commands.lua" "server/commands.lua"
} }
files {
"init.lua",
"client/vehicle/data.lua",
"compatibility/**/locale.lua",
"locales/*.json"
}
dependencies { dependencies {
"ox_lib", "ox_lib",
"oxmysql" "oxmysql"

View File

@@ -66,6 +66,7 @@
"view_impounded_vehs": "View impounded vehicles", "view_impounded_vehs": "View impounded vehicles",
"view_garage": "View garage", "view_garage": "View garage",
"personal_vehicle": "Personal vehicle", "personal_vehicle": "Personal vehicle",
"group_vehicle": "Vehicle",
"keybind_carkey": "Unlock/lock vehicle (double click)", "keybind_carkey": "Unlock/lock vehicle (double click)",
"progress_hotwiring": "Hotwiring", "progress_hotwiring": "Hotwiring",
"cruise_control": "Cruise control", "cruise_control": "Cruise control",

View File

@@ -66,6 +66,7 @@
"view_impounded_vehs": "Visa beslagtagna fordon", "view_impounded_vehs": "Visa beslagtagna fordon",
"view_garage": "Visa garage", "view_garage": "Visa garage",
"personal_vehicle": "Personligt fordon", "personal_vehicle": "Personligt fordon",
"group_vehicle": "Fordon",
"keybind_carkey": "Lås/öppna fordon (dubbelklick)", "keybind_carkey": "Lås/öppna fordon (dubbelklick)",
"progress_hotwiring": "Kopplar om", "progress_hotwiring": "Kopplar om",
"cruise_control": "Farthållare", "cruise_control": "Farthållare",

View File

@@ -84,11 +84,14 @@ local function getVehicleDatabaseInfo(vehicle)
if not vehicle then return end if not vehicle then return end
local stored = vehicle.stored == 1 local stored = vehicle.stored == 1
local impounded = vehicle.impounded == 1 local impounded = vehicle.impounded == 1
local properties = json.decode(vehicle.properties) or {}
properties.plate = vehicle.plate
return { return {
id = vehicle.id, id = vehicle.id,
owner = vehicle.owner, owner = vehicle.owner,
plate = vehicle.plate, plate = vehicle.plate,
properties = json.decode(vehicle.properties) or {}, properties = properties,
stored = stored, stored = stored,
impounded = impounded, impounded = impounded,
stolen = vehicle.stolen == 1, stolen = vehicle.stolen == 1,
@@ -97,6 +100,14 @@ local function getVehicleDatabaseInfo(vehicle)
} }
end end
function NDCore.generateVehiclePlate()
return generatePlate()
end
function NDCore.generateUniqueVehiclePlate()
return generateVehiclePlate()
end
--- get vehicle information queried from the database by the vehicle id --- get vehicle information queried from the database by the vehicle id
---@param vehicleId string | numb ---@param vehicleId string | numb
---@return table | nil ---@return table | nil
@@ -124,20 +135,45 @@ function NDCore.getVehicle(entity)
netId = NetworkGetNetworkIdFromEntity(entity) netId = NetworkGetNetworkIdFromEntity(entity)
} }
--- delete the vehicle -- save properties to db
function self.delete(saveProperties) function self.saveProperties(props, propsToSave)
if not DoesEntityExist(entity) then return end local properties = {}
if saveProperties and self.id and self.owner then local vehProps = props or lib.callback.await("ND_Vehicles:getProps", NetworkGetEntityOwner(entity), self.netId)
local properties = lib.callback.await("ND_Vehicles:getProps", NetworkGetEntityOwner(entity), self.netId)
if properties then if propsToSave then
properties = self.properties
if vehProps then
for prop, value in pairs(vehProps) do
if lib.table.contains(propsToSave, prop) then
properties[prop] = value
end
end
end
else
properties = vehProps
end
if self.properties?.callsign then if self.properties?.callsign then
properties.callsign = true properties.callsign = true
end end
self.properties = properties
state.props = properties
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id}) MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id})
end end
--- delete the vehicle
function self.delete(saveProperties, propsToSave)
if not DoesEntityExist(entity) then return end
if saveProperties and self.id and self.owner then
self.saveProperties(false, propsToSave)
end end
if DoesEntityExist(entity) then
DeleteEntity(entity) DeleteEntity(entity)
end end
return true
end
-- remvoe vehicle from db. -- remvoe vehicle from db.
@@ -159,11 +195,12 @@ function NDCore.getVehicle(entity)
properties.callsign = true properties.callsign = true
end end
state.allPropsApplied = false
state.props = properties state.props = properties
self.properties = properties self.properties = properties
if not self.id or not self.owner then return end if not self.id or not self.owner then return end
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id}) self.saveProperties(properties)
end end
--- set vehicle locked/unlocked --- set vehicle locked/unlocked
@@ -183,6 +220,7 @@ function NDCore.getVehicle(entity)
if self.properties then if self.properties then
self.properties.plate = plate self.properties.plate = plate
local state = Entity(entity).state local state = Entity(entity).state
state.allPropsApplied = false
state.props = self.properties state.props = self.properties
end end
@@ -194,9 +232,9 @@ function NDCore.getVehicle(entity)
--- set the vehicle availability status --- set the vehicle availability status
---@param statusType string ---@param statusType string
---@param status boolean ---@param status boolean
function self.setStatus(statusType, status) function self.setStatus(statusType, status, keepEntity)
if not lib.table.contains({"stored", "impounded", "stolen"}, statusType) then return end if not lib.table.contains({"stored", "impounded", "stolen"}, statusType) then return end
if statusType ~= "stolen" then self.delete(true) end if statusType ~= "stolen" and not keepEntity then self.delete(true) end
if not self.id or not self.owner then return end if not self.id or not self.owner then return end
local query = ("UPDATE nd_vehicles SET %s = ? WHERE id = ?"):format(statusType) local query = ("UPDATE nd_vehicles SET %s = ? WHERE id = ?"):format(statusType)
MySQL.query(query, {status and 1 or 0, self.id}) MySQL.query(query, {status and 1 or 0, self.id})
@@ -271,14 +309,15 @@ function NDCore.giveVehicleAccess(source, vehicle, access, info)
end end
if not inventoryStarted or not Config.useInventoryForKeys then return end if not inventoryStarted or not Config.useInventoryForKeys then return end
local plate = info?.plate or GetVehicleNumberPlateText(vehicle)
local model = info?.model or GetEntityModel(vehicle)
local modelName = info?.modelName or model and lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", source, model) or ""
local hasKey = ox_inventory:GetSlotIdWithItem(source, "keys", { local hasKey = ox_inventory:GetSlotIdWithItem(source, "keys", {
vehId = vehicleId vehId = vehicleId
}) })
if access and not hasKey then if access and not hasKey then
local plate = info?.plate or GetVehicleNumberPlateText(vehicle)
local model = info?.model or GetEntityModel(vehicle)
local modelName = info?.modelName or model and lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", source, model) or ""
ox_inventory:AddItem(source, "keys", 1, { ox_inventory:AddItem(source, "keys", 1, {
vehOwner = owner or state.owner, vehOwner = owner or state.owner,
vehId = vehicleId, vehId = vehicleId,
@@ -328,17 +367,24 @@ function NDCore.createVehicle(info)
state.locked = true state.locked = true
local keys = info.keys or {} local keys = info.keys or {}
if not properties.plate then if not properties.plate or info.plate then
properties.plate = generateVehiclePlate() properties.plate = info.plate or generateVehiclePlate()
end end
if owner then if owner then
keys[owner] = true keys[owner] = true
state.owner = owner state.owner = owner
end end
if info.blipGroups then
state.blipGroups = info.blipGroups
end
state.keys = keys state.keys = keys
state.props = properties
state.id = vehicleId state.id = vehicleId
if not info.ignoreProps then
state.props = properties
end
local vehicleName local vehicleName
if inventoryStarted and Config.useInventoryForKeys then if inventoryStarted and Config.useInventoryForKeys then
for charId, _ in pairs(keys) do for charId, _ in pairs(keys) do
@@ -354,7 +400,7 @@ function NDCore.createVehicle(info)
NDCore.giveVehicleAccess(playerSource, veh, true, { NDCore.giveVehicleAccess(playerSource, veh, true, {
vehicleId = vehicleId, vehicleId = vehicleId,
netId = netId, netId = netId,
plate = properties?.plate, plate = info.plate or properties?.plate,
model = model, model = model,
vehicleName = vehicleName, vehicleName = vehicleName,
owner = owner owner = owner
@@ -363,6 +409,13 @@ function NDCore.createVehicle(info)
end end
end end
for i=1, 3 do
if DoesEntityExist(veh) then
SetVehicleNumberPlateText(veh, properties.plate)
end
Wait(500)
end
return NDCore.getVehicle(veh) return NDCore.getVehicle(veh)
end end
@@ -423,8 +476,8 @@ end
---@param properties table ---@param properties table
---@param stored boolean ---@param stored boolean
---@return vehicleId number ---@return vehicleId number
function NDCore.setVehicleOwned(playerId, properties, stored) function NDCore.setVehicleOwned(playerId, properties, stored, setPlate)
local plate = generateVehiclePlate() local plate = setPlate or generateVehiclePlate()
properties.plate = plate properties.plate = plate
return MySQL.insert.await("INSERT INTO nd_vehicles (owner, plate, properties, stored) VALUES (?, ?, ?, ?)", {playerId, plate, json.encode(properties), stored and 1 or 0}) return MySQL.insert.await("INSERT INTO nd_vehicles (owner, plate, properties, stored) VALUES (?, ?, ?, ?)", {playerId, plate, json.encode(properties), stored and 1 or 0})
end end
@@ -464,11 +517,11 @@ end
---@param vehicleId number ---@param vehicleId number
---@param coords vector4 ---@param coords vector4
---@return table ---@return table
function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading) function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading, _vehicle)
local player = NDCore.getPlayer(source) local player = NDCore.getPlayer(source)
if not player then return end if not player then return end
local vehicle = NDCore.getVehicleById(vehicleId) local vehicle = _vehicle or NDCore.getVehicleById(vehicleId)
if not vehicle or vehicle.owner ~= player.id then return end if not vehicle or vehicle.owner ~= player.id then return end
if not vehicle.available and not vehicle.impounded then return end if not vehicle.available and not vehicle.impounded then return end
@@ -577,6 +630,16 @@ local function lockNearestVehicle(source, vehId, metadata)
end end
end end
local function hasBlipGroup(player, groups)
groups = groups or {}
for i=1, #groups do
local group = groups[i]
if player.groups[group] then
return true
end
end
end
--- inventory keys using item. --- inventory keys using item.
exports("keys", function(event, item, inventory, slot, data) exports("keys", function(event, item, inventory, slot, data)
if event ~= "usingItem" or not Config.useInventoryForKeys or not inventoryStarted then return end if event ~= "usingItem" or not Config.useInventoryForKeys or not inventoryStarted then return end
@@ -603,7 +666,9 @@ RegisterCommand("getkeys", function(source, args, rawCommand)
local player = NDCore.getPlayer(source) local player = NDCore.getPlayer(source)
local state = Entity(veh).state local state = Entity(veh).state
local owner = state.owner local owner = state.owner
if not owner or owner ~= player.id then return end local blipGroups = state.blipGroups
if not state.id or (not owner or owner ~= player.id) and (not hasBlipGroup(player, blipGroups)) then return end
local props = state.props local props = state.props
ox_inventory:AddItem(source, "keys", 1, { ox_inventory:AddItem(source, "keys", 1, {
@@ -650,32 +715,32 @@ RegisterNetEvent("ND_Vehicles:toggleVehicleLock", function(netId)
end) end)
-- locking of npc vehicles, if the players spawns inside a vehicle it won't be locked. -- locking of npc vehicles, if the players spawns inside a vehicle it won't be locked.
AddEventHandler("entityCreated", function(entity) -- AddEventHandler("entityCreated", function(entity)
local time = os.time() -- local time = os.time()
while not DoesEntityExist(entity) and os.time()-time < 5 do Wait(50) end -- while not DoesEntityExist(entity) and os.time()-time < 5 do Wait(50) end
if not DoesEntityExist(entity) or GetEntityType(entity) ~= 2 then return end -- if not DoesEntityExist(entity) or GetEntityType(entity) ~= 2 then return end
local state = Entity(entity).state -- local state = Entity(entity).state
if state.owner or state.locked ~= nil then return end -- if state.owner or state.locked ~= nil then return end
time = os.time() -- time = os.time()
local driver = GetPedInVehicleSeat(entity, -1) -- local driver = GetPedInVehicleSeat(entity, -1)
while DoesEntityExist(entity) and driver == 0 and os.time()-time < 2 do -- while DoesEntityExist(entity) and driver == 0 and os.time()-time < 2 do
driver = GetPedInVehicleSeat(entity, -1) -- driver = GetPedInVehicleSeat(entity, -1)
Wait(100) -- Wait(100)
end -- end
if not DoesEntityExist(entity) then return end -- if not DoesEntityExist(entity) then return end
if DoesEntityExist(driver) and IsPedAPlayer(driver) then -- if DoesEntityExist(driver) and IsPedAPlayer(driver) then
state.locked = false -- state.locked = false
state.hotwired = true -- state.hotwired = true
end -- end
if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end -- if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end
state.locked = true -- state.locked = true
end) -- end)
-- disables inventory vehicles keys, disabled vehicles keys can no longer be used. Kinda like taking the battery out. -- disables inventory vehicles keys, disabled vehicles keys can no longer be used. Kinda like taking the battery out.
RegisterNetEvent("ND_Vehicles:disableKey", function(slot) RegisterNetEvent("ND_Vehicles:disableKey", function(slot)
@@ -717,72 +782,12 @@ RegisterNetEvent("ND_Vehicles:hotwire", function(netId)
state.hotwired = true state.hotwired = true
end) end)
RegisterNetEvent("ND_Vehicles:storeVehicle", function(netId) RegisterNetEvent("ND_Vehicles:propsApplied", function(netId)
local src = source local veh = NetworkGetEntityFromNetworkId(netId)
local vehicle = NDCore.getVehicle(NetworkGetEntityFromNetworkId(netId)) if not veh or not DoesEntityExist(veh) then return end
if not vehicle then return end
local player = NDCore.getPlayer(src) local state = Entity(veh).state
if not vehicle.setStatus("stored", true) or not player or player.id ~= vehicle.owner then state:set("allPropsApplied", true, true)
return player.notify({
title = locale("garage"),
description = locale("no_owned_veh_nearby"),
type = "error",
position = "bottom",
duration = 3000
})
end
player.notify({
title = locale("garage"),
description = locale("veh_stored_in_garage"),
type = "success",
position = "bottom",
duration = 3000
})
NDCore.giveVehicleAccess(src, vehicle.entity, false, {
vehicleId = vehicle.id,
netId = vehicle.netId,
owner = vehicle.owner
})
end)
local function isParkingAvailable(locations)
for i=1, #locations do
local loc = locations[math.random(1, #locations)]
if #getNearbyVehicles(vec3(loc.x, loc.y, loc.z), 2.0) == 0 then
return loc
end
end
end
RegisterNetEvent("ND_Vehicles:takeVehicle", function(vehId, locations)
local src = source
local vehicle = NDCore.getVehicleById(vehId)
local player = NDCore.getPlayer(src)
if not player or not vehicle or vehicle.owner ~= player.id then return end
local info = NDCore.spawnOwnedVehicle(src, vehicle.id, isParkingAvailable(locations))
if not info then return end
TriggerClientEvent("ND_Vehicles:blip", src, info.netId, true)
if vehicle.impounded then
local reclaimPrice = vehicle.metadata.impoundReclaimPrice or 200
if not player.deductMoney("bank", reclaimPrice, locale("impound_reclaim")) then
return player.notify({
title = locale("impound"),
description = locale("impound_not_enough", reclaimPrice),
type = "error",
position = "bottom"
})
end
player.notify({
title = locale("impound"),
description = locale("impound_paid", reclaimPrice),
type = "success",
position = "bottom"
})
MySQL.query.await("UPDATE nd_vehicles SET impounded = ? WHERE id = ?", {0, vehicle.id})
end
end) end)
lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src) lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src)
@@ -814,6 +819,6 @@ AddEventHandler("ND:characterLoaded", function(player)
if #vehiclesToImpound == 0 then return end if #vehiclesToImpound == 0 then return end
local query = ("UPDATE nd_vehicles SET impounded = ? WHERE owner = ? AND id IN (%s)"):format(table.concat(vehiclesToImpound, ", ")) local query = ("UPDATE nd_vehicles SET stored = ? WHERE owner = ? AND id IN (%s)"):format(table.concat(vehiclesToImpound, ", "))
MySQL.rawExecute(query, {1, player.id}) MySQL.rawExecute(query, {1, player.id})
end) end)