diff --git a/client/vehicle/main.lua b/client/vehicle.lua similarity index 85% rename from client/vehicle/main.lua rename to client/vehicle.lua index 5b4a431..dabbac7 100644 --- a/client/vehicle/main.lua +++ b/client/vehicle.lua @@ -242,30 +242,36 @@ local function getVehicleBlipSprite(entity) end local function getVehFromNetId(netId) - local time = GetCloudTimeAsInt() - while not NetworkDoesNetworkIdExist(netId) or not NetworkDoesEntityExistWithNetworkId(netId) and time-GetCloudTimeAsInt() < 5 do + local startTime = GetCloudTimeAsInt() + while (not NetworkDoesNetworkIdExist(netId) or not NetworkDoesEntityExistWithNetworkId(netId)) and (GetCloudTimeAsInt() - startTime < 5) do Wait(100) end return NetToVeh(netId) end -RegisterNetEvent("ND_Vehicles:blip", function(netId, status) - local veh = getVehFromNetId(netId) - if not veh then return end - if not status then - local blip = GetBlipFromEntity(veh) - if not blip or not DoesBlipExist(blip) then return end - return RemoveBlip(blip) + +local function setVehicleBlip(veh, status, groupVehicle) + local currentBlip = GetBlipFromEntity(veh) + if currentBlip and DoesBlipExist(currentBlip) then + RemoveBlip(currentBlip) end + if not status then return end + local blip = AddBlipForEntity(veh) SetBlipSprite(blip, getVehicleBlipSprite(veh)) SetBlipColour(blip, 0) SetBlipScale(blip, 0.8) SetBlipAsShortRange(blip, true) BeginTextCommandSetBlipName("STRING") - AddTextComponentSubstringPlayerName(locale("personal_vehicle")) + AddTextComponentSubstringPlayerName(groupVehicle and locale("group_vehicle") or locale("personal_vehicle")) 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) RegisterNetEvent("ND_Vehicles:syncAlarm", function(netId) @@ -284,13 +290,39 @@ RegisterNetEvent("ND_VehicleSystem:setOwnedIfNot", function(netId) end) AddStateBagChangeHandler("props", nil, function(bagName, key, value, reserved, replicated) - local entity = GetEntityFromStateBagName(bagName) - if not value or not DoesEntityExist(entity) or NetworkGetEntityOwner(entity) ~= cache.playerId then return end + if not value then return end + local props = value if type(value) == "string" then props = json.decode(value) 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) + 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) local playingKey = 0 @@ -411,6 +443,17 @@ local function hasVehicleKeys(veh, checkEngine) return hasKey or checkEngine and state.hotwired 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 time = GetCloudTimeAsInt() if time-keyCheckTime.lastCheck < 5 then @@ -526,6 +569,33 @@ CreateThread(function() 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 state = playerVehicle and Entity(playerVehicle).state if not playerVehicle or state.hotwired then return end @@ -597,34 +667,51 @@ local function lockpickVehicle() local veh = lib.getClosestVehicle(pos, 2.5, false) if not veh then return end - local dificulties = { - "easy", - "medium", - "hard" - } - local dificultyTime = { - easy = 500, - medium = 800, - hard = 1000 - } - - lib.requestAnimDict("veh@break_in@0h@p_m_one@") - for i=1, Config.lockpickTries do - TaskPlayAnimAdvanced(cache.ped, "veh@break_in@0h@p_m_one@", "std_force_entry_ds", pos.x, pos.y, pos.z+0.025, rot.x, rot.y, rot.z, 8.0, 8.0, 1800, 28, 0.1) - local dificulty = dificulties[math.random(1, #dificulties)] - local success = lib.skillCheck(dificulty) + 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 - Wait(dificultyTime[dificulty]) + else + local dificulties = { + "easy", + "medium", + "hard" + } + local dificultyTime = { + easy = 500, + medium = 800, + hard = 1000 + } + + lib.requestAnimDict("veh@break_in@0h@p_m_one@") + for i=1, Config.lockpickTries do + TaskPlayAnimAdvanced(cache.ped, "veh@break_in@0h@p_m_one@", "std_force_entry_ds", pos.x, pos.y, pos.z+0.025, rot.x, rot.y, rot.z, 8.0, 8.0, 1800, 28, 0.1) + local dificulty = dificulties[math.random(1, #dificulties)] + local success = lib.skillCheck(dificulty) + if not success or not DoesEntityExist(veh) or #(pos-GetEntityCoords(veh)) > 2.5 then + TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), false) + return false, true + end + Wait(dificultyTime[dificulty]) + end end veh = lib.getClosestVehicle(pos, 2.5, false) if not veh then return false, true end TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), true) PlaySoundFromEntity(-1, "Remote_Control_Fob", cache.ped, "PI_Menu_Sounds", true, 0) - return true, true + + if lockPickResource then + return true, false + else + return true, true + end end exports("lockpick", function(data, slot) diff --git a/client/vehicle/data.lua b/client/vehicle/data.lua deleted file mode 100644 index 69800cd..0000000 --- a/client/vehicle/data.lua +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/client/vehicle/garages.lua b/client/vehicle/garages.lua deleted file mode 100644 index abeed9d..0000000 --- a/client/vehicle/garages.lua +++ /dev/null @@ -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 diff --git a/fxmanifest.lua b/fxmanifest.lua index c41acd5..653c332 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -8,16 +8,25 @@ fx_version "cerulean" game "gta5" lua54 "yes" +files { + "init.lua", + "compatibility/**/locale.lua", + "locales/*.json", + "ui/**" +} + +ui_page "ui/index.html" + shared_script "@ox_lib/init.lua" client_scripts { "client/main.lua", "shared/functions.lua", "client/peds.lua", - "client/vehicle/main.lua", - "client/vehicle/garages.lua", + "client/vehicle.lua", "client/functions.lua", "client/events.lua", "client/death.lua", + "client/groupadmin.lua", "compatibility/**/client.lua" } server_scripts { @@ -27,17 +36,12 @@ server_scripts { "server/player.lua", "server/vehicle.lua", "server/functions.lua", + "server/groups.lua", + "server/groupadmin.lua", "compatibility/**/server.lua", "server/commands.lua" } -files { - "init.lua", - "client/vehicle/data.lua", - "compatibility/**/locale.lua", - "locales/*.json" -} - dependencies { "ox_lib", "oxmysql" diff --git a/locales/en.json b/locales/en.json index a951365..8ef501f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -66,6 +66,7 @@ "view_impounded_vehs": "View impounded vehicles", "view_garage": "View garage", "personal_vehicle": "Personal vehicle", + "group_vehicle": "Vehicle", "keybind_carkey": "Unlock/lock vehicle (double click)", "progress_hotwiring": "Hotwiring", "cruise_control": "Cruise control", diff --git a/locales/sv.json b/locales/sv.json index 0cb2c6f..e25b4b2 100644 --- a/locales/sv.json +++ b/locales/sv.json @@ -66,6 +66,7 @@ "view_impounded_vehs": "Visa beslagtagna fordon", "view_garage": "Visa garage", "personal_vehicle": "Personligt fordon", + "group_vehicle": "Fordon", "keybind_carkey": "Lås/öppna fordon (dubbelklick)", "progress_hotwiring": "Kopplar om", "cruise_control": "Farthållare", diff --git a/server/vehicle.lua b/server/vehicle.lua index 094b1c0..9c06ff3 100644 --- a/server/vehicle.lua +++ b/server/vehicle.lua @@ -84,11 +84,14 @@ local function getVehicleDatabaseInfo(vehicle) if not vehicle then return end local stored = vehicle.stored == 1 local impounded = vehicle.impounded == 1 + local properties = json.decode(vehicle.properties) or {} + properties.plate = vehicle.plate + return { id = vehicle.id, owner = vehicle.owner, plate = vehicle.plate, - properties = json.decode(vehicle.properties) or {}, + properties = properties, stored = stored, impounded = impounded, stolen = vehicle.stolen == 1, @@ -97,6 +100,14 @@ local function getVehicleDatabaseInfo(vehicle) } end +function NDCore.generateVehiclePlate() + return generatePlate() +end + +function NDCore.generateUniqueVehiclePlate() + return generateVehiclePlate() +end + --- get vehicle information queried from the database by the vehicle id ---@param vehicleId string | numb ---@return table | nil @@ -124,19 +135,44 @@ function NDCore.getVehicle(entity) netId = NetworkGetNetworkIdFromEntity(entity) } + -- save properties to db + function self.saveProperties(props, propsToSave) + local properties = {} + local vehProps = props or lib.callback.await("ND_Vehicles:getProps", NetworkGetEntityOwner(entity), self.netId) + + 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 + properties.callsign = true + end + + self.properties = properties + state.props = properties + + MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id}) + end + --- delete the vehicle - function self.delete(saveProperties) + function self.delete(saveProperties, propsToSave) if not DoesEntityExist(entity) then return end if saveProperties and self.id and self.owner then - local properties = lib.callback.await("ND_Vehicles:getProps", NetworkGetEntityOwner(entity), self.netId) - if properties then - if self.properties?.callsign then - properties.callsign = true - end - MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id}) - end + self.saveProperties(false, propsToSave) end - DeleteEntity(entity) + if DoesEntityExist(entity) then + DeleteEntity(entity) + end + return true end @@ -159,11 +195,12 @@ function NDCore.getVehicle(entity) properties.callsign = true end + state.allPropsApplied = false state.props = properties self.properties = properties 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 --- set vehicle locked/unlocked @@ -183,6 +220,7 @@ function NDCore.getVehicle(entity) if self.properties then self.properties.plate = plate local state = Entity(entity).state + state.allPropsApplied = false state.props = self.properties end @@ -194,9 +232,9 @@ function NDCore.getVehicle(entity) --- set the vehicle availability status ---@param statusType string ---@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 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 local query = ("UPDATE nd_vehicles SET %s = ? WHERE id = ?"):format(statusType) MySQL.query(query, {status and 1 or 0, self.id}) @@ -271,14 +309,15 @@ function NDCore.giveVehicleAccess(source, vehicle, access, info) 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", { vehId = vehicleId }) 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, { vehOwner = owner or state.owner, vehId = vehicleId, @@ -328,17 +367,24 @@ function NDCore.createVehicle(info) state.locked = true local keys = info.keys or {} - if not properties.plate then - properties.plate = generateVehiclePlate() + if not properties.plate or info.plate then + properties.plate = info.plate or generateVehiclePlate() end if owner then keys[owner] = true state.owner = owner end + if info.blipGroups then + state.blipGroups = info.blipGroups + end state.keys = keys - state.props = properties state.id = vehicleId + + if not info.ignoreProps then + state.props = properties + end + local vehicleName if inventoryStarted and Config.useInventoryForKeys then for charId, _ in pairs(keys) do @@ -354,7 +400,7 @@ function NDCore.createVehicle(info) NDCore.giveVehicleAccess(playerSource, veh, true, { vehicleId = vehicleId, netId = netId, - plate = properties?.plate, + plate = info.plate or properties?.plate, model = model, vehicleName = vehicleName, owner = owner @@ -363,6 +409,13 @@ function NDCore.createVehicle(info) end end + for i=1, 3 do + if DoesEntityExist(veh) then + SetVehicleNumberPlateText(veh, properties.plate) + end + Wait(500) + end + return NDCore.getVehicle(veh) end @@ -423,8 +476,8 @@ end ---@param properties table ---@param stored boolean ---@return vehicleId number -function NDCore.setVehicleOwned(playerId, properties, stored) - local plate = generateVehiclePlate() +function NDCore.setVehicleOwned(playerId, properties, stored, setPlate) + local plate = setPlate or generateVehiclePlate() 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}) end @@ -464,11 +517,11 @@ end ---@param vehicleId number ---@param coords vector4 ---@return table -function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading) +function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading, _vehicle) local player = NDCore.getPlayer(source) 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.available and not vehicle.impounded then return end @@ -577,6 +630,16 @@ local function lockNearestVehicle(source, vehId, metadata) 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. exports("keys", function(event, item, inventory, slot, data) 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 state = Entity(veh).state 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 ox_inventory:AddItem(source, "keys", 1, { @@ -650,32 +715,32 @@ RegisterNetEvent("ND_Vehicles:toggleVehicleLock", function(netId) end) -- locking of npc vehicles, if the players spawns inside a vehicle it won't be locked. -AddEventHandler("entityCreated", function(entity) - local time = os.time() - while not DoesEntityExist(entity) and os.time()-time < 5 do Wait(50) end +-- AddEventHandler("entityCreated", function(entity) +-- local time = os.time() +-- 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 - if state.owner or state.locked ~= nil then return end +-- local state = Entity(entity).state +-- if state.owner or state.locked ~= nil then return end - time = os.time() - local driver = GetPedInVehicleSeat(entity, -1) - while DoesEntityExist(entity) and driver == 0 and os.time()-time < 2 do - driver = GetPedInVehicleSeat(entity, -1) - Wait(100) - end +-- time = os.time() +-- local driver = GetPedInVehicleSeat(entity, -1) +-- while DoesEntityExist(entity) and driver == 0 and os.time()-time < 2 do +-- driver = GetPedInVehicleSeat(entity, -1) +-- Wait(100) +-- end - if not DoesEntityExist(entity) then return end +-- if not DoesEntityExist(entity) then return end - if DoesEntityExist(driver) and IsPedAPlayer(driver) then - state.locked = false - state.hotwired = true - end +-- if DoesEntityExist(driver) and IsPedAPlayer(driver) then +-- state.locked = false +-- state.hotwired = true +-- end - if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end - state.locked = true -end) +-- if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end +-- state.locked = true +-- end) -- disables inventory vehicles keys, disabled vehicles keys can no longer be used. Kinda like taking the battery out. RegisterNetEvent("ND_Vehicles:disableKey", function(slot) @@ -717,72 +782,12 @@ RegisterNetEvent("ND_Vehicles:hotwire", function(netId) state.hotwired = true end) -RegisterNetEvent("ND_Vehicles:storeVehicle", function(netId) - local src = source - local vehicle = NDCore.getVehicle(NetworkGetEntityFromNetworkId(netId)) - if not vehicle then return end +RegisterNetEvent("ND_Vehicles:propsApplied", function(netId) + local veh = NetworkGetEntityFromNetworkId(netId) + if not veh or not DoesEntityExist(veh) then return end - local player = NDCore.getPlayer(src) - if not vehicle.setStatus("stored", true) or not player or player.id ~= vehicle.owner then - 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 + local state = Entity(veh).state + state:set("allPropsApplied", true, true) end) lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src) @@ -814,6 +819,6 @@ AddEventHandler("ND:characterLoaded", function(player) 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}) end)