Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server)!: Vehicle Spawning Overhaul #71

Merged
merged 9 commits into from
May 28, 2023
34 changes: 19 additions & 15 deletions client/events.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
local utils = require 'client.utils'

-- Player load and unload handling
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
ShutdownLoadingScreenNui()
Expand Down Expand Up @@ -129,26 +131,28 @@ end)

-- Vehicle Commands

---@param vehName string
RegisterNetEvent('QBCore:Command:SpawnVehicle', function(vehName)
local hash = joaat(vehName)
if not IsModelInCdimage(hash) then return end
RequestModel(hash)
while not HasModelLoaded(hash) do
Wait(0)
end
utils.entityStateHandler('initVehicle', function(entity, _, value)
if not value then return end

for i = -1, 0 do
local ped = GetPedInVehicleSeat(entity, i)

if ped ~= cache.ped and ped > 0 and NetworkGetEntityOwner(ped) == cache.playerId then
DeleteEntity(ped)
end
end

if NetworkGetEntityOwner(entity) ~= cache.playerId then return end
if cache.vehicle then
DeleteVehicle(cache.vehicle)
end

local coords = GetEntityCoords(cache.ped)
local vehicle = CreateVehicle(hash, coords.x, coords.y, coords.z, GetEntityHeading(cache.ped), true, false)
TaskWarpPedIntoVehicle(cache.ped, vehicle, -1)
SetVehicleFuelLevel(vehicle, 100.0)
SetVehicleDirtLevel(vehicle, 0.0)
SetModelAsNoLongerNeeded(hash)
TriggerEvent("vehiclekeys:client:SetOwner", QBCore.Functions.GetPlate(vehicle))
SetVehicleNeedsToBeHotwired(entity, false)
SetVehRadioStation(entity, 'OFF')
SetVehicleFuelLevel(entity, 100.0)
SetVehicleDirtLevel(entity, 0.0)
TriggerEvent('vehiclekeys:client:SetOwner', QBCore.Functions.GetPlate(veh))
Entity(entity).state:set('initVehicle', nil, true)
end)

RegisterNetEvent('QBCore:Command:DeleteVehicle', function()
Expand Down
2 changes: 2 additions & 0 deletions client/functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -406,12 +406,14 @@ end

-- Vehicle

---@deprecated call server function QBCore.Functions.CreateVehicle instead.
---@param model string|number
---@param cb? fun(vehicle: number)
---@param coords? vector3 player position if not specified
---@param isnetworked? boolean defaults to true
---@param teleportInto boolean teleport player to driver seat if true
function QBCore.Functions.SpawnVehicle(model, cb, coords, isnetworked, teleportInto)
print(string.format("%s invoked deprecated client function QBCore.Functions.SpawnVehicle. call server function QBCore.Functions.CreateVehicle instead.", GetInvokingResource()))
coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords or GetEntityCoords(cache.ped)
model = type(model) == 'string' and joaat(model) or model
if not IsModelInCdimage(model) then return end
Expand Down
76 changes: 76 additions & 0 deletions client/utils.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
-- MIT License

-- Copyright (c) 2022 Overextended

-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:

-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.

---@async
function utils.waitFor(cb, timeout)
local hasValue = cb()
local i = 0

while not hasValue do
if timeout then
i += 1

if i > timeout then return end
end

Wait(0)
hasValue = cb()
end

return hasValue
end

---@async
---@param bagName string
---@return integer?, integer
function utils.getEntityAndNetIdFromBagName(bagName)
local netId = tonumber(bagName:gsub('entity:', ''), 10)

if not utils.waitFor(function()
return NetworkDoesEntityExistWithNetworkId(netId)
end, 10000) then
return print(('statebag timed out while awaiting entity creation! (%s)'):format(bagName)), 0
end

local entity = NetworkGetEntityFromNetworkId(netId)

if entity == 0 then
return print(('statebag received invalid entity! (%s)'):format(bagName)), 0
end

return entity, netId
end

---@param keyFilter string
---@param cb fun(entity: number, netId: number, value: any, bagName: string)
---@return number
function utils.entityStateHandler(keyFilter, cb)
return AddStateBagChangeHandler(keyFilter, '', function(bagName, key, value, reserved, replicated)
local entity, netId = utils.getEntityAndNetIdFromBagName(bagName)

if entity then
cb(entity, netId, value, bagName)
end
end)
end

return utils
4 changes: 4 additions & 0 deletions fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ server_scripts {
'server/storage.lua',
}

files = {
'client/utils.lua',
}

dependency 'oxmysql'
provide 'qb-core'
lua54 'yes'
Expand Down
2 changes: 1 addition & 1 deletion server/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ end, 'admin')
-- Vehicle

QBCore.Commands.Add('car', Lang:t("command.car.help"), {{ name = Lang:t("command.car.params.model.name"), help = Lang:t("command.car.params.model.help") }}, true, function(source, args)
TriggerClientEvent('QBCore:Command:SpawnVehicle', source, args[1])
QBCore.Functions.CreateVehicle(source, args[1], nil, true)
end, 'admin')

QBCore.Commands.Add('dv', Lang:t("command.dv.help"), {}, false, function(source)
Expand Down
69 changes: 9 additions & 60 deletions server/events.lua
Original file line number Diff line number Diff line change
Expand Up @@ -224,67 +224,16 @@ RegisterNetEvent('QBCore:CallCommand', function(command, args)
end
end)

-- Use this for player vehicle spawning
-- Vehicle server-side spawning callback (netId)
-- use the netid on the client with the NetworkGetEntityFromNetworkId native
-- convert it to a vehicle via the NetToVeh native
---@param source Source
---@param cb fun(vehicleNetId: number)
---@param model string|number
---@param coords vector4
---@param warp boolean teleports player into vehicle if true
---@deprecated call server function QBCore.Functions.CreateVehicle instead.
QBCore.Functions.CreateCallback('QBCore:Server:SpawnVehicle', function(source, cb, model, coords, warp)
local ped = GetPlayerPed(source)
model = type(model) == 'string' and joaat(model) or model
if not coords then coords = GetEntityCoords(ped) end
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, true)
while not DoesEntityExist(veh) do Wait(0) end
if warp then
while GetVehiclePedIsIn(ped, false) ~= veh do
Wait(0)
TaskWarpPedIntoVehicle(ped, veh, -1)
end
end
while NetworkGetEntityOwner(veh) ~= source do Wait(0) end
cb(NetworkGetNetworkIdFromEntity(veh))
print(string.format("%s invoked deprecated callback QBCore:Server:SpawnVehicle. Call server function QBCore.Functions.CreateVehicle instead.", GetInvokingResource()))
local netId = QBCore.Functions.CreateVehicle(source, model, coords, warp)
if netId then cb(netId) end
end)

-- Use this for long distance vehicle spawning
-- vehicle server-side spawning callback (netId)
-- use the netid on the client with the NetworkGetEntityFromNetworkId native
-- convert it to a vehicle via the NetToVeh native but use a while loop before that to check if the vehicle exists first like this
--[[
```lua
while not DoesEntityExist(NetToVeh(veh)) do
Wait(0)
end
```
]]
-- If you don't use the above on the client, it will return 0 as the vehicle from the netid and 0 means no vehicle found because it doesn't exist so fast on the client
---@param source Source
---@param cb fun(vehicleNetId: number)
---@param model string|number
---@param coords vector4
---@param warp boolean
---@deprecated call server function QBCore.Functions.CreateVehicle instead.
QBCore.Functions.CreateCallback('QBCore:Server:CreateVehicle', function(source, cb, model, coords, warp)
local ped = GetPlayerPed(source)
model = type(model) == 'string' and joaat(model) or model
if not coords then coords = GetEntityCoords(ped) end
if not CreateVehicleServerSetter then
error('^1CreateVehicleServerSetter is not available on your artifact, please use artifact 5904 or above to be able to use this^0')
cb(0)
return
end
local tempVehicle = CreateVehicle(model, 0, 0, 0, 0, true, true)
while not DoesEntityExist(tempVehicle) do Wait(0) end
local vehicleType = GetVehicleType(tempVehicle)
DeleteEntity(tempVehicle)
local veh = CreateVehicleServerSetter(model, vehicleType, coords.x, coords.y, coords.z, coords.w)
while not DoesEntityExist(veh) do Wait(0) end
if warp then TaskWarpPedIntoVehicle(ped, veh, -1) end
cb(NetworkGetNetworkIdFromEntity(veh))
end)

--QBCore.Functions.CreateCallback('QBCore:HasItem', function(source, cb, items, amount)
-- https://github.com/qbcore-framework/qb-inventory/blob/e4ef156d93dd1727234d388c3f25110c350b3bcf/server/main.lua#L2066
--end)
print(string.format("%s invoked deprecated callback QBCore:Server:CreateVehicle. call server function QBCore.Functions.CreateVehicle instead.", GetInvokingResource()))
local netId = QBCore.Functions.CreateVehicle(source, model, coords, warp)
if netId then cb(netId) end
end)
46 changes: 20 additions & 26 deletions server/functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -201,36 +201,29 @@ function QBCore.Functions.GetEntitiesInBucket(bucket)
return curr_bucket_pool
end

-- Server side vehicle creation with optional callback
-- the CreateVehicle RPC still uses the client for creation so players must be near
---@param source Source
---@param model string|number
---@param coords vector4
---@param warp boolean
---@return number
---@deprecated Use QBCore.Functions.CreateVehicle instead.
function QBCore.Functions.SpawnVehicle(source, model, coords, warp)
local ped = GetPlayerPed(source)
model = type(model) == 'string' and joaat(model) or model
if not coords then coords = GetEntityCoords(ped) end
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, true)
while not DoesEntityExist(veh) do Wait(0) end
if warp then
while GetVehiclePedIsIn(ped, false) ~= veh do
Wait(0)
TaskWarpPedIntoVehicle(ped, veh, -1)
end
end
while NetworkGetEntityOwner(veh) ~= source do Wait(0) end
return veh
print(string.format("%s invoked deprecated server function QBCore.Functions.SpawnVehicle. Use QBCore.Functions.CreateVehicle instead.", GetInvokingResource()))
return QBCore.Functions.CreateVehicle(source, model, coords, warp)
end

-- Server side vehicle creation with optional callback
-- The CreateVehicleServerSetter native uses only the server to create a vehicle instead of using the client as well
---@param source Source
-- use the netid on the client with the NetworkGetEntityFromNetworkId native
-- convert it to a vehicle via the NetToVeh native but use a while loop before that to check if the vehicle exists first like this
--[[
```lua
while not DoesEntityExist(NetToVeh(veh)) do
Wait(0)
end
```
]]
-- If you don't use the above on the client, it will return 0 as the vehicle from the netid and 0 means no vehicle found because it doesn't exist so fast on the client
---@param source number
---@param model string|number
---@param coords vector4
---@param warp boolean
---@return number?
---@param coords? vector4 default to player's position
---@param warp? boolean
---@return number? netId
function QBCore.Functions.CreateVehicle(source, model, coords, warp)
model = type(model) == 'string' and joaat(model) or model
if not coords then coords = GetEntityCoords(GetPlayerPed(source)) end
Expand All @@ -245,7 +238,8 @@ function QBCore.Functions.CreateVehicle(source, model, coords, warp)
local veh = CreateVehicleServerSetter(model, vehicleType, coords.x, coords.y, coords.z, coords.w)
while not DoesEntityExist(veh) do Wait(0) end
if warp then TaskWarpPedIntoVehicle(GetPlayerPed(source), veh, -1) end
return veh
Entity(veh).state:set('initVehicle', true, true)
return NetworkGetNetworkIdFromEntity(veh)
end

-- Callback Functions --
Expand All @@ -267,7 +261,7 @@ end

---@deprecated call a function instead
function QBCore.Functions.TriggerCallback(name, source, cb, ...)
print(string.format("%s invoked deprecated function TriggerCallback. Use ox_lib callback functions instead.", GetInvokingResource()))
print(string.format("%s invoked deprecated function TriggerCallback. Call a function instead.", GetInvokingResource()))
if not QBCore.ServerCallbacks[name] then return end
QBCore.ServerCallbacks[name](source, cb, ...)
end
Expand Down