220 Commits

Author SHA1 Message Date
Andy
539e522373 Merge pull request #57 from ND-Framework/fix/driving-locked-vehicle
fix(client/vehicle): vehicle keys issues
2024-01-16 23:31:40 +01:00
Andyyy7666
2bacdd2a20 fix(client/vehicle): vehicle keys issues 2024-01-16 23:31:12 +01:00
Andy
6242a44c0b Merge pull request #53 from realCallMeJ/main
feat: config for requiring discord server membership
2024-01-16 20:59:48 +01:00
Andyyy7666
74e4ccc2de feat: police vehicle callsigns mod 2024-01-15 19:48:24 +01:00
Andyyy7666
2fda7655b5 feat(server/player): save only one column 2024-01-11 00:36:18 +01:00
CallMeJ
ade82436a5 feat: config for requiring discord server membership 2024-01-09 15:50:09 -10:00
Andyyy7666
f512d0ed0c Update fxmanifest.lua 2024-01-09 13:27:11 +01:00
Andyyy7666
0a19fe8a33 fix(client(vehicle): hide weapon while using key 2024-01-09 13:03:42 +01:00
Andyyy7666
2882c7c1ae tweak:(database/characters): phonenumber column 2024-01-07 21:25:44 +01:00
Andyyy7666
6291690bd4 feat(database/characters): phonenumber column 2024-01-07 21:14:30 +01:00
Andyyy7666
af82c447c1 feat(server/player): phonenumber player data 2024-01-07 21:13:56 +01:00
Andyyy7666
46f4bdbd64 Update fxmanifest.lua 2023-12-29 18:43:17 +01:00
Andyyy7666
4870d639d4 fix(server/player): update client data on job set 2023-12-29 02:12:17 +01:00
Andyyy7666
ed895b4e6d tweak(client/vehicle): minimum cruise control speed 2023-12-29 00:10:40 +01:00
Andyyy7666
5d2925d0c3 fix(server/player): comparing rank 2023-12-28 23:30:18 +01:00
Andyyy7666
0500cef32f tweak: remove job if it's group is removed 2023-12-28 20:38:16 +01:00
Andyyy7666
0e1fb43cd2 feat: boss for groups 2023-12-28 20:37:59 +01:00
Andyyy7666
3260aa01f2 fix(compatibility/esx): errors without inventory 2023-12-26 23:33:33 +01:00
Andyyy7666
71a54be704 Update death.lua 2023-12-26 23:00:32 +01:00
Andyyy7666
d939607d64 fix: pay command issues 2023-12-25 23:15:36 +01:00
Andyyy7666
5cb038819c tweak(compatibility): nd v1 character data 2023-12-25 02:01:39 +01:00
Andyyy7666
9f5bb34bdd fix: groups nil issue if incorrect 2023-12-23 23:47:55 +01:00
Andyyy7666
29006870c3 feat: getConfig function 2023-12-23 23:47:24 +01:00
Andyyy7666
a60e1b9e32 fix: checking for driver in spawned vehicle 2023-12-23 00:00:45 +01:00
Andy
7fe7901a13 Update fxmanifest.lua 2023-12-22 22:53:15 +01:00
Andy
ca8f4369f6 Update README.md 2023-12-22 22:51:52 +01:00
Andy
e1d4b2fcff Update README.md 2023-12-22 22:51:09 +01:00
Andyyy7666
8b0f2b010d fix: lock inconsistency on player spawned vehicles 2023-12-22 18:25:00 +01:00
Andy
24e8874050 Merge pull request #46 from realCallMeJ/discord-info-fix
fix: not getting discord info on player connecting
2023-12-20 13:03:28 +01:00
Andy
289d040b5c Update main.lua 2023-12-20 13:02:13 +01:00
CallMeJ
3f0c47c2d0 add next as a local 2023-12-19 17:22:43 -10:00
CallMeJ
543b23a644 fix: not getting discord info till restart 2023-12-19 17:03:49 -10:00
Andyyy7666
4b106d3688 fix: PlayersInfo not being set 2023-12-16 19:46:35 +01:00
Andy
db5c54f2d8 Merge pull request #44 from Blizzard098/main
Escape SQL `groups` column value and LONGTEXT defaults.
2023-12-14 00:54:12 +01:00
Michael G
a1150bb821 Make LONGTEXT default values compatible with MySQL8 2023-12-13 18:27:00 -05:00
Michael G
8f7c52130a Make default values for LONGTEXT compatible with MySQL8 2023-12-13 18:26:36 -05:00
Michael G
6e71142970 Escape the groups column 2023-12-13 18:22:01 -05:00
Andyyy7666
d9df135abb fix: merge issues 2023-12-10 19:07:06 +01:00
Andy
72fb153cfc Merge pull request #43 from ND-Framework/wip-2.0.0
v2
2023-12-10 19:00:39 +01:00
Andyyy7666
0d54d3aef5 Merge branch 'main' into wip-2.0.0 2023-12-10 19:00:19 +01:00
Andyyy7666
f65728c90a tweak(server/player): inventory saving 2023-12-09 19:44:12 +01:00
Andy
d76293d117 fix: remove selecting money from backwards comptibility 2023-12-04 21:27:17 +01:00
Andy
53a0e176ff Merge pull request #42 from Hakkodevelopment/wip-2.0.0
fix: Discord Convars causing config to break
2023-12-03 16:45:31 +01:00
Andy
6f4f94df96 fix: getting identifiers on resource start 2023-12-03 16:43:51 +01:00
Hakko
0df4077bfe again.. im tired 2023-12-03 08:41:46 -07:00
Hakko
e064af5a8b fixed errors, sorry im tired 2023-12-03 08:38:35 -07:00
Hakko
6764ac51a8 fix: Discord Convars causing config to break 2023-12-03 08:20:56 -07:00
Andyyy7666
d46d0733fa feat(client/peds): option to hide blip on range 2023-12-02 16:07:40 +01:00
Andyyy7666
2b7b6c93b2 Update fxmanifest.lua 2023-12-02 16:07:19 +01:00
Andyyy7666
ce29f21a90 tweak: get invoking resource on create ped 2023-12-01 15:54:46 +01:00
Andyyy7666
700d422477 fix: setting vehicle props 2023-11-19 01:41:20 +01:00
Andyyy7666
7cc3bbe42a refactor: revive function 2023-11-19 00:09:45 +01:00
Andyyy7666
53255bf336 tweak: checking vehicle when setting properties 2023-11-19 00:07:35 +01:00
Andyyy7666
666c3125a3 wip: qb backwards compatiblity 2023-11-11 19:10:05 +01:00
Andyyy7666
bad05a81ca feat: esx backwards compatibility 2023-11-11 19:01:40 +01:00
Andyyy7666
34346f39a9 Update README.md 2023-11-11 03:06:02 +01:00
Andyyy7666
f48ea5b4f3 feat: esx client compatibility 2023-11-11 03:02:19 +01:00
Andyyy7666
c50003cfd8 feat: convar for compatibility 2023-11-11 03:02:08 +01:00
Andyyy7666
b319d4a90a Update README.md 2023-11-10 23:26:05 +01:00
Andyyy7666
d0097203f5 Update README.md 2023-11-10 23:12:13 +01:00
Andyyy7666
954221e13e Update README.md 2023-11-05 23:43:37 +01:00
Andyyy7666
f22577d5dd wip(compatiblity/esx): server side functions & more 2023-11-05 23:22:22 +01:00
Andyyy7666
ad91854b65 Merge branch 'wip-2.0.0' of https://github.com/ND-Framework/ND_Core into wip-2.0.0 2023-11-03 16:38:16 +01:00
Andyyy7666
623afab92a fix(server/commands): return if player not found. 2023-11-03 16:38:02 +01:00
Andy
cb96fdd3e6 Update README.md 2023-11-03 16:28:54 +01:00
Andyyy7666
d5dfa02435 fix(server/player): location & dead metadata 2023-10-30 11:43:06 +01:00
Andyyy7666
a8c5ea7d79 tweak(server/vehicle): reclaiming impounded veh 2023-10-29 11:23:05 +01:00
Andyyy7666
cebe404d42 feat: vehicle metadata 2023-10-29 11:20:33 +01:00
Andyyy7666
8aee90a10f update(client/garages): context menu 2023-10-29 10:20:55 +01:00
Andyyy7666
b5c7d4dc0b fix(shared/functions): isreosurcestarted errors 2023-10-27 09:53:21 +02:00
Andyyy7666
40e74f6944 fix: allow multiple callbacks of a resource 2023-10-25 21:47:43 +02:00
Andyyy7666
87cb479ce4 fix: bot token check 2023-10-25 21:17:37 +02:00
Andy
a6db224699 Update commands.lua 2023-10-23 19:51:54 +02:00
Andy
d341d8aaa5 Update vehicle.lua 2023-10-23 02:54:54 +02:00
Andyyy7666
0fa2ee4d4b feat: core admins trough discord roles 2023-10-21 21:44:17 +02:00
Andyyy7666
a3279ea5c8 feat(server): set character if not using multi character 2023-10-16 00:28:36 +02:00
Andyyy7666
b8bb98b20d fix(vehicle): removing old keys & check for engine keys 2023-10-14 20:04:39 +02:00
Andyyy7666
fb7c7b1de4 Update vehicle.lua 2023-10-14 19:48:18 +02:00
Andyyy7666
c057455cc9 Update peds.lua 2023-10-14 19:48:11 +02:00
Andyyy7666
f0eb040eba tweak(client/vehicle/garage): readonly menu button 2023-10-14 19:48:02 +02:00
Andy
2a7b21f3ea Update README.md 2023-09-27 04:56:43 +02:00
Andyyy7666
a5a9290b5d update(vehicle): impound check & reclaiming 2023-09-27 00:22:57 +02:00
Andyyy7666
83212d45c2 fix(peds): inconsistency 2023-09-26 23:47:24 +02:00
Andyyy7666
0b40091e3e tweak(server/vehicle): removing keys on parking 2023-09-26 23:01:20 +02:00
Andyyy7666
698707c992 fix(vehicle): netId & properties inconsistencies 2023-09-26 23:00:51 +02:00
Andyyy7666
276274cf74 fix(vehicle/garage): properties not valid 2023-09-26 22:59:03 +02:00
Andyyy7666
bdb2c4033c fix(client/peds): model or entity not found 2023-09-26 22:58:41 +02:00
Andyyy7666
825ba96c50 feat: ped blip shown by group if added 2023-09-26 11:26:04 +02:00
Andyyy7666
4064d43bb0 tweak(server/player): remove job group if job is set 2023-09-26 11:25:31 +02:00
Andyyy7666
6ff461839d feat(vehicle): garages 2023-09-26 10:24:07 +02:00
Andyyy7666
fcc0ce4551 refactor(refactor): refactor 2023-09-26 06:09:53 +02:00
Andyyy7666
dc7c34caad refactor(server/vehicle): functions & more 2023-09-26 06:09:01 +02:00
Andyyy7666
7003b29db8 fix(client/vehicle): setting props on as non entity owner 2023-09-26 03:16:33 +02:00
Andyyy7666
0848480753 refactor(database/vehicles): vehicle availability 2023-09-26 03:16:02 +02:00
Andyyy7666
47b952be1d wip(server/vehicle): vehicle functions & management 2023-09-26 03:15:37 +02:00
Andyyy7666
b72df80179 wip(server/vehicle): giving keys access 2023-09-25 00:17:52 +02:00
Andyyy7666
2696ae632d update(server/player): update job data for player 2023-09-25 00:17:11 +02:00
Andyyy7666
39e302c50f wip: death system 2023-09-24 11:19:36 +02:00
Andyyy7666
bbaf1aa90f feat: revive player 2023-09-24 11:19:22 +02:00
Andyyy7666
0e35b412fb feat(server/commands): more admin actions 2023-09-24 11:17:33 +02:00
Andyyy7666
c328a8dd50 refactor(vehicle): new functions & optimize 2023-09-24 11:16:53 +02:00
Andyyy7666
166baead74 fix(client/vehicle): NULL vehicle label 2023-09-24 11:14:26 +02:00
Andyyy7666
42cd797cc0 tweak(server/functions): get players by id 2023-09-24 11:13:31 +02:00
Andyyy7666
ae7f0dc0d8 fix(client/vehicle): locking issues 2023-09-24 04:46:24 +02:00
Andyyy7666
7a97816570 tweak: remove character functions on server 2023-09-24 04:45:37 +02:00
Andyyy7666
82bc4c6d6b feat(client/functions): notification based on resource 2023-09-18 22:54:05 +02:00
Andyyy7666
3dd2daad57 fix(client/vehicle): don't lock/unlock non set 2023-09-18 22:53:23 +02:00
Andyyy7666
00563a5f41 feat(client): ai peds 2023-08-31 05:56:31 +02:00
Andyyy7666
0c70cd42dc update(server/player): full name to player 2023-08-31 02:53:34 +02:00
Andyyy7666
70866dd4b9 fix(client/vehicle): check engine hotwire with param 2023-08-30 22:18:26 +02:00
Andyyy7666
c701622172 refactor(client/vehicle): move variables to top 2023-08-30 22:17:00 +02:00
Andyyy7666
8cc7be3e4b feat(server/main): discord invite link in server info 2023-08-30 01:21:53 +02:00
Andyyy7666
73de344234 Merge branch 'wip-2.0.0' of https://github.com/ND-Framework/ND_Core into wip-2.0.0 2023-08-29 03:41:18 +02:00
Andy
b09637d68c Update README.md 2023-08-29 03:41:07 +02:00
Andyyy7666
7882b2235e Revert "Update README.md"
This reverts commit 38ac145a88.
2023-08-29 03:40:32 +02:00
Andy
38ac145a88 Update README.md 2023-08-29 03:39:23 +02:00
Andyyy7666
76ae3a1c44 tweak(server/player): return before setting active 2023-08-29 03:20:04 +02:00
Andyyy7666
8eac50be2d feat(server/commands): pay cash to nearby player 2023-08-28 03:07:14 +02:00
Andyyy7666
7f5291517e feat(server/commands): character 2023-08-28 03:07:03 +02:00
Andyyy7666
09c9ca254d feat(server/commands): skin 2023-08-28 03:06:56 +02:00
Andyyy7666
0ee9700777 feat(server/commands): setgroup 2023-08-28 03:06:43 +02:00
Andyyy7666
151961b401 feat(server/commands): setjob 2023-08-28 03:06:30 +02:00
Andyyy7666
b7237729f4 tweak(server/comands): setmoney help & player check 2023-08-28 03:06:01 +02:00
Andyyy7666
ec86f6ff86 Update main.lua 2023-08-28 03:04:25 +02:00
Andyyy7666
0de7b9c682 tweak(server/player): groups checks & admins from config 2023-08-28 03:04:16 +02:00
Andyyy7666
4868eb42a2 refactor(server/vehicle): vehicleID to vehicleId 2023-08-28 01:37:22 +02:00
Andyyy7666
dff0be4702 tweak(server/vehicle): don't lock veh with player driver 2023-08-28 01:36:22 +02:00
Andyyy7666
0ded085a28 tweak(server): character saving and unloading 2023-08-27 17:59:22 +02:00
Andyyy7666
32801873f6 feat(client/server): change veh seat keybind 2023-08-27 01:32:20 +02:00
Andyyy7666
1e6b0f4c49 feat(client/vehicle): cruise control keybind 2023-08-27 01:31:54 +02:00
Andyyy7666
0d64a1dfec feat(server/vehicle): disable key in inventory 2023-08-27 01:31:29 +02:00
Andyyy7666
f586bd17fb feat(client/vehicle): key control (trunk) 2023-08-27 01:31:08 +02:00
Andyyy7666
5ef6afca68 refactor(server/vehicle): using keys & inventory 2023-08-27 01:29:34 +02:00
Andyyy7666
74b35442c5 feat(vehicle): lockpick & hotwire 2023-08-27 01:28:29 +02:00
Andyyy7666
57d6bbf85a feat(client/vehicle): lock keybind for non inventory use 2023-08-27 01:26:43 +02:00
Andyyy7666
a381ad793f feat(client/vehicle): key checking to start vehicle 2023-08-27 01:26:13 +02:00
Andyyy7666
45fe62c7c5 fix(client/vehicle): return vehicle entity 2023-08-27 01:23:06 +02:00
Andyyy7666
bde1f5b0bb update(vehicle): lockpick event & alarm 2023-08-27 01:22:27 +02:00
Andyyy7666
e4e5d60cf4 Update commands.lua 2023-08-26 04:36:37 +02:00
Andyyy7666
a75a307873 perf(client/vehicle): hasVehicleKeys 2023-08-26 04:36:07 +02:00
Andyyy7666
0208f22df5 feat(server): setmoney command 2023-08-26 04:35:34 +02:00
Andyyy7666
83cacb7ba6 update(server/player): job info & data 2023-08-26 01:37:15 +02:00
Andyyy7666
5a1c4ecb1a feat(server/player): get metadata and player data 2023-08-25 04:59:31 +02:00
Andyyy7666
fc1ef7b7b3 update(server/player): use lib acl wrapper 2023-08-25 04:25:48 +02:00
Andyyy7666
97e175d4fd fix(client/vehicles): check for started inventory 2023-08-25 04:24:29 +02:00
Andyyy7666
5ec7a9ac83 feat(client/vehicle): veh control & keys for engine
Added check for keys to start engine
Disabled vehicle air control
Added saving wheel steering angle
2023-08-25 03:11:55 +02:00
Andyyy7666
105ac16560 fix(server/vehicle): entity exists check 2023-08-25 03:09:34 +02:00
Andyyy7666
46fc396924 update: compat for get admin and update ace on group 2023-08-24 01:00:54 +02:00
Andyyy7666
9738fc236b update: future compatibility frameworks 2023-08-23 03:36:35 +02:00
Andyyy7666
86c38aea69 feat: add more info to veh props 2023-08-23 03:31:02 +02:00
Andyyy7666
eed4855b63 update: start compatibility 2023-08-23 03:30:36 +02:00
Andyyy7666
0baadda7b0 update: notification reosurces 2023-08-23 03:30:15 +02:00
Andyyy7666
858e5e98a2 fix: save players on resource stop 2023-08-23 03:29:36 +02:00
Andyyy7666
d31be85fd8 update: license functions 2023-08-23 03:29:22 +02:00
Andyyy7666
3ec20ea087 feat: NDCore 1 backwards compatibility 2023-08-23 00:34:41 +02:00
Andyyy7666
1652dc4b5c update: client functions 2023-08-23 00:34:12 +02:00
Andyyy7666
76b93097f6 feat: display ox inventory key metadata 2023-08-23 00:34:03 +02:00
Andyyy7666
0a5ddfe1a8 fix: check if money is below 0 2023-08-23 00:33:21 +02:00
Andyyy7666
26ae7ea1a3 feat: add ace perms on group change 2023-08-23 00:33:06 +02:00
Andyyy7666
cf67d9a4e2 update: discord function to core object func 2023-08-23 00:32:49 +02:00
Andyyy7666
ab15847a32 update: ActivePlayers to NDCore.players 2023-08-23 00:30:38 +02:00
Andyyy7666
27d6ce4f9c Update vehicle.lua 2023-08-19 22:38:03 +02:00
Andyyy7666
38680c7abf update: group & job 2023-08-19 22:09:23 +02:00
Andyyy7666
47c74409ca update: discord identifier & player info 2023-08-19 22:07:41 +02:00
Andyyy7666
44066b0828 update: fetch with identifer and charid 2023-07-23 23:24:36 +02:00
Andyyy7666
f3d54a3a27 feat: set player data and player metadata by table 2023-07-23 00:00:08 +02:00
Andyyy7666
a68b6657e5 feat: job player functions 2023-07-22 23:59:39 +02:00
Andyyy7666
eaba11e4e3 feat: get player server info function 2023-07-22 23:59:11 +02:00
Andyyy7666
bc7e2ddd2a update: lock traffic vehicles 2023-07-22 21:43:36 +02:00
Andyyy7666
d29d73c075 update: vehicle locking 2023-07-22 21:43:22 +02:00
Andyyy7666
01500fe081 update: key item metadata 2023-07-22 21:43:04 +02:00
Andyyy7666
98433908e9 update: keyfob anim 2023-07-22 21:42:45 +02:00
Andyyy7666
5fd51f5329 fix: issues with key item 2023-07-22 21:42:06 +02:00
Andyyy7666
01cfcb4b05 feat: vehicle stuff 2023-07-16 16:33:58 +02:00
Andyyy7666
9dd5351b32 fix: character id 2023-07-16 16:33:37 +02:00
Andyyy7666
35726429f9 feat: set player data & more 2023-07-16 12:31:20 +02:00
Andyyy7666
075807eb51 update: loading sql 2023-07-16 12:31:02 +02:00
Andyyy7666
ee88709043 update: fxmanifest. 2023-07-16 12:30:35 +02:00
Andyyy7666
6eb044edc4 fix(database/characters): license change to identifier 2023-07-16 12:30:22 +02:00
Andyyy7666
87b2451a93 feat: get player on client 2023-07-16 12:29:20 +02:00
Andyyy7666
c06ca96457 fix: string & return 2023-07-16 12:28:54 +02:00
Andyyy7666
e19544c65e update: colon and events 2023-07-13 01:23:23 +02:00
Andyyy7666
6f4ddba976 update(server/functions): findby more data 2023-07-13 01:22:58 +02:00
Andyyy7666
e2fb381eb9 Update main.lua 2023-07-13 01:22:34 +02:00
Andyyy7666
1ab7765cff update: events and checks 2023-07-13 01:22:24 +02:00
Andyyy7666
310184cec3 Delete .gitignore 2023-07-12 04:54:58 +02:00
Andyyy7666
f82976a822 wip: vehicles 2023-07-12 04:54:03 +02:00
Andyyy7666
9da69aa350 feat: notify player function 2023-07-12 04:53:35 +02:00
Andyyy7666
5674e424a8 Update functions.lua 2023-07-12 04:53:18 +02:00
Andyyy7666
c3d2d8ad73 Update fxmanifest.lua 2023-07-12 04:53:14 +02:00
Andyyy7666
fd7a22423f refactor(server/main): warnings and identifiers 2023-07-12 04:53:00 +02:00
Andyyy7666
47e7bc5151 refactor: database queries 2023-07-12 04:52:21 +02:00
Andyyy7666
f1d1346f3f fix: convar errors on client 2023-07-12 04:51:50 +02:00
Andyyy7666
af7d893164 refactor: database queries 2023-07-12 04:51:04 +02:00
Andyyy7666
d5d3de98bf Update fxmanifest.lua 2023-07-11 01:06:13 +02:00
Andyyy7666
2068e7bb0b feat: isResourceStarted function 2023-07-11 01:06:00 +02:00
Andyyy7666
d8cb79adf5 feat: discord stuff 2023-07-11 01:05:13 +02:00
Andyyy7666
664f48acd6 Update player.lua 2023-07-11 00:35:11 +02:00
Andyyy7666
e2c6f6b97d Update init.lua 2023-07-10 03:40:54 +02:00
Andyyy7666
177f547f80 feat: change license to any identifier 2023-07-10 03:33:01 +02:00
Andyyy7666
fb933c4e58 update: gorups 2023-07-10 03:17:32 +02:00
Andyyy7666
15a65712bb Update player.lua 2023-07-10 02:45:32 +02:00
Andyyy7666
56e0b2f4fd Delete player.lua 2023-07-10 02:42:01 +02:00
Andyyy7666
5389ae0962 revert, 2.0.0 is in new branch 2023-07-10 02:40:39 +02:00
Andyyy7666
593443cae5 Revert "feat(server/player): set coords function"
This reverts commit abad068eb7.
2023-07-10 02:39:52 +02:00
Andyyy7666
975b031a02 Revert "feat: get all players & by metadata"
This reverts commit 24caa1a857.
2023-07-10 02:38:02 +02:00
Andyyy7666
9a9fa03e8a Revert "a lot of changes"
This reverts commit bf9604e851.
2023-07-10 02:37:54 +02:00
Andyyy7666
a4b40272bd tweak: starting files 2023-07-10 02:31:59 +02:00
Andyyy7666
72f4b011ec feat: load query.sql 2023-07-10 02:31:42 +02:00
Andyyy7666
426be404f3 update: query.sql 2023-07-10 02:31:20 +02:00
Andyyy7666
36c0ab209a update: old name native 2023-07-10 02:31:09 +02:00
Andyyy7666
bf9604e851 a lot of changes 2023-07-09 21:19:35 +02:00
Andyyy7666
24caa1a857 feat: get all players & by metadata 2023-07-09 03:38:17 +02:00
Andyyy7666
abad068eb7 feat(server/player): set coords function 2023-07-09 03:38:02 +02:00
Andyyy7666
1173cc2983 fix: update character info if online 2023-07-09 03:37:46 +02:00
Andyyy7666
f4846617a3 Start of ND 2.0 2023-07-09 03:07:46 +02:00
Andyyy7666
7a5f26834e Update README.md 2023-03-01 01:06:15 +01:00
Andyyy7666
e1727609df Merge pull request #36 from n1kkec/patch-2
fix(readme): make discord link work
2023-03-01 01:05:20 +01:00
Andyyy7666
ce3219dec9 Update README.md 2023-03-01 01:03:50 +01:00
n1kkec
e0c5abee27 feat(readme): make discord link work 2023-02-11 20:08:03 +01:00
33 changed files with 4908 additions and 1261 deletions

2
.gitignore vendored
View File

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

View File

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

55
client/death.lua Normal file
View File

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

View File

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

View File

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

View File

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

227
client/peds.lua Normal file
View File

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

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

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

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

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

778
client/vehicle/main.lua Normal file
View 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)

View File

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

View File

@@ -0,0 +1,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)

View File

@@ -0,0 +1,412 @@
if not lib.table.contains(Config.compatibility, "esx") then return end
NDCore.Game = {}
NDCore.Game.Utils = {}
NDCore.Scaleform = {}
NDCore.Scaleform.Utils = {}
NDCore.Streaming = {}
NDCore.UI = {}
NDCore.UI.HUD = {}
NDCore.UI.Menu = {}
NDCore.PlayerData = {}
local uiMetatable = {
__index = function(table, key)
if type(key) == "string" and key:match("^%a+$") then
return function()
print(("[^3WARNING^7] ESX Function '%s' is not compatible with NDCore!"):format(key))
end
end
end
}
setmetatable(NDCore.UI, uiMetatable)
setmetatable(NDCore.UI.HUD, uiMetatable)
setmetatable(NDCore.UI.Menu, uiMetatable)
local function exportHandler(resource, exportName, cb)
AddEventHandler(("__cfx_export_%s_%s"):format(resource, exportName), function(setCB)
setCB(cb)
end)
end
exportHandler("es_extended", "getSharedObject", function()
return NDCore
end)
function NDCore.GetPlayerData()
local player = NDCore.getPlayer()
if not player then return {} end
player.Accounts = {
Bank = player.bank,
Money = player.Cash,
Black = player.Cash
}
if player.jobInfo then
player.job = {
id = player.job,
name = player.job,
label = player.jobInfo.label,
grade = player.jobInfo.rank,
grade_name = player.jobInfo.rankName,
grade_label = player.jobInfo.rankName,
grade_salary = 0,
skin_male = {},
skin_female = {}
}
end
player.coords = GetEntityCoords(cache.ped)
player.loadout = {}
player.maxWeight = 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)

View File

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

View File

@@ -0,0 +1,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)

View File

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

View File

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

View File

@@ -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"
}
},
}

View File

@@ -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
View 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
View File

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

View File

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

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

View File

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

View File

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

View File

@@ -1,117 +0,0 @@
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6
-- Check if discord is connected, and if whitelist is enabled then it will only allow you to join if you have the roles.
AddEventHandler("playerConnecting", function(name, setKickReason, deferrals)
local player = source
local discordIdentifier = NDCore.Functions.GetPlayerIdentifierFromType("discord", player)
deferrals.defer()
Wait(0)
deferrals.update("Connecting to discord.")
Wait(0)
if not discordIdentifier then
deferrals.done("Your discord isn't connected to FiveM, make sure discord is open and restart FiveM.")
else
if config.enableDiscordWhitelist then
local discordUserId = discordIdentifier:gsub("discord:", "")
local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId)
for _, whitelistRole in pairs(config.whitelistRoles) do
if whitelistRole == 0 or whitelistRole == "0" or (discordInfo and discordInfo.roles[whitelistRole]) then
deferrals.done()
break
end
end
deferrals.done(config.notWhitelistedMessage)
else
deferrals.done()
end
end
end)
-- Getting all the characters the player has and returning them to the client.
RegisterNetEvent("ND:GetCharacters", function()
local player = source
TriggerClientEvent("ND:returnCharacters", player, NDCore.Functions.GetPlayerCharacters(player))
end)
-- Creating a new character.
RegisterNetEvent("ND:newCharacter", function(newCharacter)
local player = source
NDCore.Functions.CreateCharacter(player, newCharacter.firstName, newCharacter.lastName, newCharacter.dob, newCharacter.gender, newCharacter.cash, newCharacter.bank)
end)
-- Update the character info when edited.
RegisterNetEvent("ND:editCharacter", function(newCharacter)
local player = source
local characters = NDCore.Functions.GetPlayerCharacters(player)
if not characters[newCharacter.id] then return end
NDCore.Functions.UpdateCharacterData(newCharacter.id, newCharacter.firstName, newCharacter.lastName, newCharacter.dob, newCharacter.gender)
end)
-- Delete character from database.
RegisterNetEvent("ND:deleteCharacter", function(characterId)
local player = source
local characters = NDCore.Functions.GetPlayerCharacters(player)
if not characters[characterId] then return end
NDCore.Functions.DeleteCharacter(characterId)
end)
-- add a player to the table.
RegisterNetEvent("ND:setCharacterOnline", function(id)
local player = source
local characters = NDCore.Functions.GetPlayerCharacters(player)
if not characters[id] then return end
NDCore.Functions.SetActiveCharacter(player, id)
end)
-- Update the characters clothes.
RegisterNetEvent("ND:updateClothes", function(clothing)
local player = source
local character = NDCore.Players[player]
NDCore.Functions.SetPlayerData(character.id, "clothing", clothing)
end)
-- Disconnecting a player
RegisterNetEvent("ND:exitGame", function()
local player = source
DropPlayer(player, "Disconnected.")
end)
-- Remove player from NDCore.Players table when they leave.
AddEventHandler("playerDropped", function()
local player = source
local character = NDCore.Players[player]
if character then
local ped = GetPlayerPed(player)
local lastLocation = GetEntityCoords(ped)
NDCore.Functions.UpdateLastLocation(character.id, {x = lastLocation.x, y = lastLocation.y, z = lastLocation.z})
end
TriggerEvent("ND:characterUnloaded", player, character)
character = nil
end)
-- Get player discord info on join.
AddEventHandler("playerJoining", function()
local src = source
local discordUserId = NDCore.Functions.GetPlayerIdentifierFromType("discord", src):gsub("discord:", "")
local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId)
NDCore.PlayersDiscordInfo[src] = discordInfo
end)
AddEventHandler("onResourceStart", function(resourceName)
if (GetCurrentResourceName() ~= resourceName) then
return
end
Wait(1000)
if not next(NDCore.PlayersDiscordInfo) then
for _, playerId in ipairs(GetPlayers()) do
local discordUserId = NDCore.Functions.GetPlayerIdentifierFromType("discord", playerId):gsub("discord:", "")
local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId)
NDCore.PlayersDiscordInfo[tonumber(playerId)] = discordInfo
end
end
end)

View File

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

View File

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

521
server/player.lua Normal file
View File

@@ -0,0 +1,521 @@
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
if GetResourceState("ModernHUD") == "started" then
TriggerClientEvent("ModernHUD:notify", self.source, ...)
elseif GetResourceState("ox_lib") == "started" then
TriggerClientEvent("ox_lib:notify", self.source, ...)
end
return true
end
function self.revive()
self.triggerEvent("ND:revivePlayer")
self.setMetadata({
dead = false,
deathInfo = false,
})
end
---@param reason string
function self.drop(reason)
if not self.source then return end
DropPlayer(self.source, reason)
end
-- Set the character as the players active character/currently playing character
function self.active()
local char = NDCore.players[self.source]
if char and char.id == self.id then return end
if char then char.unload() end
for identifierType, identifier in pairs(self.identifiers) do
if lib.table.contains(Config.admins, ("%s:%s"):format(identifierType, identifier)) then
self.addGroup("admin")
end
end
local roles = self.discord.roles
if roles then
for i=1, #Config.adminDiscordRoles do
local role = Config.adminDiscordRoles[i]
if lib.table.contains(roles, role) then
self.addGroup("admin")
end
end
end
for name, _ in pairs(self.groups) do
lib.addPrincipal(self.source, ("group.%s"):format(name))
end
NDCore.players[self.source] = self
TriggerEvent("ND:characterLoaded", self)
self.triggerEvent("ND:characterLoaded", removeCharacterFunctions(self))
end
---@param name string
---@param rank number
---@param isJob boolean
---@return boolean
function self.addGroup(name, rank, isJob)
local groupRank = tonumber(rank) or 1
local groupInfo = Config.groups?[name]
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
View 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
View File

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

View File

@@ -1 +0,0 @@
NDCore = exports["ND_Core"]:GetCoreObject()

View File

@@ -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)