mirror of
https://github.com/ND-Framework/ND_Core.git
synced 2026-08-17 05:56:01 +01:00
Compare commits
281 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
544ad64779 | ||
|
|
43f690e356 | ||
|
|
e883c4e64f | ||
|
|
5928d89edf | ||
|
|
9b846dc07e | ||
|
|
baac4c75dd | ||
|
|
fb708cc7e7 | ||
|
|
1547d310eb | ||
|
|
9d4708f6d9 | ||
|
|
a7713804f0 | ||
|
|
a9785ce754 | ||
|
|
fe922e1b98 | ||
|
|
b97f42bb0a | ||
|
|
d132c12025 | ||
|
|
119b0034bf | ||
|
|
622b6fa8b0 | ||
|
|
2e6dfa9e5f | ||
|
|
4c4166de94 | ||
|
|
dea7ac6c8f | ||
|
|
120d924cae | ||
|
|
2a263a6c2e | ||
|
|
4ff4f487a1 | ||
|
|
e26821841b | ||
|
|
e9aafe1f2d | ||
|
|
6987bb63ae | ||
|
|
e0c4a8419b | ||
|
|
66f93bde14 | ||
|
|
2460df2f34 | ||
|
|
b79bb6ec7f | ||
|
|
eef1bb60f5 | ||
|
|
1320c4ab7d | ||
|
|
2c7aa5723a | ||
|
|
97a8b991ae | ||
|
|
2a3ac3b63b | ||
|
|
5d3c051149 | ||
|
|
fc82b217e9 | ||
|
|
284411ce66 | ||
|
|
a7257cf68f | ||
|
|
841a5771c5 | ||
|
|
8fd7878ba6 | ||
|
|
60e86d2038 | ||
|
|
626f0ad06f | ||
|
|
d5c3acbd81 | ||
|
|
98cba87bec | ||
|
|
001fc22ef6 | ||
|
|
87420e99e9 | ||
|
|
c5597161d9 | ||
|
|
898b9aefe9 | ||
|
|
5545650dd5 | ||
|
|
f4f01a5b1c | ||
|
|
3252118f93 | ||
|
|
9d4708a1f8 | ||
|
|
d0d04ae282 | ||
|
|
c005c1b28b | ||
|
|
590044b5d6 | ||
|
|
3146172c0c | ||
|
|
2ce47ef8d1 | ||
|
|
6258651d8b | ||
|
|
eecbc8f690 | ||
|
|
1d25b54e0f | ||
|
|
e18389a52a | ||
|
|
539e522373 | ||
|
|
2bacdd2a20 | ||
|
|
6242a44c0b | ||
|
|
74e4ccc2de | ||
|
|
2fda7655b5 | ||
|
|
ade82436a5 | ||
|
|
f512d0ed0c | ||
|
|
0a19fe8a33 | ||
|
|
2882c7c1ae | ||
|
|
6291690bd4 | ||
|
|
af82c447c1 | ||
|
|
46f4bdbd64 | ||
|
|
4870d639d4 | ||
|
|
ed895b4e6d | ||
|
|
5d2925d0c3 | ||
|
|
0500cef32f | ||
|
|
0e1fb43cd2 | ||
|
|
3260aa01f2 | ||
|
|
71a54be704 | ||
|
|
d939607d64 | ||
|
|
5cb038819c | ||
|
|
9f5bb34bdd | ||
|
|
29006870c3 | ||
|
|
a60e1b9e32 | ||
|
|
7fe7901a13 | ||
|
|
ca8f4369f6 | ||
|
|
e1d4b2fcff | ||
|
|
8b0f2b010d | ||
|
|
24e8874050 | ||
|
|
289d040b5c | ||
|
|
3f0c47c2d0 | ||
|
|
543b23a644 | ||
|
|
4b106d3688 | ||
|
|
db5c54f2d8 | ||
|
|
a1150bb821 | ||
|
|
8f7c52130a | ||
|
|
6e71142970 | ||
|
|
d9df135abb | ||
|
|
72fb153cfc | ||
|
|
0d54d3aef5 | ||
|
|
f65728c90a | ||
|
|
d76293d117 | ||
|
|
53a0e176ff | ||
|
|
6f4f94df96 | ||
|
|
0df4077bfe | ||
|
|
e064af5a8b | ||
|
|
6764ac51a8 | ||
|
|
d46d0733fa | ||
|
|
2b7b6c93b2 | ||
|
|
ce29f21a90 | ||
|
|
700d422477 | ||
|
|
7cc3bbe42a | ||
|
|
53255bf336 | ||
|
|
666c3125a3 | ||
|
|
bad05a81ca | ||
|
|
34346f39a9 | ||
|
|
f48ea5b4f3 | ||
|
|
c50003cfd8 | ||
|
|
b319d4a90a | ||
|
|
d0097203f5 | ||
|
|
954221e13e | ||
|
|
f22577d5dd | ||
|
|
ad91854b65 | ||
|
|
623afab92a | ||
|
|
cb96fdd3e6 | ||
|
|
d5dfa02435 | ||
|
|
a8c5ea7d79 | ||
|
|
cebe404d42 | ||
|
|
8aee90a10f | ||
|
|
b5c7d4dc0b | ||
|
|
40e74f6944 | ||
|
|
87cb479ce4 | ||
|
|
a6db224699 | ||
|
|
d341d8aaa5 | ||
|
|
0fa2ee4d4b | ||
|
|
a3279ea5c8 | ||
|
|
b8bb98b20d | ||
|
|
fb7c7b1de4 | ||
|
|
c057455cc9 | ||
|
|
f0eb040eba | ||
|
|
2a7b21f3ea | ||
|
|
a5a9290b5d | ||
|
|
83212d45c2 | ||
|
|
0b40091e3e | ||
|
|
698707c992 | ||
|
|
276274cf74 | ||
|
|
bdb2c4033c | ||
|
|
825ba96c50 | ||
|
|
4064d43bb0 | ||
|
|
6ff461839d | ||
|
|
fcc0ce4551 | ||
|
|
dc7c34caad | ||
|
|
7003b29db8 | ||
|
|
0848480753 | ||
|
|
47b952be1d | ||
|
|
b72df80179 | ||
|
|
2696ae632d | ||
|
|
39e302c50f | ||
|
|
bbaf1aa90f | ||
|
|
0e35b412fb | ||
|
|
c328a8dd50 | ||
|
|
166baead74 | ||
|
|
42cd797cc0 | ||
|
|
ae7f0dc0d8 | ||
|
|
7a97816570 | ||
|
|
82bc4c6d6b | ||
|
|
3dd2daad57 | ||
|
|
00563a5f41 | ||
|
|
0c70cd42dc | ||
|
|
70866dd4b9 | ||
|
|
c701622172 | ||
|
|
8cc7be3e4b | ||
|
|
73de344234 | ||
|
|
b09637d68c | ||
|
|
7882b2235e | ||
|
|
38ac145a88 | ||
|
|
76ae3a1c44 | ||
|
|
8eac50be2d | ||
|
|
7f5291517e | ||
|
|
09c9ca254d | ||
|
|
0ee9700777 | ||
|
|
151961b401 | ||
|
|
b7237729f4 | ||
|
|
ec86f6ff86 | ||
|
|
0de7b9c682 | ||
|
|
4868eb42a2 | ||
|
|
dff0be4702 | ||
|
|
0ded085a28 | ||
|
|
32801873f6 | ||
|
|
1e6b0f4c49 | ||
|
|
0d64a1dfec | ||
|
|
f586bd17fb | ||
|
|
5ef6afca68 | ||
|
|
74b35442c5 | ||
|
|
57d6bbf85a | ||
|
|
a381ad793f | ||
|
|
45fe62c7c5 | ||
|
|
bde1f5b0bb | ||
|
|
e4e5d60cf4 | ||
|
|
a75a307873 | ||
|
|
0208f22df5 | ||
|
|
83cacb7ba6 | ||
|
|
5a1c4ecb1a | ||
|
|
fc1ef7b7b3 | ||
|
|
97e175d4fd | ||
|
|
5ec7a9ac83 | ||
|
|
105ac16560 | ||
|
|
46fc396924 | ||
|
|
9738fc236b | ||
|
|
86c38aea69 | ||
|
|
eed4855b63 | ||
|
|
0baadda7b0 | ||
|
|
858e5e98a2 | ||
|
|
d31be85fd8 | ||
|
|
3ec20ea087 | ||
|
|
1652dc4b5c | ||
|
|
76b93097f6 | ||
|
|
0a5ddfe1a8 | ||
|
|
26ae7ea1a3 | ||
|
|
cf67d9a4e2 | ||
|
|
ab15847a32 | ||
|
|
27d6ce4f9c | ||
|
|
38680c7abf | ||
|
|
47c74409ca | ||
|
|
44066b0828 | ||
|
|
f3d54a3a27 | ||
|
|
a68b6657e5 | ||
|
|
eaba11e4e3 | ||
|
|
bc7e2ddd2a | ||
|
|
d29d73c075 | ||
|
|
01500fe081 | ||
|
|
98433908e9 | ||
|
|
5fd51f5329 | ||
|
|
01cfcb4b05 | ||
|
|
9dd5351b32 | ||
|
|
35726429f9 | ||
|
|
075807eb51 | ||
|
|
ee88709043 | ||
|
|
6eb044edc4 | ||
|
|
87b2451a93 | ||
|
|
c06ca96457 | ||
|
|
e19544c65e | ||
|
|
6f4ddba976 | ||
|
|
e2fb381eb9 | ||
|
|
1ab7765cff | ||
|
|
310184cec3 | ||
|
|
f82976a822 | ||
|
|
9da69aa350 | ||
|
|
5674e424a8 | ||
|
|
c3d2d8ad73 | ||
|
|
fd7a22423f | ||
|
|
47e7bc5151 | ||
|
|
f1d1346f3f | ||
|
|
af7d893164 | ||
|
|
d5d3de98bf | ||
|
|
2068e7bb0b | ||
|
|
d8cb79adf5 | ||
|
|
664f48acd6 | ||
|
|
e2c6f6b97d | ||
|
|
177f547f80 | ||
|
|
fb933c4e58 | ||
|
|
15a65712bb | ||
|
|
56e0b2f4fd | ||
|
|
5389ae0962 | ||
|
|
593443cae5 | ||
|
|
975b031a02 | ||
|
|
9a9fa03e8a | ||
|
|
a4b40272bd | ||
|
|
72f4b011ec | ||
|
|
426be404f3 | ||
|
|
36c0ab209a | ||
|
|
bf9604e851 | ||
|
|
24caa1a857 | ||
|
|
abad068eb7 | ||
|
|
1173cc2983 | ||
|
|
f4846617a3 | ||
|
|
7a5f26834e | ||
|
|
e1727609df | ||
|
|
ce3219dec9 | ||
|
|
e0c5abee27 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
|
||||
config_server.lua
|
||||
42
README.md
42
README.md
@@ -1,28 +1,28 @@
|
||||
<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="discord.gg/nc82d8zvjm">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?
|
||||
[](https://discord.gg/Z9Mxu72zZ6)
|
||||
* [Character selection](https://github.com/ND-Framework/ND_Characters/releases)
|
||||
* [Banking](https://github.com/ND-Framework/ND_Banking/releases)
|
||||
* [Appearance shops](https://github.com/ND-Framework/ND_AppearanceShops/releases)
|
||||
* [Dealership](https://github.com/ND-Framework/ND_Dealership/releases)
|
||||
* [Inventory](https://github.com/overextended/ox_inventory/releases)
|
||||
* [Ambulance job](https://github.com/ND-Framework/ND_Ambulance/releases)
|
||||
* [Casino](https://github.com/ND-Framework/ND_Casino/releases)
|
||||
* [Blackjack](https://github.com/ND-Framework/ND_Blackjack/releases)
|
||||
* [Nitro](https://github.com/ND-Framework/ND_Nitro/releases)
|
||||
|
||||
54
client/death.lua
Normal file
54
client/death.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
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
|
||||
|
||||
local hit, bone = GetPedLastDamageBone(victim)
|
||||
if hit and usingAmbulance then
|
||||
local damageWeapon = ambulance:getLastDamagingWeapon(victim)
|
||||
ambulance:updateBodyDamage(bone, damageWeapon)
|
||||
end
|
||||
|
||||
if not IsPedDeadOrDying(victim, true) or GetEntityHealth(victim) > 100 then 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)
|
||||
@@ -1,24 +1,75 @@
|
||||
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()
|
||||
if source == "" then return end
|
||||
local oldPed = cache.ped
|
||||
local veh = GetVehiclePedIsIn(oldPed)
|
||||
local seat = cache.seat
|
||||
local coords = GetEntityCoords(oldPed)
|
||||
local armor = GetPedArmour(oldPed)
|
||||
|
||||
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, GetEntityHeading(oldPed), true, true, false)
|
||||
|
||||
local ped = PlayerPedId()
|
||||
if oldPed ~= ped then
|
||||
DeleteEntity(oldPed)
|
||||
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)
|
||||
SetPedArmour(ped, armor)
|
||||
|
||||
if veh and veh ~= 0 then
|
||||
SetPedIntoVehicle(ped, veh, seat)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ND:characterUnloaded")
|
||||
|
||||
RegisterNetEvent("ND:clothingMenu", function()
|
||||
if GetResourceState("fivem-appearance") ~= "started" then return end
|
||||
|
||||
local function customize(appearance)
|
||||
if not appearance then return end
|
||||
TriggerServerEvent("ND:updateClothing", appearance)
|
||||
end
|
||||
|
||||
exports["fivem-appearance"]:startPlayerCustomization(customize, {
|
||||
ped = false,
|
||||
headBlend = true,
|
||||
faceFeatures = true,
|
||||
headOverlays = true,
|
||||
components = true,
|
||||
props = true,
|
||||
tattoos = true
|
||||
})
|
||||
end)
|
||||
|
||||
@@ -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,19 @@ 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))
|
||||
end
|
||||
end
|
||||
|
||||
if promise then
|
||||
return table.unpack(Citizen.Await(promise))
|
||||
end
|
||||
function NDCore.getConfig(info)
|
||||
if not info then
|
||||
return Config
|
||||
end
|
||||
return Config[info]
|
||||
end
|
||||
|
||||
setmetatable(NDCore.callback, {
|
||||
__call = triggerCallback
|
||||
})
|
||||
|
||||
function NDCore.callback.await(name, ...)
|
||||
return triggerCallback(nil, name, false, ...)
|
||||
function NDCore.notify(...)
|
||||
lib.notify(...)
|
||||
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
|
||||
|
||||
117
client/main.lua
117
client/main.lua
@@ -1,54 +1,71 @@
|
||||
-- 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("core:discordActionLink", "https://discord.gg/Z9Mxu72zZ6"),
|
||||
discordActionText2 = GetConvar("core:discordActionText2", "STORE"),
|
||||
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
|
||||
disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1,
|
||||
randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30),
|
||||
requireKeys = GetConvarInt("core:requireKeys", 1) == 1,
|
||||
useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1,
|
||||
groups = json.decode(GetConvar("core:groups", "[]")),
|
||||
compatibility = json.decode(GetConvar("core:compatibility", "[]")),
|
||||
lockpickTries = GetConvarInt("core:lockpickTries", 3)
|
||||
}
|
||||
|
||||
-- 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
227
client/peds.lua
Normal 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 = { drawable = %s, texture = %s },"):format(info, k, GetPedDrawableVariation(cache.ped, v), GetPedTextureVariation(cache.ped, v))
|
||||
end
|
||||
for k, v in pairs(clothingProps) do
|
||||
info = ("%s\n%s = { drawable = %s, texture = %s },"):format(info, k, GetPedPropIndex(cache.ped, v), GetPedPropTextureIndex(cache.ped, v))
|
||||
end
|
||||
lib.setClipboard(info)
|
||||
end, false)
|
||||
301
client/vehicle/data.lua
Normal file
301
client/vehicle/data.lua
Normal file
@@ -0,0 +1,301 @@
|
||||
return {
|
||||
{
|
||||
garageType = "land",
|
||||
groups = {"lsfd"},
|
||||
ped = vector4(370.9027, -593.8090, 28.8681, 73.6009),
|
||||
vehicleSpawns = {
|
||||
vec4(366.7840, -591.4883, 28.7261, 339.6823),
|
||||
vec4(365.0753, -582.8641, 28.7156, 135.0064),
|
||||
vec4(357.4239, -604.7719, 28.6767, 177.1355),
|
||||
}
|
||||
},
|
||||
{
|
||||
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)
|
||||
}
|
||||
},
|
||||
{
|
||||
garageType = "land",
|
||||
ped = vector4(587.5941, 2744.2844, 42.0691, 181.0514),
|
||||
vehicleSpawns = {
|
||||
vector4(584.3921, 2721.3599, 41.6483, 184.3260),
|
||||
vector4(577.1069, 2736.2322, 41.6097, 184.6912),
|
||||
vector4(577.9459, 2721.0010, 41.6481, 185.1414),
|
||||
vector4(574.1685, 2735.7288, 41.6397, 184.5562)
|
||||
}
|
||||
},
|
||||
{
|
||||
garageType = "land",
|
||||
ped = vector4(2746.1726, 3458.7378, 55.8272, 245.1009),
|
||||
vehicleSpawns = {
|
||||
vector4(2768.2136, 3456.2710, 55.2840, 67.7897),
|
||||
vector4(2759.3218, 3451.1448, 55.4786, 248.0150),
|
||||
vector4(2763.6526, 3445.0544, 55.4741, 66.9754),
|
||||
vector4(2787.2947, 3467.1001, 54.9407, 247.4356),
|
||||
vector4(2773.9514, 3471.6238, 55.0407, 246.8264)
|
||||
}
|
||||
},
|
||||
{
|
||||
garageType = "land",
|
||||
ped = vector4(120.4042, 6623.5190, 31.9593, 228.8283),
|
||||
vehicleSpawns = {
|
||||
vector4(155.9047, 6593.3252, 31.4343, 359.3789),
|
||||
vector4(155.7900, 6602.6978, 31.4463, 358.8232),
|
||||
vector4(145.8267, 6614.0996, 31.4007, 178.7948),
|
||||
vector4(145.6977, 6602.8950, 31.4397, 181.0616),
|
||||
vector4(132.4042, 6585.2139, 31.5501, 91.0906)
|
||||
}
|
||||
},
|
||||
{
|
||||
garageType = "heli",
|
||||
groups = {"lspd"},
|
||||
ped = vector4(465.6194, -985.4730, 43.6918, 0.8591),
|
||||
vehicleSpawns = {
|
||||
vector4(449.3105, -981.1057, 44.0788, 359.6042)
|
||||
}
|
||||
},
|
||||
{
|
||||
garageType = "heli",
|
||||
groups = {"lsfd"},
|
||||
ped = vector4(339.6271, -581.0325, 74.1656, 267.8232),
|
||||
vehicleSpawns = {
|
||||
vector4(351.3463, -588.1304, 74.1698, 108.5617)
|
||||
}
|
||||
}
|
||||
}
|
||||
291
client/vehicle/garages.lua
Normal file
291
client/vehicle/garages.lua
Normal 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
|
||||
790
client/vehicle/main.lua
Normal file
790
client/vehicle/main.lua
Normal file
@@ -0,0 +1,790 @@
|
||||
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 playingKey = 0
|
||||
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
|
||||
playingKey += 1
|
||||
SetPedCurrentWeaponVisible(ped, false, false)
|
||||
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)
|
||||
SetTimeout(400, function()
|
||||
playingKey -= 1
|
||||
end)
|
||||
SetTimeout(1200, function()
|
||||
if playingKey > 0 then return end
|
||||
SetPedCurrentWeaponVisible(ped, true, false)
|
||||
end)
|
||||
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)
|
||||
|
||||
local function getProps(veh)
|
||||
if not veh or not DoesEntityExist(veh) then return end
|
||||
|
||||
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:getProps", function(netId)
|
||||
local veh = getVehFromNetId(netId)
|
||||
return getProps(veh)
|
||||
end)
|
||||
|
||||
lib.callback.register("ND_Vehicles:getPropsFromCurrentVeh", function()
|
||||
local veh = GetVehiclePedIsIn(cache.ped)
|
||||
return getProps(veh)
|
||||
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)
|
||||
|
||||
local reset = true
|
||||
playerVehicle = cache.seat == -1 and cache.vehicle
|
||||
|
||||
local entering = GetVehiclePedIsEntering(cache.ped)
|
||||
if entering and DoesEntityExist(entering) and IsVehicleNeedsToBeHotwired(entering) then
|
||||
SetVehicleNeedsToBeHotwired(entering, false)
|
||||
end
|
||||
|
||||
if not playerVehicle then goto skip end
|
||||
|
||||
if Config.disableVehicleAirControl and not vehicleClassNotDisableAirControl[GetVehicleClass(playerVehicle)] and (IsEntityInAir(playerVehicle) or IsEntityUpsidedown(playerVehicle)) then
|
||||
wait = 0
|
||||
reset = false
|
||||
DisableControlAction(0, 59) -- disable vehicle air control.
|
||||
DisableControlAction(0, 60)
|
||||
end
|
||||
if Config.requireKeys and not GetIsVehicleEngineRunning(playerVehicle) and not hasVehicleKeysCheck(playerVehicle) then
|
||||
wait = 0
|
||||
reset = false
|
||||
DisableControlAction(0, 59)
|
||||
DisableControlAction(0, 71)
|
||||
if DoesEntityExist(playerVehicle) and IsVehicleEngineStarting(playerVehicle) then
|
||||
SetVehicleEngineOn(playerVehicle, false, true, true) -- don't turn on engine if no keys.
|
||||
end
|
||||
end
|
||||
|
||||
::skip::
|
||||
if (reset or not playerVehicle) and 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, Config.lockpickTries 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
|
||||
|
||||
local speed = math.floor(GetEntitySpeed(playerVehicle) * 2.236936)
|
||||
if speed < 10 then return end
|
||||
|
||||
cruiseControlEnabled = true
|
||||
cruiseSpeedVehicle = speed
|
||||
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)
|
||||
18
compatibility/backwards/client.lua
Normal file
18
compatibility/backwards/client.lua
Normal 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)
|
||||
278
compatibility/backwards/server.lua
Normal file
278
compatibility/backwards/server.lua
Normal file
@@ -0,0 +1,278 @@
|
||||
if not lib.table.contains(Config.compatibility, "backwards") then return end
|
||||
|
||||
exports("GetCoreObject", function()
|
||||
return NDCore
|
||||
end)
|
||||
|
||||
NDCore.Functions = {}
|
||||
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.GetPlayer(src)
|
||||
local player = NDCore.getPlayer(src)
|
||||
return {
|
||||
source = player.source,
|
||||
id = player.id,
|
||||
firstName = player.firstname,
|
||||
lastName = player.lastname,
|
||||
dob = player.dob,
|
||||
gender = player.gender,
|
||||
cash = player.cash,
|
||||
bank = player.bank,
|
||||
phoneNumber = player.metadata.phone,
|
||||
lastLocation = player.metadata.lastLocation,
|
||||
inventory = player.inventory,
|
||||
discordInfo = player.discordInfo,
|
||||
data = player.metadata,
|
||||
job = player.job
|
||||
}
|
||||
end
|
||||
|
||||
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)
|
||||
412
compatibility/esx/client.lua
Normal file
412
compatibility/esx/client.lua
Normal 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 = GetResourceState("ox_inventory") == "started" and exports.ox_inventory:GetPlayerMaxWeight() or 0
|
||||
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 = GetResourceState("ox_inventory") == "started" and exports.ox_inventory:Search(item) or 0
|
||||
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()
|
||||
return GetResourceState("ox_inventory") == "started" and 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)
|
||||
29
compatibility/esx/locale.lua
Normal file
29
compatibility/esx/locale.lua
Normal 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
|
||||
373
compatibility/esx/server.lua
Normal file
373
compatibility/esx/server.lua
Normal file
@@ -0,0 +1,373 @@
|
||||
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
|
||||
local items = GetResourceState("ox_inventory") == "started" and exports.ox_inventory:Items() or {}
|
||||
for item, data in pairs(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)
|
||||
return GetResourceState("ox_inventory") == "started" and 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 GetResourceState("ox_inventory") == "started" and exports.ox_inventory:CanCarryItem(self.source, item, count)
|
||||
end
|
||||
|
||||
function self.canSwapItem(firstItem, firstItemCount, testItem, testItemCount)
|
||||
return GetResourceState("ox_inventory") == "started" and 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 = GetResourceState("ox_inventory") == "started" and exports.ox_inventory:GetInventoryItems(self.source) or {}
|
||||
if not minimal then
|
||||
return playerItems
|
||||
end
|
||||
-- minimal stuff
|
||||
end
|
||||
|
||||
function self.getInventoryItem(item)
|
||||
local playerItems = GetResourceState("ox_inventory") == "started" and exports.ox_inventory:GetInventoryItems(self.source) or {}
|
||||
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 GetResourceState("ox_inventory") == "started" and exports.ox_inventory:GetItem(self.source, item, metadata)
|
||||
end
|
||||
|
||||
function self.removeInventoryItem(item, count)
|
||||
return GetResourceState("ox_inventory") == "started" and 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 = {}
|
||||
local items = GetResourceState("ox_inventory") == "started" and exports.ox_inventory:Items() or {}
|
||||
for item, data in pairs(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)
|
||||
2
compatibility/qb/client.lua
Normal file
2
compatibility/qb/client.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
if not lib.table.contains(Config.compatibility, "qb") then return end
|
||||
|
||||
1
compatibility/qb/server.lua
Normal file
1
compatibility/qb/server.lua
Normal file
@@ -0,0 +1 @@
|
||||
if not lib.table.contains(Config.compatibility, "qb") then return end
|
||||
@@ -1,74 +0,0 @@
|
||||
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
||||
|
||||
config = {
|
||||
serverName = "Andy's Development",
|
||||
characterLimit = 15, -- How many characters can a player create.
|
||||
customPauseMenu = true, -- A custom pause menu will display your money, characters name and the server name in the pause menu.
|
||||
enablePVP = true, -- pvp allows doing damage to other players.
|
||||
|
||||
-- Money related
|
||||
startingCash = 2500, -- default cash the character will start with if the character creator doesn't specify it.
|
||||
startingBank = 8000,-- default money in the bank account the character will start with if the character creator doesn't specify it.
|
||||
|
||||
-- If you'd like to whitelist certain roles on discord then set this to true and add role ids.
|
||||
enableDiscordWhitelist = false,
|
||||
notWhitelistedMessage = "You're not allowlisted in this server please join our discord to apply for a allowlist: https://discord.gg/Z9Mxu72zZ6",
|
||||
whitelistRoles = {
|
||||
"872921520719142932"
|
||||
},
|
||||
|
||||
-- These are admins roles that will give a user permission to admin commands and more.
|
||||
adminRoles = {
|
||||
"872921520719142932"
|
||||
},
|
||||
|
||||
-- Discord Rich presence
|
||||
enableRichPresence = true,
|
||||
updateIntervall = 60, -- how many seconds of delay until it updates status.
|
||||
appId = 858146067018416128,
|
||||
largeLogo = "andyyy",
|
||||
smallLogo = "andyyy",
|
||||
firstButtonName = "DISCORD",
|
||||
firstButtonLink = "https://discord.gg/Z9Mxu72zZ6",
|
||||
secondButtonName = "TEBEX",
|
||||
secondButtonLink = "https://andyyy.tebex.io/category/fivem-scripts",
|
||||
|
||||
|
||||
-- Groups can be gangs, jobs, subdivisions, etc.
|
||||
groups = {
|
||||
["Ballas"] = {
|
||||
"Member", -- rank 1
|
||||
"Boss" -- rank 2
|
||||
},
|
||||
["SWAT"] = {
|
||||
"Member", -- rank 1
|
||||
"Sniper", -- rank 2
|
||||
"Team lead", -- rank 3
|
||||
"Commander" -- rank 4
|
||||
},
|
||||
["SAHP"] = {
|
||||
"Trooper",
|
||||
"Senior Trooper",
|
||||
"Corporal",
|
||||
"Sergeant",
|
||||
"Lieutenant",
|
||||
"Cheif"
|
||||
},
|
||||
["LSPD"] = {
|
||||
"Officer",
|
||||
"Senior officer",
|
||||
"Corporal",
|
||||
"Sergeant",
|
||||
"Lieutenant",
|
||||
"Cheif"
|
||||
},
|
||||
["BCSO"] = {
|
||||
"Deputy",
|
||||
"Senior Deputy",
|
||||
"Corporal",
|
||||
"Sergeant",
|
||||
"Lieutenant",
|
||||
"Cheif"
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
||||
|
||||
server_config = {
|
||||
discordServerToken = "OTAwODg1MDMxMDQ2Nzc0ODQ1.GU1TSC.Ed8D2_wcU-UQ1vZhe1khahgV8J3hLahOp4N1D8", -- discord bot token for permissions
|
||||
guildId = "872496972454592523", -- discord guild id
|
||||
}
|
||||
16
database/characters.sql
Normal file
16
database/characters.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
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',
|
||||
`phonenumber` VARCHAR(20) DEFAULT NULL,
|
||||
`groups` LONGTEXT DEFAULT ('[]'),
|
||||
`metadata` LONGTEXT DEFAULT ('[]'),
|
||||
`inventory` LONGTEXT DEFAULT ('[]'),
|
||||
PRIMARY KEY (`charid`) USING BTREE
|
||||
);
|
||||
15
database/vehicles.sql
Normal file
15
database/vehicles.sql
Normal 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
|
||||
);
|
||||
@@ -1,39 +1,47 @@
|
||||
-- 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.3.2"
|
||||
|
||||
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"
|
||||
files {
|
||||
"init.lua",
|
||||
"client/vehicle/data.lua",
|
||||
"compatibility/**/locale.lua"
|
||||
}
|
||||
|
||||
server_exports {
|
||||
"GetCoreObject"
|
||||
dependencies {
|
||||
"ox_lib",
|
||||
"oxmysql"
|
||||
}
|
||||
|
||||
dependency "oxmysql"
|
||||
-- below were used with backwards compatibility but could interfere with resources checking if the resources are started.
|
||||
-- provide "es_extended"
|
||||
-- provide "qb-Core"
|
||||
|
||||
11
init.lua
Normal file
11
init.lua
Normal 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
|
||||
})
|
||||
16
query.sql
16
query.sql
@@ -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
|
||||
);
|
||||
@@ -1,117 +1,482 @@
|
||||
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, set a players 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 or source == 0 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 or source == 0 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 or source == 0 then return end
|
||||
TriggerClientEvent("chat:addMessage", source, {
|
||||
color = {50, 100, 235},
|
||||
multiline = true,
|
||||
args = {"Staff action", "success"}
|
||||
})
|
||||
end)
|
||||
|
||||
lib.addCommand("skin", {
|
||||
help = "Admin command, show character clothing menu for player.",
|
||||
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, show character selection menu for player.",
|
||||
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 = "target",
|
||||
type = "playerId",
|
||||
help = "Target player's server id"
|
||||
},
|
||||
{
|
||||
name = "amount",
|
||||
type = "number",
|
||||
help = "Amount of cash to give"
|
||||
}
|
||||
}
|
||||
}, function(source, args, raw)
|
||||
if not source or source == 0 then return end
|
||||
|
||||
local player = NDCore.getPlayer(source)
|
||||
local targetPlayer = NDCore.getPlayer(args.target)
|
||||
|
||||
if not player or not targetPlayer then
|
||||
return player.notify({
|
||||
title = "Error",
|
||||
description = "No player found",
|
||||
type = "error"
|
||||
})
|
||||
end
|
||||
|
||||
if player.cash < args.amount then
|
||||
return player.notify({
|
||||
title = "Error",
|
||||
description = "You don't have enough cash",
|
||||
type = "error"
|
||||
})
|
||||
end
|
||||
|
||||
local playerCoords = GetEntityCoords(GetPlayerPed(player.source))
|
||||
local targetCoords = GetEntityCoords(GetPlayerPed(targetPlayer.source))
|
||||
if #(playerCoords-targetCoords) > 2.0 then
|
||||
return player.notify({
|
||||
title = "Error",
|
||||
description = "Player is not nearby",
|
||||
type = "error"
|
||||
})
|
||||
end
|
||||
|
||||
local success = player.deductMoney("cash", args.amount) and targetPlayer.addMoney("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
|
||||
targetPlayer.notify({
|
||||
title = "Money received",
|
||||
description = ("Received $%d in cash"):format(args.amount),
|
||||
type = "inform",
|
||||
duration = 5000
|
||||
})
|
||||
|
||||
player.notify({
|
||||
title = "Money given",
|
||||
description = ("You gave $%d in cash"):format(args.amount),
|
||||
type = "inform",
|
||||
duration = 5000
|
||||
})
|
||||
end)
|
||||
|
||||
local job = args[2]
|
||||
if not job then
|
||||
return {
|
||||
color = {255, 0, 0},
|
||||
args = {"Error", "job required."}
|
||||
}
|
||||
end
|
||||
lib.addCommand("unlock", {
|
||||
help = "Admin Command, force unlock a vehicle.",
|
||||
restricted = "group.admin",
|
||||
}, function(source, args, raw)
|
||||
local ped = GetPlayerPed(source)
|
||||
local coords = GetEntityCoords(ped)
|
||||
local veh = lib.getClosestVehicle(coords, 2.5, true)
|
||||
|
||||
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 veh then return end
|
||||
|
||||
local state = Entity(veh).state
|
||||
state.hotwired = true
|
||||
state.locked = false
|
||||
end)
|
||||
|
||||
lib.addCommand("revive", {
|
||||
help = "Admin command, revive a 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 or the closest.",
|
||||
restricted = "group.admin",
|
||||
params = {
|
||||
{
|
||||
name = "range",
|
||||
type = "number",
|
||||
help = "Range to delete vehicles in",
|
||||
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 coords = GetEntityCoords(ped)
|
||||
local count = 0
|
||||
local message = nil
|
||||
local veh = lib.getClosestVehicle(coords, 3.0)
|
||||
|
||||
if args.range then
|
||||
local vehicles = lib.getNearbyVehicles(coords, args.range)
|
||||
count = #vehicles
|
||||
|
||||
for i=1, count do
|
||||
local veh = vehicles[i]
|
||||
DeleteEntity(veh.vehicle)
|
||||
end
|
||||
elseif veh then
|
||||
DeleteEntity(veh)
|
||||
count = 1
|
||||
end
|
||||
|
||||
if count == 0 then
|
||||
message = {"Staff action [dv]", "no vehicles found"}
|
||||
elseif count == 1 then
|
||||
message = {"Staff action [dv]", "deleted 1 vehicle"}
|
||||
else
|
||||
message = {"Staff action [dv]", ("deleted %d vehicles"):format(count)}
|
||||
end
|
||||
|
||||
TriggerClientEvent("chat:addMessage", source, {
|
||||
color = {50, 100, 235},
|
||||
multiline = true,
|
||||
args = message
|
||||
})
|
||||
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, spawn a vehicle.",
|
||||
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)
|
||||
|
||||
lib.addCommand("claim-veh", {
|
||||
help = "Admin command, add a copy of the vehicle you're inside to players garage.",
|
||||
restricted = "group.admin",
|
||||
params = {
|
||||
{
|
||||
name = "target",
|
||||
type = "playerId",
|
||||
help = "Target player's server id"
|
||||
}
|
||||
}
|
||||
}, function(source, args, raw)
|
||||
local player = NDCore.getPlayer(source)
|
||||
if not player then return end
|
||||
|
||||
local targetPlayer = NDCore.getPlayer(args.target)
|
||||
if not targetPlayer then
|
||||
return player.notify({
|
||||
title = "Player not found!",
|
||||
description = "Target player not found, make sure they select a charcter!",
|
||||
type = "error"
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
local properties = lib.callback.await("ND_Vehicles:getPropsFromCurrentVeh", source)
|
||||
if not properties then
|
||||
return player.notify({
|
||||
title = "Vehicle data not found!",
|
||||
description = "The vehicle properties data was not found!",
|
||||
type = "error"
|
||||
})
|
||||
end
|
||||
|
||||
NDCore.setVehicleOwned(targetPlayer.id, properties, true)
|
||||
|
||||
player.notify({
|
||||
title = "Vehicle added!",
|
||||
description = ("The vehicle has now been added to %s's garage!"):format(targetPlayer.name),
|
||||
type = "success"
|
||||
})
|
||||
end)
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
||||
|
||||
-- Check if discord is connected, and if whitelist is enabled then it will only allow you to join if you have the roles.
|
||||
AddEventHandler("playerConnecting", function(name, setKickReason, deferrals)
|
||||
local player = source
|
||||
local discordIdentifier = NDCore.Functions.GetPlayerIdentifierFromType("discord", player)
|
||||
|
||||
deferrals.defer()
|
||||
Wait(0)
|
||||
deferrals.update("Connecting to discord.")
|
||||
Wait(0)
|
||||
|
||||
if not discordIdentifier then
|
||||
deferrals.done("Your discord isn't connected to FiveM, make sure discord is open and restart FiveM.")
|
||||
else
|
||||
if config.enableDiscordWhitelist then
|
||||
local discordUserId = discordIdentifier:gsub("discord:", "")
|
||||
local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId)
|
||||
for _, whitelistRole in pairs(config.whitelistRoles) do
|
||||
if whitelistRole == 0 or whitelistRole == "0" or (discordInfo and discordInfo.roles[whitelistRole]) then
|
||||
deferrals.done()
|
||||
break
|
||||
end
|
||||
end
|
||||
deferrals.done(config.notWhitelistedMessage)
|
||||
else
|
||||
deferrals.done()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Getting all the characters the player has and returning them to the client.
|
||||
RegisterNetEvent("ND:GetCharacters", function()
|
||||
local player = source
|
||||
TriggerClientEvent("ND:returnCharacters", player, NDCore.Functions.GetPlayerCharacters(player))
|
||||
end)
|
||||
|
||||
-- Creating a new character.
|
||||
RegisterNetEvent("ND:newCharacter", function(newCharacter)
|
||||
local player = source
|
||||
NDCore.Functions.CreateCharacter(player, newCharacter.firstName, newCharacter.lastName, newCharacter.dob, newCharacter.gender, newCharacter.cash, newCharacter.bank)
|
||||
end)
|
||||
|
||||
-- Update the character info when edited.
|
||||
RegisterNetEvent("ND:editCharacter", function(newCharacter)
|
||||
local player = source
|
||||
local characters = NDCore.Functions.GetPlayerCharacters(player)
|
||||
if not characters[newCharacter.id] then return end
|
||||
NDCore.Functions.UpdateCharacterData(newCharacter.id, newCharacter.firstName, newCharacter.lastName, newCharacter.dob, newCharacter.gender)
|
||||
end)
|
||||
|
||||
-- Delete character from database.
|
||||
RegisterNetEvent("ND:deleteCharacter", function(characterId)
|
||||
local player = source
|
||||
local characters = NDCore.Functions.GetPlayerCharacters(player)
|
||||
if not characters[characterId] then return end
|
||||
NDCore.Functions.DeleteCharacter(characterId)
|
||||
end)
|
||||
|
||||
-- add a player to the table.
|
||||
RegisterNetEvent("ND:setCharacterOnline", function(id)
|
||||
local player = source
|
||||
local characters = NDCore.Functions.GetPlayerCharacters(player)
|
||||
if not characters[id] then return end
|
||||
NDCore.Functions.SetActiveCharacter(player, id)
|
||||
end)
|
||||
|
||||
-- Update the characters clothes.
|
||||
RegisterNetEvent("ND:updateClothes", function(clothing)
|
||||
local player = source
|
||||
local character = NDCore.Players[player]
|
||||
NDCore.Functions.SetPlayerData(character.id, "clothing", clothing)
|
||||
end)
|
||||
|
||||
-- Disconnecting a player
|
||||
RegisterNetEvent("ND:exitGame", function()
|
||||
local player = source
|
||||
DropPlayer(player, "Disconnected.")
|
||||
end)
|
||||
|
||||
-- Remove player from NDCore.Players table when they leave.
|
||||
AddEventHandler("playerDropped", function()
|
||||
local player = source
|
||||
local character = NDCore.Players[player]
|
||||
if character then
|
||||
local ped = GetPlayerPed(player)
|
||||
local lastLocation = GetEntityCoords(ped)
|
||||
NDCore.Functions.UpdateLastLocation(character.id, {x = lastLocation.x, y = lastLocation.y, z = lastLocation.z})
|
||||
end
|
||||
TriggerEvent("ND:characterUnloaded", player, character)
|
||||
character = nil
|
||||
end)
|
||||
|
||||
-- Get player discord info on join.
|
||||
AddEventHandler("playerJoining", function()
|
||||
local src = source
|
||||
|
||||
local discordUserId = NDCore.Functions.GetPlayerIdentifierFromType("discord", src):gsub("discord:", "")
|
||||
local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId)
|
||||
|
||||
NDCore.PlayersDiscordInfo[src] = discordInfo
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStart", function(resourceName)
|
||||
if (GetCurrentResourceName() ~= resourceName) then
|
||||
return
|
||||
end
|
||||
Wait(1000)
|
||||
|
||||
if not next(NDCore.PlayersDiscordInfo) then
|
||||
for _, playerId in ipairs(GetPlayers()) do
|
||||
local discordUserId = NDCore.Functions.GetPlayerIdentifierFromType("discord", playerId):gsub("discord:", "")
|
||||
local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId)
|
||||
NDCore.PlayersDiscordInfo[tonumber(playerId)] = discordInfo
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -1,766 +1,113 @@
|
||||
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
||||
|
||||
-- Get an active players character data.
|
||||
function NDCore.Functions.GetPlayer(player)
|
||||
return NDCore.Players[player]
|
||||
function NDCore.getPlayerIdentifierByType(src, indentifierType)
|
||||
if Config.sv_lan then
|
||||
return ("%s:sv_lan"):format(indentifierType)
|
||||
end
|
||||
return GetPlayerIdentifierByType(src, indentifierType)
|
||||
end
|
||||
|
||||
-- Get all active players character data.
|
||||
function NDCore.Functions.GetPlayers(getBy, value)
|
||||
if not getBy or not value then
|
||||
return NDCore.Players
|
||||
end
|
||||
local players = {}
|
||||
---@param src number
|
||||
---@return table
|
||||
function NDCore.getPlayer(src)
|
||||
return NDCore.players[src]
|
||||
end
|
||||
|
||||
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
|
||||
---@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 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
|
||||
|
||||
function NDCore.getConfig(info)
|
||||
if not info then
|
||||
return Config
|
||||
end
|
||||
return Config[info]
|
||||
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
|
||||
|
||||
194
server/main.lua
194
server/main.lua
@@ -1,21 +1,185 @@
|
||||
-- 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"),
|
||||
discordMemeberRequired = GetConvarInt("core:discordMemeberRequired", 1) == 1,
|
||||
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", "[]")),
|
||||
groupRoles = json.decode(GetConvar("core:groupRoles", "[]")),
|
||||
multiCharacter = false,
|
||||
compatibility = json.decode(GetConvar("core:compatibility", "[]")),
|
||||
sv_lan = GetConvar("sv_lan", "false") == "true",
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if Config.sv_lan then
|
||||
list[Config.characterIdentifier] = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
|
||||
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
|
||||
local oldTempId = tonumber(oldId)
|
||||
PlayersInfo[src] = tempPlayersInfo[oldTempId]
|
||||
tempPlayersInfo[oldTempId] = nil
|
||||
|
||||
if Config.sv_lan then
|
||||
lib.addPrincipal(("player.%s"):format(src), "group.admin")
|
||||
end
|
||||
|
||||
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 = nil
|
||||
|
||||
deferrals.defer()
|
||||
Wait(0)
|
||||
|
||||
if mainIdentifier and Config.discordBotToken ~= "false" and Config.discordGuildId ~= "false" then
|
||||
discordInfo = checkDiscordIdentifier(identifiers)
|
||||
if not discordInfo and Config.discordMemeberRequired and not Config.sv_lan 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 Config.sv_lan then
|
||||
tempPlayersInfo[tempSrc] = {
|
||||
identifiers = {
|
||||
[Config.characterIdentifier] = "sv_lan"
|
||||
},
|
||||
discord = discordInfo
|
||||
}
|
||||
deferrals.done()
|
||||
return
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
MySQL.ready(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)
|
||||
|
||||
RegisterNetEvent("ND:updateClothing", function(clothing)
|
||||
local src = source
|
||||
local player = NDCore.getPlayer(src)
|
||||
if not player or not clothing or type(clothing) ~= "table" then return end
|
||||
player.setMetadata("clothing", clothing)
|
||||
end)
|
||||
|
||||
549
server/player.lua
Normal file
549
server/player.lua
Normal file
@@ -0,0 +1,549 @@
|
||||
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,
|
||||
phonenumber = info.phonenumber,
|
||||
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), key)
|
||||
TriggerEvent("ND:updateCharacter", self, key)
|
||||
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), "metadata")
|
||||
TriggerEvent("ND:updateCharacter", self, "metadata")
|
||||
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(key)
|
||||
if key then
|
||||
local charData = self[key]
|
||||
if not charData or type(charData) == "function" then
|
||||
return lib.print.error(key, "Is not valid character data.")
|
||||
end
|
||||
|
||||
if type(charData) == "table" then
|
||||
charData = json.encode(charData)
|
||||
end
|
||||
|
||||
local query = ("UPDATE nd_characters SET `%s` = ? WHERE charid = ?"):format(key)
|
||||
local affectedRows = MySQL.update.await(query, {charData, self.id})
|
||||
return affectedRows > 0
|
||||
end
|
||||
|
||||
local affectedRows = MySQL.update.await("UPDATE nd_characters SET name = ?, firstname = ?, lastname = ?, dob = ?, gender = ?, cash = ?, bank = ?, phonenumber = ?, `groups` = ?, metadata = ? WHERE charid = ?", {
|
||||
self.name,
|
||||
self.firstname,
|
||||
self.lastname,
|
||||
self.dob,
|
||||
self.gender,
|
||||
self.cash,
|
||||
self.bank,
|
||||
self.phonenumber,
|
||||
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
|
||||
local found = false
|
||||
|
||||
for i=1, #licenses do
|
||||
if licenses[i].type == licenseType then
|
||||
self.metadata.licenses[i] = license
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
self.metadata.licenses[#licenses+1] = license
|
||||
end
|
||||
else
|
||||
self.metadata.licenses = {license}
|
||||
end
|
||||
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "metadata")
|
||||
TriggerEvent("ND:updateCharacter", self, "metadata")
|
||||
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
|
||||
TriggerClientEvent("ox_lib:notify", self.source, ...)
|
||||
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
|
||||
|
||||
for group, role in pairs(Config.groupRoles) do
|
||||
if lib.table.contains(roles, role) then
|
||||
self.addGroup(group)
|
||||
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, customGroup, isJob)
|
||||
local groupRank = tonumber(rank) or 1
|
||||
local groupInfo = lib.table.deepclone(Config.groups?[name] or {})
|
||||
local bossRank = groupInfo?.minimumBossRank
|
||||
|
||||
for k, v in pairs(customGroup or {}) do
|
||||
groupInfo[k] = v
|
||||
end
|
||||
|
||||
if isJob then
|
||||
for _, group in pairs(self.groups) do
|
||||
group.isJob = nil
|
||||
end
|
||||
end
|
||||
|
||||
self.groups[name] = {
|
||||
name = name,
|
||||
label = groupInfo?.label or name,
|
||||
rankName = groupInfo?.ranks?[groupRank] or groupRank,
|
||||
rank = groupRank,
|
||||
isJob = isJob,
|
||||
isBoss = bossRank and groupRank >= bossRank,
|
||||
metadata = groupInfo.metadata or {}
|
||||
}
|
||||
|
||||
if not isJob then
|
||||
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "groups")
|
||||
TriggerEvent("ND:updateCharacter", self, "groups")
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
if self.job == name then
|
||||
self.job = nil
|
||||
self.jobInfo = nil
|
||||
end
|
||||
|
||||
self.groups[name] = nil
|
||||
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "groups")
|
||||
TriggerEvent("ND:updateCharacter", self, "groups")
|
||||
TriggerEvent("ND:groupRemoved", self, group)
|
||||
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, nil, true)
|
||||
if job then
|
||||
self.job = job.name
|
||||
self.jobInfo = job
|
||||
end
|
||||
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "job")
|
||||
TriggerEvent("ND:updateCharacter", self, "job")
|
||||
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 = NDCore.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,
|
||||
phonenumber = info.phonenumber,
|
||||
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, phonenumber, `groups`, metadata, inventory) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", {
|
||||
identifier,
|
||||
charInfo.name,
|
||||
charInfo.firstname,
|
||||
charInfo.lastname,
|
||||
charInfo.dob,
|
||||
charInfo.gender,
|
||||
charInfo.cash,
|
||||
charInfo.bank,
|
||||
charInfo.phonenumber,
|
||||
json.encode(charInfo.groups),
|
||||
json.encode(charInfo.metadata),
|
||||
json.encode(charInfo.inventory)
|
||||
})
|
||||
|
||||
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, NDCore.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,
|
||||
phonenumber = info.phonenumber,
|
||||
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 = ?", {NDCore.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,
|
||||
phonenumber = info.phonenumber,
|
||||
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
|
||||
788
server/vehicle.lua
Normal file
788
server/vehicle.lua
Normal file
@@ -0,0 +1,788 @@
|
||||
local ox_inventory
|
||||
local inventoryStarted = false
|
||||
local spawnedPlayerVehicles = {}
|
||||
local vehicleTypes = json.decode(GetResourceKvpString("ND_Core:vehTypes") or "[]")
|
||||
|
||||
NDCore.isResourceStarted("ox_inventory", function(started)
|
||||
inventoryStarted = started
|
||||
if not started then return end
|
||||
ox_inventory = exports.ox_inventory
|
||||
end)
|
||||
|
||||
local function getVehicleType(coords, model)
|
||||
if vehicleTypes[model] then
|
||||
return vehicleTypes[model]
|
||||
end
|
||||
|
||||
local tempVehicle = CreateVehicle(model, coords.x, coords.y, coords.z-5.0, coords.w, true, false)
|
||||
local time = os.time()
|
||||
|
||||
while not DoesEntityExist(tempVehicle) and os.time()-time < 5 do Wait(0) end
|
||||
if not DoesEntityExist(tempVehicle) then return end
|
||||
|
||||
local vehType = GetVehicleType(tempVehicle)
|
||||
DeleteEntity(tempVehicle)
|
||||
|
||||
vehicleTypes[model] = vehType
|
||||
SetResourceKvp("ND_Core:vehTypes", json.encode(vehicleTypes))
|
||||
|
||||
return vehType
|
||||
end
|
||||
|
||||
local function generatePlate()
|
||||
local plate = {}
|
||||
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
|
||||
if self.properties?.callsign then
|
||||
properties.callsign = true
|
||||
end
|
||||
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id})
|
||||
end
|
||||
end
|
||||
DeleteEntity(entity)
|
||||
end
|
||||
|
||||
|
||||
-- remvoe vehicle from db.
|
||||
function self.remove(keepEntity)
|
||||
if not keepEntity and DoesEntityExist(entity) then
|
||||
DeleteEntity(entity)
|
||||
end
|
||||
MySQL.query("DELETE FROM nd_vehicles WHERE id = ?", {self.id})
|
||||
end
|
||||
|
||||
--- set vehicle properties
|
||||
---@param props table
|
||||
function self.setProperties(props)
|
||||
if not DoesEntityExist(entity) then return end
|
||||
local properties = type(props) == "string" and json.decode(props) or props
|
||||
local state = Entity(entity).state
|
||||
|
||||
if self.properties?.callsign then
|
||||
properties.callsign = true
|
||||
end
|
||||
|
||||
state.props = properties
|
||||
self.properties = properties
|
||||
|
||||
if not self.id or not self.owner then return end
|
||||
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id})
|
||||
end
|
||||
|
||||
--- set vehicle locked/unlocked
|
||||
---@param status boolean
|
||||
function self.setLocked(status)
|
||||
if not DoesEntityExist(entity) then return end
|
||||
local state = Entity(entity).state
|
||||
state.locked = status
|
||||
self.locked = status
|
||||
end
|
||||
|
||||
--- update the vehicle plate
|
||||
---@param plate string
|
||||
---@return boolean
|
||||
function self.setPlate(plate)
|
||||
if not DoesEntityExist(entity) or MySQL.scalar.await("SELECT 1 FROM nd_vehicles WHERE plate = ?", {plate}) then return end
|
||||
if self.properties then
|
||||
self.properties.plate = plate
|
||||
local state = Entity(entity).state
|
||||
state.props = self.properties
|
||||
end
|
||||
|
||||
if not self.id or not self.owner then return end
|
||||
MySQL.query("UPDATE nd_vehicles SET plate = ? WHERE id = ?", {plate, self.id})
|
||||
return true
|
||||
end
|
||||
|
||||
--- set the vehicle availability status
|
||||
---@param statusType string
|
||||
---@param status boolean
|
||||
function self.setStatus(statusType, status)
|
||||
if not lib.table.contains({"stored", "impounded", "stolen"}, statusType) then return end
|
||||
if statusType ~= "stolen" then self.delete(true) end
|
||||
if not self.id or not self.owner then return end
|
||||
local query = ("UPDATE nd_vehicles SET %s = ? WHERE id = ?"):format(statusType)
|
||||
MySQL.query(query, {status and 1 or 0, self.id})
|
||||
return true
|
||||
end
|
||||
|
||||
function self.setMetadata(key, value)
|
||||
self.metadata[key] = value
|
||||
|
||||
if DoesEntityExist(entity) then
|
||||
local state = Entity(self.entity).state
|
||||
|
||||
if not state.metadata then
|
||||
state.metadata = self.metadata
|
||||
else
|
||||
state.metadata[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
if not self.id or not self.owner then
|
||||
lib.print.debug(('Skipped storing metadata for vehicle %s as it does not have an id/owner associated with it.'):format(self.entity))
|
||||
return
|
||||
end
|
||||
|
||||
MySQL.query("UPDATE nd_vehicles SET metadata = ? WHERE id = ?", {json.encode(self.metadata), self.id})
|
||||
end
|
||||
|
||||
if self.id and self.owner then
|
||||
spawnedPlayerVehicles[self.id] = self
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- get a characters owned vehicles
|
||||
---@param characterId number
|
||||
---@return table
|
||||
function NDCore.getVehicles(characterId)
|
||||
local result = MySQL.query.await("SELECT * FROM nd_vehicles WHERE owner = ?", {characterId})
|
||||
if not result then return {} end
|
||||
|
||||
local vehicles = {}
|
||||
for _, vehicle in pairs(result) do
|
||||
local info = getVehicleDatabaseInfo(vehicle)
|
||||
if info then
|
||||
vehicles[#vehicles+1] = info
|
||||
end
|
||||
end
|
||||
return vehicles
|
||||
end
|
||||
|
||||
--- give a player keys/access to a vehicle
|
||||
---@param source number
|
||||
---@param vehicle number
|
||||
---@param access boolean
|
||||
---@param info table
|
||||
function NDCore.giveVehicleAccess(source, vehicle, access, info)
|
||||
if not vehicle or not DoesEntityExist(vehicle) then return end
|
||||
local state = Entity(vehicle).state
|
||||
local netId = info?.netId or NetworkGetNetworkIdFromEntity(vehicle)
|
||||
local vehicleId = info?.vehicleId or state.id or generateTemporaryVehicleId()
|
||||
|
||||
if not state.id then
|
||||
state.id = vehicleId
|
||||
end
|
||||
|
||||
local player = NDCore.getPlayer(source)
|
||||
if player then
|
||||
local keys = state.keys or {}
|
||||
keys[player.id] = access
|
||||
state.keys = keys
|
||||
end
|
||||
|
||||
if not inventoryStarted or not Config.useInventoryForKeys then return end
|
||||
local plate = info?.plate or GetVehicleNumberPlateText(vehicle)
|
||||
local model = info?.model or GetEntityModel(vehicle)
|
||||
local modelName = info?.modelName or model and lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", source, model) or ""
|
||||
local hasKey = ox_inventory:GetSlotIdWithItem(source, "keys", {
|
||||
vehId = vehicleId
|
||||
})
|
||||
|
||||
if access and not hasKey then
|
||||
ox_inventory:AddItem(source, "keys", 1, {
|
||||
vehOwner = owner or state.owner,
|
||||
vehId = vehicleId,
|
||||
vehPlate = plate,
|
||||
vehModel = modelName,
|
||||
keyEnabled = true,
|
||||
vehNetId = netId
|
||||
})
|
||||
elseif not access and hasKey then
|
||||
ox_inventory:RemoveItem(source, "keys", 1, nil, hasKey)
|
||||
end
|
||||
end
|
||||
|
||||
--- spawn a vehicle, set info & give keys.
|
||||
---@param info table
|
||||
---@return table
|
||||
function NDCore.createVehicle(info)
|
||||
local owner = info.owner
|
||||
local vehicleId = info.vehicleId or generateTemporaryVehicleId()
|
||||
local properties = info.properties or {}
|
||||
local coords = info.coords
|
||||
local spawnCoords = coords
|
||||
|
||||
local coordType = type(coords)
|
||||
if coordType == "vector3" or coordType == "table" then
|
||||
spawnCoords = vector4(coords.x, coords.y, coords.z, coords.w or coords.h or info.heading or 0.0)
|
||||
end
|
||||
if not spawnCoords then
|
||||
return Citizen.Trace("NDCore.createVehicle", "spawnCoords not found")
|
||||
end
|
||||
|
||||
local model = info.model or properties.model
|
||||
local vehType = getVehicleType(spawnCoords, model)
|
||||
if not vehType then
|
||||
return Citizen.Trace("NDCore.createVehicle", "vehType not found")
|
||||
end
|
||||
|
||||
local veh = CreateVehicleServerSetter(model, vehType, spawnCoords.x, spawnCoords.y, spawnCoords.z, spawnCoords.w)
|
||||
local time = os.time()
|
||||
while not DoesEntityExist(veh) and os.time()-time < 5 do Wait(5) end
|
||||
if not veh or not DoesEntityExist(veh) then
|
||||
return Citizen.Trace("NDCore.createVehicle", "vehicle entity doesn't exist")
|
||||
end
|
||||
|
||||
local netId = NetworkGetNetworkIdFromEntity(veh)
|
||||
local state = Entity(veh).state
|
||||
state.locked = true
|
||||
local keys = info.keys or {}
|
||||
|
||||
if not properties.plate then
|
||||
properties.plate = generateVehiclePlate()
|
||||
end
|
||||
if owner then
|
||||
keys[owner] = true
|
||||
state.owner = owner
|
||||
end
|
||||
|
||||
state.keys = keys
|
||||
state.props = properties
|
||||
state.id = vehicleId
|
||||
local vehicleName
|
||||
if inventoryStarted and Config.useInventoryForKeys then
|
||||
for charId, _ in pairs(keys) do
|
||||
local playerSource = charId == owner and info.source
|
||||
if not playerSource then
|
||||
local player = NDCore.getPlayers("id", charId, true)[1]
|
||||
playerSource = player and player.source
|
||||
end
|
||||
if playerSource then
|
||||
if not vehicleName then
|
||||
vehicleName = lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", playerSource, model) or ""
|
||||
end
|
||||
NDCore.giveVehicleAccess(playerSource, veh, true, {
|
||||
vehicleId = vehicleId,
|
||||
netId = netId,
|
||||
plate = properties?.plate,
|
||||
model = model,
|
||||
vehicleName = vehicleName,
|
||||
owner = owner
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return NDCore.getVehicle(veh)
|
||||
end
|
||||
|
||||
--- transfer ownership of a vehicle between players
|
||||
---@param vehicleId number|string
|
||||
---@param fromSource number
|
||||
---@param toSource number
|
||||
---@return boolean
|
||||
function NDCore.transferVehicleOwnership(vehicleId, fromSource, toSource)
|
||||
local playerFrom = NDCore.getPlayer(fromSource)
|
||||
local playerTo = NDCore.getPlayer(toSource)
|
||||
if not playerFrom or not playerTo then return end
|
||||
|
||||
local vehicle = NDCore.getVehicleById(vehicleId)
|
||||
if not vehicle or vehicle.owner ~= playerFrom.id then return end
|
||||
|
||||
MySQL.query.await("UPDATE nd_vehicles SET owner = ? WHERE id = ?", {playerTo.id, vehicleId})
|
||||
local vehicleInfo = spawnedPlayerVehicles[vehicleId]
|
||||
|
||||
if vehicleInfo then
|
||||
local veh, netId, model = vehicleInfo.entity, vehicleInfo.netId, GetEntityModel(veh)
|
||||
playerFrom.triggerEvent("ND_Vehicles:blip", netId, false)
|
||||
playerTo.triggerEvent("ND_Vehicles:blip", netId, true)
|
||||
|
||||
NDCore.giveVehicleAccess(fromSource, veh, false, {
|
||||
vehicleId = vehicleId,
|
||||
netId = netId,
|
||||
model = model,
|
||||
owner = playerFrom.id
|
||||
})
|
||||
|
||||
local state = Entity(veh).state
|
||||
state.owner = playerTo.id
|
||||
NDCore.giveVehicleAccess(toSource, veh, true, {
|
||||
vehicleId = vehicleId,
|
||||
netId = netId,
|
||||
model = model,
|
||||
owner = playerTo.id
|
||||
})
|
||||
end
|
||||
|
||||
playerFrom.notify({
|
||||
title = "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 vehicle.owner ~= player.id then return end
|
||||
if not vehicle.available and not vehicle.impounded then return end
|
||||
|
||||
MySQL.query.await("UPDATE nd_vehicles SET stored = ? WHERE id = ?", {0, vehicleId})
|
||||
|
||||
local properties = vehicle.properties
|
||||
if properties.callsign then
|
||||
local callsign = player.getMetadata("callsign")
|
||||
if not callsign or callsign == "" then goto skip end
|
||||
|
||||
callsign = tostring(callsign)
|
||||
if not callsign or callsign:len() ~= 3 then goto skip end
|
||||
|
||||
properties.modFender = tonumber(callsign:sub(1, 1))
|
||||
properties.modRightFender = tonumber(callsign:sub(2, 2))
|
||||
properties.modRoof = tonumber(callsign:sub(3, 3))
|
||||
end
|
||||
|
||||
::skip::
|
||||
|
||||
return NDCore.createVehicle({
|
||||
owner = player.id,
|
||||
model = properties.model,
|
||||
coords = vec4(coords.x, coords.y, coords.z, coords.w or coords.heading or heading),
|
||||
properties = properties,
|
||||
vehicleId = vehicleId,
|
||||
source = source
|
||||
})
|
||||
end
|
||||
|
||||
local function toggleVehicleLock(source, entity, nearby, metadata)
|
||||
local player = NDCore.getPlayer(source)
|
||||
|
||||
if metadata and not metadata.keyEnabled then
|
||||
return player.notify({
|
||||
title = "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.
|
||||
AddEventHandler("entityCreated", function(entity)
|
||||
local time = os.time()
|
||||
while not DoesEntityExist(entity) and os.time()-time < 5 do Wait(50) end
|
||||
|
||||
if not DoesEntityExist(entity) or GetEntityType(entity) ~= 2 then return end
|
||||
|
||||
local state = Entity(entity).state
|
||||
if state.owner or state.locked ~= nil then return end
|
||||
|
||||
time = os.time()
|
||||
local driver = GetPedInVehicleSeat(entity, -1)
|
||||
while DoesEntityExist(entity) and driver == 0 and os.time()-time < 2 do
|
||||
driver = GetPedInVehicleSeat(entity, -1)
|
||||
Wait(100)
|
||||
end
|
||||
|
||||
if not DoesEntityExist(entity) then return end
|
||||
|
||||
if DoesEntityExist(driver) and IsPedAPlayer(driver) then
|
||||
state.locked = false
|
||||
state.hotwired = true
|
||||
end
|
||||
|
||||
if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end
|
||||
state.locked = true
|
||||
end)
|
||||
|
||||
-- disables inventory vehicles keys, disabled vehicles keys can no longer be used. Kinda like taking the battery out.
|
||||
RegisterNetEvent("ND_Vehicles:disableKey", function(slot)
|
||||
local src = source
|
||||
local key = ox_inventory:GetSlot(src, slot)
|
||||
local metadata = key.metadata
|
||||
if not metadata.keyEnabled then return end
|
||||
metadata.keyEnabled = false
|
||||
ox_inventory:SetMetadata(src, slot, key.metadata)
|
||||
end)
|
||||
|
||||
-- sync alarm when vehicle is lockpicked.
|
||||
RegisterNetEvent("ND_Vehicles:lockpick", function(netId, success)
|
||||
local src = source
|
||||
local veh = NetworkGetEntityFromNetworkId(netId)
|
||||
if not veh or not DoesEntityExist(veh) then return end
|
||||
|
||||
local ped = GetPlayerPed(src)
|
||||
local pedCoords = GetEntityCoords(ped)
|
||||
local vehCoords = GetEntityCoords(veh)
|
||||
if #(pedCoords-vehCoords) > 5.0 then return end
|
||||
|
||||
local owner = NetworkGetEntityOwner(veh)
|
||||
TriggerClientEvent("ND_Vehicles:syncAlarm", owner, netId)
|
||||
|
||||
if not success then return end
|
||||
local state = Entity(veh).state
|
||||
state.locked = false
|
||||
end)
|
||||
|
||||
-- sync alarm if vehicle gets hotwired.
|
||||
RegisterNetEvent("ND_Vehicles:hotwire", function(netId)
|
||||
local src = source
|
||||
local ped = GetPlayerPed(src)
|
||||
local playerVeh = GetVehiclePedIsIn(ped)
|
||||
local veh = NetworkGetEntityFromNetworkId(netId)
|
||||
if not playerVeh or playerVeh == 0 or playerVeh ~= veh then return end
|
||||
local state = Entity(veh).state
|
||||
state.hotwired = true
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ND_Vehicles:storeVehicle", function(netId)
|
||||
local src = source
|
||||
local vehicle = NDCore.getVehicle(NetworkGetEntityFromNetworkId(netId))
|
||||
if not vehicle then return end
|
||||
|
||||
local player = NDCore.getPlayer(src)
|
||||
if not vehicle.setStatus("stored", true) or not player or player.id ~= vehicle.owner then
|
||||
return player.notify({
|
||||
title = "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
|
||||
|
||||
local info = NDCore.spawnOwnedVehicle(src, vehicle.id, isParkingAvailable(locations))
|
||||
if not info then return end
|
||||
TriggerClientEvent("ND_Vehicles:blip", src, info.netId, true)
|
||||
|
||||
if vehicle.impounded then
|
||||
local reclaimPrice = vehicle.metadata.impoundReclaimPrice or 200
|
||||
if not player.deductMoney("bank", reclaimPrice, "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
|
||||
end)
|
||||
|
||||
lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src)
|
||||
local player = NDCore.getPlayer(src)
|
||||
if not player then return end
|
||||
return NDCore.getVehicles(player.id)
|
||||
end)
|
||||
|
||||
AddEventHandler("ND:characterLoaded", function(player)
|
||||
local ownedExistingVehicles = {}
|
||||
local ownedVehicles = NDCore.getVehicles(player.id)
|
||||
local vehicles = GetAllVehicles()
|
||||
local vehiclesToImpound = {}
|
||||
|
||||
for i=1, #vehicles do
|
||||
local veh = vehicles[i]
|
||||
local state = Entity(veh).state
|
||||
if state.owner == player.id then
|
||||
ownedExistingVehicles[#ownedExistingVehicles+1] = state.id
|
||||
end
|
||||
end
|
||||
|
||||
for i=1, #ownedVehicles do
|
||||
local veh = ownedVehicles[i]
|
||||
if veh and not veh.stored and not veh.impounded and not lib.table.contains(ownedExistingVehicles, veh.id) then
|
||||
vehiclesToImpound[#vehiclesToImpound+1] = veh.id
|
||||
end
|
||||
end
|
||||
|
||||
if #vehiclesToImpound == 0 then return end
|
||||
|
||||
local query = ("UPDATE nd_vehicles SET impounded = ? WHERE owner = ? AND id IN (%s)"):format(table.concat(vehiclesToImpound, ", "))
|
||||
MySQL.rawExecute(query, {1, player.id})
|
||||
end)
|
||||
30
shared/functions.lua
Normal file
30
shared/functions.lua
Normal 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
|
||||
@@ -1 +0,0 @@
|
||||
NDCore = exports["ND_Core"]:GetCoreObject()
|
||||
@@ -1,30 +0,0 @@
|
||||
local startedResources = {}
|
||||
local callbacks = {}
|
||||
|
||||
AddEventHandler("onResourceStart", function(resourceName)
|
||||
startedResources[resourceName] = true
|
||||
local callback = callbacks[resourceName]
|
||||
if not callback then return end
|
||||
callback(true)
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStop", function(resourceName)
|
||||
startedResources[resourceName] = nil
|
||||
local callback = callbacks[resourceName]
|
||||
if not callback then return end
|
||||
callback(false)
|
||||
end)
|
||||
|
||||
function isResourceStarted(resourceName, cb)
|
||||
local started = GetResourceState(resourceName) == "started"
|
||||
startedResources[resourceName] = started
|
||||
|
||||
if cb then
|
||||
callbacks[resourceName] = cb
|
||||
cb(started)
|
||||
end
|
||||
|
||||
return started
|
||||
end
|
||||
|
||||
exports("isResourceStarted", isResourceStarted)
|
||||
Reference in New Issue
Block a user