diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 0000000..65be3a5 --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,17 @@ +# Set to true to add reviewers to pull requests +addReviewers: true + +# Set to true to add assignees to pull requests +addAssignees: author + +# A list of reviewers to be added to pull requests (GitHub user name) +reviewers: + - qbcore-framework/maintenance + +# A list of keywords to be skipped the process that add reviewers if pull requests include it +skipKeywords: + - wip + +# A number of reviewers added to the pull request +# Set 0 to add all the reviewers (default: 0) +numberOfReviewers: 0 diff --git a/client/drawtext.lua b/client/drawtext.lua new file mode 100644 index 0000000..ef669b2 --- /dev/null +++ b/client/drawtext.lua @@ -0,0 +1,60 @@ +local function hideText() + SendNUIMessage({ + action = 'HIDE_TEXT', + }) +end + +local function drawText(text, position) + if not type(position) == "string" then position = "left" end + + SendNUIMessage({ + action = 'DRAW_TEXT', + data = { + text = text, + position = position + } + }) +end + +local function changeText(text, position) + if not type(position) == "string" then position = "left" end + + SendNUIMessage({ + action = 'CHANGE_TEXT', + data = { + text = text, + position = position + } + }) +end + +local function keyPressed() + CreateThread(function() -- Not sure if a thread is needed but why not eh? + SendNUIMessage({ + action = 'KEY_PRESSED', + }) + Wait(500) + hideText() + end) +end + +RegisterNetEvent('qb-core:client:DrawText', function(text, position) + drawText(text, position) +end) + +RegisterNetEvent('qb-core:client:ChangeText', function(text, position) + changeText(text, position) +end) + +RegisterNetEvent('qb-core:client:HideText', function() + hideText() +end) + +RegisterNetEvent('qb-core:client:KeyPressed', function() + keyPressed() +end) + +exports('DrawText', drawText) +exports('ChangeText', changeText) +exports('HideText', hideText) +exports('KeyPressed', keyPressed) diff --git a/client/events.lua b/client/events.lua index 18a4a61..df2adec 100644 --- a/client/events.lua +++ b/client/events.lua @@ -4,10 +4,9 @@ RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() ShutdownLoadingScreenNui() LocalPlayer.state:set('isLoggedIn', true, false) - if QBConfig.Server.pvp then - SetCanAttackFriendly(PlayerPedId(), true, false) - NetworkSetFriendlyFireOption(true) - end + if not QBConfig.Server.PVP then return end + SetCanAttackFriendly(PlayerPedId(), true, false) + NetworkSetFriendlyFireOption(true) end) RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() @@ -121,16 +120,21 @@ end) RegisterNetEvent('QBCore:Command:SpawnVehicle', function(vehName) local ped = PlayerPedId() local hash = GetHashKey(vehName) - if not IsModelInCdimage(hash) then - return - end + local veh = GetVehiclePedIsUsing(ped) + if not IsModelInCdimage(hash) then return end RequestModel(hash) while not HasModelLoaded(hash) do - Wait(10) + Wait(0) end + + if IsPedInAnyVehicle(ped) then + DeleteVehicle(veh) + end + local vehicle = CreateVehicle(hash, GetEntityCoords(ped), GetEntityHeading(ped), true, false) TaskWarpPedIntoVehicle(ped, vehicle, -1) - SetModelAsNoLongerNeeded(vehicle) + SetVehicleFuelLevel(vehicle, 100.0) + SetModelAsNoLongerNeeded(hash) TriggerEvent("vehiclekeys:client:SetOwner", QBCore.Functions.GetPlate(vehicle)) end) @@ -191,9 +195,9 @@ local function Draw3DText(coords, str) SetTextProportional(1) SetTextOutline() SetTextCentre(1) - SetTextEntry("STRING") - AddTextComponentString(str) - DrawText(worldX, worldY) + BeginTextCommandDisplayText("STRING") + AddTextComponentSubstringPlayerName(str) + EndTextCommandDisplayText(worldX, worldY) end end @@ -209,3 +213,16 @@ RegisterNetEvent('QBCore:Command:ShowMe3D', function(senderId, msg) end end) end) + +-- Listen to Shared being updated +RegisterNetEvent('QBCore:Client:OnSharedUpdate', function(tableName, key, value) + QBCore.Shared[tableName][key] = value + TriggerEvent('QBCore:Client:UpdateObject') +end) + +RegisterNetEvent('QBCore:Client:OnSharedUpdateMultiple', function(tableName, values) + for key, value in pairs(values) do + QBCore.Shared[tableName][key] = value + end + TriggerEvent('QBCore:Client:UpdateObject') +end) diff --git a/client/functions.lua b/client/functions.lua index 87a5887..c11c1ab 100644 --- a/client/functions.lua +++ b/client/functions.lua @@ -1,20 +1,14 @@ QBCore.Functions = {} -QBCore.RequestId = 0 -- Player function QBCore.Functions.GetPlayerData(cb) - if cb then - cb(QBCore.PlayerData) - else - return QBCore.PlayerData - end + if not cb then return QBCore.PlayerData end + cb(QBCore.PlayerData) end function QBCore.Functions.GetCoords(entity) - local coords = GetEntityCoords(entity, false) - local heading = GetEntityHeading(entity) - return vector4(coords.x, coords.y, coords.z, heading) + return vector4(GetEntityCoords(entity), GetEntityHeading(entity)) end function QBCore.Functions.HasItem(item) @@ -58,37 +52,60 @@ function QBCore.Functions.DrawText3D(x, y, z, text) ClearDrawOrigin() end -function QBCore.Functions.CreateBlip(coords, sprite, display, scale, colour, shortRange, title) - if not coords or not sprite or not display or not scale or not colour or shortRange == nil or not title then - print("Blip failed to create, most likely missed a setting, debug log: ") - print("Coords: " .. coords .. " Sprite: " .. sprite .. " Display: " .. display .. " scale: " .. scale .. " shortRange: " .. shortRange .. " Title: " .. title .. " if you're attempting to use a blip without a title, use an empty string.") - else - blip = AddBlipForCoord(coords) - SetBlipSprite(blip, sprite) - SetBlipDisplay(blip, display) - SetBlipScale(blip, scale) - SetBlipColour(blip, colour) - SetBlipAsShortRange(blip, shortRange) +function QBCore.Functions.CreateBlip(coords, sprite, display, scale, colour, shortRange, title, alpha, friendly, bright, category, hiddenOnLegend, highDetail, rotation, cone, shrink, showHeight, showNumber, showOutline) + if not coords or (type(coords) ~= 'table' and type(coords) ~= 'vector3') then + print("Blip failed to create, the coords were not specified or was specified in the wrong format, coords must be a table or vector3, debug log: ") + print(("Coords: %s Sprite: %s Display: %s scale: %s shortRange: %s Title: %s Alpha: %s Friendly: %s Bright: %s Category: %s Hidden On Legend: %s High Detail: %s Rotation: %s Cone: %s Shrink: %s Show Heigt: %s Show Number: %s Show Outline: %s"):format(coords, sprite, display, scale, colour, shortRange, title, alpha, friendly, bright, category, hiddenOnLegend, highDetail, rotation, cone, shrink, showHeight, showNumber, showOutline)) + return + end + coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords + local blip = AddBlipForCoord(coords) + if sprite then SetBlipSprite(blip, sprite) end + if display then SetBlipDisplay(blip, display) end + if scale then SetBlipScale(blip, scale) end + if colour then SetBlipColour(blip, colour) end + if shortRange ~= nil then SetBlipAsShortRange(blip, shortRange) end + if title then BeginTextCommandSetBlipName("STRING") AddTextComponentString(title) EndTextCommandSetBlipName(blip) end + if alpha then SetBlipAlpha(blip, alpha) end + if friendly ~= nil then SetBlipAsFriendly(blip, friendly) end + if bright ~= nil then SetBlipBright(blip, bright) end + if category then SetBlipCategory(blip, category) end -- categories can be found here: https://docs.fivem.net/natives/?_0x234CDD44D996FD9A + if hiddenOnLegend ~= nil then SetBlipHiddenOnLegend(blip, hiddenOnLegend) end + if highDetail ~= nil then SetBlipHighDetail(blip, highDetail) end + if rotation then SetBlipRotation(blip, rotation) end -- Required to be an integer + if cone ~= nil then SetBlipShowCone(blip, cone) end + if shrink ~= nil then SetBlipShrink(blip, shrink) end + if showHeight ~= nil then ShowHeightOnBlip(blip, showHeight) end + if showNumber then ShowNumberOnBlip(blip, showNumber) end + if showOutline ~= nil then ShowOutlineIndicatorOnBlip(blip, showOutline) end + return blip end function QBCore.Functions.RequestAnimDict(animDict) - if not HasAnimDictLoaded(animDict) then - RequestAnimDict(animDict) - - while not HasAnimDictLoaded(animDict) do - Wait(4) - end + if HasAnimDictLoaded(animDict) then return end + RequestAnimDict(animDict) + while not HasAnimDictLoaded(animDict) do + Wait(0) end end -function QBCore.Functions.LoadModel(ModelName) - RequestModel(ModelName) - while not HasModelLoaded(ModelName) do - Wait(100) +function QBCore.Functions.PlayAnim(animDict, animName, upperbodyOnly, duration) + local flags = upperbodyOnly == true and 16 or 0 + local runTime = duration ~= nil and duration or -1 + QBCore.Functions.RequestAnimDict(animDict) + TaskPlayAnim(PlayerPedId(), animDict, animName, 8.0, 1.0, runTime, flags, 0.0, false, false, true) + RemoveAnimDict(animDict) +end + +function QBCore.Functions.LoadModel(model) + if HasModelLoaded(model) then return end + RequestModel(model) + while not HasModelLoaded(model) do + Wait(0) end end @@ -96,25 +113,25 @@ RegisterNUICallback('getNotifyConfig', function(_, cb) cb(QBCore.Config.Notify) end) -function QBCore.Functions.Notify(text, textype, length) +function QBCore.Functions.Notify(text, texttype, length) if type(text) == "table" then local ttext = text.text or 'Placeholder' local caption = text.caption or 'Placeholder' - local ttype = textype or 'primary' - local length = length or 5000 + texttype = texttype or 'primary' + length = length or 5000 SendNUIMessage({ action = 'notify', - type = ttype, + type = texttype, length = length, text = ttext, caption = caption }) else - local ttype = textype or 'primary' - local length = length or 5000 + texttype = texttype or 'primary' + length = length or 5000 SendNUIMessage({ action = 'notify', - type = ttype, + type = texttype, length = length, text = text }) @@ -131,6 +148,7 @@ function QBCore.Functions.TriggerCallback(name, cb, ...) end 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({ name = name:lower(), duration = duration, @@ -159,17 +177,19 @@ end function QBCore.Functions.GetVehicles() return GetGamePool('CVehicle') end + function QBCore.Functions.GetObjects() return GetGamePool('CObject') end + function QBCore.Functions.GetPlayers() return GetActivePlayers() end function QBCore.Functions.GetPeds(ignoreList) local pedPool = GetGamePool('CPed') - local ignoreList = ignoreList or {} local peds = {} + ignoreList = ignoreList or {} for i = 1, #pedPool, 1 do local found = false for j = 1, #ignoreList, 1 do @@ -208,19 +228,19 @@ function QBCore.Functions.GetClosestPed(coords, ignoreList) end function QBCore.Functions.IsWearingGloves() - local armIndex = GetPedDrawableVariation(PlayerPedId(), 3) - local model = GetEntityModel(PlayerPedId()) - local retval = true + local ped = PlayerPedId() + local armIndex = GetPedDrawableVariation(ped, 3) + local model = GetEntityModel(ped) if model == `mp_m_freemode_01` then - if QBCore.Shared.MaleNoGloves[armIndex] ~= nil and QBCore.Shared.MaleNoGloves[armIndex] then - retval = false + if QBCore.Shared.MaleNoGloves[armIndex] then + return false end else - if QBCore.Shared.FemaleNoGloves[armIndex] ~= nil and QBCore.Shared.FemaleNoGloves[armIndex] then - retval = false + if QBCore.Shared.FemaleNoGloves[armIndex] then + return false end end - return retval + return true end function QBCore.Functions.GetClosestPlayer(coords) @@ -248,14 +268,14 @@ function QBCore.Functions.GetClosestPlayer(coords) end function QBCore.Functions.GetPlayersFromCoords(coords, distance) - local players = QBCore.Functions.GetPlayers() + local players = GetActivePlayers() local ped = PlayerPedId() if coords then coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords else coords = GetEntityCoords(ped) end - local distance = distance or 5 + distance = distance or 5 local closePlayers = {} for _, player in pairs(players) do local target = GetPlayerPed(player) @@ -313,83 +333,68 @@ end function QBCore.Functions.GetClosestBone(entity, list) local playerCoords, bone, coords, distance = GetEntityCoords(PlayerPedId()) - for _, element in pairs(list) do local boneCoords = GetWorldPositionOfEntityBone(entity, element.id or element) local boneDistance = #(playerCoords - boneCoords) - if not coords then bone, coords, distance = element, boneCoords, boneDistance elseif distance > boneDistance then bone, coords, distance = element, boneCoords, boneDistance end end - if not bone then bone = {id = GetEntityBoneIndexByName(entity, "bodyshell"), type = "remains", name = "bodyshell"} coords = GetWorldPositionOfEntityBone(entity, bone.id) distance = #(coords - playerCoords) end - return bone, coords, distance end -function QBCore.Functions.GetBoneDistance(entity, Type, Bone) +function QBCore.Functions.GetBoneDistance(entity, boneType, boneIndex) local bone - - if Type == 1 then - bone = GetPedBoneIndex(entity, Bone) + if boneType == 1 then + bone = GetPedBoneIndex(entity, boneIndex) else - bone = GetEntityBoneIndexByName(entity, Bone) + bone = GetEntityBoneIndexByName(entity, boneIndex) end - local boneCoords = GetWorldPositionOfEntityBone(entity, bone) local playerCoords = GetEntityCoords(PlayerPedId()) - return #(boneCoords - playerCoords) end -function QBCore.Functions.AttachProp(ped, model, boneId, x, y, z, xR, yR, zR, Vertex) - local modelHash = GetHashKey(model) +function QBCore.Functions.AttachProp(ped, model, boneId, x, y, z, xR, yR, zR, vertex) + local modelHash = type(model) == 'string' and GetHashKey(model) or model local bone = GetPedBoneIndex(ped, boneId) - RequestModel(modelHash) - while not HasModelLoaded(modelHash) do - Wait(0) - end + 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) + AttachEntityToEntity(prop, ped, bone, x, y, z, xR, yR, zR, 1, 1, 0, 1, not vertex and 2 or 0, 1) SetModelAsNoLongerNeeded(modelHash) return prop end -- Vehicle -function QBCore.Functions.SpawnVehicle(model, cb, coords, isnetworked) - local model = GetHashKey(model) +function QBCore.Functions.SpawnVehicle(model, cb, coords, isnetworked, teleportInto) local ped = PlayerPedId() + model = type(model) == 'string' and GetHashKey(model) or model + if not IsModelInCdimage(model) then return end if coords then coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords else coords = GetEntityCoords(ped) end - local isnetworked = isnetworked or true - if not IsModelInCdimage(model) then - return - end - RequestModel(model) - while not HasModelLoaded(model) do - Citizen.Wait(10) - end + isnetworked = isnetworked or true + QBCore.Functions.LoadModel(model) local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, isnetworked, false) local netid = NetworkGetNetworkIdFromEntity(veh) SetVehicleHasBeenOwnedByPlayer(veh, true) SetNetworkIdCanMigrate(netid, true) SetVehicleNeedsToBeHotwired(veh, false) SetVehRadioStation(veh, 'OFF') + SetVehicleFuelLevel(veh, 100.0) SetModelAsNoLongerNeeded(model) - if cb then - cb(veh) - end + if teleportInto then TaskWarpPedIntoVehicle(PlayerPedId(), veh, -1) end + if cb then cb(veh) end end function QBCore.Functions.DeleteVehicle(vehicle) @@ -403,9 +408,14 @@ function QBCore.Functions.GetPlate(vehicle) end function QBCore.Functions.SpawnClear(coords, radius) + if coords then + coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords + else + coords = GetEntityCoords(PlayerPedId()) + end local vehicles = GetGamePool('CVehicle') local closeVeh = {} - for i=1, #vehicles, 1 do + for i = 1, #vehicles, 1 do local vehicleCoords = GetEntityCoords(vehicles[i]) local distance = #(vehicleCoords - coords) if distance <= radius then @@ -418,20 +428,20 @@ end function QBCore.Functions.GetVehicleProperties(vehicle) if DoesEntityExist(vehicle) then - local colorPrimary, colorSecondary = GetVehicleColours(vehicle) local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle) - local extras = {} + local colorPrimary, colorSecondary = GetVehicleColours(vehicle) if GetIsVehiclePrimaryColourCustom(vehicle) then r, g, b = GetVehicleCustomPrimaryColour(vehicle) - colorPrimary = { r, g, b } + colorPrimary = {r, g, b} end if GetIsVehicleSecondaryColourCustom(vehicle) then r, g, b = GetVehicleCustomSecondaryColour(vehicle) - colorSecondary = { r, g, b } + colorSecondary = {r, g, b} end + local extras = {} for extraId = 0, 12 do if DoesExtraExist(vehicle, extraId) then local state = IsVehicleExtraTurnedOn(vehicle, extraId) == 1 @@ -439,10 +449,39 @@ function QBCore.Functions.GetVehicleProperties(vehicle) end end - if GetVehicleMod(vehicle, 48) == -1 and GetVehicleLivery(vehicle) ~= -1 then + local modLivery = GetVehicleMod(vehicle, 48) + if GetVehicleMod(vehicle, 48) == -1 and GetVehicleLivery(vehicle) ~= 0 then modLivery = GetVehicleLivery(vehicle) - else - modLivery = GetVehicleMod(vehicle, 48) + end + + local neons = {} + for i = 0, 3 do + neons[i] = IsVehicleNeonLightEnabled(vehicle, i) + end + + local tireHealth = {} + for i = 0, 3 do + tireHealth[i] = GetVehicleWheelHealth(vehicle, i) + end + + local tireBurstState = {} + for i = 0, 5 do + tireBurstState[i] = IsVehicleTyreBurst(vehicle, i, false) + end + + local tireBurstCompletely = {} + for i = 0, 5 do + tireBurstCompletely[i] = IsVehicleTyreBurst(vehicle, i, true) + end + + local windowStatus = {} + for i = 0, 7 do + windowStatus[i] = IsVehicleWindowIntact(vehicle, i) == 1 + end + + local doorStatus = {} + for i = 0, 5 do + doorStatus[i] = IsVehicleDoorDamaged(vehicle, i) == 1 end return { @@ -454,22 +493,26 @@ function QBCore.Functions.GetVehicleProperties(vehicle) 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, - interiorColor = GetVehicleInteriorColor(vehicle), 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, xenonColor = GetVehicleXenonLightsColour(vehicle), - neonEnabled = { - IsVehicleNeonLightEnabled(vehicle, 0), - IsVehicleNeonLightEnabled(vehicle, 1), - IsVehicleNeonLightEnabled(vehicle, 2), - IsVehicleNeonLightEnabled(vehicle, 3) - }, + neonEnabled = neons, neonColor = table.pack(GetVehicleNeonLightsColour(vehicle)), + headlightColor = GetVehicleHeadlightsColour(vehicle), + interiorColor = GetVehicleInteriorColour(vehicle), extras = extras, tyreSmokeColor = table.pack(GetVehicleTyreSmokeColor(vehicle)), modSpoilers = GetVehicleMod(vehicle, 0), @@ -489,8 +532,11 @@ function QBCore.Functions.GetVehicleProperties(vehicle) 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), @@ -518,7 +564,10 @@ function QBCore.Functions.GetVehicleProperties(vehicle) 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), } else return @@ -527,6 +576,16 @@ end 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) + else + SetVehicleExtra(vehicle, tonumber(id), 1) + end + end + end + local colorPrimary, colorSecondary = GetVehicleColours(vehicle) local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle) SetVehicleModKit(vehicle, 0) @@ -542,12 +601,18 @@ function QBCore.Functions.SetVehicleProperties(vehicle, props) if props.engineHealth then SetVehicleEngineHealth(vehicle, props.engineHealth + 0.0) end + if props.tankHealth then + SetVehiclePetrolTankHealth(vehicle, props.tankHealth) + end if props.fuelLevel then SetVehicleFuelLevel(vehicle, props.fuelLevel + 0.0) end if props.dirtLevel then SetVehicleDirtLevel(vehicle, props.dirtLevel + 0.0) end + if props.oilLevel then + SetVehicleOilLevel(vehicle, props.oilLevel) + end if props.color1 then if type(props.color1) == "number" then SetVehicleColours(vehicle, props.color1, colorSecondary) @@ -577,29 +642,59 @@ function QBCore.Functions.SetVehicleProperties(vehicle, props) if props.wheels then SetVehicleWheelType(vehicle, props.wheels) end + if props.tireHealth then + for wheelIndex, health in pairs(props.tireHealth) do + SetVehicleWheelHealth(vehicle, wheelIndex, health) + end + end + if props.tireBurstState then + for wheelIndex, burstState in pairs(props.tireBurstState) do + if burstState then + SetVehicleTyreBurst(vehicle, tonumber(wheelIndex), 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) + end + end + end if props.windowTint then SetVehicleWindowTint(vehicle, props.windowTint) end - if props.neonEnabled then - SetVehicleNeonLightEnabled(vehicle, 0, props.neonEnabled[1]) - SetVehicleNeonLightEnabled(vehicle, 1, props.neonEnabled[2]) - SetVehicleNeonLightEnabled(vehicle, 2, props.neonEnabled[3]) - SetVehicleNeonLightEnabled(vehicle, 3, props.neonEnabled[4]) + if props.windowStatus then + for windowIndex, smashWindow in pairs(props.windowStatus) do + if not smashWindow then SmashVehicleWindow(vehicle, windowIndex) end + end end - if props.extras then - for id, enabled in pairs(props.extras) do - if enabled then - SetVehicleExtra(vehicle, tonumber(id), 0) - else - SetVehicleExtra(vehicle, tonumber(id), 1) + if props.doorStatus then + for doorIndex, breakDoor in pairs(props.doorStatus) do + if breakDoor then + SetVehicleDoorBroken(vehicle, doorIndex, true) end end end + if props.neonEnabled then + for neonIndex, enableNeons in pairs(props.neonEnabled) do + SetVehicleNeonLightEnabled(vehicle, neonIndex, enableNeons) + end + end if props.neonColor then SetVehicleNeonLightsColour(vehicle, props.neonColor[1], props.neonColor[2], props.neonColor[3]) end - if props.modSmokeEnabled then - ToggleVehicleMod(vehicle, 20, true) + if props.headlightColor then + SetVehicleHeadlightsColour(vehicle, props.headlightColor) + end + if props.interiorColor then + SetVehicleInteriorColour(vehicle, props.interiorColor) + end + if props.wheelSize then + SetVehicleWheelSize(vehicle, props.wheelSize) + end + if props.wheelWidth then + SetVehicleWheelWidth(vehicle, props.wheelWidth) end if props.tyreSmokeColor then SetVehicleTyreSmokeColor(vehicle, props.tyreSmokeColor[1], props.tyreSmokeColor[2], props.tyreSmokeColor[3]) @@ -655,9 +750,21 @@ function QBCore.Functions.SetVehicleProperties(vehicle, props) if props.modArmor then SetVehicleMod(vehicle, 16, props.modArmor, false) end + if props.modKit17 then + SetVehicleMod(vehicle, 17, props.modKit17, false) + end if props.modTurbo then ToggleVehicleMod(vehicle, 18, props.modTurbo) end + if props.modKit19 then + SetVehicleMod(vehicle, 19, props.modKit19, false) + end + if props.modSmokeEnabled then + ToggleVehicleMod(vehicle, 20, props.modSmokeEnabled) + end + if props.modKit21 then + SetVehicleMod(vehicle, 21, props.modKit21, false) + end if props.modXenon then ToggleVehicleMod(vehicle, 22, props.modXenon) end @@ -742,26 +849,39 @@ function QBCore.Functions.SetVehicleProperties(vehicle, props) if props.modWindows then SetVehicleMod(vehicle, 46, props.modWindows, false) end + if props.modKit47 then + SetVehicleMod(vehicle, 47, props.modKit47, false) + end if props.modLivery then SetVehicleMod(vehicle, 48, props.modLivery, false) SetVehicleLivery(vehicle, props.modLivery) end + if props.modKit49 then + SetVehicleMod(vehicle, 49, props.modKit49, false) + end + if props.liveryRoof then + SetVehicleRoofLivery(vehicle, props.liveryRoof) + end end end function QBCore.Functions.LoadParticleDictionary(dictionary) - if not HasNamedPtfxAssetLoaded(dictionary) then - RequestNamedPtfxAsset(dictionary) - while not HasNamedPtfxAssetLoaded(dictionary) do - Wait(0) - end + if HasNamedPtfxAssetLoaded(dictionary) then return end + RequestNamedPtfxAsset(dictionary) + while not HasNamedPtfxAssetLoaded(dictionary) do + Wait(0) end end -function QBCore.Functions.StartParticleAtCoord(Dict, ptName, looped, coords, rot, scale, alpha, color, duration) - QBCore.Functions.LoadParticleDictionary(Dict) - UseParticleFxAssetNextCall(Dict) - SetPtfxAssetNextCall(Dict) +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 + else + coords = GetEntityCoords(PlayerPedId()) + end + QBCore.Functions.LoadParticleDictionary(dict) + UseParticleFxAssetNextCall(dict) + 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) @@ -780,13 +900,12 @@ function QBCore.Functions.StartParticleAtCoord(Dict, ptName, looped, coords, rot end StartParticleFxNonLoopedAtCoord(ptName, coords.x, coords.y, coords.z, rot.x, rot.y, rot.z, scale or 1.0) end - return particleHandle end -function QBCore.Functions.StartParticleOnEntity(Dict, ptName, looped, entity, bone, offset, rot, scale, alpha, color, evolution, duration) - QBCore.Functions.LoadParticleDictionary(Dict) - UseParticleFxAssetNextCall(Dict) +function QBCore.Functions.StartParticleOnEntity(dict, ptName, looped, entity, bone, offset, rot, scale, alpha, color, evolution, duration) + QBCore.Functions.LoadParticleDictionary(dict) + UseParticleFxAssetNextCall(dict) local particleHandle, boneID if bone and GetEntityType(entity) == 1 then boneID = GetPedBoneIndex(entity, bone) @@ -821,6 +940,5 @@ function QBCore.Functions.StartParticleOnEntity(Dict, ptName, looped, entity, bo StartParticleFxNonLoopedOnEntity(ptName, entity, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, scale) end end - return particleHandle end diff --git a/client/loops.lua b/client/loops.lua index ad79218..62b0f5d 100644 --- a/client/loops.lua +++ b/client/loops.lua @@ -1,23 +1,24 @@ CreateThread(function() while true do - Wait(0) + local sleep = 0 if LocalPlayer.state.isLoggedIn then - Wait((1000 * 60) * QBCore.Config.UpdateInterval) + sleep = (1000 * 60) * QBCore.Config.UpdateInterval TriggerServerEvent('QBCore:UpdatePlayer') end + Wait(sleep) end end) CreateThread(function() while true do - Wait(QBCore.Config.StatusInterval) if LocalPlayer.state.isLoggedIn then - if QBCore.Functions.GetPlayerData().metadata['hunger'] <= 0 or - QBCore.Functions.GetPlayerData().metadata['thirst'] <= 0 then + if (QBCore.PlayerData.metadata['hunger'] <= 0 or QBCore.PlayerData.metadata['thirst'] <= 0) and not QBCore.PlayerData.metadata['isdead'] then local ped = PlayerPedId() local currentHealth = GetEntityHealth(ped) - SetEntityHealth(ped, currentHealth - math.random(5, 10)) + local decreaseTreshold = math.random(5, 10) + SetEntityHealth(ped, currentHealth - decreaseTreshold) end end + Wait(QBCore.Config.StatusInterval) end end) diff --git a/config.lua b/config.lua index 35a025a..43de9d5 100644 --- a/config.lua +++ b/config.lua @@ -3,11 +3,11 @@ QBConfig = {} QBConfig.MaxPlayers = GetConvarInt('sv_maxclients', 48) -- Gets max players from config file, default 48 QBConfig.DefaultSpawn = vector4(-1035.71, -2731.87, 12.86, 0.0) QBConfig.UpdateInterval = 5 -- how often to update player data in minutes -QBConfig.StatusInterval = 5000 -- how often to check hunger/thirst status in ms +QBConfig.StatusInterval = 5000 -- how often to check hunger/thirst status in milliseconds QBConfig.Money = {} -QBConfig.Money.MoneyTypes = { ['cash'] = 500, ['bank'] = 5000, ['crypto'] = 0 } -- ['type']=startamount - Add or remove money types for your server (for ex. ['blackmoney']=0), remember once added it will not be removed from the database! -QBConfig.Money.DontAllowMinus = { 'cash', 'crypto' } -- Money that is not allowed going in minus +QBConfig.Money.MoneyTypes = {['cash'] = 500, ['bank'] = 5000, ['crypto'] = 0} -- ['type'] = startamount - Add or remove money types for your server (for ex. ['blackmoney'] = 0), remember once added it will not be removed from the database! +QBConfig.Money.DontAllowMinus = {'cash', 'crypto'} -- Money that is not allowed going in minus QBConfig.Money.PayCheckTimeOut = 10 -- The time in minutes that it will give the paycheck QBConfig.Money.PayCheckSociety = false -- If true paycheck will come from the society account that the player is employed at, requires qb-bossmenu @@ -17,25 +17,20 @@ QBConfig.Player.MaxInvSlots = 41 -- Max inventory slots for a player QBConfig.Player.HungerRate = 4.2 -- Rate at which hunger goes down. QBConfig.Player.ThirstRate = 3.8 -- Rate at which thirst goes down. QBConfig.Player.Bloodtypes = { - "A+", - "A-", - "B+", - "B-", - "AB+", - "AB-", - "O+", - "O-", + "A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-", } QBConfig.Server = {} -- General server config -QBConfig.Server.closed = false -- Set server closed (no one can join except people with ace permission 'qbadmin.join') -QBConfig.Server.closedReason = "Server Closed" -- Reason message to display when people can't join the server -QBConfig.Server.uptime = 0 -- Time the server has been up. -QBConfig.Server.whitelist = false -- Enable or disable whitelist on the server -QBConfig.Server.pvp = true -- Enable or disable pvp on the server (Ability to shoot other players) -QBConfig.Server.discord = "" -- Discord invite link -QBConfig.Server.checkDuplicateLicense = true -- check for duplicate rockstar license on join -QBConfig.Server.PermissionList = {} -- permission list +QBConfig.Server.UseConnectQueue = true -- Use connectqueue as a queue for your server +QBConfig.Server.Closed = false -- Set server closed (no one can join except people with ace permission 'qbadmin.join') +QBConfig.Server.ClosedReason = "Server Closed" -- Reason message to display when people can't join the server +QBConfig.Server.Uptime = 0 -- Time the server has been up. +QBConfig.Server.Whitelist = false -- Enable or disable whitelist on the server +QBConfig.Server.WhitelistPermission = 'admin' -- Permission that's able to enter the server when the whitelist is on +QBConfig.Server.PVP = true -- Enable or disable pvp on the server (Ability to shoot other players) +QBConfig.Server.Discord = "" -- Discord invite link +QBConfig.Server.CheckDuplicateLicense = true -- Check for duplicate rockstar license on join +QBConfig.Server.Permissions = {'god', 'admin', 'mod'} -- Add as many groups as you want here after creating them in your server.cfg QBConfig.Notify = {} diff --git a/fxmanifest.lua b/fxmanifest.lua index f654d19..21a8fde 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -2,12 +2,12 @@ fx_version 'cerulean' game 'gta5' description 'QB-Core' -version '1.0.0' +version '1.1.0' shared_scripts { - 'shared/locale.lua', - 'locale/en.lua', -- replace with desired language 'config.lua', + 'shared/locale.lua', + 'locale/en.lua', -- replace with desired language 'shared/main.lua', 'shared/items.lua', 'shared/jobs.lua', @@ -20,7 +20,8 @@ client_scripts { 'client/main.lua', 'client/functions.lua', 'client/loops.lua', - 'client/events.lua' + 'client/events.lua', + 'client/drawtext.lua' } server_scripts { @@ -30,6 +31,7 @@ server_scripts { 'server/player.lua', 'server/events.lua', 'server/commands.lua', + 'server/exports.lua', 'server/debug.lua' } @@ -37,14 +39,11 @@ ui_page 'html/index.html' files { 'html/index.html', - 'html/style.css', - 'html/*.js' + 'html/css/style.css', + 'html/css/drawtext.css', + 'html/js/*.js' } -dependencies { - 'oxmysql', - 'progressbar', - 'connectqueue' -} +dependency 'oxmysql' -lua54 'yes' \ No newline at end of file +lua54 'yes' diff --git a/html/css/drawtext.css b/html/css/drawtext.css new file mode 100644 index 0000000..489e967 --- /dev/null +++ b/html/css/drawtext.css @@ -0,0 +1,76 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;500&display=swap"); +:root { + --primary-bg: rgba(23, 23, 23, 90%); + --active-bg: #dc143c; + --font-color: white; +} + +#drawtext-container { + display: none; + width: 100%; + height: 100%; + overflow: hidden; + padding: 0; + margin: 0; + font-family: "Poppins", sans-serif !important; + font-weight: 300; +} +.text { + position: absolute; + background: var(--primary-bg); + color: var(--font-color); + margin-top: 0.5rem; + padding: 0.45rem; + border-radius: 0.15rem; + box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; +} + +.text.pressed { + background: var(--active-bg); +} + +.top { + left: 45vw; + top: -100px; +} +.top.show { + transition: 0.5s; + top: 10px; + opacity: 1; +} +.top.hide { + transition: 0.5s; + top: -100px; + opacity: 0; +} + +.right { + top: 50%; + right: -100px; +} +.right.show { + transition: 0.5s; + right: 10px; + opacity: 1; +} +.right.hide { + transition: 0.5s; + right: -100px; + opacity: 0; +} + +.left { + top: 50%; + left: -100px; +} + +.left.show { + transition: 0.5s; + left: 10px; + opacity: 1; +} +.left.hide { + transition: 0.5s; + left: -100px; + opacity: 0; +} diff --git a/html/style.css b/html/css/style.css similarity index 94% rename from html/style.css rename to html/css/style.css index 80b86dc..4581314 100644 --- a/html/style.css +++ b/html/css/style.css @@ -1,3 +1,7 @@ +html::-webkit-scrollbar { + display: none; +} + .success { background-color: rgba(23, 23, 23, 90%); border-radius: 10px; @@ -31,4 +35,4 @@ border-radius: 10px; box-shadow: 0rem 0rem 0.1rem 0.05rem #000000; border-left: 5px solid #f44236; -} \ No newline at end of file +} diff --git a/html/index.html b/html/index.html index 5abf792..a229080 100644 --- a/html/index.html +++ b/html/index.html @@ -19,7 +19,7 @@ type="text/css" /> - + + +
+
+
+
- \ No newline at end of file + diff --git a/html/app.js b/html/js/app.js similarity index 100% rename from html/app.js rename to html/js/app.js diff --git a/html/config.js b/html/js/config.js similarity index 100% rename from html/config.js rename to html/js/config.js diff --git a/html/js/drawtext.js b/html/js/drawtext.js new file mode 100644 index 0000000..282d522 --- /dev/null +++ b/html/js/drawtext.js @@ -0,0 +1,124 @@ +let direction = null; + +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; + } + + text.innerHTML = textData.text; + document.getElementById("drawtext-container").style.display = "block"; + await sleep(100); + addClass(text, "show"); +}; + +const changeText = async (textData) => { + const text = document.getElementById("text"); + let {position} = textData; + + 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"); + + 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"); +}; + +const hideText = async () => { + 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); +}; + +const keyPressed = () => { + 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 sleep = (ms) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +const removeClass = (element, name) => { + if (element.classList.contains(name)) { + element.classList.remove(name); + } +}; + +const addClass = (element, name) => { + if (!element.classList.contains(name)) { + element.classList.add(name); + } +}; diff --git a/html/testing.js b/html/js/testing.js similarity index 100% rename from html/testing.js rename to html/js/testing.js diff --git a/html/utils.js b/html/js/utils.js similarity index 100% rename from html/utils.js rename to html/js/utils.js diff --git a/locale/ar.lua b/locale/ar.lua index 75e01f8..11b32af 100644 --- a/locale/ar.lua +++ b/locale/ar.lua @@ -1,21 +1,21 @@ local Translations = { error = { - not_online = 'اللاعب غير متواجد', + not_online = 'اللاعب غير متصل', wrong_format = 'التنسيق غير صحيح', - missing_args = 'لم يتم ادخل جميع الحقول تاكد من وضع (x, y, z)', - missing_args2 = 'يجب ملأ جميع الحقول اللازمة!', + missing_args = '(x, y, z) لم يتم ادخل جميع المعلومات', + missing_args2 = 'يجب ملأ جميع الحقول اللازمة', no_access = 'لا يمكن الوصول إلى هذا الأمر', - company_too_poor = 'رئيس العمل الخاص بك لا يملك اموال كافية', - item_not_exist = 'لا يوجد هذا العنصر', - too_heavy = 'المحفظة ممتلئة' + company_too_poor = 'مسؤول الوظيفة لا يمكلك مال كاف-ي', + item_not_exist = 'عنصر غير موجود', + too_heavy = 'لا يوجد مساحة في جقيبتك' }, success = {}, info = { - received_paycheck = 'لقد استملت راتبك الشهري $%{value}', - job_info = 'الوظيفة: %{value} | الرتبة: %{value2} | الخدمة: %{value3}', - gang_info = 'العصابة: %{value} | الرتبة: %{value2}', - on_duty = 'انت الان خارج الخدمة!', - off_duty = 'انت الان في الخدمة!' + received_paycheck = '$%{value} لقد استملت راتبك الشهري', + job_info = '%{value} | %{value2} | %{value3}', + gang_info = '%{value} | %{value2}', + on_duty = 'انت الان خارج الخدمة', + off_duty = 'انت الان في الخدمة' } } diff --git a/locale/es.lua b/locale/es.lua index 0133ea0..7e3ffdb 100644 --- a/locale/es.lua +++ b/locale/es.lua @@ -4,8 +4,6 @@ local Translations = { wrong_format = 'Formato incorrecto!', missing_args = 'No todos los argumentos estan presente! (x, y, z)', missing_args2 = 'Todos los argumentos tienen que estar presente!', - missing_args = 'No todos los argumentos estan presentes! (x, y, z)', - missing_args2 = 'Todos los argumentos deben estar presentes!', no_access = 'No tienes acceso a este comando!', company_too_poor = 'Tu empresa está en bancarrota. No hay dinero suficiente.', item_not_exist = 'El objeto no existe en el sistema.', @@ -24,4 +22,4 @@ local Translations = { Lang = Locale:new({ phrases = Translations, warnOnMissing = true -}) +}) \ No newline at end of file diff --git a/locale/he.lua b/locale/he.lua new file mode 100644 index 0000000..5a983b2 --- /dev/null +++ b/locale/he.lua @@ -0,0 +1,25 @@ +local Translations = { + error = { + not_online = 'שחקן לא מחובר', + wrong_format = 'פורמט שגוי', + missing_args = 'יש לכתוב את הפקודה לפי מה שכתוב (x, y, z)', + missing_args2 = 'יש למלא את כל המידע המבוקש', + no_access = 'אין לך גישה לפקודה זו', + company_too_poor = 'העסק שלך שבור', + item_not_exist = 'אייטם לא נמצא', + too_heavy = 'אינבנטורי מלא' + }, + success = {}, + info = { + received_paycheck = '$%{value} קיבלת משבורת על סך', + job_info = '%{value3} :בתפקיד | %{value2} :דרגה | %{value} :עבודה', + gang_info = '%{value2} :דרגה | %{value} :גאנג', + on_duty = 'אתה בתפקיד כעת', + off_duty = 'ירדת מתפקיד כעת' + } +} + +Lang = Locale:new({ + phrases = Translations, + warnOnMissing = true +}) diff --git a/locale/pt.lua b/locale/pt.lua index 0abca34..0fc86a7 100644 --- a/locale/pt.lua +++ b/locale/pt.lua @@ -13,7 +13,7 @@ local Translations = { info = { received_paycheck = 'Recebeste o pagamento de %{value}€', job_info = 'Emprego: %{value} | Grau: %{value2} | Serviço: %{value3}', - gang_info = 'Gang: %{value} | Grade: %{value2}', + gang_info = 'Gang: %{value} | Grau: %{value2}', on_duty = 'Agora estás de serviço!', off_duty = 'Agora estás fora de serviço!' } diff --git a/server/commands.lua b/server/commands.lua index d094d0b..457277d 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -1,17 +1,29 @@ QBCore.Commands = {} QBCore.Commands.List = {} +QBCore.Commands.IgnoreList = { -- Ignore old perm levels while keeping backwards compatibility + ['god'] = true, -- We don't need to create an ace because god is allowed all commands + ['user'] = true -- We don't need to create an ace because builtin.everyone +} + +CreateThread(function() -- Add ace to node for perm checking + for k,v in pairs(QBConfig.Server.Permissions) do + ExecuteCommand(('add_ace qbcore.%s %s allow'):format(v, v)) + end +end) -- Register & Refresh Commands function QBCore.Commands.Add(name, help, arguments, argsrequired, callback, permission) - if type(permission) == 'string' then - permission = permission:lower() - else - permission = 'user' + local restricted = true -- Default to restricted for all commands + if not permission then permission = 'user' end -- some commands don't pass permission level + if permission == 'user' then restricted = false end -- allow all users to use command + RegisterCommand(name, callback, restricted) -- Register command within fivem + if not QBCore.Commands.IgnoreList[permission] then -- only create aces for extra perm levels + ExecuteCommand(('add_ace qbcore.%s command.%s allow'):format(permission, name)) end QBCore.Commands.List[name:lower()] = { name = name:lower(), - permission = permission, + permission = tostring(permission:lower()), help = help, arguments = arguments, argsrequired = argsrequired, @@ -25,195 +37,214 @@ function QBCore.Commands.Refresh(source) local suggestions = {} if Player then for command, info in pairs(QBCore.Commands.List) do - local isGod = QBCore.Functions.HasPermission(src, 'god') - local hasPerm = QBCore.Functions.HasPermission(src, QBCore.Commands.List[command].permission) - local isPrincipal = IsPlayerAceAllowed(src, 'command') - if isGod or hasPerm or isPrincipal then + local hasPerm = IsPlayerAceAllowed(tostring(src), 'command.'..command) + if hasPerm then suggestions[#suggestions + 1] = { name = '/' .. command, help = info.help, params = info.arguments } + else + TriggerClientEvent('chat:removeSuggestion', src, '/'..command) end end - TriggerClientEvent('chat:addSuggestions', tonumber(source), suggestions) + TriggerClientEvent('chat:addSuggestions', src, suggestions) end end -- Teleport QBCore.Commands.Add('tp', 'TP To Player or Coords (Admin Only)', { { name = 'id/x', help = 'ID of player or X position' }, { name = 'y', help = 'Y position' }, { name = 'z', help = 'Z position' } }, false, function(source, args) - local src = source if args[1] and not args[2] and not args[3] then local target = GetPlayerPed(tonumber(args[1])) if target ~= 0 then local coords = GetEntityCoords(target) - TriggerClientEvent('QBCore:Command:TeleportToPlayer', src, coords) + TriggerClientEvent('QBCore:Command:TeleportToPlayer', source, coords) else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_online'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') end else if args[1] and args[2] and args[3] then local x = tonumber(args[1]) local y = tonumber(args[2]) local z = tonumber(args[3]) - if (x ~= 0) and (y ~= 0) and (z ~= 0) then - TriggerClientEvent('QBCore:Command:TeleportToCoords', src, x, y, z) + if x ~= 0 and y ~= 0 and z ~= 0 then + TriggerClientEvent('QBCore:Command:TeleportToCoords', source, x, y, z) else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.wrong_format'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.wrong_format'), 'error') end else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.missing_args'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.missing_args'), 'error') end end end, 'admin') QBCore.Commands.Add('tpm', 'TP To Marker (Admin Only)', {}, false, function(source) - local src = source - TriggerClientEvent('QBCore:Command:GoToMarker', src) + TriggerClientEvent('QBCore:Command:GoToMarker', source) end, 'admin') - -QBCore.Commands.Add('togglepvp', 'Toggle PVP on the server (Admin Only)', {}, false, function(source) - local src = source - local pvp_state = QBConfig.Server.pvp - QBConfig.Server.pvp = not pvp_state - TriggerClientEvent('QBCore:Client:PvpHasToggled', -1, QBConfig.Server.pvp) +QBCore.Commands.Add('togglepvp', 'Toggle PVP on the server (Admin Only)', {}, false, function() + QBConfig.Server.PVP = not QBConfig.Server.PVP + TriggerClientEvent('QBCore:Client:PvpHasToggled', -1, QBConfig.Server.PVP) end, 'admin') + -- Permissions QBCore.Commands.Add('addpermission', 'Give Player Permissions (God Only)', { { name = 'id', help = 'ID of player' }, { name = 'permission', help = 'Permission level' } }, true, function(source, args) - local src = source local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) local permission = tostring(args[2]):lower() if Player then QBCore.Functions.AddPermission(Player.PlayerData.source, permission) else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_online'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') end end, 'god') -QBCore.Commands.Add('removepermission', 'Remove Players Permissions (God Only)', { { name = 'id', help = 'ID of player' } }, true, function(source, args) - local src = source +QBCore.Commands.Add('removepermission', 'Remove Players Permissions (God Only)', { { name = 'id', help = 'ID of player' }, { name = 'permission', help = 'Permission level' } }, true, function(source, args) local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) + local permission = tostring(args[2]):lower() if Player then - QBCore.Functions.RemovePermission(Player.PlayerData.source) + QBCore.Functions.RemovePermission(Player.PlayerData.source, permission) else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_online'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') end end, 'god') +-- Open & Close Server + +QBCore.Commands.Add('openserver', 'Open the server for everyone (Admin Only)', {}, false, function(source) + if not QBCore.Config.Server.Closed then + TriggerClientEvent('QBCore:Notify', source, 'The server is already open', 'error') + return + end + if QBCore.Functions.HasPermission(source, 'admin') then + QBCore.Config.Server.Closed = false + else + QBCore.Functions.Kick(source, 'You don\'t have permissions for this..', nil, nil) + end +end, 'admin') + +QBCore.Commands.Add('closeserver', 'Close the server for people without permissions (Admin Only)', { { name = 'reason', help = 'Reason for closing it (optional)' } }, false, function(source, args) + if QBCore.Config.Server.Closed then + TriggerClientEvent('QBCore:Notify', source, 'The server is already closed', 'error') + return + end + if QBCore.Functions.HasPermission(source, 'admin') then + local reason = args[1] or 'No reason specified' + QBCore.Config.Server.Closed = true + 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) + end + end + else + QBCore.Functions.Kick(source, 'You don\'t have permissions for this..', nil, nil) + end +end, 'admin') + -- Vehicle QBCore.Commands.Add('car', 'Spawn Vehicle (Admin Only)', { { name = 'model', help = 'Model name of the vehicle' } }, true, function(source, args) - local src = source - TriggerClientEvent('QBCore:Command:SpawnVehicle', src, args[1]) + TriggerClientEvent('QBCore:Command:SpawnVehicle', source, args[1]) end, 'admin') QBCore.Commands.Add('dv', 'Delete Vehicle (Admin Only)', {}, false, function(source) - local src = source - TriggerClientEvent('QBCore:Command:DeleteVehicle', src) + TriggerClientEvent('QBCore:Command:DeleteVehicle', source) end, 'admin') -- Money QBCore.Commands.Add('givemoney', 'Give A Player Money (Admin Only)', { { name = 'id', help = 'Player ID' }, { name = 'moneytype', help = 'Type of money (cash, bank, crypto)' }, { name = 'amount', help = 'Amount of money' } }, true, function(source, args) - local src = source local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) if Player then Player.Functions.AddMoney(tostring(args[2]), tonumber(args[3])) else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_online'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') end end, 'admin') QBCore.Commands.Add('setmoney', 'Set Players Money Amount (Admin Only)', { { name = 'id', help = 'Player ID' }, { name = 'moneytype', help = 'Type of money (cash, bank, crypto)' }, { name = 'amount', help = 'Amount of money' } }, true, function(source, args) - local src = source local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) if Player then Player.Functions.SetMoney(tostring(args[2]), tonumber(args[3])) else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_online'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') end end, 'admin') -- Job QBCore.Commands.Add('job', 'Check Your Job', {}, false, function(source) - local src = source - local PlayerJob = QBCore.Functions.GetPlayer(src).PlayerData.job - TriggerClientEvent('QBCore:Notify', src, Lang:t('info.job_info', {value = PlayerJob.label, value2 = PlayerJob.grade.name, value3 = PlayerJob.onduty})) + local PlayerJob = QBCore.Functions.GetPlayer(source).PlayerData.job + TriggerClientEvent('QBCore:Notify', source, Lang:t('info.job_info', {value = PlayerJob.label, value2 = PlayerJob.grade.name, value3 = PlayerJob.onduty})) end, 'user') QBCore.Commands.Add('setjob', 'Set A Players Job (Admin Only)', { { name = 'id', help = 'Player ID' }, { name = 'job', help = 'Job name' }, { name = 'grade', help = 'Grade' } }, true, function(source, args) - local src = source local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) if Player then Player.Functions.SetJob(tostring(args[2]), tonumber(args[3])) else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_online'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') end end, 'admin') -- Gang QBCore.Commands.Add('gang', 'Check Your Gang', {}, false, function(source) - local src = source local PlayerGang = QBCore.Functions.GetPlayer(source).PlayerData.gang - TriggerClientEvent('QBCore:Notify', src, Lang:t('info.gang_info', {value = PlayerGang.label, value2 = PlayerGang.grade.name})) + TriggerClientEvent('QBCore:Notify', source, Lang:t('info.gang_info', {value = PlayerGang.label, value2 = PlayerGang.grade.name})) end, 'user') QBCore.Commands.Add('setgang', 'Set A Players Gang (Admin Only)', { { name = 'id', help = 'Player ID' }, { name = 'gang', help = 'Name of a gang' }, { name = 'grade', help = 'Grade' } }, true, function(source, args) - local src = source local Player = QBCore.Functions.GetPlayer(tonumber(args[1])) if Player then Player.Functions.SetGang(tostring(args[2]), tonumber(args[3])) else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_online'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') end end, 'admin') -- Inventory (should be in qb-inventory?) QBCore.Commands.Add('clearinv', 'Clear Players Inventory (Admin Only)', { { name = 'id', help = 'Player ID' } }, false, function(source, args) - local src = source - local playerId = args[1] or src + local playerId = args[1] and args[1] ~= '' or source local Player = QBCore.Functions.GetPlayer(tonumber(playerId)) if Player then Player.Functions.ClearInventory() else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_online'), 'error') + TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_online'), 'error') end end, 'admin') -- Out of Character Chat QBCore.Commands.Add('ooc', 'OOC Chat Message', {}, false, function(source, args) - local src = source local message = table.concat(args, ' ') local Players = QBCore.Functions.GetPlayers() - local Player = QBCore.Functions.GetPlayer(src) + local Player = QBCore.Functions.GetPlayer(source) + local playerCoords = GetEntityCoords(GetPlayerPed(source)) for k, v in pairs(Players) do - if v == src then + if v == source then TriggerClientEvent('chat:addMessage', v, { color = { 0, 0, 255}, multiline = true, - args = {'OOC | '.. GetPlayerName(src), message} + args = {'OOC | '.. GetPlayerName(source), message} }) - elseif #(GetEntityCoords(GetPlayerPed(src)) - GetEntityCoords(GetPlayerPed(v))) < 20.0 then + elseif #(playerCoords - GetEntityCoords(GetPlayerPed(v))) < 20.0 then TriggerClientEvent('chat:addMessage', v, { color = { 0, 0, 255}, multiline = true, - args = {'OOC | '.. GetPlayerName(src), message} + args = {'OOC | '.. GetPlayerName(source), message} }) elseif QBCore.Functions.HasPermission(v, 'admin') then if QBCore.Functions.IsOptin(v) then TriggerClientEvent('chat:addMessage', v, { color = { 0, 0, 255}, multiline = true, - args = {'Proxmity OOC | '.. GetPlayerName(src), message} + args = {'Proxmity OOC | '.. GetPlayerName(source), message} }) - TriggerEvent('qb-log:server:CreateLog', 'ooc', 'OOC', 'white', '**' .. GetPlayerName(src) .. '** (CitizenID: ' .. Player.PlayerData.citizenid .. ' | ID: ' .. src .. ') **Message:** ' .. message, false) + TriggerEvent('qb-log:server:CreateLog', 'ooc', 'OOC', 'white', '**' .. GetPlayerName(source) .. '** (CitizenID: ' .. Player.PlayerData.citizenid .. ' | ID: ' .. source .. ') **Message:** ' .. message, false) end end end @@ -222,16 +253,16 @@ end, 'user') -- Me command QBCore.Commands.Add('me', 'Show local message', {{name = 'message', help = 'Message to respond with'}}, false, function(source, args) - local src = source - local ped = GetPlayerPed(src) + local ped = GetPlayerPed(source) local pCoords = GetEntityCoords(ped) local msg = table.concat(args, ' ') if msg == '' then return end + if string.match(msg, "<") then TriggerClientEvent('QBCore:Notify', source, Lang:t('error.wrong_format'), 'error') return end for k,v in pairs(QBCore.Functions.GetPlayers()) do local target = GetPlayerPed(v) local tCoords = GetEntityCoords(target) if #(pCoords - tCoords) < 20 then - TriggerClientEvent('QBCore:Command:ShowMe3D', v, src, msg) + TriggerClientEvent('QBCore:Command:ShowMe3D', v, source, msg) end end end, 'user') diff --git a/server/debug.lua b/server/debug.lua index bcdd919..7a8a9cf 100644 --- a/server/debug.lua +++ b/server/debug.lua @@ -1,38 +1,39 @@ local function tPrint(tbl, indent) indent = indent or 0 - for k, v in pairs(tbl) do - local tblType = type(v) - local formatting = ("%s ^3%s:^0"):format(string.rep(" ", indent), k) + if type(tbl) == 'table' then + for k, v in pairs(tbl) do + local tblType = type(v) + local formatting = ("%s ^3%s:^0"):format(string.rep(" ", indent), k) - if tblType == "table" then - print(formatting) - tPrint(v, indent + 1) - elseif tblType == 'boolean' then - print(("%s^1 %s ^0"):format(formatting, v)) - elseif tblType == "function" then - print(("%s^9 %s ^0"):format(formatting, v)) - elseif tblType == 'number' then - print(("%s^5 %s ^0"):format(formatting, v)) - elseif tblType == 'string' then - print(("%s ^2'%s' ^0"):format(formatting, v)) - else - print(("%s^2 %s ^0"):format(formatting, v)) + if tblType == "table" then + print(formatting) + tPrint(v, indent + 1) + elseif tblType == 'boolean' then + print(("%s^1 %s ^0"):format(formatting, v)) + elseif tblType == "function" then + print(("%s^9 %s ^0"):format(formatting, v)) + elseif tblType == 'number' then + print(("%s^5 %s ^0"):format(formatting, v)) + elseif tblType == 'string' then + print(("%s ^2'%s' ^0"):format(formatting, v)) + else + print(("%s^2 %s ^0"):format(formatting, v)) + end end + else + print(("%s ^0%s"):format(string.rep(" ", indent), tbl)) end end -RegisterServerEvent('QBCore:DebugSomething', function(table, indent) +RegisterServerEvent('QBCore:DebugSomething', function(tbl, indent) local resource = GetInvokingResource() or "qb-core" - print(('\x1b[4m\x1b[36m[ %s : DEBUG]\x1b[0m'):format(resource)) - - tPrint(table, indent) - + tPrint(tbl, indent) print('\x1b[4m\x1b[36m[ END DEBUG ]\x1b[0m') end) -function QBCore.Debug(table, indent) - TriggerEvent('QBCore:DebugSomething', table, indent) +function QBCore.Debug(tbl, indent) + TriggerEvent('QBCore:DebugSomething', tbl, indent) end function QBCore.ShowError(resource, msg) @@ -41,4 +42,4 @@ end function QBCore.ShowSuccess(resource, msg) print('\x1b[32m['..resource..':LOG]\x1b[0m '..msg) -end +end \ No newline at end of file diff --git a/server/events.lua b/server/events.lua index d543ca3..745a4fa 100644 --- a/server/events.lua +++ b/server/events.lua @@ -1,54 +1,39 @@ -- Event Handler -AddEventHandler('playerDropped', function() - local src = source - if QBCore.Players[src] then - local Player = QBCore.Players[src] - TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Dropped', 'red', '**' .. GetPlayerName(src) .. '** (' .. Player.PlayerData.license .. ') left..') - Player.Functions.Save() - _G.Player_Buckets[Player.PlayerData.license] = nil - QBCore.Players[src] = nil +AddEventHandler('chatMessage', function(source, _, message) + if string.sub(message, 1, 1) == '/' then + CancelEvent() + return end end) -AddEventHandler('chatMessage', function(source, n, message) +AddEventHandler('playerDropped', function() local src = source - if string.sub(message, 1, 1) == '/' then - local args = QBCore.Shared.SplitStr(message, ' ') - local command = string.gsub(args[1]:lower(), '/', '') - CancelEvent() - if QBCore.Commands.List[command] then - local Player = QBCore.Functions.GetPlayer(src) - if Player then - local isGod = QBCore.Functions.HasPermission(src, 'god') - local hasPerm = QBCore.Functions.HasPermission(src, QBCore.Commands.List[command].permission) - local isPrincipal = IsPlayerAceAllowed(src, 'command') - table.remove(args, 1) - if isGod or hasPerm or isPrincipal then - if (QBCore.Commands.List[command].argsrequired and #QBCore.Commands.List[command].arguments ~= 0 and args[#QBCore.Commands.List[command].arguments] == nil) then - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.missing_args2'), 'error') - else - QBCore.Commands.List[command].callback(src, args) - end - else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.no_access'), 'error') - end - end - end - end + if not QBCore.Players[src] then return end + local Player = QBCore.Players[src] + TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Dropped', 'red', '**' .. GetPlayerName(src) .. '** (' .. Player.PlayerData.license .. ') left..') + Player.Functions.Save() + QBCore.Player_Buckets[Player.PlayerData.license] = nil + QBCore.Players[src] = nil end) -- Player Connecting -local function OnPlayerConnecting(name, setKickReason, deferrals) - local player = source +local function onPlayerConnecting(name, setKickReason, deferrals) + local src = source local license - local identifiers = GetPlayerIdentifiers(player) + local identifiers = GetPlayerIdentifiers(src) deferrals.defer() - -- mandatory wait! + -- Mandatory wait Wait(0) + if QBCore.Config.Server.Closed then + if not IsPlayerAceAllowed(src, 'qbadmin.join') then + deferrals.done(QBCore.Config.Server.ClosedReason) + end + end + deferrals.update(string.format('Hello %s. Validating Your Rockstar License', name)) for _, v in pairs(identifiers) do @@ -58,13 +43,14 @@ local function OnPlayerConnecting(name, setKickReason, deferrals) end end - -- mandatory wait! + -- Mandatory wait Wait(2500) - deferrals.update(string.format('Hello %s. We are checking if you are banned.', name)) + deferrals.update(string.format('Hello %s. We are checking your allowance.', name)) - local isBanned, Reason = QBCore.Functions.IsPlayerBanned(player) + local isBanned, Reason = QBCore.Functions.IsPlayerBanned(src) local isLicenseAlreadyInUse = QBCore.Functions.IsLicenseInUse(license) + local isWhitelist, whitelisted = QBCore.Config.Server.Whitelist, QBCore.Functions.IsWhitelisted(src) Wait(2500) @@ -74,35 +60,44 @@ local function OnPlayerConnecting(name, setKickReason, deferrals) deferrals.done('No Valid Rockstar License Found') elseif isBanned then deferrals.done(Reason) - elseif isLicenseAlreadyInUse and QBCore.Config.Server.checkDuplicateLicense then + elseif isLicenseAlreadyInUse and QBCore.Config.Server.CheckDuplicateLicense then deferrals.done('Duplicate Rockstar License Found') + elseif isWhitelist and not whitelisted then + deferrals.done('You\'re not whitelisted for this server') else deferrals.done() - Wait(1000) - TriggerEvent('connectqueue:playerConnect', name, setKickReason, deferrals) + if QBCore.Config.Server.UseConnectQueue then + Wait(1000) + TriggerEvent('connectqueue:playerConnect', name, setKickReason, deferrals) + end end - --Add any additional defferals you may need! + -- Add any additional defferals you may need! end -AddEventHandler('playerConnecting', OnPlayerConnecting) +AddEventHandler('playerConnecting', onPlayerConnecting) -- Open & Close Server (prevents players from joining) -RegisterNetEvent('QBCore:server:CloseServer', function(reason) +RegisterNetEvent('QBCore:Server:CloseServer', function(reason) local src = source - if QBCore.Functions.HasPermission(src, 'admin') or QBCore.Functions.HasPermission(src, 'god') then - local reason = reason or 'No reason specified' - QBCore.Config.Server.closed = true - QBCore.Config.Server.closedReason = reason + if QBCore.Functions.HasPermission(src, 'admin') then + reason = reason or 'No reason specified' + QBCore.Config.Server.Closed = true + 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) + end + end else QBCore.Functions.Kick(src, 'You don\'t have permissions for this..', nil, nil) end end) -RegisterNetEvent('QBCore:server:OpenServer', function() +RegisterNetEvent('QBCore:Server:OpenServer', function() local src = source - if QBCore.Functions.HasPermission(src, 'admin') or QBCore.Functions.HasPermission(src, 'god') then - QBCore.Config.Server.closed = false + if QBCore.Functions.HasPermission(src, 'admin') then + QBCore.Config.Server.Closed = false else QBCore.Functions.Kick(src, 'You don\'t have permissions for this..', nil, nil) end @@ -122,20 +117,19 @@ end) RegisterNetEvent('QBCore:UpdatePlayer', function() local src = source local Player = QBCore.Functions.GetPlayer(src) - if Player then - local newHunger = Player.PlayerData.metadata['hunger'] - QBCore.Config.Player.HungerRate - local newThirst = Player.PlayerData.metadata['thirst'] - QBCore.Config.Player.ThirstRate - if newHunger <= 0 then - newHunger = 0 - end - if newThirst <= 0 then - newThirst = 0 - end - Player.Functions.SetMetaData('thirst', newThirst) - Player.Functions.SetMetaData('hunger', newHunger) - TriggerClientEvent('hud:client:UpdateNeeds', src, newHunger, newThirst) - Player.Functions.Save() + if not Player then return end + local newHunger = Player.PlayerData.metadata['hunger'] - QBCore.Config.Player.HungerRate + local newThirst = Player.PlayerData.metadata['thirst'] - QBCore.Config.Player.ThirstRate + if newHunger <= 0 then + newHunger = 0 end + if newThirst <= 0 then + newThirst = 0 + end + Player.Functions.SetMetaData('thirst', newThirst) + Player.Functions.SetMetaData('hunger', newHunger) + TriggerClientEvent('hud:client:UpdateNeeds', src, newHunger, newThirst) + Player.Functions.Save() end) RegisterNetEvent('QBCore:Server:SetMetaData', function(meta, data) @@ -155,6 +149,7 @@ end) RegisterNetEvent('QBCore:ToggleDuty', function() local src = source local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end if Player.PlayerData.job.onduty then Player.Functions.SetJobDuty(false) TriggerClientEvent('QBCore:Notify', src, Lang:t('info.off_duty')) @@ -169,22 +164,21 @@ end) RegisterNetEvent('QBCore:Server:UseItem', function(item) local src = source - if item and item.amount > 0 then - if QBCore.Functions.CanUseItem(item.name) then - QBCore.Functions.UseItem(src, item) - end - end + if not item or item.amount <= 0 or not QBCore.Functions.CanUseItem(item.name) then return end + QBCore.Functions.UseItem(src, item) end) RegisterNetEvent('QBCore:Server:RemoveItem', function(itemName, amount, slot) local src = source local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end Player.Functions.RemoveItem(itemName, amount, slot) end) RegisterNetEvent('QBCore:Server:AddItem', function(itemName, amount, slot, info) local src = source local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end Player.Functions.AddItem(itemName, amount, slot, info) end) @@ -192,83 +186,72 @@ end) RegisterNetEvent('QBCore:CallCommand', function(command, args) local src = source - if QBCore.Commands.List[command] then - local Player = QBCore.Functions.GetPlayer(src) - if Player then - local isGod = QBCore.Functions.HasPermission(src, 'god') - local hasPerm = QBCore.Functions.HasPermission(src, QBCore.Commands.List[command].permission) - local isPrincipal = IsPlayerAceAllowed(src, 'command') - if (QBCore.Commands.List[command].permission == Player.PlayerData.job.name) or isGod or hasPerm or isPrincipal then - if (QBCore.Commands.List[command].argsrequired and #QBCore.Commands.List[command].arguments ~= 0 and args[#QBCore.Commands.List[command].arguments] == nil) then - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.missing_args2'), 'error') - else - QBCore.Commands.List[command].callback(src, args) - end - else - TriggerClientEvent('QBCore:Notify', src, Lang:t('error.no_access'), 'error') - end + if not QBCore.Commands.List[command] then return end + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end + local hasPerm = QBCore.Functions.HasPermission(src, QBCore.Commands.List[command].permission) + if hasPerm then + if QBCore.Commands.List[command].argsrequired and #QBCore.Commands.List[command].arguments ~= 0 and not args[#QBCore.Commands.List[command].arguments] then + TriggerClientEvent('QBCore:Notify', src, Lang:t('error.missing_args2'), 'error') + else + QBCore.Commands.List[command].callback(src, args) end + else + TriggerClientEvent('QBCore:Notify', src, Lang:t('error.no_access'), 'error') end end) -- Has Item Callback (can also use client function - QBCore.Functions.HasItem(item)) QBCore.Functions.CreateCallback('QBCore:HasItem', function(source, cb, items, amount) - local src = source local retval = false - local Player = QBCore.Functions.GetPlayer(src) - if Player then - if type(items) == 'table' then - local count = 0 - local finalcount = 0 - for k, v in pairs(items) do - if type(k) == 'string' then - finalcount = 0 - for i, _ in pairs(items) do - if i then - finalcount = finalcount + 1 - end - end - local item = Player.Functions.GetItemByName(k) - if item then - if item.amount >= v then - count = count + 1 - if count == finalcount then - retval = true - end - end - end - else - finalcount = #items - local item = Player.Functions.GetItemByName(v) - if item then - if amount then - if item.amount >= amount then - count = count + 1 - if count == finalcount then - retval = true - end - end - else - count = count + 1 - if count == finalcount then - retval = true - end + local Player = QBCore.Functions.GetPlayer(source) + if not Player then return cb(false) end + if type(items) == 'table' then + local count = 0 + local finalcount = 0 + for k, v in pairs(items) do + if type(k) == 'string' then + finalcount = 0 + for i, _ in pairs(items) do finalcount += 1 end + local item = Player.Functions.GetItemByName(k) + if item then + if item.amount >= v then + count += 1 + if count == finalcount then + retval = true end end end + else + finalcount = #items + local item = Player.Functions.GetItemByName(v) + if item then + if amount then + if item.amount >= amount then + count += 1 + if count == finalcount then + retval = true + end + end + else + count += 1 + if count == finalcount then + retval = true + end + end + end + end + end + else + local item = Player.Functions.GetItemByName(items) + if not item then return cb(false) end + if amount then + if item.amount >= amount then + retval = true end else - local item = Player.Functions.GetItemByName(items) - if item then - if amount then - if item.amount >= amount then - retval = true - end - else - retval = true - end - end + retval = true end end cb(retval) diff --git a/server/exports.lua b/server/exports.lua new file mode 100644 index 0000000..2cfed5a --- /dev/null +++ b/server/exports.lua @@ -0,0 +1,154 @@ +-- Single add job function which should only be used if you planning on adding a single job +local function AddJob(jobName, job) + if type(jobName) ~= "string" then + return false, "invalid_job_name" + end + + if QBCore.Shared.Jobs[jobName] then + return false, "job_exists" + end + + QBCore.Shared.Jobs[jobName] = job + TriggerClientEvent('QBCore:Client:OnSharedUpdate', -1,'Jobs', jobName, job) + TriggerEvent('QBCore:Server:UpdateObject') + return true, "success" +end +QBCore.Functions.AddJob = AddJob +exports('AddJob', AddJob) + +-- Multiple Add Jobs +local function AddJobs(jobs) + local shouldContinue = true + local message = "success" + local errorItem = nil + for key, value in pairs(jobs) do + if type(key) ~= "string" then + message = 'invalid_job_name' + shouldContinue = false + errorItem = jobs[key] + break + end + + if QBCore.Shared.Jobs[key] then + message = 'job_exists' + shouldContinue = false + errorItem = jobs[key] + break + end + + QBCore.Shared.Jobs[key] = value + end + + if not shouldContinue then return false, message, errorItem end + TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Jobs', jobs) + TriggerEvent('QBCore:Server:UpdateObject') + return true, message, nil +end +QBCore.Functions.AddJobs = AddJobs +exports('AddJobs', AddJobs) + +-- Single add item +local function AddItem(itemName, item) + if type(itemName) ~= "string" then + return false, "invalid_item_name" + end + + if QBCore.Shared.Items[itemName] then + return false, "item_exists" + end + + QBCore.Shared.Items[itemName] = item + TriggerClientEvent('QBCore:Client:OnSharedUpdate', -1, 'Items', itemName, item) + TriggerEvent('QBCore:Server:UpdateObject') + return true, "success" +end +QBCore.Functions.AddItem = AddItem +exports('AddItem', AddItem) + +-- Multiple Add Items +local function AddItems(items) + local shouldContinue = true + local message = "success" + local errorItem = nil + for key, value in pairs(items) do + if type(key) ~= "string" then + message = "invalid_item_name" + shouldContinue = false + errorItem = items[key] + break + end + + if QBCore.Shared.Items[key] then + message = "item_exists" + shouldContinue = false + errorItem = items[key] + break + end + + QBCore.Shared.Items[key] = value + end + + if not shouldContinue then return false, message, errorItem end + TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Items', items) + TriggerEvent('QBCore:Server:UpdateObject') + return true, message, nil +end +QBCore.Functions.AddItems = AddItems +exports('AddItems', AddItems) + +-- Single Add Gang +local function AddGang(gangName, gang) + if type(gangName) ~= "string" then + return false, "invalid_gang_name" + end + if QBCore.Shared.Gangs[gangName] then + return false, "gang_exists" + end + + QBCore.Shared.Gangs[gangName] = gang + TriggerClientEvent('QBCore:Client:OnSharedUpdate', -1, 'Gangs', gangName, gang) + TriggerEvent('QBCore:Server:UpdateObject') + return true, "success" +end +QBCore.Functions.AddGang = AddGang +exports('AddGang', AddGang) + +-- Multiple Add Gangs +local function AddGangs(gangs) + local shouldContinue = true + local message = "success" + local errorItem = nil + for key, value in pairs(gangs) do + if type(key) ~= "string" then + message = "invalid_gang_name" + shouldContinue = false + errorItem = gangs[key] + break + end + + if QBCore.Shared.Gangs[key] then + message = "gang_exists" + shouldContinue = false + errorItem = gangs[key] + break + end + QBCore.Shared.Gangs[key] = value + end + + if not shouldContinue then return false, message, errorItem end + TriggerClientEvent('QBCore:Client:OnSharedUpdateMultiple', -1, 'Gangs', gangs) + TriggerEvent('QBCore:Server:UpdateObject') + return true, message, nil +end +QBCore.Functions.AddGangs = AddGangs +exports('AddGangs', AddGangs) + +local function GetCoreVersion(InvokingResource) + local resourceVersion = GetResourceMetadata(GetCurrentResourceName(), 'version') + if InvokingResource and InvokingResource ~= '' then + print(("%s called qbcore version check: %s"):format(InvokingResource or 'Unknown Resource', resourceVersion)) + end + return resourceVersion +end +QBCore.Functions.GetCoreVersion = GetCoreVersion +exports('GetCoreVersion', GetCoreVersion) diff --git a/server/functions.lua b/server/functions.lua index df63ca7..95b302d 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -1,4 +1,6 @@ QBCore.Functions = {} +QBCore.Player_Buckets = {} +QBCore.Entity_Buckets = {} -- Getters -- Get your player first and then trigger a function on them @@ -12,9 +14,8 @@ function QBCore.Functions.GetCoords(entity) end function QBCore.Functions.GetIdentifier(source, idtype) - local src = source - local idtype = idtype or QBConfig.IdentifierType - for _, identifier in pairs(GetPlayerIdentifiers(src)) do + local identifiers = GetPlayerIdentifiers(source) + for _, identifier in pairs(identifiers) do if string.find(identifier, idtype) then return identifier end @@ -23,7 +24,7 @@ function QBCore.Functions.GetIdentifier(source, idtype) end function QBCore.Functions.GetSource(identifier) - for src, player in pairs(QBCore.Players) do + for src, _ in pairs(QBCore.Players) do local idens = GetPlayerIdentifiers(src) for _, id in pairs(idens) do if identifier == id then @@ -35,18 +36,16 @@ function QBCore.Functions.GetSource(identifier) end function QBCore.Functions.GetPlayer(source) - local src = source - if type(src) == 'number' then - return QBCore.Players[src] + if type(source) == 'number' then + return QBCore.Players[source] else - return QBCore.Players[QBCore.Functions.GetSource(src)] + return QBCore.Players[QBCore.Functions.GetSource(source)] end end function QBCore.Functions.GetPlayerByCitizenId(citizenid) - for src, player in pairs(QBCore.Players) do - local cid = citizenid - if QBCore.Players[src].PlayerData.citizenid == cid then + for src, _ in pairs(QBCore.Players) do + if QBCore.Players[src].PlayerData.citizenid == citizenid then return QBCore.Players[src] end end @@ -54,8 +53,7 @@ function QBCore.Functions.GetPlayerByCitizenId(citizenid) end function QBCore.Functions.GetPlayerByPhone(number) - for src, player in pairs(QBCore.Players) do - local cid = citizenid + for src, _ in pairs(QBCore.Players) do if QBCore.Players[src].PlayerData.charinfo.phone == number then return QBCore.Players[src] end @@ -81,12 +79,11 @@ end function QBCore.Functions.GetPlayersOnDuty(job) local players = {} local count = 0 - for src, Player in pairs(QBCore.Players) do if Player.PlayerData.job.name == job then if Player.PlayerData.job.onduty then players[#players + 1] = src - count = count + 1 + count += 1 end end end @@ -96,59 +93,53 @@ end -- Returns only the amount of players on duty for the specified job function QBCore.Functions.GetDutyCount(job) local count = 0 - - for _, Player in pairs(QBCore.Functions.GetQBPlayers()) do + for _, Player in pairs(QBCore.Players) do if Player.PlayerData.job.name == job then if Player.PlayerData.job.onduty then - count = count + 1 + count += 1 end end end return count end ---- Routingbucket stuff (Only touch if you know what you are doing) -_G.Player_Buckets = {} -- Bucket array containing all players that have been set to a different bucket -_G.Entity_Buckets = {} -- Bucket array containing all entities that have been set to a different bucket +-- Routing buckets (Only touch if you know what you are doing) - ---- Returns the objects related to buckets, first returned value is the player buckets , second one is entity buckets +-- Returns the objects related to buckets, first returned value is the player buckets, second one is entity buckets function QBCore.Functions.GetBucketObjects() - return _G.Player_Buckets, _G.Entity_Buckets + return QBCore.Player_Buckets, QBCore.Entity_Buckets end - ---- Will set the provided player id / source into the provided bucket id -function QBCore.Functions.SetPlayerBucket(player_source --[[int]],bucket --[[int]]) - if player_source and bucket then - local plicense = QBCore.Functions.GetIdentifier(player_source, 'license') - SetPlayerRoutingBucket(player_source, bucket) - _G.Player_Buckets[plicense] = {player_id = player_source, player_bucket = bucket} +-- Will set the provided player id / source into the provided bucket id +function QBCore.Functions.SetPlayerBucket(source --[[ int ]], bucket --[[ int ]]) + if source and bucket then + local plicense = QBCore.Functions.GetIdentifier(source, 'license') + SetPlayerRoutingBucket(source, bucket) + QBCore.Player_Buckets[plicense] = {id = source, bucket = bucket} return true else return false end end ---- Will set any entity into the provided bucket, for example peds / vehicles / props / etc... -function QBCore.Functions.SetEntityBucket(entity --[[int]],bucket --[[int]]) +-- Will set any entity into the provided bucket, for example peds / vehicles / props / etc. +function QBCore.Functions.SetEntityBucket(entity --[[ int ]], bucket --[[ int ]]) if entity and bucket then SetEntityRoutingBucket(entity, bucket) - _G.Entity_Buckets[entity] = {entity_id = entity, entity_bucket = bucket} + QBCore.Entity_Buckets[entity] = {id = entity, bucket = bucket} return true else return false end end - -- Will return an array of all the player ids inside the current bucket -function QBCore.Functions.GetPlayersInBucket(bucket --[[int]]) +function QBCore.Functions.GetPlayersInBucket(bucket --[[ int ]]) local curr_bucket_pool = {} - if _G.Player_Buckets ~= nil then - for k, v in pairs(_G.Player_Buckets) do - if k['player_bucket'] == bucket then - curr_bucket_pool[#curr_bucket_pool + 1] = k['player_id'] + if QBCore.Player_Buckets and next(QBCore.Player_Buckets) then + for k, v in pairs(QBCore.Player_Buckets) do + if v.bucket == bucket then + curr_bucket_pool[#curr_bucket_pool + 1] = v.id end end return curr_bucket_pool @@ -157,14 +148,13 @@ function QBCore.Functions.GetPlayersInBucket(bucket --[[int]]) end end - ---- Will return an array of all the entities inside the current bucket (Not player entities , use GetPlayersInBucket for that) -function QBCore.Functions.GetEntitiesInBucket(bucket --[[int]]) +-- Will return an array of all the entities inside the current bucket (not for player entities, use GetPlayersInBucket for that) +function QBCore.Functions.GetEntitiesInBucket(bucket --[[ int ]]) local curr_bucket_pool = {} - if _G.Entity_Buckets ~= nil then - for k, v in pairs(_G.Entity_Buckets) do - if k['entity_bucket'] == bucket then - curr_bucket_pool[#curr_bucket_pool + 1] = k['entity_id'] + if QBCore.Entity_Buckets and next(QBCore.Entity_Buckets) then + for k, v in pairs(QBCore.Entity_Buckets) do + if v.bucket == bucket then + curr_bucket_pool[#curr_bucket_pool + 1] = v.id end end return curr_bucket_pool @@ -173,40 +163,37 @@ function QBCore.Functions.GetEntitiesInBucket(bucket --[[int]]) end end ---- Will return true / false wheter the mentioned player id is present in the bucket provided -function QBCore.Functions.IsPlayerInBucket(player_source --[[int]] ,bucket --[[int]]) - local curr_player_bucket = GetPlayerRoutingBucket(player_source) - return curr_player_bucket == bucket -end - -- Paychecks (standalone - don't touch) -function PaycheckLoop() - local Players = QBCore.Functions.GetQBPlayers() - for _, Player in pairs(Players) do - local payment = Player.PlayerData.job.payment - if Player.PlayerData.job and payment > 0 and (QBShared.Jobs[Player.PlayerData.job.name].offDutyPay or Player.PlayerData.job.onduty) then - if QBCore.Config.Money.PayCheckSociety then - local account = exports['qb-bossmenu']:GetAccount(Player.PlayerData.job.name) - if account ~= 0 then -- Checks if player is employed by a society - if account < payment then -- Checks if company has enough money to pay society - TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('error.company_too_poor'), 'error') +function PaycheckInterval() + if next(QBCore.Players) then + for _, Player in pairs(QBCore.Players) do + if Player then + local payment = Player.PlayerData.job.payment + if Player.PlayerData.job and payment > 0 and (QBShared.Jobs[Player.PlayerData.job.name].offDutyPay or Player.PlayerData.job.onduty) then + if QBCore.Config.Money.PayCheckSociety then + local account = exports['qb-management']:GetAccount(Player.PlayerData.job.name) + if account ~= 0 then -- Checks if player is employed by a society + if account < payment then -- Checks if company has enough money to pay society + TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('error.company_too_poor'), 'error') + else + Player.Functions.AddMoney('bank', payment) + exports['qb-management']:RemoveMoney(Player.PlayerData.job.name, payment) + TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('info.received_paycheck', {value = payment})) + end + else + Player.Functions.AddMoney('bank', payment) + TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('info.received_paycheck', {value = payment})) + end else Player.Functions.AddMoney('bank', payment) - TriggerEvent('qb-bossmenu:server:removeAccountMoney', Player.PlayerData.job.name, payment) TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('info.received_paycheck', {value = payment})) end - else - Player.Functions.AddMoney('bank', payment) - TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('info.received_paycheck', {value = payment})) end - else - Player.Functions.AddMoney('bank', payment) - TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, Lang:t('info.received_paycheck', {value = payment})) end end end - SetTimeout(QBCore.Config.Money.PayCheckTimeOut * (60 * 1000), PaycheckLoop) + SetTimeout(QBCore.Config.Money.PayCheckTimeOut * (60 * 1000), PaycheckInterval) end -- Callbacks @@ -216,10 +203,8 @@ function QBCore.Functions.CreateCallback(name, cb) end function QBCore.Functions.TriggerCallback(name, source, cb, ...) - local src = source - if QBCore.ServerCallbacks[name] then - QBCore.ServerCallbacks[name](src, cb, ...) - end + if not QBCore.ServerCallbacks[name] then return end + QBCore.ServerCallbacks[name](source, cb, ...) end -- Items @@ -233,15 +218,13 @@ function QBCore.Functions.CanUseItem(item) end function QBCore.Functions.UseItem(source, item) - local src = source - QBCore.UseableItems[item.name](src, item) + QBCore.UseableItems[item.name](source, item) end -- Kick Player function QBCore.Functions.Kick(source, reason, setKickReason, deferrals) - local src = source - reason = '\n' .. reason .. '\n🔸 Check our Discord for further information: ' .. QBCore.Config.Server.discord + reason = '\n' .. reason .. '\n🔸 Check our Discord for further information: ' .. QBCore.Config.Server.Discord if setKickReason then setKickReason(reason) end @@ -250,20 +233,18 @@ function QBCore.Functions.Kick(source, reason, setKickReason, deferrals) deferrals.update(reason) Wait(2500) end - if src then - DropPlayer(src, reason) + if source then + DropPlayer(source, reason) end - local i = 0 - while (i <= 4) do - i = i + 1 + for i = 0, 4 do while true do - if src then - if (GetPlayerPing(src) >= 0) then + if source then + if GetPlayerPing(source) >= 0 then break end Wait(100) CreateThread(function() - DropPlayer(src, reason) + DropPlayer(source, reason) end) end end @@ -272,24 +253,11 @@ function QBCore.Functions.Kick(source, reason, setKickReason, deferrals) end) end --- Check if player is whitelisted (not used anywhere) +-- Check if player is whitelisted, kept like this for backwards compatibility or future plans function QBCore.Functions.IsWhitelisted(source) - local src = source - local plicense = QBCore.Functions.GetIdentifier(src, 'license') - local identifiers = GetPlayerIdentifiers(src) - if QBCore.Config.Server.whitelist then - local result = MySQL.Sync.fetchSingle('SELECT * FROM whitelist WHERE license = ?', { plicense }) - if result then - for _, id in pairs(identifiers) do - if result.license == id then - return true - end - end - end - else - return true - end + if not QBCore.Config.Server.Whitelist then return true end + if QBCore.Functions.HasPermission(source, QBCore.Config.Server.WhitelistPermission) then return true end return false end @@ -297,34 +265,26 @@ end function QBCore.Functions.AddPermission(source, permission) local src = source - local Player = QBCore.Functions.GetPlayer(src) - local plicense = Player.PlayerData.license - if Player then - QBCore.Config.Server.PermissionList[plicense] = { - license = plicense, - permission = permission:lower(), - } - MySQL.Async.execute('DELETE FROM permissions WHERE license = ?', { plicense }) - - MySQL.Async.insert('INSERT INTO permissions (name, license, permission) VALUES (?, ?, ?)', { - GetPlayerName(src), - plicense, - permission:lower() - }) - - Player.Functions.UpdatePlayerData() - TriggerClientEvent('QBCore:Client:OnPermissionUpdate', src, permission) - end + local license = QBCore.Functions.GetIdentifier(src, 'license') + ExecuteCommand(('add_principal identifier.%s qbcore.%s'):format(license, permission)) + QBCore.Commands.Refresh(src) end -function QBCore.Functions.RemovePermission(source) +function QBCore.Functions.RemovePermission(source, permission) local src = source - local Player = QBCore.Functions.GetPlayer(src) - local license = Player.PlayerData.license - if Player then - QBCore.Config.Server.PermissionList[license] = nil - MySQL.Async.execute('DELETE FROM permissions WHERE license = ?', { license }) - Player.Functions.UpdatePlayerData() + local license = QBCore.Functions.GetIdentifier(src, 'license') + if permission then + if IsPlayerAceAllowed(src, permission) then + ExecuteCommand(('remove_principal identifier.%s qbcore.%s'):format(license, permission)) + QBCore.Commands.Refresh(src) + end + else + for k,v in pairs(QBCore.Config.Server.Permissions) do + if IsPlayerAceAllowed(src, v) then + ExecuteCommand(('remove_principal identifier.%s qbcore.%s'):format(license, v)) + QBCore.Commands.Refresh(src) + end + end end end @@ -332,71 +292,51 @@ end function QBCore.Functions.HasPermission(source, permission) local src = source - local license = QBCore.Functions.GetIdentifier(src, 'license') - local permission = tostring(permission:lower()) - if permission == 'user' then - return true - else - if QBCore.Config.Server.PermissionList[license] then - if QBCore.Config.Server.PermissionList[license].license == license then - if QBCore.Config.Server.PermissionList[license].permission == permission or QBCore.Config.Server.PermissionList[license].permission == 'god' then - return true - end - end - end - end + if IsPlayerAceAllowed(src, permission) then return true end return false end function QBCore.Functions.GetPermission(source) local src = source - local license = QBCore.Functions.GetIdentifier(src, 'license') - if license then - if QBCore.Config.Server.PermissionList[license] then - if QBCore.Config.Server.PermissionList[license].license == license then - return QBCore.Config.Server.PermissionList[license].permission - end + local perms = {} + for k,v in pairs (QBCore.Config.Server.Permissions) do + if IsPlayerAceAllowed(src, v) then + perms[v] = true end end - return 'user' + return perms end -- Opt in or out of admin reports function QBCore.Functions.IsOptin(source) - local src = source - local license = QBCore.Functions.GetIdentifier(src, 'license') - if QBCore.Functions.HasPermission(src, 'admin') then - return QBCore.Config.Server.PermissionList[license].optin - end + local license = QBCore.Functions.GetIdentifier(source, 'license') + if not license or not QBCore.Functions.HasPermission(source, 'admin') then return false end + local Player = QBCore.Functions.GetPlayer(source) + return Player.PlayerData.optin end function QBCore.Functions.ToggleOptin(source) - local src = source - local license = QBCore.Functions.GetIdentifier(src, 'license') - if QBCore.Functions.HasPermission(src, 'admin') then - QBCore.Config.Server.PermissionList[license].optin = not QBCore.Config.Server.PermissionList[license].optin - end + local license = QBCore.Functions.GetIdentifier(source, 'license') + if not license or not QBCore.Functions.HasPermission(source, 'admin') then return end + local Player = QBCore.Functions.GetPlayer(source) + Player.PlayerData.optin = not Player.PlayerData.optin + Player.Functions.SetMetaData('optin', Player.PlayerData.optin) end -- Check if player is banned function QBCore.Functions.IsPlayerBanned(source) - local src = source - local retval = false - local message = '' - local plicense = QBCore.Functions.GetIdentifier(src, 'license') + local plicense = QBCore.Functions.GetIdentifier(source, 'license') local result = MySQL.Sync.fetchSingle('SELECT * FROM bans WHERE license = ?', { plicense }) - if result then - if os.time() < result.expire then - retval = true - local timeTable = os.date('*t', tonumber(result.expire)) - message = 'You have been banned from the server:\n' .. result[1].reason .. '\nYour ban expires ' .. timeTable.day .. '/' .. timeTable.month .. '/' .. timeTable.year .. ' ' .. timeTable.hour .. ':' .. timeTable.min .. '\n' - else - MySQL.Async.execute('DELETE FROM bans WHERE id = ?', { result[1].id }) - end + if not result then return false end + if os.time() < result.expire then + local timeTable = os.date('*t', tonumber(result.expire)) + return true, 'You have been banned from the server:\n' .. result.reason .. '\nYour ban expires ' .. timeTable.day .. '/' .. timeTable.month .. '/' .. timeTable.year .. ' ' .. timeTable.hour .. ':' .. timeTable.min .. '\n' + else + MySQL.Async.execute('DELETE FROM bans WHERE id = ?', { result.id }) end - return retval, message + return false end -- Check for duplicate license @@ -407,8 +347,7 @@ function QBCore.Functions.IsLicenseInUse(license) local identifiers = GetPlayerIdentifiers(player) for _, id in pairs(identifiers) do if string.find(id, 'license') then - local playerLicense = id - if playerLicense == license then + if id == license then return true end end diff --git a/server/main.lua b/server/main.lua index ff93dfc..41c0c43 100644 --- a/server/main.lua +++ b/server/main.lua @@ -10,19 +10,4 @@ end) -- To use this export in a script instead of manifest method -- Just put this line of code below at the very top of the script --- local QBCore = exports['qb-core']:GetCoreObject() - --- Get permissions on server start - -CreateThread(function() - local result = MySQL.Sync.fetchAll('SELECT * FROM permissions', {}) - if result[1] then - for k, v in pairs(result) do - QBCore.Config.Server.PermissionList[v.license] = { - license = v.license, - permission = v.permission, - optin = true, - } - end - end -end) \ No newline at end of file +-- local QBCore = exports['qb-core']:GetCoreObject() \ No newline at end of file diff --git a/server/player.lua b/server/player.lua index 9609875..542cf15 100644 --- a/server/player.lua +++ b/server/player.lua @@ -6,10 +6,9 @@ QBCore.Player = {} -- Will cause major issues! function QBCore.Player.Login(source, citizenid, newData) - local src = source - if src then + if source and source ~= '' then if citizenid then - local license = QBCore.Functions.GetIdentifier(src, 'license') + local license = QBCore.Functions.GetIdentifier(source, 'license') local PlayerData = MySQL.Sync.prepare('SELECT * FROM players where citizenid = ?', { citizenid }) if PlayerData and license == PlayerData.license then PlayerData.money = json.decode(PlayerData.money) @@ -22,13 +21,13 @@ function QBCore.Player.Login(source, citizenid, newData) else PlayerData.gang = {} end - QBCore.Player.CheckPlayerData(src, PlayerData) + QBCore.Player.CheckPlayerData(source, PlayerData) else - DropPlayer(src, 'You Have Been Kicked For Exploitation') - TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(src) .. ' Has Been Dropped For Character Joining Exploit', false) + DropPlayer(source, 'You Have Been Kicked For Exploitation') + TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(source) .. ' Has Been Dropped For Character Joining Exploit', false) end else - QBCore.Player.CheckPlayerData(src, newData) + QBCore.Player.CheckPlayerData(source, newData) end return true else @@ -38,14 +37,14 @@ function QBCore.Player.Login(source, citizenid, newData) end function QBCore.Player.CheckPlayerData(source, PlayerData) - local src = source PlayerData = PlayerData or {} - PlayerData.source = src + PlayerData.source = source PlayerData.citizenid = PlayerData.citizenid or QBCore.Player.CreateCitizenId() - PlayerData.license = PlayerData.license or QBCore.Functions.GetIdentifier(src, 'license') - PlayerData.name = GetPlayerName(src) + PlayerData.license = PlayerData.license or QBCore.Functions.GetIdentifier(source, 'license') + PlayerData.name = GetPlayerName(source) PlayerData.cid = PlayerData.cid or 1 PlayerData.money = PlayerData.money or {} + PlayerData.optin = PlayerData.optin or true for moneytype, startamount in pairs(QBCore.Config.Money.MoneyTypes) do PlayerData.money[moneytype] = PlayerData.money[moneytype] or startamount end @@ -57,8 +56,8 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData.charinfo.gender = PlayerData.charinfo.gender or 0 PlayerData.charinfo.backstory = PlayerData.charinfo.backstory or 'placeholder backstory' PlayerData.charinfo.nationality = PlayerData.charinfo.nationality or 'USA' - PlayerData.charinfo.phone = PlayerData.charinfo.phone ~= nil and PlayerData.charinfo.phone or '1' .. math.random(111111111, 999999999) - PlayerData.charinfo.account = PlayerData.charinfo.account ~= nil and PlayerData.charinfo.account or 'US0' .. math.random(1, 9) .. 'QBCore' .. math.random(1111, 9999) .. math.random(1111, 9999) .. math.random(11, 99) + PlayerData.charinfo.phone = PlayerData.charinfo.phone or QBCore.Functions.CreatePhoneNumber() + PlayerData.charinfo.account = PlayerData.charinfo.account or QBCore.Functions.CreateAccountNumber() -- Metadata PlayerData.metadata = PlayerData.metadata or {} PlayerData.metadata['hunger'] = PlayerData.metadata['hunger'] or 100 @@ -80,21 +79,11 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData.metadata['craftingrep'] = PlayerData.metadata['craftingrep'] or 0 PlayerData.metadata['attachmentcraftingrep'] = PlayerData.metadata['attachmentcraftingrep'] or 0 PlayerData.metadata['currentapartment'] = PlayerData.metadata['currentapartment'] or nil - if PlayerData.metadata['jobrep'] ~= nil then - PlayerData.metadata['jobrep'] = { - ['tow'] = PlayerData.metadata['jobrep']['tow'] or 0, - ['trucker'] = PlayerData.metadata['jobrep']['trucker'] or 0, - ['taxi'] = PlayerData.metadata['jobrep']['taxi'] or 0, - ['hotdog'] = PlayerData.metadata['jobrep']['hotdog'] or 0, - } - else - PlayerData.metadata['jobrep'] = { - ['tow'] = 0, - ['trucker'] = 0, - ['taxi'] = 0, - ['hotdog'] = 0, - } - end + PlayerData.metadata['jobrep'] = PlayerData.metadata['jobrep'] or {} + PlayerData.metadata['jobrep']['tow'] = PlayerData.metadata['jobrep']['tow'] or 0 + PlayerData.metadata['jobrep']['trucker'] = PlayerData.metadata['jobrep']['trucker'] or 0 + PlayerData.metadata['jobrep']['taxi'] = PlayerData.metadata['jobrep']['taxi'] or 0 + PlayerData.metadata['jobrep']['hotdog'] = PlayerData.metadata['jobrep']['hotdog'] or 0 PlayerData.metadata['callsign'] = PlayerData.metadata['callsign'] or 'NO CALLSIGN' PlayerData.metadata['fingerprint'] = PlayerData.metadata['fingerprint'] or QBCore.Player.CreateFingerId() PlayerData.metadata['walletid'] = PlayerData.metadata['walletid'] or QBCore.Player.CreateWalletId() @@ -119,6 +108,7 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) InstalledApps = {}, } -- Job + if PlayerData.job and PlayerData.job.name and not QBCore.Shared.Jobs[PlayerData.job.name] then PlayerData.job = nil end PlayerData.job = PlayerData.job or {} PlayerData.job.name = PlayerData.job.name or 'unemployed' PlayerData.job.label = PlayerData.job.label or 'Civilian' @@ -131,6 +121,7 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData.job.grade.name = PlayerData.job.grade.name or 'Freelancer' PlayerData.job.grade.level = PlayerData.job.grade.level or 0 -- Gang + if PlayerData.gang and PlayerData.gang.name and not QBCore.Shared.Gangs[PlayerData.gang.name] then PlayerData.gang = nil end PlayerData.gang = PlayerData.gang or {} PlayerData.gang.name = PlayerData.gang.name or 'none' PlayerData.gang.label = PlayerData.gang.label or 'No Gang Affiliaton' @@ -140,7 +131,6 @@ function QBCore.Player.CheckPlayerData(source, PlayerData) PlayerData.gang.grade.level = PlayerData.gang.grade.level or 0 -- Other PlayerData.position = PlayerData.position or QBConfig.DefaultSpawn - PlayerData.LoggedIn = true PlayerData = QBCore.Player.LoadInventory(PlayerData) QBCore.Player.CreatePlayer(PlayerData) end @@ -148,11 +138,10 @@ end -- On player logout function QBCore.Player.Logout(source) - local src = source - TriggerClientEvent('QBCore:Client:OnPlayerUnload', src) - TriggerClientEvent('QBCore:Player:UpdatePlayerData', src) + TriggerClientEvent('QBCore:Client:OnPlayerUnload', source) + TriggerClientEvent('QBCore:Player:UpdatePlayerData', source) Wait(200) - QBCore.Players[src] = nil + QBCore.Players[source] = nil end -- Create a new character @@ -164,180 +153,154 @@ function QBCore.Player.CreatePlayer(PlayerData) self.Functions = {} self.PlayerData = PlayerData - self.Functions.UpdatePlayerData = function(dontUpdateChat) + function self.Functions.UpdatePlayerData(dontUpdateChat) TriggerClientEvent('QBCore:Player:SetPlayerData', self.PlayerData.source, self.PlayerData) - if dontUpdateChat == nil then + if not dontUpdateChat then QBCore.Commands.Refresh(self.PlayerData.source) end end - self.Functions.SetJob = function(job, grade) - local job = job:lower() - local grade = tostring(grade) or '0' - - if QBCore.Shared.Jobs[job] then - self.PlayerData.job.name = job - self.PlayerData.job.label = QBCore.Shared.Jobs[job].label - self.PlayerData.job.onduty = QBCore.Shared.Jobs[job].defaultDuty - - if QBCore.Shared.Jobs[job].grades[grade] then - local jobgrade = QBCore.Shared.Jobs[job].grades[grade] - self.PlayerData.job.grade = {} - self.PlayerData.job.grade.name = jobgrade.name - self.PlayerData.job.grade.level = tonumber(grade) - self.PlayerData.job.payment = jobgrade.payment or 30 - self.PlayerData.job.isboss = jobgrade.isboss or false - else - self.PlayerData.job.grade = {} - self.PlayerData.job.grade.name = 'No Grades' - self.PlayerData.job.grade.level = 0 - self.PlayerData.job.payment = 30 - self.PlayerData.job.isboss = false - end - - self.Functions.UpdatePlayerData() - TriggerEvent('QBCore:Server:OnJobUpdate', self.PlayerData.source, self.PlayerData.job) - TriggerClientEvent('QBCore:Client:OnJobUpdate', self.PlayerData.source, self.PlayerData.job) - return true + function self.Functions.SetJob(job, grade) + job = job:lower() + grade = tostring(grade) or '0' + if not QBCore.Shared.Jobs[job] then return false end + self.PlayerData.job.name = job + self.PlayerData.job.label = QBCore.Shared.Jobs[job].label + self.PlayerData.job.onduty = QBCore.Shared.Jobs[job].defaultDuty + if QBCore.Shared.Jobs[job].grades[grade] then + local jobgrade = QBCore.Shared.Jobs[job].grades[grade] + self.PlayerData.job.grade = {} + self.PlayerData.job.grade.name = jobgrade.name + self.PlayerData.job.grade.level = tonumber(grade) + self.PlayerData.job.payment = jobgrade.payment or 30 + self.PlayerData.job.isboss = jobgrade.isboss or false + else + self.PlayerData.job.grade = {} + self.PlayerData.job.grade.name = 'No Grades' + self.PlayerData.job.grade.level = 0 + self.PlayerData.job.payment = 30 + self.PlayerData.job.isboss = false end - - return false - end - - self.Functions.SetGang = function(gang, grade) - local gang = gang:lower() - local grade = tostring(grade) or '0' - - if QBCore.Shared.Gangs[gang] then - self.PlayerData.gang.name = gang - self.PlayerData.gang.label = QBCore.Shared.Gangs[gang].label - if QBCore.Shared.Gangs[gang].grades[grade] then - local ganggrade = QBCore.Shared.Gangs[gang].grades[grade] - self.PlayerData.gang.grade = {} - self.PlayerData.gang.grade.name = ganggrade.name - self.PlayerData.gang.grade.level = tonumber(grade) - self.PlayerData.gang.isboss = ganggrade.isboss or false - else - self.PlayerData.gang.grade = {} - self.PlayerData.gang.grade.name = 'No Grades' - self.PlayerData.gang.grade.level = 0 - self.PlayerData.gang.isboss = false - end - - self.Functions.UpdatePlayerData() - TriggerClientEvent('QBCore:Client:OnGangUpdate', self.PlayerData.source, self.PlayerData.gang) - return true - end - return false - end - - self.Functions.SetJobDuty = function(onDuty) - self.PlayerData.job.onduty = onDuty self.Functions.UpdatePlayerData() TriggerEvent('QBCore:Server:OnJobUpdate', self.PlayerData.source, self.PlayerData.job) TriggerClientEvent('QBCore:Client:OnJobUpdate', self.PlayerData.source, self.PlayerData.job) + return true end - self.Functions.SetMetaData = function(meta, val) - local meta = meta:lower() - if val ~= nil then - self.PlayerData.metadata[meta] = val - self.Functions.UpdatePlayerData() + function self.Functions.SetGang(gang, grade) + gang = gang:lower() + grade = tostring(grade) or '0' + if not QBCore.Shared.Gangs[gang] then return false end + self.PlayerData.gang.name = gang + self.PlayerData.gang.label = QBCore.Shared.Gangs[gang].label + if QBCore.Shared.Gangs[gang].grades[grade] then + local ganggrade = QBCore.Shared.Gangs[gang].grades[grade] + self.PlayerData.gang.grade = {} + self.PlayerData.gang.grade.name = ganggrade.name + self.PlayerData.gang.grade.level = tonumber(grade) + self.PlayerData.gang.isboss = ganggrade.isboss or false + else + self.PlayerData.gang.grade = {} + self.PlayerData.gang.grade.name = 'No Grades' + self.PlayerData.gang.grade.level = 0 + self.PlayerData.gang.isboss = false end + self.Functions.UpdatePlayerData() + TriggerClientEvent('QBCore:Client:OnGangUpdate', self.PlayerData.source, self.PlayerData.gang) + return true end - self.Functions.AddJobReputation = function(amount) - local amount = tonumber(amount) + function self.Functions.SetJobDuty(onDuty) + self.PlayerData.job.onduty = onDuty + self.Functions.UpdatePlayerData() + end + + function self.Functions.SetMetaData(meta, val) + if not meta then return end + meta = meta:lower() + self.PlayerData.metadata[meta] = val + self.Functions.UpdatePlayerData() + end + + function self.Functions.AddJobReputation(amount) + if not amount then return end + amount = tonumber(amount) self.PlayerData.metadata['jobrep'][self.PlayerData.job.name] = self.PlayerData.metadata['jobrep'][self.PlayerData.job.name] + amount self.Functions.UpdatePlayerData() end - self.Functions.AddMoney = function(moneytype, amount, reason) + function self.Functions.AddMoney(moneytype, amount, reason) reason = reason or 'unknown' - local moneytype = moneytype:lower() - local amount = tonumber(amount) - if amount < 0 then - return + moneytype = moneytype:lower() + amount = tonumber(amount) + if amount < 0 then return end + if not self.PlayerData.money[moneytype] then return false end + self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] + amount + self.Functions.UpdatePlayerData() + if amount > 100000 then + TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'AddMoney', 'lightgreen', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') added, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype], true) + else + TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'AddMoney', 'lightgreen', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') added, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype]) end - if self.PlayerData.money[moneytype] then - self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] + amount - self.Functions.UpdatePlayerData() - if amount > 100000 then - TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'AddMoney', 'lightgreen', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') added, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype], true) - else - TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'AddMoney', 'lightgreen', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') added, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype]) - end - TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, amount, false) - return true - end - return false + TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, amount, false) + return true end - self.Functions.RemoveMoney = function(moneytype, amount, reason) + function self.Functions.RemoveMoney(moneytype, amount, reason) reason = reason or 'unknown' - local moneytype = moneytype:lower() - local amount = tonumber(amount) - if amount < 0 then - return - end - if self.PlayerData.money[moneytype] then - for _, mtype in pairs(QBCore.Config.Money.DontAllowMinus) do - if mtype == moneytype then - if self.PlayerData.money[moneytype] - amount < 0 then - return false - end + moneytype = moneytype:lower() + amount = tonumber(amount) + if amount < 0 then return end + if not self.PlayerData.money[moneytype] then return false end + for _, mtype in pairs(QBCore.Config.Money.DontAllowMinus) do + if mtype == moneytype then + if (self.PlayerData.money[moneytype] - amount) < 0 then + return false end end - self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] - amount - self.Functions.UpdatePlayerData() - if amount > 100000 then - TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'RemoveMoney', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') removed, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype], true) - else - TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'RemoveMoney', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') removed, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype]) - end - TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, amount, true) - if moneytype == 'bank' then - TriggerClientEvent('qb-phone:client:RemoveBankMoney', self.PlayerData.source, amount) - end - return true end - return false + self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] - amount + self.Functions.UpdatePlayerData() + if amount > 100000 then + TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'RemoveMoney', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') removed, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype], true) + else + TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'RemoveMoney', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') removed, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype]) + end + TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, amount, true) + if moneytype == 'bank' then + TriggerClientEvent('qb-phone:client:RemoveBankMoney', self.PlayerData.source, amount) + end + return true end - self.Functions.SetMoney = function(moneytype, amount, reason) + function self.Functions.SetMoney(moneytype, amount, reason) reason = reason or 'unknown' - local moneytype = moneytype:lower() - local amount = tonumber(amount) - if amount < 0 then - return - end - if self.PlayerData.money[moneytype] then - self.PlayerData.money[moneytype] = amount - self.Functions.UpdatePlayerData() - TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'SetMoney', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') set, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype]) - return true - end - return false + moneytype = moneytype:lower() + amount = tonumber(amount) + if amount < 0 then return false end + if not self.PlayerData.money[moneytype] then return false end + self.PlayerData.money[moneytype] = amount + self.Functions.UpdatePlayerData() + TriggerEvent('qb-log:server:CreateLog', 'playermoney', 'SetMoney', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') set, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype]) + return true end - self.Functions.GetMoney = function(moneytype) - if moneytype then - local moneytype = moneytype:lower() - return self.PlayerData.money[moneytype] - end - return false + function self.Functions.GetMoney(moneytype) + if not moneytype then return false end + moneytype = moneytype:lower() + return self.PlayerData.money[moneytype] end - self.Functions.AddItem = function(item, amount, slot, info) + function self.Functions.AddItem(item, amount, slot, info) local totalWeight = QBCore.Player.GetTotalWeight(self.PlayerData.items) local itemInfo = QBCore.Shared.Items[item:lower()] - if itemInfo == nil then + if not itemInfo then TriggerClientEvent('QBCore:Notify', self.PlayerData.source, Lang:t('error.item_not_exist'), 'error') return end - local amount = tonumber(amount) - local slot = tonumber(slot) or QBCore.Player.GetFirstSlotByItem(self.PlayerData.items, item) - if itemInfo['type'] == 'weapon' and info == nil then + amount = tonumber(amount) + slot = tonumber(slot) or QBCore.Player.GetFirstSlotByItem(self.PlayerData.items, item) + if itemInfo['type'] == 'weapon' and not info then info = { serie = tostring(QBCore.Shared.RandomInt(2) .. QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(1) .. QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(4)), } @@ -348,12 +311,12 @@ function QBCore.Player.CreatePlayer(PlayerData) self.Functions.UpdatePlayerData() TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'AddItem', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** got item: [slot:' .. slot .. '], itemname: ' .. self.PlayerData.items[slot].name .. ', added amount: ' .. amount .. ', new total amount: ' .. self.PlayerData.items[slot].amount) return true - elseif (not itemInfo['unique'] and slot or slot and self.PlayerData.items[slot] == nil) then + elseif not itemInfo['unique'] and slot or slot and self.PlayerData.items[slot] == nil then self.PlayerData.items[slot] = { name = itemInfo['name'], amount = amount, info = info or '', label = itemInfo['label'], description = itemInfo['description'] or '', weight = itemInfo['weight'], type = itemInfo['type'], unique = itemInfo['unique'], useable = itemInfo['useable'], image = itemInfo['image'], shouldClose = itemInfo['shouldClose'], slot = slot, combinable = itemInfo['combinable'] } self.Functions.UpdatePlayerData() TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'AddItem', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** got item: [slot:' .. slot .. '], itemname: ' .. self.PlayerData.items[slot].name .. ', added amount: ' .. amount .. ', new total amount: ' .. self.PlayerData.items[slot].amount) return true - elseif (itemInfo['unique']) or (not slot or slot == nil) or (itemInfo['type'] == 'weapon') then + elseif itemInfo['unique'] or (not slot or slot == nil) or itemInfo['type'] == 'weapon' then for i = 1, QBConfig.Player.MaxInvSlots, 1 do if self.PlayerData.items[i] == nil then self.PlayerData.items[i] = { name = itemInfo['name'], amount = amount, info = info or '', label = itemInfo['label'], description = itemInfo['description'] or '', weight = itemInfo['weight'], type = itemInfo['type'], unique = itemInfo['unique'], useable = itemInfo['useable'], image = itemInfo['image'], shouldClose = itemInfo['shouldClose'], slot = i, combinable = itemInfo['combinable'] } @@ -369,9 +332,9 @@ function QBCore.Player.CreatePlayer(PlayerData) return false end - self.Functions.RemoveItem = function(item, amount, slot) - local amount = tonumber(amount) - local slot = tonumber(slot) + function self.Functions.RemoveItem(item, amount, slot) + amount = tonumber(amount) + slot = tonumber(slot) if slot then if self.PlayerData.items[slot].amount > amount then self.PlayerData.items[slot].amount = self.PlayerData.items[slot].amount - amount @@ -406,29 +369,26 @@ function QBCore.Player.CreatePlayer(PlayerData) return false end - self.Functions.SetInventory = function(items, dontUpdateChat) + function self.Functions.SetInventory(items, dontUpdateChat) self.PlayerData.items = items self.Functions.UpdatePlayerData(dontUpdateChat) TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'SetInventory', 'blue', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** items set: ' .. json.encode(items)) end - self.Functions.ClearInventory = function() + function self.Functions.ClearInventory() self.PlayerData.items = {} self.Functions.UpdatePlayerData() TriggerEvent('qb-log:server:CreateLog', 'playerinventory', 'ClearInventory', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** inventory cleared') end - self.Functions.GetItemByName = function(item) - local item = tostring(item):lower() + function self.Functions.GetItemByName(item) + item = tostring(item):lower() local slot = QBCore.Player.GetFirstSlotByItem(self.PlayerData.items, item) - if slot then - return self.PlayerData.items[slot] - end - return nil + return self.PlayerData.items[slot] end - self.Functions.GetItemsByName = function(item) - local item = tostring(item):lower() + function self.Functions.GetItemsByName(item) + item = tostring(item):lower() local items = {} local slots = QBCore.Player.GetSlotsByItem(self.PlayerData.items, item) for _, slot in pairs(slots) do @@ -439,12 +399,12 @@ function QBCore.Player.CreatePlayer(PlayerData) return items end - self.Functions.SetCreditCard = function(cardNumber) + function self.Functions.SetCreditCard(cardNumber) self.PlayerData.charinfo.card = cardNumber self.Functions.UpdatePlayerData() end - self.Functions.GetCardSlot = function(cardNumber, cardType) + function self.Functions.GetCardSlot(cardNumber, cardType) local item = tostring(cardType):lower() local slots = QBCore.Player.GetSlotsByItem(self.PlayerData.items, item) for _, slot in pairs(slots) do @@ -457,18 +417,19 @@ function QBCore.Player.CreatePlayer(PlayerData) return nil end - self.Functions.GetItemBySlot = function(slot) - local slot = tonumber(slot) - if self.PlayerData.items[slot] then - return self.PlayerData.items[slot] - end - return nil + function self.Functions.GetItemBySlot(slot) + slot = tonumber(slot) + return self.PlayerData.items[slot] end - self.Functions.Save = function() + function self.Functions.Save() QBCore.Player.Save(self.PlayerData.source) end + function self.Functions.Logout() + QBCore.Player.Logout(self.PlayerData.source) + end + QBCore.Players[self.PlayerData.source] = self QBCore.Player.Save(self.PlayerData.source) @@ -480,10 +441,9 @@ end -- Save player info to database (make sure citizenid is the primary key in your database) function QBCore.Player.Save(source) - local src = source - local ped = GetPlayerPed(src) + local ped = GetPlayerPed(source) local pcoords = GetEntityCoords(ped) - local PlayerData = QBCore.Players[src].PlayerData + local PlayerData = QBCore.Players[source].PlayerData if PlayerData then MySQL.Async.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', { citizenid = PlayerData.citizenid, @@ -497,7 +457,7 @@ function QBCore.Player.Save(source) position = json.encode(pcoords), metadata = json.encode(PlayerData.metadata) }) - QBCore.Player.SaveInventory(src) + QBCore.Player.SaveInventory(source) QBCore.ShowSuccess(GetCurrentResourceName(), PlayerData.name .. ' PLAYER SAVED!') else QBCore.ShowError(GetCurrentResourceName(), 'ERROR QBCORE.PLAYER.SAVE - PLAYERDATA IS EMPTY!') @@ -523,35 +483,35 @@ local playertables = { -- Add tables as needed } function QBCore.Player.DeleteCharacter(source, citizenid) - local src = source - local license = QBCore.Functions.GetIdentifier(src, 'license') + local license = QBCore.Functions.GetIdentifier(source, 'license') local result = MySQL.Sync.fetchScalar('SELECT license FROM players where citizenid = ?', { citizenid }) if license == result then local query = "DELETE FROM %s WHERE citizenid = ?" local tableCount = #playertables local queries = table.create(tableCount, 0) - for i=1, tableCount do + for i = 1, tableCount do local v = playertables[i] queries[i] = {query = query:format(v.table), values = { citizenid }} end MySQL.Async.transaction(queries, function(result) if result then - TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Character Deleted', 'red', '**' .. GetPlayerName(src) .. '** ' .. license .. ' deleted **' .. citizenid .. '**..') + TriggerEvent('qb-log:server:CreateLog', 'joinleave', 'Character Deleted', 'red', '**' .. GetPlayerName(source) .. '** ' .. license .. ' deleted **' .. citizenid .. '**..') end end) else - DropPlayer(src, 'You Have Been Kicked For Exploitation') - TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(src) .. ' Has Been Dropped For Character Deletion Exploit', false) + DropPlayer(source, 'You Have Been Kicked For Exploitation') + TriggerEvent('qb-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(source) .. ' Has Been Dropped For Character Deletion Exploit', true) end end -- Inventory -QBCore.Player.LoadInventory = function(PlayerData) +function QBCore.Player.LoadInventory(PlayerData) PlayerData.items = {} local inventory = MySQL.Sync.prepare('SELECT inventory FROM players WHERE citizenid = ?', { PlayerData.citizenid }) + local missingItems = {} if inventory then inventory = json.decode(inventory) if next(inventory) then @@ -574,36 +534,41 @@ QBCore.Player.LoadInventory = function(PlayerData) slot = item.slot, combinable = itemInfo['combinable'] } + else + missingItems[#missingItems+1] = item.name:lower() end + end end end end + + if #missingItems > 0 then + print(("%s the following items removed as they no longer exist: %s"):format(GetPlayerName(PlayerData.source), json.encode(missingItems))) + end return PlayerData end -QBCore.Player.SaveInventory = function(source) - local src = source - if QBCore.Players[src] then - local PlayerData = QBCore.Players[src].PlayerData - local items = PlayerData.items - local ItemsJson = {} - if items and next(items) then - for slot, item in pairs(items) do - if items[slot] then - ItemsJson[#ItemsJson+1] = { - name = item.name, - amount = item.amount, - info = item.info, - type = item.type, - slot = slot, - } - end +function QBCore.Player.SaveInventory(source) + if not QBCore.Players[source] then return end + local PlayerData = QBCore.Players[source].PlayerData + local items = PlayerData.items + local ItemsJson = {} + if items and next(items) then + for slot, item in pairs(items) do + if items[slot] then + ItemsJson[#ItemsJson+1] = { + name = item.name, + amount = item.amount, + info = item.info, + type = item.type, + slot = slot, + } end - MySQL.Async.prepare('UPDATE players SET inventory = ? WHERE citizenid = ?', { json.encode(ItemsJson), PlayerData.citizenid }) - else - MySQL.Async.prepare('UPDATE players SET inventory = ? WHERE citizenid = ?', { '[]', PlayerData.citizenid }) end + MySQL.Async.prepare('UPDATE players SET inventory = ? WHERE citizenid = ?', { json.encode(ItemsJson), PlayerData.citizenid }) + else + MySQL.Async.prepare('UPDATE players SET inventory = ? WHERE citizenid = ?', { '[]', PlayerData.citizenid }) end end @@ -611,32 +576,29 @@ end function QBCore.Player.GetTotalWeight(items) local weight = 0 - if items then - for slot, item in pairs(items) do - weight = weight + (item.weight * item.amount) - end + if not items then return 0 end + for _, item in pairs(items) do + weight += item.weight * item.amount end return tonumber(weight) end function QBCore.Player.GetSlotsByItem(items, itemName) local slotsFound = {} - if items then - for slot, item in pairs(items) do - if item.name:lower() == itemName:lower() then - slotsFound[#slotsFound+1] = slot - end + if not items then return slotsFound end + for slot, item in pairs(items) do + if item.name:lower() == itemName:lower() then + slotsFound[#slotsFound+1] = slot end end return slotsFound end function QBCore.Player.GetFirstSlotByItem(items, itemName) - if items then - for slot, item in pairs(items) do - if item.name:lower() == itemName:lower() then - return tonumber(slot) - end + if not items then return nil end + for slot, item in pairs(items) do + if item.name:lower() == itemName:lower() then + return tonumber(slot) end end return nil @@ -655,6 +617,34 @@ function QBCore.Player.CreateCitizenId() return CitizenId end +function QBCore.Functions.CreateAccountNumber() + local UniqueFound = false + local AccountNumber = nil + while not UniqueFound do + AccountNumber = 'US0' .. math.random(1, 9) .. 'QBCore' .. math.random(1111, 9999) .. math.random(1111, 9999) .. math.random(11, 99) + local query = '%' .. AccountNumber .. '%' + local result = MySQL.Sync.prepare('SELECT COUNT(*) as count FROM players WHERE charinfo LIKE ?', { query }) + if result == 0 then + UniqueFound = true + end + end + return AccountNumber +end + +function QBCore.Functions.CreatePhoneNumber() + local UniqueFound = false + local PhoneNumber = nil + while not UniqueFound do + PhoneNumber = math.random(100,999) .. math.random(1000000,9999999) + local query = '%' .. PhoneNumber .. '%' + local result = MySQL.Sync.prepare('SELECT COUNT(*) as count FROM players WHERE charinfo LIKE ?', { query }) + if result == 0 then + UniqueFound = true + end + end + return PhoneNumber +end + function QBCore.Player.CreateFingerId() local UniqueFound = false local FingerId = nil @@ -697,4 +687,4 @@ function QBCore.Player.CreateSerialNumber() return SerialNumber end -PaycheckLoop() -- This just starts the paycheck system +PaycheckInterval() -- This starts the paycheck system diff --git a/shared/items.lua b/shared/items.lua index 86687c2..546a0c2 100644 --- a/shared/items.lua +++ b/shared/items.lua @@ -356,6 +356,7 @@ QBShared.Items = { -- Theft and Jewelry ['rolex'] = {['name'] = 'rolex', ['label'] = 'Golden Watch', ['weight'] = 1500, ['type'] = 'item', ['image'] = 'rolex.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'A golden watch seems like the jackpot to me!'}, ['diamond_ring'] = {['name'] = 'diamond_ring', ['label'] = 'Diamond Ring', ['weight'] = 1500, ['type'] = 'item', ['image'] = 'diamond_ring.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'A diamond ring seems like the jackpot to me!'}, + ['diamond'] = {['name'] = 'diamond', ['label'] = 'Diamond', ['weight'] = 1000, ['type'] = 'item', ['image'] = 'diamond.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'A diamond seems like the jackpot to me!'}, ['goldchain'] = {['name'] = 'goldchain', ['label'] = 'Golden Chain', ['weight'] = 1500, ['type'] = 'item', ['image'] = 'goldchain.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'A golden chain seems like the jackpot to me!'}, ['10kgoldchain'] = {['name'] = '10kgoldchain', ['label'] = '10k Gold Chain', ['weight'] = 2000, ['type'] = 'item', ['image'] = '10kgoldchain.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = '10 carat golden chain'}, ['goldbar'] = {['name'] = 'goldbar', ['label'] = 'Gold Bar', ['weight'] = 7000, ['type'] = 'item', ['image'] = 'goldbar.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Looks pretty expensive to me'}, diff --git a/shared/locale.lua b/shared/locale.lua index 20a5ddc..eb7e462 100644 --- a/shared/locale.lua +++ b/shared/locale.lua @@ -135,4 +135,4 @@ function Locale:delete(phraseTarget, prefix) end end end -end +end \ No newline at end of file diff --git a/shared/main.lua b/shared/main.lua index 082a287..cace3c0 100644 --- a/shared/main.lua +++ b/shared/main.lua @@ -1,4 +1,4 @@ -QBShared = {} +QBShared = QBShared or {} local StringCharset = {} local NumberCharset = {} @@ -7,18 +7,18 @@ for i = 48, 57 do NumberCharset[#NumberCharset+1] = string.char(i) end for i = 65, 90 do StringCharset[#StringCharset+1] = string.char(i) end for i = 97, 122 do StringCharset[#StringCharset+1] = string.char(i) end -QBShared.RandomStr = function(length) +function QBShared.RandomStr(length) if length <= 0 then return '' end return QBShared.RandomStr(length - 1) .. StringCharset[math.random(1, #StringCharset)] end -QBShared.RandomInt = function(length) +function QBShared.RandomInt(length) if length <= 0 then return '' end return QBShared.RandomInt(length - 1) .. NumberCharset[math.random(1, #NumberCharset)] end -QBShared.SplitStr = function(str, delimiter) - local result = { } +function QBShared.SplitStr(str, delimiter) + local result = {} local from = 1 local delim_from, delim_to = string.find(str, delimiter, from) while delim_from do @@ -30,18 +30,18 @@ QBShared.SplitStr = function(str, delimiter) return result end -QBShared.Trim = function(value) +function QBShared.Trim(value) if not value then return nil end return (string.gsub(value, '^%s*(.-)%s*$', '%1')) end -QBShared.Round = function(value, numDecimalPlaces) +function QBShared.Round(value, numDecimalPlaces) if not numDecimalPlaces then return math.floor(value + 0.5) end local power = 10 ^ numDecimalPlaces return math.floor((value * power) + 0.5) / (power) end -QBShared.ChangeVehicleExtra = function (vehicle, extra, enable) +function QBShared.ChangeVehicleExtra(vehicle, extra, enable) if DoesExtraExist(vehicle, extra) then if enable then SetVehicleExtra(vehicle, extra, false) @@ -57,16 +57,16 @@ QBShared.ChangeVehicleExtra = function (vehicle, extra, enable) end end -QBShared.SetDefaultVehicleExtras = function (vehicle, config) +function QBShared.SetDefaultVehicleExtras(vehicle, config) -- Clear Extras - for i=1,20 do + for i = 1,20 do if DoesExtraExist(vehicle, i) then SetVehicleExtra(vehicle, i, 1) end end for id, enabled in pairs(config) do - QBShared.ChangeVehicleExtra(vehicle, tonumber(id), true) + QBShared.ChangeVehicleExtra(vehicle, tonumber(id), type(enabled) == 'boolean' and enabled or true) end end @@ -77,9 +77,82 @@ QBShared.StarterItems = { } QBShared.MaleNoGloves = { - [0] = true, [1] = true, [2] = true, [3] = true, [4] = true, [5] = true, [6] = true, [7] = true, [8] = true, [9] = true, [10] = true, [11] = true, [12] = true, [13] = true, [14] = true, [15] = true, [18] = true, [26] = true, [52] = true, [53] = true, [54] = true, [55] = true, [56] = true, [57] = true, [58] = true, [59] = true, [60] = true, [61] = true, [62] = true, [112] = true, [113] = true, [114] = true, [118] = true, [125] = true, [132] = true + [0] = true, + [1] = true, + [2] = true, + [3] = true, + [4] = true, + [5] = true, + [6] = true, + [7] = true, + [8] = true, + [9] = true, + [10] = true, + [11] = true, + [12] = true, + [13] = true, + [14] = true, + [15] = true, + [18] = true, + [26] = true, + [52] = true, + [53] = true, + [54] = true, + [55] = true, + [56] = true, + [57] = true, + [58] = true, + [59] = true, + [60] = true, + [61] = true, + [62] = true, + [112] = true, + [113] = true, + [114] = true, + [118] = true, + [125] = true, + [132] = true } QBShared.FemaleNoGloves = { - [0] = true, [1] = true, [2] = true, [3] = true, [4] = true, [5] = true, [6] = true, [7] = true, [8] = true, [9] = true, [10] = true, [11] = true, [12] = true, [13] = true, [14] = true, [15] = true, [19] = true, [59] = true, [60] = true, [61] = true, [62] = true, [63] = true, [64] = true, [65] = true, [66] = true, [67] = true, [68] = true, [69] = true, [70] = true, [71] = true, [129] = true, [130] = true, [131] = true, [135] = true, [142] = true, [149] = true, [153] = true, [157] = true, [161] = true, [165] = true -} + [0] = true, + [1] = true, + [2] = true, + [3] = true, + [4] = true, + [5] = true, + [6] = true, + [7] = true, + [8] = true, + [9] = true, + [10] = true, + [11] = true, + [12] = true, + [13] = true, + [14] = true, + [15] = true, + [19] = true, + [59] = true, + [60] = true, + [61] = true, + [62] = true, + [63] = true, + [64] = true, + [65] = true, + [66] = true, + [67] = true, + [68] = true, + [69] = true, + [70] = true, + [71] = true, + [129] = true, + [130] = true, + [131] = true, + [135] = true, + [142] = true, + [149] = true, + [153] = true, + [157] = true, + [161] = true, + [165] = true +} \ No newline at end of file diff --git a/shared/vehicles.lua b/shared/vehicles.lua index c0be86a..7c41559 100644 --- a/shared/vehicles.lua +++ b/shared/vehicles.lua @@ -4018,13 +4018,13 @@ QBShared.Vehicles = { ['hash'] = `warrener2`, ['shop'] = 'pdm', }, - -- Boats + -- Boats ['squalo'] = { ['name'] = 'Squalo', ['brand'] = 'Shitzu', ['model'] = 'squalo', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `squalo`, ['shop'] = 'boats', }, @@ -4033,7 +4033,7 @@ QBShared.Vehicles = { ['brand'] = 'Dinka', ['model'] = 'marquis', ['price'] = 40000, - ['category'] = 'sail', + ['category'] = 'boats', ['hash'] = `marquis`, ['shop'] = 'boats', }, @@ -4042,7 +4042,7 @@ QBShared.Vehicles = { ['brand'] = 'Speedophile', ['model'] = 'seashark', ['price'] = 40000, - ['category'] = 'jetski', + ['category'] = 'boats', ['hash'] = `seashark`, ['shop'] = 'boats', }, @@ -4051,7 +4051,7 @@ QBShared.Vehicles = { ['brand'] = 'Speedophile', ['model'] = 'seashark2', ['price'] = 40000, - ['category'] = 'jetski', + ['category'] = 'boats', ['hash'] = `seashark2`, ['shop'] = 'boats', }, @@ -4060,7 +4060,7 @@ QBShared.Vehicles = { ['brand'] = 'Speedophile', ['model'] = 'seashark3', ['price'] = 40000, - ['category'] = 'jetski', + ['category'] = 'boats', ['hash'] = `seashark3`, ['shop'] = 'boats', }, @@ -4069,7 +4069,7 @@ QBShared.Vehicles = { ['brand'] = 'Jetmax', ['model'] = 'jetmax', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `jetmax`, ['shop'] = 'boats', }, @@ -4078,7 +4078,7 @@ QBShared.Vehicles = { ['brand'] = 'Tropic', ['model'] = 'tropic', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `tropic`, ['shop'] = 'boats', }, @@ -4087,7 +4087,7 @@ QBShared.Vehicles = { ['brand'] = 'Tropic', ['model'] = 'tropic2', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `tropic2`, ['shop'] = 'boats', }, @@ -4096,7 +4096,7 @@ QBShared.Vehicles = { ['brand'] = 'Dinghy', ['model'] = 'dinghy', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `dinghy`, ['shop'] = 'boats', }, @@ -4105,7 +4105,7 @@ QBShared.Vehicles = { ['brand'] = 'Dinghy', ['model'] = 'dinghy2', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `dinghy2`, ['shop'] = 'boats', }, @@ -4114,7 +4114,7 @@ QBShared.Vehicles = { ['brand'] = 'Dinghy', ['model'] = 'dinghy3', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `dinghy3`, ['shop'] = 'boats', }, @@ -4123,7 +4123,7 @@ QBShared.Vehicles = { ['brand'] = 'Dinghy', ['model'] = 'dinghy4', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `dinghy4`, ['shop'] = 'boats', }, @@ -4132,7 +4132,7 @@ QBShared.Vehicles = { ['brand'] = 'Suntrap', ['model'] = 'suntrap', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `suntrap`, ['shop'] = 'boats', }, @@ -4141,7 +4141,7 @@ QBShared.Vehicles = { ['brand'] = 'Pegassi', ['model'] = 'speeder', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `speeder`, ['shop'] = 'boats', }, @@ -4150,7 +4150,7 @@ QBShared.Vehicles = { ['brand'] = 'Pegassi', ['model'] = 'speeder2', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `speeder2`, ['shop'] = 'boats', }, @@ -4159,7 +4159,7 @@ QBShared.Vehicles = { ['brand'] = 'Shitzu', ['model'] = 'longfin', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `longfin`, ['shop'] = 'boats', }, @@ -4168,7 +4168,7 @@ QBShared.Vehicles = { ['brand'] = 'Lampadati', ['model'] = 'toro', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `toro`, ['shop'] = 'boats', }, @@ -4177,17 +4177,17 @@ QBShared.Vehicles = { ['brand'] = 'Lampadati', ['model'] = 'toro2', ['price'] = 40000, - ['category'] = 'speed', + ['category'] = 'boats', ['hash'] = `toro2`, ['shop'] = 'boats', }, - -- Helis + -- helicopters ['buzzard2'] = { ['name'] = 'Buzzard', ['brand'] = 'Unknown', ['model'] = 'buzzard2', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `buzzard2`, ['shop'] = 'air', }, @@ -4196,7 +4196,7 @@ QBShared.Vehicles = { ['brand'] = 'Unknown', ['model'] = 'frogger', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `frogger`, ['shop'] = 'air', }, @@ -4205,7 +4205,7 @@ QBShared.Vehicles = { ['brand'] = 'Unknown', ['model'] = 'frogger2', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `frogger2`, ['shop'] = 'air', }, @@ -4214,7 +4214,7 @@ QBShared.Vehicles = { ['brand'] = 'Unknown', ['model'] = 'maverick', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `maverick`, ['shop'] = 'air', }, @@ -4223,7 +4223,7 @@ QBShared.Vehicles = { ['brand'] = 'Buckingham', ['model'] = 'swift', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `swift`, ['shop'] = 'air', }, @@ -4232,7 +4232,7 @@ QBShared.Vehicles = { ['brand'] = 'Buckingham', ['model'] = 'swift2', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `swift2`, ['shop'] = 'air', }, @@ -4241,7 +4241,7 @@ QBShared.Vehicles = { ['brand'] = 'Unknown', ['model'] = 'seasparrow', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `seasparrow`, ['shop'] = 'air', }, @@ -4250,7 +4250,7 @@ QBShared.Vehicles = { ['brand'] = 'Unknown', ['model'] = 'seasparrow2', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `seasparrow2`, ['shop'] = 'air', }, @@ -4259,7 +4259,7 @@ QBShared.Vehicles = { ['brand'] = 'Unknown', ['model'] = 'seasparrow3', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `seasparrow3`, ['shop'] = 'air', }, @@ -4268,7 +4268,7 @@ QBShared.Vehicles = { ['brand'] = 'Buckingham', ['model'] = 'supervolito', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `supervolito`, ['shop'] = 'air', }, @@ -4277,7 +4277,7 @@ QBShared.Vehicles = { ['brand'] = 'Buckingham', ['model'] = 'supervolito2', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `supervolito2`, ['shop'] = 'air', }, @@ -4286,7 +4286,7 @@ QBShared.Vehicles = { ['brand'] = 'Buckingham', ['model'] = 'volatus', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `volatus`, ['shop'] = 'air', }, @@ -4295,7 +4295,7 @@ QBShared.Vehicles = { ['brand'] = 'Nagasaki', ['model'] = 'havok', ['price'] = 52000, - ['category'] = 'helis', + ['category'] = 'helicopters', ['hash'] = `havok`, ['shop'] = 'air', }, @@ -4417,4 +4417,4 @@ QBShared.Vehicles = { ['hash'] = `nimbus`, ['shop'] = 'air', }, -} \ No newline at end of file +}