mirror of
https://github.com/ND-Framework/ND_Core.git
synced 2026-08-17 13:56:02 +01:00
Compare commits
243 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
e9d8826188 | ||
|
|
7412c14976 | ||
|
|
5242cf289d | ||
|
|
906383bbd9 | ||
|
|
a5b297e6f8 | ||
|
|
4551db736c | ||
|
|
30ac126949 | ||
|
|
220ecbbce1 | ||
|
|
9a7afdbc8f | ||
|
|
1b6870dd87 | ||
|
|
aa73db1111 | ||
|
|
68563c6d18 | ||
|
|
a9d42b9861 | ||
|
|
eac4de4623 | ||
|
|
08815f2ecd | ||
|
|
88e8a4218a | ||
|
|
99b8d85901 | ||
|
|
378e552fe7 |
38
README.md
38
README.md
@@ -1,22 +1,28 @@
|
|||||||
<p align="center">
|
<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>
|
||||||
|
|
||||||
<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://ndframework.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:
|
## Dependencies:
|
||||||
[oxmysql](https://github.com/overextended/oxmysql/releases/download/v2.4.0/oxmysql.zip)
|
* [oxmysql](https://github.com/overextended/oxmysql/releases)
|
||||||
|
* [ox_lib](https://github.com/overextended/ox_lib/releases)
|
||||||
|
|
||||||
## Addons
|
## Addons
|
||||||
* [Character Selection](https://github.com/ND-Framework/ND_CharacterSelection)
|
* [Character selection](https://github.com/ND-Framework/ND_Characters/releases)
|
||||||
* [Fuel (with nozzle and hose)](https://github.com/ND-Framework/ND_Fuel)
|
* [Banking](https://github.com/ND-Framework/ND_Banking/releases)
|
||||||
* [Shot Spotter](https://github.com/ND-Framework/ND_ShotSpotter)
|
* [Appearance shops](https://github.com/ND-Framework/ND_AppearanceShops/releases)
|
||||||
* [Banking](https://github.com/ND-Framework/ND_Banking)
|
* [Dealership](https://github.com/ND-Framework/ND_Dealership/releases)
|
||||||
* [Door locks](https://github.com/ND-Framework/ND_Doorlocks)
|
* [Inventory](https://github.com/overextended/ox_inventory/releases)
|
||||||
* [Ox Inventory](https://github.com/overextended/ox_inventory/releases)
|
* [Ambulance job](https://github.com/ND-Framework/ND_Ambulance/releases)
|
||||||
* [No Characters (use framework without character selection)](https://github.com/ND-Framework/ND_NoCharacters)
|
* [Casino](https://github.com/ND-Framework/ND_Casino/releases)
|
||||||
|
* [Blackjack](https://github.com/ND-Framework/ND_Blackjack/releases)
|
||||||
# Need support?
|
* [Nitro](https://github.com/ND-Framework/ND_Nitro/releases)
|
||||||
[](https://discord.gg/Z9Mxu72zZ6)
|
|
||||||
|
|||||||
55
client/death.lua
Normal file
55
client/death.lua
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
local ambulance
|
||||||
|
local usingAmbulance = false
|
||||||
|
local alreadyEliminated = false
|
||||||
|
|
||||||
|
NDCore.isResourceStarted("ND_Ambulance", function(started)
|
||||||
|
usingAmbulance = started
|
||||||
|
if not usingAmbulance then return end
|
||||||
|
ambulance = exports["ND_Ambulance"]
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function PlayerEliminated(deathCause, killerServerId, killerClientId)
|
||||||
|
if alreadyEliminated then return end
|
||||||
|
alreadyEliminated = true
|
||||||
|
local info = {
|
||||||
|
deathCause = deathCause,
|
||||||
|
killerServerId = killerServerId,
|
||||||
|
killerClientId = killerClientId,
|
||||||
|
damagedBones = usingAmbulance and ambulance:getBodyDamage() or {}
|
||||||
|
}
|
||||||
|
TriggerEvent("ND:playerEliminated", info)
|
||||||
|
TriggerServerEvent("ND:playerEliminated", info)
|
||||||
|
Wait(1000)
|
||||||
|
alreadyEliminated = false
|
||||||
|
end
|
||||||
|
|
||||||
|
AddEventHandler("gameEventTriggered", function(name, args)
|
||||||
|
if name ~= "CEventNetworkEntityDamage" then return end
|
||||||
|
|
||||||
|
local victim = args[1]
|
||||||
|
if not IsPedAPlayer(victim) or NetworkGetPlayerIndexFromPed(victim) ~= cache.playerId then return end
|
||||||
|
|
||||||
|
if not IsPedDeadOrDying(victim, true) then
|
||||||
|
local hit, bone = GetPedLastDamageBone(victim)
|
||||||
|
if hit and usingAmbulance then
|
||||||
|
local damageWeapon = ambulance:getLastDamagingWeapon(victim)
|
||||||
|
return damageWeapon and ambulance:updateBodyDamage(bone, damageWeapon)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local killerEntity, deathCause = GetPedSourceOfDeath(cache.ped), GetPedCauseOfDeath(cache.ped)
|
||||||
|
local killerClientId = NetworkGetPlayerIndexFromPed(killerEntity)
|
||||||
|
if killerEntity ~= cache.ped and killerClientId and NetworkIsPlayerActive(killerClientId) then
|
||||||
|
return PlayerEliminated(deathCause, GetPlayerServerId(killerClientId), killerClientId)
|
||||||
|
end
|
||||||
|
PlayerEliminated(deathCause)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local firstSpawn = true
|
||||||
|
exports.spawnmanager:setAutoSpawnCallback(function()
|
||||||
|
if firstSpawn then
|
||||||
|
firstSpawn = false
|
||||||
|
return exports.spawnmanager:spawnPlayer() and exports.spawnmanager:setAutoSpawn(false)
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -1,24 +1,26 @@
|
|||||||
RegisterNetEvent("ND:returnCharacters", function(characters)
|
|
||||||
NDCore.Characters = characters
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- updates the money on the client.
|
-- updates the money on the client.
|
||||||
RegisterNetEvent("ND:updateMoney", function(cash, bank)
|
RegisterNetEvent("ND:updateMoney", function(cash, bank)
|
||||||
NDCore.SelectedCharacter.cash = cash
|
if not NDCore.player then return end
|
||||||
NDCore.SelectedCharacter.bank = bank
|
NDCore.player.cash = cash
|
||||||
|
NDCore.player.bank = bank
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Sets main character.
|
-- Sets main character.
|
||||||
RegisterNetEvent("ND:setCharacter", function(character)
|
RegisterNetEvent("ND:characterLoaded", function(character)
|
||||||
NDCore.SelectedCharacter = character
|
NDCore.player = character
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Update main character info.
|
-- Update main character info.
|
||||||
RegisterNetEvent("ND:updateCharacter", function(character)
|
RegisterNetEvent("ND:updateCharacter", function(character)
|
||||||
NDCore.SelectedCharacter = character
|
NDCore.player = character
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Updates last lcoation.
|
-- Updates last lcoation.
|
||||||
RegisterNetEvent("ND:updateLastLocation", function(location)
|
RegisterNetEvent("ND:updateLastLocation", function(location)
|
||||||
NDCore.SelectedCharacter.lastLocation = location
|
if not NDCore.player then return end
|
||||||
|
NDCore.player.lastLocation = location
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent("ND:revivePlayer", function()
|
||||||
|
NDCore.revivePlayer(true)
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -1,11 +1,88 @@
|
|||||||
function GetCoreObject()
|
function NDCore.getPlayer()
|
||||||
return NDCore
|
return NDCore.player
|
||||||
end
|
end
|
||||||
|
|
||||||
function NDCore.Functions.GetSelectedCharacter()
|
function NDCore.getCharacters()
|
||||||
return NDCore.SelectedCharacter
|
return NDCore.characters
|
||||||
end
|
end
|
||||||
|
|
||||||
function NDCore.Functions.GetCharacters()
|
function NDCore.getPlayersFromCoords(distance, coords)
|
||||||
return NDCore.Characters
|
if coords then
|
||||||
end
|
coords = type(coords) == "table" and vec3(coords.x, coords.y, coords.z) or coords
|
||||||
|
else
|
||||||
|
coords = GetEntityCoords(PlayerPedId())
|
||||||
|
end
|
||||||
|
distance = distance or 5
|
||||||
|
local closePlayers = {}
|
||||||
|
local players = GetActivePlayers()
|
||||||
|
for _, player in ipairs(players) do
|
||||||
|
local target = GetPlayerPed(player)
|
||||||
|
local targetCoords = GetEntityCoords(target)
|
||||||
|
local targetdistance = #(targetCoords - coords)
|
||||||
|
if targetdistance <= distance then
|
||||||
|
closePlayers[#closePlayers + 1] = player
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return closePlayers
|
||||||
|
end
|
||||||
|
|
||||||
|
function NDCore.getConfig(info)
|
||||||
|
if not info then
|
||||||
|
return Config
|
||||||
|
end
|
||||||
|
return Config[info]
|
||||||
|
end
|
||||||
|
|
||||||
|
function NDCore.revivePlayer(reset, keepDead)
|
||||||
|
local usingAmbulance = GetResourceState("ND_Ambulance") == "started"
|
||||||
|
if not keepDead then
|
||||||
|
LocalPlayer.state.dead = false
|
||||||
|
if usingAmbulance then
|
||||||
|
local state = Player(cache.serverId).state
|
||||||
|
state:set("isDead", false, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local veh = GetVehiclePedIsIn(cache.ped)
|
||||||
|
local seat = cache.seat
|
||||||
|
local coords = GetEntityCoords(cache.ped)
|
||||||
|
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, GetEntityHeading(cache.ped), true, true, false)
|
||||||
|
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
if cache.ped ~= ped then
|
||||||
|
DeleteEntity(cache.ped)
|
||||||
|
ClearAreaOfPeds(coords.x, coords.y, coords.z, 0.2, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
SetEntityInvincible(ped, false)
|
||||||
|
FreezeEntityPosition(ped, false)
|
||||||
|
SetEntityVisible(ped, true)
|
||||||
|
SetEveryoneIgnorePlayer(ped, false)
|
||||||
|
SetPedCanBeTargetted(ped, true)
|
||||||
|
SetEntityCanBeDamaged(ped, true)
|
||||||
|
SetBlockingOfNonTemporaryEvents(ped, false)
|
||||||
|
SetPedCanRagdollFromPlayerImpact(ped, true)
|
||||||
|
ClearPedTasksImmediately(ped)
|
||||||
|
|
||||||
|
if veh and veh ~= 0 then
|
||||||
|
SetPedIntoVehicle(ped, veh, seat)
|
||||||
|
end
|
||||||
|
if reset and GetPedMovementClipset(ped) == `move_m@injured` then
|
||||||
|
ClearEntityLastDamageEntity(ped)
|
||||||
|
SetPedMoveRateOverride(ped, 1.0)
|
||||||
|
ResetPedMovementClipset(ped, 0)
|
||||||
|
end
|
||||||
|
if reset and usingAmbulance then
|
||||||
|
exports["ND_Ambulance"]:resetBodyDamage()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function NDCore.notify(...)
|
||||||
|
lib.notify(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
for name, func in pairs(NDCore) do
|
||||||
|
if type(func) == "function" then
|
||||||
|
exports(name, func)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
116
client/main.lua
116
client/main.lua
@@ -1,54 +1,70 @@
|
|||||||
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
|
||||||
|
|
||||||
NDCore = {}
|
NDCore = {}
|
||||||
NDCore.SelectedCharacter = nil
|
|
||||||
NDCore.Characters = {}
|
|
||||||
NDCore.Functions = {}
|
|
||||||
NDCore.Config = config
|
|
||||||
|
|
||||||
-- discord rich precense will show on a users profile.
|
Config = {
|
||||||
if config.enableRichPresence then
|
serverName = GetConvar("core:serverName", "Unconfigured ND-Core Server"),
|
||||||
Citizen.CreateThread(function()
|
discordInvite = GetConvar("core:discordInvite", "https://discord.gg/Z9Mxu72zZ6"),
|
||||||
while true do
|
discordAppId = GetConvar("core:discordAppId", "858146067018416128"),
|
||||||
if NDCore.SelectedCharacter then
|
discordAsset = GetConvar("core:discordAsset", "andyyy"),
|
||||||
SetDiscordAppId(config.appId)
|
discordAssetSmall = GetConvar("core:discordAssetSmall", "andyyy"),
|
||||||
SetRichPresence(" Playing : " .. config.serverName .. " as " .. NDCore.SelectedCharacter.firstName .. " " .. NDCore.SelectedCharacter.lastName)
|
discordActionText = GetConvar("core:discordActionText", "DISCORD"),
|
||||||
SetDiscordRichPresenceAsset(config.largeLogo)
|
discordActionLink = GetConvar("discordActionLink", "https://discord.gg/Z9Mxu72zZ6"),
|
||||||
SetDiscordRichPresenceAssetText("Playing: " .. config.serverName)
|
discordActionText2 = GetConvar("core:discordActionText2", "STORE"),
|
||||||
SetDiscordRichPresenceAssetSmall(config.smallLogo)
|
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
|
||||||
SetDiscordRichPresenceAssetSmallText("Playing as: " .. NDCore.SelectedCharacter.firstName .. " " .. NDCore.SelectedCharacter.lastName)
|
disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1,
|
||||||
SetDiscordRichPresenceAction(0, config.firstButtonName, config.firstButtonLink)
|
randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30),
|
||||||
SetDiscordRichPresenceAction(1, config.secondButtonName, config.secondButtonLink)
|
requireKeys = GetConvarInt("core:requireKeys", 1) == 1,
|
||||||
end
|
useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1,
|
||||||
Citizen.Wait(config.updateIntervall * 1000)
|
groups = json.decode(GetConvar("core:groups", "[]")),
|
||||||
|
compatibility = json.decode(GetConvar("core:compatibility", "[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Discord rich presence.
|
||||||
|
CreateThread(function()
|
||||||
|
SetDiscordAppId(Config.discordAppId)
|
||||||
|
SetDiscordRichPresenceAsset(Config.discordAsset)
|
||||||
|
SetDiscordRichPresenceAssetSmall(Config.discordAssetSmall)
|
||||||
|
SetDiscordRichPresenceAction(0, Config.discordActionText, Config.discordActionLink)
|
||||||
|
SetDiscordRichPresenceAction(1, Config.discordActionText2, Config.discordActionLink2)
|
||||||
|
local presenceText = ("Playing: %s"):format(Config.serverName)
|
||||||
|
while true do
|
||||||
|
if NDCore.player then
|
||||||
|
local presence = ("Playing: %s as %s %s"):format(Config.serverName, NDCore.player.firstname, NDCore.player.lastname)
|
||||||
|
local presenceTextSmall = ("Playing as: %s %s"):format(NDCore.player.firstname, NDCore.player.lastname)
|
||||||
|
SetRichPresence(presence)
|
||||||
|
SetDiscordRichPresenceAssetText(presenceText)
|
||||||
|
SetDiscordRichPresenceAssetSmallText(presenceTextSmall)
|
||||||
end
|
end
|
||||||
end)
|
Wait(60000)
|
||||||
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)
|
|
||||||
end
|
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)
|
||||||
243
client/vehicle/data.lua
Normal file
243
client/vehicle/data.lua
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
return {
|
||||||
|
{
|
||||||
|
garageType = "land",
|
||||||
|
groups = {"sahp", "lspd", "bcso"},
|
||||||
|
ped = vector4(452.83, -1027.79, 28.54, 2.49),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(446.19, -1025.47, 28.24, 185.96),
|
||||||
|
vector4(438.75, -1026.09, 28.38, 184.29),
|
||||||
|
vector4(434.98, -1026.61, 28.46, 185.99),
|
||||||
|
vector4(431.26, -1027.31, 28.53, 185.59),
|
||||||
|
vector4(427.52, -1026.84, 28.58, 184.25)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "land",
|
||||||
|
impound = true,
|
||||||
|
ped = vector4(407.99, -1624.74, 29.29, 229.93),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(396.34, -1644.28, 28.86, 319.10),
|
||||||
|
vector4(398.56, -1646.42, 28.8616, 319.18),
|
||||||
|
vector4(400.68, -1648.86, 28.86, 140.78),
|
||||||
|
vector4(403.32, -1650.54, 28.86, 319.92),
|
||||||
|
vector4(403.21, -1650.68, 28.86, 139.40)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "plane",
|
||||||
|
ped = vector4(-941.05, -2966.04, 13.95, 136.06),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(-974.98, -2977.90, 14.55, 59.91)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "heli",
|
||||||
|
ped = vector4(-731.00, -1394.54, 5.00, 245.52),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(-746.01, -1469.39, 5.68, 139.27),
|
||||||
|
vector4(-725.26, -1444.72, 5.68, 139.58)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "plane",
|
||||||
|
ped = vector4(1742.77, 3298.25, 41.22, 132.91),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(1734.26, 3250.98, 41.96, 81.11),
|
||||||
|
vector4(1729.32, 3270.69, 41.74, 145.39)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "plane",
|
||||||
|
ped = vector4(-1241.26, -3391.48, 13.94, 35.03),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(-1254.71, -3388.15, 14.54, 330.04),
|
||||||
|
vector4(-1270.36, -3378.80, 14.54, 330.20),
|
||||||
|
vector4(-1286.05, -3369.44, 14.54, 329.99)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "plane",
|
||||||
|
ped = vector4(-1621.10, -3151.63, 13.99, 41.26),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(-1634.04, -3147.84, 14.60, 330.28),
|
||||||
|
vector4(-1649.75, -3138.34, 14.60, 329.20),
|
||||||
|
vector4(-1665.42, -3129.26, 14.60, 330.10)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "heli",
|
||||||
|
ped = vector4(-1121.87, -2839.88, 13.95, 150.58),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(-1178.71, -2846.51, 14.62, 150.16),
|
||||||
|
vector4(-1146.40, -2865.22, 14.62, 151.08),
|
||||||
|
vector4(-1112.85, -2884.66, 14.62, 149.70)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "water",
|
||||||
|
ped = vector4(-831.03, -1359.53, 5.00, 299.80),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(-846.16, -1362.07, 0.39, 110.54),
|
||||||
|
vector4(-842.53, -1372.00, 0.39, 111.54),
|
||||||
|
vector4(-849.29, -1353.26, 0.38, 109.53),
|
||||||
|
vector4(-852.26, -1345.09, 0.41, 110.52),
|
||||||
|
vector4(-855.49, -1336.60, 0.40, 107.43),
|
||||||
|
vector4(-858.63, -1328.30, 0.40, 109.82),
|
||||||
|
vector4(-839.35, -1380.30, 0.39, 109.27),
|
||||||
|
vector4(-836.38, -1388.93, 0.41, 110.11),
|
||||||
|
vector4(-833.29, -1397.32, 0.40, 110.80),
|
||||||
|
vector4(-830.34, -1405.65, 0.37, 110.16)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "land",
|
||||||
|
ped = vector4(-280.32, -888.42, 31.32, 250.68),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(-282.36, -915.11, 30.38, 68.81),
|
||||||
|
vector4(-284.27, -918.37, 30.38, 70.58),
|
||||||
|
vector4(-285.44, -921.82, 30.38, 250.22),
|
||||||
|
vector4(-285.70, -887.62, 30.38, 167.04),
|
||||||
|
vector4(-292.70, -885.93, 30.38, 167.09),
|
||||||
|
vector4(-285.76, -887.47, 30.38, 169.59),
|
||||||
|
vector4(-300.44, -885.14, 30.38, 347.05),
|
||||||
|
vector4(-303.59, -883.71, 30.38, 167.69),
|
||||||
|
vector4(-309.38, -896.88, 30.38, 167.15),
|
||||||
|
vector4(-312.98, -896.30, 30.38, 166.46),
|
||||||
|
vector4(-316.68, -896.26, 30.37, 347.94),
|
||||||
|
vector4(-311.06, -881.94, 30.38, 168.21),
|
||||||
|
vector4(-314.78, -881.86, 30.37, 348.45)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "land",
|
||||||
|
ped = vector4(597.53, 91.08, 93.13, 250.54),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(598.53, 98.37, 92.27, 69.47),
|
||||||
|
vector4(599.79, 102.00, 92.27, 249.38),
|
||||||
|
vector4(608.21, 103.90, 92.18, 248.98),
|
||||||
|
vector4(600.60, 111.38, 92.27, 73.07),
|
||||||
|
vector4(609.91, 107.59, 92.23, 68.77),
|
||||||
|
vector4(601.36, 114.99, 92.27, 250.19),
|
||||||
|
vector4(611.07, 111.39, 92.29, 249.69),
|
||||||
|
vector4(603.52, 118.52, 92.26, 67.62),
|
||||||
|
vector4(612.74, 114.94, 92.28, 69.01),
|
||||||
|
vector4(604.00, 122.58, 92.27, 249.76),
|
||||||
|
vector4(613.78, 118.83, 92.29, 248.82),
|
||||||
|
vector4(622.43, 115.48, 91.99, 70.39),
|
||||||
|
vector4(628.56, 110.25, 91.47, 249.30),
|
||||||
|
vector4(620.67, 111.89, 92.03, 250.24),
|
||||||
|
vector4(618.41, 104.51, 91.97, 72.22),
|
||||||
|
vector4(624.87, 99.25, 91.36, 63.32),
|
||||||
|
vector4(616.59, 100.82, 91.97, 249.00)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "land",
|
||||||
|
ped = vector4(100.64, -1072.88, 29.37, 341.55),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(106.13, -1063.24, 28.51, 66.86),
|
||||||
|
vector4(107.82, -1059.73, 28.51, 66.99),
|
||||||
|
vector4(112.39, -1049.71, 28.52, 67.40),
|
||||||
|
vector4(110.76, -1053.09, 28.51, 68.13),
|
||||||
|
vector4(109.06, -1056.38, 28.51, 66.45),
|
||||||
|
vector4(117.53, -1081.03, 28.50, 181.40),
|
||||||
|
vector4(119.09, -1069.44, 28.50, 181.12),
|
||||||
|
vector4(121.22, -1081.42, 28.50, 0.57),
|
||||||
|
vector4(122.32, -1070.46, 28.50, 0.67),
|
||||||
|
vector4(125.62, -1069.54, 28.50, 179.25),
|
||||||
|
vector4(124.83, -1081.17, 28.50, 180.37),
|
||||||
|
vector4(128.62, -1081.71, 28.50, 1.36),
|
||||||
|
vector4(128.93, -1070.62, 28.50, 359.89),
|
||||||
|
vector4(132.24, -1069.35, 28.50, 181.54),
|
||||||
|
vector4(132.31, -1081.45, 28.50, 180.86),
|
||||||
|
vector4(135.93, -1081.35, 28.50, 358.82),
|
||||||
|
vector4(135.64, -1070.75, 28.50, 0.43),
|
||||||
|
vector4(139.75, -1081.91, 28.50, 182.18),
|
||||||
|
vector4(138.86, -1070.37, 28.50, 181.90),
|
||||||
|
vector4(143.55, -1081.41, 28.50, 0.89),
|
||||||
|
vector4(147.13, -1081.48, 28.50, 179.02),
|
||||||
|
vector4(150.95, -1081.49, 28.50, 358.79),
|
||||||
|
vector4(154.64, -1081.32, 28.50, 179.51),
|
||||||
|
vector4(158.38, -1082.03, 28.50, 1.57),
|
||||||
|
vector4(162.10, -1081.32, 28.50, 179.88)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
garageType = "land",
|
||||||
|
ped = vector4(214.84, -806.24, 30.81, 342.23),
|
||||||
|
vehicleSpawns = {
|
||||||
|
vector4(251.26, -774.63, 29.98, 67.34),
|
||||||
|
vector4(245.36, -772.34, 30.02, 66.81),
|
||||||
|
vector4(219.19, -765.93, 30.14, 249.78),
|
||||||
|
vector4(228.11, -768.94, 30.10, 249.66),
|
||||||
|
vector4(233.68, -771.18, 30.07, 249.82),
|
||||||
|
vector4(244.01, -775.06, 29.99, 249.41),
|
||||||
|
vector4(249.53, -777.07, 29.95, 251.09),
|
||||||
|
vector4(248.74, -779.52, 29.92, 69.14),
|
||||||
|
vector4(243.64, -777.63, 29.96, 69.23),
|
||||||
|
vector4(233.47, -773.91, 30.05, 69.88),
|
||||||
|
vector4(218.62, -768.58, 30.14, 69.81),
|
||||||
|
vector4(217.02, -770.83, 30.16, 248.11),
|
||||||
|
vector4(226.09, -773.95, 30.09, 249.30),
|
||||||
|
vector4(231.82, -776.14, 30.04, 249.17),
|
||||||
|
vector4(242.05, -779.91, 29.93, 246.99),
|
||||||
|
vector4(247.72, -782.02, 29.88, 251.04),
|
||||||
|
vector4(216.19, -773.65, 30.16, 67.90),
|
||||||
|
vector4(225.82, -776.85, 30.08, 65.64),
|
||||||
|
vector4(230.77, -778.89, 30.03, 69.40),
|
||||||
|
vector4(241.72, -782.84, 29.90, 69.13),
|
||||||
|
vector4(247.12, -784.93, 29.84, 67.56),
|
||||||
|
vector4(246.23, -787.13, 29.82, 247.56),
|
||||||
|
vector4(239.40, -784.68, 29.90, 248.06),
|
||||||
|
vector4(230.42, -781.41, 30.01, 247.48),
|
||||||
|
vector4(224.14, -779.01, 30.07, 247.39),
|
||||||
|
vector4(215.32, -775.95, 30.17, 248.99),
|
||||||
|
vector4(243.81, -792.19, 29.77, 246.23),
|
||||||
|
vector4(238.08, -789.95, 29.85, 250.62),
|
||||||
|
vector4(228.14, -786.30, 30.01, 249.07),
|
||||||
|
vector4(221.98, -783.92, 30.08, 248.25),
|
||||||
|
vector4(213.41, -781.01, 30.19, 249.21),
|
||||||
|
vector4(214.77, -778.70, 30.17, 67.86),
|
||||||
|
vector4(224.02, -781.91, 30.07, 68.82),
|
||||||
|
vector4(229.34, -784.05, 30.01, 66.57),
|
||||||
|
vector4(239.87, -787.83, 29.86, 69.37),
|
||||||
|
vector4(245.18, -789.87, 29.79, 68.00),
|
||||||
|
vector4(241.85, -797.20, 29.72, 245.51),
|
||||||
|
vector4(236.57, -795.07, 29.82, 247.54),
|
||||||
|
vector4(226.12, -791.34, 29.99, 247.84),
|
||||||
|
vector4(220.61, -789.19, 30.08, 248.09),
|
||||||
|
vector4(212.00, -786.22, 30.21, 248.93),
|
||||||
|
vector4(212.91, -783.71, 30.19, 68.61),
|
||||||
|
vector4(222.11, -786.89, 30.07, 70.10),
|
||||||
|
vector4(227.84, -789.16, 29.99, 67.55),
|
||||||
|
vector4(237.73, -792.73, 29.82, 69.40),
|
||||||
|
vector4(243.54, -794.88, 29.73, 68.74),
|
||||||
|
vector4(237.03, -812.64, 29.59, 243.63),
|
||||||
|
vector4(207.66, -798.70, 30.29, 70.06),
|
||||||
|
vector4(216.56, -801.92, 30.10, 70.31),
|
||||||
|
vector4(222.28, -804.17, 29.98, 69.97),
|
||||||
|
vector4(232.41, -807.95, 29.74, 69.42),
|
||||||
|
vector4(238.16, -810.17, 29.60, 68.95),
|
||||||
|
vector4(238.32, -807.43, 29.63, 248.28),
|
||||||
|
vector4(233.32, -805.44, 29.75, 248.13),
|
||||||
|
vector4(222.46, -801.41, 29.98, 248.50),
|
||||||
|
vector4(216.85, -799.11, 30.10, 248.54),
|
||||||
|
vector4(207.60, -795.90, 30.29, 248.64),
|
||||||
|
vector4(209.28, -793.74, 30.25, 69.82),
|
||||||
|
vector4(218.31, -796.92, 30.08, 68.55),
|
||||||
|
vector4(224.50, -799.34, 29.96, 68.63),
|
||||||
|
vector4(234.40, -803.02, 29.76, 66.67),
|
||||||
|
vector4(240.09, -805.27, 29.64, 69.02),
|
||||||
|
vector4(240.53, -802.44, 29.67, 248.95),
|
||||||
|
vector4(234.46, -800.13, 29.79, 249.40),
|
||||||
|
vector4(224.30, -796.51, 29.98, 248.31),
|
||||||
|
vector4(218.23, -794.00, 30.09, 249.09),
|
||||||
|
vector4(209.84, -791.10, 30.24, 251.56),
|
||||||
|
vector4(211.09, -788.68, 30.22, 69.53),
|
||||||
|
vector4(220.17, -791.73, 30.06, 70.56),
|
||||||
|
vector4(225.84, -794.12, 29.98, 67.53),
|
||||||
|
vector4(236.38, -797.85, 29.79, 69.39),
|
||||||
|
vector4(216.56, -801.90, 30.79, 69.39)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
291
client/vehicle/garages.lua
Normal file
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
|
||||||
778
client/vehicle/main.lua
Normal file
778
client/vehicle/main.lua
Normal file
@@ -0,0 +1,778 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
lib.callback.register("ND_Vehicles:getProps", function(netId)
|
||||||
|
local veh = getVehFromNetId(netId)
|
||||||
|
local props = lib.getVehicleProperties(veh)
|
||||||
|
local colorPrimary, colorSecondary = GetVehicleColours(veh)
|
||||||
|
if not props then return end
|
||||||
|
|
||||||
|
props.colorNamePrimary = vehicleColorNames[colorPrimary]
|
||||||
|
props.colorNameSecondary = vehicleColorNames[colorSecondary]
|
||||||
|
props.colorName = props.colorNamePrimary == props.colorNameSecondary and props.colorNamePrimary or ("%s & %s"):format(props.colorNamePrimary, props.colorNameSecondary)
|
||||||
|
props.className = vehicleClassNames[GetVehicleClass(veh)]
|
||||||
|
props.makeName = GetLabelText(GetMakeNameFromVehicleModel(props.model))
|
||||||
|
props.modelName = GetLabelText(GetDisplayNameFromVehicleModel(props.model))
|
||||||
|
return props
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.callback.register("ND_Vehicles:getVehicleModelMakeLabel", function(model)
|
||||||
|
local make = GetLabelText(GetMakeNameFromVehicleModel(model))
|
||||||
|
local name = GetLabelText(GetDisplayNameFromVehicleModel(model))
|
||||||
|
if make == "NULL" then
|
||||||
|
return name
|
||||||
|
elseif name == "NULL" then
|
||||||
|
return make
|
||||||
|
end
|
||||||
|
return ("%s %s"):format(make, name)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function hasVehicleKeys(veh, checkEngine)
|
||||||
|
local state = Entity(veh).state
|
||||||
|
if Config.ox_inventory and Config.useInventoryForKeys then
|
||||||
|
local metadata = {
|
||||||
|
vehId = state.id,
|
||||||
|
keyEnabled = true
|
||||||
|
}
|
||||||
|
local hasKey = exports.ox_inventory:GetItemCount("keys", metadata) > 0
|
||||||
|
return hasKey or checkEngine and state.hotwired
|
||||||
|
end
|
||||||
|
|
||||||
|
local keys = state and state.keys
|
||||||
|
local player = NDCore.getPlayer()
|
||||||
|
local hasKey = player and keys and keys[player.id]
|
||||||
|
return hasKey or checkEngine and state.hotwired
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hasVehicleKeysCheck(veh)
|
||||||
|
local time = GetCloudTimeAsInt()
|
||||||
|
if time-keyCheckTime.lastCheck < 5 then
|
||||||
|
return keyCheckTime.hasKey
|
||||||
|
end
|
||||||
|
|
||||||
|
local hasKey = hasVehicleKeys(veh, true)
|
||||||
|
keyCheckTime.lastCheck = time
|
||||||
|
keyCheckTime.hasKey = hasKey
|
||||||
|
return hasKey
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getNearestVehicle(hasKeysForOnly)
|
||||||
|
local coords = GetEntityCoords(cache.ped)
|
||||||
|
local vehicles = lib.getNearbyVehicles(coords, 25.0, true)
|
||||||
|
local nearestVeh = {}
|
||||||
|
|
||||||
|
local function setNearestVehicle(veh)
|
||||||
|
if hasKeysForOnly and not hasVehicleKeys(veh.vehicle) then return end
|
||||||
|
local nearestDist = nearestVeh.dist
|
||||||
|
local dist = #(coords-veh.coords)
|
||||||
|
if not nearestDist or dist < nearestDist then
|
||||||
|
nearestVeh.dist = dist
|
||||||
|
nearestVeh.coords = veh.coords
|
||||||
|
nearestVeh.entity = veh.vehicle
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=1, #vehicles do
|
||||||
|
setNearestVehicle(vehicles[i])
|
||||||
|
end
|
||||||
|
return nearestVeh.entity, nearestVeh.coords, nearestVeh.dist
|
||||||
|
end
|
||||||
|
|
||||||
|
local vehicleLockKeybind = lib.addKeybind({
|
||||||
|
name = "vehicleKey",
|
||||||
|
description = "Unlock/lock vehicle (double click)",
|
||||||
|
defaultKey = "E",
|
||||||
|
onPressed = function(self)
|
||||||
|
local time = GetCloudTimeAsInt()
|
||||||
|
if time-vehicleLockCheckTime.lastCheck < 1 and time-vehicleLockCheckTime.lastUse > 1 then
|
||||||
|
vehicleLockCheckTime.lastUse = time
|
||||||
|
local veh = getNearestVehicle(true)
|
||||||
|
if not veh then return end
|
||||||
|
TriggerServerEvent("ND_Vehicles:toggleVehicleLock", VehToNet(veh))
|
||||||
|
end
|
||||||
|
vehicleLockCheckTime.lastCheck = time
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
NDCore.isResourceStarted("ox_inventory", function(started)
|
||||||
|
Config.ox_inventory = started
|
||||||
|
if not started or not Config.useInventoryForKeys then
|
||||||
|
return vehicleLockKeybind:disable(false)
|
||||||
|
end
|
||||||
|
Wait(1000)
|
||||||
|
vehicleLockKeybind:disable(true)
|
||||||
|
exports.ox_inventory:displayMetadata({
|
||||||
|
vehPlate = "Plate",
|
||||||
|
vehModel = "Model"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- save wheels steering angle.
|
||||||
|
CreateThread(function()
|
||||||
|
local angle = 0.0
|
||||||
|
while true do
|
||||||
|
Wait(300)
|
||||||
|
if playerVehicle then
|
||||||
|
if GetIsTaskActive(cache.ped, 2) then
|
||||||
|
SetVehicleSteeringAngle(playerVehicle, angle)
|
||||||
|
end
|
||||||
|
angle = DoesEntityExist(playerVehicle) and GetVehicleSteeringAngle(playerVehicle)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
local wait = 500
|
||||||
|
while true do
|
||||||
|
Wait(wait)
|
||||||
|
|
||||||
|
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, 7 do
|
||||||
|
TaskPlayAnimAdvanced(cache.ped, "veh@break_in@0h@p_m_one@", "std_force_entry_ds", pos.x, pos.y, pos.z+0.025, rot.x, rot.y, rot.z, 8.0, 8.0, 1800, 28, 0.1)
|
||||||
|
local dificulty = dificulties[math.random(1, #dificulties)]
|
||||||
|
local success = lib.skillCheck(dificulty)
|
||||||
|
if not success or not DoesEntityExist(veh) or #(pos-GetEntityCoords(veh)) > 2.5 then
|
||||||
|
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), false)
|
||||||
|
return false, true
|
||||||
|
end
|
||||||
|
Wait(dificultyTime[dificulty])
|
||||||
|
end
|
||||||
|
|
||||||
|
veh = lib.getClosestVehicle(pos, 2.5, false)
|
||||||
|
if not veh then return false, true end
|
||||||
|
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), true)
|
||||||
|
PlaySoundFromEntity(-1, "Remote_Control_Fob", cache.ped, "PI_Menu_Sounds", true, 0)
|
||||||
|
return true, true
|
||||||
|
end
|
||||||
|
|
||||||
|
exports("lockpick", function(data, slot)
|
||||||
|
local _, used = lockpickVehicle()
|
||||||
|
if used then
|
||||||
|
exports.ox_inventory:useItem(data)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
exports("hotwire", function(data, slot)
|
||||||
|
local _, used = hotwireVehicle()
|
||||||
|
if used then
|
||||||
|
exports.ox_inventory:useItem(data)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
exports("keyControl", function(action, slot)
|
||||||
|
for item, data in pairs(exports.ox_inventory:Items()) do
|
||||||
|
local metadata = data.metadata
|
||||||
|
if data.slot == slot then
|
||||||
|
if metadata and not metadata.keyEnabled then
|
||||||
|
return lib.notify({
|
||||||
|
title = "No signal",
|
||||||
|
description = "Vehicle key disabled.",
|
||||||
|
type = "error",
|
||||||
|
position = "bottom-right",
|
||||||
|
duration = 3000
|
||||||
|
})
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
exports.ox_inventory:closeInventory()
|
||||||
|
if action == "trunk" then
|
||||||
|
local veh = getNearestVehicle(true)
|
||||||
|
if not veh then return end
|
||||||
|
playKeyFob(veh)
|
||||||
|
if GetVehicleDoorAngleRatio(veh, 5) > 0.0 then
|
||||||
|
SetVehicleDoorShut(veh, 5)
|
||||||
|
else
|
||||||
|
SetVehicleDoorOpen(veh, 5, false)
|
||||||
|
end
|
||||||
|
elseif action == "disable" then
|
||||||
|
TriggerServerEvent("ND_Vehicles:disableKey", slot)
|
||||||
|
lib.notify({
|
||||||
|
title = "Key disabled",
|
||||||
|
description = "This vehicle key has been disabled and cannot be used anymore.",
|
||||||
|
type = "inform",
|
||||||
|
position = "bottom-right",
|
||||||
|
duration = 3000
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function cruiseControl()
|
||||||
|
cruiseSpeedVehicle = GetEntitySpeed(playerVehicle) * 2.236936
|
||||||
|
if not playerVehicle then
|
||||||
|
lib.notify({
|
||||||
|
title = "Cruise control",
|
||||||
|
description = "Vehicle cruise control disabled.",
|
||||||
|
type = "inform",
|
||||||
|
position = "bottom-right",
|
||||||
|
duration = 3000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if cruiseSpeedVehicle < cruiseSpeedSet/3 then
|
||||||
|
lib.notify({
|
||||||
|
title = "Cruise control",
|
||||||
|
description = "Vehicle cruise control disabled.",
|
||||||
|
type = "inform",
|
||||||
|
position = "bottom-right",
|
||||||
|
duration = 3000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if cruiseSpeedVehicle < cruiseSpeedSet then
|
||||||
|
SetControlNormal(0, 71, 0.6)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
lib.addKeybind({
|
||||||
|
name = "vehicleCruiseControl",
|
||||||
|
description = "Toggle vehicle cruise control",
|
||||||
|
defaultKey = "",
|
||||||
|
onPressed = function(self)
|
||||||
|
if cruiseControlEnabled and cruiseSpeedVehicle-1 > cruiseSpeedSet then
|
||||||
|
cruiseSpeedSet = cruiseSpeedVehicle
|
||||||
|
return lib.notify({
|
||||||
|
title = "Cruise control",
|
||||||
|
description = ("Increased to %d mph."):format(math.floor(cruiseSpeedSet)),
|
||||||
|
type = "inform",
|
||||||
|
position = "bottom-right",
|
||||||
|
duration = 3000
|
||||||
|
})
|
||||||
|
elseif cruiseControlEnabled then
|
||||||
|
cruiseControlEnabled = false
|
||||||
|
return lib.notify({
|
||||||
|
title = "Cruise control",
|
||||||
|
description = "Vehicle cruise control disabled.",
|
||||||
|
type = "inform",
|
||||||
|
position = "bottom-right",
|
||||||
|
duration = 3000
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if not playerVehicle then return end
|
||||||
|
|
||||||
|
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,38 +1,44 @@
|
|||||||
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
||||||
|
|
||||||
author "Andyyy#7666, N1K0#0001"
|
author "Andyyy#7666"
|
||||||
description "ND Framework Core"
|
description "ND Framework Core"
|
||||||
version "1.0.0"
|
version "2.1.2"
|
||||||
|
|
||||||
fx_version "cerulean"
|
fx_version "cerulean"
|
||||||
game "gta5"
|
game "gta5"
|
||||||
lua54 "yes"
|
lua54 "yes"
|
||||||
|
|
||||||
shared_scripts {
|
shared_script "@ox_lib/init.lua"
|
||||||
"config_client.lua",
|
|
||||||
"shared/main.lua"
|
|
||||||
}
|
|
||||||
client_scripts {
|
client_scripts {
|
||||||
"client/main.lua",
|
"client/main.lua",
|
||||||
|
"shared/functions.lua",
|
||||||
|
"client/peds.lua",
|
||||||
|
"client/vehicle/main.lua",
|
||||||
|
"client/vehicle/garages.lua",
|
||||||
"client/functions.lua",
|
"client/functions.lua",
|
||||||
"client/events.lua"
|
"client/events.lua",
|
||||||
|
"client/death.lua",
|
||||||
|
"compatibility/**/client.lua"
|
||||||
}
|
}
|
||||||
|
|
||||||
server_scripts {
|
server_scripts {
|
||||||
"@oxmysql/lib/MySQL.lua",
|
"@oxmysql/lib/MySQL.lua",
|
||||||
"config_server.lua",
|
|
||||||
"server/main.lua",
|
"server/main.lua",
|
||||||
|
"shared/functions.lua",
|
||||||
|
"server/player.lua",
|
||||||
|
"server/vehicle.lua",
|
||||||
"server/functions.lua",
|
"server/functions.lua",
|
||||||
"server/events.lua",
|
"compatibility/**/server.lua",
|
||||||
"server/commands.lua"
|
"server/commands.lua"
|
||||||
}
|
}
|
||||||
|
|
||||||
exports {
|
files {
|
||||||
"GetCoreObject"
|
"init.lua",
|
||||||
|
"client/vehicle/data.lua",
|
||||||
|
"compatibility/**/locale.lua"
|
||||||
}
|
}
|
||||||
|
|
||||||
server_exports {
|
dependency "oxmysql"
|
||||||
"GetCoreObject"
|
|
||||||
}
|
|
||||||
|
|
||||||
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,468 @@
|
|||||||
NDCore.Functions.AddCommand("setmoney", "Admin command, manage player money.", function(source, args, rawCommand)
|
local moneyTypes = {"bank", "cash"}
|
||||||
if not NDCore.Functions.IsPlayerAdmin(source) then return end
|
local moneyActions = {
|
||||||
|
remove = function(player, account, amount)
|
||||||
local target = tonumber(args[1])
|
player.deductMoney(account, amount, "Staff action")
|
||||||
local action = args[2]
|
return ("Removed $%d (%s) to %s"):format(amount, account, player.name), ("removed $%d from %s"):format(amount, account)
|
||||||
local moneyType = args[3]:lower()
|
end,
|
||||||
local amount = tonumber(args[4])
|
add = function(player, account, amount)
|
||||||
|
player.addMoney(account, amount, "Staff action")
|
||||||
if not target or GetPlayerPing(target) == 0 then return end
|
return ("Added $%d (%s) to %s"):format(amount, account, player.name), ("added $%d to %s"):format(amount, account)
|
||||||
if action ~= "remove" and action ~= "add" and action ~= "set" then return end
|
end,
|
||||||
if moneyType ~= "bank" and moneyType ~= "cash" then return end
|
set = function(player, account, amount)
|
||||||
|
player.setData(account, amount, "Staff action")
|
||||||
if action == "remove" then
|
return ("Set %s's (%s) to $%d"):format(player.name, account, amount), ("set %s to $%d"):format(account, amount)
|
||||||
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.")
|
|
||||||
end
|
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)
|
lib.addCommand("setmoney", {
|
||||||
if not NDCore.Functions.IsPlayerAdmin(source) then
|
help = "Admin command, manage player money.",
|
||||||
return {
|
restricted = "group.admin",
|
||||||
color = {255, 0, 0},
|
params = {
|
||||||
args = {"Error", "you don't have access to this command."}
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "action",
|
||||||
|
type = "string",
|
||||||
|
help = "remove/add/set"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "type",
|
||||||
|
type = "string",
|
||||||
|
help = "bank/cash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "amount",
|
||||||
|
type = "number"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
local action = moneyActions[args.action]
|
||||||
|
local moneyType = args.type:lower()
|
||||||
|
if not action or not lib.table.contains(moneyTypes, moneyType) then return end
|
||||||
|
|
||||||
|
local player = NDCore.getPlayer(args.target)
|
||||||
|
if not player then return end
|
||||||
|
local staffMessage, userMessage = action(player, moneyType, args.amount)
|
||||||
|
|
||||||
|
player.notify({
|
||||||
|
title = "Staff action",
|
||||||
|
description = userMessage,
|
||||||
|
type = "inform",
|
||||||
|
duration = 10000
|
||||||
|
})
|
||||||
|
|
||||||
|
if not source then return end
|
||||||
|
TriggerClientEvent("chat:addMessage", source, {
|
||||||
|
color = {50, 100, 235},
|
||||||
|
multiline = true,
|
||||||
|
args = {"Staff action", staffMessage}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("setjob", {
|
||||||
|
help = "Admin command, set a players job.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "job",
|
||||||
|
type = "string",
|
||||||
|
help = "Job name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "rank",
|
||||||
|
type = "number",
|
||||||
|
optional = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
local player = NDCore.getPlayer(args.target)
|
||||||
|
if not player then return end
|
||||||
|
|
||||||
|
local job = args.job:lower()
|
||||||
|
local jobInfo = player.setJob(job, args.rank)
|
||||||
|
if not player or not jobInfo then return end
|
||||||
|
player.notify({
|
||||||
|
title = "Staff action",
|
||||||
|
description = ("Job updated to %s, rank %s"):format(jobInfo.label, jobInfo.rankName),
|
||||||
|
type = "inform",
|
||||||
|
duration = 10000
|
||||||
|
})
|
||||||
|
|
||||||
|
if not source then return end
|
||||||
|
TriggerClientEvent("chat:addMessage", source, {
|
||||||
|
color = {50, 100, 235},
|
||||||
|
multiline = true,
|
||||||
|
args = {"Staff action", "success"}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("setgroup", {
|
||||||
|
help = "Admin command, set a players group.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "action",
|
||||||
|
type = "string",
|
||||||
|
help = "remove/add"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "group",
|
||||||
|
type = "string",
|
||||||
|
help = "group name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "rank",
|
||||||
|
type = "number",
|
||||||
|
optional = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
local player = NDCore.getPlayer(args.target)
|
||||||
|
if not player then return end
|
||||||
|
|
||||||
|
if args.action == "add" then
|
||||||
|
local groupInfo = player.addGroup(args.group, args.rank)
|
||||||
|
if not groupInfo then return end
|
||||||
|
player.notify({
|
||||||
|
title = "Staff action",
|
||||||
|
description = ("Added to group %s, rank %s."):format(groupInfo.label, groupInfo.rankName),
|
||||||
|
type = "inform",
|
||||||
|
duration = 10000
|
||||||
|
})
|
||||||
|
elseif args.action == "remove" then
|
||||||
|
local groupInfo = player.removeGroup(args.group)
|
||||||
|
if not groupInfo then return end
|
||||||
|
player.notify({
|
||||||
|
title = "Staff action",
|
||||||
|
description = ("Removed from group %s."):format(groupInfo.label),
|
||||||
|
type = "inform",
|
||||||
|
duration = 10000
|
||||||
|
})
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not source then return end
|
||||||
|
TriggerClientEvent("chat:addMessage", source, {
|
||||||
|
color = {50, 100, 235},
|
||||||
|
multiline = true,
|
||||||
|
args = {"Staff action", "success"}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("skin", {
|
||||||
|
help = "Admin command, set player into character clothing menu.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
TriggerClientEvent("ND:clothingMenu", args.target)
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("character", {
|
||||||
|
help = "Admin command, set player into character selection menu.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
TriggerClientEvent("ND:characterMenu", args.target)
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("pay", {
|
||||||
|
help = "give money to nearby player.",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "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 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
|
end
|
||||||
|
|
||||||
local target = tonumber(args[1])
|
targetPlayer.notify({
|
||||||
if not target or GetPlayerPing(target) == 0 then
|
title = "Money received",
|
||||||
return {
|
description = ("Received $%d in cash"):format(args.amount),
|
||||||
color = {255, 0, 0},
|
type = "inform",
|
||||||
args = {"Error", "target player not found."}
|
duration = 5000
|
||||||
}
|
})
|
||||||
end
|
|
||||||
|
player.notify({
|
||||||
|
title = "Money given",
|
||||||
|
description = ("You gave $%d in cash"):format(args.amount),
|
||||||
|
type = "inform",
|
||||||
|
duration = 5000
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
local job = args[2]
|
lib.addCommand("unlock", {
|
||||||
if not job then
|
help = "Admin Command, force unlock vehicle.",
|
||||||
return {
|
restricted = "group.admin",
|
||||||
color = {255, 0, 0},
|
}, function(source, args, raw)
|
||||||
args = {"Error", "job required."}
|
local ped = GetPlayerPed(source)
|
||||||
}
|
local playerVeh = GetVehiclePedIsIn(ped)
|
||||||
end
|
local coords = GetEntityCoords(ped)
|
||||||
|
local vehicles = GetAllVehicles()
|
||||||
|
local maxDistance = 2.0
|
||||||
|
local closestVehicle
|
||||||
|
|
||||||
local character = NDCore.Functions.GetPlayer(target)
|
if not playerVeh or playerVeh == 0 or not DoesEntityExist(playerVeh) then
|
||||||
NDCore.Functions.SetPlayerJob(character.id, job, args[3])
|
for i=1, #vehicles do
|
||||||
return {
|
local vehicle = vehicles[i]
|
||||||
color = {0, 255, 0},
|
local vehicleCoords = GetEntityCoords(vehicle)
|
||||||
args = {"Success", GetPlayerName(target) .. " job set to " .. job .. (args[3] and " rank " .. args[3] or " rank 1.")}
|
local distance = #(coords-vehicleCoords)
|
||||||
|
|
||||||
|
if distance < maxDistance then
|
||||||
|
maxDistance = distance
|
||||||
|
closestVehicle = vehicle
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not closestVehicle or not DoesEntityExist(closestVehicle) then return end
|
||||||
|
local state = Entity(closestVehicle).state
|
||||||
|
state.locked = false
|
||||||
|
else
|
||||||
|
local state = Entity(playerVeh).state
|
||||||
|
state.hotwired = true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("revive", {
|
||||||
|
help = "Admin command, revive player.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
end, false, {
|
}, function(source, args, raw)
|
||||||
{ name="player", help="Player server id" },
|
local player = NDCore.getPlayer(args.target)
|
||||||
{ name="job name" },
|
if not player then return end
|
||||||
{ name="rank", help="This should be a number, default value is 1." }
|
player.revive()
|
||||||
})
|
end)
|
||||||
|
|
||||||
NDCore.Functions.AddCommand("setgroup", "Admin command, set player group.", function(source, args, rawCommand)
|
lib.addCommand("dv", {
|
||||||
if not NDCore.Functions.IsPlayerAdmin(source) then return end
|
help = "Admin command, delete vehicles within the range or the closest.",
|
||||||
|
restricted = "group.admin",
|
||||||
local target = tonumber(args[1])
|
params = {
|
||||||
if not target or GetPlayerPing(target) == 0 then
|
{
|
||||||
return {
|
name = "range",
|
||||||
color = {255, 0, 0},
|
type = "number",
|
||||||
args = {"Error", "target player not found."}
|
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, {
|
}, function(source, args, raw)
|
||||||
{ name="Amount" },
|
local ped = GetPlayerPed(source)
|
||||||
})
|
local veh = GetVehiclePedIsIn(ped)
|
||||||
|
if veh and veh ~= 0 and DoesEntityExist(veh) then
|
||||||
|
DeleteEntity(veh)
|
||||||
|
return TriggerClientEvent("chat:addMessage", source, {
|
||||||
|
color = {50, 100, 235},
|
||||||
|
multiline = true,
|
||||||
|
args = {"Staff action", "deleted 1 vehicle"}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
local coords = GetEntityCoords(ped)
|
||||||
|
if args.range then
|
||||||
|
for _, veh in ipairs(GetAllVehicles()) do
|
||||||
|
local vehDist = #(GetEntityCoords(veh) - coords)
|
||||||
|
if vehDist < args.range then
|
||||||
|
DeleteEntity(veh)
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return TriggerClientEvent("chat:addMessage", source, {
|
||||||
|
color = {50, 100, 235},
|
||||||
|
multiline = true,
|
||||||
|
args = {"Staff action", ("deleted %d vehicles"):format(count)}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local closest, dist
|
||||||
|
for _, veh in ipairs(GetAllVehicles()) do
|
||||||
|
local vehDist = #(GetEntityCoords(veh) - coords)
|
||||||
|
if vehDist < 5.0 and not closest or (dist and dist > vehDist) then
|
||||||
|
closest = veh
|
||||||
|
dist = vehDist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not closest then return "no vehicle found nearby" end
|
||||||
|
DeleteEntity(closest)
|
||||||
|
TriggerClientEvent("chat:addMessage", source, {
|
||||||
|
color = {50, 100, 235},
|
||||||
|
multiline = true,
|
||||||
|
args = {"Staff action", "deleted 1 vehicle"}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("goto", {
|
||||||
|
help = "Admin command, teleport to a player.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
if args.target == source then return end
|
||||||
|
local target = GetPlayerPed(args.target)
|
||||||
|
local coords = GetEntityCoords(target)
|
||||||
|
local ped = GetPlayerPed(source)
|
||||||
|
SetEntityCoords(ped, coords.x, coords.y, coords.z)
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("bring", {
|
||||||
|
help = "Admin command, teleport a player to you.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
if args.target == source then return end
|
||||||
|
local ped = GetPlayerPed(source)
|
||||||
|
local coords = GetEntityCoords(ped)
|
||||||
|
local targetPed = GetPlayerPed(args.target)
|
||||||
|
SetEntityCoords(targetPed, coords.x, coords.y, coords.z)
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("freeze", {
|
||||||
|
help = "Admin command, freeze a player.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
local ped = GetPlayerPed(args.target)
|
||||||
|
FreezeEntityPosition(ped, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("unfreeze", {
|
||||||
|
help = "Admin command, unfreeze a player.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "target",
|
||||||
|
type = "playerId",
|
||||||
|
help = "Target player's server id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
local ped = GetPlayerPed(args.target)
|
||||||
|
FreezeEntityPosition(ped, false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.addCommand("vehicle", {
|
||||||
|
help = "Admin command, unfreeze a player.",
|
||||||
|
restricted = "group.admin",
|
||||||
|
params = {
|
||||||
|
{
|
||||||
|
name = "model",
|
||||||
|
type = "string",
|
||||||
|
help = "Name of the vehicle to spawn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(source, args, raw)
|
||||||
|
local player = NDCore.getPlayer(source)
|
||||||
|
if not player then return end
|
||||||
|
|
||||||
|
local ped = GetPlayerPed(source)
|
||||||
|
local coords = GetEntityCoords(ped)
|
||||||
|
local heading = GetEntityHeading(ped)
|
||||||
|
local info = NDCore.createVehicle({
|
||||||
|
owner = player.id,
|
||||||
|
coords = coords,
|
||||||
|
heading = heading,
|
||||||
|
model = GetHashKey(args.model)
|
||||||
|
})
|
||||||
|
|
||||||
|
local veh = info.entity
|
||||||
|
for i=1, 10 do
|
||||||
|
if GetPedInVehicleSeat(veh, -1) ~= ped then
|
||||||
|
SetPedIntoVehicle(ped, veh, -1)
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|||||||
@@ -1,114 +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
|
|
||||||
NDCore.Functions.SetPlayerData(player, "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
|
|
||||||
NDCore.Functions.UpdateLastLocation(character.id, character.lastLocation)
|
|
||||||
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,719 +1,106 @@
|
|||||||
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
---@param src number
|
||||||
|
---@return table
|
||||||
-- Get an active players character data.
|
function NDCore.getPlayer(src)
|
||||||
function NDCore.Functions.GetPlayer(player)
|
return NDCore.players[src]
|
||||||
return NDCore.Players[player]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get all active players character data.
|
---@param metadata string
|
||||||
function NDCore.Functions.GetPlayers(getBy, value)
|
---@param data any
|
||||||
if not getBy or not value then
|
---@return table
|
||||||
return NDCore.Players
|
function NDCore.getPlayers(key, value, returnArray)
|
||||||
end
|
if not key or not value then return NDCore.players end
|
||||||
|
|
||||||
local players = {}
|
local players = {}
|
||||||
|
local keyTypes = {id = "id", firstname = "firstname", lastname = "lastname", gender = "gender", groups = "groups"}
|
||||||
|
local findBy = keyTypes[key] or "metadata"
|
||||||
|
|
||||||
if getBy == "groups" then
|
if findBy then
|
||||||
for player, playerInfo in pairs(NDCore.Players) do
|
for src, info in pairs(NDCore.players) do
|
||||||
if playerInfo.data.groups then
|
if findBy == "metadata" and info["metadata"][key] == value or info[findBy] == value then
|
||||||
local valueGroup = value:lower()
|
if returnArray then
|
||||||
for group, _ in pairs(playerInfo.data.groups) do
|
players[#players+1] = info
|
||||||
if group and group:lower() == valueGroup then
|
else
|
||||||
players[player] = playerInfo
|
players[src] = info
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
for player, playerInfo in pairs(NDCore.Players) do
|
|
||||||
if playerInfo[getBy] == value then
|
|
||||||
players[player] = playerInfo
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return players
|
return players
|
||||||
end
|
end
|
||||||
|
|
||||||
local discordErrors = {
|
---@param source number
|
||||||
[400] = "improper http request",
|
---@return table
|
||||||
[401] = "Discord bot token might be missing or incorrect",
|
function NDCore.getPlayerServerInfo(source)
|
||||||
[404] = "user might not be in server.",
|
return PlayersInfo[source]
|
||||||
[429] = "Discord bot rate limited."
|
end
|
||||||
}
|
|
||||||
-- Used to retrive the players discord server nickname, discord name and tag, and the roles.
|
function NDCore.getConfig(info)
|
||||||
function NDCore.Functions.GetUserDiscordInfo(discordUserId)
|
if not info then
|
||||||
local data
|
return Config
|
||||||
local timeout = 0
|
end
|
||||||
PerformHttpRequest("https://discordapp.com/api/guilds/" .. server_config.guildId .. "/members/" .. discordUserId, function(errorCode, resultData, resultHeaders)
|
return Config[info]
|
||||||
if errorCode ~= 200 then
|
end
|
||||||
print("Error: " .. errorCode .. " " .. discordErrors[errorCode])
|
|
||||||
end
|
---@param fileLocation string|tabale
|
||||||
local result = json.decode(resultData)
|
---@return boolean
|
||||||
local roles = {}
|
function NDCore.loadSQL(fileLocation, resource)
|
||||||
local nickname = ""
|
local resourceName = resource or GetInvokingResource() or GetCurrentResourceName()
|
||||||
local tag = ""
|
|
||||||
if result and result.roles then
|
if type(fileLocation) == "string" then
|
||||||
for _, roleId in pairs(result.roles) do
|
local file = LoadResourceFile(resourceName, fileLocation)
|
||||||
roles[roleId] = roleId
|
if not file then return end
|
||||||
end
|
MySQL.query(file)
|
||||||
if result.nick then
|
return true
|
||||||
nickname = result.nick
|
end
|
||||||
end
|
|
||||||
if result.user and result.user.username and result.user.discriminator then
|
for i=1, #fileLocation do
|
||||||
tag = tostring(result.user.username) .. "#" .. tostring(result.user.discriminator)
|
local file = LoadResourceFile(resourceName, fileLocation[i])
|
||||||
end
|
if file then
|
||||||
data = {
|
MySQL.query(file)
|
||||||
nickname = nickname,
|
Wait(100)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
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
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get player any identifier, available types: steam, license, xbl, ip, discord, live.
|
function NDCore.enableMultiCharacter(enable)
|
||||||
function NDCore.Functions.GetPlayerIdentifierFromType(type, player)
|
Config.multiCharacter = enable
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This will return the server id and ND player data of a nearby player.
|
for name, func in pairs(NDCore) do
|
||||||
function NDCore.Functions.GetNearbyPedToPlayer(player)
|
if type(func) == "function" then
|
||||||
local pedCoords = GetEntityCoords(GetPlayerPed(player))
|
exports(name, func)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
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, cash, bank, cb)
|
|
||||||
local characterId = false
|
|
||||||
local license = NDCore.Functions.GetPlayerIdentifierFromType("license", player)
|
|
||||||
if not cash or not bank or tonumber(cash) > config.startingCash or tonumber(bank) > config.startingBank then
|
|
||||||
cash = config.startingCash
|
|
||||||
bank = config.startingBank
|
|
||||||
end
|
|
||||||
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) VALUES (?, ?, ?, ?, ?, ?, ?)", {license, firstName, lastName, dob, gender, cash, bank})
|
|
||||||
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")
|
|
||||||
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")
|
|
||||||
|
|||||||
173
server/main.lua
173
server/main.lua
@@ -1,35 +1,156 @@
|
|||||||
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
|
|
||||||
|
|
||||||
NDCore = {}
|
NDCore = {}
|
||||||
NDCore.Players = {}
|
NDCore.players = {}
|
||||||
NDCore.Functions = {}
|
PlayersInfo = {}
|
||||||
NDCore.Commands = {}
|
local resourceName = GetCurrentResourceName()
|
||||||
NDCore.PlayersDiscordInfo = {}
|
local tempPlayersInfo = {}
|
||||||
NDCore.Config = config
|
|
||||||
|
|
||||||
function GetCoreObject()
|
Config = {
|
||||||
return NDCore
|
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", "[]")),
|
||||||
|
multiCharacter = false,
|
||||||
|
compatibility = json.decode(GetConvar("core:compatibility", "[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
SetConvarServerInfo("Discord", Config.discordInvite)
|
||||||
|
SetConvarServerInfo("NDCore", GetResourceMetadata(resourceName, "version", 0) or "invalid")
|
||||||
|
SetConvarReplicated("inventory:framework", "nd")
|
||||||
|
|
||||||
|
local function getIdentifierList(src)
|
||||||
|
local list = {}
|
||||||
|
for i=0, GetNumPlayerIdentifiers(src) do
|
||||||
|
local identifier = GetPlayerIdentifier(src, i)
|
||||||
|
if identifier then
|
||||||
|
local colon = identifier:find(":")
|
||||||
|
local identifierType = identifier:sub(1, colon-1)
|
||||||
|
list[identifierType] = identifier
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return list
|
||||||
end
|
end
|
||||||
|
|
||||||
CreateThread(function()
|
AddEventHandler("playerJoining", function(oldId)
|
||||||
while true do
|
local src = source
|
||||||
Wait(30000)
|
local oldTempId = tonumber(oldId)
|
||||||
for player, playerInfo in pairs(NDCore.Players) do
|
PlayersInfo[src] = tempPlayersInfo[oldTempId]
|
||||||
local ped = GetPlayerPed(player)
|
tempPlayersInfo[oldTempId] = nil
|
||||||
if DoesEntityExist(ped) then
|
|
||||||
local lastLocation = GetEntityCoords(ped)
|
if Config.multiCharacter then return end
|
||||||
playerInfo.lastLocation = {x = lastLocation.x, y = lastLocation.y, z = lastLocation.z}
|
Wait(3000)
|
||||||
TriggerClientEvent("ND:updateLastLocation", player, playerInfo.lastLocation)
|
|
||||||
end
|
local characters = NDCore.fetchAllCharacters(src)
|
||||||
end
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
isResourceStarted("ox_inventory", function(started)
|
AddEventHandler("playerConnecting", function(name, setKickReason, deferrals)
|
||||||
if not started then return end
|
local tempSrc = source
|
||||||
SetConvarReplicated("inventory:framework", "nd")
|
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 then
|
||||||
|
deferrals.done(("Your discord was not found, join our discord here: %s."):format(Config.discordInvite))
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
deferrals.update("Connecting...")
|
||||||
|
Wait(0)
|
||||||
|
|
||||||
|
if mainIdentifier then
|
||||||
|
tempPlayersInfo[tempSrc] = {
|
||||||
|
identifiers = identifiers,
|
||||||
|
discord = discordInfo
|
||||||
|
}
|
||||||
|
deferrals.done()
|
||||||
|
else
|
||||||
|
deferrals.done(("Your %s was not found."):format(Config.characterIdentifier))
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
for _, roleid in pairs(config.adminRoles) do
|
AddEventHandler("playerDropped", function()
|
||||||
ExecuteCommand("add_principal identifier.discord:" .. roleid .. " group.admin")
|
local src = source
|
||||||
end
|
local char = NDCore.players[src]
|
||||||
|
if char then char.unload() end
|
||||||
|
PlayersInfo[src] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
AddEventHandler("onResourceStop", function(name)
|
||||||
|
if name ~= resourceName then return end
|
||||||
|
for _, player in pairs(NDCore.players) do
|
||||||
|
player.unload()
|
||||||
|
Wait(10)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
SetTimeout(500, function()
|
||||||
|
NDCore.loadSQL({
|
||||||
|
"database/characters.sql",
|
||||||
|
"database/vehicles.sql"
|
||||||
|
}, resourceName)
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent("ND:playerEliminated", function(info)
|
||||||
|
local src = source
|
||||||
|
local player = NDCore.getPlayer(src)
|
||||||
|
if not player then return end
|
||||||
|
player.setMetadata({
|
||||||
|
dead = true,
|
||||||
|
deathInfo = info
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|||||||
517
server/player.lua
Normal file
517
server/player.lua
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
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))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param key string|table
|
||||||
|
---@param value any
|
||||||
|
---@return table
|
||||||
|
function self.setMetadata(key, value)
|
||||||
|
if type(key) == "table" then
|
||||||
|
for k, v in pairs(key) do
|
||||||
|
self.metadata[k] = v
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.metadata[key] = value
|
||||||
|
end
|
||||||
|
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
|
||||||
|
return self.metadata
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Completely delete character
|
||||||
|
function self.delete()
|
||||||
|
local result = MySQL.query.await("DELETE FROM nd_characters WHERE charid = ?", {self.id})
|
||||||
|
if result and NDCore.players[self.source] then
|
||||||
|
NDCore.players[self.source] = nil
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Unload and save character
|
||||||
|
function self.unload()
|
||||||
|
if not NDCore.players[self.source] then return end
|
||||||
|
for name, _ in pairs(self.groups) do
|
||||||
|
lib.removePrincipal(self.source, ("group.%s"):format(name))
|
||||||
|
end
|
||||||
|
local ped = GetPlayerPed(self.source)
|
||||||
|
if ped then
|
||||||
|
local coords = GetEntityCoords(ped)
|
||||||
|
local heading = GetEntityHeading(ped)
|
||||||
|
self.setMetadata("location", {
|
||||||
|
x = coords.x,
|
||||||
|
y = coords.y,
|
||||||
|
z = coords.z,
|
||||||
|
w = heading
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self.triggerEvent("ND:characterUnloaded")
|
||||||
|
TriggerEvent("ND:characterUnloaded", self.source, self)
|
||||||
|
local saved = self.save()
|
||||||
|
NDCore.players[self.source] = nil
|
||||||
|
return saved
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save character information to database
|
||||||
|
function self.save(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
|
||||||
|
self.metadata.licenses[#licenses+1] = license
|
||||||
|
else
|
||||||
|
self.metadata.licenses = {license}
|
||||||
|
end
|
||||||
|
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
|
||||||
|
end
|
||||||
|
|
||||||
|
function self.getLicense(identifier)
|
||||||
|
local licenses = self.metadata.licenses or {}
|
||||||
|
for i=1, #licenses do
|
||||||
|
local data = licenses[i]
|
||||||
|
if data.identifier == identifier then
|
||||||
|
return data, i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function self.updateLicense(identifier, newData)
|
||||||
|
local data, i = self.getLicense(identifier)
|
||||||
|
if not data then return end
|
||||||
|
for k, v in pairs(newData) do
|
||||||
|
data[k] = v
|
||||||
|
end
|
||||||
|
self.save()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param coords vector3|vector4
|
||||||
|
---@return boolean
|
||||||
|
function self.setCoords(coords)
|
||||||
|
if not self.source or not coords then return end
|
||||||
|
local ped = GetPlayerPed(self.source)
|
||||||
|
if not DoesEntityExist(ped) then return end
|
||||||
|
SetEntityCoords(ped, coords.x, coords.y, coords.z)
|
||||||
|
if coords.w then
|
||||||
|
SetEntityHeading(ped, coords.w)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param eventName string
|
||||||
|
---@param ... any
|
||||||
|
---@return boolean
|
||||||
|
function self.triggerEvent(eventName, ...)
|
||||||
|
if not self.source then return end
|
||||||
|
TriggerClientEvent(eventName, self.source, ...)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function self.notify(...)
|
||||||
|
if not self.source then return end
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
for name, _ in pairs(self.groups) do
|
||||||
|
lib.addPrincipal(self.source, ("group.%s"):format(name))
|
||||||
|
end
|
||||||
|
NDCore.players[self.source] = self
|
||||||
|
TriggerEvent("ND:characterLoaded", self)
|
||||||
|
self.triggerEvent("ND:characterLoaded", removeCharacterFunctions(self))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param rank number
|
||||||
|
---@param isJob boolean
|
||||||
|
---@return boolean
|
||||||
|
function self.addGroup(name, rank, isJob)
|
||||||
|
local groupRank = tonumber(rank) or 1
|
||||||
|
local groupInfo = Config.groups?[name]
|
||||||
|
local bossRank = groupInfo and groupInfo.minimumBossRank
|
||||||
|
|
||||||
|
-- if not groupInfo then return end
|
||||||
|
if isJob then
|
||||||
|
for _, group in pairs(self.groups) do
|
||||||
|
group.isJob = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.groups[name] = {
|
||||||
|
name = name,
|
||||||
|
label = groupInfo and groupInfo.label or name,
|
||||||
|
rankName = groupInfo and groupInfo.ranks[groupRank] or groupRank,
|
||||||
|
rank = groupRank,
|
||||||
|
isJob = isJob,
|
||||||
|
isBoss = bossRank and groupRank >= bossRank
|
||||||
|
}
|
||||||
|
|
||||||
|
if not isJob then
|
||||||
|
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
|
||||||
|
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))
|
||||||
|
lib.removePrincipal(self.source, ("group.%s"):format(name))
|
||||||
|
return group
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param rank number
|
||||||
|
---@return boolean
|
||||||
|
function self.setJob(name, rank)
|
||||||
|
self.removeGroup(self.job)
|
||||||
|
local job = self.addGroup(name, rank, true)
|
||||||
|
if job then
|
||||||
|
self.job = job.name
|
||||||
|
self.jobInfo = job
|
||||||
|
end
|
||||||
|
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
|
||||||
|
return job
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param job string
|
||||||
|
---@return boolean
|
||||||
|
function self.getJob()
|
||||||
|
for name, group in pairs(self.groups) do
|
||||||
|
if group.isJob then
|
||||||
|
return name, group
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local jobName, jobInfo = self.getJob()
|
||||||
|
if jobInfo then
|
||||||
|
self.job = jobName
|
||||||
|
self.jobInfo = jobInfo
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param src number
|
||||||
|
---@param info table
|
||||||
|
---@return table
|
||||||
|
function NDCore.newCharacter(src, info)
|
||||||
|
local identifier = GetPlayerIdentifierByType(src, Config.characterIdentifier)
|
||||||
|
if not identifier then return end
|
||||||
|
|
||||||
|
local charInfo = {
|
||||||
|
source = src,
|
||||||
|
identifier = identifier,
|
||||||
|
name = GetPlayerName(src) or "",
|
||||||
|
firstname = info.firstname or "",
|
||||||
|
lastname = info.lastname or "",
|
||||||
|
dob = info.dob or "",
|
||||||
|
gender = info.gender or "",
|
||||||
|
cash = info.cash or 0,
|
||||||
|
bank = info.bank or 0,
|
||||||
|
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) 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
return createCharacterTable(charInfo)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param id number
|
||||||
|
---@return table
|
||||||
|
function NDCore.fetchCharacter(id, src)
|
||||||
|
local result
|
||||||
|
if src then
|
||||||
|
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ? and identifier = ?", {id, GetPlayerIdentifierByType(src, Config.characterIdentifier)})
|
||||||
|
else
|
||||||
|
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id})
|
||||||
|
end
|
||||||
|
|
||||||
|
if not result then return end
|
||||||
|
local info = result[1]
|
||||||
|
return createCharacterTable({
|
||||||
|
source = src,
|
||||||
|
id = info.charid,
|
||||||
|
identifier = info.identifier,
|
||||||
|
name = info.name,
|
||||||
|
firstname = info.firstname,
|
||||||
|
lastname = info.lastname,
|
||||||
|
dob = info.dob,
|
||||||
|
gender = info.gender,
|
||||||
|
cash = info.cash,
|
||||||
|
bank = info.bank,
|
||||||
|
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 = ?", {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
|
||||||
732
server/vehicle.lua
Normal file
732
server/vehicle.lua
Normal file
@@ -0,0 +1,732 @@
|
|||||||
|
local ox_inventory
|
||||||
|
local inventoryStarted = false
|
||||||
|
local spawnedPlayerVehicles = {}
|
||||||
|
|
||||||
|
NDCore.isResourceStarted("ox_inventory", function(started)
|
||||||
|
inventoryStarted = started
|
||||||
|
if not started then return end
|
||||||
|
ox_inventory = exports.ox_inventory
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function getVehicleType(model)
|
||||||
|
local tempVehicle = CreateVehicle(model, 0, 0, 0, 0, true, true)
|
||||||
|
|
||||||
|
local time = os.time()
|
||||||
|
while not DoesEntityExist(tempVehicle) and time-os.time() < 5 do Wait(5) end
|
||||||
|
|
||||||
|
if not DoesEntityExist(tempVehicle) then return end
|
||||||
|
local entityType = GetVehicleType(tempVehicle)
|
||||||
|
DeleteEntity(tempVehicle)
|
||||||
|
return entityType
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generatePlate()
|
||||||
|
local plate = {}
|
||||||
|
for i=1, 8 do
|
||||||
|
plate[i] = math.random(0, 1) == 1 and string.char(math.random(65, 90)) or math.random(0, 9)
|
||||||
|
end
|
||||||
|
return table.concat(plate)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generateVehiclePlate(newPlate)
|
||||||
|
local plate = newPlate or generatePlate()
|
||||||
|
while MySQL.scalar.await("SELECT 1 FROM nd_vehicles WHERE plate = ?", {plate}) do
|
||||||
|
plate = generatePlate()
|
||||||
|
end
|
||||||
|
return plate
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generateTemporaryVehicleId()
|
||||||
|
return ("temp_%s%d"):format(string.char(math.random(65, 90)), math.random(1, 999999))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getVehicleDatabaseInfo(vehicle)
|
||||||
|
if not vehicle then return end
|
||||||
|
local stored = vehicle.stored == 1
|
||||||
|
local impounded = vehicle.impounded == 1
|
||||||
|
return {
|
||||||
|
id = vehicle.id,
|
||||||
|
owner = vehicle.owner,
|
||||||
|
plate = vehicle.plate,
|
||||||
|
properties = json.decode(vehicle.properties) or {},
|
||||||
|
stored = stored,
|
||||||
|
impounded = impounded,
|
||||||
|
stolen = vehicle.stolen == 1,
|
||||||
|
available = not impounded and stored,
|
||||||
|
metadata = json.decode(vehicle.metadata) or {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- get vehicle information queried from the database by the vehicle id
|
||||||
|
---@param vehicleId string | numb
|
||||||
|
---@return table | nil
|
||||||
|
function NDCore.getVehicleById(vehicleId)
|
||||||
|
local result = MySQL.query.await("SELECT * FROM nd_vehicles WHERE id = ?", {vehicleId})
|
||||||
|
return getVehicleDatabaseInfo(result?[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
--- get vehicle information and functions
|
||||||
|
---@param entity number
|
||||||
|
---@return table
|
||||||
|
function NDCore.getVehicle(entity)
|
||||||
|
if not DoesEntityExist(entity) then return end
|
||||||
|
|
||||||
|
local state = Entity(entity).state
|
||||||
|
local self = {
|
||||||
|
entity = entity,
|
||||||
|
id = state.id,
|
||||||
|
owner = state.owner,
|
||||||
|
keys = state.keys,
|
||||||
|
properties = state.props,
|
||||||
|
locked = state.locked,
|
||||||
|
hotwired = state.hotwired,
|
||||||
|
metadata = state.metadata or {},
|
||||||
|
netId = NetworkGetNetworkIdFromEntity(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
--- delete the vehicle
|
||||||
|
function self.delete(saveProperties)
|
||||||
|
if not DoesEntityExist(entity) then return end
|
||||||
|
if saveProperties and self.id and self.owner then
|
||||||
|
local properties = lib.callback.await("ND_Vehicles:getProps", NetworkGetEntityOwner(entity), self.netId)
|
||||||
|
if properties then
|
||||||
|
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
|
||||||
|
|
||||||
|
--- 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
|
||||||
|
local metadata = state.metadata
|
||||||
|
metadata[key] = value
|
||||||
|
state.metadata = metadata
|
||||||
|
end
|
||||||
|
if not self.id or not self.owner then return end
|
||||||
|
MySQL.query("UPDATE nd_vehicles SET metadata = ? WHERE id = ?", {json.encode(self.metadata), self.id})
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.id and self.owner then
|
||||||
|
spawnedPlayerVehicles[self.id] = self
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- get a characters owned vehicles
|
||||||
|
---@param characterId number
|
||||||
|
---@return table
|
||||||
|
function NDCore.getVehicles(characterId)
|
||||||
|
local result = MySQL.query.await("SELECT * FROM nd_vehicles WHERE owner = ?", {characterId})
|
||||||
|
if not result then return {} end
|
||||||
|
|
||||||
|
local vehicles = {}
|
||||||
|
for _, vehicle in pairs(result) do
|
||||||
|
local info = getVehicleDatabaseInfo(vehicle)
|
||||||
|
if info then
|
||||||
|
vehicles[#vehicles+1] = info
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return vehicles
|
||||||
|
end
|
||||||
|
|
||||||
|
--- give a player keys/access to a vehicle
|
||||||
|
---@param source number
|
||||||
|
---@param vehicle number
|
||||||
|
---@param access boolean
|
||||||
|
---@param info table
|
||||||
|
function NDCore.giveVehicleAccess(source, vehicle, access, info)
|
||||||
|
if not vehicle or not DoesEntityExist(vehicle) then return end
|
||||||
|
local state = Entity(vehicle).state
|
||||||
|
local netId = info?.netId or NetworkGetNetworkIdFromEntity(vehicle)
|
||||||
|
local vehicleId = info?.vehicleId or state.id or generateTemporaryVehicleId()
|
||||||
|
|
||||||
|
if not state.id then
|
||||||
|
state.id = vehicleId
|
||||||
|
end
|
||||||
|
|
||||||
|
local player = NDCore.getPlayer(source)
|
||||||
|
if player then
|
||||||
|
local keys = state.keys or {}
|
||||||
|
keys[player.id] = access
|
||||||
|
state.keys = keys
|
||||||
|
end
|
||||||
|
|
||||||
|
if not inventoryStarted or not Config.useInventoryForKeys then return end
|
||||||
|
local plate = info?.plate or GetVehicleNumberPlateText(vehicle)
|
||||||
|
local model = info?.model or GetEntityModel(vehicle)
|
||||||
|
local modelName = info?.modelName or model and lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", source, model) or ""
|
||||||
|
local hasKey = ox_inventory:GetSlotIdWithItem(source, "keys", {
|
||||||
|
vehId = vehicleId
|
||||||
|
})
|
||||||
|
|
||||||
|
if access and not hasKey then
|
||||||
|
ox_inventory:AddItem(source, "keys", 1, {
|
||||||
|
vehOwner = owner or state.owner,
|
||||||
|
vehId = vehicleId,
|
||||||
|
vehPlate = plate,
|
||||||
|
vehModel = modelName,
|
||||||
|
keyEnabled = true,
|
||||||
|
vehNetId = netId
|
||||||
|
})
|
||||||
|
elseif not access and hasKey then
|
||||||
|
ox_inventory:RemoveItem(source, "keys", 1, nil, hasKey)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- spawn a vehicle, set info & give keys.
|
||||||
|
---@param info table
|
||||||
|
---@return table
|
||||||
|
function NDCore.createVehicle(info)
|
||||||
|
local owner = info.owner
|
||||||
|
local vehicleId = info.vehicleId or generateTemporaryVehicleId()
|
||||||
|
local properties = info.properties or {}
|
||||||
|
local coords = info.coords
|
||||||
|
local spawnCoords = coords
|
||||||
|
|
||||||
|
local coordType = type(coords)
|
||||||
|
if coordType == "vector3" or coordType == "table" then
|
||||||
|
spawnCoords = vector4(coords.x, coords.y, coords.z, coords.w or coords.h or info.heading or 0.0)
|
||||||
|
end
|
||||||
|
if not spawnCoords then
|
||||||
|
return Citizen.Trace("NDCore.createVehicle", "spawnCoords not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
local model = info.model or properties.model
|
||||||
|
local vehType = getVehicleType(model)
|
||||||
|
if not vehType then
|
||||||
|
return Citizen.Trace("NDCore.createVehicle", "vehType not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
local veh = CreateVehicleServerSetter(model, vehType, spawnCoords.x, spawnCoords.y, spawnCoords.z, spawnCoords.w)
|
||||||
|
local time = os.time()
|
||||||
|
while not DoesEntityExist(veh) and time-os.time() < 5 do Wait(5) end
|
||||||
|
if not veh or not DoesEntityExist(veh) then
|
||||||
|
return Citizen.Trace("NDCore.createVehicle", "vehicle entity doesn't exist")
|
||||||
|
end
|
||||||
|
|
||||||
|
local netId = NetworkGetNetworkIdFromEntity(veh)
|
||||||
|
local state = Entity(veh).state
|
||||||
|
state.locked = true
|
||||||
|
local keys = info.keys or {}
|
||||||
|
|
||||||
|
if not properties.plate then
|
||||||
|
properties.plate = generateVehiclePlate()
|
||||||
|
end
|
||||||
|
if owner then
|
||||||
|
keys[owner] = true
|
||||||
|
state.owner = owner
|
||||||
|
end
|
||||||
|
|
||||||
|
state.keys = keys
|
||||||
|
state.props = properties
|
||||||
|
state.id = vehicleId
|
||||||
|
local vehicleName
|
||||||
|
if inventoryStarted and Config.useInventoryForKeys then
|
||||||
|
for charId, _ in pairs(keys) do
|
||||||
|
local playerSource = charId == owner and info.source
|
||||||
|
if not playerSource then
|
||||||
|
local player = NDCore.getPlayers("id", charId, true)[1]
|
||||||
|
playerSource = player and player.source
|
||||||
|
end
|
||||||
|
if playerSource then
|
||||||
|
if not vehicleName then
|
||||||
|
vehicleName = lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", playerSource, model) or ""
|
||||||
|
end
|
||||||
|
NDCore.giveVehicleAccess(playerSource, veh, true, {
|
||||||
|
vehicleId = vehicleId,
|
||||||
|
netId = netId,
|
||||||
|
plate = properties?.plate,
|
||||||
|
model = model,
|
||||||
|
vehicleName = vehicleName,
|
||||||
|
owner = owner
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return NDCore.getVehicle(veh)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- transfer ownership of a vehicle between players
|
||||||
|
---@param vehicleId number|string
|
||||||
|
---@param fromSource number
|
||||||
|
---@param toSource number
|
||||||
|
---@return boolean
|
||||||
|
function NDCore.transferVehicleOwnership(vehicleId, fromSource, toSource)
|
||||||
|
local playerFrom = NDCore.getPlayer(fromSource)
|
||||||
|
local playerTo = NDCore.getPlayer(toSource)
|
||||||
|
if not playerFrom or not playerTo then return end
|
||||||
|
|
||||||
|
local vehicle = NDCore.getVehicleById(vehicleId)
|
||||||
|
if not vehicle or vehicle.owner ~= playerFrom.id then return end
|
||||||
|
|
||||||
|
MySQL.query.await("UPDATE nd_vehicles SET owner = ? WHERE id = ?", {playerTo.id, vehicleId})
|
||||||
|
local vehicleInfo = spawnedPlayerVehicles[vehicleId]
|
||||||
|
|
||||||
|
if vehicleInfo then
|
||||||
|
local veh, netId, model = vehicleInfo.entity, vehicleInfo.netId, GetEntityModel(veh)
|
||||||
|
playerFrom.triggerEvent("ND_Vehicles:blip", netId, false)
|
||||||
|
playerTo.triggerEvent("ND_Vehicles:blip", netId, true)
|
||||||
|
|
||||||
|
NDCore.giveVehicleAccess(fromSource, veh, false, {
|
||||||
|
vehicleId = vehicleId,
|
||||||
|
netId = netId,
|
||||||
|
model = model,
|
||||||
|
owner = playerFrom.id
|
||||||
|
})
|
||||||
|
|
||||||
|
local state = Entity(veh).state
|
||||||
|
state.owner = playerTo.id
|
||||||
|
NDCore.giveVehicleAccess(toSource, veh, true, {
|
||||||
|
vehicleId = vehicleId,
|
||||||
|
netId = netId,
|
||||||
|
model = model,
|
||||||
|
owner = playerTo.id
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
playerFrom.notify({
|
||||||
|
title = "Ownership transfered",
|
||||||
|
description = ("Vehicle ownership of %s has been transfered."):format(vehicle.plate),
|
||||||
|
position = "bottom-right",
|
||||||
|
type = "success"
|
||||||
|
})
|
||||||
|
playerTo.notify({
|
||||||
|
title = "Ownership received",
|
||||||
|
description = ("Received vehicle ownership of %s."):format(vehicle.plate),
|
||||||
|
position = "bottom-right"
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- set vehicle as owned by player
|
||||||
|
---@param playerId number
|
||||||
|
---@param properties table
|
||||||
|
---@param stored boolean
|
||||||
|
---@return vehicleId number
|
||||||
|
function NDCore.setVehicleOwned(playerId, properties, stored)
|
||||||
|
local plate = generateVehiclePlate()
|
||||||
|
properties.plate = plate
|
||||||
|
return MySQL.insert.await("INSERT INTO nd_vehicles (owner, plate, properties, stored) VALUES (?, ?, ?, ?)", {playerId, plate, json.encode(properties), stored and 1 or 0})
|
||||||
|
end
|
||||||
|
|
||||||
|
--- give a player access to another players vehicle as if they are next to eachother and hand the keys.
|
||||||
|
---@param source number
|
||||||
|
---@param target number
|
||||||
|
---@param vehicle number
|
||||||
|
---@return boolean
|
||||||
|
function NDCore.shareVehicleKeys(source, target, vehicle)
|
||||||
|
if not vehicle or not DoesEntityExist(vehicle) then return end
|
||||||
|
|
||||||
|
local player = NDCore.getPlayer(source)
|
||||||
|
local targetPlayer = NDCore.getPlayer(target)
|
||||||
|
local state = Entity(vehicle).state
|
||||||
|
if not targetPlayer or not player or player.id ~= state.owner then return end
|
||||||
|
|
||||||
|
NDCore.giveVehicleAccess(target, vehicle, true)
|
||||||
|
local plate = GetVehicleNumberPlateText(vehicle)
|
||||||
|
|
||||||
|
player.notify({
|
||||||
|
title = "Keys shared",
|
||||||
|
description = ("You've shared vehicle keys to %s."):format(plate),
|
||||||
|
position = "bottom-right",
|
||||||
|
type = "success",
|
||||||
|
})
|
||||||
|
targetPlayer.notify({
|
||||||
|
title = "Keys received",
|
||||||
|
description = ("Received vehicle keys to %s."):format(plate),
|
||||||
|
position = "bottom-right",
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- spawned a vehicle that's owned by the player and checks for availability
|
||||||
|
---@param source number
|
||||||
|
---@param vehicleId number
|
||||||
|
---@param coords vector4
|
||||||
|
---@return table
|
||||||
|
function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading)
|
||||||
|
local player = NDCore.getPlayer(source)
|
||||||
|
if not player then return end
|
||||||
|
|
||||||
|
local vehicle = NDCore.getVehicleById(vehicleId)
|
||||||
|
if not vehicle or not vehicle.available or vehicle.owner ~= player.id then return end
|
||||||
|
|
||||||
|
MySQL.query.await("UPDATE nd_vehicles SET stored = ? WHERE id = ?", {0, vehicleId})
|
||||||
|
|
||||||
|
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
|
||||||
|
if vehicle.impounded then
|
||||||
|
local reclaimPrice = vehicle.metadata.impoundReclaimPrice or 200
|
||||||
|
if not player.deductMoney("bank", reclaimPrice, "Vehicle impound reclaim") then
|
||||||
|
return player.notify({
|
||||||
|
title = "Impound",
|
||||||
|
description = ("Price to reclaim is $%d, you don't have enough!"):format(reclaimPrice),
|
||||||
|
type = "error",
|
||||||
|
position = "bottom"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
player.notify({
|
||||||
|
title = "Impound",
|
||||||
|
description = ("Paid $%d to reclaim vehicle!"):format(reclaimPrice),
|
||||||
|
type = "success",
|
||||||
|
position = "bottom"
|
||||||
|
})
|
||||||
|
MySQL.query.await("UPDATE nd_vehicles SET impounded = ? WHERE id = ?", {0, vehicle.id})
|
||||||
|
end
|
||||||
|
|
||||||
|
local info = NDCore.spawnOwnedVehicle(src, vehicle.id, isParkingAvailable(locations))
|
||||||
|
if not info then return end
|
||||||
|
TriggerClientEvent("ND_Vehicles:blip", src, info.netId, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src)
|
||||||
|
local player = NDCore.getPlayer(src)
|
||||||
|
if not player then return end
|
||||||
|
return NDCore.getVehicles(player.id)
|
||||||
|
end)
|
||||||
30
shared/functions.lua
Normal file
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,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