mirror of
https://github.com/ND-Framework/ND_Core.git
synced 2026-08-17 13:56:02 +01:00
820 lines
27 KiB
Lua
820 lines
27 KiB
Lua
local ox_inventory
|
||
local inventoryStarted = false
|
||
local spawnedPlayerVehicles = {}
|
||
local vehicleTypes = json.decode(GetResourceKvpString("ND_Core:vehTypes") or "[]")
|
||
|
||
NDCore.isResourceStarted("ox_inventory", function(started)
|
||
inventoryStarted = started
|
||
if not started then return end
|
||
ox_inventory = exports.ox_inventory
|
||
end)
|
||
|
||
local function getVehicleType(coords, model)
|
||
if vehicleTypes[model] then
|
||
return vehicleTypes[model]
|
||
end
|
||
|
||
local tempVehicle = CreateVehicle(model, coords.x, coords.y, coords.z-5.0, coords.w, true, false)
|
||
local time = os.time()
|
||
|
||
while not DoesEntityExist(tempVehicle) and os.time()-time < 5 do Wait(0) end
|
||
if not DoesEntityExist(tempVehicle) then return end
|
||
|
||
local vehType = GetVehicleType(tempVehicle)
|
||
DeleteEntity(tempVehicle)
|
||
|
||
vehicleTypes[model] = vehType
|
||
SetResourceKvp("ND_Core:vehTypes", json.encode(vehicleTypes))
|
||
|
||
return vehType
|
||
end
|
||
|
||
local function generatePlate()
|
||
local plate = {}
|
||
local i = 1
|
||
local p = 1
|
||
|
||
-- ensure pattern exists and pad to 8 chars
|
||
local pattern = Config.platePattern
|
||
if #pattern < 8 then
|
||
pattern = pattern .. string.rep(" ", 8 - #pattern)
|
||
end
|
||
|
||
while i <= 8 do
|
||
local char = pattern:sub(p, p)
|
||
|
||
if char == "^" then
|
||
-- literal next character
|
||
p = p + 1
|
||
plate[i] = pattern:sub(p, p)
|
||
elseif char == "1" then
|
||
plate[i] = tostring(math.random(0, 9))
|
||
elseif char == "A" then
|
||
plate[i] = string.char(math.random(65, 90)) -- A–Z
|
||
elseif char == "." then
|
||
if math.random(0, 1) == 0 then
|
||
plate[i] = tostring(math.random(0, 9))
|
||
else
|
||
plate[i] = string.char(math.random(65, 90))
|
||
end
|
||
else
|
||
plate[i] = char
|
||
end
|
||
|
||
i = i + 1
|
||
p = p + 1
|
||
end
|
||
|
||
return table.concat(plate)
|
||
end
|
||
|
||
local function generateVehiclePlate(newPlate)
|
||
local plate = newPlate or generatePlate()
|
||
while MySQL.scalar.await("SELECT 1 FROM nd_vehicles WHERE plate = ?", {plate}) do
|
||
plate = generatePlate()
|
||
end
|
||
return plate
|
||
end
|
||
|
||
local function generateTemporaryVehicleId()
|
||
return ("temp_%s%d"):format(string.char(math.random(65, 90)), math.random(1, 999999))
|
||
end
|
||
|
||
local function getVehicleDatabaseInfo(vehicle)
|
||
if not vehicle then return end
|
||
local stored = vehicle.stored == 1
|
||
local impounded = vehicle.impounded == 1
|
||
return {
|
||
id = vehicle.id,
|
||
owner = vehicle.owner,
|
||
plate = vehicle.plate,
|
||
properties = json.decode(vehicle.properties) or {},
|
||
stored = stored,
|
||
impounded = impounded,
|
||
stolen = vehicle.stolen == 1,
|
||
available = not impounded and stored,
|
||
metadata = json.decode(vehicle.metadata) or {}
|
||
}
|
||
end
|
||
|
||
--- get vehicle information queried from the database by the vehicle id
|
||
---@param vehicleId string | numb
|
||
---@return table | nil
|
||
function NDCore.getVehicleById(vehicleId)
|
||
local result = MySQL.query.await("SELECT * FROM nd_vehicles WHERE id = ?", {vehicleId})
|
||
return getVehicleDatabaseInfo(result?[1])
|
||
end
|
||
|
||
--- get vehicle information and functions
|
||
---@param entity number
|
||
---@return table
|
||
function NDCore.getVehicle(entity)
|
||
if not DoesEntityExist(entity) then return end
|
||
|
||
local state = Entity(entity).state
|
||
local self = {
|
||
entity = entity,
|
||
id = state.id,
|
||
owner = state.owner,
|
||
keys = state.keys,
|
||
properties = state.props,
|
||
locked = state.locked,
|
||
hotwired = state.hotwired,
|
||
metadata = state.metadata or {},
|
||
netId = NetworkGetNetworkIdFromEntity(entity)
|
||
}
|
||
|
||
--- delete the vehicle
|
||
function self.delete(saveProperties)
|
||
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
|
||
end
|
||
DeleteEntity(entity)
|
||
end
|
||
|
||
|
||
-- remvoe vehicle from db.
|
||
function self.remove(keepEntity)
|
||
if not keepEntity and DoesEntityExist(entity) then
|
||
DeleteEntity(entity)
|
||
end
|
||
MySQL.query("DELETE FROM nd_vehicles WHERE id = ?", {self.id})
|
||
end
|
||
|
||
--- set vehicle properties
|
||
---@param props table
|
||
function self.setProperties(props)
|
||
if not DoesEntityExist(entity) then return end
|
||
local properties = type(props) == "string" and json.decode(props) or props
|
||
local state = Entity(entity).state
|
||
|
||
if self.properties?.callsign then
|
||
properties.callsign = true
|
||
end
|
||
|
||
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})
|
||
end
|
||
|
||
--- set vehicle locked/unlocked
|
||
---@param status boolean
|
||
function self.setLocked(status)
|
||
if not DoesEntityExist(entity) then return end
|
||
local state = Entity(entity).state
|
||
state.locked = status
|
||
self.locked = status
|
||
end
|
||
|
||
--- update the vehicle plate
|
||
---@param plate string
|
||
---@return boolean
|
||
function self.setPlate(plate)
|
||
if not DoesEntityExist(entity) or MySQL.scalar.await("SELECT 1 FROM nd_vehicles WHERE plate = ?", {plate}) then return end
|
||
if self.properties then
|
||
self.properties.plate = plate
|
||
local state = Entity(entity).state
|
||
state.props = self.properties
|
||
end
|
||
|
||
if not self.id or not self.owner then return end
|
||
MySQL.query("UPDATE nd_vehicles SET plate = ? WHERE id = ?", {plate, self.id})
|
||
return true
|
||
end
|
||
|
||
--- set the vehicle availability status
|
||
---@param statusType string
|
||
---@param status boolean
|
||
function self.setStatus(statusType, status)
|
||
if not lib.table.contains({"stored", "impounded", "stolen"}, statusType) then return end
|
||
if statusType ~= "stolen" 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})
|
||
return true
|
||
end
|
||
|
||
function self.setMetadata(key, value)
|
||
self.metadata[key] = value
|
||
|
||
if DoesEntityExist(entity) then
|
||
local state = Entity(self.entity).state
|
||
|
||
if not state.metadata then
|
||
state.metadata = self.metadata
|
||
else
|
||
state.metadata[key] = value
|
||
end
|
||
end
|
||
|
||
if not self.id or not self.owner then
|
||
lib.print.debug(('Skipped storing metadata for vehicle %s as it does not have an id/owner associated with it.'):format(self.entity))
|
||
return
|
||
end
|
||
|
||
MySQL.query("UPDATE nd_vehicles SET metadata = ? WHERE id = ?", {json.encode(self.metadata), self.id})
|
||
end
|
||
|
||
if self.id and self.owner then
|
||
spawnedPlayerVehicles[self.id] = self
|
||
end
|
||
|
||
return self
|
||
end
|
||
|
||
--- get a characters owned vehicles
|
||
---@param characterId number
|
||
---@return table
|
||
function NDCore.getVehicles(characterId)
|
||
local result = MySQL.query.await("SELECT * FROM nd_vehicles WHERE owner = ?", {characterId})
|
||
if not result then return {} end
|
||
|
||
local vehicles = {}
|
||
for _, vehicle in pairs(result) do
|
||
local info = getVehicleDatabaseInfo(vehicle)
|
||
if info then
|
||
vehicles[#vehicles+1] = info
|
||
end
|
||
end
|
||
return vehicles
|
||
end
|
||
|
||
--- give a player keys/access to a vehicle
|
||
---@param source number
|
||
---@param vehicle number
|
||
---@param access boolean
|
||
---@param info table
|
||
function NDCore.giveVehicleAccess(source, vehicle, access, info)
|
||
if not vehicle or not DoesEntityExist(vehicle) then return end
|
||
local state = Entity(vehicle).state
|
||
local netId = info?.netId or NetworkGetNetworkIdFromEntity(vehicle)
|
||
local vehicleId = info?.vehicleId or state.id or generateTemporaryVehicleId()
|
||
|
||
if not state.id then
|
||
state.id = vehicleId
|
||
end
|
||
|
||
local player = NDCore.getPlayer(source)
|
||
if player then
|
||
local keys = state.keys or {}
|
||
keys[player.id] = access
|
||
state.keys = keys
|
||
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
|
||
ox_inventory:AddItem(source, "keys", 1, {
|
||
vehOwner = owner or state.owner,
|
||
vehId = vehicleId,
|
||
vehPlate = plate,
|
||
vehModel = modelName,
|
||
keyEnabled = true,
|
||
vehNetId = netId
|
||
})
|
||
elseif not access and hasKey then
|
||
ox_inventory:RemoveItem(source, "keys", 1, nil, hasKey)
|
||
end
|
||
end
|
||
|
||
--- spawn a vehicle, set info & give keys.
|
||
---@param info table
|
||
---@return table
|
||
function NDCore.createVehicle(info)
|
||
local owner = info.owner
|
||
local vehicleId = info.vehicleId or generateTemporaryVehicleId()
|
||
local properties = info.properties or {}
|
||
local coords = info.coords
|
||
local spawnCoords = coords
|
||
|
||
local coordType = type(coords)
|
||
if coordType == "vector3" or coordType == "table" then
|
||
spawnCoords = vector4(coords.x, coords.y, coords.z, coords.w or coords.h or info.heading or 0.0)
|
||
end
|
||
if not spawnCoords then
|
||
return Citizen.Trace("NDCore.createVehicle", "spawnCoords not found")
|
||
end
|
||
|
||
local model = info.model or properties.model
|
||
local vehType = getVehicleType(spawnCoords, model)
|
||
if not vehType then
|
||
return Citizen.Trace("NDCore.createVehicle", "vehType not found")
|
||
end
|
||
|
||
local veh = CreateVehicleServerSetter(model, vehType, spawnCoords.x, spawnCoords.y, spawnCoords.z, spawnCoords.w)
|
||
local time = os.time()
|
||
while not DoesEntityExist(veh) and os.time()-time < 5 do Wait(5) end
|
||
if not veh or not DoesEntityExist(veh) then
|
||
return Citizen.Trace("NDCore.createVehicle", "vehicle entity doesn't exist")
|
||
end
|
||
|
||
local netId = NetworkGetNetworkIdFromEntity(veh)
|
||
local state = Entity(veh).state
|
||
state.locked = true
|
||
local keys = info.keys or {}
|
||
|
||
if not properties.plate then
|
||
properties.plate = generateVehiclePlate()
|
||
end
|
||
if owner then
|
||
keys[owner] = true
|
||
state.owner = owner
|
||
end
|
||
|
||
state.keys = keys
|
||
state.props = properties
|
||
state.id = vehicleId
|
||
local vehicleName
|
||
if inventoryStarted and Config.useInventoryForKeys then
|
||
for charId, _ in pairs(keys) do
|
||
local playerSource = charId == owner and info.source
|
||
if not playerSource then
|
||
local player = NDCore.getPlayers("id", charId, true)[1]
|
||
playerSource = player and player.source
|
||
end
|
||
if playerSource then
|
||
if not vehicleName then
|
||
vehicleName = lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", playerSource, model) or ""
|
||
end
|
||
NDCore.giveVehicleAccess(playerSource, veh, true, {
|
||
vehicleId = vehicleId,
|
||
netId = netId,
|
||
plate = properties?.plate,
|
||
model = model,
|
||
vehicleName = vehicleName,
|
||
owner = owner
|
||
})
|
||
end
|
||
end
|
||
end
|
||
|
||
return NDCore.getVehicle(veh)
|
||
end
|
||
|
||
--- transfer ownership of a vehicle between players
|
||
---@param vehicleId number|string
|
||
---@param fromSource number
|
||
---@param toSource number
|
||
---@return boolean
|
||
function NDCore.transferVehicleOwnership(vehicleId, fromSource, toSource)
|
||
local playerFrom = NDCore.getPlayer(fromSource)
|
||
local playerTo = NDCore.getPlayer(toSource)
|
||
if not playerFrom or not playerTo then return end
|
||
|
||
local vehicle = NDCore.getVehicleById(vehicleId)
|
||
if not vehicle or vehicle.owner ~= playerFrom.id then return end
|
||
|
||
MySQL.query.await("UPDATE nd_vehicles SET owner = ? WHERE id = ?", {playerTo.id, vehicleId})
|
||
local vehicleInfo = spawnedPlayerVehicles[vehicleId]
|
||
|
||
if vehicleInfo then
|
||
local veh, netId, model = vehicleInfo.entity, vehicleInfo.netId, GetEntityModel(veh)
|
||
playerFrom.triggerEvent("ND_Vehicles:blip", netId, false)
|
||
playerTo.triggerEvent("ND_Vehicles:blip", netId, true)
|
||
|
||
NDCore.giveVehicleAccess(fromSource, veh, false, {
|
||
vehicleId = vehicleId,
|
||
netId = netId,
|
||
model = model,
|
||
owner = playerFrom.id
|
||
})
|
||
|
||
local state = Entity(veh).state
|
||
state.owner = playerTo.id
|
||
NDCore.giveVehicleAccess(toSource, veh, true, {
|
||
vehicleId = vehicleId,
|
||
netId = netId,
|
||
model = model,
|
||
owner = playerTo.id
|
||
})
|
||
end
|
||
|
||
playerFrom.notify({
|
||
title = locale("vehicle_ownership_transfered"),
|
||
description = locale("vehicle_ownership_transfered2", vehicle.plate),
|
||
position = "bottom-right",
|
||
type = "success"
|
||
})
|
||
playerTo.notify({
|
||
title = locale("vehicle_ownership_received"),
|
||
description = locale("vehicle_ownership_received2", vehicle.plate),
|
||
position = "bottom-right"
|
||
})
|
||
return true
|
||
end
|
||
|
||
--- set vehicle as owned by player
|
||
---@param playerId number
|
||
---@param properties table
|
||
---@param stored boolean
|
||
---@return vehicleId number
|
||
function NDCore.setVehicleOwned(playerId, properties, stored)
|
||
local plate = 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
|
||
|
||
--- give a player access to another players vehicle as if they are next to eachother and hand the keys.
|
||
---@param source number
|
||
---@param target number
|
||
---@param vehicle number
|
||
---@return boolean
|
||
function NDCore.shareVehicleKeys(source, target, vehicle)
|
||
if not vehicle or not DoesEntityExist(vehicle) then return end
|
||
|
||
local player = NDCore.getPlayer(source)
|
||
local targetPlayer = NDCore.getPlayer(target)
|
||
local state = Entity(vehicle).state
|
||
if not targetPlayer or not player or player.id ~= state.owner then return end
|
||
|
||
NDCore.giveVehicleAccess(target, vehicle, true)
|
||
local plate = GetVehicleNumberPlateText(vehicle)
|
||
|
||
player.notify({
|
||
title = locale("keys_shared"),
|
||
description = locale("keys_shared2", plate),
|
||
position = "bottom-right",
|
||
type = "success"
|
||
})
|
||
targetPlayer.notify({
|
||
title = locale("keys_received"),
|
||
description = locale("keys_received2", plate),
|
||
position = "bottom-right"
|
||
})
|
||
return true
|
||
end
|
||
|
||
--- spawned a vehicle that's owned by the player and checks for availability
|
||
---@param source number
|
||
---@param vehicleId number
|
||
---@param coords vector4
|
||
---@return table
|
||
function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading)
|
||
local player = NDCore.getPlayer(source)
|
||
if not player then return end
|
||
|
||
local vehicle = 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
|
||
|
||
MySQL.query.await("UPDATE nd_vehicles SET stored = ? WHERE id = ?", {0, vehicleId})
|
||
|
||
local properties = vehicle.properties
|
||
if properties.callsign then
|
||
local callsign = player.getMetadata("callsign")
|
||
if not callsign or callsign == "" then goto skip end
|
||
|
||
callsign = tostring(callsign)
|
||
if not callsign or callsign:len() ~= 3 then goto skip end
|
||
|
||
properties.modFender = tonumber(callsign:sub(1, 1))
|
||
properties.modRightFender = tonumber(callsign:sub(2, 2))
|
||
properties.modRoof = tonumber(callsign:sub(3, 3))
|
||
end
|
||
|
||
::skip::
|
||
|
||
return NDCore.createVehicle({
|
||
owner = player.id,
|
||
model = properties.model,
|
||
coords = vec4(coords.x, coords.y, coords.z, coords.w or coords.heading or heading),
|
||
properties = properties,
|
||
vehicleId = vehicleId,
|
||
source = source
|
||
})
|
||
end
|
||
|
||
local function toggleVehicleLock(source, entity, nearby, metadata)
|
||
local player = NDCore.getPlayer(source)
|
||
|
||
if metadata and not metadata.keyEnabled then
|
||
return player.notify({
|
||
title = locale("no_signal"),
|
||
description = locale("veh_key_disabled"),
|
||
type = "error",
|
||
position = "bottom-right",
|
||
duration = 3000
|
||
})
|
||
elseif not nearby then
|
||
return player.notify({
|
||
title = locale("no_signal"),
|
||
description = locale("veh_far_away"),
|
||
type = "error",
|
||
position = "bottom-right",
|
||
duration = 3000
|
||
})
|
||
end
|
||
|
||
local vehicle = NDCore.getVehicle(entity)
|
||
local locked = not vehicle.locked
|
||
vehicle.setLocked(locked)
|
||
|
||
player.triggerEvent("ND_Vehicles:keyFob", vehicle.netId)
|
||
if locked then
|
||
return player.notify({
|
||
title = locale("veh_locked"),
|
||
description = locale("veh_locked2"),
|
||
type = "success",
|
||
position = "bottom-right",
|
||
duration = 3000
|
||
})
|
||
end
|
||
player.notify({
|
||
title = locale("veh_unlocked"),
|
||
description = locale("veh_unlocked2"),
|
||
type = "inform",
|
||
position = "bottom-right",
|
||
duration = 3000
|
||
})
|
||
end
|
||
|
||
local function getNearbyVehicles(coords, range)
|
||
local nearby = {}
|
||
local vehicles = GetAllVehicles()
|
||
for i=1, #vehicles do
|
||
local veh = vehicles[i]
|
||
local vehCoords = GetEntityCoords(veh)
|
||
if #(coords-vehCoords) < range then
|
||
nearby[#nearby+1] = veh
|
||
end
|
||
end
|
||
return nearby
|
||
end
|
||
|
||
local function lockNearestVehicle(source, vehId, metadata)
|
||
local ped = GetPlayerPed(source)
|
||
local pedCoords = GetEntityCoords(ped)
|
||
local veh = spawnedPlayerVehicles[vehId]?.entity
|
||
|
||
if veh and DoesEntityExist(veh) then
|
||
local vehCoords = GetEntityCoords(veh)
|
||
if not pedCoords or not vehCoords then return end
|
||
return toggleVehicleLock(source, veh, #(pedCoords-vehCoords) < 25.0, metadata)
|
||
end
|
||
|
||
local vehicles = getNearbyVehicles(pedCoords, 25.0)
|
||
for i=1, #vehicles do
|
||
local veh = vehicles[i]
|
||
local state = Entity(veh).state
|
||
if state and state.id == vehId then
|
||
return toggleVehicleLock(source, veh, true, metadata)
|
||
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
|
||
local metadata
|
||
for i=1, #inventory.items do
|
||
local item = inventory.items[i]
|
||
if item and item.slot == slot then
|
||
metadata = item.metadata
|
||
break
|
||
end
|
||
end
|
||
|
||
if not metadata then return false end
|
||
lockNearestVehicle(inventory.id, metadata.vehId, metadata)
|
||
return false
|
||
end)
|
||
|
||
-- if using inventory and inventory keys players can spawn keys when in their vehicle with this command.
|
||
RegisterCommand("getkeys", function(source, args, rawCommand)
|
||
if not Config.useInventoryForKeys or not inventoryStarted then return end
|
||
local veh = GetVehiclePedIsIn(GetPlayerPed(source))
|
||
if not veh or veh == 0 then return end
|
||
|
||
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 props = state.props
|
||
ox_inventory:AddItem(source, "keys", 1, {
|
||
vehOwner = owner,
|
||
vehId = state.id,
|
||
vehPlate = props.plate,
|
||
vehModel = lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", source, props.model),
|
||
keyEnabled = true,
|
||
vehNetId = NetworkGetNetworkIdFromEntity(veh)
|
||
})
|
||
end, false)
|
||
|
||
-- key sharing if not using inventory or inventory keys.
|
||
RegisterCommand("givekeys", function(source, args, rawCommand)
|
||
if Config.useInventoryForKeys and inventoryStarted then return end
|
||
|
||
local src = source
|
||
if not args[1] then return end
|
||
local target = tonumber(args[1])
|
||
if not GetPlayerPing(target) then return end
|
||
|
||
local veh = GetVehiclePedIsIn(GetPlayerPed(src))
|
||
if veh == 0 then
|
||
veh = GetVehiclePedIsIn(GetPlayerPed(src), true)
|
||
if veh == 0 then return end
|
||
end
|
||
|
||
NDCore.shareVehicleKeys(src, target, veh)
|
||
end, false)
|
||
|
||
-- lock/unlock vehicles if they're within range.
|
||
RegisterNetEvent("ND_Vehicles:toggleVehicleLock", function(netId)
|
||
if Config.useInventoryForKeys and inventoryStarted then return end
|
||
|
||
local src = source
|
||
local veh = NetworkGetEntityFromNetworkId(netId)
|
||
if not veh or not DoesEntityExist(veh) then return end
|
||
|
||
local ped = GetPlayerPed(src)
|
||
local pedCoords = GetEntityCoords(ped)
|
||
local vehCoords = GetEntityCoords(veh)
|
||
if not pedCoords or not vehCoords then return end
|
||
toggleVehicleLock(src, veh, #(pedCoords-vehCoords) < 25.0)
|
||
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
|
||
|
||
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
|
||
|
||
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 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)
|
||
|
||
-- disables inventory vehicles keys, disabled vehicles keys can no longer be used. Kinda like taking the battery out.
|
||
RegisterNetEvent("ND_Vehicles:disableKey", function(slot)
|
||
local src = source
|
||
local key = ox_inventory:GetSlot(src, slot)
|
||
local metadata = key.metadata
|
||
if not metadata.keyEnabled then return end
|
||
metadata.keyEnabled = false
|
||
ox_inventory:SetMetadata(src, slot, key.metadata)
|
||
end)
|
||
|
||
-- sync alarm when vehicle is lockpicked.
|
||
RegisterNetEvent("ND_Vehicles:lockpick", function(netId, success)
|
||
local src = source
|
||
local veh = NetworkGetEntityFromNetworkId(netId)
|
||
if not veh or not DoesEntityExist(veh) then return end
|
||
|
||
local ped = GetPlayerPed(src)
|
||
local pedCoords = GetEntityCoords(ped)
|
||
local vehCoords = GetEntityCoords(veh)
|
||
if #(pedCoords-vehCoords) > 5.0 then return end
|
||
|
||
local owner = NetworkGetEntityOwner(veh)
|
||
TriggerClientEvent("ND_Vehicles:syncAlarm", owner, netId)
|
||
|
||
if not success then return end
|
||
local state = Entity(veh).state
|
||
state.locked = false
|
||
end)
|
||
|
||
-- sync alarm if vehicle gets hotwired.
|
||
RegisterNetEvent("ND_Vehicles:hotwire", function(netId)
|
||
local src = source
|
||
local ped = GetPlayerPed(src)
|
||
local playerVeh = GetVehiclePedIsIn(ped)
|
||
local veh = NetworkGetEntityFromNetworkId(netId)
|
||
if not playerVeh or playerVeh == 0 or playerVeh ~= veh then return end
|
||
local state = Entity(veh).state
|
||
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
|
||
|
||
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
|
||
end)
|
||
|
||
lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src)
|
||
local player = NDCore.getPlayer(src)
|
||
if not player then return end
|
||
return NDCore.getVehicles(player.id)
|
||
end)
|
||
|
||
AddEventHandler("ND:characterLoaded", function(player)
|
||
local ownedExistingVehicles = {}
|
||
local ownedVehicles = NDCore.getVehicles(player.id)
|
||
local vehicles = GetAllVehicles()
|
||
local vehiclesToImpound = {}
|
||
|
||
for i=1, #vehicles do
|
||
local veh = vehicles[i]
|
||
local state = Entity(veh).state
|
||
if state.owner == player.id then
|
||
ownedExistingVehicles[#ownedExistingVehicles+1] = state.id
|
||
end
|
||
end
|
||
|
||
for i=1, #ownedVehicles do
|
||
local veh = ownedVehicles[i]
|
||
if veh and not veh.stored and not veh.impounded and not lib.table.contains(ownedExistingVehicles, veh.id) then
|
||
vehiclesToImpound[#vehiclesToImpound+1] = veh.id
|
||
end
|
||
end
|
||
|
||
if #vehiclesToImpound == 0 then return end
|
||
|
||
local query = ("UPDATE nd_vehicles SET impounded = ? WHERE owner = ? AND id IN (%s)"):format(table.concat(vehiclesToImpound, ", "))
|
||
MySQL.rawExecute(query, {1, player.id})
|
||
end)
|