From 5b0112a6fc74b895fcf7263da8ad6ea0bd17b862 Mon Sep 17 00:00:00 2001 From: Cocodrulo <142546774+Cocodrulo@users.noreply.github.com> Date: Sun, 16 Feb 2025 16:11:02 +0000 Subject: [PATCH 1/4] Add VSCode configuration and improve Lua and JS function documentation and linting errors --- .vscode/extensions.json | 3 + .vscode/settings.json | 6 + client/drawtext.lua | 8 + client/events.lua | 15 +- client/functions.lua | 538 +++++++++++++++++++++++++++------------- client/main.lua | 19 +- html/js/drawtext.js | 41 ++- html/js/testing.js | 5 +- server/commands.lua | 16 +- server/debug.lua | 3 + server/events.lua | 2 +- server/exports.lua | 75 ++++-- server/functions.lua | 83 +++---- server/main.lua | 13 + server/player.lua | 145 ++++++++++- 15 files changed, 709 insertions(+), 263 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..d1c28bdd3 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["sumneko.lua", "overextended.cfxlua-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..dd4106d2a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "Lua.runtime.nonstandardSymbol": ["/**/", "`", "+=", "-=", "*=", "/="], + "Lua.runtime.version": "Lua 5.4", + "Lua.diagnostics.globals": ["MySQL"], + "Lua.diagnostics.disable": ["duplicate-set-field"] +} diff --git a/client/drawtext.lua b/client/drawtext.lua index c54d40146..a8874f76d 100644 --- a/client/drawtext.lua +++ b/client/drawtext.lua @@ -1,9 +1,13 @@ +--- Hides the displayed text local function hideText() SendNUIMessage({ action = 'HIDE_TEXT', }) end +--- Draws text on the screen +--- @param text string +--- @param position? 'left' | 'right' | 'top' default 'left' local function drawText(text, position) if type(position) ~= 'string' then position = 'left' end @@ -16,6 +20,9 @@ local function drawText(text, position) }) end +--- Changes the text on the screen +--- @param text string +--- @param position? 'left' | 'right' | 'top' default 'left' local function changeText(text, position) if type(position) ~= 'string' then position = 'left' end @@ -28,6 +35,7 @@ local function changeText(text, position) }) end +--- Displays an animation on key press and then hides the currently displayed text local function keyPressed() CreateThread(function() -- Not sure if a thread is needed but why not eh? SendNUIMessage({ diff --git a/client/events.lua b/client/events.lua index f08e0195e..1ec2e01a6 100644 --- a/client/events.lua +++ b/client/events.lua @@ -38,7 +38,7 @@ RegisterNetEvent('QBCore:Command:GoToMarker', function() local blipMarker = GetFirstBlipInfoId(8) if not DoesBlipExist(blipMarker) then QBCore.Functions.Notify(Lang:t('error.no_waypoint'), 'error', 5000) - return 'marker' + return end -- Fade screen to hide how clients get teleported. @@ -128,12 +128,14 @@ RegisterNetEvent('QBCore:Command:SpawnVehicle', function(vehName) Wait(0) end - if IsPedInAnyVehicle(ped) then + if IsPedInAnyVehicle(ped, false) then SetEntityAsMissionEntity(veh, true, true) DeleteVehicle(veh) end - local vehicle = CreateVehicle(hash, GetEntityCoords(ped), GetEntityHeading(ped), true, false) + local coords = GetEntityCoords(ped) + + local vehicle = CreateVehicle(hash, coords.x, coords.y, coords.z, GetEntityHeading(ped), true, false) TaskWarpPedIntoVehicle(ped, vehicle, -1) SetVehicleFuelLevel(vehicle, 100.0) SetVehicleDirtLevel(vehicle, 0.0) @@ -230,6 +232,9 @@ end) -- Me command +--- Draw a 3D text on the screen +--- @param coords vector3 | vector4 +--- @param str string local function Draw3DText(coords, str) local onScreen, worldX, worldY = World3dToScreen2d(coords.x, coords.y, coords.z) local camCoords = GetGameplayCamCoord() @@ -239,9 +244,9 @@ local function Draw3DText(coords, str) SetTextFont(4) SetTextColour(255, 255, 255, 255) SetTextEdge(2, 0, 0, 0, 150) - SetTextProportional(1) + SetTextProportional(true) SetTextOutline() - SetTextCentre(1) + SetTextCentre(true) BeginTextCommandDisplayText('STRING') AddTextComponentSubstringPlayerName(str) EndTextCommandDisplayText(worldX, worldY) diff --git a/client/functions.lua b/client/functions.lua index f6bca002d..c281e9e19 100644 --- a/client/functions.lua +++ b/client/functions.lua @@ -2,10 +2,17 @@ QBCore.Functions = {} -- Callbacks +--- Registers a new client callback +--- @param name string +--- @param cb function function QBCore.Functions.CreateClientCallback(name, cb) QBCore.ClientCallbacks[name] = cb end +--- Triggers a server callback +--- @param name string +--- @param ... any The first value can be a function where the returned data is given as argument (optional). +--- @return any function QBCore.Functions.TriggerCallback(name, ...) local cb = nil local args = { ... } @@ -28,22 +35,36 @@ function QBCore.Functions.TriggerCallback(name, ...) end end +--- Debugs something to the server console +--- @param resource string +--- @param obj any +--- @param depth? number default 0 function QBCore.Debug(resource, obj, depth) TriggerServerEvent('QBCore:DebugSomething', resource, obj, depth) end -- Player +--- Gets the player data from the current object +--- @param cb? function +--- @return table? function QBCore.Functions.GetPlayerData(cb) if not cb then return QBCore.PlayerData end cb(QBCore.PlayerData) end +--- Gets an entity coords and heading +--- @param entity number +--- @return vector4 function QBCore.Functions.GetCoords(entity) local coords = GetEntityCoords(entity) return vector4(coords.x, coords.y, coords.z, GetEntityHeading(entity)) end +--- Checks if the player has an item or items in their inventory +--- @param items string | string[] +--- @param amount number +--- @return boolean function QBCore.Functions.HasItem(items, amount) return exports['qb-inventory']:HasItem(items, amount) end @@ -58,7 +79,7 @@ end ---@param entity number - The entity to look at ---@param timeout number - The time in milliseconds before the function times out ---@param speed number - The speed at which the entity should turn ----@return number - The time at which the entity was looked at +---@return number | string - The time at which the entity was looked at or the error message function QBCore.Functions.LookAtEntity(entity, timeout, speed) local involved = GetInvokingResource() if not DoesEntityExist(entity) then @@ -105,7 +126,7 @@ function QBCore.Functions.LookAtEntity(entity, timeout, speed) end -- Function to run an animation ---- @param animDic string: The name of the animation dictionary +--- @param animDict string: The name of the animation dictionary --- @param animName string - The name of the animation within the dictionary --- @param duration number - The duration of the animation in milliseconds. -1 will play the animation indefinitely --- @param upperbodyOnly boolean - If true, the animation will only affect the upper body of the ped @@ -150,6 +171,8 @@ function QBCore.Functions.PlayAnim(animDict, animName, upperbodyOnly, duration) return animPromise.value end +--- Gets if the player is wearing gloves +--- @return boolean function QBCore.Functions.IsWearingGloves() local ped = PlayerPedId() local armIndex = GetPedDrawableVariation(ped, 3) @@ -168,6 +191,11 @@ end -- NUI Calls +--- Sends a notification to the player +--- @param text string | {text: string, caption: string} +--- @param texttype? string default 'primary' +--- @param length? number default 5000 +--- @param icon? string function QBCore.Functions.Notify(text, texttype, length, icon) local message = { action = 'notify', @@ -189,6 +217,18 @@ function QBCore.Functions.Notify(text, texttype, length, icon) SendNUIMessage(message) end +--- Opens a progressbar for the player +--- @param name string +--- @param label string +--- @param duration number +--- @param useWhileDead boolean +--- @param canCancel any +--- @param disableControls table +--- @param animation table +--- @param prop table +--- @param propTwo table +--- @param onFinish? function +--- @param onCancel? function function QBCore.Functions.Progressbar(name, label, duration, useWhileDead, canCancel, disableControls, animation, prop, propTwo, onFinish, onCancel) if GetResourceState('progressbar') ~= 'started' then error('progressbar needs to be started in order for QBCore.Functions.Progressbar to work') end exports['progressbar']:Progress({ @@ -216,18 +256,27 @@ end -- World Getters +--- Gets all the vehicles that the players has loaded +--- @return table function QBCore.Functions.GetVehicles() return GetGamePool('CVehicle') end +--- Gets all the objects that the players has loaded +--- @return table function QBCore.Functions.GetObjects() return GetGamePool('CObject') end +--- Gets all the peds that the players has loaded +--- @return table function QBCore.Functions.GetPlayers() return GetActivePlayers() end +--- Gets the all the players to a given coord between a given distance +--- @param coords? vector3 +--- @return number[] function QBCore.Functions.GetPlayersFromCoords(coords, distance) local players = GetActivePlayers() local ped = PlayerPedId() @@ -248,6 +297,9 @@ function QBCore.Functions.GetPlayersFromCoords(coords, distance) return closePlayers end +--- Gets the closest player to a given coord +--- @param coords? vector3 | vector4 default player coords +--- @return number, number - player, distance function QBCore.Functions.GetClosestPlayer(coords) local ped = PlayerPedId() if coords then @@ -272,6 +324,9 @@ function QBCore.Functions.GetClosestPlayer(coords) return closestPlayer, closestDistance end +--- Gets the all the peds that the players has loaded +--- @param ignoreList? number[] +--- @return number[] function QBCore.Functions.GetPeds(ignoreList) local pedPool = GetGamePool('CPed') local peds = {} @@ -288,6 +343,10 @@ function QBCore.Functions.GetPeds(ignoreList) return peds end +--- Gets the closest ped to a given coord +--- @param coords? vector3 | vector4 default player coords +--- @param ignoreList? number[] +--- @return number, number - ped, distance function QBCore.Functions.GetClosestPed(coords, ignoreList) local ped = PlayerPedId() if coords then @@ -311,6 +370,9 @@ function QBCore.Functions.GetClosestPed(coords, ignoreList) return closestPed, closestDistance end +--- Gets the closest vehicle to a given coord +--- @param coords? vector3 | vector4 default player coords +--- @return number, number - vehicle, distance function QBCore.Functions.GetClosestVehicle(coords) local ped = PlayerPedId() local vehicles = GetGamePool('CVehicle') @@ -333,6 +395,9 @@ function QBCore.Functions.GetClosestVehicle(coords) return closestVehicle, closestDistance end +--- Gets the closest object to a given coord +--- @param coords? vector3 | vector4 default player coords +--- @return number, number - object, distance function QBCore.Functions.GetClosestObject(coords) local ped = PlayerPedId() local objects = GetGamePool('CObject') @@ -356,7 +421,11 @@ end -- Vehicle +--- Loads a model +--- @param model number | string function QBCore.Functions.LoadModel(model) + model = type(model) == 'string' and joaat(model) or model + model = type(model) == 'string' and joaat(model) or model if HasModelLoaded(model) then return end RequestModel(model) while not HasModelLoaded(model) do @@ -364,6 +433,12 @@ function QBCore.Functions.LoadModel(model) end end +--- Spawns a vehicle +--- @param model string | number +--- @param cb function +--- @param coords vector2|vector3|vector4 +--- @param isnetworked boolean +--- @param teleportInto? boolean function QBCore.Functions.SpawnVehicle(model, cb, coords, isnetworked, teleportInto) local ped = PlayerPedId() model = type(model) == 'string' and joaat(model) or model @@ -387,184 +462,200 @@ function QBCore.Functions.SpawnVehicle(model, cb, coords, isnetworked, teleportI if cb then cb(veh) end end +--- Deletes a vehicle +--- @param vehicle number function QBCore.Functions.DeleteVehicle(vehicle) SetEntityAsMissionEntity(vehicle, true, true) DeleteVehicle(vehicle) end +--- Gets a vehicle plate +--- @param vehicle any +--- @return string? function QBCore.Functions.GetPlate(vehicle) if vehicle == 0 then return end return QBCore.Shared.Trim(GetVehicleNumberPlateText(vehicle)) end +--- Gets a vehicle label +--- @param vehicle number +--- @return string? function QBCore.Functions.GetVehicleLabel(vehicle) if vehicle == nil or vehicle == 0 then return end return GetLabelText(GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))) end +--- Gets a vehicle properties +--- @param vehicle number +--- @return table? function QBCore.Functions.GetVehicleProperties(vehicle) - if DoesEntityExist(vehicle) then - local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle) + if not DoesEntityExist(vehicle) then return end + local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle) - local colorPrimary, colorSecondary = GetVehicleColours(vehicle) - if GetIsVehiclePrimaryColourCustom(vehicle) then - local r, g, b = GetVehicleCustomPrimaryColour(vehicle) - colorPrimary = { r, g, b } - end + local colorPrimary, colorSecondary = GetVehicleColours(vehicle) + if GetIsVehiclePrimaryColourCustom(vehicle) then + local r, g, b = GetVehicleCustomPrimaryColour(vehicle) - if GetIsVehicleSecondaryColourCustom(vehicle) then - local r, g, b = GetVehicleCustomSecondaryColour(vehicle) - colorSecondary = { r, g, b } - end + ---@diagnostic disable-next-line: cast-local-type + colorPrimary = { r, g, b } + end + if GetIsVehicleSecondaryColourCustom(vehicle) then + local r, g, b = GetVehicleCustomSecondaryColour(vehicle) - local extras = {} - for extraId = 0, 12 do - if DoesExtraExist(vehicle, extraId) then - local state = IsVehicleExtraTurnedOn(vehicle, extraId) == 1 - extras[tostring(extraId)] = state - end - end + ---@diagnostic disable-next-line: cast-local-type + colorSecondary = { r, g, b } + end - local modLivery = GetVehicleMod(vehicle, 48) - if GetVehicleMod(vehicle, 48) == -1 and GetVehicleLivery(vehicle) ~= 0 then - modLivery = GetVehicleLivery(vehicle) + local extras = {} + for extraId = 0, 12 do + if DoesExtraExist(vehicle, extraId) then + local state = IsVehicleExtraTurnedOn(vehicle, extraId) == 1 + extras[tostring(extraId)] = state end + end - local tireHealth = {} - for i = 0, 3 do - tireHealth[i] = GetVehicleWheelHealth(vehicle, i) - end + local modLivery = GetVehicleMod(vehicle, 48) + if GetVehicleMod(vehicle, 48) == -1 and GetVehicleLivery(vehicle) ~= 0 then + modLivery = GetVehicleLivery(vehicle) + end - local tireBurstState = {} - for i = 0, 5 do - tireBurstState[i] = IsVehicleTyreBurst(vehicle, i, false) - end + local tireHealth = {} + for i = 0, 3 do + tireHealth[i] = GetVehicleWheelHealth(vehicle, i) + end - local tireBurstCompletely = {} - for i = 0, 5 do - tireBurstCompletely[i] = IsVehicleTyreBurst(vehicle, i, true) - end + local tireBurstState = {} + for i = 0, 5 do + tireBurstState[i] = IsVehicleTyreBurst(vehicle, i, false) + end - local windowStatus = {} - for i = 0, 7 do - windowStatus[i] = IsVehicleWindowIntact(vehicle, i) == 1 - end + local tireBurstCompletely = {} + for i = 0, 5 do + tireBurstCompletely[i] = IsVehicleTyreBurst(vehicle, i, true) + end - local doorStatus = {} - for i = 0, 5 do - doorStatus[i] = IsVehicleDoorDamaged(vehicle, i) == 1 - end + local windowStatus = {} + for i = 0, 7 do + windowStatus[i] = IsVehicleWindowIntact(vehicle, i) == 1 + end - local xenonColor - local hasCustom, r, g, b = GetVehicleXenonLightsCustomColor(vehicle) - if hasCustom then - xenonColor = table.pack(r, g, b) - else - xenonColor = GetVehicleXenonLightsColor(vehicle) - end - - return { - model = GetEntityModel(vehicle), - plate = QBCore.Functions.GetPlate(vehicle), - plateIndex = GetVehicleNumberPlateTextIndex(vehicle), - bodyHealth = QBCore.Shared.Round(GetVehicleBodyHealth(vehicle), 0.1), - engineHealth = QBCore.Shared.Round(GetVehicleEngineHealth(vehicle), 0.1), - tankHealth = QBCore.Shared.Round(GetVehiclePetrolTankHealth(vehicle), 0.1), - fuelLevel = QBCore.Shared.Round(GetVehicleFuelLevel(vehicle), 0.1), - dirtLevel = QBCore.Shared.Round(GetVehicleDirtLevel(vehicle), 0.1), - oilLevel = QBCore.Shared.Round(GetVehicleOilLevel(vehicle), 0.1), - color1 = colorPrimary, - color2 = colorSecondary, - pearlescentColor = pearlescentColor, - dashboardColor = GetVehicleDashboardColour(vehicle), - wheelColor = wheelColor, - wheels = GetVehicleWheelType(vehicle), - wheelSize = GetVehicleWheelSize(vehicle), - wheelWidth = GetVehicleWheelWidth(vehicle), - tireHealth = tireHealth, - tireBurstState = tireBurstState, - tireBurstCompletely = tireBurstCompletely, - windowTint = GetVehicleWindowTint(vehicle), - windowStatus = windowStatus, - doorStatus = doorStatus, - neonEnabled = { - IsVehicleNeonLightEnabled(vehicle, 0), - IsVehicleNeonLightEnabled(vehicle, 1), - IsVehicleNeonLightEnabled(vehicle, 2), - IsVehicleNeonLightEnabled(vehicle, 3) - }, - neonColor = table.pack(GetVehicleNeonLightsColour(vehicle)), - interiorColor = GetVehicleInteriorColour(vehicle), - extras = extras, - tyreSmokeColor = table.pack(GetVehicleTyreSmokeColor(vehicle)), - xenonColor = xenonColor, - modSpoilers = GetVehicleMod(vehicle, 0), - modFrontBumper = GetVehicleMod(vehicle, 1), - modRearBumper = GetVehicleMod(vehicle, 2), - modSideSkirt = GetVehicleMod(vehicle, 3), - modExhaust = GetVehicleMod(vehicle, 4), - modFrame = GetVehicleMod(vehicle, 5), - modGrille = GetVehicleMod(vehicle, 6), - modHood = GetVehicleMod(vehicle, 7), - modFender = GetVehicleMod(vehicle, 8), - modRightFender = GetVehicleMod(vehicle, 9), - modRoof = GetVehicleMod(vehicle, 10), - modEngine = GetVehicleMod(vehicle, 11), - modBrakes = GetVehicleMod(vehicle, 12), - modTransmission = GetVehicleMod(vehicle, 13), - modHorns = GetVehicleMod(vehicle, 14), - modSuspension = GetVehicleMod(vehicle, 15), - modArmor = GetVehicleMod(vehicle, 16), - modKit17 = GetVehicleMod(vehicle, 17), - modTurbo = IsToggleModOn(vehicle, 18), - modKit19 = GetVehicleMod(vehicle, 19), - modSmokeEnabled = IsToggleModOn(vehicle, 20), - modKit21 = GetVehicleMod(vehicle, 21), - modXenon = IsToggleModOn(vehicle, 22), - modFrontWheels = GetVehicleMod(vehicle, 23), - modBackWheels = GetVehicleMod(vehicle, 24), - modCustomTiresF = GetVehicleModVariation(vehicle, 23), - modCustomTiresR = GetVehicleModVariation(vehicle, 24), - modPlateHolder = GetVehicleMod(vehicle, 25), - modVanityPlate = GetVehicleMod(vehicle, 26), - modTrimA = GetVehicleMod(vehicle, 27), - modOrnaments = GetVehicleMod(vehicle, 28), - modDashboard = GetVehicleMod(vehicle, 29), - modDial = GetVehicleMod(vehicle, 30), - modDoorSpeaker = GetVehicleMod(vehicle, 31), - modSeats = GetVehicleMod(vehicle, 32), - modSteeringWheel = GetVehicleMod(vehicle, 33), - modShifterLeavers = GetVehicleMod(vehicle, 34), - modAPlate = GetVehicleMod(vehicle, 35), - modSpeakers = GetVehicleMod(vehicle, 36), - modTrunk = GetVehicleMod(vehicle, 37), - modHydrolic = GetVehicleMod(vehicle, 38), - modEngineBlock = GetVehicleMod(vehicle, 39), - modAirFilter = GetVehicleMod(vehicle, 40), - modStruts = GetVehicleMod(vehicle, 41), - modArchCover = GetVehicleMod(vehicle, 42), - modAerials = GetVehicleMod(vehicle, 43), - modTrimB = GetVehicleMod(vehicle, 44), - modTank = GetVehicleMod(vehicle, 45), - modWindows = GetVehicleMod(vehicle, 46), - modKit47 = GetVehicleMod(vehicle, 47), - modLivery = modLivery, - modKit49 = GetVehicleMod(vehicle, 49), - liveryRoof = GetVehicleRoofLivery(vehicle), - } + local doorStatus = {} + for i = 0, 5 do + doorStatus[i] = IsVehicleDoorDamaged(vehicle, i) == 1 + end + + local xenonColor + local hasCustom, r, g, b = GetVehicleXenonLightsCustomColor(vehicle) + if hasCustom then + xenonColor = table.pack(r, g, b) else - return + xenonColor = GetVehicleXenonLightsColor(vehicle) end + + return { + model = GetEntityModel(vehicle), + plate = QBCore.Functions.GetPlate(vehicle), + plateIndex = GetVehicleNumberPlateTextIndex(vehicle), + bodyHealth = QBCore.Shared.Round(GetVehicleBodyHealth(vehicle), 0.1), + engineHealth = QBCore.Shared.Round(GetVehicleEngineHealth(vehicle), 0.1), + tankHealth = QBCore.Shared.Round(GetVehiclePetrolTankHealth(vehicle), 0.1), + fuelLevel = QBCore.Shared.Round(GetVehicleFuelLevel(vehicle), 0.1), + dirtLevel = QBCore.Shared.Round(GetVehicleDirtLevel(vehicle), 0.1), + oilLevel = QBCore.Shared.Round(GetVehicleOilLevel(vehicle), 0.1), + color1 = colorPrimary, + color2 = colorSecondary, + pearlescentColor = pearlescentColor, + dashboardColor = GetVehicleDashboardColour(vehicle), + wheelColor = wheelColor, + wheels = GetVehicleWheelType(vehicle), + wheelSize = GetVehicleWheelSize(vehicle), + wheelWidth = GetVehicleWheelWidth(vehicle), + tireHealth = tireHealth, + tireBurstState = tireBurstState, + tireBurstCompletely = tireBurstCompletely, + windowTint = GetVehicleWindowTint(vehicle), + windowStatus = windowStatus, + doorStatus = doorStatus, + neonEnabled = { + IsVehicleNeonLightEnabled(vehicle, 0), + IsVehicleNeonLightEnabled(vehicle, 1), + IsVehicleNeonLightEnabled(vehicle, 2), + IsVehicleNeonLightEnabled(vehicle, 3) + }, + neonColor = table.pack(GetVehicleNeonLightsColour(vehicle)), + interiorColor = GetVehicleInteriorColour(vehicle), + extras = extras, + tyreSmokeColor = table.pack(GetVehicleTyreSmokeColor(vehicle)), + xenonColor = xenonColor, + modSpoilers = GetVehicleMod(vehicle, 0), + modFrontBumper = GetVehicleMod(vehicle, 1), + modRearBumper = GetVehicleMod(vehicle, 2), + modSideSkirt = GetVehicleMod(vehicle, 3), + modExhaust = GetVehicleMod(vehicle, 4), + modFrame = GetVehicleMod(vehicle, 5), + modGrille = GetVehicleMod(vehicle, 6), + modHood = GetVehicleMod(vehicle, 7), + modFender = GetVehicleMod(vehicle, 8), + modRightFender = GetVehicleMod(vehicle, 9), + modRoof = GetVehicleMod(vehicle, 10), + modEngine = GetVehicleMod(vehicle, 11), + modBrakes = GetVehicleMod(vehicle, 12), + modTransmission = GetVehicleMod(vehicle, 13), + modHorns = GetVehicleMod(vehicle, 14), + modSuspension = GetVehicleMod(vehicle, 15), + modArmor = GetVehicleMod(vehicle, 16), + modKit17 = GetVehicleMod(vehicle, 17), + modTurbo = IsToggleModOn(vehicle, 18), + modKit19 = GetVehicleMod(vehicle, 19), + modSmokeEnabled = IsToggleModOn(vehicle, 20), + modKit21 = GetVehicleMod(vehicle, 21), + modXenon = IsToggleModOn(vehicle, 22), + modFrontWheels = GetVehicleMod(vehicle, 23), + modBackWheels = GetVehicleMod(vehicle, 24), + modCustomTiresF = GetVehicleModVariation(vehicle, 23), + modCustomTiresR = GetVehicleModVariation(vehicle, 24), + modPlateHolder = GetVehicleMod(vehicle, 25), + modVanityPlate = GetVehicleMod(vehicle, 26), + modTrimA = GetVehicleMod(vehicle, 27), + modOrnaments = GetVehicleMod(vehicle, 28), + modDashboard = GetVehicleMod(vehicle, 29), + modDial = GetVehicleMod(vehicle, 30), + modDoorSpeaker = GetVehicleMod(vehicle, 31), + modSeats = GetVehicleMod(vehicle, 32), + modSteeringWheel = GetVehicleMod(vehicle, 33), + modShifterLeavers = GetVehicleMod(vehicle, 34), + modAPlate = GetVehicleMod(vehicle, 35), + modSpeakers = GetVehicleMod(vehicle, 36), + modTrunk = GetVehicleMod(vehicle, 37), + modHydrolic = GetVehicleMod(vehicle, 38), + modEngineBlock = GetVehicleMod(vehicle, 39), + modAirFilter = GetVehicleMod(vehicle, 40), + modStruts = GetVehicleMod(vehicle, 41), + modArchCover = GetVehicleMod(vehicle, 42), + modAerials = GetVehicleMod(vehicle, 43), + modTrimB = GetVehicleMod(vehicle, 44), + modTank = GetVehicleMod(vehicle, 45), + modWindows = GetVehicleMod(vehicle, 46), + modKit47 = GetVehicleMod(vehicle, 47), + modLivery = modLivery, + modKit49 = GetVehicleMod(vehicle, 49), + liveryRoof = GetVehicleRoofLivery(vehicle), + } end +--- Sets a vehicle properties +--- @param vehicle number +--- @param props table function QBCore.Functions.SetVehicleProperties(vehicle, props) if DoesEntityExist(vehicle) then if props.extras then for id, enabled in pairs(props.extras) do if enabled then - SetVehicleExtra(vehicle, tonumber(id), 0) + SetVehicleExtra(vehicle, tonumber(id) or 0, false) + SetVehicleExtra(vehicle, tonumber(id) or 0, false) else - SetVehicleExtra(vehicle, tonumber(id), 1) + SetVehicleExtra(vehicle, tonumber(id)or 0, true) + SetVehicleExtra(vehicle, tonumber(id)or 0, true) end end end @@ -635,14 +726,16 @@ function QBCore.Functions.SetVehicleProperties(vehicle, props) if props.tireBurstState then for wheelIndex, burstState in pairs(props.tireBurstState) do if burstState then - SetVehicleTyreBurst(vehicle, tonumber(wheelIndex), false, 1000.0) + SetVehicleTyreBurst(vehicle, tonumber(wheelIndex) or 0, false, 1000.0) + SetVehicleTyreBurst(vehicle, tonumber(wheelIndex) or 0, false, 1000.0) end end end if props.tireBurstCompletely then for wheelIndex, burstState in pairs(props.tireBurstCompletely) do if burstState then - SetVehicleTyreBurst(vehicle, tonumber(wheelIndex), true, 1000.0) + SetVehicleTyreBurst(vehicle, tonumber(wheelIndex) or 0, true, 1000.0) + SetVehicleTyreBurst(vehicle, tonumber(wheelIndex) or 0, true, 1000.0) end end end @@ -657,7 +750,8 @@ function QBCore.Functions.SetVehicleProperties(vehicle, props) if props.doorStatus then for doorIndex, breakDoor in pairs(props.doorStatus) do if breakDoor then - SetVehicleDoorBroken(vehicle, tonumber(doorIndex), true) + SetVehicleDoorBroken(vehicle, tonumber(doorIndex) or 0, true) + SetVehicleDoorBroken(vehicle, tonumber(doorIndex) or 0, true) end end end @@ -854,6 +948,18 @@ end -- Unused +--- Draw a text in the screen +--- @param x number +--- @param y number +--- @param width number +--- @param height number +--- @param scale number +--- @param r number +--- @param g number +--- @param b number +--- @param a number +--- @param text string +--- @deprecated function QBCore.Functions.DrawText(x, y, width, height, scale, r, g, b, a, text) -- Use local function instead SetTextFont(4) @@ -864,11 +970,18 @@ function QBCore.Functions.DrawText(x, y, width, height, scale, r, g, b, a, text) EndTextCommandDisplayText(x - width / 2, y - height / 2 + 0.005) end +--- Draw a 3D text in the screen +--- @param x number +--- @param y number +--- @param z number +--- @param text string +--- @deprecated function QBCore.Functions.DrawText3D(x, y, z, text) -- Use local function instead SetTextScale(0.35, 0.35) SetTextFont(4) - SetTextProportional(1) + SetTextProportional(true) + SetTextProportional(true) SetTextColour(255, 255, 255, 215) BeginTextCommandDisplayText('STRING') SetTextCentre(true) @@ -880,6 +993,8 @@ function QBCore.Functions.DrawText3D(x, y, z, text) ClearDrawOrigin() end +--- Load an animation dictionary +--- @param animDict string function QBCore.Functions.RequestAnimDict(animDict) if HasAnimDictLoaded(animDict) then return end RequestAnimDict(animDict) @@ -888,6 +1003,12 @@ function QBCore.Functions.RequestAnimDict(animDict) end end +--- Get the closest bone of a list +--- @param entity any +--- @param list any +--- @return table +--- @return vector3 +--- @return integer|nil function QBCore.Functions.GetClosestBone(entity, list) local playerCoords, bone, coords, distance = GetEntityCoords(PlayerPedId()) for _, element in pairs(list) do @@ -907,28 +1028,53 @@ function QBCore.Functions.GetClosestBone(entity, list) return bone, coords, distance end +--- Get the distance between a bone and the player +--- @param entity number +--- @param boneType number +--- @param boneIndex number | string +--- @return integer function QBCore.Functions.GetBoneDistance(entity, boneType, boneIndex) local bone if boneType == 1 then - bone = GetPedBoneIndex(entity, boneIndex) + bone = GetPedBoneIndex(entity, tonumber(boneIndex) or 0) + bone = GetPedBoneIndex(entity, tonumber(boneIndex) or 0) else - bone = GetEntityBoneIndexByName(entity, boneIndex) + bone = GetEntityBoneIndexByName(entity, tostring(boneIndex) or '') + bone = GetEntityBoneIndexByName(entity, tostring(boneIndex) or '') end local boneCoords = GetWorldPositionOfEntityBone(entity, bone) local playerCoords = GetEntityCoords(PlayerPedId()) return #(boneCoords - playerCoords) end +--- Attach a prop to a ped +--- @param ped number +--- @param model string | number +--- @param boneId number +--- @param x number +--- @param y number +--- @param z number +--- @param xR number +--- @param yR number +--- @param zR number +--- @param vertex number +--- @return integer function QBCore.Functions.AttachProp(ped, model, boneId, x, y, z, xR, yR, zR, vertex) local modelHash = type(model) == 'string' and joaat(model) or model local bone = GetPedBoneIndex(ped, boneId) QBCore.Functions.LoadModel(modelHash) - local prop = CreateObject(modelHash, 1.0, 1.0, 1.0, 1, 1, 0) - AttachEntityToEntity(prop, ped, bone, x, y, z, xR, yR, zR, 1, 1, 0, 1, not vertex and 2 or 0, 1) + local prop = CreateObject(modelHash, 1.0, 1.0, 1.0, true, true, false) + AttachEntityToEntity(prop, ped, bone, x, y, z, xR, yR, zR, true, true, false, true, not vertex and 2 or 0, true) + local prop = CreateObject(modelHash, 1.0, 1.0, 1.0, true, true, false) + AttachEntityToEntity(prop, ped, bone, x, y, z, xR, yR, zR, true, true, false, true, not vertex and 2 or 0, true) SetModelAsNoLongerNeeded(modelHash) return prop end +--- Check if a spawn point is clear in a certain radius +--- @param coords vector3 | vector4 +--- @param radius number +--- @return boolean function QBCore.Functions.SpawnClear(coords, radius) if coords then coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords @@ -948,6 +1094,8 @@ function QBCore.Functions.SpawnClear(coords, radius) return true end +--- Load an animation set +--- @param animSet string function QBCore.Functions.LoadAnimSet(animSet) if HasAnimSetLoaded(animSet) then return end RequestAnimSet(animSet) @@ -956,6 +1104,8 @@ function QBCore.Functions.LoadAnimSet(animSet) end end +--- Load a particle dictionary +--- @param dictionary string function QBCore.Functions.LoadParticleDictionary(dictionary) if HasNamedPtfxAssetLoaded(dictionary) then return end RequestNamedPtfxAsset(dictionary) @@ -964,6 +1114,17 @@ function QBCore.Functions.LoadParticleDictionary(dictionary) end end +--- Start a particle effect at a coordinate +--- @param dict string +--- @param ptName string +--- @param looped boolean +--- @param coords vector3 | vector4 +--- @param rot vector3 +--- @param scale number +--- @param alpha number +--- @param color {r: number, g: number, b: number} +--- @param duration number +--- @return integer function QBCore.Functions.StartParticleAtCoord(dict, ptName, looped, coords, rot, scale, alpha, color, duration) if coords then coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords @@ -975,21 +1136,24 @@ function QBCore.Functions.StartParticleAtCoord(dict, ptName, looped, coords, rot SetPtfxAssetNextCall(dict) local particleHandle if looped then - particleHandle = StartParticleFxLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0) + particleHandle = StartParticleFxLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0, false, false, false, false) + particleHandle = StartParticleFxLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0, false, false, false, false) if color then SetParticleFxLoopedColour(particleHandle, color.r, color.g, color.b, false) end SetParticleFxLoopedAlpha(particleHandle, alpha or 10.0) if duration then Wait(duration) - StopParticleFxLooped(particleHandle, 0) + StopParticleFxLooped(particleHandle, false) + StopParticleFxLooped(particleHandle, false) end else SetParticleFxNonLoopedAlpha(alpha or 10.0) if color then SetParticleFxNonLoopedColour(color.r, color.g, color.b) end - StartParticleFxNonLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0) + StartParticleFxNonLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0, false, false, false) + StartParticleFxNonLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0, false, false, false) end return particleHandle end @@ -1005,9 +1169,11 @@ function QBCore.Functions.StartParticleOnEntity(dict, ptName, looped, entity, bo end if looped then if bone then - particleHandle = StartParticleFxLoopedOnEntityBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale) + particleHandle = StartParticleFxLoopedOnEntityBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale, false, false, false) + particleHandle = StartParticleFxLoopedOnEntityBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale, false, false, false) else - particleHandle = StartParticleFxLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale) + particleHandle = StartParticleFxLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale, false, false, false) + particleHandle = StartParticleFxLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale, false, false, false) end if evolution then SetParticleFxLoopedEvolution(particleHandle, evolution.name, evolution.amount, false) @@ -1018,7 +1184,8 @@ function QBCore.Functions.StartParticleOnEntity(dict, ptName, looped, entity, bo SetParticleFxLoopedAlpha(particleHandle, alpha) if duration then Wait(duration) - StopParticleFxLooped(particleHandle, 0) + StopParticleFxLooped(particleHandle, false) + StopParticleFxLooped(particleHandle, false) end else SetParticleFxNonLoopedAlpha(alpha or 10.0) @@ -1026,41 +1193,52 @@ function QBCore.Functions.StartParticleOnEntity(dict, ptName, looped, entity, bo SetParticleFxNonLoopedColour(color.r, color.g, color.b) end if bone then - StartParticleFxNonLoopedOnPedBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale) + StartParticleFxNonLoopedOnPedBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale, false, false, false) + StartParticleFxNonLoopedOnPedBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale, false, false, false) else - StartParticleFxNonLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale) + StartParticleFxNonLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale, false, false, false) + StartParticleFxNonLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale, false, false, false) end end return particleHandle end +--- Gets the street name at coords +--- @param coords vector3 | vector4 +--- @return {main: string, cross: string} function QBCore.Functions.GetStreetNametAtCoords(coords) local streetname1, streetname2 = GetStreetNameAtCoord(coords.x, coords.y, coords.z) return { main = GetStreetNameFromHashKey(streetname1), cross = GetStreetNameFromHashKey(streetname2) } end +--- Gets the zone name at coords +--- @param coords vector3 | vector4 function QBCore.Functions.GetZoneAtCoords(coords) - return GetLabelText(GetNameOfZone(coords)) + return GetLabelText(GetNameOfZone(coords.x, coords.y, coords.z)) end +--- Gets the direction of an entity +--- @param entity number +--- @return string function QBCore.Functions.GetCardinalDirection(entity) entity = DoesEntityExist(entity) and entity or PlayerPedId() - if DoesEntityExist(entity) then - local heading = GetEntityHeading(entity) - if ((heading >= 0 and heading < 45) or (heading >= 315 and heading < 360)) then - return 'North' - elseif (heading >= 45 and heading < 135) then - return 'West' - elseif (heading >= 135 and heading < 225) then - return 'South' - elseif (heading >= 225 and heading < 315) then - return 'East' - end - else - return 'Cardinal Direction Error' + if DoesEntityExist(entity) then return 'Cardinal Direction Error' end + local heading = GetEntityHeading(entity) + if ((heading >= 0 and heading < 45) or (heading >= 315 and heading < 360)) then + return 'North' + elseif (heading >= 45 and heading < 135) then + return 'West' + elseif (heading >= 135 and heading < 225) then + return 'South' + elseif (heading >= 225 and heading < 315) then + return 'East' end + + return 'Cardinal Direction Error' end +--- Get the current time +--- @return {min: number, hour: number, ampm: string, formattedHour: number, formattedMin: string} function QBCore.Functions.GetCurrentTime() local obj = {} obj.min = GetClockMinutes() @@ -1077,10 +1255,14 @@ function QBCore.Functions.GetCurrentTime() return obj end +--- Gets the ground coords in a vector3 +--- @param coords vector3 | vector4 +--- @return vector3? function QBCore.Functions.GetGroundZCoord(coords) if not coords then return end - local retval, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, 0) + local retval, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, false) + local retval, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, false) if retval then return vector3(coords.x, coords.y, groundZ) else @@ -1088,6 +1270,14 @@ function QBCore.Functions.GetGroundZCoord(coords) end end +--- Gets the ground hash where an entity is on +--- @param entity number +--- @return number +--- @return number +--- @return vector3 +--- @return vector3 +--- @return boolean +--- @return number function QBCore.Functions.GetGroundHash(entity) local coords = GetEntityCoords(entity) local num = StartShapeTestCapsule(coords.x, coords.y, coords.z + 4, coords.x, coords.y, coords.z - 2.0, 1, 1, entity, 7) diff --git a/client/main.lua b/client/main.lua index 05d3b6883..42a9e24a1 100644 --- a/client/main.lua +++ b/client/main.lua @@ -5,12 +5,9 @@ QBCore.Shared = QBShared QBCore.ClientCallbacks = {} QBCore.ServerCallbacks = {} --- Get the full QBCore object (default behavior): --- local QBCore = GetCoreObject() - --- Get only specific parts of QBCore: --- local QBCore = GetCoreObject({'Players', 'Config'}) - +--- Get the full QBCore object (default behavior) or a specific part of the core +--- @param filters table +--- @return table local function GetCoreObject(filters) if not filters then return QBCore end local results = {} @@ -24,26 +21,36 @@ local function GetCoreObject(filters) end exports('GetCoreObject', GetCoreObject) +--- Get the QBCore items +--- @return table local function GetSharedItems() return QBShared.Items end exports('GetSharedItems', GetSharedItems) +--- Get the QBCore vehicles +--- @return table local function GetSharedVehicles() return QBShared.Vehicles end exports('GetSharedVehicles', GetSharedVehicles) +--- Get the QBCore weapons +--- @return table local function GetSharedWeapons() return QBShared.Weapons end exports('GetSharedWeapons', GetSharedWeapons) +--- Get the QBCore jobs +--- @return table local function GetSharedJobs() return QBShared.Jobs end exports('GetSharedJobs', GetSharedJobs) +--- Get the QBCore gangs +--- @return table local function GetSharedGangs() return QBShared.Gangs end diff --git a/html/js/drawtext.js b/html/js/drawtext.js index 975d6e106..752b14c02 100644 --- a/html/js/drawtext.js +++ b/html/js/drawtext.js @@ -1,8 +1,13 @@ let direction = null; +/** + * Draw text on the screen + * @param {{position: 'left' | 'top' | 'right', text: string}} textData + * @returns {void} + */ const drawText = async (textData) => { const text = document.getElementById("text"); - let {position} = textData; + let { position } = textData; switch (textData.position) { case "left": addClass(text, position); @@ -28,9 +33,14 @@ const drawText = async (textData) => { addClass(text, "show"); }; +/** + * Change the text on the screen + * @param {{position: 'left' | 'top' | 'right', text: string}} textData + * @returns {void} + */ const changeText = async (textData) => { const text = document.getElementById("text"); - let {position} = textData; + let { position } = textData; removeClass(text, "show"); addClass(text, "pressed"); @@ -68,6 +78,10 @@ const changeText = async (textData) => { text.classList.add("show"); }; +/** + * Hide the text on the screen + * @returns {void} + */ const hideText = async () => { const text = document.getElementById("text"); removeClass(text, "show"); @@ -84,6 +98,10 @@ const hideText = async () => { }, 1000); }; +/** + * Key pressed animation + * @returns {void} + */ const keyPressed = () => { const text = document.getElementById("text"); addClass(text, "pressed"); @@ -104,19 +122,36 @@ window.addEventListener("message", (event) => { return keyPressed(); default: return; - } + } }); +/** + * Sleep for a given amount of time + * @param {number} ms + * @returns + */ const sleep = (ms) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; +/** + * Remove a class from an element + * @param {HTMLElement} element + * @param {string} name + * @returns {void} + */ const removeClass = (element, name) => { if (element.classList.contains(name)) { element.classList.remove(name); } }; +/** + * Add a class to an element + * @param {HTMLElement} element + * @param {string} name + * @returns {void} + */ const addClass = (element, name) => { if (!element.classList.contains(name)) { element.classList.add(name); diff --git a/html/js/testing.js b/html/js/testing.js index 5181f57c7..ca62ed0f5 100644 --- a/html/js/testing.js +++ b/html/js/testing.js @@ -1,4 +1,7 @@ -// Will register dev utilities on window +/** + * Will register dev utilities on window + * @returns {void} + */ export const registerWindowMethods = () => { window.SendNotification = (data) => { window.dispatchEvent( diff --git a/server/commands.lua b/server/commands.lua index 368736fdc..11bcd60ec 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -84,7 +84,7 @@ end QBCore.Commands.Add('tp', Lang:t('command.tp.help'), { { name = Lang:t('command.tp.params.x.name'), help = Lang:t('command.tp.params.x.help') }, { name = Lang:t('command.tp.params.y.name'), help = Lang:t('command.tp.params.y.help') }, { name = Lang:t('command.tp.params.z.name'), help = Lang:t('command.tp.params.z.help') } }, false, function(source, args) if args[1] and not args[2] and not args[3] then if tonumber(args[1]) then - local target = GetPlayerPed(tonumber(args[1])) + local target = GetPlayerPed(tonumber(args[1]) or 0) if target ~= 0 then local coords = GetEntityCoords(target) TriggerClientEvent('QBCore:Command:TeleportToPlayer', source, coords) @@ -158,7 +158,7 @@ QBCore.Commands.Add('openserver', Lang:t('command.openserver.help'), {}, false, QBCore.Config.Server.Closed = false TriggerClientEvent('QBCore:Notify', source, Lang:t('success.server_opened'), 'success') else - QBCore.Functions.Kick(source, Lang:t('error.no_permission'), nil, nil) + QBCore.Functions.Kick(source, Lang:t('error.no_permission'), false, false) end end, 'admin') @@ -173,12 +173,12 @@ QBCore.Commands.Add('closeserver', Lang:t('command.closeserver.help'), { { name QBCore.Config.Server.ClosedReason = reason for k in pairs(QBCore.Players) do if not QBCore.Functions.HasPermission(k, QBCore.Config.Server.WhitelistPermission) then - QBCore.Functions.Kick(k, reason, nil, nil) + QBCore.Functions.Kick(k, reason, false, false) end end TriggerClientEvent('QBCore:Notify', source, Lang:t('success.server_closed'), 'success') else - QBCore.Functions.Kick(source, Lang:t('error.no_permission'), nil, nil) + QBCore.Functions.Kick(source, Lang:t('error.no_permission'), false, false) end end, 'admin') @@ -194,8 +194,12 @@ end, 'admin') QBCore.Commands.Add('dvall', Lang:t('command.dvall.help'), {}, false, function() local vehicles = GetAllVehicles() - for _, vehicle in ipairs(vehicles) do - DeleteEntity(vehicle) + if type(vehicles) == "number" then + DeleteEntity(vehicles) + elseif type(vehicles) == "table" then + for _, vehicle in ipairs(vehicles) do + DeleteEntity(vehicle) + end end end, 'admin') diff --git a/server/debug.lua b/server/debug.lua index 8ec63e61f..0deab2242 100644 --- a/server/debug.lua +++ b/server/debug.lua @@ -1,3 +1,6 @@ +--- Prints a table in a readable format to the console +--- @param tbl table +--- @param indent number local function tPrint(tbl, indent) indent = indent or 0 if type(tbl) == 'table' then diff --git a/server/events.lua b/server/events.lua index 00b0fa5a9..27c5add7d 100644 --- a/server/events.lua +++ b/server/events.lua @@ -268,7 +268,7 @@ end) -- convert it to a vehicle via the NetToVeh native QBCore.Functions.CreateCallback('QBCore:Server:SpawnVehicle', function(source, cb, model, coords, warp) local veh = QBCore.Functions.SpawnVehicle(source, model, coords, warp) - cb(NetworkGetNetworkIdFromEntity(veh)) + cb(NetworkGetNetworkIdFromEntity(tonumber(veh) or 0)) end) -- Use this for long distance vehicle spawning diff --git a/server/exports.lua b/server/exports.lua index d02518cda..dfc746326 100644 --- a/server/exports.lua +++ b/server/exports.lua @@ -1,4 +1,7 @@ --- Add or change (a) method(s) in the QBCore.Functions table +--- Add or change (a) method(s) in the QBCore.Functions table +--- @param methodName string +--- @param handler function +--- @return boolean, string local function SetMethod(methodName, handler) if type(methodName) ~= 'string' then return false, 'invalid_method_name' @@ -14,7 +17,10 @@ end QBCore.Functions.SetMethod = SetMethod exports('SetMethod', SetMethod) --- Add or change (a) field(s) in the QBCore table +--- Add or change (a) field(s) in the QBCore table +--- @param fieldName string +--- @param data any +--- @return boolean, string local function SetField(fieldName, data) if type(fieldName) ~= 'string' then return false, 'invalid_field_name' @@ -30,7 +36,10 @@ end QBCore.Functions.SetField = SetField exports('SetField', SetField) --- Single add job function which should only be used if you planning on adding a single job +--- Single add job function which should only be used if you planning on adding a single job +--- @param jobName string +--- @param job table +--- @return boolean, string local function AddJob(jobName, job) if type(jobName) ~= 'string' then return false, 'invalid_job_name' @@ -50,7 +59,9 @@ end QBCore.Functions.AddJob = AddJob exports('AddJob', AddJob) --- Multiple Add Jobs +--- Multiple Add Jobs +--- @param jobs table +--- @return boolean, string, table? local function AddJobs(jobs) local shouldContinue = true local message = 'success' @@ -83,7 +94,9 @@ end QBCore.Functions.AddJobs = AddJobs exports('AddJobs', AddJobs) --- Single Remove Job +--- Single Remove Job +--- @param jobName string +--- @return boolean, string local function RemoveJob(jobName) if type(jobName) ~= 'string' then return false, 'invalid_job_name' @@ -103,7 +116,10 @@ end QBCore.Functions.RemoveJob = RemoveJob exports('RemoveJob', RemoveJob) --- Single Update Job +--- Single Update Job +--- @param jobName string +--- @param job table +--- @return boolean, string local function UpdateJob(jobName, job) if type(jobName) ~= 'string' then return false, 'invalid_job_name' @@ -123,7 +139,10 @@ end QBCore.Functions.UpdateJob = UpdateJob exports('UpdateJob', UpdateJob) --- Single add item +--- Single add item +--- @param itemName string +--- @param item table +--- @return boolean, string local function AddItem(itemName, item) if type(itemName) ~= 'string' then return false, 'invalid_item_name' @@ -143,7 +162,10 @@ end QBCore.Functions.AddItem = AddItem exports('AddItem', AddItem) --- Single update item +--- Single update item +--- @param itemName string +--- @param item table +--- @return boolean, string local function UpdateItem(itemName, item) if type(itemName) ~= 'string' then return false, 'invalid_item_name' @@ -160,7 +182,9 @@ end QBCore.Functions.UpdateItem = UpdateItem exports('UpdateItem', UpdateItem) --- Multiple Add Items +--- Multiple Add Items +--- @param items table +--- @return boolean, string, table? local function AddItems(items) local shouldContinue = true local message = 'success' @@ -193,7 +217,9 @@ end QBCore.Functions.AddItems = AddItems exports('AddItems', AddItems) --- Single Remove Item +--- Single Remove Item +--- @param itemName string +--- @return boolean, string local function RemoveItem(itemName) if type(itemName) ~= 'string' then return false, 'invalid_item_name' @@ -213,7 +239,10 @@ end QBCore.Functions.RemoveItem = RemoveItem exports('RemoveItem', RemoveItem) --- Single Add Gang +--- Single Add Gang +--- @param gangName string +--- @param gang table +--- @return boolean, string local function AddGang(gangName, gang) if type(gangName) ~= 'string' then return false, 'invalid_gang_name' @@ -233,7 +262,9 @@ end QBCore.Functions.AddGang = AddGang exports('AddGang', AddGang) --- Multiple Add Gangs +--- Multiple Add Gangs +--- @param gangs table +--- @return boolean, string, table? local function AddGangs(gangs) local shouldContinue = true local message = 'success' @@ -266,7 +297,9 @@ end QBCore.Functions.AddGangs = AddGangs exports('AddGangs', AddGangs) --- Single Remove Gang +--- Single Remove Gang +--- @param gangName string +--- @return boolean, string local function RemoveGang(gangName) if type(gangName) ~= 'string' then return false, 'invalid_gang_name' @@ -286,7 +319,10 @@ end QBCore.Functions.RemoveGang = RemoveGang exports('RemoveGang', RemoveGang) --- Single Update Gang +--- Single Update Gang +--- @param gangName string +--- @param gang table +--- @return boolean, string local function UpdateGang(gangName, gang) if type(gangName) ~= 'string' then return false, 'invalid_gang_name' @@ -307,8 +343,12 @@ QBCore.Functions.UpdateGang = UpdateGang exports('UpdateGang', UpdateGang) local resourceName = GetCurrentResourceName() + +--- Get the version of the core +--- @param InvokingResource string +--- @return string local function GetCoreVersion(InvokingResource) - local resourceVersion = GetResourceMetadata(resourceName, 'version') + local resourceVersion = GetResourceMetadata(resourceName, 'version', 0) if InvokingResource and InvokingResource ~= '' then print(('%s called qbcore version check: %s'):format(InvokingResource or 'Unknown Resource', resourceVersion)) end @@ -318,6 +358,9 @@ end QBCore.Functions.GetCoreVersion = GetCoreVersion exports('GetCoreVersion', GetCoreVersion) +--- Ban a player for exploiting +--- @param playerId number | string +--- @param origin string local function ExploitBan(playerId, origin) local name = GetPlayerName(playerId) MySQL.insert('INSERT INTO bans (name, license, discord, ip, reason, expire, bannedby) VALUES (?, ?, ?, ?, ?, ?, ?)', { @@ -329,7 +372,7 @@ local function ExploitBan(playerId, origin) 2147483647, 'Anti Cheat' }) - DropPlayer(playerId, Lang:t('info.exploit_banned', { discord = QBCore.Config.Server.Discord })) + DropPlayer(tostring(playerId), Lang:t('info.exploit_banned', { discord = QBCore.Config.Server.Discord })) TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'red', name .. ' has been banned for exploiting ' .. origin, true) end diff --git a/server/functions.lua b/server/functions.lua index 8f81093d4..1c42a24b2 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -167,7 +167,7 @@ end --- @param source number source player's server ID. --- @param coords vector The coordinates to calculate the distance from. Can be a table with x, y, z fields or a vector3. If not provided, the source player's Ped's coordinates are used. ---- @return string closestPlayer - The Player that is closest to the source player (or the provided coordinates). Returns -1 if no Players are found. +--- @return number closestPlayer - The Player that is closest to the source player (or the provided coordinates). Returns -1 if no Players are found. --- @return number closestDistance - The distance to the closest Player. Returns -1 if no Players are found. function QBCore.Functions.GetClosestPlayer(source, coords) local ped = GetPlayerPed(source) @@ -176,7 +176,7 @@ function QBCore.Functions.GetClosestPlayer(source, coords) if coords then coords = type(coords) == 'table' and vector3(coords.x, coords.y, coords.z) or coords end if not coords then coords = GetEntityCoords(ped) end for i = 1, #players do - local playerId = players[i] + local playerId = tonumber(players[i]) or 0 local playerPed = GetPlayerPed(playerId) if playerPed ~= ped then local playerCoords = GetEntityCoords(playerPed) @@ -268,15 +268,14 @@ end ---@param bucket any ---@return boolean function QBCore.Functions.SetPlayerBucket(source, bucket) - if source and bucket then - local plicense = QBCore.Functions.GetIdentifier(source, 'license') - Player(source).state:set('instance', bucket, true) - SetPlayerRoutingBucket(source, bucket) - QBCore.Player_Buckets[plicense] = { id = source, bucket = bucket } - return true - else - return false - end + if not source or not bucket then return false end + local plicense = QBCore.Functions.GetIdentifier(source, 'license') + if not plicense then return false end + + Player(source).state:set('instance', bucket, true) + SetPlayerRoutingBucket(source, bucket) + QBCore.Player_Buckets[plicense] = { id = source, bucket = bucket } + return true end ---Will set any entity into the provided bucket, for example peds / vehicles / props / etc. @@ -284,13 +283,11 @@ end ---@param bucket number ---@return boolean function QBCore.Functions.SetEntityBucket(entity, bucket) - if entity and bucket then - SetEntityRoutingBucket(entity, bucket) - QBCore.Entity_Buckets[entity] = { id = entity, bucket = bucket } - return true - else - return false - end + if not entity or not bucket then return false end + + SetEntityRoutingBucket(entity, bucket) + QBCore.Entity_Buckets[entity] = { id = entity, bucket = bucket } + return true end ---Will return an array of all the player ids inside the current bucket @@ -298,16 +295,14 @@ end ---@return table|boolean function QBCore.Functions.GetPlayersInBucket(bucket) local curr_bucket_pool = {} - if QBCore.Player_Buckets and next(QBCore.Player_Buckets) then - for _, v in pairs(QBCore.Player_Buckets) do - if v.bucket == bucket then - curr_bucket_pool[#curr_bucket_pool + 1] = v.id - end + if not QBCore.Player_Buckets or not next(QBCore.Player_Buckets) then return false end + + for _, v in pairs(QBCore.Player_Buckets) do + if v.bucket == bucket then + curr_bucket_pool[#curr_bucket_pool + 1] = v.id end - return curr_bucket_pool - else - return false end + return curr_bucket_pool end ---Will return an array of all the entities inside the current bucket @@ -316,16 +311,14 @@ end ---@return table|boolean function QBCore.Functions.GetEntitiesInBucket(bucket) local curr_bucket_pool = {} - if QBCore.Entity_Buckets and next(QBCore.Entity_Buckets) then - for _, v in pairs(QBCore.Entity_Buckets) do - if v.bucket == bucket then - curr_bucket_pool[#curr_bucket_pool + 1] = v.id - end + if not QBCore.Entity_Buckets or not next(QBCore.Entity_Buckets) then return false end + + for _, v in pairs(QBCore.Entity_Buckets) do + if v.bucket == bucket then + curr_bucket_pool[#curr_bucket_pool + 1] = v.id end - return curr_bucket_pool - else - return false end + return curr_bucket_pool end ---Server side vehicle creation with optional callback @@ -343,7 +336,7 @@ function QBCore.Functions.SpawnVehicle(source, model, coords, warp) local veh = CreateVehicle(model, coords.x, coords.y, coords.z, heading, true, true) while not DoesEntityExist(veh) do Wait(0) end if warp then - while GetVehiclePedIsIn(ped) ~= veh do + while GetVehiclePedIsIn(ped, false) ~= veh do Wait(0) TaskWarpPedIntoVehicle(ped, veh, -1) end @@ -365,7 +358,8 @@ function QBCore.Functions.CreateAutomobile(source, model, coords, warp) model = type(model) == 'string' and joaat(model) or model if not coords then coords = GetEntityCoords(GetPlayerPed(source)) end local heading = coords.w and coords.w or 0.0 - local CreateAutomobile = `CREATE_AUTOMOBILE` + local CreateAutomobile = tonumber(`CREATE_AUTOMOBILE`) + if not CreateAutomobile then return -1 end local veh = Citizen.InvokeNative(CreateAutomobile, model, coords, heading, true, true) while not DoesEntityExist(veh) do Wait(0) end if warp then TaskWarpPedIntoVehicle(GetPlayerPed(source), veh, -1) end @@ -373,14 +367,13 @@ function QBCore.Functions.CreateAutomobile(source, model, coords, warp) end --- New & more reliable server side native for creating vehicles ----comment ---@param source any ---@param model any ---@param vehtype any -- The appropriate vehicle type for the model info. -- Can be one of automobile, bike, boat, heli, plane, submarine, trailer, and (potentially), train. -- This should be the same type as the type field in vehicles.meta. ----@param coords vector +---@param coords vector3 | vector4 ---@param warp boolean ---@return number function QBCore.Functions.CreateVehicle(source, model, vehtype, coords, warp) @@ -388,12 +381,13 @@ function QBCore.Functions.CreateVehicle(source, model, vehtype, coords, warp) vehtype = type(vehtype) == 'string' and tostring(vehtype) or vehtype if not coords then coords = GetEntityCoords(GetPlayerPed(source)) end local heading = coords.w and coords.w or 0.0 - local veh = CreateVehicleServerSetter(model, vehtype, coords, heading) + local veh = CreateVehicleServerSetter(model, vehtype, coords.x, coords.y, coords.z, heading) while not DoesEntityExist(veh) do Wait(0) end if warp then TaskWarpPedIntoVehicle(GetPlayerPed(source), veh, -1) end return veh end +--- Do the paycheck interval function PaycheckInterval() if not next(QBCore.Players) then return end for _, Player in pairs(QBCore.Players) do @@ -429,8 +423,7 @@ end ---Trigger Client Callback ---@param name string ---@param source any ----@param cb function ----@param ... any +---@param ... any first element can be a function (optional) function QBCore.Functions.TriggerClientCallback(name, source, ...) local cb = nil local args = { ... } @@ -506,8 +499,8 @@ end ---Kick Player ---@param source any ---@param reason string ----@param setKickReason boolean ----@param deferrals boolean +---@param setKickReason? boolean +---@param deferrals? boolean | table function QBCore.Functions.Kick(source, reason, setKickReason, deferrals) reason = '\n' .. reason .. '\n🔸 Check our Discord for further information: ' .. QBCore.Config.Server.Discord if setKickReason then @@ -663,6 +656,7 @@ function QBCore.Functions.GetDatabaseInfo() details.exists = true return details else + ---@diagnostic disable-next-line: cast-local-type connectionString = { string.strsplit(';', connectionString) } for i = 1, #connectionString do @@ -674,6 +668,7 @@ function QBCore.Functions.GetDatabaseInfo() end end end + return details end ---Check for duplicate license @@ -696,7 +691,7 @@ end ---@param amount number ---@return boolean function QBCore.Functions.HasItem(source, items, amount) - if GetResourceState('qb-inventory') == 'missing' then return end + if GetResourceState('qb-inventory') == 'missing' then return false end return exports['qb-inventory']:HasItem(source, items, amount) end diff --git a/server/main.lua b/server/main.lua index d6228e598..c8bd99e69 100644 --- a/server/main.lua +++ b/server/main.lua @@ -10,6 +10,9 @@ QBCore.ServerCallbacks = {} -- Get only specific parts of QBCore: -- local QBCore = GetCoreObject({'Players', 'Config'}) +--- Gets the QBCore object +--- @param filters table +--- @return table local function GetCoreObject(filters) if not filters then return QBCore end local results = {} @@ -23,26 +26,36 @@ local function GetCoreObject(filters) end exports('GetCoreObject', GetCoreObject) +--- Gets the QBCore Items +--- @return table local function GetSharedItems() return QBShared.Items end exports('GetSharedItems', GetSharedItems) +--- Gets the QBCore Vehicles +--- @return table local function GetSharedVehicles() return QBShared.Vehicles end exports('GetSharedVehicles', GetSharedVehicles) +--- Gets the QBCore Weapons +--- @return table local function GetSharedWeapons() return QBShared.Weapons end exports('GetSharedWeapons', GetSharedWeapons) +--- Gets the QBCore Jobs +--- @return table local function GetSharedJobs() return QBShared.Jobs end exports('GetSharedJobs', GetSharedJobs) +--- Gets the QBCore Gangs +--- @return table local function GetSharedGangs() return QBShared.Gangs end diff --git a/server/player.lua b/server/player.lua index 4f73c0d30..81c5f330c 100644 --- a/server/player.lua +++ b/server/player.lua @@ -6,6 +6,12 @@ QBCore.Player = {} -- Will cause major issues! local resourceName = GetCurrentResourceName() + +--- Logs in a player +--- @param source number | string +--- @param citizenid string +--- @param newData table +--- @return boolean function QBCore.Player.Login(source, citizenid, newData) if source and source ~= '' then if citizenid then @@ -20,7 +26,7 @@ function QBCore.Player.Login(source, citizenid, newData) PlayerData.charinfo = json.decode(PlayerData.charinfo) QBCore.Player.CheckPlayerData(source, PlayerData) else - DropPlayer(source, Lang:t('info.exploit_dropped')) + DropPlayer(tostring(source), Lang:t('info.exploit_dropped')) TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(source) .. ' Has Been Dropped For Character Joining Exploit', false) end else @@ -33,6 +39,9 @@ function QBCore.Player.Login(source, citizenid, newData) end end +--- Gets an offline character by their citizenid +--- @param citizenid string +--- @return table? function QBCore.Player.GetOfflinePlayer(citizenid) if citizenid then local PlayerData = MySQL.prepare.await('SELECT * FROM players where citizenid = ?', { citizenid }) @@ -49,6 +58,9 @@ function QBCore.Player.GetOfflinePlayer(citizenid) return nil end +--- Gets a character by their license +--- @param license string +--- @return table? function QBCore.Player.GetPlayerByLicense(license) if license then local source = QBCore.Functions.GetSource(license) @@ -61,6 +73,9 @@ function QBCore.Player.GetPlayerByLicense(license) return nil end +--- Gets an offline character by their license +--- @param license string +--- @return table? function QBCore.Player.GetOfflinePlayerByLicense(license) if license then local PlayerData = MySQL.prepare.await('SELECT * FROM players where license = ?', { license }) @@ -77,6 +92,9 @@ function QBCore.Player.GetOfflinePlayerByLicense(license) return nil end +--- Applies the default values to player data +--- @param playerData table +--- @param defaults table local function applyDefaults(playerData, defaults) for key, value in pairs(defaults) do if type(value) == 'function' then @@ -90,6 +108,9 @@ local function applyDefaults(playerData, defaults) end end +--- Checks the player data and applies defaults +--- @param source? number +--- @param PlayerData table function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData = PlayerData or {} local Offline = not source @@ -155,6 +176,8 @@ end -- On player logout +--- Logs out a player +--- @param source number | string function QBCore.Player.Logout(source) TriggerClientEvent('QBCore:Client:OnPlayerUnload', source) TriggerEvent('QBCore:Server:OnPlayerUnload', source) @@ -167,18 +190,26 @@ end -- Don't touch any of this unless you know what you are doing -- Will cause major issues! +--- Creates a new player +--- @param PlayerData table +--- @param Offline boolean function QBCore.Player.CreatePlayer(PlayerData, Offline) local self = {} self.Functions = {} self.PlayerData = PlayerData self.Offline = Offline + --- Updates the player data function self.Functions.UpdatePlayerData() if self.Offline then return end TriggerEvent('QBCore:Player:SetPlayerData', self.PlayerData) TriggerClientEvent('QBCore:Player:SetPlayerData', self.PlayerData.source, self.PlayerData) end + --- Sets the player's job + --- @param job string + --- @param grade string + --- @return boolean function self.Functions.SetJob(job, grade) job = job:lower() grade = grade or '0' @@ -214,6 +245,10 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) return true end + --- Sets the player's gang + --- @param gang string + --- @param grade string + --- @return boolean function self.Functions.SetGang(gang, grade) gang = gang:lower() grade = grade or '0' @@ -245,32 +280,50 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) return true end + --- Notifies the player + --- @param text string + --- @param type? string + --- @param length? number function self.Functions.Notify(text, type, length) TriggerClientEvent('QBCore:Notify', self.PlayerData.source, text, type, length) end + --- Returns whether the player has an item or items + --- @param items string | string[] + --- @param amount number + --- @return boolean function self.Functions.HasItem(items, amount) return QBCore.Functions.HasItem(self.PlayerData.source, items, amount) end + --- Returns the player's full name + --- @return string function self.Functions.GetName() local charinfo = self.PlayerData.charinfo return charinfo.firstname .. ' ' .. charinfo.lastname end + --- Set the player's job duty + --- @param onDuty boolean function self.Functions.SetJobDuty(onDuty) - self.PlayerData.job.onduty = not not onDuty + self.PlayerData.job.onduty = onDuty TriggerEvent('QBCore:Server:OnJobUpdate', self.PlayerData.source, self.PlayerData.job) TriggerClientEvent('QBCore:Client:OnJobUpdate', self.PlayerData.source, self.PlayerData.job) self.Functions.UpdatePlayerData() end + --- Sets a field in the player's data + --- @param key string + --- @param val any function self.Functions.SetPlayerData(key, val) if not key or type(key) ~= 'string' then return end self.PlayerData[key] = val self.Functions.UpdatePlayerData() end + --- Sets a metadata field in the player's data + --- @param meta string + --- @param val any function self.Functions.SetMetaData(meta, val) if not meta or type(meta) ~= 'string' then return end if meta == 'hunger' or meta == 'thirst' then @@ -280,11 +333,17 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) self.Functions.UpdatePlayerData() end + --- Gets a metadata field from the player's data + --- @param meta string + --- @return any function self.Functions.GetMetaData(meta) if not meta or type(meta) ~= 'string' then return end return self.PlayerData.metadata[meta] end + --- Add rep points to the player + --- @param rep string + --- @param amount number function self.Functions.AddRep(rep, amount) if not rep or not amount then return end local addAmount = tonumber(amount) @@ -293,6 +352,9 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) self.Functions.UpdatePlayerData() end + --- Remove rep points from the player + --- @param rep string + --- @param amount number function self.Functions.RemoveRep(rep, amount) if not rep or not amount then return end local removeAmount = tonumber(amount) @@ -305,16 +367,23 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) self.Functions.UpdatePlayerData() end + --- Get rep points from the player + --- @param rep string function self.Functions.GetRep(rep) if not rep then return end return self.PlayerData.metadata['rep'][rep] or 0 end + --- Add money to the player + --- @param moneytype string + --- @param amount number + --- @param reason? string + --- @return boolean function self.Functions.AddMoney(moneytype, amount, reason) reason = reason or 'unknown' moneytype = moneytype:lower() - amount = tonumber(amount) - if amount < 0 then return end + amount = tonumber(amount) or 0 + if amount < 0 then return false end if not self.PlayerData.money[moneytype] then return false end self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] + amount @@ -333,11 +402,16 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) return true end + --- Remove money from the player + --- @param moneytype string + --- @param amount number + --- @param reason? string + --- @return boolean function self.Functions.RemoveMoney(moneytype, amount, reason) reason = reason or 'unknown' moneytype = moneytype:lower() - amount = tonumber(amount) - if amount < 0 then return end + amount = tonumber(amount) or 0 + if amount < 0 then return false end if not self.PlayerData.money[moneytype] then return false end for _, mtype in pairs(QBCore.Config.Money.DontAllowMinus) do if mtype == moneytype then @@ -367,10 +441,15 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) return true end + --- Set money for the player + --- @param moneytype string + --- @param amount number + --- @param reason? string + --- @return boolean function self.Functions.SetMoney(moneytype, amount, reason) reason = reason or 'unknown' moneytype = moneytype:lower() - amount = tonumber(amount) + amount = tonumber(amount) or 0 if amount < 0 then return false end if not self.PlayerData.money[moneytype] then return false end local difference = amount - self.PlayerData.money[moneytype] @@ -387,12 +466,15 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) return true end + --- Get the of the player + --- @param moneytype string function self.Functions.GetMoney(moneytype) if not moneytype then return false end moneytype = moneytype:lower() return self.PlayerData.money[moneytype] end + --- Saves the player function self.Functions.Save() if self.Offline then QBCore.Player.SaveOffline(self.PlayerData) @@ -401,15 +483,18 @@ function QBCore.Player.CreatePlayer(PlayerData, Offline) end end + --- Logs out the player function self.Functions.Logout() if self.Offline then return end QBCore.Player.Logout(self.PlayerData.source) end + --- Adds a method to the player class function self.Functions.AddMethod(methodName, handler) self.Functions[methodName] = handler end + --- Adds a field to the player class function self.Functions.AddField(fieldName, data) self[fieldName] = data end @@ -434,6 +519,10 @@ end end) ]] +--- Adds a new method to the player class +--- @param ids number | number[] +--- @param methodName string +--- @param handler function function QBCore.Functions.AddPlayerMethod(ids, methodName, handler) local idType = type(ids) if idType == 'number' then @@ -461,6 +550,10 @@ end end) ]] +--- Adds a new field to the player class +--- @param ids number | number[] +--- @param fieldName string +--- @param data any function QBCore.Functions.AddPlayerField(ids, fieldName, data) local idType = type(ids) if idType == 'number' then @@ -482,6 +575,8 @@ end -- Save player info to database (make sure citizenid is the primary key in your database) +--- Saves the player +--- @param source number | string function QBCore.Player.Save(source) local ped = GetPlayerPed(source) local pcoords = GetEntityCoords(ped) @@ -506,6 +601,8 @@ function QBCore.Player.Save(source) end end +--- Saves an offline player +--- @param PlayerData table function QBCore.Player.SaveOffline(PlayerData) if PlayerData then MySQL.insert('INSERT INTO players (citizenid, cid, license, name, money, charinfo, job, gang, position, metadata) VALUES (:citizenid, :cid, :license, :name, :money, :charinfo, :job, :gang, :position, :metadata) ON DUPLICATE KEY UPDATE cid = :cid, name = :name, money = :money, charinfo = :charinfo, job = :job, gang = :gang, position = :position, metadata = :metadata', { @@ -544,6 +641,9 @@ local playertables = { -- Add tables as needed { table = 'player_vehicles' } } +--- Deletes a character +--- @param source number | string +--- @param citizenid string function QBCore.Player.DeleteCharacter(source, citizenid) local license = QBCore.Functions.GetIdentifier(source, 'license') local result = MySQL.scalar.await('SELECT license FROM players where citizenid = ?', { citizenid }) @@ -568,6 +668,8 @@ function QBCore.Player.DeleteCharacter(source, citizenid) end end +--- Force deletes a character +--- @param citizenid string function QBCore.Player.ForceDeleteCharacter(citizenid) local result = MySQL.scalar.await('SELECT license FROM players where citizenid = ?', { citizenid }) if result then @@ -594,26 +696,43 @@ end -- Inventory Backwards Compatibility +--- Saves the player's inventory +--- @param source number | string +--- @deprecated function QBCore.Player.SaveInventory(source) if GetResourceState('qb-inventory') == 'missing' then return end exports['qb-inventory']:SaveInventory(source, false) end +--- Saves the player's offline inventory +--- @param PlayerData table +--- @deprecated function QBCore.Player.SaveOfflineInventory(PlayerData) if GetResourceState('qb-inventory') == 'missing' then return end exports['qb-inventory']:SaveInventory(PlayerData, true) end +--- Gets the current total weight of some items +--- @param items table +--- @return number? function QBCore.Player.GetTotalWeight(items) if GetResourceState('qb-inventory') == 'missing' then return end return exports['qb-inventory']:GetTotalWeight(items) end +--- Gets all the slots of an item +--- @param items table +--- @param itemName string +--- @return number[]? function QBCore.Player.GetSlotsByItem(items, itemName) if GetResourceState('qb-inventory') == 'missing' then return end return exports['qb-inventory']:GetSlotsByItem(items, itemName) end +--- Gets the first slot of an item +--- @param items table +--- @param itemName string +--- @return number? function QBCore.Player.GetFirstSlotByItem(items, itemName) if GetResourceState('qb-inventory') == 'missing' then return end return exports['qb-inventory']:GetFirstSlotByItem(items, itemName) @@ -621,6 +740,8 @@ end -- Util Functions +--- Generates a random citizenid +--- @return string function QBCore.Player.CreateCitizenId() local CitizenId = tostring(QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(5)):upper() local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE citizenid = ?) AS uniqueCheck', { CitizenId }) @@ -628,6 +749,8 @@ function QBCore.Player.CreateCitizenId() return QBCore.Player.CreateCitizenId() end +--- Generates a random account number +--- @return string function QBCore.Functions.CreateAccountNumber() local AccountNumber = 'US0' .. math.random(1, 9) .. 'QBCore' .. math.random(1111, 9999) .. math.random(1111, 9999) .. math.random(11, 99) local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.account")) = ?) AS uniqueCheck', { AccountNumber }) @@ -635,6 +758,8 @@ function QBCore.Functions.CreateAccountNumber() return QBCore.Functions.CreateAccountNumber() end +--- Generates a random phone number +--- @return string function QBCore.Functions.CreatePhoneNumber() local PhoneNumber = math.random(100, 999) .. math.random(1000000, 9999999) local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.phone")) = ?) AS uniqueCheck', { PhoneNumber }) @@ -642,6 +767,8 @@ function QBCore.Functions.CreatePhoneNumber() return QBCore.Functions.CreatePhoneNumber() end +--- Generates a random fingerprint +--- @return string function QBCore.Player.CreateFingerId() local FingerId = tostring(QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(1) .. QBCore.Shared.RandomInt(2) .. QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(4)) local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(metadata, "$.fingerprint")) = ?) AS uniqueCheck', { FingerId }) @@ -649,6 +776,8 @@ function QBCore.Player.CreateFingerId() return QBCore.Player.CreateFingerId() end +--- Generates a random wallet id +--- @return string function QBCore.Player.CreateWalletId() local WalletId = 'QB-' .. math.random(11111111, 99999999) local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(metadata, "$.walletid")) = ?) AS uniqueCheck', { WalletId }) @@ -656,6 +785,8 @@ function QBCore.Player.CreateWalletId() return QBCore.Player.CreateWalletId() end +--- Generates a random serial number +--- @return number function QBCore.Player.CreateSerialNumber() local SerialNumber = math.random(11111111, 99999999) local result = MySQL.prepare.await('SELECT EXISTS(SELECT 1 FROM players WHERE JSON_UNQUOTE(JSON_EXTRACT(metadata, "$.phonedata.SerialNumber")) = ?) AS uniqueCheck', { SerialNumber }) From 54f7886048a452b352c6b3239681bbba2c83aa23 Mon Sep 17 00:00:00 2001 From: Cocodrulo <142546774+Cocodrulo@users.noreply.github.com> Date: Sun, 16 Feb 2025 16:16:37 +0000 Subject: [PATCH 2/4] Remove redundant code and improve function efficiency in QBCore functions --- client/functions.lua | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/client/functions.lua b/client/functions.lua index c281e9e19..b5f66ddb6 100644 --- a/client/functions.lua +++ b/client/functions.lua @@ -1037,10 +1037,8 @@ function QBCore.Functions.GetBoneDistance(entity, boneType, boneIndex) local bone if boneType == 1 then bone = GetPedBoneIndex(entity, tonumber(boneIndex) or 0) - bone = GetPedBoneIndex(entity, tonumber(boneIndex) or 0) else bone = GetEntityBoneIndexByName(entity, tostring(boneIndex) or '') - bone = GetEntityBoneIndexByName(entity, tostring(boneIndex) or '') end local boneCoords = GetWorldPositionOfEntityBone(entity, bone) local playerCoords = GetEntityCoords(PlayerPedId()) @@ -1065,8 +1063,6 @@ function QBCore.Functions.AttachProp(ped, model, boneId, x, y, z, xR, yR, zR, ve QBCore.Functions.LoadModel(modelHash) local prop = CreateObject(modelHash, 1.0, 1.0, 1.0, true, true, false) AttachEntityToEntity(prop, ped, bone, x, y, z, xR, yR, zR, true, true, false, true, not vertex and 2 or 0, true) - local prop = CreateObject(modelHash, 1.0, 1.0, 1.0, true, true, false) - AttachEntityToEntity(prop, ped, bone, x, y, z, xR, yR, zR, true, true, false, true, not vertex and 2 or 0, true) SetModelAsNoLongerNeeded(modelHash) return prop end @@ -1136,7 +1132,6 @@ function QBCore.Functions.StartParticleAtCoord(dict, ptName, looped, coords, rot SetPtfxAssetNextCall(dict) local particleHandle if looped then - particleHandle = StartParticleFxLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0, false, false, false, false) particleHandle = StartParticleFxLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0, false, false, false, false) if color then SetParticleFxLoopedColour(particleHandle, color.r, color.g, color.b, false) @@ -1145,7 +1140,6 @@ function QBCore.Functions.StartParticleAtCoord(dict, ptName, looped, coords, rot if duration then Wait(duration) StopParticleFxLooped(particleHandle, false) - StopParticleFxLooped(particleHandle, false) end else SetParticleFxNonLoopedAlpha(alpha or 10.0) @@ -1153,7 +1147,6 @@ function QBCore.Functions.StartParticleAtCoord(dict, ptName, looped, coords, rot SetParticleFxNonLoopedColour(color.r, color.g, color.b) end StartParticleFxNonLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0, false, false, false) - StartParticleFxNonLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0, false, false, false) end return particleHandle end @@ -1170,10 +1163,8 @@ function QBCore.Functions.StartParticleOnEntity(dict, ptName, looped, entity, bo if looped then if bone then particleHandle = StartParticleFxLoopedOnEntityBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale, false, false, false) - particleHandle = StartParticleFxLoopedOnEntityBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale, false, false, false) else particleHandle = StartParticleFxLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale, false, false, false) - particleHandle = StartParticleFxLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale, false, false, false) end if evolution then SetParticleFxLoopedEvolution(particleHandle, evolution.name, evolution.amount, false) @@ -1194,10 +1185,8 @@ function QBCore.Functions.StartParticleOnEntity(dict, ptName, looped, entity, bo end if bone then StartParticleFxNonLoopedOnPedBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale, false, false, false) - StartParticleFxNonLoopedOnPedBone(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneID, scale, false, false, false) else StartParticleFxNonLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale, false, false, false) - StartParticleFxNonLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale, false, false, false) end end return particleHandle @@ -1261,7 +1250,6 @@ end function QBCore.Functions.GetGroundZCoord(coords) if not coords then return end - local retval, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, false) local retval, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, false) if retval then return vector3(coords.x, coords.y, groundZ) From d0e9606efff31f8a078fb62ef032ec6048baa51f Mon Sep 17 00:00:00 2001 From: Cocodrulo <142546774+Cocodrulo@users.noreply.github.com> Date: Wed, 9 Apr 2025 21:43:59 +0100 Subject: [PATCH 3/4] refactor: improve documentation and formatting in drawtext.js; remove unused testing.js and utils.js files --- html/js/drawtext.js | 210 +++++++++++++++++++++----------------------- html/js/testing.js | 47 ---------- html/js/utils.js | 27 ------ 3 files changed, 102 insertions(+), 182 deletions(-) delete mode 100644 html/js/testing.js delete mode 100644 html/js/utils.js diff --git a/html/js/drawtext.js b/html/js/drawtext.js index 752b14c02..1fd4f2af0 100644 --- a/html/js/drawtext.js +++ b/html/js/drawtext.js @@ -1,159 +1,153 @@ let direction = null; /** - * Draw text on the screen - * @param {{position: 'left' | 'top' | 'right', text: string}} textData - * @returns {void} + * Draws text on the screen + * @param {{text: string, position?: 'left' | 'right' | 'top'}} textData */ const drawText = async (textData) => { - const text = document.getElementById("text"); - let { position } = textData; - switch (textData.position) { - case "left": - addClass(text, position); - direction = "left"; - break; - case "top": - addClass(text, position); - direction = "top"; - break; - case "right": - addClass(text, position); - direction = "right"; - break; - default: - addClass(text, "left"); - direction = "left"; - break; - } + const text = document.getElementById("text"); + let { position } = textData; + switch (textData.position) { + case "left": + addClass(text, position); + direction = "left"; + break; + case "top": + addClass(text, position); + direction = "top"; + break; + case "right": + addClass(text, position); + direction = "right"; + break; + default: + addClass(text, "left"); + direction = "left"; + break; + } - text.innerHTML = textData.text; - document.getElementById("drawtext-container").style.display = "block"; - await sleep(100); - addClass(text, "show"); + text.innerHTML = textData.text; + document.getElementById("drawtext-container").style.display = "block"; + await sleep(100); + addClass(text, "show"); }; /** - * Change the text on the screen - * @param {{position: 'left' | 'top' | 'right', text: string}} textData - * @returns {void} + * Changes the text on the screen + * @param {{text: string, position?: 'left' | 'right' | 'top'}} textData */ const changeText = async (textData) => { - const text = document.getElementById("text"); - let { position } = textData; + const text = document.getElementById("text"); + let { position } = textData; - removeClass(text, "show"); - addClass(text, "pressed"); - addClass(text, "hide"); + removeClass(text, "show"); + addClass(text, "pressed"); + addClass(text, "hide"); - await sleep(500); - removeClass(text, "left"); - removeClass(text, "right"); - removeClass(text, "top"); - removeClass(text, "bottom"); - removeClass(text, "hide"); - removeClass(text, "pressed"); + await sleep(500); + removeClass(text, "left"); + removeClass(text, "right"); + removeClass(text, "top"); + removeClass(text, "bottom"); + removeClass(text, "hide"); + removeClass(text, "pressed"); - switch (textData.position) { - case "left": - addClass(text, position); - direction = "left"; - break; - case "top": - addClass(text, position); - direction = "top"; - break; - case "right": - addClass(text, position); - direction = "right"; - break; - default: - addClass(text, "left"); - direction = "left"; - break; - } - text.innerHTML = textData.text; + switch (textData.position) { + case "left": + addClass(text, position); + direction = "left"; + break; + case "top": + addClass(text, position); + direction = "top"; + break; + case "right": + addClass(text, position); + direction = "right"; + break; + default: + addClass(text, "left"); + direction = "left"; + break; + } + text.innerHTML = textData.text; - await sleep(100); - text.classList.add("show"); + await sleep(100); + text.classList.add("show"); }; /** - * Hide the text on the screen - * @returns {void} + * Hides the displayed text */ const hideText = async () => { - const text = document.getElementById("text"); - removeClass(text, "show"); - addClass(text, "hide"); + const text = document.getElementById("text"); + removeClass(text, "show"); + addClass(text, "hide"); - setTimeout(() => { - removeClass(text, "left"); - removeClass(text, "right"); - removeClass(text, "top"); - removeClass(text, "bottom"); - removeClass(text, "hide"); - removeClass(text, "pressed"); - document.getElementById("drawtext-container").style.display = "none"; - }, 1000); + setTimeout(() => { + removeClass(text, "left"); + removeClass(text, "right"); + removeClass(text, "top"); + removeClass(text, "bottom"); + removeClass(text, "hide"); + removeClass(text, "pressed"); + document.getElementById("drawtext-container").style.display = "none"; + }, 1000); }; /** - * Key pressed animation - * @returns {void} + * Handles key press events */ const keyPressed = () => { - const text = document.getElementById("text"); - addClass(text, "pressed"); + const text = document.getElementById("text"); + addClass(text, "pressed"); }; window.addEventListener("message", (event) => { - const data = event.data; - const action = data.action; - const textData = data.data; - switch (action) { - case "DRAW_TEXT": - return drawText(textData); - case "CHANGE_TEXT": - return changeText(textData); - case "HIDE_TEXT": - return hideText(); - case "KEY_PRESSED": - return keyPressed(); - default: - return; + const data = event.data; + const action = data.action; + const textData = data.data; + switch (action) { + case "DRAW_TEXT": + return drawText(textData); + case "CHANGE_TEXT": + return changeText(textData); + case "HIDE_TEXT": + return hideText(); + case "KEY_PRESSED": + return keyPressed(); + default: + return; } }); /** - * Sleep for a given amount of time + * Sleep function to delay execution for a given number of milliseconds. * @param {number} ms - * @returns + * @returns Promise */ const sleep = (ms) => { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); }; /** - * Remove a class from an element + * Removes a class from an element if it exists. * @param {HTMLElement} element * @param {string} name - * @returns {void} */ const removeClass = (element, name) => { - if (element.classList.contains(name)) { - element.classList.remove(name); - } + if (element.classList.contains(name)) { + element.classList.remove(name); + } }; /** - * Add a class to an element + * Adds a class to an element if it doesn't already exist. * @param {HTMLElement} element * @param {string} name - * @returns {void} */ const addClass = (element, name) => { - if (!element.classList.contains(name)) { - element.classList.add(name); - } + if (!element.classList.contains(name)) { + element.classList.add(name); + } }; diff --git a/html/js/testing.js b/html/js/testing.js deleted file mode 100644 index ca62ed0f5..000000000 --- a/html/js/testing.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Will register dev utilities on window - * @returns {void} - */ -export const registerWindowMethods = () => { - window.SendNotification = (data) => { - window.dispatchEvent( - new MessageEvent("message", { - data: { - action: "notify", - ...data, - }, - }) - ); - }; -}; - -// Used for browser env handling -export const BrowserMockConfigData = { - NotificationStyling: { - group: true, - position: "top-right", - progress: true, - }, - VariantDefinitions: { - success: { - classes: "success", - icon: "done", - }, - primary: { - classes: "primary", - icon: "info", - }, - error: { - classes: "error", - icon: "dangerous", - }, - police: { - classes: "police", - icon: "local_police", - }, - ambulance: { - classes: "ambulance", - icon: "fas fa-ambulance", - }, - }, -}; diff --git a/html/js/utils.js b/html/js/utils.js deleted file mode 100644 index bf2fb8cf0..000000000 --- a/html/js/utils.js +++ /dev/null @@ -1,27 +0,0 @@ -// Returns whether we are in browser or not -export const isEnvBrowser = () => !window.invokeNative; - -/** - * Real simple wrapper around fetch api for NUI focus - * it will return the mockData param if we are in browser. So we don't - * make a useless request to a hostname that doesn't exist. - * @param evName {string} - The callback event name/type - * @param data {any} - Optional `body` data JSON stringified & passed with the request - * @param mockData {any} - Mock data to return if this is running in browser - * @return Promise - **/ -export const fetchNui = async (evName, data, mockData = null) => { - if (isEnvBrowser()) return mockData; - - const resourceName = window.GetParentResourceName(); - - const rawResp = await fetch(`https://${resourceName}/${evName}`, { - body: JSON.stringify(data), - headers: { - "Content-Type": "application/json; charset=UTF8", - }, - method: "POST", - }); - - return await rawResp.json(); -}; From 7a460379161e0dc714fb5e3f6fc2321a891a9c2c Mon Sep 17 00:00:00 2001 From: Cocodrulo <142546774+Cocodrulo@users.noreply.github.com> Date: Wed, 9 Apr 2025 21:47:21 +0100 Subject: [PATCH 4/4] fix: remove unnecessary whitespace in PaycheckInterval function --- server/functions.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/functions.lua b/server/functions.lua index 8e925799e..a217f5af2 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -389,9 +389,9 @@ end --- Do the paycheck interval function PaycheckInterval() - if not next(QBCore.Players) then + if not next(QBCore.Players) then SetTimeout(QBCore.Config.Money.PayCheckTimeOut * (60 * 1000), PaycheckInterval) -- Prevent paychecks from stopping forever once 0 players - return + return end for _, Player in pairs(QBCore.Players) do if not Player then return end