Merge pull request #43 from ND-Framework/wip-2.0.0

v2
This commit is contained in:
Andy
2023-12-10 19:00:39 +01:00
committed by GitHub
28 changed files with 4744 additions and 1035 deletions

2
.gitignore vendored
View File

@@ -1,2 +0,0 @@
config_server.lua

View File

@@ -1,28 +1,24 @@
<p align="center">
<img src="https://user-images.githubusercontent.com/86536434/193703880-5cb7deef-af37-42cc-8df2-b13332afee67.png" />
<a href="https://ndcore.dev" target="_blank">
<img src="https://user-images.githubusercontent.com/86536434/193703880-5cb7deef-af37-42cc-8df2-b13332afee67.png" />
</a>
</p>
<p align='center'><b><a href="https://discord.gg/andys-development-857672921912836116">Discord</a></b>
<p align="center"><b><a href="https://ndcore.dev/">Documentation</a></b>
<p align='center'><b><a href="https://ndfw.gitbook.io/nd-framework/">Documentation</a></b>
<div align="center">
<a href="https://discord.gg/Z9Mxu72zZ6" target="_blank">
<img src="https://discordapp.com/api/guilds/857672921912836116/widget.png?style=banner2" alt="Andyyy Development Server" height="60px" />
</a>
</div>
## Dependency:
[oxmysql](https://github.com/overextended/oxmysql/releases/download/v2.4.0/oxmysql.zip)
## Dependencies:
* [oxmysql](https://github.com/overextended/oxmysql/releases)
* [ox_lib](https://github.com/overextended/ox_lib/releases)
## Addons
* [Ox Inventory](https://github.com/overextended/ox_inventory/releases)
* [Character Selection](https://github.com/ND-Framework/ND_CharacterSelection)
* [No Characters (use framework without character selection)](https://github.com/ND-Framework/ND_NoCharacters)
* [Vehicle System](https://github.com/ND-Framework/ND_VehicleSystem)
* [Dealerships](https://github.com/ND-Framework/ND_Dealership)
* [Fuel (with nozzle and hose)](https://github.com/ND-Framework/ND_Fuel)
* [Nitro](https://github.com/ND-Framework/ND_Nitro)
* [Shot Spotter](https://github.com/ND-Framework/ND_ShotSpotter)
* [Banking](https://github.com/ND-Framework/ND_Banking)
* [Doorlocks](https://github.com/ND-Framework/ND_Doorlocks)
* [Properties](https://github.com/ND-Framework/ND_Properties)
* [Appearance Shops](https://github.com/ND-Framework/ND_AppearanceShops)
# Need support?
[![Discord](https://discordapp.com/api/guilds/857672921912836116/widget.png?style=banner3)](https://discord.gg/Z9Mxu72zZ6)
## v2 Addons
* [Character selection](https://github.com/ND-Framework/ND_Characters/tree/wip-v2)
* [Banking](https://github.com/ND-Framework/ND_Banking/tree/wip-v2)
* [Appearance shops](https://github.com/ND-Framework/ND_AppearanceShops/tree/wip-v2)
* [Dealership](https://github.com/ND-Framework/ND_Dealership/tree/v2)
* [Inventory](https://github.com/overextended/ox_inventory/pull/1403)

55
client/death.lua Normal file
View File

@@ -0,0 +1,55 @@
local ambulance
local usingAmbulance = false
local alreadyEliminated = false
NDCore.isResourceStarted("ND_Ambulance", function(started)
usingAmbulance = started
if not usingAmbulance then return end
ambulance = exports["ND_Ambulance"]
end)
local function PlayerEliminated(deathCause, killerServerId, killerClientId)
if alreadyEliminated then return end
alreadyEliminated = true
local info = {
deathCause = deathCause,
killerServerId = killerServerId,
killerClientId = killerClientId,
damagedBones = usingAmbulance and ambulance:getBodyDamage() or {}
}
TriggerEvent("ND:playerEliminated", info)
TriggerServerEvent("ND:playerEliminated", info)
Wait(1000)
alreadyEliminated = false
end
AddEventHandler("gameEventTriggered", function(name, args)
if name ~= "CEventNetworkEntityDamage" then return end
local victim = args[1]
if not IsPedAPlayer(victim) or NetworkGetPlayerIndexFromPed(victim) ~= cache.playerId then return end
if not IsPedDeadOrDying(victim, true) and GetEntityHealth(victim) > 0 then
local hit, bone = GetPedLastDamageBone(victim)
if hit and usingAmbulance then
local damageWeapon = ambulance:getLastDamagingWeapon(victim)
return damageWeapon and ambulance:updateBodyDamage(bone, damageWeapon)
end
return
end
local killerEntity, deathCause = GetPedSourceOfDeath(cache.ped), GetPedCauseOfDeath(cache.ped)
local killerClientId = NetworkGetPlayerIndexFromPed(killerEntity)
if killerEntity ~= cache.ped and killerClientId and NetworkIsPlayerActive(killerClientId) then
return PlayerEliminated(deathCause, GetPlayerServerId(killerClientId), killerClientId)
end
PlayerEliminated(deathCause)
end)
local firstSpawn = true
exports.spawnmanager:setAutoSpawnCallback(function()
if firstSpawn then
firstSpawn = false
return exports.spawnmanager:spawnPlayer() and exports.spawnmanager:setAutoSpawn(false)
end
end)

View File

@@ -1,24 +1,26 @@
RegisterNetEvent("ND:returnCharacters", function(characters)
NDCore.Characters = characters
end)
-- updates the money on the client.
RegisterNetEvent("ND:updateMoney", function(cash, bank)
NDCore.SelectedCharacter.cash = cash
NDCore.SelectedCharacter.bank = bank
if not NDCore.player then return end
NDCore.player.cash = cash
NDCore.player.bank = bank
end)
-- Sets main character.
RegisterNetEvent("ND:setCharacter", function(character)
NDCore.SelectedCharacter = character
RegisterNetEvent("ND:characterLoaded", function(character)
NDCore.player = character
end)
-- Update main character info.
RegisterNetEvent("ND:updateCharacter", function(character)
NDCore.SelectedCharacter = character
NDCore.player = character
end)
-- Updates last lcoation.
RegisterNetEvent("ND:updateLastLocation", function(location)
NDCore.SelectedCharacter.lastLocation = location
if not NDCore.player then return end
NDCore.player.lastLocation = location
end)
RegisterNetEvent("ND:revivePlayer", function()
NDCore.revivePlayer(true)
end)

View File

@@ -1,19 +1,14 @@
function GetCoreObject()
return NDCore
function NDCore.getPlayer()
return NDCore.player
end
function NDCore.Functions.GetSelectedCharacter()
return NDCore.SelectedCharacter
function NDCore.getCharacters()
return NDCore.characters
end
function NDCore.Functions.GetCharacters()
return NDCore.Characters
end
function NDCore.Functions.GetPlayersFromCoords(distance, coords)
function NDCore.getPlayersFromCoords(distance, coords)
if coords then
coords = type(coords) == 'table' and vec3(coords.x, coords.y, coords.z) or coords
coords = type(coords) == "table" and vec3(coords.x, coords.y, coords.z) or coords
else
coords = GetEntityCoords(PlayerPedId())
end
@@ -31,50 +26,60 @@ function NDCore.Functions.GetPlayersFromCoords(distance, coords)
return closePlayers
end
-- Callbacks are licensed under LGPL v3.0
-- <https://github.com/overextended/ox_lib>
NDCore.callback = {}
local events = {}
RegisterNetEvent("ND:callbacks", function(key, ...)
local cb = events[key]
return cb and cb(...)
end)
local function triggerCallback(_, name, cb, ...)
local key = ("%s:%s"):format(name, math.random(0, 100000))
TriggerServerEvent(("ND:%s_cb"):format(name), key, ...)
local promise = not cb and promise.new()
events[key] = function(response, ...)
response = { response, ... }
events[key] = nil
if promise then
return promise:resolve(response)
end
if cb then
cb(table.unpack(response))
function NDCore.revivePlayer(reset, keepDead)
local usingAmbulance = GetResourceState("ND_Ambulance") == "started"
if not keepDead then
LocalPlayer.state.dead = false
if usingAmbulance then
local state = Player(cache.serverId).state
state:set("isDead", false, true)
end
end
end
if promise then
return table.unpack(Citizen.Await(promise))
end
local veh = GetVehiclePedIsIn(cache.ped)
local seat = cache.seat
local coords = GetEntityCoords(cache.ped)
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, GetEntityHeading(cache.ped), true, true, false)
local ped = PlayerPedId()
if cache.ped ~= ped then
DeleteEntity(cache.ped)
ClearAreaOfPeds(coords.x, coords.y, coords.z, 0.2, false)
end
SetEntityInvincible(ped, false)
FreezeEntityPosition(ped, false)
SetEntityVisible(ped, true)
SetEveryoneIgnorePlayer(ped, false)
SetPedCanBeTargetted(ped, true)
SetEntityCanBeDamaged(ped, true)
SetBlockingOfNonTemporaryEvents(ped, false)
SetPedCanRagdollFromPlayerImpact(ped, true)
ClearPedTasksImmediately(ped)
if veh and veh ~= 0 then
SetPedIntoVehicle(ped, veh, seat)
end
if reset and GetPedMovementClipset(ped) == `move_m@injured` then
ClearEntityLastDamageEntity(ped)
SetPedMoveRateOverride(ped, 1.0)
ResetPedMovementClipset(ped, 0)
end
if reset and usingAmbulance then
exports["ND_Ambulance"]:resetBodyDamage()
end
end
setmetatable(NDCore.callback, {
__call = triggerCallback
})
function NDCore.callback.await(name, ...)
return triggerCallback(nil, name, false, ...)
function NDCore.notify(...)
if GetResourceState("ModernHUD") == "started" then
exports["ModernHUD"]:notify(...)
elseif GetResourceState("ox_lib") == "started" then
lib.notify(...)
end
end
function NDCore.callback.register(name, callback)
RegisterNetEvent(("ND:%s_cb"):format(name), function(key, ...)
TriggerServerEvent("ND:callbacks", key, callback(...))
end)
for name, func in pairs(NDCore) do
if type(func) == "function" then
exports(name, func)
end
end

View File

@@ -1,54 +1,69 @@
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
NDCore = {}
NDCore.SelectedCharacter = nil
NDCore.Characters = {}
NDCore.Functions = {}
NDCore.Config = config
-- discord rich precense will show on a users profile.
if config.enableRichPresence then
Citizen.CreateThread(function()
while true do
if NDCore.SelectedCharacter then
SetDiscordAppId(config.appId)
SetRichPresence(" Playing : " .. config.serverName .. " as " .. NDCore.SelectedCharacter.firstName .. " " .. NDCore.SelectedCharacter.lastName)
SetDiscordRichPresenceAsset(config.largeLogo)
SetDiscordRichPresenceAssetText("Playing: " .. config.serverName)
SetDiscordRichPresenceAssetSmall(config.smallLogo)
SetDiscordRichPresenceAssetSmallText("Playing as: " .. NDCore.SelectedCharacter.firstName .. " " .. NDCore.SelectedCharacter.lastName)
SetDiscordRichPresenceAction(0, config.firstButtonName, config.firstButtonLink)
SetDiscordRichPresenceAction(1, config.secondButtonName, config.secondButtonLink)
end
Citizen.Wait(config.updateIntervall * 1000)
Config = {
serverName = GetConvar("core:serverName", "Unconfigured ND-Core Server"),
discordInvite = GetConvar("core:discordInvite", "https://discord.gg/Z9Mxu72zZ6"),
discordAppId = GetConvar("core:discordAppId", "858146067018416128"),
discordAsset = GetConvar("core:discordAsset", "andyyy"),
discordAssetSmall = GetConvar("core:discordAssetSmall", "andyyy"),
discordActionText = GetConvar("core:discordActionText", "DISCORD"),
discordActionLink = GetConvar("discordActionLink", "https://discord.gg/Z9Mxu72zZ6"),
discordActionText2 = GetConvar("core:discordActionText2", "STORE"),
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30),
disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1,
useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1,
groups = json.decode(GetConvar("core:groups", "[]")),
compatibility = json.decode(GetConvar("core:compatibility", "[]"))
}
-- Discord rich presence.
CreateThread(function()
SetDiscordAppId(Config.discordAppId)
SetDiscordRichPresenceAsset(Config.discordAsset)
SetDiscordRichPresenceAssetSmall(Config.discordAssetSmall)
SetDiscordRichPresenceAction(0, Config.discordActionText, Config.discordActionLink)
SetDiscordRichPresenceAction(1, Config.discordActionText2, Config.discordActionLink2)
local presenceText = ("Playing: %s"):format(Config.serverName)
while true do
if NDCore.player then
local presence = ("Playing: %s as %s %s"):format(Config.serverName, NDCore.player.firstname, NDCore.player.lastname)
local presenceTextSmall = ("Playing as: %s %s"):format(NDCore.player.firstname, NDCore.player.lastname)
SetRichPresence(presence)
SetDiscordRichPresenceAssetText(presenceText)
SetDiscordRichPresenceAssetSmallText(presenceTextSmall)
end
end)
end
-- show server name, first name, last name, and the amount of money the character has in the pause menu.
if config.customPauseMenu then
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
if NDCore.SelectedCharacter then
if IsPauseMenuActive() then
BeginScaleformMovieMethodOnFrontendHeader("SET_HEADING_DETAILS")
AddTextEntry("FE_THDR_GTAO", config.serverName)
ScaleformMovieMethodAddParamPlayerNameString(NDCore.SelectedCharacter.firstName .. " " .. NDCore.SelectedCharacter.lastName)
PushScaleformMovieFunctionParameterString("Cash: $" .. tostring(NDCore.SelectedCharacter.cash))
PushScaleformMovieFunctionParameterString("Bank: $" .. tostring(NDCore.SelectedCharacter.bank))
EndScaleformMovieMethod()
end
end
end
end)
end
-- Enables pvp if it's selected in the config.
AddEventHandler("playerSpawned", function()
if config.enablePVP then
SetCanAttackFriendly(PlayerPedId(), true, false)
NetworkSetFriendlyFireOption(true)
Wait(60000)
end
print("^0This framework is created by ^5Andyyy#7666 ^0for support you can join the ^5discord: ^0https://discord.gg/Z9Mxu72zZ6")
end)
end)
-- Pause menu information.
CreateThread(function()
AddTextEntry("FE_THDR_GTAO", Config.serverName)
local sleep = 500
while true do
Wait(sleep)
if NDCore.player and IsPauseMenuActive() then
sleep = 0
BeginScaleformMovieMethodOnFrontendHeader("SET_HEADING_DETAILS")
ScaleformMovieMethodAddParamPlayerNameString(("%s %s"):format(NDCore.player.firstname, NDCore.player.lastname))
ScaleformMovieMethodAddParamTextureNameString(("Cash: $%d"):format(NDCore.player.cash))
ScaleformMovieMethodAddParamTextureNameString(("Bank: $%d"):format(NDCore.player.bank))
EndScaleformMovieMethod()
elseif sleep == 0 then
sleep = 500
end
end
end)
AddEventHandler("playerSpawned", function()
print("^0ND Framework support discord: ^5https://discord.gg/Z9Mxu72zZ6")
SetCanAttackFriendly(PlayerPedId(), true, false)
NetworkSetFriendlyFireOption(true)
end)
AddEventHandler("onResourceStart", function(resourceName)
if resourceName ~= GetCurrentResourceName() then return end
SetCanAttackFriendly(PlayerPedId(), true, false)
NetworkSetFriendlyFireOption(true)
end)

227
client/peds.lua Normal file
View File

@@ -0,0 +1,227 @@
local target, ox_target
local locations = {}
local pedBlips = {}
local clothingComponents = {
face = 0,
mask = 1,
hair = 2,
torso = 3,
leg = 4,
bag = 5,
shoes = 6,
accessory = 7,
undershirt = 8,
kevlar = 9,
badge = 10,
torso2 = 11
}
local clothingProps = {
hat = 0,
glasses = 1,
ears = 2,
watch = 6,
bracelets = 7
}
NDCore.isResourceStarted("ox_target", function(started)
target = started
if not target then return end
ox_target = exports.ox_target
end)
local function configPed(ped)
SetPedCanBeTargetted(ped, false)
SetEntityCanBeDamaged(ped, false)
SetBlockingOfNonTemporaryEvents(ped, true)
SetPedCanRagdollFromPlayerImpact(ped, false)
SetPedResetFlag(ped, 249, true)
SetPedConfigFlag(ped, 185, true)
SetPedConfigFlag(ped, 108, true)
SetPedConfigFlag(ped, 208, true)
SetPedCanRagdoll(ped, false)
end
local function setClothing(ped, clothing)
if not clothing then return end
for component, clothingInfo in pairs(clothing) do
if clothingComponents[component] then
SetPedComponentVariation(ped, clothingComponents[component], clothingInfo.drawable, clothingInfo.texture, 0)
elseif clothingProps[component] then
SetPedPropIndex(ped, clothingProps[component], clothingInfo.drawable, clothingInfo.texture, true)
end
end
end
local function groupCheck(groups, playerGroups)
if not groups or #groups == 0 then return true end
for i=1, #groups do
if playerGroups and playerGroups[groups[i]] then
return true
end
end
end
local function createBlip(coords, info, i)
if not info then return end
local groups = info.groups
local playerGroups = NDCore.player?.groups
local key = i or #pedBlips+1
pedBlips[key] = {
groups = groups,
info = info,
coords = coords
}
if not groupCheck(groups, playerGroups) then return end
local blip = AddBlipForCoord(coords.x, coords.y, coords.z)
SetBlipSprite(blip, info.sprite or 280)
SetBlipScale(blip, info.scale or 0.8)
SetBlipColour(blip, info.color or 3)
SetBlipAsShortRange(blip, true)
if info.label then
BeginTextCommandSetBlipName("STRING")
AddTextComponentString(info.label)
EndTextCommandSetBlipName(blip)
end
pedBlips[key].blip = blip
return blip
end
local function updateBlips(playerGroups)
for i=1, #pedBlips do
local blipInfo = pedBlips[i]
local access = groupCheck(blipInfo.groups, playerGroups)
if access and not blipInfo.blip or not DoesBlipExist(blipInfo.blip) then
createBlip(blipInfo.coords, blipInfo.info, i)
elseif not access and blipInfo.blip and DoesBlipExist(blipInfo.blip) then
RemoveBlip(blipInfo.blip)
end
end
end
function NDCore.createAiPed(info)
local ped
local model = type(info.model) == "string" and GetHashKey(info.model) or info.model
local blipInfo = info.blip
local anim = info.anim
local clothing = info.clothing
local coords = info.coords
local options = info.options
local blip = createBlip(coords, blipInfo)
local point = lib.points.new({
coords = vec3(coords.x, coords.y, coords.z),
distance = info.distance or 25.0
})
local id = #locations+1
locations[id] = {
point = point,
blip = blip,
options = info.options,
resource = info.resource or GetInvokingResource()
}
function point:onEnter()
local found, ground = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, true)
lib.requestModel(model)
ped = CreatePed(4, model, coords.x, coords.y, found and ground or coords.z, coords.w or coords.h or info.heading, false, false)
local time = GetCloudTimeAsInt()
while not DoesEntityExist(ped) and time-GetCloudTimeAsInt() < 5 do
Wait(100)
end
configPed(ped)
setClothing(ped, clothing)
locations[id].ped = ped
if anim and anim.dict and anim.clip then
lib.requestAnimDict(anim.dict)
TaskPlayAnim(ped, anim.dict, anim.clip, 2.0, 8.0, -1, 1, 0, 0, 0, 0)
end
if target and options then
Wait(500)
ox_target:addLocalEntity({ped}, options)
end
if blip and blipInfo and blipInfo.showWhenNear and DoesBlipExist(blip) then
SetBlipAlpha(blip, 255)
end
end
function point:onExit()
if blip and blipInfo and blipInfo.showWhenNear and DoesBlipExist(blip) then
SetBlipAlpha(blip, 0)
end
if ped and DoesEntityExist(ped) then
if target and options then
ox_target:removeLocalEntity({ped})
end
Wait(500)
DeleteEntity(ped)
end
end
return id
end
function NDCore.removeAiPed(id)
local info = locations[id]
if not info then return end
local ped = info.ped
local blip = info.blip
info.point:remove()
locations[id] = nil
if blip and DoesBlipExist(blip) then
RemoveBlip(blip)
end
if ped and DoesEntityExist(ped) then
if info.options then
ox_target:removeLocalEntity({ped})
end
DeleteEntity(ped)
end
end
RegisterNetEvent("ND:updateCharacter", function(character)
Wait(3000)
if character.id ~= NDCore.player?.id then return end
updateBlips(character.groups)
end)
RegisterNetEvent("ND:characterLoaded", function(character)
Wait(3000)
if character.id ~= NDCore.player?.id then return end
updateBlips(character.groups)
end)
AddEventHandler("onResourceStop", function(name)
if name == GetCurrentResourceName() then
for i, _ in ipairs(locations) do
NDCore.removeAiPed(i)
end
else
for i, v in ipairs(locations) do
if v.resource == name then
NDCore.removeAiPed(i)
end
end
end
end)
RegisterCommand("getclothing", function(source, args, rawCommand)
local info = ""
for k, v in pairs(clothingComponents) do
info = ("%s\n%s = {\n drawable = %s,\n texture = %s\n},"):format(info, k, GetPedDrawableVariation(cache.ped, v), GetPedTextureVariation(cache.ped, v))
end
for k, v in pairs(clothingProps) do
info = ("%s\n%s = {\n drawable = %s,\n texture = %s\n},"):format(info, k, GetPedPropIndex(cache.ped, v), GetPedPropTextureIndex(cache.ped, v))
end
lib.setClipboard(info)
end, false)

243
client/vehicle/data.lua Normal file
View File

@@ -0,0 +1,243 @@
return {
{
garageType = "land",
groups = {"sahp", "lspd", "bcso"},
ped = vector4(452.83, -1027.79, 28.54, 2.49),
vehicleSpawns = {
vector4(446.19, -1025.47, 28.24, 185.96),
vector4(438.75, -1026.09, 28.38, 184.29),
vector4(434.98, -1026.61, 28.46, 185.99),
vector4(431.26, -1027.31, 28.53, 185.59),
vector4(427.52, -1026.84, 28.58, 184.25)
}
},
{
garageType = "land",
impound = true,
ped = vector4(407.99, -1624.74, 29.29, 229.93),
vehicleSpawns = {
vector4(396.34, -1644.28, 28.86, 319.10),
vector4(398.56, -1646.42, 28.8616, 319.18),
vector4(400.68, -1648.86, 28.86, 140.78),
vector4(403.32, -1650.54, 28.86, 319.92),
vector4(403.21, -1650.68, 28.86, 139.40)
}
},
{
garageType = "plane",
ped = vector4(-941.05, -2966.04, 13.95, 136.06),
vehicleSpawns = {
vector4(-974.98, -2977.90, 14.55, 59.91)
}
},
{
garageType = "heli",
ped = vector4(-731.00, -1394.54, 5.00, 245.52),
vehicleSpawns = {
vector4(-746.01, -1469.39, 5.68, 139.27),
vector4(-725.26, -1444.72, 5.68, 139.58)
}
},
{
garageType = "plane",
ped = vector4(1742.77, 3298.25, 41.22, 132.91),
vehicleSpawns = {
vector4(1734.26, 3250.98, 41.96, 81.11),
vector4(1729.32, 3270.69, 41.74, 145.39)
}
},
{
garageType = "plane",
ped = vector4(-1241.26, -3391.48, 13.94, 35.03),
vehicleSpawns = {
vector4(-1254.71, -3388.15, 14.54, 330.04),
vector4(-1270.36, -3378.80, 14.54, 330.20),
vector4(-1286.05, -3369.44, 14.54, 329.99)
}
},
{
garageType = "plane",
ped = vector4(-1621.10, -3151.63, 13.99, 41.26),
vehicleSpawns = {
vector4(-1634.04, -3147.84, 14.60, 330.28),
vector4(-1649.75, -3138.34, 14.60, 329.20),
vector4(-1665.42, -3129.26, 14.60, 330.10)
}
},
{
garageType = "heli",
ped = vector4(-1121.87, -2839.88, 13.95, 150.58),
vehicleSpawns = {
vector4(-1178.71, -2846.51, 14.62, 150.16),
vector4(-1146.40, -2865.22, 14.62, 151.08),
vector4(-1112.85, -2884.66, 14.62, 149.70)
}
},
{
garageType = "water",
ped = vector4(-831.03, -1359.53, 5.00, 299.80),
vehicleSpawns = {
vector4(-846.16, -1362.07, 0.39, 110.54),
vector4(-842.53, -1372.00, 0.39, 111.54),
vector4(-849.29, -1353.26, 0.38, 109.53),
vector4(-852.26, -1345.09, 0.41, 110.52),
vector4(-855.49, -1336.60, 0.40, 107.43),
vector4(-858.63, -1328.30, 0.40, 109.82),
vector4(-839.35, -1380.30, 0.39, 109.27),
vector4(-836.38, -1388.93, 0.41, 110.11),
vector4(-833.29, -1397.32, 0.40, 110.80),
vector4(-830.34, -1405.65, 0.37, 110.16)
}
},
{
garageType = "land",
ped = vector4(-280.32, -888.42, 31.32, 250.68),
vehicleSpawns = {
vector4(-282.36, -915.11, 30.38, 68.81),
vector4(-284.27, -918.37, 30.38, 70.58),
vector4(-285.44, -921.82, 30.38, 250.22),
vector4(-285.70, -887.62, 30.38, 167.04),
vector4(-292.70, -885.93, 30.38, 167.09),
vector4(-285.76, -887.47, 30.38, 169.59),
vector4(-300.44, -885.14, 30.38, 347.05),
vector4(-303.59, -883.71, 30.38, 167.69),
vector4(-309.38, -896.88, 30.38, 167.15),
vector4(-312.98, -896.30, 30.38, 166.46),
vector4(-316.68, -896.26, 30.37, 347.94),
vector4(-311.06, -881.94, 30.38, 168.21),
vector4(-314.78, -881.86, 30.37, 348.45)
}
},
{
garageType = "land",
ped = vector4(597.53, 91.08, 93.13, 250.54),
vehicleSpawns = {
vector4(598.53, 98.37, 92.27, 69.47),
vector4(599.79, 102.00, 92.27, 249.38),
vector4(608.21, 103.90, 92.18, 248.98),
vector4(600.60, 111.38, 92.27, 73.07),
vector4(609.91, 107.59, 92.23, 68.77),
vector4(601.36, 114.99, 92.27, 250.19),
vector4(611.07, 111.39, 92.29, 249.69),
vector4(603.52, 118.52, 92.26, 67.62),
vector4(612.74, 114.94, 92.28, 69.01),
vector4(604.00, 122.58, 92.27, 249.76),
vector4(613.78, 118.83, 92.29, 248.82),
vector4(622.43, 115.48, 91.99, 70.39),
vector4(628.56, 110.25, 91.47, 249.30),
vector4(620.67, 111.89, 92.03, 250.24),
vector4(618.41, 104.51, 91.97, 72.22),
vector4(624.87, 99.25, 91.36, 63.32),
vector4(616.59, 100.82, 91.97, 249.00)
}
},
{
garageType = "land",
ped = vector4(100.64, -1072.88, 29.37, 341.55),
vehicleSpawns = {
vector4(106.13, -1063.24, 28.51, 66.86),
vector4(107.82, -1059.73, 28.51, 66.99),
vector4(112.39, -1049.71, 28.52, 67.40),
vector4(110.76, -1053.09, 28.51, 68.13),
vector4(109.06, -1056.38, 28.51, 66.45),
vector4(117.53, -1081.03, 28.50, 181.40),
vector4(119.09, -1069.44, 28.50, 181.12),
vector4(121.22, -1081.42, 28.50, 0.57),
vector4(122.32, -1070.46, 28.50, 0.67),
vector4(125.62, -1069.54, 28.50, 179.25),
vector4(124.83, -1081.17, 28.50, 180.37),
vector4(128.62, -1081.71, 28.50, 1.36),
vector4(128.93, -1070.62, 28.50, 359.89),
vector4(132.24, -1069.35, 28.50, 181.54),
vector4(132.31, -1081.45, 28.50, 180.86),
vector4(135.93, -1081.35, 28.50, 358.82),
vector4(135.64, -1070.75, 28.50, 0.43),
vector4(139.75, -1081.91, 28.50, 182.18),
vector4(138.86, -1070.37, 28.50, 181.90),
vector4(143.55, -1081.41, 28.50, 0.89),
vector4(147.13, -1081.48, 28.50, 179.02),
vector4(150.95, -1081.49, 28.50, 358.79),
vector4(154.64, -1081.32, 28.50, 179.51),
vector4(158.38, -1082.03, 28.50, 1.57),
vector4(162.10, -1081.32, 28.50, 179.88)
}
},
{
garageType = "land",
ped = vector4(214.84, -806.24, 30.81, 342.23),
vehicleSpawns = {
vector4(251.26, -774.63, 29.98, 67.34),
vector4(245.36, -772.34, 30.02, 66.81),
vector4(219.19, -765.93, 30.14, 249.78),
vector4(228.11, -768.94, 30.10, 249.66),
vector4(233.68, -771.18, 30.07, 249.82),
vector4(244.01, -775.06, 29.99, 249.41),
vector4(249.53, -777.07, 29.95, 251.09),
vector4(248.74, -779.52, 29.92, 69.14),
vector4(243.64, -777.63, 29.96, 69.23),
vector4(233.47, -773.91, 30.05, 69.88),
vector4(218.62, -768.58, 30.14, 69.81),
vector4(217.02, -770.83, 30.16, 248.11),
vector4(226.09, -773.95, 30.09, 249.30),
vector4(231.82, -776.14, 30.04, 249.17),
vector4(242.05, -779.91, 29.93, 246.99),
vector4(247.72, -782.02, 29.88, 251.04),
vector4(216.19, -773.65, 30.16, 67.90),
vector4(225.82, -776.85, 30.08, 65.64),
vector4(230.77, -778.89, 30.03, 69.40),
vector4(241.72, -782.84, 29.90, 69.13),
vector4(247.12, -784.93, 29.84, 67.56),
vector4(246.23, -787.13, 29.82, 247.56),
vector4(239.40, -784.68, 29.90, 248.06),
vector4(230.42, -781.41, 30.01, 247.48),
vector4(224.14, -779.01, 30.07, 247.39),
vector4(215.32, -775.95, 30.17, 248.99),
vector4(243.81, -792.19, 29.77, 246.23),
vector4(238.08, -789.95, 29.85, 250.62),
vector4(228.14, -786.30, 30.01, 249.07),
vector4(221.98, -783.92, 30.08, 248.25),
vector4(213.41, -781.01, 30.19, 249.21),
vector4(214.77, -778.70, 30.17, 67.86),
vector4(224.02, -781.91, 30.07, 68.82),
vector4(229.34, -784.05, 30.01, 66.57),
vector4(239.87, -787.83, 29.86, 69.37),
vector4(245.18, -789.87, 29.79, 68.00),
vector4(241.85, -797.20, 29.72, 245.51),
vector4(236.57, -795.07, 29.82, 247.54),
vector4(226.12, -791.34, 29.99, 247.84),
vector4(220.61, -789.19, 30.08, 248.09),
vector4(212.00, -786.22, 30.21, 248.93),
vector4(212.91, -783.71, 30.19, 68.61),
vector4(222.11, -786.89, 30.07, 70.10),
vector4(227.84, -789.16, 29.99, 67.55),
vector4(237.73, -792.73, 29.82, 69.40),
vector4(243.54, -794.88, 29.73, 68.74),
vector4(237.03, -812.64, 29.59, 243.63),
vector4(207.66, -798.70, 30.29, 70.06),
vector4(216.56, -801.92, 30.10, 70.31),
vector4(222.28, -804.17, 29.98, 69.97),
vector4(232.41, -807.95, 29.74, 69.42),
vector4(238.16, -810.17, 29.60, 68.95),
vector4(238.32, -807.43, 29.63, 248.28),
vector4(233.32, -805.44, 29.75, 248.13),
vector4(222.46, -801.41, 29.98, 248.50),
vector4(216.85, -799.11, 30.10, 248.54),
vector4(207.60, -795.90, 30.29, 248.64),
vector4(209.28, -793.74, 30.25, 69.82),
vector4(218.31, -796.92, 30.08, 68.55),
vector4(224.50, -799.34, 29.96, 68.63),
vector4(234.40, -803.02, 29.76, 66.67),
vector4(240.09, -805.27, 29.64, 69.02),
vector4(240.53, -802.44, 29.67, 248.95),
vector4(234.46, -800.13, 29.79, 249.40),
vector4(224.30, -796.51, 29.98, 248.31),
vector4(218.23, -794.00, 30.09, 249.09),
vector4(209.84, -791.10, 30.24, 251.56),
vector4(211.09, -788.68, 30.22, 69.53),
vector4(220.17, -791.73, 30.06, 70.56),
vector4(225.84, -794.12, 29.98, 67.53),
vector4(236.38, -797.85, 29.79, 69.39),
vector4(216.56, -801.90, 30.79, 69.39)
}
},
}

291
client/vehicle/garages.lua Normal file
View File

@@ -0,0 +1,291 @@
local locations = require "client.vehicle.data"
local sprite = {
["water"] = 356,
["heli"] = 360,
["plane"] = 359,
["land"] = 357
}
local garageTypes = {
["water"] = 14,
["heli"] = 15,
["plane"] = 16
}
local clothing = {
{
face = {
drawable = 1,
texture = 1
},
undershirt = {
drawable = 0,
texture = 0
},
torso = {
drawable = 1,
texture = 1
},
leg = {
drawable = 0,
texture = 0
},
glasses = {
drawable = 1,
texture = 0
},
hat = {
drawable = -1,
texture = -1
},
},
{
leg = {
drawable = 0,
texture = 1
},
undershirt = {
drawable = 0,
texture = 0
},
face = {
drawable = 0,
texture = 0
},
torso = {
drawable = 0,
texture = 2
},
glasses = {
drawable = -1,
texture = -1
},
hat = {
drawable = 0,
texture = 0
},
},
{
face = {
drawable = 0,
texture = 2
},
undershirt = {
drawable = 0,
texture = 0
},
torso = {
drawable = 1,
texture = 2
},
leg = {
drawable = 0,
texture = 0
},
hat = {
drawable = -1,
texture = -1
},
glasses = {
drawable = -1,
texture = -1
},
}
}
local function getClosestOwnedVehicle()
local coords = GetEntityCoords(cache.ped)
local vehicles = lib.getNearbyVehicles(coords, 50.0, true)
local nearestVeh = {}
local function setNearestVehicle(veh)
local state = Entity(veh.vehicle).state
if not state.owner or state.owner ~= NDCore.player?.id then return end
local nearestDist = nearestVeh.dist
local dist = #(coords-veh.coords)
if not nearestDist or dist < nearestDist then
nearestVeh.dist = dist
nearestVeh.coords = veh.coords
nearestVeh.entity = veh.vehicle
end
end
for i=1, #vehicles do
setNearestVehicle(vehicles[i])
end
return nearestVeh.entity, nearestVeh.coords, nearestVeh.dist
end
local function parkVehicle(veh)
if not veh or not DoesEntityExist(veh) then
return NDCore.notify({
title = "Garage",
description = "No owned vehicle found nearby.",
type = "error",
position = "bottom",
duration = 3000
})
end
if GetPedInVehicleSeat(veh, -1) ~= 0 then
NDCore.notify({
title = "Garage",
description = "Player in vehicle!",
type = "error",
position = "bottom",
duration = 3000
})
return
end
local properties = lib.getVehicleProperties(veh)
properties.class = GetVehicleClass(veh)
TriggerServerEvent("ND_Vehicles:storeVehicle", VehToNet(veh))
end
local function isVehicleAvailable(vehicle, garageType, impound)
local class = vehicle.properties.class
local available = vehicle.available and not impound or vehicle.impounded and impound
if available and not garageTypes[garageType] then return true end
for garType, garClass in pairs(garageTypes) do
if available and garType == garageType and garClass == class then
return true
end
end
end
local function getEngineStatus(health)
if health > 950 then
return "Perfect"
elseif health > 750 then
return "Good"
elseif health > 500 then
return "Bad"
end
return "Very bad"
end
local function createMenuOptions(vehicle, vehicleSpawns)
local props = vehicle.properties
local makeName = GetLabelText(GetMakeNameFromVehicleModel(props.model))
local modelName = GetLabelText(GetDisplayNameFromVehicleModel(props.model))
local metadata = {}
if not makeName or makeName == "NULL" then
makeName = ""
else
metadata[#metadata+1] = {label = "Make", value = makeName}
makeName = makeName .. " "
end
if not modelName or modelName == "NULL" then
modelName = ""
else
metadata[#metadata+1] = {label = "Model", value = modelName}
end
if props?.plate then
metadata[#metadata+1] = {label = "Plate", value = props.plate}
end
if props?.engineHealth then
metadata[#metadata+1] = {
label = "Engine status",
value = getEngineStatus(props.engineHealth),
progress = props.engineHealth/10,
colorScheme = "blue"
}
end
if props?.fuelLevel then
metadata[#metadata+1] = {
label = "Fuel",
value = ("%d%s"):format(props.fuelLevel, "%"),
progress = props.fuelLevel,
colorScheme = "yellow"
}
end
return {
title = ("Vehicle: %s%s\nPlate: %s"):format(makeName, modelName, props?.plate or "not found"),
metadata = metadata,
onSelect = function(args)
TriggerServerEvent("ND_Vehicles:takeVehicle", vehicle.id, vehicleSpawns)
end,
}
end
local function createMenu(vehicles, garageType, vehicleSpawns, impound)
local options = {}
if not impound then
options[#options+1] = {
title = "Park vehicle",
onSelect = function(args)
local veh = getClosestOwnedVehicle()
parkVehicle(veh)
end
}
end
for _, vehicle in ipairs(vehicles) do
if isVehicleAvailable(vehicle, garageType, impound) then
options[#options+1] = createMenuOptions(vehicle, vehicleSpawns)
end
end
if impound and #options == 0 then
options[#options+1] = {
title = "No vehicles found",
readOnly = true
}
end
return {
id = ("garage_%s"):format(garageType),
title = impound and "Vehicle impound" or "Parking garage",
options = options,
onExit = function()
garageOpen = false
end
}
end
for i=1, #locations do
local location = locations[i]
NDCore.createAiPed({
model = `s_m_y_airworker`,
coords = location.ped,
distance = 45.0,
clothing = clothing[math.random(1, #clothing)],
blip = {
label = location.impound and ("Impound (%s)"):format(location.garageType) or ("Parking garage (%s)"):format(location.garageType),
sprite = location.impound and 285 or sprite[location.garageType],
scale = 0.7,
color = 3,
groups = location.groups
},
anim = {
dict = "anim@amb@casino@valet_scenario@pose_d@",
clip = "base_a_m_y_vinewood_01"
},
options = {
{
name = "nd_core:garagePed",
icon = "fa-solid fa-warehouse",
label = location.impound and "View impounded vehicles" or "View garage",
distance = 2.0,
canInteract = function(entity, distance, coords, name, bone)
if not location.groups then return true end
local groups = location.groups
local playerGroups = NDCore.player?.groups
for i=1, #groups do
if playerGroups?[groups[i]] then
return true
end
end
end,
onSelect = function(data)
local vehicles = lib.callback.await("ND_Vehicles:getOwnedVehicles") or {}
local menu = createMenu(vehicles, location.garageType, location.vehicleSpawns, location.impound)
lib.registerContext(menu)
lib.showContext(menu.id)
end
}
},
})
end

750
client/vehicle/main.lua Normal file
View File

@@ -0,0 +1,750 @@
local vehicleColorNames = {
[0] = "Black",
[1] = "Black",
[2] = "Black",
[3] = "Silver",
[4] = "Silver",
[5] = "Silver",
[6] = "Gray",
[7] = "Silver",
[8] = "Silver",
[9] = "Silver",
[10] = "Metal",
[11] = "Grey",
[12] = "Black",
[13] = "Gray",
[14] = "Grey",
[15] = "Black",
[16] = "Black",
[17] = "Silver",
[18] = "Silver",
[19] = "Metal",
[20] = "Silver",
[21] = "Black",
[22] = "Graphite",
[23] = "Silver",
[24] = "Silver",
[25] = "Silver",
[26] = "Silver",
[27] = "Red",
[28] = "Red",
[29] = "Red",
[30] = "Red",
[31] = "Red",
[32] = "Red",
[33] = "Red",
[34] = "Red",
[35] = "Red",
[36] = "Orange",
[37] = "Gold",
[38] = "Orange",
[39] = "Red",
[40] = "Red",
[41] = "Orange",
[42] = "Yellow",
[43] = "Red",
[44] = "Red",
[45] = "Red",
[46] = "Red",
[47] = "Red",
[48] = "Red",
[49] = "Green",
[50] = "Green",
[51] = "Green",
[52] = "Green",
[53] = "Green",
[54] = "Green",
[55] = "Green",
[56] = "Green",
[57] = "Green",
[58] = "Green",
[59] = "Green",
[60] = "Green",
[61] = "Blue",
[62] = "Blue",
[63] = "Blue",
[64] = "Blue",
[65] = "Blue",
[66] = "Blue",
[67] = "Blue",
[68] = "Blue",
[69] = "Blue",
[70] = "Blue",
[71] = "Blue",
[72] = "Blue",
[73] = "Blue",
[74] = "Blue",
[75] = "Blue",
[76] = "Blue",
[77] = "Blue",
[78] = "Blue",
[79] = "Bblue",
[80] = "Blue",
[81] = "Blue",
[82] = "Blue",
[83] = "Blue",
[84] = "Blue",
[85] = "Blue",
[86] = "Blue",
[87] = "Blue",
[88] = "Yellow",
[89] = "Yellow",
[90] = "Bronze",
[91] = "Yellow",
[92] = "Lime",
[93] = "Champagne",
[94] = "Beige",
[95] = "Ivory",
[96] = "Brown",
[97] = "Brown",
[98] = "Brown",
[99] = "Beige",
[100] = "Brown",
[101] = "Brown",
[102] = "Beechwood",
[103] = "Beechwood",
[104] = "Orange",
[105] = "Sand",
[106] = "Sand",
[107] = "Cream",
[108] = "Brown",
[109] = "Brown",
[110] = "Brown",
[111] = "White",
[112] = "White",
[113] = "Beige",
[114] = "Brown",
[115] = "Brown",
[116] = "Beige",
[117] = "Steel",
[118] = "Steel",
[119] = "Aluminium",
[120] = "Chrome",
[121] = "White",
[122] = "White",
[123] = "Orange",
[124] = "Orange",
[125] = "Green",
[126] = "Yellow",
[127] = "Blue",
[128] = "Green",
[129] = "Brown",
[130] = "Orange",
[131] = "White",
[132] = "White",
[133] = "Green",
[134] = "White",
[135] = "Pink",
[136] = "pink",
[137] = "Pink",
[138] = "Orange",
[139] = "Green",
[140] = "Blue",
[141] = "Black",
[142] = "Black",
[143] = "Black",
[144] = "Green",
[145] = "Purple",
[146] = "Blue",
[147] = "Black",
[148] = "Purple",
[149] = "Purple",
[150] = "Red",
[151] = "Green",
[152] = "Green",
[153] = "Brown",
[154] = "Tan",
[155] = "Green",
[156] = "ALLOY",
[157] = "Blue",
}
local vehicleClassNames = {
[0] = "Compact",
[1] = "Sedan",
[2] = "SUV",
[3] = "Coupe",
[4] = "Muscle",
[5] = "Sports Classic",
[6] = "Sport",
[7] = "Super",
[8] = "Motorcycle",
[9] = "Off-road",
[10] = "Industrial",
[11] = "Utility",
[12] = "Van",
[13] = "Cycle",
[14] = "Boat",
[15] = "Helicopter",
[16] = "Plane",
[17] = "Service",
[18] = "Emergency",
[19] = "Military",
[20] = "Commercial",
[21] = "Train",
[22] = "Open wheel"
}
local cruiseSpeedSet = 0
local cruiseSpeedVehicle = 0
local cruiseControlEnabled = false
local playerVehicle = cache.seat == -1 and cache.vehicle
local cloudTime = GetCloudTimeAsInt()
local vehicleLockCheckTime = {
lastCheck = cloudTime,
lastUse = cloudTime
}
local keyCheckTime = {
lastCheck = cloudTime,
hasKey = false
}
local vehicleClassNotDisableAirControl = {
[8] = true, --motorcycle
[13] = true, --bicycles
[14] = true, --boats
[15] = true, --helicopter
[16] = true, --plane
[19] = true --military
}
local function getVehicleBlipSprite(entity)
if not IsEntityAVehicle(entity) then
return 148 -- circle blip
end
local class = GetVehicleClass(entity)
local model = GetEntityModel(entity)
local classBlip = {
[16] = 423, -- plane
[8] = 226, -- motorcycle
[15] = 64, -- helicopter
[14] = 427, -- boat
[6] = 825, -- sports
[7] = 523, -- super
[2] = 821, -- SUV
[4] = 663 -- muscle
}
local typeBlip = {
[`seashark`] = 471,
[`marquis`] = 410,
[`rhino`] = 421,
[`hydra`] = 424,
[`lazer`] = 424,
[`taxi`] = 198,
[`trash`] = 318,
[`trash2`] = 318
}
return typeBlip[model] or classBlip[class] or 225 -- 255 is default car blip
end
local function getVehFromNetId(netId)
local time = GetCloudTimeAsInt()
while not NetworkDoesNetworkIdExist(netId) or not NetworkDoesEntityExistWithNetworkId(netId) and time-GetCloudTimeAsInt() < 5 do
Wait(100)
end
return NetToVeh(netId)
end
RegisterNetEvent("ND_Vehicles:blip", function(netId, status)
local veh = getVehFromNetId(netId)
if not veh then return end
if not status then
local blip = GetBlipFromEntity(veh)
if not blip or not DoesBlipExist(blip) then return end
return RemoveBlip(blip)
end
local blip = AddBlipForEntity(veh)
SetBlipSprite(blip, getVehicleBlipSprite(veh))
SetBlipColour(blip, 0)
SetBlipScale(blip, 0.8)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName("STRING")
AddTextComponentSubstringPlayerName("Personal vehicle")
EndTextCommandSetBlipName(blip)
end)
RegisterNetEvent("ND_Vehicles:syncAlarm", function(netId)
local veh = getVehFromNetId(netId)
if not veh then return end
SetVehicleAlarmTimeLeft(veh, 1)
SetVehicleAlarm(veh, true)
StartVehicleAlarm(veh)
end)
RegisterNetEvent("ND_VehicleSystem:setOwnedIfNot", function(netId)
local veh = getVehFromNetId(netId)
if not veh then return end
setVehicleOwned(veh, true)
setVehicleLocked(veh, true)
end)
AddStateBagChangeHandler("props", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if not value or not DoesEntityExist(entity) or NetworkGetEntityOwner(entity) ~= cache.playerId then return end
local props = value
if type(value) == "string" then
props = json.decode(value)
end
lib.setVehicleProperties(entity, props)
end)
local function playKeyFob(veh)
local keyFob
local ped = cache.ped
local coords = GetEntityCoords(ped)
if #(coords-GetEntityCoords(veh)) > 25.0 then return end
if not playerVehicle then
ClearPedTasks(ped)
lib.requestAnimDict("anim@mp_player_intmenu@key_fob@")
TaskPlayAnim(ped, "anim@mp_player_intmenu@key_fob@", "fob_click_fp", 8.0, 8.0, -1, 48, 1, false, false, false)
keyFob = CreateObject(`lr_prop_carkey_fob`, 0, 0, 0, true, true, true)
AttachEntityToEntity(keyFob, ped, GetPedBoneIndex(ped, 0xDEAD), 0.12, 0.04, -0.025, -100.0, 100.0, 0.0, true, true, false, true, 1, true)
Wait(700)
end
PlaySoundFromEntity(-1, "Remote_Control_Fob", ped, "PI_Menu_Sounds", true, 0)
SetVehicleLights(veh, 2)
Wait(100)
SetVehicleLights(veh, 0)
Wait(200)
SetVehicleLights(veh, 2)
Wait(100)
SetVehicleLights(veh, 0)
return keyFob and DeleteEntity(keyFob)
end
RegisterNetEvent("ND_Vehicles:keyFob", function(netId)
playKeyFob(getVehFromNetId(netId))
end)
local dontLock = {
[8] = true, -- Motorcycles
[13] = true, -- Cycles
[14] = true -- boats
}
AddStateBagChangeHandler("locked", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if entity == 0 or value == nil or dontLock[GetVehicleClass(entity)] then return end
if value then
-- SetVehicleDoorsLockedForAllPlayers(entity, true)
return SetVehicleDoorsLocked(entity, 2)
end
CreateThread(function()
while GetVehiclePedIsEntering(cache.ped) == entity do Wait(10) end
-- SetVehicleDoorsLockedForAllPlayers(entity, false)
SetVehicleDoorsLocked(entity, 0)
end)
end)
lib.callback.register("ND_Vehicles:getProps", function(netId)
local veh = getVehFromNetId(netId)
local props = lib.getVehicleProperties(veh)
local colorPrimary, colorSecondary = GetVehicleColours(veh)
if not props then return end
props.colorNamePrimary = vehicleColorNames[colorPrimary]
props.colorNameSecondary = vehicleColorNames[colorSecondary]
props.colorName = props.colorNamePrimary == props.colorNameSecondary and props.colorNamePrimary or ("%s & %s"):format(props.colorNamePrimary, props.colorNameSecondary)
props.className = vehicleClassNames[GetVehicleClass(veh)]
props.makeName = GetLabelText(GetMakeNameFromVehicleModel(props.model))
props.modelName = GetLabelText(GetDisplayNameFromVehicleModel(props.model))
return props
end)
lib.callback.register("ND_Vehicles:getVehicleModelMakeLabel", function(model)
local make = GetLabelText(GetMakeNameFromVehicleModel(model))
local name = GetLabelText(GetDisplayNameFromVehicleModel(model))
if make == "NULL" then
return name
elseif name == "NULL" then
return make
end
return ("%s %s"):format(make, name)
end)
local function hasVehicleKeys(veh, checkEngine)
local state = Entity(veh).state
if Config.ox_inventory and Config.useInventoryForKeys then
local metadata = {
vehId = state.id,
keyEnabled = true
}
local hasKey = exports.ox_inventory:GetItemCount("keys", metadata) > 0
return hasKey or checkEngine and state.hotwired
end
local keys = state and state.keys
local player = NDCore.getPlayer()
local hasKey = player and keys and keys[player.id]
return hasKey or checkEngine and state.hotwired
end
local function hasVehicleKeysCheck(veh)
local time = GetCloudTimeAsInt()
if time-keyCheckTime.lastCheck < 5 then
return keyCheckTime.hasKey
end
local hasKey = hasVehicleKeys(veh, true)
keyCheckTime.lastCheck = time
keyCheckTime.hasKey = hasKey
return hasKey
end
local function getNearestVehicle(hasKeysForOnly)
local coords = GetEntityCoords(cache.ped)
local vehicles = lib.getNearbyVehicles(coords, 25.0, true)
local nearestVeh = {}
local function setNearestVehicle(veh)
if hasKeysForOnly and not hasVehicleKeys(veh.vehicle) then return end
local nearestDist = nearestVeh.dist
local dist = #(coords-veh.coords)
if not nearestDist or dist < nearestDist then
nearestVeh.dist = dist
nearestVeh.coords = veh.coords
nearestVeh.entity = veh.vehicle
end
end
for i=1, #vehicles do
setNearestVehicle(vehicles[i])
end
return nearestVeh.entity, nearestVeh.coords, nearestVeh.dist
end
local vehicleLockKeybind = lib.addKeybind({
name = "vehicleKey",
description = "Unlock/lock vehicle (double click)",
defaultKey = "E",
onPressed = function(self)
local time = GetCloudTimeAsInt()
if time-vehicleLockCheckTime.lastCheck < 1 and time-vehicleLockCheckTime.lastUse > 1 then
vehicleLockCheckTime.lastUse = time
local veh = getNearestVehicle(true)
if not veh then return end
TriggerServerEvent("ND_Vehicles:toggleVehicleLock", VehToNet(veh))
end
vehicleLockCheckTime.lastCheck = time
end
})
NDCore.isResourceStarted("ox_inventory", function(started)
Config.ox_inventory = started
if not started or not Config.useInventoryForKeys then
return vehicleLockKeybind:disable(false)
end
Wait(1000)
vehicleLockKeybind:disable(true)
exports.ox_inventory:displayMetadata({
vehPlate = "Plate",
vehModel = "Model"
})
end)
-- save wheels steering angle.
CreateThread(function()
local angle = 0.0
while true do
Wait(300)
if playerVehicle then
if GetIsTaskActive(cache.ped, 2) then
SetVehicleSteeringAngle(playerVehicle, angle)
end
angle = DoesEntityExist(playerVehicle) and GetVehicleSteeringAngle(playerVehicle)
end
end
end)
CreateThread(function()
local wait = 500
while true do
Wait(wait)
playerVehicle = cache.seat == -1 and cache.vehicle
if playerVehicle then
if Config.disableVehicleAirControl and not vehicleClassNotDisableAirControl[GetVehicleClass(playerVehicle)] and (IsEntityInAir(playerVehicle) or IsEntityUpsidedown(playerVehicle)) then
wait = 0
DisableControlAction(0, 59) -- disable vehicle air control.
DisableControlAction(0, 60)
elseif not GetIsVehicleEngineRunning(playerVehicle) and not hasVehicleKeysCheck(playerVehicle) then
wait = 0
DisableControlAction(0, 59)
if DoesEntityExist(playerVehicle) and IsVehicleEngineStarting(playerVehicle) then
SetVehicleEngineOn(playerVehicle, false, true, true) -- don't turn on engine if no keys.
end
else
wait = 500
end
elseif wait ~= 500 then
wait = 500
end
end
end)
local function hotwireVehicle()
local state = playerVehicle and Entity(playerVehicle).state
if not playerVehicle or state.hotwired then return end
local finished = false
lib.requestModel(`imp_prop_impexp_pliers_02`)
lib.requestModel(`prop_tool_screwdvr01`)
lib.requestAnimDict("veh@handler@base")
if GetFollowVehicleCamViewMode() > 2 then
SetFollowVehicleCamViewMode(0)
end
CreateThread(function()
while not finished do
Wait(0)
DisableFirstPersonCamThisFrame()
end
end)
local modelWithHandles = {`seashark`, `seashark2`, `seashark3`}
local vehicleModel = GetEntityModel(playerVehicle)
local bikeHandles = GetVehicleClass(playerVehicle) == 8 or lib.table.contains(modelWithHandles, vehicleModel)
local success = lib.progressCircle({
duration = math.random(10000, 20000),
label = "Hotwiring",
useWhileDead = false,
allowRagdoll = false,
allowCuffed = false,
allowFalling = false,
canCancel = true,
anim = {
dict = bikeHandles and "anim@veh@boat@jetski@front@base" or "veh@handler@base",
clip = "hotwire"
},
disable = {
move = true,
car = true,
combat = true
},
prop = {
{
model = `imp_prop_impexp_pliers_02`,
bone = 0x49D9, -- SKEL_R_Hand
pos = vec3(0.1, -0.05, 0.0),
rot = vec3(-1.5, -15.0, -1.5)
},
{
model = `prop_tool_screwdvr01`,
bone = 0xDEAD, -- SKEL_R_Hand
pos = vec3(0.1, 0.08, -0.03),
rot = vec3(90.0, 0.0, 0.0)
}
}
})
finished = true
if not success then return end
if not playerVehicle then return false, true end
TriggerServerEvent("ND_Vehicles:hotwire", VehToNet(playerVehicle))
return true, true
end
local function lockpickVehicle()
if cache.vehicle then return end
local pos = GetEntityCoords(cache.ped)
local rot = GetEntityRotation(cache.ped, 2)
local veh = lib.getClosestVehicle(pos, 2.5, false)
if not veh then return end
local dificulties = {
"easy",
"medium",
"hard"
}
local dificultyTime = {
easy = 500,
medium = 800,
hard = 1000
}
lib.requestAnimDict("veh@break_in@0h@p_m_one@")
for i=1, 7 do
TaskPlayAnimAdvanced(cache.ped, "veh@break_in@0h@p_m_one@", "std_force_entry_ds", pos.x, pos.y, pos.z+0.025, rot.x, rot.y, rot.z, 8.0, 8.0, 1800, 28, 0.1)
local dificulty = dificulties[math.random(1, #dificulties)]
local success = lib.skillCheck(dificulty)
if not success or not DoesEntityExist(veh) or #(pos-GetEntityCoords(veh)) > 2.5 then
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), false)
return false, true
end
Wait(dificultyTime[dificulty])
end
veh = lib.getClosestVehicle(pos, 2.5, false)
if not veh then return false, true end
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), true)
PlaySoundFromEntity(-1, "Remote_Control_Fob", cache.ped, "PI_Menu_Sounds", true, 0)
return true, true
end
exports("lockpick", function(data, slot)
local _, used = lockpickVehicle()
if used then
exports.ox_inventory:useItem(data)
end
end)
exports("hotwire", function(data, slot)
local _, used = hotwireVehicle()
if used then
exports.ox_inventory:useItem(data)
end
end)
exports("keyControl", function(action, slot)
for item, data in pairs(exports.ox_inventory:Items()) do
local metadata = data.metadata
if data.slot == slot then
if metadata and not metadata.keyEnabled then
return lib.notify({
title = "No signal",
description = "Vehicle key disabled.",
type = "error",
position = "bottom-right",
duration = 3000
})
end
break
end
end
exports.ox_inventory:closeInventory()
if action == "trunk" then
local veh = getNearestVehicle(true)
if not veh then return end
playKeyFob(veh)
if GetVehicleDoorAngleRatio(veh, 5) > 0.0 then
SetVehicleDoorShut(veh, 5)
else
SetVehicleDoorOpen(veh, 5, false)
end
elseif action == "disable" then
TriggerServerEvent("ND_Vehicles:disableKey", slot)
lib.notify({
title = "Key disabled",
description = "This vehicle key has been disabled and cannot be used anymore.",
type = "inform",
position = "bottom-right",
duration = 3000
})
end
end)
local function cruiseControl()
cruiseSpeedVehicle = GetEntitySpeed(playerVehicle) * 2.236936
if not playerVehicle then
lib.notify({
title = "Cruise control",
description = "Vehicle cruise control disabled.",
type = "inform",
position = "bottom-right",
duration = 3000
})
return
end
if cruiseSpeedVehicle < cruiseSpeedSet/3 then
lib.notify({
title = "Cruise control",
description = "Vehicle cruise control disabled.",
type = "inform",
position = "bottom-right",
duration = 3000
})
return
end
if cruiseSpeedVehicle < cruiseSpeedSet then
SetControlNormal(0, 71, 0.6)
end
return true
end
lib.addKeybind({
name = "vehicleCruiseControl",
description = "Toggle vehicle cruise control",
defaultKey = "",
onPressed = function(self)
if cruiseControlEnabled and cruiseSpeedVehicle-1 > cruiseSpeedSet then
cruiseSpeedSet = cruiseSpeedVehicle
return lib.notify({
title = "Cruise control",
description = ("Increased to %d mph."):format(math.floor(cruiseSpeedSet)),
type = "inform",
position = "bottom-right",
duration = 3000
})
elseif cruiseControlEnabled then
cruiseControlEnabled = false
return lib.notify({
title = "Cruise control",
description = "Vehicle cruise control disabled.",
type = "inform",
position = "bottom-right",
duration = 3000
})
end
if not playerVehicle then return end
cruiseControlEnabled = true
cruiseSpeedVehicle = math.floor(GetEntitySpeed(playerVehicle) * 2.236936)
cruiseSpeedSet = cruiseSpeedVehicle
lib.notify({
title = "Cruise control",
description = ("Set to %d mph."):format(math.floor(cruiseSpeedSet)),
type = "inform",
position = "bottom-right",
duration = 3000
})
CreateThread(function()
while cruiseControlEnabled and cruiseControl() do Wait(0) end
cruiseControlEnabled = false
end)
end
})
lib.addKeybind({
name = "vehicleShuffleSeat",
description = "Suffle vehicle seat.",
defaultKey = "",
onPressed = function(self)
if not cache.vehicle then return end
local seats = {
[-1] = 0,
[0] = -1,
[1] = 2,
[2] = 1,
[3] = 4,
[4] = 3,
[5] = 6,
[6] = 5
}
SetPedIntoVehicle(cache.ped, cache.vehicle, seats[cache.seat])
end
})
lib.onCache("ped", function(value)
SetPedConfigFlag(value, 184, true)
end)
lib.onCache("vehicle", function(value)
local veh = value or cache.vehicle
local blip = GetBlipFromEntity(veh)
if not blip or not DoesBlipExist(blip) then return end
SetBlipAlpha(blip, value and 0 or 255)
end)
SetTimeout(500, function()
SetPedConfigFlag(cache.ped, 184, true)
end)

View File

@@ -0,0 +1,18 @@
if not lib.table.contains(Config.compatibility, "backwards") then return end
NDCore.Functions = {}
NDCore.Functions.GetSelectedCharacter = NDCore.getPlayer
NDCore.Functions.GetCharacters = NDCore.getCharacters
NDCore.Functions.GetPlayersFromCoords = NDCore.getPlayersFromCoords
exports("GetCoreObject", function()
return NDCore
end)
RegisterNetEvent("ND:returnCharacters", function(characters)
NDCore.characters = characters
end)
RegisterNetEvent("ND:setCharacter", function(character)
NDCore.player = character
end)

View File

@@ -0,0 +1,259 @@
if not lib.table.contains(Config.compatibility, "backwards") then return end
exports("GetCoreObject", function()
return NDCore
end)
NDCore.Functions = {}
NDCore.Functions.GetPlayer = NDCore.getPlayer
NDCore.Functions.GetPlayers = NDCore.getPlayers
NDCore.Functions.GetUserDiscordInfo = NDCore.getDiscordInfo
NDCore.Functions.SetActiveCharacter = NDCore.setActiveCharacter
NDCore.Functions.GetPlayerCharacters = NDCore.fetchAllCharacters
NDCore.Functions.GetPlayerByCharacterId = NDCore.fetchCharacter
function NDCore.Functions.GetPlayerIdentifierFromType(identifierType, src)
return GetPlayerIdentifierByType(src, identifierType)
end
function NDCore.Functions.GetNearbyPedToPlayer(src)
local pedCoords = GetEntityCoords(GetPlayerPed(src))
for targetId, targetInfo in pairs(NDCore.players) do
local targetCoords = GetEntityCoords(GetPlayerPed(targetId))
if #(pedCoords - targetCoords) < 2.0 and targetId ~= src then
return targetId, targetInfo
end
end
end
function NDCore.Functions.UpdateMoney(src)
local player = NDCore.getPlayer(src)
player.triggerEvent("ND:updateMoney", player.cash, player.bank)
end
function NDCore.Functions.TransferBank(amount, source, target, descriptionSender, descriptionReceiver)
local amount = tonumber(amount)
local src = tonumber(source)
local target = tonumber(target)
local player = NDCore.getPlayer(src)
if not player then return end
if src == target then
return false, "Can't transfer money to same account"
elseif GetPlayerPing(target) == 0 then
return false, "Account not found"
elseif amount <= 0 then
return false, "Invalid amount"
elseif player.bank < amount then
return false, "Insufficient funds"
end
local targetPlayer = NDCore.getPlayer(target)
if not targetPlayer then return end
return player.deductMoney("bank", amount, descriptionSender or "Transfer") and targetPlayer.addMoney("bank", amount, descriptionReceiver or "Transfer")
end
function NDCore.Functions.GiveCash(amount, source, target)
local amount = tonumber(amount)
local src = tonumber(source)
local target = tonumber(target)
local player = NDCore.getPlayer(src)
if not player then return end
if src == target then
return false, "Can't give to self"
elseif GetPlayerPing(target) == 0 then
return false, "Target not found"
elseif amount <= 0 then
return false, "Invalid amount"
elseif player.bank < amount then
return false, "Not enough money"
end
local targetPlayer = NDCore.getPlayer(target)
if not targetPlayer then return endn end
return player.deductMoney("cash", amount) and targetPlayer.addMoney("cash", amount)
end
function NDCore.Functions.GiveCashToNearbyPlayer(source, amount)
local targetId = NDCore.Functions.GetNearbyPedToPlayer(source)
if not targetId then return end
return NDCore.Functions.GiveCash(amount, source, targetId)
end
function NDCore.Functions.WithdrawMoney(amount, source)
local player = NDCore.getPlayer(source)
return player and player.withdrawMoney(amount)
end
function NDCore.Functions.DepositMoney(amount, source)
local player = NDCore.getPlayer(source)
return player and player.depositMoney(amount)
end
function NDCore.Functions.DeductMoney(amount, source, account, description)
local player = NDCore.getPlayer(source)
return player and player.deductMoney(amount, account, description)
end
function NDCore.Functions.AddMoney(amount, source, account, description)
local player = NDCore.getPlayer(source)
return player and player.addMoney(amount, account, description)
end
function NDCore.Functions.CreateCharacter(src, firstName, lastName, dob, gender, cb)
local player = NDCore.newCharacter(src, {
firstname = firstName,
lastname = lastName,
dob = dob,
gender = gender,
})
if cb then cb(player.id) end
player.triggerEvent("ND:returnCharacters", NDCore.fetchAllCharacters(src))
return player.id
end
function NDCore.Functions.UpdateCharacter(characterId, firstName, lastName, dob, gender)
local player = NDCore.fetchCharacter(characterId)
player.setData({
source = src,
firstname = firstName,
lastname = lastName,
dob = dob,
gender = gender
})
return player
end
function NDCore.Functions.DeleteCharacter(characterId)
local player = NDCore.fetchCharacter(characterId)
return player and player.delete()
end
function NDCore.Functions.SetPlayerData(characterId, key, value)
local player = NDCore.fetchCharacter(characterId)
if not player then return end
if player[key] then
return player.setData(key, value)
end
return player.setMetadata(key, value)
end
function NDCore.Functions.CreatePlayerLicense(characterId, licenseType, expire)
local player = NDCore.fetchCharacter(characterId)
return self.createLicense(licenseType, expire)
end
-- find a players license by it's identifier.
function NDCore.Functions.FindLicenseByIdentifier(licences, identifier)
for key, license in pairs(licences) do
if license.identifier == identifier then
return license, key
end
end
return {}
end
-- Edit a license by the license identifier.
function NDCore.Functions.EditPlayerLicense(characterId, identifier, newData)
local player = NDCore.fetchCharacter(characterId)
if not player then return end
player.updateLicense(identifier, newData)
end
-- Set the players job and job rank.
function NDCore.Functions.SetPlayerJob(characterId, job, rank)
local player = NDCore.fetchCharacter(characterId)
if not player then return end
if player.source then
local oldJob = player.getJob(job)
TriggerEvent("ND:jobChanged", player.source, {name = job, rank = rank or 1}, {name = player.job, rank = oldJob and oldJob.rank or 1})
TriggerClientEvent("ND:jobChanged", player.source, {name = job, rank = rank or 1}, {name = player.job, rank = oldJob and oldJob.rank or 1})
end
return player.setJob(job, rank)
end
-- Set a player to a group.
function NDCore.Functions.SetPlayerToGroup(characterId, group, rank)
local player = NDCore.fetchCharacter(characterId)
return player and player.addGroup(group, rank)
end
-- Remove a player from a group.
function NDCore.Functions.RemovePlayerFromGroup(characterId, group)
local player = NDCore.fetchCharacter(characterId)
return player and player.removeGroup(group)
end
-- Update the characters last location into the database.
function NDCore.Functions.UpdateLastLocation(characterId, location)
local player = NDCore.fetchCharacter(characterId)
return player and player.setMetadata("location", {
x = location.x,
y = location.y,
x = location.z,
w = location.h or location.heading or location.w or 0.0
})
end
function NDCore.Functions.IsPlayerAdmin(src)
local player = NDCore.getPlayer(src)
if player.groups["admin"] then
return true
end
end
-- Getting all the characters the player has and returning them to the client.
RegisterNetEvent("ND:GetCharacters", function()
local src = source
TriggerClientEvent("ND:returnCharacters", src, NDCore.fetchAllCharacters(src))
end)
-- Creating a new character.
RegisterNetEvent("ND:newCharacter", function(newCharacter)
local src = source
NDCore.newCharacter(src, {
firstname = newCharacter.firstName,
lastname = newCharacter.lastName,
dob = newCharacter.dob,
gender = newCharacter.gender,
cash = 0,
bank = 0,
})
end)
-- Update the character info when edited.
RegisterNetEvent("ND:editCharacter", function(newCharacter)
local src = source
local player = NDCore.fetchCharacter(newCharacter.id, src)
return player and player.setData({
source = src,
firstname = newCharacter.firstName,
lastname = newCharacter.lastName,
dob = newCharacter.dob,
gender = newCharacter.gender
})
end)
-- Delete character from database.
RegisterNetEvent("ND:deleteCharacter", function(characterId)
local src = source
local player = NDCore.fetchCharacter(characterId, src)
if not player or player.source ~= source then return end
return player.delete()
end)
-- add a player to the table.
RegisterNetEvent("ND:setCharacterOnline", function(id)
local src = source
NDCore.setActiveCharacter(src, tonumber(id))
end)
-- Update the characters clothes.
RegisterNetEvent("ND:updateClothes", function(clothing)
local src = source
local player = NDCore.getPlayer(src)
player.setMetadata("clothing", clothing)
end)

View File

@@ -0,0 +1,412 @@
if not lib.table.contains(Config.compatibility, "esx") then return end
NDCore.Game = {}
NDCore.Game.Utils = {}
NDCore.Scaleform = {}
NDCore.Scaleform.Utils = {}
NDCore.Streaming = {}
NDCore.UI = {}
NDCore.UI.HUD = {}
NDCore.UI.Menu = {}
NDCore.PlayerData = {}
local uiMetatable = {
__index = function(table, key)
if type(key) == "string" and key:match("^%a+$") then
return function()
print(("[^3WARNING^7] ESX Function '%s' is not compatible with NDCore!"):format(key))
end
end
end
}
setmetatable(NDCore.UI, uiMetatable)
setmetatable(NDCore.UI.HUD, uiMetatable)
setmetatable(NDCore.UI.Menu, uiMetatable)
local function exportHandler(resource, exportName, cb)
AddEventHandler(("__cfx_export_%s_%s"):format(resource, exportName), function(setCB)
setCB(cb)
end)
end
exportHandler("es_extended", "getSharedObject", function()
return NDCore
end)
function NDCore.GetPlayerData()
local player = NDCore.getPlayer()
if not player then return {} end
player.Accounts = {
Bank = player.bank,
Money = player.Cash,
Black = player.Cash
}
if player.jobInfo then
player.job = {
id = player.job,
name = player.job,
label = player.jobInfo.label,
grade = player.jobInfo.rank,
grade_name = player.jobInfo.rankName,
grade_label = player.jobInfo.rankName,
grade_salary = 0,
skin_male = {},
skin_female = {}
}
end
player.coords = GetEntityCoords(cache.ped)
player.loadout = {}
player.maxWeight = exports.ox_inventory:GetPlayerMaxWeight()
player.money = player.Accounts.Money
player.sex = player.gender
player.firstName = player.firstname
player.lastName = player.lastname
player.dateofbirth = player.dob
player.height = 120
player.dead = LocalPlayer.state.dead or false
NDCore.PlayerData = player
return player
end
function NDCore.IsPlayerLoaded()
return NDCore.getPlayer() ~= nil
end
function NDCore.Progressbar(message, lenght, options)
local newOptions = {
duration = lenght,
label = message,
canCancel = true
}
if options.animation then
newOptions.anim = {}
if options.animation.type == "anim" then
if options.animation.dict then
newOptions.anim.dict = options.animation.dict
end
if options.animation.lib then
newOptions.anim.clip = options.animation.lib
end
elseif options.animation.type == "Scenario" then
newOptions.anim.scenario = options.animation.Scenario
end
end
if options.FreezePlayer then FreezeEntityPosition(cache.ped, true) end
local complete = lib.progressBar(newOptions)
if options.FreezePlayer then FreezeEntityPosition(cache.ped, false) end
if newOptions.onFinish and complete then
newOptions.onFinish()
end
if newOptions.onCancel and not complete then
newOptions.onCancel()
end
return complete
end
function NDCore.SearchInventory(item, count)
local itemCount = exports.ox_inventory:Search(item)
return itemCount >= count and itemCount
end
function NDCore.SetPlayerData()
print("[^3WARNING^7] ESX Function 'SetPlayerData' is not compatible with NDCore!")
end
function NDCore.ShowAdvancedNotification()
print("[^3WARNING^7] ESX Function 'ShowAdvancedNotification' is not compatible with NDCore!")
end
function NDCore.ShowFloatingHelpNotification()
print("[^3WARNING^7] ESX Function 'ShowFloatingHelpNotification' is not compatible with NDCore!")
end
function NDCore.ShowHelpNotification()
print("[^3WARNING^7] ESX Function 'ShowHelpNotification' is not compatible with NDCore!")
end
function NDCore.ShowInventory()
exports.ox_inventory:openInventory("player", cache.serverId)
end
function NDCore.ShowNotification(msg, type, time)
NDCore.notify({
title = "Notification",
description = msg,
type = type == "info" and "inform" or type,
duration = time
})
end
function NDCore.TriggerServerCallback(name, cb, ...)
lib.callback(name, nil, cb, ...)
end
function NDCore.Streaming.RequestAnimDict(animDict, cb)
lib.requestAnimDict(animDict)
if cb then cb(animDict) end
return animDict
end
function NDCore.Streaming.RequestAnimSet(animSet, cb)
lib.requestAnimSet(animSet)
if cb then cb(animSet) end
return animSet
end
function NDCore.Streaming.RequestModel(model, cb)
lib.requestModel(model)
if cb then cb(model) end
return model
end
function NDCore.Streaming.RequestNamedPtfxAsset(assetName, cb)
lib.requestNamedPtfxAsset(assetName)
if cb then cb(assetName) end
return assetName
end
function NDCore.Streaming.RequestStreamedTextureDict(textureDict, cb)
lib.requestStreamedTextureDict(textureDict)
if cb then cb(textureDict) end
return textureDict
end
function NDCore.Streaming.RequestWeaponAsset(weaponHash, cb)
lib.requestWeaponAsset(weaponHash)
if cb then cb(weaponHash) end
return weaponHash
end
function NDCore.Scaleform.ShowBreakingNews()
print("[^3WARNING^7] ESX Function 'Scaleform.ShowBreakingNews' is not compatible with NDCore!")
end
function NDCore.Scaleform.ShowFreemodeMessage()
print("[^3WARNING^7] ESX Function 'Scaleform.ShowFreemodeMessage' is not compatible with NDCore!")
end
function NDCore.Scaleform.ShowPopupWarning()
print("[^3WARNING^7] ESX Function 'Scaleform.ShowPopupWarning' is not compatible with NDCore!")
end
function NDCore.Scaleform.ShowTrafficMovie()
print("[^3WARNING^7] ESX Function 'Scaleform.ShowTrafficMovie' is not compatible with NDCore!")
end
function NDCore.Scaleform.Utils.RequestScaleformMovie()
print("[^3WARNING^7] ESX Function 'Scaleform.Utils.RequestScaleformMovie' is not compatible with NDCore!")
end
function NDCore.Game.Utils.DrawText3D(coords, text, size, font)
local onScreen, x, y = World3dToScreen2d(coords.x, coords.y, coords.z)
if not onScreen then return end
SetTextScale(size or 0.4, size or 0.4)
SetTextFont(font or 4)
SetTextProportional(1)
SetTextEntry("STRING")
SetTextCentre(true)
SetTextColour(255, 255, 255, 255)
SetTextOutline()
AddTextComponentString(text)
DrawText(x, y)
end
function NDCore.Game.DeleteObject(object)
if not DoesEntityExist(object) then return end
DeleteEntity(object)
end
function NDCore.Game.DeleteVehicle(vehicle)
if not DoesEntityExist(vehicle) then return end
DeleteEntity(vehicle)
end
function NDCore.Game.GetClosestEntity(coords)
coords = coords or GetEntityCoords(cache.ped)
local loc = vec3(coords.x, coords.y, coords.z)
local entities = {lib.getClosestObject(loc), lib.getClosestPed(loc), lib.getClosestVehicle(loc)}
local closest = nil
local closestDistance = math.huge
for i = 1, #entities do
local ent = entities[i]
if DoesEntityExist(ent.object or ent.ped or ent.vehicle) then
local distance = #(loc-ent.coords)
if distance < closestDistance then
closest = ent
closestDistance = distance
end
end
end
return closest.object or closest.ped or closest.vehicle
end
function NDCore.Game.GetClosestObject(coords)
coords = coords or GetEntityCoords(cache.ped)
local loc = vec3(coords.x, coords.y, coords.z)
return lib.getClosestObject(loc).object
end
function NDCore.Game.GetClosestPed(coords)
coords = coords or GetEntityCoords(cache.ped)
local loc = vec3(coords.x, coords.y, coords.z)
return lib.getClosestPed(loc).ped
end
function NDCore.Game.GetClosestPlayer(coords)
coords = coords or GetEntityCoords(cache.ped)
local loc = vec3(coords.x, coords.y, coords.z)
local ply = lib.getClosestPlayer(loc)
return ply.playerId, #(ply.playerCoords-loc)
end
function NDCore.Game.GetClosestVehicle(coords)
coords = coords or GetEntityCoords(cache.ped)
local loc = vec3(coords.x, coords.y, coords.z)
return lib.getClosestVehicle(loc).vehicle
end
function NDCore.Game.GetObjects()
return GetGamePool("CObject")
end
function NDCore.Game.GetPedMugshot()
print("[^3WARNING^7] ESX Function 'Game.GetPedMugshot' is not compatible with NDCore!")
end
function NDCore.Game.GetPeds(onlyOtherPeds)
local peds = GetGamePool("CPed")
if onlyOtherPeds then
for i=1, #peds do
if peds[i] == cache.ped then
table.remove(peds, i)
end
end
end
return peds
end
function NDCore.Game.GetPlayers()
print("[^3WARNING^7] ESX Function 'Game.GetPlayers' is not compatible with NDCore!")
end
function NDCore.Game.GetPlayersInArea()
print("[^3WARNING^7] ESX Function 'Game.GetPlayersInArea' is not compatible with NDCore!")
end
function NDCore.Game.GetPlayersInArea()
print("[^3WARNING^7] ESX Function 'Game.GetPlayersInArea' is not compatible with NDCore!")
end
function NDCore.Game.GetVehicleInDirection()
print("[^3WARNING^7] ESX Function 'Game.GetVehicleInDirection' is not compatible with NDCore!")
end
function NDCore.Game.GetVehicleProperties(vehicle)
return lib.getVehicleProperties(vehicle)
end
function NDCore.Game.GetVehicles()
return GetGamePool("CVehicle")
end
function NDCore.Game.GetVehiclesInArea()
print("[^3WARNING^7] ESX Function 'Game.GetVehiclesInArea' is not compatible with NDCore!")
end
function NDCore.Game.IsSpawnPointClear()
print("[^3WARNING^7] ESX Function 'Game.IsSpawnPointClear' is not compatible with NDCore!")
end
function NDCore.Game.IsVehicleEmpty(vehicle)
for i=-1, 6 do
if not IsVehicleSeatFree(vehicle, i) then
return false
end
end
return true
end
function NDCore.Game.SetVehicleProperties(vehicle, props)
lib.setVehicleProperties(vehicle, props)
end
function NDCore.Game.SpawnLocalObject(model, coords, cb)
if type(model) == "string" then
model = GetHashKey(model)
end
local entity = CreateObject(model, coords.x, coords.y, coords.z, false, false, false)
entity = lib.waitFor(function()
if DoesEntityExist(entity) then return entity end
end)
if cb then cb(entity) end
return entity
end
function NDCore.Game.SpawnLocalVehicle(model, coords, heading, cb)
if type(model) == "string" then
model = GetHashKey(model)
end
local entity = CreateVehicle(model, coords.x, coords.y, coords.z, heading, false, false, false)
entity = lib.waitFor(function()
if DoesEntityExist(entity) then return entity end
end)
if cb then cb(entity) end
return entity
end
function NDCore.Game.SpawnObject(model, coords, cb)
if type(model) == "string" then
model = GetHashKey(model)
end
local entity = CreateObject(model, coords.x, coords.y, coords.z, true, false, false)
entity = lib.waitFor(function()
if DoesEntityExist(entity) then return entity end
end)
if cb then cb(entity) end
return entity
end
function NDCore.Game.SpawnVehicle(model, coords, heading, cb)
if type(model) == "string" then
model = GetHashKey(model)
end
local entity = CreateVehicle(model, coords.x, coords.y, coords.z, heading, true, false, false)
entity = lib.waitFor(function()
if DoesEntityExist(entity) then return entity end
end)
if cb then cb(entity) end
return entity
end
function NDCore.Game.Teleport(entity, coords, cb)
if DoesEntityExist(entity) then
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
while not HasCollisionLoadedAroundEntity(entity) do
Wait(0)
end
SetEntityCoords(entity, coords.x, coords.y, coords.z, false, false, false, false)
SetEntityHeading(entity, coords.w or coords.heading or 0.0)
end
if cb then
cb()
end
end
AddEventHandler("ND:characterLoaded", function()
NDCore.GetPlayerData()
end)
AddEventHandler("ND:updateCharacter", function()
NDCore.GetPlayerData()
end)

View File

@@ -0,0 +1,29 @@
local config_locale = "en"
Locales = {}
function Translate(str, ...) -- Translate string
if not str then
print(("[^1ERROR^7] Resource ^5%s^7 You did not specify a parameter for the Translate function or the value is nil!"):format(GetInvokingResource() or GetCurrentResourceName()))
return 'Given translate function parameter is nil!'
end
if Locales[config_locale] then
if Locales[config_locale][str] then
return string.format(Locales[config_locale][str], ...)
elseif config_locale ~= 'en' and Locales['en'] and Locales['en'][str] then
return string.format(Locales['en'][str], ...)
else
return 'Translation [' .. config_locale .. '][' .. str .. '] does not exist'
end
elseif config_locale ~= 'en' and Locales['en'] and Locales['en'][str] then
return string.format(Locales['en'][str], ...)
else
return 'Locale [' .. config_locale .. '] does not exist'
end
end
function TranslateCap(str, ...) -- Translate string first char uppercase
return _(str, ...):gsub("^%l", string.upper)
end
_ = Translate
_U = TranslateCap

View File

@@ -0,0 +1,371 @@
if not lib.table.contains(Config.compatibility, "esx") then return end
local itemNames
local registeredItems = {}
local function getAmmoFromWeapon(weapon)
if not weapon then return end
for item, data in pairs(exports.ox_inventory:Items()) do
if data.weapon and data.model and data.model:lower() == weapon:lower() then
return data.ammoname
end
end
end
local function createPlayerFunctions(self)
self.Accounts = {
Bank = self.bank,
Money = self.Cash,
Black = self.Cash
}
if self.jobInfo then
self.job = {
id = self.job,
name = self.job,
label = self.jobInfo.label,
grade = self.jobInfo.rank,
grade_name = self.jobInfo.rankName,
grade_label = self.jobInfo.rankName,
grade_salary = 0,
skin_male = {},
skin_female = {}
}
end
local ped = GetPlayerPed(self.source)
if DoesEntityExist(ped) then
self.coords = GetEntityCoords()
end
self.loadout = {}
self.maxWeight = 30000
self.money = self.Accounts.Money
self.sex = self.gender
self.firstName = self.firstname
self.lastName = self.lastname
self.dateofbirth = self.dob
self.height = 120
self.dead = self.getMetadata("dead")
function self.addAccountMoney(account, amount)
local amount = tonumber(amount)
if not amount or amount <= 0 or account ~= "bank" and account ~= "cash" then return end
self[account] += amount
if NDCore.players[self.source] then
self.triggerEvent("ND:updateMoney", self.cash, self.bank)
TriggerEvent("ND:moneyChange", self.source, account, amount, "add")
end
return true
end
function self.addInventoryItem(item, count)
exports.ox_inventory:AddItem(self.source, item, count)
end
function self.addMoney(amount)
local amount = tonumber(amount)
if not amount or amount <= 0 then return end
self["bank"] += amount
if NDCore.players[self.source] then
self.triggerEvent("ND:updateMoney", self.cash, self.bank)
TriggerEvent("ND:moneyChange", self.source, "bank", amount, "add", reason)
end
return true
end
function self.addWeaponAmmo(weaponName, ammoCount)
local ammoName = getAmmoFromWeapon(weaponName)
if not ammoName then return end
self.addInventoryItem(ammoName, ammoCount)
end
function self.addWeapon(weaponName, ammo)
local name = nil
if weaponName:find("weapon") then
name = weaponName
else
name = ("weapon_%s"):format(weaponName)
end
self.addInventoryItem(name, 1)
self.addWeaponAmmo(name, ammo)
-- local weapon = GetHashKey(name)
-- local ped = GetPlayerPed(self.source)
-- GiveWeaponToPed(ped, weapon, ammo, false, false)
end
function self.addWeaponComponent(_, component)
self.addInventoryItem(component, 1)
end
function self.canCarryItem(item, count)
return exports.ox_inventory:CanCarryItem(self.source, item, count)
end
function self.canSwapItem(firstItem, firstItemCount, testItem, testItemCount)
exports.ox_inventory:CanSwapItem(self.source, firstItem, firstItemCount, testItem, testItemCount)
end
function self.clearMeta(index)
self.setMetadata(index, nil)
end
function self.getMeta(index, subIndex)
local meta = self.getMetadata(index)
if type(meta) == "table" then
return meta[subIndex]
end
return meta
end
function self.getCoords(useVector)
local ped = GetPlayerPed(self.source)
local coords = GetEntityCoords(ped)
if useVector then
return coords
end
return {
x = coords.x,
y = coords.y,
z = coords.z
}
end
function self.getIdentifier()
return self.identifier
end
function self.getInventory(minimal)
local playerItems = exports.ox_inventory:GetInventoryItems(self.source)
if not minimal then
return playerItems
end
-- minimal stuff
end
function self.getInventoryItem(item)
local playerItems = exports.ox_inventory:GetInventoryItems(self.source)
for name, data in pairs(playerItems) do
if name == item then
return data
end
end
end
function self.getMoney()
return self.bank
end
function self.getName()
return self.fullname
end
function self.hasItem(item, metadata)
return exports.ox_inventory:GetItem(self.source, item, metadata)
end
function self.removeInventoryItem(item, count)
exports.ox_inventory:RemoveItem(self.source, item, count)
end
function self.removeMoney(amount)
self.deductMoney("bank", amount)
end
function setMoney(amount)
self.setData("bank", amount)
end
self.kick = self.drop
self.removeAccountMoney = self.deductMoney
self.setMeta = self.setMetadata
self.setAccountMoney = self.setData
self.setInventoryItem = self.addInventoryItem
-- self.getAccount(account)
-- self.getAccounts()
-- self.getGroup()
-- self.getJob()
-- self.getLoadout()
-- self.getMissingAccounts(cb)
-- self.getWeapon(weaponName)
-- self.getWeaponTint(weaponName, weaponTintIndex)
-- self.getWeight()
-- self.hasWeapon(weaponName)
-- self.hasWeaponComponent(weaponName, weaponComponent)
-- self.removeWeapon(weaponName)
-- self.removeWeaponAmmo(weaponName, ammoCount)
-- self.removeWeaponComponent(weaponName, weaponComponent)
-- self.setMaxWeight(newWeight)
-- self.setName(newName)
-- self.setWeaponTint(weaponName, weaponTintIndex)
-- self.showAdvancedNotification(sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
-- self.showHelpNotification(msg, thisFrame, beep, duration)
-- self.showNotification(msg, flash, saveToBrief, hudColorIndex)
return self
end
NDCore.OneSync = {}
NDCore.RegisterServerCallback = lib.callback.register
NDCore.SetTimeout = SetTimeout
NDCore.Trace = Citizen.Trace
local function exportHandler(resource, exportName, cb)
AddEventHandler(("__cfx_export_%s_%s"):format(resource, exportName), function(setCB)
setCB(cb)
end)
end
exportHandler("es_extended", "getSharedObject", function()
return NDCore
end)
function NDCore.ClearTimeout()
print("[^3WARNING^7] ESX Function 'ClearTimeout' is not compatible with NDCore!")
end
function NDCore.CreatePickup()
print("[^3WARNING^7] ESX Function 'CreatePickup' is not compatible with NDCore!")
end
function NDCore.DiscordLog()
print("[^3WARNING^7] ESX Function 'DiscordLog' is not compatible with NDCore!")
end
function NDCore.DiscordLogFields()
print("[^3WARNING^7] ESX Function 'DiscordLogFields' is not compatible with NDCore!")
end
function NDCore.RegisterUsableItem()
print("[^3WARNING^7] ESX Function 'RegisterUsableItem' is not compatible with NDCore!")
end
function NDCore.UseItem()
print("[^3WARNING^7] ESX Function 'UseItem' is not compatible with NDCore!")
end
function NDCore.GetPlayerFromId(src)
return createPlayerFunctions(NDCore.getPlayer(src))
end
function NDCore.GetExtendedPlayers(key, value)
local players = {}
if not key or not value then
for _, info in pairs(NDCore.players) do
players[#players+1] = createPlayerFunctions(info)
end
return players
end
local keyTypes = {id = "id", firstname = "firstname", lastname = "lastname", gender = "gender", groups = "groups"}
local findBy = keyTypes[key] or "metadata"
if findBy then
for _, info in pairs(NDCore.players) do
if findBy == "metadata" and info["metadata"][key] == value or info[findBy] == value then
players[#players+1] = createPlayerFunctions(info)
end
end
end
return players
end
NDCore.GetPlayers = NDCore.GetExtendedPlayers
function NDCore.RegisterCommand(name, perms, cb, allowConsole, suggestion)
lib.addCommand(name, {
help = suggestion.help,
params = suggestion.arguments,
restricted = perms and ("group.%s"):format(perms)
}, function(source, args, raw)
if allowConsole and source == 0 then
return print("[^3WARNING^7] ^5Command Cannot be executed from console")
end
local player = NDCore.getPlayer(source)
cb(player, args, function(msg)
if source == 0 then
return print(("[^3WARNING^7] %s^7"):format(msg))
end
player.showNotification(msg)
end)
end)
end
function NDCore.DoesJobExist(job, grade)
local groupInfo = Config.groups[job]
if groupInfo and groupInfo.ranks[grade] then
return true
end
end
function NDCore.GetItemLabel(item)
if not itemNames then
itemNames = {}
for item, data in pairs(exports.ox_inventory:Items()) do
itemNames[item] = data.label
end
end
return itemNames[item]
end
function NDCore.GetJobs()
return Config.groups
end
function NDCore.GetPlayerFromIdentifier(identifier)
for _, info in pairs(NDCore.players) do
if info.identifier:find(identifier) then
return info
end
end
end
function NDCore.OneSync.SpawnObject(model, coords, heading, cb)
local entity = CreateObject(model, coords.x, coords.y, coords.z, true, false, false)
local value = lib.waitFor(function()
if DoesEntityExist(entity) then return entity end
end, "Failed to spawn object", 5000)
if not value then return end
SetEntityHeading(value, heading)
if not cb then return end
cb(value)
end
function NDCore.OneSync.SpawnPed(model, coords, heading, cb)
local entity = CreatePed(0, model, coords.x, coords.y, coords.z, heading, true, false)
local value = lib.waitFor(function()
if DoesEntityExist(entity) then return entity end
end, "Failed to spawn ped", 5000)
if not value or not cb then return end
cb(NetworkGetNetworkIdFromEntity(value))
end
function NDCore.OneSync.SpawnVehicle(model, coords, heading, properties, cb)
local vehicle = NDCore.createVehicle({
model = model,
coords = coords,
heading = heading
})
if not cb then return end
cb(vehicle.netId)
end
function NDCore.OneSync.SpawnPedInVehicle(model, vehicle, seat, cb)
local entity = CreatePedInsideVehicle(vehicle, 0, model, seat, true, false)
local value = lib.waitFor(function()
if DoesEntityExist(entity) then return entity end
end, "Failed to spawn ped", 5000)
if not value or not cb then return end
cb(value)
end
AddEventHandler("ND:characterUnloaded", function(src, character)
TriggerEvent("esx:playerDropped", src, "")
end)
AddEventHandler("ND:characterLoaded", function(character)
TriggerEvent("esx:playerLoaded", character.source, createPlayerFunctions(character))
end)

View File

@@ -0,0 +1,2 @@
if not lib.table.contains(Config.compatibility, "qb") then return end

View File

@@ -0,0 +1 @@
if not lib.table.contains(Config.compatibility, "qb") then return end

15
database/characters.sql Normal file
View File

@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS `nd_characters` (
`charid` INT(10) NOT NULL AUTO_INCREMENT,
`identifier` VARCHAR(200) NOT NULL DEFAULT '0',
`name` VARCHAR(50) DEFAULT NULL,
`firstname` VARCHAR(50) DEFAULT NULL,
`lastname` VARCHAR(50) DEFAULT NULL,
`dob` VARCHAR(50) DEFAULT NULL,
`gender` VARCHAR(50) DEFAULT NULL,
`cash` INT(10) DEFAULT '0',
`bank` INT(10) DEFAULT '0',
`groups` LONGTEXT DEFAULT '[]',
`metadata` LONGTEXT DEFAULT '[]',
`inventory` LONGTEXT DEFAULT '[]',
PRIMARY KEY (`charid`) USING BTREE
);

15
database/vehicles.sql Normal file
View File

@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS `nd_vehicles` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`owner` INT(11) DEFAULT NULL,
`plate` VARCHAR(255) DEFAULT NULL,
`glovebox` LONGTEXT DEFAULT '[]',
`trunk` LONGTEXT DEFAULT '[]',
`properties` LONGTEXT DEFAULT '[]',
`stored` INT(11) DEFAULT '1',
`impounded` INT(11) DEFAULT '0',
`stolen` INT(11) DEFAULT '0',
`metadata` LONGTEXT DEFAULT '[]',
PRIMARY KEY (`id`) USING BTREE,
INDEX `owner` (`owner`) USING BTREE,
CONSTRAINT `vehowner` FOREIGN KEY (`owner`) REFERENCES `nd_characters` (`charid`) ON UPDATE CASCADE ON DELETE CASCADE
);

View File

@@ -1,39 +1,42 @@
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
author "Andyyy#7666, N1K0#0001"
author "Andyyy#7666"
description "ND Framework Core"
version "1.0.3"
version "2.0.0"
fx_version "cerulean"
game "gta5"
lua54 "yes"
shared_scripts {
"config_client.lua",
"shared/main.lua"
}
shared_script "@ox_lib/init.lua"
client_scripts {
"client/main.lua",
"shared/functions.lua",
"client/peds.lua",
"client/vehicle/main.lua",
"client/vehicle/garages.lua",
"client/functions.lua",
"client/events.lua",
"shared/import.lua"
"client/death.lua",
"compatibility/**/client.lua"
}
server_scripts {
"@oxmysql/lib/MySQL.lua",
"config_server.lua",
"server/main.lua",
"shared/functions.lua",
"server/player.lua",
"server/vehicle.lua",
"server/functions.lua",
"server/events.lua",
"server/commands.lua",
"shared/import.lua"
"compatibility/**/server.lua",
"server/commands.lua"
}
exports {
"GetCoreObject"
}
server_exports {
"GetCoreObject"
files {
"init.lua",
"client/vehicle/data.lua",
"compatibility/**/locale.lua"
}
dependency "oxmysql"
-- provide "es_extended"
-- provide "qb-Core"

11
init.lua Normal file
View File

@@ -0,0 +1,11 @@
local nd_core = exports["ND_Core"]
NDCore = setmetatable({}, {
__index = function(self, index)
self[index] = function(...)
return nd_core[index](nil, ...)
end
return self[index]
end
})

View File

@@ -1,16 +0,0 @@
CREATE TABLE `characters` (
`character_id` INT(10) NOT NULL AUTO_INCREMENT,
`license` VARCHAR(200) NOT NULL DEFAULT '0',
`first_name` VARCHAR(50) DEFAULT NULL,
`last_name` VARCHAR(50) DEFAULT NULL,
`dob` VARCHAR(50) DEFAULT NULL,
`gender` VARCHAR(50) DEFAULT NULL,
`cash` INT(10) DEFAULT '0',
`bank` INT(10) DEFAULT '0',
`job` VARCHAR(50) DEFAULT NULL,
`phone_number` VARCHAR(20) DEFAULT NULL,
`data` LONGTEXT DEFAULT '[]',
`inventory` LONGTEXT DEFAULT '[]',
`last_location` LONGTEXT DEFAULT '[]',
PRIMARY KEY (`character_id`) USING BTREE
);

View File

@@ -1,117 +1,446 @@
NDCore.Functions.AddCommand("setmoney", "Admin command, manage player money.", function(source, args, rawCommand)
if not NDCore.Functions.IsPlayerAdmin(source) then return end
local target = tonumber(args[1])
local action = args[2]
local moneyType = args[3]:lower()
local amount = tonumber(args[4])
if not target or GetPlayerPing(target) == 0 then return end
if action ~= "remove" and action ~= "add" and action ~= "set" then return end
if moneyType ~= "bank" and moneyType ~= "cash" then return end
if action == "remove" then
if not amount or amount < 1 then return end
NDCore.Functions.DeductMoney(amount, target, moneyType, "Server staff action.")
elseif action == "add" then
if not amount or amount < 1 then return end
NDCore.Functions.AddMoney(amount, target, moneyType, "Server staff action.")
elseif action == "set" then
local character = NDCore.Functions.GetPlayer(target)
NDCore.Functions.SetPlayerData(character.id, moneyType, amount, "Server staff action.")
local moneyTypes = {"bank", "cash"}
local moneyActions = {
remove = function(player, account, amount)
player.deductMoney(account, amount, "Staff action")
return ("Removed $%d (%s) to %s"):format(amount, account, player.name), ("removed $%d from %s"):format(amount, account)
end,
add = function(player, account, amount)
player.addMoney(account, amount, "Staff action")
return ("Added $%d (%s) to %s"):format(amount, account, player.name), ("added $%d to %s"):format(amount, account)
end,
set = function(player, account, amount)
player.setData(account, amount, "Staff action")
return ("Set %s's (%s) to $%d"):format(player.name, account, amount), ("set %s to $%d"):format(account, amount)
end
end, true, {
{ name="player", help="Player server id" },
{ name="action", help="remove/add/set" },
{ name="type", help="bank/cash" },
{ name="amount" }
})
}
NDCore.Functions.AddCommand("setjob", "Admin command, set player job.", function(source, args, rawCommand)
if not NDCore.Functions.IsPlayerAdmin(source) then
return {
color = {255, 0, 0},
args = {"Error", "you don't have access to this command."}
lib.addCommand("setmoney", {
help = "Admin command, manage player money.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
},
{
name = "action",
type = "string",
help = "remove/add/set"
},
{
name = "type",
type = "string",
help = "bank/cash"
},
{
name = "amount",
type = "number"
}
}
}, function(source, args, raw)
local action = moneyActions[args.action]
local moneyType = args.type:lower()
if not action or not lib.table.contains(moneyTypes, moneyType) then return end
local player = NDCore.getPlayer(args.target)
if not player then return end
local staffMessage, userMessage = action(player, moneyType, args.amount)
player.notify({
title = "Staff action",
description = userMessage,
type = "inform",
duration = 10000
})
if not source then return end
TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {"Staff action", staffMessage}
})
end)
lib.addCommand("setjob", {
help = "Admin command, set a players job.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
},
{
name = "job",
type = "string",
help = "Job name"
},
{
name = "rank",
type = "number",
optional = true
}
}
}, function(source, args, raw)
local player = NDCore.getPlayer(args.target)
if not player then return end
local job = args.job:lower()
local jobInfo = player.setJob(job, args.rank)
if not player or not jobInfo then return end
player.notify({
title = "Staff action",
description = ("Job updated to %s, rank %s"):format(jobInfo.label, jobInfo.rankName),
type = "inform",
duration = 10000
})
if not source then return end
TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {"Staff action", "success"}
})
end)
lib.addCommand("setgroup", {
help = "Admin command, set a players group.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
},
{
name = "action",
type = "string",
help = "remove/add"
},
{
name = "group",
type = "string",
help = "group name"
},
{
name = "rank",
type = "number",
optional = true
}
}
}, function(source, args, raw)
local player = NDCore.getPlayer(args.target)
if not player then return end
if args.action == "add" then
local groupInfo = player.addGroup(args.group, args.rank)
if not groupInfo then return end
player.notify({
title = "Staff action",
description = ("Added to group %s, rank %s."):format(groupInfo.label, groupInfo.rankName),
type = "inform",
duration = 10000
})
elseif args.action == "remove" then
local groupInfo = player.removeGroup(args.group)
if not groupInfo then return end
player.notify({
title = "Staff action",
description = ("Removed from group %s."):format(groupInfo.label),
type = "inform",
duration = 10000
})
else
return
end
if not source then return end
TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {"Staff action", "success"}
})
end)
lib.addCommand("skin", {
help = "Admin command, set player into character clothing menu.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
TriggerClientEvent("ND:clothingMenu", args.target)
end)
lib.addCommand("character", {
help = "Admin command, set player into character selection menu.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
TriggerClientEvent("ND:characterMenu", args.target)
end)
lib.addCommand("pay", {
help = "give money to nearby player.",
params = {
{
name = "amount",
type = "number"
}
}
}, function(source, args, raw)
if not source then return end
local targetPlayer
local pedCoords = GetEntityCoords(GetPlayerPed(source))
for targetId, targetInfo in pairs(NDCore.players) do
local targetCoords = GetEntityCoords(GetPlayerPed(targetId))
if #(pedCoords-targetCoords) < 2.0 and targetId ~= source then
targetPlayer = targetInfo
break
end
end
local player = NDCore.getPlayer(source)
if not player then return end
local success = player.deductMoney("cash", args.amount)
if not success then
return player.notify({
title = "Couldn't give money",
type = "error",
duration = 5000
})
end
local target = tonumber(args[1])
if not target or GetPlayerPing(target) == 0 then
return {
color = {255, 0, 0},
args = {"Error", "target player not found."}
}
end
if not targetPlayer or not targetPlayer.addMoney("cash", args.amount) then return end
targetPlayer.notify({
title = "Money received",
description = ("Received $%d in cash"):format(args.amount),
type = "inform",
duration = 10000
})
player.notify({
title = "Money given",
description = ("You gave someone $%d in cash"):format(args.amount),
type = "inform",
duration = 10000
})
end)
local job = args[2]
if not job then
return {
color = {255, 0, 0},
args = {"Error", "job required."}
}
end
lib.addCommand("unlock", {
help = "Admin force unlock vehicles",
restricted = "group.admin",
}, function(source, args, raw)
local ped = GetPlayerPed(source)
local playerVeh = GetVehiclePedIsIn(ped)
local coords = GetEntityCoords(ped)
local vehicles = GetAllVehicles()
local maxDistance = 2.0
local closestVehicle
local character = NDCore.Functions.GetPlayer(target)
NDCore.Functions.SetPlayerJob(character.id, job, args[3])
return {
color = {0, 255, 0},
args = {"Success", GetPlayerName(target) .. " job set to " .. job .. (args[3] and " rank " .. args[3] or " rank 1.")}
if not playerVeh or playerVeh == 0 or not DoesEntityExist(playerVeh) then
for i=1, #vehicles do
local vehicle = vehicles[i]
local vehicleCoords = GetEntityCoords(vehicle)
local distance = #(coords-vehicleCoords)
if distance < maxDistance then
maxDistance = distance
closestVehicle = vehicle
end
end
if not closestVehicle or not DoesEntityExist(closestVehicle) then return end
local state = Entity(closestVehicle).state
state.locked = false
else
local state = Entity(playerVeh).state
state.hotwired = true
end
end)
lib.addCommand("revive", {
help = "Admin command, revive player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
end, false, {
{ name="player", help="Player server id" },
{ name="job name" },
{ name="rank", help="This should be a number, default value is 1." }
})
}, function(source, args, raw)
local player = NDCore.getPlayer(args.target)
if not player then return end
player.revive()
end)
NDCore.Functions.AddCommand("setgroup", "Admin command, set player group.", function(source, args, rawCommand)
if not NDCore.Functions.IsPlayerAdmin(source) then return end
local target = tonumber(args[1])
if not target or GetPlayerPing(target) == 0 then
return {
color = {255, 0, 0},
args = {"Error", "target player not found."}
lib.addCommand("dv", {
help = "Admin command, delete vehicles within the range.",
restricted = "group.admin",
params = {
{
name = "range",
type = "number",
help = "The range to select vehicles for deleteion from",
optional = true
}
end
local group = args[3]
if not group then
return {
color = {255, 0, 0},
args = {"Error", "group required."}
}
end
local character = NDCore.Functions.GetPlayer(target)
if args[2] == "remove" then
NDCore.Functions.RemovePlayerFromGroup(character.id, group)
return {
color = {0, 255, 0},
args = {"Success", GetPlayerName(target) .. " removed from " .. group}
}
elseif args[2] == "add" then
local rank = args[4]
NDCore.Functions.SetPlayerToGroup(character.id, group, rank)
return {
color = {0, 255, 0},
args = {"Success", GetPlayerName(target) .. " added to " .. group .. (rank and " rank " .. rank or " rank 1.")}
}
end
end, false, {
{ name="player", help="Player server id" },
{ name="action", help="remove/add"},
{ name="group", help="Group name, make sure it's correct or it won't work."},
{ name="rank", help="This should be a number, default value is 1 (not required if removing)." }
})
NDCore.Functions.AddCommand("pay", "give cash to a nearby player", function(source, args, rawCommand)
local amount = args[1]
if not amount or amount == 0 then return end
NDCore.Functions.GiveCashToNearbyPlayer(source, amount)
return {
color = {0, 255, 0},
args = {"Success", amount .. " paid."}
}
end, true, {
{ name="Amount" },
})
}, function(source, args, raw)
local ped = GetPlayerPed(source)
local veh = GetVehiclePedIsIn(ped)
if veh and veh ~= 0 and DoesEntityExist(veh) then
DeleteEntity(veh)
return TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {"Staff action", "deleted 1 vehicle"}
})
end
local count = 0
local coords = GetEntityCoords(ped)
if args.range then
for _, veh in ipairs(GetAllVehicles()) do
local vehDist = #(GetEntityCoords(veh) - coords)
if vehDist < args.range then
DeleteEntity(veh)
count += 1
end
end
return TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {"Staff action", ("deleted %d vehicles"):format(count)}
})
end
local closest, dist
for _, veh in ipairs(GetAllVehicles()) do
local vehDist = #(GetEntityCoords(veh) - coords)
if vehDist < 5.0 and not closest or (dist and dist > vehDist) then
closest = veh
dist = vehDist
end
end
if not closest then return "no vehicle found nearby" end
DeleteEntity(closest)
TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {"Staff action", "deleted 1 vehicle"}
})
end)
lib.addCommand("goto", {
help = "Admin command, teleport to a player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
if args.target == source then return end
local target = GetPlayerPed(args.target)
local coords = GetEntityCoords(target)
local ped = GetPlayerPed(source)
SetEntityCoords(ped, coords.x, coords.y, coords.z)
end)
lib.addCommand("bring", {
help = "Admin command, teleport a player to you.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
if args.target == source then return end
local ped = GetPlayerPed(source)
local coords = GetEntityCoords(ped)
local targetPed = GetPlayerPed(args.target)
SetEntityCoords(targetPed, coords.x, coords.y, coords.z)
end)
lib.addCommand("freeze", {
help = "Admin command, freeze a player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
local ped = GetPlayerPed(args.target)
FreezeEntityPosition(ped, true)
end)
lib.addCommand("unfreeze", {
help = "Admin command, unfreeze a player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
local ped = GetPlayerPed(args.target)
FreezeEntityPosition(ped, false)
end)
lib.addCommand("vehicle", {
help = "Admin command, unfreeze a player.",
restricted = "group.admin",
params = {
{
name = "model",
type = "string",
help = "Name of the vehicle to spawn"
}
}
}, function(source, args, raw)
local player = NDCore.getPlayer(source)
if not player then return end
local ped = GetPlayerPed(source)
local coords = GetEntityCoords(ped)
local heading = GetEntityHeading(ped)
local info = NDCore.createVehicle({
owner = player.id,
coords = coords,
heading = heading,
model = GetHashKey(args.model)
})
local veh = info.entity
for i=1, 10 do
if GetPedInVehicleSeat(veh, -1) ~= ped then
SetPedIntoVehicle(ped, veh, -1)
else
break
end
Wait(100)
end
end)

View File

@@ -1,766 +1,99 @@
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
-- Get an active players character data.
function NDCore.Functions.GetPlayer(player)
return NDCore.Players[player]
---@param src number
---@return table
function NDCore.getPlayer(src)
return NDCore.players[src]
end
-- Get all active players character data.
function NDCore.Functions.GetPlayers(getBy, value)
if not getBy or not value then
return NDCore.Players
end
---@param metadata string
---@param data any
---@return table
function NDCore.getPlayers(key, value, returnArray)
if not key or not value then return NDCore.players end
local players = {}
local keyTypes = {id = "id", firstname = "firstname", lastname = "lastname", gender = "gender", groups = "groups"}
local findBy = keyTypes[key] or "metadata"
if getBy == "groups" then
for player, playerInfo in pairs(NDCore.Players) do
if playerInfo.data.groups then
local valueGroup = value:lower()
for group, _ in pairs(playerInfo.data.groups) do
if group and group:lower() == valueGroup then
players[player] = playerInfo
end
if findBy then
for src, info in pairs(NDCore.players) do
if findBy == "metadata" and info["metadata"][key] == value or info[findBy] == value then
if returnArray then
players[#players+1] = info
else
players[src] = info
end
end
end
else
for player, playerInfo in pairs(NDCore.Players) do
if playerInfo[getBy] == value then
players[player] = playerInfo
end
end
end
return players
end
local discordErrors = {
[400] = "improper http request",
[401] = "Discord bot token might be missing or incorrect",
[404] = "user might not be in server.",
[429] = "Discord bot rate limited."
}
-- Used to retrive the players discord server nickname, discord name and tag, and the roles.
function NDCore.Functions.GetUserDiscordInfo(discordUserId)
local data
local timeout = 0
PerformHttpRequest("https://discordapp.com/api/guilds/" .. server_config.guildId .. "/members/" .. discordUserId, function(errorCode, resultData, resultHeaders)
if errorCode ~= 200 then
print("Error: " .. errorCode .. " " .. discordErrors[errorCode])
end
local result = json.decode(resultData)
local roles = {}
local nickname = ""
local tag = ""
if result and result.roles then
for _, roleId in pairs(result.roles) do
roles[roleId] = roleId
end
if result.nick then
nickname = result.nick
end
if result.user and result.user.username and result.user.discriminator then
tag = tostring(result.user.username) .. "#" .. tostring(result.user.discriminator)
end
data = {
nickname = nickname,
discordTag = tag,
roles = roles
}
return
end
data = {
nickname = nickname,
discordTag = tag,
roles = roles
}
end, "GET", "", {["Content-Type"] = "application/json", ["Authorization"] = "Bot " .. server_config.discordServerToken})
while not data do
Wait(1000)
timeout = timeout + 1
if timeout > 5 then
break
---@param source number
---@return table
function NDCore.getPlayerServerInfo(source)
return PlayersInfo[source]
end
---@param fileLocation string|tabale
---@return boolean
function NDCore.loadSQL(fileLocation, resource)
local resourceName = resource or GetInvokingResource() or GetCurrentResourceName()
if type(fileLocation) == "string" then
local file = LoadResourceFile(resourceName, fileLocation)
if not file then return end
MySQL.query(file)
return true
end
for i=1, #fileLocation do
local file = LoadResourceFile(resourceName, fileLocation[i])
if file then
MySQL.query(file)
Wait(100)
end
end
return true
end
function NDCore.getDiscordInfo(discordUserId)
if not discordUserId or not Config.discordBotToken or not Config.discordGuildId then return end
local done = false
local data
local discordErrors = {
[400] = "Improper HTTP request",
[401] = "Discord bot token might be missing or incorrect",
[404] = "User might not be in the server",
[429] = "Discord bot rate limited"
}
if type(discordUserId) == "string" and discordUserId:find("discord:") then discordUserId:gsub("discord:", "") end
PerformHttpRequest(("https://discordapp.com/api/guilds/%s/members/%s"):format(Config.discordGuildId, discordUserId), function(errorCode, resultData, resultHeaders)
if errorCode ~= 200 then
done = true
return print(("^3Warning: %d %s"):format(errorCode, discordErrors[errorCode]))
end
local result = json.decode(resultData)
data = {
nickname = result.nick or result.user.username,
user = result.user,
roles = result.roles
}
done = true
end, "GET", "", {["Content-Type"] = "application/json", ["Authorization"] = ("Bot %s"):format(Config.discordBotToken)})
while not done do Wait(50) end
return data
end
-- Get player any identifier, available types: steam, license, xbl, ip, discord, live.
function NDCore.Functions.GetPlayerIdentifierFromType(type, player)
local identifierCount = GetNumPlayerIdentifiers(player)
for count = 0, identifierCount do
local identifier = GetPlayerIdentifier(player, count)
if identifier and string.find(identifier, type) then
return identifier
end
end
return nil
function NDCore.enableMultiCharacter(enable)
Config.multiCharacter = enable
end
-- This will return the server id and ND player data of a nearby player.
function NDCore.Functions.GetNearbyPedToPlayer(player)
local pedCoords = GetEntityCoords(GetPlayerPed(player))
for targetId, targetInfo in pairs(NDCore.Players) do
local targetCoords = GetEntityCoords(GetPlayerPed(targetId))
if #(pedCoords - targetCoords) < 2.0 and targetId ~= player then
return targetId, targetInfo
end
end
end
-- update the players money on the client kinda like a refresh.
function NDCore.Functions.UpdateMoney(player)
local player = tonumber(player)
local result = MySQL.query.await("SELECT cash, bank FROM characters WHERE character_id = ? LIMIT 1", {NDCore.Players[player].id})
if result then
local cash = result[1].cash
local bank = result[1].bank
NDCore.Players[player].cash = cash
NDCore.Players[player].bank = bank
TriggerClientEvent("ND:updateMoney", player, cash, bank)
for name, func in pairs(NDCore) do
if type(func) == "function" then
exports(name, func)
end
end
-- Transfer money from one players bank account to another.
function NDCore.Functions.TransferBank(amount, player, target, descriptionSender, descriptionReceiver)
local amount = tonumber(amount)
local player = tonumber(player)
local target = tonumber(target)
if player == target then
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "You can't send money to yourself."}
})
return false
elseif GetPlayerPing(target) == 0 then
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "That player does not exist."}
})
return false
elseif amount <= 0 then
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "You can't send that amount."}
})
return false
elseif NDCore.Players[player].bank < amount then
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "You don't have enough money."}
})
return false
else
MySQL.query.await("UPDATE characters SET bank = bank - ? WHERE character_id = ?", {amount, NDCore.Players[player].id})
NDCore.Functions.UpdateMoney(player)
TriggerEvent("ND:moneyChange", player, "bank", amount, "remove", descriptionSender or "Transfer")
TriggerClientEvent("chat:addMessage", player, {
color = {0, 255, 0},
args = {"Success", "You paid " .. NDCore.Players[target].firstName .. " " .. NDCore.Players[target].lastName .. " $" .. amount .. "."}
})
MySQL.query.await("UPDATE characters SET bank = bank + ? WHERE character_id = ?", {amount, NDCore.Players[target].id})
NDCore.Functions.UpdateMoney(target)
TriggerEvent("ND:moneyChange", target, "bank", amount, "add", descriptionReceiver or "Transfer")
TriggerClientEvent("chat:addMessage", target, {
color = {0, 255, 0},
args = {"Success", NDCore.Players[player].firstName .. " " .. NDCore.Players[player].lastName .. " sent you $" .. amount .. "."}
})
return true
end
end
-- Give cash from one players wallet to another.
function NDCore.Functions.GiveCash(amount, player, target)
local amount = tonumber(amount)
local player = tonumber(player)
local target = tonumber(target)
if player == target then
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "You can't give money to yourself."}
})
return false
elseif GetPlayerPing(target) == 0 then
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "That player does not exist."}
})
return false
elseif amount <= 0 then
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "You can't give that amount."}
})
return false
elseif NDCore.Players[player].cash < amount then
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "You don't have enough money."}
})
return false
else
MySQL.query.await("UPDATE characters SET cash = cash - ? WHERE character_id = ?", {amount, NDCore.Players[player].id})
NDCore.Functions.UpdateMoney(player)
TriggerEvent("ND:moneyChange", player, "cash", amount, "remove")
TriggerClientEvent("chat:addMessage", player, {
color = {0, 255, 0},
args = {"Success", "You gave " .. NDCore.Players[target].firstName .. " " .. NDCore.Players[target].lastName .. " $" .. amount .. "."}
})
MySQL.query.await("UPDATE characters SET cash = cash + ? WHERE character_id = ?", {amount, NDCore.Players[target].id})
NDCore.Functions.UpdateMoney(target)
TriggerEvent("ND:moneyChange", target, "cash", amount, "add")
TriggerClientEvent("chat:addMessage", target, {
color = {0, 255, 0},
args = {"Success", " Received $" .. amount .. "."}
})
return true
end
end
-- Give money from a players wallet to a nearby player.
function NDCore.Functions.GiveCashToNearbyPlayer(player, amount)
local targetId = NDCore.Functions.GetNearbyPedToPlayer(player)
if targetId then
NDCore.Functions.GiveCash(amount, player, targetId)
return true
end
TriggerClientEvent("chat:addMessage", player, {
color = {255, 0, 0},
args = {"Error", "No players nearby."}
})
return false
end
-- withdraws money from a players bank account to their wallet/cash.
function NDCore.Functions.WithdrawMoney(amount, player)
local amount = tonumber(amount)
local player = tonumber(player)
if amount <= 0 then return false end
if NDCore.Players[player].bank < amount then return false end
MySQL.query.await("UPDATE characters SET bank = bank - ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id})
MySQL.query.await("UPDATE characters SET cash = cash + ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id})
NDCore.Functions.UpdateMoney(player)
TriggerEvent("ND:moneyChange", player, "bank", amount, "remove", "Withdraw")
TriggerEvent("ND:moneyChange", player, "cash", amount, "add", "Withdraw")
return true
end
-- deposits money from a players wallet/cash to their bank account.
function NDCore.Functions.DepositMoney(amount, player)
local amount = tonumber(amount)
local player = tonumber(player)
if amount <= 0 then return false end
if NDCore.Players[player].cash < amount then return false end
MySQL.query.await("UPDATE characters SET cash = cash - ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id})
MySQL.query.await("UPDATE characters SET bank = bank + ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id})
NDCore.Functions.UpdateMoney(player)
TriggerEvent("ND:moneyChange", player, "cash", amount, "remove", "Deposit")
TriggerEvent("ND:moneyChange", player, "bank", amount, "add", "Deposit")
return true
end
-- Deducts money from the player, "bank" or "cash" needs to be specified.
function NDCore.Functions.DeductMoney(amount, player, from, description)
local amount = tonumber(amount)
local player = tonumber(player)
if from == "bank" then
MySQL.query.await("UPDATE characters SET bank = bank - ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id})
elseif from == "cash" then
MySQL.query.await("UPDATE characters SET cash = cash - ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id})
end
NDCore.Functions.UpdateMoney(player)
TriggerEvent("ND:moneyChange", player, from, amount, "remove", description)
end
-- Adds money from the player, "bank" or "cash" needs to be specified.
function NDCore.Functions.AddMoney(amount, player, to, description)
local amount = tonumber(amount)
local player = tonumber(player)
if to == "bank" then
MySQL.query.await("UPDATE characters SET bank = bank + ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id})
elseif to == "cash" then
MySQL.query.await("UPDATE characters SET cash = cash + ? WHERE character_id = ? LIMIT 1", {amount, NDCore.Players[player].id})
end
NDCore.Functions.UpdateMoney(player)
TriggerEvent("ND:moneyChange", player, to, amount, "add", description)
end
-- Adds the players character to the NDCore.Players table, this table consists of every players selected character.
function NDCore.Functions.SetActiveCharacter(player, characterId)
if NDCore.Players[player] then
TriggerEvent("ND:characterUnloaded", player, NDCore.Players[player])
end
local result = MySQL.query.await("SELECT * FROM characters WHERE character_id = ? LIMIT 1", {characterId})
if result then
local i = result[1]
NDCore.Players[player] = {
source = player,
id = characterId,
firstName = i.first_name,
lastName = i.last_name,
dob = i.dob,
gender = i.gender,
cash = i.cash,
bank = i.bank,
phoneNumber = i.phone_number,
lastLocation = json.decode(i.last_location),
inventory = json.decode(i.inventory),
discordInfo = NDCore.PlayersDiscordInfo[player],
data = json.decode(i.data),
job = i.job
}
end
NDCore.Functions.RefreshCommands(player)
TriggerEvent("ND:characterLoaded", NDCore.Players[player])
TriggerClientEvent("ND:setCharacter", player, NDCore.Players[player])
end
-- This returns all the characters the player has.
function NDCore.Functions.GetPlayerCharacters(player)
local characters = {}
local result = MySQL.query.await("SELECT * FROM characters WHERE license = ?", {NDCore.Functions.GetPlayerIdentifierFromType("license", player)})
for i = 1, #result do
local temp = result[i]
characters[temp.character_id] = {
id = temp.character_id,
firstName = temp.first_name,
lastName = temp.last_name,
dob = temp.dob,
gender = temp.gender,
cash = temp.cash,
bank = temp.bank,
phoneNumber = temp.phone_number,
lastLocation = json.decode(temp.last_location),
inventory = json.decode(temp.inventory),
discordInfo = NDCore.PlayersDiscordInfo[player],
data = json.decode(temp.data),
job = temp.job
}
end
return characters
end
-- Creates a new character for the player and returns all their characters to the client.
function NDCore.Functions.CreateCharacter(player, firstName, lastName, dob, gender, cb)
local characterId = false
local license = NDCore.Functions.GetPlayerIdentifierFromType("license", player)
local result = MySQL.query.await("SELECT character_id FROM characters WHERE license = ?", {license})
if result and config.characterLimit > #result then
characterId = MySQL.insert.await("INSERT INTO characters (license, first_name, last_name, dob, gender, cash, bank, data) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", {license, firstName, lastName, dob, gender, config.startingCash, config.startingBank, json.encode({groups={}})})
if cb then cb(characterId) end
TriggerClientEvent("ND:returnCharacters", player, NDCore.Functions.GetPlayerCharacters(player))
end
return characterId
end
-- Update/edit a character info by character id.
function NDCore.Functions.UpdateCharacter(characterId, firstName, lastName, dob, gender)
local result = MySQL.query.await("UPDATE characters SET first_name = ?, last_name = ?, dob = ?, gender = ? WHERE character_id = ? LIMIT 1", {firstName, lastName, dob, gender, characterId})
return result
end
-- Delete a character by character id.
function NDCore.Functions.DeleteCharacter(characterId)
local result = MySQL.query.await("DELETE FROM characters WHERE character_id = ? LIMIT 1", {characterId})
return result
end
-- Updates the player's data
function NDCore.Functions.SetPlayerData(characterId, key, value, description)
if not key then return end
local player = nil
for id, character in pairs(NDCore.Players) do
if character.id == characterId then
player = id
break
end
end
if key == "cash" then
if player then
NDCore.Players[player][key] = value
TriggerEvent("ND:moneyChange", player, "cash", tonumber(value), "set", description)
end
MySQL.query.await("UPDATE characters SET cash = ? WHERE character_id = ?", {tonumber(value), characterId})
elseif key == "bank" then
if player then
NDCore.Players[player][key] = value
TriggerEvent("ND:moneyChange", player, "bank", tonumber(value), "set", description)
end
MySQL.query.await("UPDATE characters SET bank = ? WHERE character_id = ?", {tonumber(value), characterId})
elseif key == "job" then
if player then
NDCore.Players[player].job = value
end
MySQL.query.await("UPDATE characters SET job = ? WHERE character_id = ?", {value, characterId})
else
if player then
NDCore.Players[player].data[key] = value
MySQL.query.await("UPDATE characters SET `data` = ? WHERE character_id = ?", {json.encode(NDCore.Players[player].data), characterId})
else
local result = MySQL.query.await("SELECT `data` FROM characters WHERE character_id = ?", {characterId})
if not result or not result[1] then return end
local data = json.decode(result[1].data)
data[key] = value
MySQL.query.await("UPDATE characters SET `data` = ? WHERE character_id = ?", {json.encode(data), characterId})
end
end
if not player then return end
TriggerClientEvent("ND:updateCharacter", player, NDCore.Players[player])
end
-- Get a character by the character id.
function NDCore.Functions.GetPlayerByCharacterId(id)
for _, character in pairs(NDCore.Players) do
if character.id == id then
return character
end
end
end
-- Generate a random string with letters and numbers.
function randomString(length)
local number = {}
for i = 1, length do
number[i] = math.random(0, 1) == 1 and string.char(math.random(65, 90)) or math.random(0, 9)
end
return table.concat(number)
end
-- Give a player a license.
function NDCore.Functions.CreatePlayerLicense(characterId, licenseType, expire)
local expireIn = tonumber(expire)
if not expireIn then
expireIn = 2592000
end
local time = os.time()
local license = {
type = licenseType,
status = "valid",
issued = time,
expires = time+expireIn,
identifier = randomString(16)
}
local character = NDCore.Functions.GetPlayerByCharacterId(characterId)
if character then
local data = character.data
if not data.licences then
data.licences = {}
end
character.data.licences[#character.data.licences+1] = license
NDCore.Functions.SetPlayerData(character.id, "licences", character.data.licences)
return true
end
local result = MySQL.query.await("SELECT data FROM characters WHERE character_id = ?", {characterId})
if result and result[1] then
local data = result[1].data
if not data.licences then
data.licences = {}
end
data.licences[#data.licences+1] = license
NDCore.Functions.SetPlayerData(character.id, "licences", data.licences)
return true
end
end
-- find a players license by it's identifier.
function NDCore.Functions.FindLicenseByIdentifier(licences, identifier)
for key, license in pairs(licences) do
if license.identifier == identifier then
return license
end
end
return {}
end
-- Edit a license by the license identifier.
function NDCore.Functions.EditPlayerLicense(characterId, identifier, newData)
local licences = {}
local character = NDCore.Functions.GetPlayerByCharacterId(characterId)
if character then
licences = character.data.licences
else
local result = MySQL.query.await("SELECT data FROM characters WHERE character_id = ?", {characterId})
if result and result[1] then
local data = result[1].data
if not data.licences then
data.licences = {}
end
licences = data.licences
end
end
local license = NDCore.Functions.FindLicenseByIdentifier(licences, identifier)
for k, v in pairs(newData) do
license[k] = v
end
NDCore.Functions.SetPlayerData(characterId, "licences", licences)
return licences
end
-- Set the players job and job rank.
function NDCore.Functions.SetPlayerJob(characterId, job, rank)
if not job then return end
local jobRank = tonumber(rank)
if not jobRank then
jobRank = 1
end
local result = MySQL.query.await("SELECT job FROM characters WHERE character_id = ?", {characterId})
if result and result[1] then
local character = NDCore.Functions.GetPlayerByCharacterId(characterId)
if character then
local oldRank = 1
if character.data.groups and character.data.groups[character.job] then
oldRank = character.data.groups[character.job].rank
end
TriggerEvent("ND:jobChanged", character.source, {name = job, rank = jobRank}, {name = character.job, rank = oldRank})
TriggerClientEvent("ND:jobChanged", character.source, {name = job, rank = jobRank}, {name = character.job, rank = oldRank})
end
NDCore.Functions.RemovePlayerFromGroup(characterId, result[1].job)
end
NDCore.Functions.SetPlayerData(characterId, "job", job)
NDCore.Functions.SetPlayerToGroup(characterId, job, jobRank)
end
-- Set a player to a group.
function NDCore.Functions.SetPlayerToGroup(characterId, group, rank)
local groupRank = tonumber(rank)
if not groupRank then
groupRank = 1
end
local group = group:lower()
for groupName, groupRanks in pairs(config.groups) do
if groupName:lower() == group then
group = groupName
break
end
end
local character = NDCore.Functions.GetPlayerByCharacterId(characterId)
if character then
local data = character.data
if not data.groups then
data.groups = {}
end
local rankName = tostring(groupRank)
if config.groups[group] and config.groups[group][groupRank] then
rankName = config.groups[group][groupRank]
end
data.groups[group] = {
rank = groupRank,
rankName = rankName
}
NDCore.Functions.SetPlayerData(characterId, "groups", data.groups)
return true
end
local result = MySQL.query.await("SELECT data FROM characters WHERE character_id = ?", {characterId})
if not result or not result[1] then return end
local data = json.decode(result[1].data)
if not data then
data = {}
end
if not data.groups then
data.groups = {}
end
local rankName = tostring(groupRank)
if config.groups[group] and config.groups[group][groupRank] then
rankName = config.groups[group][groupRank]
end
data.groups[group] = {
rank = groupRank,
rankName = rankName
}
NDCore.Functions.SetPlayerData(characterId, "groups", data.groups)
return true
end
-- Remove a player from a group.
function NDCore.Functions.RemovePlayerFromGroup(characterId, group)
if not group then return end
local group = group:lower()
for groupName, groupRanks in pairs(config.groups) do
if groupName:lower() == group then
group = groupName
break
end
end
local character = NDCore.Functions.GetPlayerByCharacterId(characterId)
if character then
local data = character.data
if not data.groups then
data.groups = {}
end
data.groups[group] = nil
NDCore.Functions.SetPlayerData(characterId, "groups", data.groups)
return true
end
local result = MySQL.query.await("SELECT data FROM characters WHERE character_id = ?", {characterId})
if result and result[1] then
local data = result[1].data
if not data.groups then
data.groups = {}
end
data.groups[group] = nil
NDCore.Functions.SetPlayerData(characterId, "groups", data.groups)
return true
end
end
-- Update the characters last location into the database.
function NDCore.Functions.UpdateLastLocation(characterId, location)
local result = MySQL.query.await("UPDATE characters SET last_location = ? WHERE character_id = ? LIMIT 1", {json.encode(location), characterId})
return result
end
function NDCore.Functions.AddCommand(name, help, callback, argsrequired, arguments)
local commandName = name:lower()
if NDCore.Commands[commandName] then print("/" .. commandName .. " has already been registered.") return end
local arguments = arguments or {}
RegisterCommand(commandName, function(source, args, rawCommand)
if argsrequired and #args < #arguments then
return TriggerClientEvent("chat:addMessage", source, {
color = {255, 0, 0},
multiline = true,
args = {"Error", "all arguments required."}
})
end
local message = callback(source, args, rawCommand)
if not message then return end
TriggerClientEvent("chat:addMessage", source, message)
end, false)
NDCore.Commands[commandName] = {
name = commandName,
help = help,
callback = callback,
argsrequired = argsrequired,
arguments = arguments
}
end
function NDCore.Functions.RefreshCommands(source)
local suggestions = {}
for command, info in pairs(NDCore.Commands) do
suggestions[#suggestions + 1] = {
name = "/" .. command,
help = info.help,
params = info.arguments
}
end
TriggerClientEvent("chat:addSuggestions", source, suggestions)
end
function NDCore.Functions.IsPlayerAdmin(src)
local discordInfo = NDCore.PlayersDiscordInfo[src]
if not discordInfo or not discordInfo.roles then return end
for _, adminRole in pairs(config.adminRoles) do
for _, role in pairs(discordInfo.roles) do
if role == adminRole then return true end
end
end
end
function NDCore.Functions.VersionChecker(expectedResourceName, resourceName, downloadLink, rawGithubLink)
if expectedResourceName ~= resourceName then
print(("^4%s ^1WARNING^0"):format(expectedResourceName))
print(("Change the resource name to ^4%s ^0or else it won't work properly!"):format(expectedResourceName))
StopResource(resourceName)
return
end
PerformHttpRequest(rawGithubLink, function(errorCode, resultData, resultHeaders)
local i, j = tostring(resultData):find("version")
if not i or not j then return end
local resultData = tostring(resultData):sub(i, j + 12)
local resultData = resultData:gsub("version \"", "")
local i, j = resultData:find("\"")
local resultData = resultData:sub(1, i - 1)
local githubVersion = resultData:gsub("%.", "")
local fileVersion = GetResourceMetadata(expectedResourceName, "version", 0):gsub("%.", "")
if not githubVersion and not fileVersion then
print(("^4%s ^1WARNING^0"):format(expectedResourceName))
print(("You may not have the latest version of ^4%s^0. A newer, improved version may be present at ^5%s^0"):format(expectedResourceName, downloadLink))
elseif githubVersion > fileVersion then
local oldVersion = ("%s.%s.%s"):format(fileVersion:sub(1, 1), fileVersion:sub(2, 2), fileVersion:sub(3, 3))
local newVersion = ("%s.%s.%s"):format(githubVersion:sub(1, 1), githubVersion:sub(2, 2), githubVersion:sub(3, 3))
print(("^4%s ^1WARNING^0"):format(expectedResourceName))
print(("^4%s ^0is outdated. Please update it from ^5%s ^0| Current Version: ^1%s ^0| New Version: ^2%s ^0|"):format(expectedResourceName, downloadLink, oldVersion, newVersion))
elseif githubVersion < fileVersion then
local oldVersion = ("%s.%s.%s"):format(fileVersion:sub(1, 1), fileVersion:sub(2, 2), fileVersion:sub(3, 3))
local newVersion = ("%s.%s.%s"):format(githubVersion:sub(1, 1), githubVersion:sub(2, 2), githubVersion:sub(3, 3))
print(("^4%s ^1WARNING^0"):format(expectedResourceName))
print(("^4%s ^0version number is higher than expected | Current Version: ^3%s ^0| Expected Version: ^2%s ^0|"):format(expectedResourceName, oldVersion, newVersion))
else
local newVersion = ("%s.%s.%s"):format(githubVersion:sub(1, 1), githubVersion:sub(2, 2), githubVersion:sub(3, 3))
print(("^4%s ^0is up to date | Current Version: ^2%s ^0|"):format(expectedResourceName, newVersion))
end
end)
end
NDCore.Functions.VersionChecker("ND_Core", GetCurrentResourceName(), "https://github.com/ND-Framework/ND_Core", "https://raw.githubusercontent.com/ND-Framework/ND_Core/main/fxmanifest.lua")
-- Callbacks are licensed under LGPL v3.0
-- <https://github.com/overextended/ox_lib>
NDCore.callback = {}
local events = {}
RegisterNetEvent("ND:callbacks", function(key, ...)
local cb = events[key]
return cb and cb(...)
end)
function triggerCallback(_, name, playerId, cb, ...)
local key = ("%s:%s:%s"):format(name, math.random(0, 100000), playerId)
TriggerClientEvent(("ND:%s_cb"):format(name), playerId, key, ...)
local promise = not cb and promise.new()
events[key] = function(response, ...)
response = { response, ... }
events[key] = nil
if promise then
return promise:resolve(response)
end
if cb then
cb(table.unpack(response))
end
end
if promise then
return table.unpack(Citizen.Await(promise))
end
end
setmetatable(NDCore.callback, {
__call = triggerCallback
})
function NDCore.callback.await(name, playerId, ...)
return triggerCallback(nil, name, playerId, false, ...)
end
function NDCore.callback.register(name, callback)
RegisterNetEvent(("ND:%s_cb"):format(name), function(key, ...)
local src = source
TriggerClientEvent("ND:callbacks", src, key, callback(src, ...))
end)
end

View File

@@ -1,21 +1,154 @@
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
NDCore = {}
NDCore.Players = {}
NDCore.Functions = {}
NDCore.Commands = {}
NDCore.PlayersDiscordInfo = {}
NDCore.Config = config
NDCore.players = {}
PlayersInfo = {}
local resourceName = GetCurrentResourceName()
local tempPlayersInfo = {}
function GetCoreObject()
return NDCore
Config = {
serverName = GetConvar("core:serverName", "Unconfigured ND-Core Server"),
discordInvite = GetConvar("core:discordInvite", "https://discord.gg/Z9Mxu72zZ6"),
discordAppId = GetConvar("core:discordAppId", "858146067018416128"),
discordAsset = GetConvar("core:discordAsset", "andyyy"),
discordAssetSmall = GetConvar("core:discordAssetSmall", "andyyy"),
discordActionText = GetConvar("core:discordActionText", "DISCORD"),
discordActionLink = GetConvar("discordActionLink", "https://discord.gg/Z9Mxu72zZ6"),
discordActionText2 = GetConvar("core:discordActionText2", "STORE"),
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
characterIdentifier = GetConvar("core:characterIdentifier", "license"),
discordGuildId = GetConvar("core:discordGuildId", "false"),
discordBotToken = GetConvar("core:discordBotToken", "false"),
randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30),
disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1,
useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1,
groups = json.decode(GetConvar("core:groups", "[]")),
admins = json.decode(GetConvar("core:admins", "[]")),
adminDiscordRoles = json.decode(GetConvar("core:adminDiscordRoles", "[]")),
multiCharacter = false,
compatibility = json.decode(GetConvar("core:compatibility", "[]"))
}
SetConvarServerInfo("Discord", Config.discordInvite)
SetConvarServerInfo("NDCore", GetResourceMetadata(resourceName, "version", 0) or "invalid")
SetConvarReplicated("inventory:framework", "nd")
local function getIdentifierList(src)
local list = {}
for i=0, GetNumPlayerIdentifiers(src) do
local identifier = GetPlayerIdentifier(src, i)
if identifier then
local colon = identifier:find(":")
local identifierType = identifier:sub(1, colon-1)
list[identifierType] = identifier
end
end
return list
end
isResourceStarted("ox_inventory", function(started)
if not started then return end
SetConvarReplicated("inventory:framework", "nd")
AddEventHandler("playerJoining", function(oldId)
local src = source
PlayersInfo[src] = tempPlayersInfo[oldId]
tempPlayersInfo[oldId] = nil
if Config.multiCharacter then return end
Wait(3000)
local characters = NDCore.fetchAllCharacters(src)
local id = next(characters)
if id then
return NDCore.setActiveCharacter(src, id)
end
local player = NDCore.newCharacter(src, {
firstname = GetPlayerName(src),
lastname = "",
dob = "",
gender = ""
})
NDCore.setActiveCharacter(src, player.id)
end)
for _, roleid in pairs(config.adminRoles) do
ExecuteCommand("add_principal identifier.discord:" .. roleid .. " group.admin")
end
local function checkDiscordIdentifier(identifiers)
if Config.discordBotToken == "false" or Config.discordGuildId == "false" then return end
local discordIdentifier = identifiers["discord"]
if not discordIdentifier then return end
return NDCore.getDiscordInfo(discordIdentifier:gsub("discord:", ""))
end
AddEventHandler("onResourceStart", function(name)
if name ~= resourceName then return end
for _, playerId in ipairs(GetPlayers()) do
local src = tonumber(playerId)
local identifiers = getIdentifierList(src)
PlayersInfo[src] = {
identifiers = identifiers,
discord = checkDiscordIdentifier(identifiers) or {}
}
Wait(65)
end
end)
AddEventHandler("playerConnecting", function(name, setKickReason, deferrals)
local tempSrc = source
local identifiers = getIdentifierList(tempSrc)
local mainIdentifier = identifiers[Config.characterIdentifier]
local discordInfo = {}
deferrals.defer()
Wait(0)
if mainIdentifier and Config.discordBotToken ~= "false" and Config.discordGuildId ~= "false" and not discordInfo then
discordInfo = checkDiscordIdentifier(identifiers)
if not discordInfo then
deferrals.done(("Your discord was not found, join our discord here: %s."):format(Config.discordInvite))
Wait(0)
end
end
deferrals.update("Connecting...")
Wait(0)
if mainIdentifier then
tempPlayersInfo[tempSrc] = {
identifiers = identifiers,
discord = discordInfo
}
deferrals.done()
else
deferrals.done(("Your %s was not found."):format(Config.characterIdentifier))
Wait(0)
end
end)
AddEventHandler("playerDropped", function()
local src = source
local char = NDCore.players[src]
if char then char.unload() end
PlayersInfo[src] = nil
end)
AddEventHandler("onResourceStop", function(name)
if name ~= resourceName then return end
for _, player in pairs(NDCore.players) do
player.unload()
Wait(10)
end
end)
SetTimeout(500, function()
NDCore.loadSQL({
"database/characters.sql",
"database/vehicles.sql"
}, resourceName)
end)
RegisterNetEvent("ND:playerEliminated", function(info)
local src = source
local player = NDCore.getPlayer(src)
if not player then return end
player.setMetadata({
dead = true,
deathInfo = info
})
end)

486
server/player.lua Normal file
View File

@@ -0,0 +1,486 @@
local function removeCharacterFunctions(character)
local newData = {}
for k, v in pairs(character) do
if type(v) ~= "function" then
newData[k] = v
end
end
return newData
end
local function createCharacterTable(info)
local playerInfo = PlayersInfo[info.source] or {}
local self = {
id = info.id,
source = info.source,
identifier = info.identifier,
identifiers = playerInfo.identifiers or {},
discord = playerInfo.discord or {},
name = info.name,
firstname = info.firstname,
lastname = info.lastname,
fullname = ("%s %s"):format(info.firstname, info.lastname),
dob = info.dob,
gender = info.gender,
cash = info.cash,
bank = info.bank,
groups = info.groups,
metadata = info.metadata,
inventory = info.inventory
}
---@param account string
---@param amount number
---@param reason string|nil
---@return boolean
function self.deductMoney(account, amount, reason)
local amount = tonumber(amount)
if not amount or amount <= 0 or account ~= "bank" and account ~= "cash" then return end
self[account] -= amount
if NDCore.players[self.source] then
self.triggerEvent("ND:updateMoney", self.cash, self.bank)
TriggerEvent("ND:moneyChange", self.source, account, amount, "remove", reason)
end
return true
end
---@param account string
---@param amount number
---@param reason string|nil
---@return boolean
function self.addMoney(account, amount, reason)
local amount = tonumber(amount)
if not amount or amount <= 0 or account ~= "bank" and account ~= "cash" then return end
self[account] += amount
if NDCore.players[self.source] then
self.triggerEvent("ND:updateMoney", self.cash, self.bank)
TriggerEvent("ND:moneyChange", self.source, account, amount, "add", reason)
end
return true
end
---@param amount number
---@return boolean
function self.depositMoney(amount)
local amount = tonumber(amount)
if not amount or self.cash < amount or amount <= 0 then return end
return self.deductMoney("cash", amount, "Deposit") and self.addMoney("bank", amount, "Deposit")
end
---@param amount number
---@return boolean
function self.withdrawMoney(amount)
local amount = tonumber(amount)
if not amount or self.bank < amount or amount <= 0 then return end
return self.deductMoney("bank", amount, "Withdraw") and self.addMoney("cash", amount, "Withdraw")
end
---@param data string
---@return any
function self.getData(data)
return self[data]
end
---@param metadata string|table
---@return any
function self.getMetadata(metadata)
if type(metadata) ~= "table" then
return self.metadata[metadata]
end
local returnData = {}
for i=1, #metadata do
local data = metadata[i]
returnData[data] = self.metadata[data]
end
return returnData
end
---@param key string|table
---@param value any
function self.setData(key, value, reason)
if type(key) == "table" then
for k, v in pairs(key) do
self[k] = v
if k == "cash" or k == "bank" then
TriggerEvent("ND:moneyChange", self.source, k, v, "set", reason)
end
end
else
self[key] = value
if key == "cash" or key == "bank" then
TriggerEvent("ND:moneyChange", self.source, key, value, "set", reason)
end
end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
end
---@param key string|table
---@param value any
---@return table
function self.setMetadata(key, value)
if type(key) == "table" then
for k, v in pairs(key) do
self.metadata[k] = v
end
else
self.metadata[key] = value
end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
return self.metadata
end
-- Completely delete character
function self.delete()
local result = MySQL.query.await("DELETE FROM nd_characters WHERE charid = ?", {self.id})
if result and NDCore.players[self.source] then
NDCore.players[self.source] = nil
end
return result
end
-- Unload and save character
function self.unload()
if not NDCore.players[self.source] then return end
for name, _ in pairs(self.groups) do
lib.removePrincipal(self.source, ("group.%s"):format(name))
end
local ped = GetPlayerPed(self.source)
if ped then
local coords = GetEntityCoords(ped)
local heading = GetEntityHeading(ped)
self.setMetadata("location", {
x = coords.x,
y = coords.y,
z = coords.z,
w = heading
})
end
self.triggerEvent("ND:characterUnloaded")
TriggerEvent("ND:characterUnloaded", self.source, self)
local saved = self.save()
NDCore.players[self.source] = nil
return saved
end
-- Save character information to database
function self.save()
local affectedRows = MySQL.update.await("UPDATE nd_characters SET name = ?, firstname = ?, lastname = ?, dob = ?, gender = ?, cash = ?, bank = ?, groups = ?, metadata = ? WHERE charid = ?", {
self.name,
self.firstname,
self.lastname,
self.dob,
self.gender,
self.cash,
self.bank,
json.encode(self.groups),
json.encode(self.metadata),
self.id
})
return affectedRows > 0
end
---Create a license/permit for the character
---@param licenseType string
---@param expire number
function self.createLicense(licenseType, expire)
local expireIn = tonumber(expire) or 2592000
local time = os.time()
local licenses = self.metadata.licenses
local identifier = {}
for i=1, 16 do
identifier[i] = math.random(0, 1) == 1 and string.char(math.random(65, 90)) or math.random(0, 9)
end
local license = {
type = licenseType,
status = "valid",
issued = time,
expires = time+expireIn,
identifier = table.concat(identifier)
}
if licenses then
self.metadata.licenses[#licenses+1] = license
else
self.metadata.licenses = {license}
end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
end
function self.getLicense(identifier)
local licenses = self.metadata.licenses or {}
for i=1, #licenses do
local data = licenses[i]
if data.identifier == identifier then
return data, i
end
end
end
function self.updateLicense(identifier, newData)
local data, i = self.getLicense(identifier)
if not data then return end
for k, v in pairs(newData) do
data[k] = v
end
self.save()
end
---@param coords vector3|vector4
---@return boolean
function self.setCoords(coords)
if not self.source or not coords then return end
local ped = GetPlayerPed(self.source)
if not DoesEntityExist(ped) then return end
SetEntityCoords(ped, coords.x, coords.y, coords.z)
if coords.w then
SetEntityHeading(ped, coords.w)
end
return true
end
---@param eventName string
---@param ... any
---@return boolean
function self.triggerEvent(eventName, ...)
if not self.source then return end
TriggerClientEvent(eventName, self.source, ...)
return true
end
function self.notify(...)
if not self.source then return end
if GetResourceState("ModernHUD") == "started" then
TriggerClientEvent("ModernHUD:notify", self.source, ...)
elseif GetResourceState("ox_lib") == "started" then
TriggerClientEvent("ox_lib:notify", self.source, ...)
end
return true
end
function self.revive()
self.triggerEvent("ND:revivePlayer")
self.setMetadata({
dead = false,
deathInfo = false,
})
end
---@param reason string
function self.drop(reason)
if not self.source then return end
DropPlayer(self.source, reason)
end
-- Set the character as the players active character/currently playing character
function self.active()
local char = NDCore.players[self.source]
if char and char.id == self.id then return end
if char then char.unload() end
for identifierType, identifier in pairs(self.identifiers) do
if lib.table.contains(Config.admins, ("%s:%s"):format(identifierType, identifier)) then
self.addGroup("admin")
end
end
local roles = self.discord.roles
if roles then
for i=1, #Config.adminDiscordRoles do
local role = Config.adminDiscordRoles[i]
if lib.table.contains(roles, role) then
self.addGroup("admin")
end
end
end
for name, _ in pairs(self.groups) do
lib.addPrincipal(self.source, ("group.%s"):format(name))
end
NDCore.players[self.source] = self
TriggerEvent("ND:characterLoaded", self)
self.triggerEvent("ND:characterLoaded", removeCharacterFunctions(self))
end
---@param name string
---@param rank number
---@param isJob boolean
---@return boolean
function self.addGroup(name, rank, isJob)
local groupRank = tonumber(rank) or 1
local groupInfo = Config.groups[name]
-- if not groupInfo then return end
if isJob then
for _, group in pairs(self.groups) do
group.isJob = nil
end
end
self.groups[name] = {
label = groupInfo and groupInfo.label or name,
rankName = groupInfo and groupInfo.ranks[groupRank] or groupRank,
rank = groupRank,
isJob = isJob
}
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
lib.addPrincipal(self.source, ("group.%s"):format(name))
return self.groups[name]
end
---@param name string
---@return table
function self.getGroup(name)
return self.groups[name]
end
---@param name string
function self.removeGroup(name)
local group = self.groups[name]
if not group then return end
self.groups[name] = nil
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
lib.removePrincipal(self.source, ("group.%s"):format(name))
return group
end
---@param name string
---@param rank number
---@return boolean
function self.setJob(name, rank)
self.removeGroup(self.job)
local job = self.addGroup(name, rank, true)
local jobName, jobInfo = self.getJob()
if jobInfo then
self.job = jobName
self.jobInfo = jobInfo
end
return job
end
---@param job string
---@return boolean
function self.getJob()
for name, group in pairs(self.groups) do
if group.isJob then
return name, group
end
end
end
local jobName, jobInfo = self.getJob()
if jobInfo then
self.job = jobName
self.jobInfo = jobInfo
end
return self
end
---@param src number
---@param info table
---@return table
function NDCore.newCharacter(src, info)
local identifier = GetPlayerIdentifierByType(src, Config.characterIdentifier)
if not identifier then return end
local charInfo = {
source = src,
identifier = identifier,
name = GetPlayerName(src) or "",
firstname = info.firstname or "",
lastname = info.lastname or "",
dob = info.dob or "",
gender = info.gender or "",
cash = info.cash or 0,
bank = info.bank or 0,
groups = info.groups or {},
metadata = info.metadata or {},
inventory = info.inventory or {},
}
charInfo.id = MySQL.insert.await("INSERT INTO nd_characters (identifier, name, firstname, lastname, dob, gender, cash, bank, groups, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", {
identifier,
charInfo.name,
charInfo.firstname,
charInfo.lastname,
charInfo.dob,
charInfo.gender,
charInfo.cash,
charInfo.bank,
json.encode(charInfo.groups),
json.encode(charInfo.metadata)
})
return createCharacterTable(charInfo)
end
---@param id number
---@return table
function NDCore.fetchCharacter(id, src)
local result
if src then
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ? and identifier = ?", {id, GetPlayerIdentifierByType(src, Config.characterIdentifier)})
else
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id})
end
if not result then return end
local info = result[1]
return createCharacterTable({
source = src,
id = info.charid,
identifier = info.identifier,
name = info.name,
firstname = info.firstname,
lastname = info.lastname,
dob = info.dob,
gender = info.gender,
cash = info.cash,
bank = info.bank,
groups = json.decode(info.groups),
metadata = json.decode(info.metadata),
inventory = json.decode(info.inventory)
})
end
---@param src number
---@return table
function NDCore.fetchAllCharacters(src)
local characters = {}
local result = MySQL.query.await("SELECT * FROM nd_characters WHERE identifier = ?", {GetPlayerIdentifierByType(src, Config.characterIdentifier)})
for i=1, #result do
local info = result[i]
characters[info.charid] = createCharacterTable({
source = src,
id = info.charid,
identifier = info.identifier,
name = info.name,
firstname = info.firstname,
lastname = info.lastname,
dob = info.dob,
gender = info.gender,
cash = info.cash,
bank = info.bank,
groups = json.decode(info.groups),
metadata = json.decode(info.metadata),
inventory = json.decode(info.inventory),
})
end
return characters
end
---@param src number
---@param id number
---@return table
function NDCore.setActiveCharacter(src, id)
local char = NDCore.players[src]
if not src or char and char.id == id then return end
local character = NDCore.fetchCharacter(id, src)
if not character then return end
character.name = GetPlayerName(src)
character.active()
return character
end

696
server/vehicle.lua Normal file
View File

@@ -0,0 +1,696 @@
local ox_inventory
local inventoryStarted = false
local spawnedPlayerVehicles = {}
NDCore.isResourceStarted("ox_inventory", function(started)
inventoryStarted = started
if not started then return end
ox_inventory = exports.ox_inventory
end)
local function getVehicleType(model)
local tempVehicle = CreateVehicle(model, 0, 0, 0, 0, true, true)
local time = os.time()
while not DoesEntityExist(tempVehicle) and time-os.time() < 5 do Wait(5) end
if not DoesEntityExist(tempVehicle) then return end
local entityType = GetVehicleType(tempVehicle)
DeleteEntity(tempVehicle)
return entityType
end
local function generatePlate()
local plate = {}
for i=1, 8 do
plate[i] = math.random(0, 1) == 1 and string.char(math.random(65, 90)) or math.random(0, 9)
end
return table.concat(plate)
end
local function generateVehiclePlate(newPlate)
local plate = newPlate or generatePlate()
while MySQL.scalar.await("SELECT 1 FROM nd_vehicles WHERE plate = ?", {plate}) do
plate = generatePlate()
end
return plate
end
local function generateTemporaryVehicleId()
return ("temp_%s%d"):format(string.char(math.random(65, 90)), math.random(1, 999999))
end
local function getVehicleDatabaseInfo(vehicle)
if not vehicle then return end
local stored = vehicle.stored == 1
local impounded = vehicle.impounded == 1
return {
id = vehicle.id,
owner = vehicle.owner,
plate = vehicle.plate,
properties = json.decode(vehicle.properties) or {},
stored = stored,
impounded = impounded,
stolen = vehicle.stolen == 1,
available = not impounded and stored,
metadata = json.decode(vehicle.metadata) or {}
}
end
--- get vehicle information queried from the database by the vehicle id
---@param vehicleId string | numb
---@return table | nil
function NDCore.getVehicleById(vehicleId)
local result = MySQL.query.await("SELECT * FROM nd_vehicles WHERE id = ?", {vehicleId})
return getVehicleDatabaseInfo(result?[1])
end
--- get vehicle information and functions
---@param entity number
---@return table
function NDCore.getVehicle(entity)
if not DoesEntityExist(entity) then return end
local state = Entity(entity).state
local self = {
entity = entity,
id = state.id,
owner = state.owner,
keys = state.keys,
properties = state.props,
locked = state.locked,
hotwired = state.hotwired,
metadata = state.metadata or {},
netId = NetworkGetNetworkIdFromEntity(entity)
}
--- delete the vehicle
function self.delete(saveProperties)
if not DoesEntityExist(entity) then return end
if saveProperties and self.id and self.owner then
local properties = lib.callback.await("ND_Vehicles:getProps", NetworkGetEntityOwner(entity), self.netId)
if properties then
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id})
end
end
DeleteEntity(entity)
end
--- set vehicle properties
---@param props table
function self.setProperties(props)
if not DoesEntityExist(entity) then return end
local properties = type(props) == "string" and json.decode(props) or props
local state = Entity(entity).state
state.props = properties
self.properties = properties
if not self.id or not self.owner then return end
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id})
end
--- set vehicle locked/unlocked
---@param status boolean
function self.setLocked(status)
if not DoesEntityExist(entity) then return end
local state = Entity(entity).state
state.locked = status
self.locked = status
end
--- update the vehicle plate
---@param plate string
---@return boolean
function self.setPlate(plate)
if not DoesEntityExist(entity) or MySQL.scalar.await("SELECT 1 FROM nd_vehicles WHERE plate = ?", {plate}) then return end
if self.properties then
self.properties.plate = plate
local state = Entity(entity).state
state.props = self.properties
end
if not self.id or not self.owner then return end
MySQL.query("UPDATE nd_vehicles SET plate = ? WHERE id = ?", {plate, self.id})
return true
end
--- set the vehicle availability status
---@param statusType string
---@param status boolean
function self.setStatus(statusType, status)
if not lib.table.contains({"stored", "impounded", "stolen"}, statusType) then return end
if statusType ~= "stolen" then self.delete(true) end
if not self.id or not self.owner then return end
local query = ("UPDATE nd_vehicles SET %s = ? WHERE id = ?"):format(statusType)
MySQL.query(query, {status and 1 or 0, self.id})
return true
end
function self.setMetadata(key, value)
self.metadata[key] = value
if DoesEntityExist(entity) then
local state = Entity(self.entity).state
local metadata = state.metadata
metadata[key] = value
state.metadata = metadata
end
if not self.id or not self.owner then return end
MySQL.query("UPDATE nd_vehicles SET metadata = ? WHERE id = ?", {json.encode(self.metadata), self.id})
end
if self.id and self.owner then
spawnedPlayerVehicles[self.id] = self
end
return self
end
--- get a characters owned vehicles
---@param characterId number
---@return table
function NDCore.getVehicles(characterId)
local result = MySQL.query.await("SELECT * FROM nd_vehicles WHERE owner = ?", {characterId})
if not result then return {} end
local vehicles = {}
for _, vehicle in pairs(result) do
local info = getVehicleDatabaseInfo(vehicle)
if info then
vehicles[#vehicles+1] = info
end
end
return vehicles
end
--- give a player keys/access to a vehicle
---@param source number
---@param vehicle number
---@param access boolean
---@param info table
function NDCore.giveVehicleAccess(source, vehicle, access, info)
if not vehicle or not DoesEntityExist(vehicle) then return end
local state = Entity(vehicle).state
local netId = info?.netId or NetworkGetNetworkIdFromEntity(vehicle)
local vehicleId = info?.vehicleId or state.id or generateTemporaryVehicleId()
if not state.id then
state.id = vehicleId
end
local player = NDCore.getPlayer(source)
if player then
local keys = state.keys or {}
keys[player.id] = access
state.keys = keys
end
if not inventoryStarted or not Config.useInventoryForKeys then return end
local plate = info?.plate or GetVehicleNumberPlateText(vehicle)
local model = info?.model or GetEntityModel(vehicle)
local modelName = info?.modelName or model and lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", source, model) or ""
local hasKey = ox_inventory:GetSlotIdWithItem(source, "keys", {
vehId = vehicleId
})
if access and not hasKey then
ox_inventory:AddItem(source, "keys", 1, {
vehOwner = owner or state.owner,
vehId = vehicleId,
vehPlate = plate,
vehModel = modelName,
keyEnabled = true,
vehNetId = netId
})
elseif not access and hasKey then
ox_inventory:RemoveItem(source, "keys", 1, nil, hasKey)
end
end
--- spawn a vehicle, set info & give keys.
---@param info table
---@return table
function NDCore.createVehicle(info)
local owner = info.owner
local vehicleId = info.vehicleId or generateTemporaryVehicleId()
local properties = info.properties or {}
local coords = info.coords
local spawnCoords = coords
local coordType = type(coords)
if coordType == "vector3" or coordType == "table" then
spawnCoords = vector4(coords.x, coords.y, coords.z, coords.w or coords.h or info.heading or 0.0)
end
if not spawnCoords then
return Citizen.Trace("NDCore.createVehicle", "spawnCoords not found")
end
local model = info.model or properties.model
local vehType = getVehicleType(model)
if not vehType then
return Citizen.Trace("NDCore.createVehicle", "vehType not found")
end
local veh = CreateVehicleServerSetter(model, vehType, spawnCoords.x, spawnCoords.y, spawnCoords.z, spawnCoords.w)
local time = os.time()
while not DoesEntityExist(veh) and time-os.time() < 5 do Wait(5) end
if not veh or not DoesEntityExist(veh) then
return Citizen.Trace("NDCore.createVehicle", "vehicle entity doesn't exist")
end
local netId = NetworkGetNetworkIdFromEntity(veh)
local state = Entity(veh).state
state.locked = true
local keys = info.keys or {}
if not properties.plate then
properties.plate = generateVehiclePlate()
end
if owner then
keys[owner] = true
state.owner = owner
end
state.keys = keys
state.props = properties
state.id = vehicleId
local vehicleName
if inventoryStarted and Config.useInventoryForKeys then
for charId, _ in pairs(keys) do
local playerSource = charId == owner and info.source
if not playerSource then
local player = NDCore.getPlayers("id", charId, true)[1]
playerSource = player and player.source
end
if playerSource then
if not vehicleName then
vehicleName = lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", playerSource, model) or ""
end
NDCore.giveVehicleAccess(playerSource, veh, true, {
vehicleId = vehicleId,
netId = netId,
plate = properties?.plate,
model = model,
vehicleName = vehicleName,
owner = owner
})
end
end
end
return NDCore.getVehicle(veh)
end
--- transfer ownership of a vehicle between players
---@param vehicleId number|string
---@param fromSource number
---@param toSource number
---@return boolean
function NDCore.transferVehicleOwnership(vehicleId, fromSource, toSource)
local playerFrom = NDCore.getPlayer(fromSource)
local playerTo = NDCore.getPlayer(toSource)
if not playerFrom or not playerTo then return end
local vehicle = NDCore.getVehicleById(vehicleId)
if not vehicle or vehicle.owner ~= playerFrom.id then return end
MySQL.query.await("UPDATE nd_vehicles SET owner = ? WHERE id = ?", {playerTo.id, vehicleId})
local vehicleInfo = spawnedPlayerVehicles[vehicleId]
if vehicleInfo then
local veh, netId, model = vehicleInfo.entity, vehicleInfo.netId, GetEntityModel(veh)
playerFrom.triggerEvent("ND_Vehicles:blip", netId, false)
playerTo.triggerEvent("ND_Vehicles:blip", netId, true)
NDCore.giveVehicleAccess(fromSource, veh, false, {
vehicleId = vehicleId,
netId = netId,
model = model,
owner = playerFrom.id
})
local state = Entity(veh).state
state.owner = playerTo.id
NDCore.giveVehicleAccess(toSource, veh, true, {
vehicleId = vehicleId,
netId = netId,
model = model,
owner = playerTo.id
})
end
playerFrom.notify({
title = "Ownership transfered",
description = ("Vehicle ownership of %s has been transfered."):format(vehicle.plate),
position = "bottom-right",
type = "success"
})
playerTo.notify({
title = "Ownership received",
description = ("Received vehicle ownership of %s."):format(vehicle.plate),
position = "bottom-right"
})
return true
end
--- set vehicle as owned by player
---@param playerId number
---@param properties table
---@param stored boolean
---@return vehicleId number
function NDCore.setVehicleOwned(playerId, properties, stored)
local plate = generateVehiclePlate()
properties.plate = plate
return MySQL.insert.await("INSERT INTO nd_vehicles (owner, plate, properties, stored) VALUES (?, ?, ?, ?)", {playerId, plate, json.encode(properties), stored and 1 or 0})
end
--- give a player access to another players vehicle as if they are next to eachother and hand the keys.
---@param source number
---@param target number
---@param vehicle number
---@return boolean
function NDCore.shareVehicleKeys(source, target, vehicle)
if not vehicle or not DoesEntityExist(vehicle) then return end
local player = NDCore.getPlayer(source)
local targetPlayer = NDCore.getPlayer(target)
local state = Entity(vehicle).state
if not targetPlayer or not player or player.id ~= state.owner then return end
NDCore.giveVehicleAccess(target, vehicle, true)
local plate = GetVehicleNumberPlateText(vehicle)
player.notify({
title = "Keys shared",
description = ("You've shared vehicle keys to %s."):format(plate),
position = "bottom-right",
type = "success",
})
targetPlayer.notify({
title = "Keys received",
description = ("Received vehicle keys to %s."):format(plate),
position = "bottom-right",
})
return true
end
--- spawned a vehicle that's owned by the player and checks for availability
---@param source number
---@param vehicleId number
---@param coords vector4
---@return table
function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading)
local player = NDCore.getPlayer(source)
if not player then return end
local vehicle = NDCore.getVehicleById(vehicleId)
if not vehicle or not vehicle.available or vehicle.owner ~= player.id then return end
MySQL.query.await("UPDATE nd_vehicles SET stored = ? WHERE id = ?", {0, vehicleId})
return NDCore.createVehicle({
owner = player.id,
model = vehicle.properties.model,
coords = vec4(coords.x, coords.y, coords.z, coords.w or coords.heading or heading),
properties = vehicle.properties,
vehicleId = vehicleId,
source = source
})
end
local function toggleVehicleLock(source, entity, nearby, metadata)
local player = NDCore.getPlayer(source)
if metadata and not metadata.keyEnabled then
return player.notify({
title = "No signal",
description = "Vehicle key disabled.",
type = "error",
position = "bottom-right",
duration = 3000
})
elseif not nearby then
return player.notify({
title = "No signal",
description = "Vehicle to far away.",
type = "error",
position = "bottom-right",
duration = 3000
})
end
local vehicle = NDCore.getVehicle(entity)
local locked = not vehicle.locked
vehicle.setLocked(locked)
player.triggerEvent("ND_Vehicles:keyFob", vehicle.netId)
if locked then
return player.notify({
title = "LOCKED",
description = "Your vehicle has now been locked.",
type = "success",
position = "bottom-right",
duration = 3000
})
end
player.notify({
title = "UNLOCKED",
description = "Your vehicle has now been unlocked.",
type = "inform",
position = "bottom-right",
duration = 3000
})
end
local function getNearbyVehicles(coords, range)
local nearby = {}
local vehicles = GetAllVehicles()
for i=1, #vehicles do
local veh = vehicles[i]
local vehCoords = GetEntityCoords(veh)
if #(coords-vehCoords) < range then
nearby[#nearby+1] = veh
end
end
return nearby
end
local function lockNearestVehicle(source, vehId, metadata)
local ped = GetPlayerPed(source)
local pedCoords = GetEntityCoords(ped)
local veh = spawnedPlayerVehicles[vehId]?.entity
if veh and DoesEntityExist(veh) then
local vehCoords = GetEntityCoords(veh)
if not pedCoords or not vehCoords then return end
return toggleVehicleLock(source, veh, #(pedCoords-vehCoords) < 25.0, metadata)
end
local vehicles = getNearbyVehicles(pedCoords, 25.0)
for i=1, #vehicles do
local veh = vehicles[i]
local state = Entity(veh).state
if state and state.id == vehId then
return toggleVehicleLock(source, veh, true, metadata)
end
end
end
--- inventory keys using item.
exports("keys", function(event, item, inventory, slot, data)
if event ~= "usingItem" or not Config.useInventoryForKeys or not inventoryStarted then return end
local metadata
for i=1, #inventory.items do
local item = inventory.items[i]
if item and item.slot == slot then
metadata = item.metadata
break
end
end
if not metadata then return false end
lockNearestVehicle(inventory.id, metadata.vehId, metadata)
return false
end)
-- if using inventory and inventory keys players can spawn keys when in their vehicle with this command.
RegisterCommand("getkeys", function(source, args, rawCommand)
if not Config.useInventoryForKeys or not inventoryStarted then return end
local veh = GetVehiclePedIsIn(GetPlayerPed(source))
if not veh or veh == 0 then return end
local player = NDCore.getPlayer(source)
local state = Entity(veh).state
local owner = state.owner
if not owner or owner ~= player.id then return end
local props = state.props
ox_inventory:AddItem(source, "keys", 1, {
vehOwner = owner,
vehId = state.id,
vehPlate = props.plate,
vehModel = lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", source, props.model),
keyEnabled = true,
vehNetId = NetworkGetNetworkIdFromEntity(veh)
})
end, false)
-- key sharing if not using inventory or inventory keys.
RegisterCommand("givekeys", function(source, args, rawCommand)
if Config.useInventoryForKeys and inventoryStarted then return end
local src = source
if not args[1] then return end
local target = tonumber(args[1])
if not GetPlayerPing(target) then return end
local veh = GetVehiclePedIsIn(GetPlayerPed(src))
if veh == 0 then
veh = GetVehiclePedIsIn(GetPlayerPed(src), true)
if veh == 0 then return end
end
NDCore.shareVehicleKeys(src, target, veh)
end, false)
-- lock/unlock vehicles if they're within range.
RegisterNetEvent("ND_Vehicles:toggleVehicleLock", function(netId)
if Config.useInventoryForKeys and inventoryStarted then return end
local src = source
local veh = NetworkGetEntityFromNetworkId(netId)
if not veh or not DoesEntityExist(veh) then return end
local ped = GetPlayerPed(src)
local pedCoords = GetEntityCoords(ped)
local vehCoords = GetEntityCoords(veh)
if not pedCoords or not vehCoords then return end
toggleVehicleLock(src, veh, #(pedCoords-vehCoords) < 25.0)
end)
-- locking of npc vehicles, if the players spawns inside a vehicle it won't be locked.
RegisterNetEvent("entityCreated", function(entity)
if not DoesEntityExist(entity) or GetEntityType(entity) ~= 2 then return end
local state = Entity(entity).state
if state.owner or state.locked ~= nil then return end
local driver = GetPedInVehicleSeat(entity, -1)
if DoesEntityExist(driver) and IsPedAPlayer(driver) then
state.locked = false
state.hotwired = true
end
if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end
state.locked = true
end)
-- disables inventory vehicles keys, disabled vehicles keys can no longer be used. Kinda like taking the battery out.
RegisterNetEvent("ND_Vehicles:disableKey", function(slot)
local src = source
local key = ox_inventory:GetSlot(src, slot)
local metadata = key.metadata
if not metadata.keyEnabled then return end
metadata.keyEnabled = false
ox_inventory:SetMetadata(src, slot, key.metadata)
end)
-- sync alarm when vehicle is lockpicked.
RegisterNetEvent("ND_Vehicles:lockpick", function(netId, success)
local src = source
local veh = NetworkGetEntityFromNetworkId(netId)
if not veh or not DoesEntityExist(veh) then return end
local ped = GetPlayerPed(src)
local pedCoords = GetEntityCoords(ped)
local vehCoords = GetEntityCoords(veh)
if #(pedCoords-vehCoords) > 5.0 then return end
local owner = NetworkGetEntityOwner(veh)
TriggerClientEvent("ND_Vehicles:syncAlarm", owner, netId)
if not success then return end
local state = Entity(veh).state
state.locked = false
end)
-- sync alarm if vehicle gets hotwired.
RegisterNetEvent("ND_Vehicles:hotwire", function(netId)
local src = source
local ped = GetPlayerPed(src)
local playerVeh = GetVehiclePedIsIn(ped)
local veh = NetworkGetEntityFromNetworkId(netId)
if not playerVeh or playerVeh == 0 or playerVeh ~= veh then return end
local state = Entity(veh).state
state.hotwired = true
end)
RegisterNetEvent("ND_Vehicles:storeVehicle", function(netId)
local src = source
local vehicle = NDCore.getVehicle(NetworkGetEntityFromNetworkId(netId))
if not vehicle then return end
local player = NDCore.getPlayer(src)
if not vehicle.setStatus("stored", true) or not player or player.id ~= vehicle.owner then
return player.notify({
title = "Garage",
description = "No owned vehicle found nearby.",
type = "error",
position = "bottom",
duration = 3000
})
end
player.notify({
title = "Garage",
description = "Vehicle stored in garage.",
type = "success",
position = "bottom",
duration = 3000
})
NDCore.giveVehicleAccess(src, vehicle.entity, false, {
vehicleId = vehicle.id,
netId = vehicle.netId,
owner = vehicle.owner
})
end)
local function isParkingAvailable(locations)
for i=1, #locations do
local loc = locations[math.random(1, #locations)]
if #getNearbyVehicles(vec3(loc.x, loc.y, loc.z), 2.0) == 0 then
return loc
end
end
end
RegisterNetEvent("ND_Vehicles:takeVehicle", function(vehId, locations)
local src = source
local vehicle = NDCore.getVehicleById(vehId)
local player = NDCore.getPlayer(src)
if not player or not vehicle or vehicle.owner ~= player.id then return end
if vehicle.impounded then
local reclaimPrice = vehicle.metadata.impoundReclaimPrice or 200
if not player.deductMoney("bank", reclaimPrice, "Vehicle impound reclaim") then
return player.notify({
title = "Impound",
description = ("Price to reclaim is $%d, you don't have enough!"):format(reclaimPrice),
type = "error",
position = "bottom"
})
end
player.notify({
title = "Impound",
description = ("Paid $%d to reclaim vehicle!"):format(reclaimPrice),
type = "success",
position = "bottom"
})
MySQL.query.await("UPDATE nd_vehicles SET impounded = ? WHERE id = ?", {0, vehicle.id})
end
local info = NDCore.spawnOwnedVehicle(src, vehicle.id, isParkingAvailable(locations))
if not info then return end
TriggerClientEvent("ND_Vehicles:blip", src, info.netId, true)
end)
lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src)
local player = NDCore.getPlayer(src)
if not player then return end
return NDCore.getVehicles(player.id)
end)

30
shared/functions.lua Normal file
View File

@@ -0,0 +1,30 @@
local startedResources = {}
local function stateChanged(resourceName, state)
local callbacks = startedResources[resourceName]
if not callbacks then return end
for i=1, #callbacks do
local cb = callbacks[i]
cb(state)
end
end
AddEventHandler("onResourceStart", function(resourceName)
stateChanged(resourceName, true)
end)
AddEventHandler("onResourceStop", function(resourceName)
stateChanged(resourceName, false)
end)
function NDCore.isResourceStarted(resourceName, cb)
local started = GetResourceState(resourceName) == "started"
if cb then
if not startedResources[resourceName] then
startedResources[resourceName] = {}
end
startedResources[resourceName][#startedResources[resourceName]+1] = cb
cb(started)
end
return started
end