319 Commits
v1.0.1 ... main

Author SHA1 Message Date
Andyyy7666
0e6caf7996 fix(groups): group data & client getting 2026-03-20 14:58:54 +01:00
Andyyy7666
b06df2aae6 fix(core): group data updating 2026-03-20 07:30:53 +01:00
Andyyy7666
b837771300 refactor(vehicles): move garages to separate resource keep api
- Moved traffic locking to client
- removed garages (moved to ND_Garages resource)
- optimized and improved the api
- fixed smaller bugs
2026-03-16 08:07:43 +01:00
Andyyy7666
b7222a1f8f feat: admin group panel 2026-03-16 08:04:08 +01:00
Andyyy7666
ec05cce082 Update player.lua 2026-03-16 07:21:15 +01:00
Andyyy7666
7ecd8fa883 Create moneylogs.sql 2026-03-16 05:47:23 +01:00
Andyyy7666
72a0b8c53f feat(server): money logs
logging of all transaction with a stack trace
2026-03-16 05:46:44 +01:00
Andyyy7666
df0d0a8160 fix(server/player): updating admin & group roles on character activate 2026-03-16 05:45:49 +01:00
Andyyy7666
ba2d4951b9 feat(server/player): parameter to keeping group if job removed 2026-03-16 05:45:05 +01:00
Andyyy7666
8a13170999 feat(server/player): server event for group added 2026-03-16 05:44:05 +01:00
Andyyy7666
2ab26fe51d refactor(server/player): saving last location into separate function 2026-03-16 05:43:18 +01:00
Andyyy7666
6939566cf4 feat: user data for db & character fetching 2026-03-16 05:42:38 +01:00
Andyyy7666
075fb087f2 feat(client/events): event reliablity 2026-03-16 05:36:53 +01:00
Andyyy7666
3d5c1c32bb feat(client/death): timestamp on death 2026-03-16 05:36:14 +01:00
Andyyy7666
d3278e9be1 feat: add more key types to getting all players 2025-09-08 19:15:01 +02:00
Andyyy7666
24229485d9 feat: creataiped callback on ped creation 2025-09-08 19:14:47 +02:00
Andyyy7666
4f9d274c62 feat: function to avoid saving last location on specifc locations 2025-09-08 19:12:43 +02:00
Andyyy7666
73f38c12f6 feat: validate character data upon creation 2025-09-08 19:12:15 +02:00
Andyyy7666
0b39a8c039 feat: customgroup data for setjob function 2025-09-05 04:22:34 +02:00
Andyyy7666
568185ca19 fix(client/peds): models being nil error 2025-08-19 19:58:24 +02:00
Andyyy7666
bc41788cc9 feat(client/peds): disable collision between models 2025-08-18 23:24:00 +02:00
Andyyy7666
8a3502e515 feat: locale for many languages 2025-08-14 18:06:45 +02:00
Andyyy7666
a431a49d8c feat: locales 2025-08-14 01:13:48 +02:00
Andyyy7666
0bd76ed79f fix: ai ped blips showWhenNear hide on spawn 2025-08-13 18:17:44 +02:00
Andyyy7666
3f20d0f918 feat: plate pattern 2025-08-13 16:57:35 +02:00
Andy
31bf86a826 Merge pull request #86 from realneonium/patch-1
fix: missing return
2025-08-13 15:49:28 +02:00
neonium
9bd42fb26e fix: missing return 2025-04-16 22:34:51 -10:00
Andy
085bb71dcc Merge pull request #85 from realneonium/main
feat: add version checking
2025-03-11 00:27:39 +01:00
neonium
35d8d887c8 add version checking 2025-02-22 23:37:30 -10:00
Andy
544ad64779 Update fxmanifest.lua 2025-02-22 23:31:12 +01:00
Andy
43f690e356 Merge pull request #84 from ND-Framework/fix/duplicate-licenses
fix: duplicate character licenses
2025-02-22 23:29:46 +01:00
Andyyy7666
e883c4e64f fix: duplicate character licenses 2025-02-22 23:29:29 +01:00
Andy
5928d89edf Merge pull request #83 from NikartikTheBear/patch-1
fix(fxmanifest typo)
2025-02-16 02:20:21 +01:00
nikartik
9b846dc07e fix(typo) 2025-02-14 22:39:10 +01:00
Andyyy7666
baac4c75dd refactor(server/main): wait for oxmysql 2025-02-05 21:22:26 +01:00
Andyyy7666
fb708cc7e7 Update commands.lua 2025-01-19 18:08:20 +01:00
Andy
1547d310eb Merge pull request #81 from ND-Framework/feat/claim-veh-command
feat(server/comands): claim veh command
2025-01-18 14:41:58 +01:00
Andyyy7666
9d4708f6d9 feat(server/comands): claim veh command 2025-01-18 14:41:41 +01:00
Andy
a7713804f0 Merge pull request #80 from ND-Framework/fix/dv-command-inconsistency
fix(server/commands): dv command inconsistency
2025-01-18 14:40:37 +01:00
Andyyy7666
a9785ce754 fix(server/commands): dv command inconsistency 2025-01-18 14:40:01 +01:00
Andy
fe922e1b98 Merge pull request #79 from ND-Framework/fix/unlock-command-inconsistency
fix(server/commands): unlock command inconsistency
2025-01-18 14:38:42 +01:00
Andyyy7666
b97f42bb0a fix(server/commands): unlock command inconsistency 2025-01-18 14:38:13 +01:00
Andy
d132c12025 Merge pull request #78 from ND-Framework/feat/replace-armor
feat(client): replace armor on revive
2025-01-17 21:13:03 +01:00
Andyyy7666
119b0034bf feat(client): replace armor on revive
When reviving player to heal, replace the armor with the previous amount.
2025-01-17 21:12:43 +01:00
Andyyy7666
622b6fa8b0 tweak: add oxlib as dependency 2025-01-17 20:58:48 +01:00
Andyyy7666
2e6dfa9e5f Update fxmanifest.lua 2025-01-17 20:56:23 +01:00
Andy
4c4166de94 Merge pull request #77 from ND-Framework/feat/lan-identifier
feat(server): lan identifer
2025-01-17 20:53:12 +01:00
Andyyy7666
dea7ac6c8f feat(server): lan identifer
Create fake identifier when using sv_lan
2025-01-17 20:52:28 +01:00
Andy
120d924cae Merge pull request #76 from ND-Framework/tweak/lockpick-tries-convar
tweak(client/vehicle): lockpick tries convar
2025-01-17 20:42:59 +01:00
Andyyy7666
2a263a6c2e tweak(client/vehicle): lockpick tries convar 2025-01-17 20:42:27 +01:00
Andy
4ff4f487a1 Merge pull request #75 from ND-Framework/feat/garage-locations
feat: more default garage locations
2025-01-17 20:36:38 +01:00
Andyyy7666
e26821841b feat: more default garage locations 2025-01-17 20:36:16 +01:00
Andy
e9aafe1f2d Merge pull request #70 from NotSomething0/fix-vehicle-metadata
fix(vehicle): set metadata state for vehicle if non-existent
2024-12-20 21:29:39 +01:00
NotSomething0
6987bb63ae Merge branch 'ND-Framework:main' into fix-vehicle-metadata 2024-05-13 17:17:27 -05:00
NotSomething0
e0c4a8419b fix(vehicle): set metadata state for vehicle if non-existent
- Added debug print for when metadata is storage is skipped
2024-05-11 14:54:57 -05:00
Andyyy7666
66f93bde14 Merge branch 'main' of https://github.com/ND-Framework/ND_Core 2024-03-26 17:08:11 +01:00
Andyyy7666
2460df2f34 feat(server/vehicles): remove vehicles from db 2024-03-26 17:08:09 +01:00
Andy
b79bb6ec7f Merge pull request #68 from Hakkodevelopment/main
small changes
2024-03-25 23:12:04 +01:00
Hakko
eef1bb60f5 bump fxmanifest version 2024-03-25 16:04:43 -06:00
Hakko
1320c4ab7d Update commands.lua 2024-03-25 16:02:24 -06:00
Andyyy7666
2c7aa5723a perf(server/vehicle): getting vehicle types 2024-03-19 01:53:06 +01:00
Andyyy7666
97a8b991ae fix(server/vehicle): taking out impounded vehicle 2024-03-19 01:52:45 +01:00
Andyyy7666
2a3ac3b63b feat(server/player): player group removed event 2024-03-19 01:52:18 +01:00
Andyyy7666
5d3c051149 fix(server/vehicle): calculating time 2024-03-17 14:26:11 +01:00
Andyyy7666
fc82b217e9 fix: clothing not updating 2024-03-16 19:12:34 +01:00
Andyyy7666
284411ce66 fix(server/player): nil group metadata 2024-03-16 16:43:20 +01:00
Andyyy7666
a7257cf68f feat(server/player): group metadata 2024-03-10 00:35:45 +01:00
Andyyy7666
841a5771c5 tweak(server/player): custom group data 2024-03-10 00:01:49 +01:00
Andy
8fd7878ba6 Merge pull request #67 from ND-Framework/tweak/updateCharacter-event
tweak(server/player): also pass which data changed
2024-03-09 19:05:49 +01:00
Andyyy7666
60e86d2038 tweak(server/player): also pass which data changed 2024-03-09 19:05:21 +01:00
Andy
626f0ad06f Merge pull request #66 from ND-Framework/feat/custom-group-info
Feat/custom group info
2024-03-09 19:03:19 +01:00
Andyyy7666
d5c3acbd81 tweak(server/player): adding job group 2024-03-09 19:02:18 +01:00
Andyyy7666
98cba87bec feat(server/player): adding custom group info 2024-03-09 19:00:15 +01:00
Andyyy7666
001fc22ef6 fix: syntax error 2024-02-29 23:17:08 +01:00
Andy
87420e99e9 Merge pull request #65 from ND-Framework/feat/discord-groups
feat: receive groups by discord roles
2024-02-29 22:29:13 +01:00
Andyyy7666
c5597161d9 feat: receive groups by discord roles
Player can receive groups based on the discord roles they have, ace perms are also added when group is received.
2024-02-29 22:28:25 +01:00
Andyyy7666
898b9aefe9 feat(server/player): updatecharacter server event 2024-02-28 14:12:18 +01:00
Andyyy7666
5545650dd5 feat: impound non parked vehicles 2024-02-27 23:13:40 +01:00
Andy
f4f01a5b1c fix: config discord invinte 2024-02-27 20:43:15 +01:00
Andyyy7666
3252118f93 tweak: move clothing menu command to core 2024-02-25 23:54:10 +01:00
Andyyy7666
9d4708a1f8 tweak(server/player): save inventory when character created 2024-02-25 23:43:00 +01:00
Andyyy7666
d0d04ae282 tweak(death): damage event for ambulance 2024-02-17 23:57:42 +01:00
Andyyy7666
c005c1b28b tweak(client/death): player death checks 2024-02-12 23:28:18 +01:00
Andyyy7666
590044b5d6 refactor(client): revive function 2024-02-12 23:27:43 +01:00
Andyyy7666
3146172c0c fix: register unloaded event 2024-02-04 16:44:55 +01:00
Andy
2ce47ef8d1 Merge pull request #62 from ND-Framework/tweak/peds-clothing-command
tweak(client/peds): copying clothing
2024-01-30 14:38:25 +01:00
Andyyy7666
6258651d8b tweak(client/peds): copying clothing 2024-01-30 14:37:44 +01:00
Andy
eecbc8f690 Merge pull request #59 from Hakkodevelopment/patch-1
Fix: Command grammar/spelling mistakes/fluidity
2024-01-26 04:29:52 +01:00
Andyyy7666
1d25b54e0f tweak: notifiacitons 2024-01-26 04:16:24 +01:00
Hakko
e18389a52a Fix: Command grammar/spelling mistakes/fluidity 2024-01-21 21:06:51 -07:00
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
Andyyy7666
e9d8826188 Update fxmanifest.lua 2023-01-11 13:16:33 +01:00
Andyyy7666
7412c14976 Merge pull request #35 from Dark3581/patch-1
feat(server/functions): GetPlayersFromCoords
2023-01-11 13:03:02 +01:00
Andyyy7666
5242cf289d perf: updating last location 2023-01-05 17:05:50 +01:00
Andyyy7666
906383bbd9 fix: groups nil 2023-01-04 19:30:50 +01:00
Dark3581
a5b297e6f8 ipair 2023-01-03 18:24:38 -08:00
Dark3581
4551db736c requested changes 2023-01-03 18:18:54 -08:00
Dark3581
30ac126949 GetPlayersFromCoords - Converted from qb/esx
gets all players within an area
2022-12-27 12:33:36 -08:00
Niko
220ecbbce1 Refactor: speed * 10000000 2022-12-26 18:15:57 -05:00
Andyyy7666
9a7afdbc8f refactor: starting bank & cash 2022-12-26 00:19:45 +01:00
56 changed files with 7777 additions and 1272 deletions

2
.gitignore vendored
View File

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

View File

@@ -1,28 +1,28 @@
<p align="center"> <p align="center">
<a href="https://ndcore.dev" target="_blank">
<img src="https://user-images.githubusercontent.com/86536434/193703880-5cb7deef-af37-42cc-8df2-b13332afee67.png" /> <img src="https://user-images.githubusercontent.com/86536434/193703880-5cb7deef-af37-42cc-8df2-b13332afee67.png" />
</a>
</p> </p>
<p align='center'><b><a href="discord.gg/nc82d8zvjm">Discord</a></b> <p align="center"><b><a href="https://ndcore.dev/">Documentation</a></b>
<p align='center'><b><a href="https://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: ## Dependencies:
[oxmysql](https://github.com/overextended/oxmysql/releases/download/v2.4.0/oxmysql.zip) * [oxmysql](https://github.com/overextended/oxmysql/releases)
* [ox_lib](https://github.com/overextended/ox_lib/releases)
## Addons ## Addons
* [Character selection](https://github.com/ND-Framework/ND_Characters/releases)
* [Ox Inventory](https://github.com/overextended/ox_inventory/releases) * [Banking](https://github.com/ND-Framework/ND_Banking/releases)
* [Character Selection](https://github.com/ND-Framework/ND_CharacterSelection) * [Appearance shops](https://github.com/ND-Framework/ND_AppearanceShops/releases)
* [No Characters (use framework without character selection)](https://github.com/ND-Framework/ND_NoCharacters) * [Dealership](https://github.com/ND-Framework/ND_Dealership/releases)
* [Vehicle System](https://github.com/ND-Framework/ND_VehicleSystem) * [Inventory](https://github.com/overextended/ox_inventory/releases)
* [Dealerships](https://github.com/ND-Framework/ND_Dealership) * [Ambulance job](https://github.com/ND-Framework/ND_Ambulance/releases)
* [Fuel (with nozzle and hose)](https://github.com/ND-Framework/ND_Fuel) * [Casino](https://github.com/ND-Framework/ND_Casino/releases)
* [Nitro](https://github.com/ND-Framework/ND_Nitro) * [Blackjack](https://github.com/ND-Framework/ND_Blackjack/releases)
* [Shot Spotter](https://github.com/ND-Framework/ND_ShotSpotter) * [Nitro](https://github.com/ND-Framework/ND_Nitro/releases)
* [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)

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 {},
timestamp = GetCloudTimeAsInt()
}
TriggerEvent("ND:playerEliminated", info)
TriggerServerEvent("ND:playerEliminated", info)
Wait(1000)
alreadyEliminated = false
end
AddEventHandler("gameEventTriggered", function(name, args)
if name ~= "CEventNetworkEntityDamage" then return end
local victim = args[1]
if not IsPedAPlayer(victim) or NetworkGetPlayerIndexFromPed(victim) ~= cache.playerId then return end
local hit, bone = GetPedLastDamageBone(victim)
if hit and usingAmbulance then
local damageWeapon = ambulance:getLastDamagingWeapon(victim)
ambulance:updateBodyDamage(bone, damageWeapon)
end
if not IsPedDeadOrDying(victim, true) or GetEntityHealth(victim) > 100 then return end
local killerEntity, deathCause = GetPedSourceOfDeath(cache.ped), GetPedCauseOfDeath(cache.ped)
local killerClientId = NetworkGetPlayerIndexFromPed(killerEntity)
if killerEntity ~= cache.ped and killerClientId and NetworkIsPlayerActive(killerClientId) then
return PlayerEliminated(deathCause, GetPlayerServerId(killerClientId), killerClientId)
end
PlayerEliminated(deathCause)
end)
local firstSpawn = true
exports.spawnmanager:setAutoSpawnCallback(function()
if firstSpawn then
firstSpawn = false
return exports.spawnmanager:spawnPlayer() and exports.spawnmanager:setAutoSpawn(false)
end
end)

View File

@@ -1,24 +1,92 @@
RegisterNetEvent("ND:returnCharacters", function(characters) RegisterNetEvent("ND:notifyClient", function(...)
NDCore.Characters = characters NDCore.notify(...)
end) end)
-- updates the money on the client. -- updates the money on the client.
RegisterNetEvent("ND:updateMoney", function(cash, bank) RegisterNetEvent("ND:updateMoney", function(cash, bank)
NDCore.SelectedCharacter.cash = cash if source == "" or not NDCore.player then return end
NDCore.SelectedCharacter.bank = bank NDCore.player.cash = cash
NDCore.player.bank = bank
end) end)
-- Sets main character. -- Sets main character.
RegisterNetEvent("ND:setCharacter", function(character) RegisterNetEvent("ND:characterLoaded", function(character)
NDCore.SelectedCharacter = character if source == "" then return end
NDCore.player = character
end) end)
-- Update main character info. -- Update main character info.
RegisterNetEvent("ND:updateCharacter", function(character) RegisterNetEvent("ND:updateCharacter", function(character)
NDCore.SelectedCharacter = character if source == "" or not NDCore.player then return end
NDCore.player = character
end) end)
-- Updates last lcoation. -- Updates last lcoation.
RegisterNetEvent("ND:updateLastLocation", function(location) RegisterNetEvent("ND:updateLastLocation", function(location)
NDCore.SelectedCharacter.lastLocation = location if source == "" or not NDCore.player then return end
NDCore.player.lastLocation = location
end)
-- Unsets main character.
RegisterNetEvent("ND:characterUnloaded", function()
if source == "" or not NDCore.player then return end
NDCore.player = nil
end)
RegisterNetEvent("ND:revivePlayer", function()
if source == "" then return end
local oldPed = cache.ped
local veh = GetVehiclePedIsIn(oldPed)
local seat = cache.seat
local coords = GetEntityCoords(oldPed)
local armor = GetPedArmour(oldPed)
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, GetEntityHeading(oldPed), true, true, false)
local ped = PlayerPedId()
if oldPed ~= ped then
DeleteEntity(oldPed)
ClearAreaOfPeds(coords.x, coords.y, coords.z, 0.2, false)
end
SetEntityInvincible(ped, false)
FreezeEntityPosition(ped, false)
SetEntityVisible(ped, true)
SetEveryoneIgnorePlayer(ped, false)
SetPedCanBeTargetted(ped, true)
SetEntityCanBeDamaged(ped, true)
SetBlockingOfNonTemporaryEvents(ped, false)
SetPedCanRagdollFromPlayerImpact(ped, true)
ClearPedTasksImmediately(ped)
SetPedArmour(ped, armor)
if veh and veh ~= 0 then
SetPedIntoVehicle(ped, veh, seat)
end
end)
RegisterNetEvent("ND:clothingMenu", function()
if GetResourceState("fivem-appearance") ~= "started" then return end
local function customize(appearance)
if not appearance then return end
TriggerServerEvent("ND:updateClothing", appearance)
end
exports["fivem-appearance"]:startPlayerCustomization(customize, {
ped = false,
headBlend = true,
faceFeatures = true,
headOverlays = true,
components = true,
props = true,
tattoos = true
})
end)
RegisterNetEvent("ND:groupsUpdated", function(simplified)
if source == "" then return end
if not simplified then return end
Config.groups = simplified
end) end)

View File

@@ -1,59 +1,44 @@
function GetCoreObject() function NDCore.getPlayer()
return NDCore return NDCore.player
end end
function NDCore.Functions.GetSelectedCharacter() function NDCore.getCharacters()
return NDCore.SelectedCharacter return NDCore.characters
end end
function NDCore.Functions.GetCharacters() function NDCore.getPlayersFromCoords(distance, coords)
return NDCore.Characters if coords then
end coords = type(coords) == "table" and vec3(coords.x, coords.y, coords.z) or coords
else
-- Callbacks are licensed under LGPL v3.0 coords = GetEntityCoords(PlayerPedId())
-- <https://github.com/overextended/ox_lib>
NDCore.callback = {}
local events = {}
RegisterNetEvent("ND:callbacks", function(key, ...)
local cb = events[key]
return cb and cb(...)
end)
local function triggerCallback(_, name, cb, ...)
local key = ("%s:%s"):format(name, math.random(0, 100000))
TriggerServerEvent(("ND:%s_cb"):format(name), key, ...)
local promise = not cb and promise.new()
events[key] = function(response, ...)
response = { response, ... }
events[key] = nil
if promise then
return promise:resolve(response)
end end
distance = distance or 5
if cb then local closePlayers = {}
cb(table.unpack(response)) local players = GetActivePlayers()
for _, player in ipairs(players) do
local target = GetPlayerPed(player)
local targetCoords = GetEntityCoords(target)
local targetdistance = #(targetCoords - coords)
if targetdistance <= distance then
closePlayers[#closePlayers + 1] = player
end end
end end
return closePlayers
end
if promise then function NDCore.getConfig(info)
return table.unpack(Citizen.Await(promise)) if not info then
return Config
end
return Config[info]
end
function NDCore.notify(...)
lib.notify(...)
end
for name, func in pairs(NDCore) do
if type(func) == "function" then
exports(name, func)
end end
end end
setmetatable(NDCore.callback, {
__call = triggerCallback
})
function NDCore.callback.await(name, ...)
return triggerCallback(nil, name, false, ...)
end
function NDCore.callback.register(name, callback)
RegisterNetEvent(("ND:%s_cb"):format(name), function(key, ...)
TriggerServerEvent("ND:callbacks", key, callback(...))
end)
end

32
client/groupadmin.lua Normal file
View File

@@ -0,0 +1,32 @@
local isOpen = false
RegisterNUICallback("hide", function(_, cb)
isOpen = false
SetNuiFocus(false, false)
cb("ok")
end)
RegisterNUICallback("groups:create", function(data, cb)
local result = lib.callback.await("ND_Core:groups:create", false, data)
cb(result or {})
end)
RegisterNUICallback("groups:edit", function(data, cb)
local result = lib.callback.await("ND_Core:groups:edit", false, data)
cb(result or {})
end)
RegisterNUICallback("groups:delete", function(data, cb)
local result = lib.callback.await("ND_Core:groups:delete", false, data)
cb(result or {})
end)
RegisterNetEvent("ND:openGroupAdmin", function(groups)
if isOpen then return end
isOpen = true
SetNuiFocus(true, true)
SendNUIMessage({
type = "groups:open",
groups = groups
})
end)

View File

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

247
client/peds.lua Normal file
View File

@@ -0,0 +1,247 @@
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
local function disableCollisionWithModels(ped, models)
if not models then return end
local coords = GetEntityCoords(ped)
for i=1, #models do
local m = models[i]
local model = type(m) == "number" and m or GetHashKey(m)
local obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 5.0, model, false, false, false)
SetEntityNoCollisionEntity(ped, obj, false)
end
end
function NDCore.createAiPed(info, cb)
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 = coords.xyz,
distance = info.distance or 25.0
})
local id = #locations+1
locations[id] = {
point = point,
blip = blip,
options = info.options,
resource = info.resource or GetInvokingResource()
}
if blip and blipInfo and blipInfo.showWhenNear and DoesBlipExist(blip) and #(GetEntityCoords(cache.ped)-coords.xyz) > point.distance then
SetBlipAlpha(blip, 0)
end
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
if cb then
cb(ped)
end
disableCollisionWithModels(ped, info.disableCollisionWithModels)
configPed(ped)
setClothing(ped, clothing)
locations[id].ped = ped
if anim and anim.dict and anim.clip then
lib.requestAnimDict(anim.dict)
TaskPlayAnim(ped, anim.dict, anim.clip, 2.0, 8.0, -1, 1, 0, 0, 0, 0)
end
if target and options then
Wait(500)
ox_target:addLocalEntity({ped}, options)
end
if blip and blipInfo and blipInfo.showWhenNear and DoesBlipExist(blip) then
SetBlipAlpha(blip, 255)
end
end
function point:onExit()
if blip and blipInfo and blipInfo.showWhenNear and DoesBlipExist(blip) then
SetBlipAlpha(blip, 0)
end
if ped and DoesEntityExist(ped) then
if target and options then
ox_target:removeLocalEntity({ped})
end
Wait(500)
DeleteEntity(ped)
end
end
return id
end
function NDCore.removeAiPed(id)
local info = locations[id]
if not info then return end
local ped = info.ped
local blip = info.blip
info.point:remove()
locations[id] = nil
if blip and DoesBlipExist(blip) then
RemoveBlip(blip)
end
if ped and DoesEntityExist(ped) then
if info.options then
ox_target:removeLocalEntity({ped})
end
DeleteEntity(ped)
end
end
RegisterNetEvent("ND:updateCharacter", function(character)
Wait(3000)
if character.id ~= NDCore.player?.id then return end
updateBlips(character.groups)
end)
RegisterNetEvent("ND:characterLoaded", function(character)
Wait(3000)
if character.id ~= NDCore.player?.id then return end
updateBlips(character.groups)
end)
AddEventHandler("onResourceStop", function(name)
if name == GetCurrentResourceName() then
for i, _ in ipairs(locations) do
NDCore.removeAiPed(i)
end
else
for i, v in ipairs(locations) do
if v.resource == name then
NDCore.removeAiPed(i)
end
end
end
end)
RegisterCommand("getclothing", function(source, args, rawCommand)
local info = ""
for k, v in pairs(clothingComponents) do
info = ("%s\n%s = { drawable = %s, texture = %s },"):format(info, k, GetPedDrawableVariation(cache.ped, v), GetPedTextureVariation(cache.ped, v))
end
for k, v in pairs(clothingProps) do
info = ("%s\n%s = { drawable = %s, texture = %s },"):format(info, k, GetPedPropIndex(cache.ped, v), GetPedPropTextureIndex(cache.ped, v))
end
lib.setClipboard(info)
end, false)

877
client/vehicle.lua Normal file
View File

@@ -0,0 +1,877 @@
local vehicleColorNames = {
[0] = locale("veh_color_black"),
[1] = locale("veh_color_black"),
[2] = locale("veh_color_black"),
[3] = locale("veh_color_silver"),
[4] = locale("veh_color_silver"),
[5] = locale("veh_color_silver"),
[6] = locale("veh_color_grey"),
[7] = locale("veh_color_silver"),
[8] = locale("veh_color_silver"),
[9] = locale("veh_color_silver"),
[10] = locale("veh_color_metal"),
[11] = locale("veh_color_grey"),
[12] = locale("veh_color_black"),
[13] = locale("veh_color_grey"),
[14] = locale("veh_color_grey"),
[15] = locale("veh_color_black"),
[16] = locale("veh_color_black"),
[17] = locale("veh_color_silver"),
[18] = locale("veh_color_silver"),
[19] = locale("veh_color_metal"),
[20] = locale("veh_color_silver"),
[21] = locale("veh_color_black"),
[22] = locale("veh_color_graphite"),
[23] = locale("veh_color_silver"),
[24] = locale("veh_color_silver"),
[25] = locale("veh_color_silver"),
[26] = locale("veh_color_silver"),
[27] = locale("veh_color_red"),
[28] = locale("veh_color_red"),
[29] = locale("veh_color_red"),
[30] = locale("veh_color_red"),
[31] = locale("veh_color_red"),
[32] = locale("veh_color_red"),
[33] = locale("veh_color_red"),
[34] = locale("veh_color_red"),
[35] = locale("veh_color_red"),
[36] = locale("veh_color_orange"),
[37] = locale("veh_color_gold"),
[38] = locale("veh_color_orange"),
[39] = locale("veh_color_red"),
[40] = locale("veh_color_red"),
[41] = locale("veh_color_orange"),
[42] = locale("veh_color_yellow"),
[43] = locale("veh_color_red"),
[44] = locale("veh_color_red"),
[45] = locale("veh_color_red"),
[46] = locale("veh_color_red"),
[47] = locale("veh_color_red"),
[48] = locale("veh_color_red"),
[49] = locale("veh_color_green"),
[50] = locale("veh_color_green"),
[51] = locale("veh_color_green"),
[52] = locale("veh_color_green"),
[53] = locale("veh_color_green"),
[54] = locale("veh_color_green"),
[55] = locale("veh_color_green"),
[56] = locale("veh_color_green"),
[57] = locale("veh_color_green"),
[58] = locale("veh_color_green"),
[59] = locale("veh_color_green"),
[60] = locale("veh_color_green"),
[61] = locale("veh_color_blue"),
[62] = locale("veh_color_blue"),
[63] = locale("veh_color_blue"),
[64] = locale("veh_color_blue"),
[65] = locale("veh_color_blue"),
[66] = locale("veh_color_blue"),
[67] = locale("veh_color_blue"),
[68] = locale("veh_color_blue"),
[69] = locale("veh_color_blue"),
[70] = locale("veh_color_blue"),
[71] = locale("veh_color_blue"),
[72] = locale("veh_color_blue"),
[73] = locale("veh_color_blue"),
[74] = locale("veh_color_blue"),
[75] = locale("veh_color_blue"),
[76] = locale("veh_color_blue"),
[77] = locale("veh_color_blue"),
[78] = locale("veh_color_blue"),
[79] = locale("veh_color_bblue"),
[80] = locale("veh_color_blue"),
[81] = locale("veh_color_blue"),
[82] = locale("veh_color_blue"),
[83] = locale("veh_color_blue"),
[84] = locale("veh_color_blue"),
[85] = locale("veh_color_blue"),
[86] = locale("veh_color_blue"),
[87] = locale("veh_color_blue"),
[88] = locale("veh_color_yellow"),
[89] = locale("veh_color_yellow"),
[90] = locale("veh_color_bronze"),
[91] = locale("veh_color_yellow"),
[92] = locale("veh_color_lime"),
[93] = locale("veh_color_champagne"),
[94] = locale("veh_color_beige"),
[95] = locale("veh_color_ivory"),
[96] = locale("veh_color_brown"),
[97] = locale("veh_color_brown"),
[98] = locale("veh_color_brown"),
[99] = locale("veh_color_beige"),
[100] = locale("veh_color_brown"),
[101] = locale("veh_color_brown"),
[102] = locale("veh_color_beechwood"),
[103] = locale("veh_color_beechwood"),
[104] = locale("veh_color_orange"),
[105] = locale("veh_color_sand"),
[106] = locale("veh_color_sand"),
[107] = locale("veh_color_cream"),
[108] = locale("veh_color_brown"),
[109] = locale("veh_color_brown"),
[110] = locale("veh_color_brown"),
[111] = locale("veh_color_white"),
[112] = locale("veh_color_white"),
[113] = locale("veh_color_beige"),
[114] = locale("veh_color_brown"),
[115] = locale("veh_color_brown"),
[116] = locale("veh_color_beige"),
[117] = locale("veh_color_steel"),
[118] = locale("veh_color_steel"),
[119] = locale("veh_color_aluminium"),
[120] = locale("veh_color_chrome"),
[121] = locale("veh_color_white"),
[122] = locale("veh_color_white"),
[123] = locale("veh_color_orange"),
[124] = locale("veh_color_orange"),
[125] = locale("veh_color_green"),
[126] = locale("veh_color_yellow"),
[127] = locale("veh_color_blue"),
[128] = locale("veh_color_green"),
[129] = locale("veh_color_brown"),
[130] = locale("veh_color_orange"),
[131] = locale("veh_color_white"),
[132] = locale("veh_color_white"),
[133] = locale("veh_color_green"),
[134] = locale("veh_color_white"),
[135] = locale("veh_color_pink"),
[136] = locale("veh_color_pink"),
[137] = locale("veh_color_pink"),
[138] = locale("veh_color_orange"),
[139] = locale("veh_color_green"),
[140] = locale("veh_color_blue"),
[141] = locale("veh_color_black"),
[142] = locale("veh_color_black"),
[143] = locale("veh_color_black"),
[144] = locale("veh_color_green"),
[145] = locale("veh_color_purple"),
[146] = locale("veh_color_blue"),
[147] = locale("veh_color_black"),
[148] = locale("veh_color_purple"),
[149] = locale("veh_color_purple"),
[150] = locale("veh_color_red"),
[151] = locale("veh_color_green"),
[152] = locale("veh_color_green"),
[153] = locale("veh_color_brown"),
[154] = locale("veh_color_tan"),
[155] = locale("veh_color_green"),
[156] = locale("veh_color_alloy"),
[157] = locale("veh_color_blue"),
}
local vehicleClassNames = {
[0] = locale("veh_class_compact"),
[1] = locale("veh_class_sedan"),
[2] = locale("veh_class_suv"),
[3] = locale("veh_class_coupe"),
[4] = locale("veh_class_muscle"),
[5] = locale("veh_class_sports_classic"),
[6] = locale("veh_class_sport"),
[7] = locale("veh_class_super"),
[8] = locale("veh_class_motorcycle"),
[9] = locale("veh_class_off_road"),
[10] = locale("veh_class_industrial"),
[11] = locale("veh_class_utility"),
[12] = locale("veh_class_van"),
[13] = locale("veh_class_cycle"),
[14] = locale("veh_class_boat"),
[15] = locale("veh_class_helicopter"),
[16] = locale("veh_class_plane"),
[17] = locale("veh_class_service"),
[18] = locale("veh_class_emergency"),
[19] = locale("veh_class_military"),
[20] = locale("veh_class_commercial"),
[21] = locale("veh_class_train"),
[22] = locale("veh_class_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 startTime = GetCloudTimeAsInt()
while (not NetworkDoesNetworkIdExist(netId) or not NetworkDoesEntityExistWithNetworkId(netId)) and (GetCloudTimeAsInt() - startTime < 5) do
Wait(100)
end
return NetToVeh(netId)
end
local function setVehicleBlip(veh, status, groupVehicle)
local currentBlip = GetBlipFromEntity(veh)
if currentBlip and DoesBlipExist(currentBlip) then
RemoveBlip(currentBlip)
end
if not status then return end
local blip = AddBlipForEntity(veh)
SetBlipSprite(blip, getVehicleBlipSprite(veh))
SetBlipColour(blip, 0)
SetBlipScale(blip, 0.8)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName("STRING")
AddTextComponentSubstringPlayerName(groupVehicle and locale("group_vehicle") or locale("personal_vehicle"))
EndTextCommandSetBlipName(blip)
end
RegisterNetEvent("ND_Vehicles:blip", function(netId, status, groupVehicle)
local veh = getVehFromNetId(netId)
if not veh then return end
setVehicleBlip(veh, status, groupVehicle)
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)
if not value then return end
local props = value
if type(value) == "string" then
props = json.decode(value)
end
if not props or type(props) ~= "table" then return end
local entity = GetEntityFromStateBagName(bagName)
if not DoesEntityExist(entity) or NetworkGetEntityOwner(entity) ~= cache.playerId then return end
lib.setVehicleProperties(entity, props)
TriggerServerEvent("ND_Vehicles:propsApplied", DoesEntityExist(entity) and NetworkGetNetworkIdFromEntity(entity))
end)
local function hasBlipGroup(player, groups)
groups = groups or {}
for i=1, #groups do
local group = groups[i]
if player.groups[group] then
return true
end
end
end
AddStateBagChangeHandler("blipGroups", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if not value or not DoesEntityExist(entity) then return end
local player = NDCore.getPlayer()
if not player then return end
setVehicleBlip(entity, hasBlipGroup(player, value), true)
end)
local playingKey = 0
local function playKeyFob(veh)
local keyFob
local ped = cache.ped
local coords = GetEntityCoords(ped)
if #(coords-GetEntityCoords(veh)) > 25.0 then return end
if not playerVehicle then
playingKey += 1
SetPedCurrentWeaponVisible(ped, false, false)
ClearPedTasks(ped)
lib.requestAnimDict("anim@mp_player_intmenu@key_fob@")
TaskPlayAnim(ped, "anim@mp_player_intmenu@key_fob@", "fob_click_fp", 8.0, 8.0, -1, 48, 1, false, false, false)
keyFob = CreateObject(`lr_prop_carkey_fob`, 0, 0, 0, true, true, true)
AttachEntityToEntity(keyFob, ped, GetPedBoneIndex(ped, 0xDEAD), 0.12, 0.04, -0.025, -100.0, 100.0, 0.0, true, true, false, true, 1, true)
Wait(700)
SetTimeout(400, function()
playingKey -= 1
end)
SetTimeout(1200, function()
if playingKey > 0 then return end
SetPedCurrentWeaponVisible(ped, true, false)
end)
end
PlaySoundFromEntity(-1, "Remote_Control_Fob", ped, "PI_Menu_Sounds", true, 0)
SetVehicleLights(veh, 2)
Wait(100)
SetVehicleLights(veh, 0)
Wait(200)
SetVehicleLights(veh, 2)
Wait(100)
SetVehicleLights(veh, 0)
return keyFob and DeleteEntity(keyFob)
end
RegisterNetEvent("ND_Vehicles:keyFob", function(netId)
playKeyFob(getVehFromNetId(netId))
end)
local dontLock = {
[8] = true, -- Motorcycles
[13] = true, -- Cycles
[14] = true -- boats
}
AddStateBagChangeHandler("locked", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if entity == 0 or value == nil or dontLock[GetVehicleClass(entity)] then return end
if value then
-- SetVehicleDoorsLockedForAllPlayers(entity, true)
return SetVehicleDoorsLocked(entity, 2)
end
CreateThread(function()
while GetVehiclePedIsEntering(cache.ped) == entity do Wait(10) end
-- SetVehicleDoorsLockedForAllPlayers(entity, false)
SetVehicleDoorsLocked(entity, 0)
end)
end)
local function getProps(veh)
if not veh or not DoesEntityExist(veh) then return end
local props = lib.getVehicleProperties(veh)
local colorPrimary, colorSecondary = GetVehicleColours(veh)
if not props then return end
props.colorNamePrimary = vehicleColorNames[colorPrimary]
props.colorNameSecondary = vehicleColorNames[colorSecondary]
props.colorName = props.colorNamePrimary == props.colorNameSecondary and props.colorNamePrimary or ("%s & %s"):format(props.colorNamePrimary, props.colorNameSecondary)
props.className = vehicleClassNames[GetVehicleClass(veh)]
props.makeName = GetLabelText(GetMakeNameFromVehicleModel(props.model))
props.modelName = GetLabelText(GetDisplayNameFromVehicleModel(props.model))
return props
end
lib.callback.register("ND_Vehicles:getProps", function(netId)
local veh = getVehFromNetId(netId)
return getProps(veh)
end)
lib.callback.register("ND_Vehicles:getPropsFromCurrentVeh", function()
local veh = GetVehiclePedIsIn(cache.ped)
return getProps(veh)
end)
lib.callback.register("ND_Vehicles:getVehicleModelMakeLabel", function(model)
local make = GetLabelText(GetMakeNameFromVehicleModel(model))
local name = GetLabelText(GetDisplayNameFromVehicleModel(model))
if make == "NULL" then
return name
elseif name == "NULL" then
return make
end
return ("%s %s"):format(make, name)
end)
local function hasVehicleKeys(veh, checkEngine)
local state = Entity(veh).state
if Config.ox_inventory and Config.useInventoryForKeys then
local metadata = {
vehId = state.id,
keyEnabled = true
}
local hasKey = exports.ox_inventory:GetItemCount("keys", metadata) > 0
return hasKey or checkEngine and state.hotwired
end
local keys = state and state.keys
local player = NDCore.getPlayer()
local hasKey = player and keys and keys[player.id]
return hasKey or checkEngine and state.hotwired
end
AddStateBagChangeHandler("owner", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if not value or not DoesEntityExist(entity) or GetEntityType(entity) ~= 2 then return end
local player = NDCore.getPlayer()
if not player or player.id ~= value then return end
local hasKey = hasVehicleKeys(entity)
setVehicleBlip(entity, hasKey)
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 = locale("keybind_carkey"),
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 = locale("veh_plate"),
vehModel = locale("veh_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)
CreateThread(function()
while true do
Wait(100)
local veh = cache.vehicle
if veh then goto skip end
local ped = cache.ped
local vehEntering = GetVehiclePedIsEntering(ped)
if not DoesEntityExist(vehEntering) or NetworkGetEntityOwner(vehEntering) == cache.serverId then goto skip end
local state = Entity(vehEntering).state
if state.owner or state.locked ~= nil then goto skip end
local driver = GetPedInVehicleSeat(vehEntering, -1)
if DoesEntityExist(driver) and IsPedAPlayer(driver) then
state.locked = false
state.hotwired = true
goto skip
end
if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end
state.locked = true
::skip::
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 = locale("progress_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 lockPickResource = GetResourceState("lockpick") == "started" and "lockpick" or GetResourceState("t3_lockpick") == "started" and "t3_lockpick"
if lockPickResource then
local success = exports[lockPickResource]:startLockpick("lockpick_vehicle", 4, Config.lockpickTries)
if not success or not DoesEntityExist(veh) or #(pos-GetEntityCoords(veh)) > 2.5 then
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), false)
if success == nil then -- nil would mean the minigame was cancelled
return false, false
end
return false, true
end
else
local dificulties = {
"easy",
"medium",
"hard"
}
local dificultyTime = {
easy = 500,
medium = 800,
hard = 1000
}
lib.requestAnimDict("veh@break_in@0h@p_m_one@")
for i=1, Config.lockpickTries do
TaskPlayAnimAdvanced(cache.ped, "veh@break_in@0h@p_m_one@", "std_force_entry_ds", pos.x, pos.y, pos.z+0.025, rot.x, rot.y, rot.z, 8.0, 8.0, 1800, 28, 0.1)
local dificulty = dificulties[math.random(1, #dificulties)]
local success = lib.skillCheck(dificulty)
if not success or not DoesEntityExist(veh) or #(pos-GetEntityCoords(veh)) > 2.5 then
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), false)
return false, true
end
Wait(dificultyTime[dificulty])
end
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)
if lockPickResource then
return true, false
else
return true, true
end
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 = locale("no_signal"),
description = locale("veh_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 = locale("veh_key_disabled"),
description = locale("veh_key_disabled2"),
type = "inform",
position = "bottom-right",
duration = 3000
})
end
end)
local function cruiseControl()
cruiseSpeedVehicle = GetEntitySpeed(playerVehicle) * 2.236936
if not playerVehicle then
lib.notify({
title = locale("cruise_control"),
description = locale("cruise_control_disabled"),
type = "inform",
position = "bottom-right",
duration = 3000
})
return
end
if cruiseSpeedVehicle < cruiseSpeedSet/3 then
lib.notify({
title = locale("cruise_control"),
description = locale("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 = locale("cruise_control_toggle"),
defaultKey = "",
onPressed = function(self)
if cruiseControlEnabled and cruiseSpeedVehicle-1 > cruiseSpeedSet then
cruiseSpeedSet = cruiseSpeedVehicle
return lib.notify({
title = locale("cruise_control"),
description = locale("cruise_control_increase"),
type = "inform",
position = "bottom-right",
duration = 3000
})
elseif cruiseControlEnabled then
cruiseControlEnabled = false
return lib.notify({
title = locale("cruise_control"),
description = locale("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 = locale("cruise_control"),
description = locale("cruise_control_enabled"),
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 = locale("seat_shuffle"),
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
}

20
database/characters.sql Normal file
View File

@@ -0,0 +1,20 @@
CREATE TABLE IF NOT EXISTS `nd_characters` (
`charid` INT(10) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) DEFAULT NULL,
`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,
`deleted_at` DATETIME NULL DEFAULT NULL,
`groups` LONGTEXT DEFAULT ('[]'),
`metadata` LONGTEXT DEFAULT ('[]'),
`inventory` LONGTEXT DEFAULT ('[]'),
PRIMARY KEY (`charid`) USING BTREE,
INDEX `idx_nd_characters_user_id` (`user_id`),
CONSTRAINT `fk_nd_characters_user_id` FOREIGN KEY (`user_id`) REFERENCES `nd_users` (`user_id`) ON UPDATE CASCADE ON DELETE SET NULL
);

10
database/group_ranks.sql Normal file
View File

@@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS `nd_group_ranks` (
`id` INT NOT NULL AUTO_INCREMENT,
`group_name` VARCHAR(50) NOT NULL,
`label` VARCHAR(100) NOT NULL,
`weight` INT NOT NULL DEFAULT 1,
`isBoss` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `idx_group_name` (`group_name`),
CONSTRAINT `fk_group_ranks_group` FOREIGN KEY (`group_name`) REFERENCES `nd_groups`(`name`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

6
database/groups.sql Normal file
View File

@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS `nd_groups` (
`name` VARCHAR(50) NOT NULL,
`label` VARCHAR(100) NOT NULL,
`isJob` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

13
database/moneylogs.sql Normal file
View File

@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS `nd_money_log` (
`log_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`character_id` INT(10) NOT NULL,
`action` VARCHAR(50) DEFAULT NULL,
`account` ENUM('cash', 'bank') NOT NULL,
`amount` INT(10) NOT NULL,
`reason` VARCHAR(255) DEFAULT NULL,
`trace` TEXT DEFAULT NULL,
`date_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`log_id`),
KEY `idx_character_id` (`character_id`),
CONSTRAINT `fk_moneylog_character` FOREIGN KEY (`character_id`) REFERENCES `nd_characters` (`charid`) ON UPDATE CASCADE ON DELETE CASCADE
);

12
database/users.sql Normal file
View File

@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS `nd_users` (
`user_id` INT(11) NOT NULL AUTO_INCREMENT,
`id_steam` LONGTEXT NULL DEFAULT NULL,
`id_discord` LONGTEXT NULL DEFAULT NULL,
`id_xbl` LONGTEXT NULL DEFAULT NULL,
`id_live` LONGTEXT NULL DEFAULT NULL,
`id_license` LONGTEXT NULL DEFAULT NULL,
`id_license2` LONGTEXT NULL DEFAULT NULL,
`id_fivem` LONGTEXT NULL DEFAULT NULL,
`id_ip` LONGTEXT NULL DEFAULT NULL,
PRIMARY KEY (`user_id`) USING BTREE
) COLLATE='utf8mb4_unicode_ci';

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,52 @@
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 -- For support join my discord: https://discord.gg/Z9Mxu72zZ6
author "Andyyy#7666, N1K0#0001" author "Andyyy#7666"
description "ND Framework Core" description "ND Framework Core"
version "1.0.0" version "2.3.2"
fx_version "cerulean" fx_version "cerulean"
game "gta5" game "gta5"
lua54 "yes" lua54 "yes"
shared_scripts { files {
"config_client.lua", "init.lua",
"shared/main.lua" "compatibility/**/locale.lua",
"locales/*.json",
"ui/**"
} }
ui_page "ui/index.html"
shared_script "@ox_lib/init.lua"
client_scripts { client_scripts {
"client/main.lua", "client/main.lua",
"shared/functions.lua",
"client/peds.lua",
"client/vehicle.lua",
"client/functions.lua", "client/functions.lua",
"client/events.lua", "client/events.lua",
"shared/import.lua" "client/death.lua",
"client/groupadmin.lua",
"compatibility/**/client.lua"
} }
server_scripts { server_scripts {
"@oxmysql/lib/MySQL.lua", "@oxmysql/lib/MySQL.lua",
"config_server.lua",
"server/main.lua", "server/main.lua",
"shared/functions.lua",
"server/player.lua",
"server/vehicle.lua",
"server/functions.lua", "server/functions.lua",
"server/events.lua", "server/groups.lua",
"server/commands.lua", "server/groupadmin.lua",
"shared/import.lua" "compatibility/**/server.lua",
"server/commands.lua"
} }
exports { dependencies {
"GetCoreObject" "ox_lib",
"oxmysql"
} }
server_exports { -- below were used with backwards compatibility but could interfere with resources checking if the resources are started.
"GetCoreObject" -- provide "es_extended"
} -- provide "qb-Core"
dependency "oxmysql"

13
init.lua Normal file
View File

@@ -0,0 +1,13 @@
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
})
return NDCore

134
locales/ar.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "لم يتم العثور على حساب ديسكورد الخاص بك، انضم إلى ديسكورد هنا: %s.",
"connecting": "جارٍ الاتصال...",
"identifier_not_found": "لم يتم العثور على %s الخاص بك.",
"deposit": "إيداع",
"withdraw": "سحب",
"vehicle_ownership_transfered": "تم نقل الملكية",
"vehicle_ownership_transfered2": "تم نقل ملكية المركبة %s.",
"vehicle_ownership_received": "تم استلام الملكية",
"vehicle_ownership_received2": "تم استلام ملكية المركبة %s.",
"keys_shared": "تمت مشاركة المفاتيح",
"keys_shared2": "لقد شاركت مفاتيح المركبة مع %s.",
"keys_received": "تم استلام المفاتيح",
"keys_received2": "لقد استلمت مفاتيح المركبة من %s.",
"no_signal": "لا يوجد إشارة",
"veh_key_disabled": "تم تعطيل مفتاح المركبة.",
"veh_key_disabled2": "تم تعطيل هذا المفتاح ولا يمكن استخدامه بعد الآن.",
"veh_far_away": "المركبة بعيدة جدًا.",
"veh_locked": "مقفول",
"veh_locked2": "تم قفل مركبتك الآن.",
"veh_unlocked": "مفتوح",
"veh_unlocked2": "تم فتح مركبتك الآن.",
"garage": "كراج",
"no_owned_veh_nearby": "لم يتم العثور على مركبة مملوكة بالقرب منك.",
"veh_stored_in_garage": "تم تخزين المركبة في الكراج.",
"impound_reclaim": "استعادة المركبة من الحجز",
"impound": "حجز",
"impound_not_enough": "سعر الاستعادة هو $%d، ليس لديك ما يكفي!",
"impound_paid": "تم دفع $%d لاستعادة المركبة!",
"success": "نجاح",
"staff_action": "إجراء من الموظفين",
"staff_money_removed": "تم إزالة $%d (%s) من %s",
"user_money_removed": "تمت إزالة $%d من %s",
"staff_money_added": "تمت إضافة $%d (%s) إلى %s",
"user_money_added": "تمت إضافة $%d إلى %s",
"staff_money_set": "تم ضبط %s (%s) على $%d",
"user_money_set": "تم ضبط %s على $%d",
"job_updated_noti": "تم تحديث الوظيفة إلى %s، الرتبة %s.",
"group_added_noti": "تمت إضافتك إلى مجموعة %s، الرتبة %s.",
"group_removed_noti": "تمت إزالتك من مجموعة %s.",
"no_player_found": "لم يتم العثور على لاعب",
"not_enough_cash": "ليس لديك ما يكفي من النقود",
"player_not_nearby": "اللاعب ليس بالقرب منك",
"cant_give_money": "تعذر إعطاء المال",
"money_received": "تم استلام المال",
"money_received2": "تم استلام $%d نقدًا",
"money_given": "تم إعطاء المال",
"money_given2": "لقد أعطيت $%d نقدًا",
"player_in_veh": "اللاعب داخل المركبة!",
"perfect": "ممتاز",
"good": "جيد",
"bad": "سيء",
"very_bad": "سيء جدًا",
"veh_make_brand": "الصانع",
"veh_model": "الطراز",
"veh_plate": "اللوحة",
"engine_status": "حالة المحرك",
"vehicle": "مركبة",
"not_found": "غير موجود",
"park_veh": "ركن المركبة",
"no_vehs_found": "لم يتم العثور على مركبات",
"vehicle_impound": "حجز المركبة",
"parking_garage": "كراج سيارات",
"impound_w_location": "الحجز (%s)",
"garage_w_location": "كراج السيارات (%s)",
"view_impounded_vehs": "عرض المركبات المحجوزة",
"view_garage": "عرض الكراج",
"personal_vehicle": "مركبة شخصية",
"keybind_carkey": "فتح/قفل المركبة (نقر مزدوج)",
"progress_hotwiring": "تشغيل المركبة بدون مفتاح",
"cruise_control": "التحكم بالسرعة",
"cruise_control_enabled": "تم تفعيل التحكم بالسرعة",
"cruise_control_disabled": "تم تعطيل التحكم بالسرعة",
"cruise_control_toggle": "تبديل التحكم بالسرعة",
"cruise_control_increase": "زيادة سرعة التحكم",
"seat_shuffle": "تبديل مقعد المركبة",
"discord_text": "تلعب: %s كـ %s %s",
"discord_text_small": "تلعب كـ: %s %s",
"discord_text_server": "تلعب: %s",
"pause_menu_cash": "نقد: $%d",
"pause_menu_bank": "البنك: $%d",
"veh_color_black": "أسود",
"veh_color_silver": "فضي",
"veh_color_grey": "رمادي",
"veh_color_metal": "معدني",
"veh_color_graphite": "جرافيت",
"veh_color_red": "أحمر",
"veh_color_orange": "برتقالي",
"veh_color_gold": "ذهبي",
"veh_color_yellow": "أصفر",
"veh_color_green": "أخضر",
"veh_color_blue": "أزرق",
"veh_color_bblue": "أزرق فاتح",
"veh_color_bronze": "برونزي",
"veh_color_lime": "ليموني",
"veh_color_champagne": "شامبانيا",
"veh_color_beige": "بيج",
"veh_color_ivory": "عاجي",
"veh_color_brown": "بني",
"veh_color_beechwood": "خشب الزان",
"veh_color_sand": "رملي",
"veh_color_cream": "كريمي",
"veh_color_white": "أبيض",
"veh_color_steel": "فولاذي",
"veh_color_aluminium": "ألمنيوم",
"veh_color_chrome": "كروم",
"veh_color_pink": "وردي",
"veh_color_purple": "بنفسجي",
"veh_color_tan": "قمحي",
"veh_color_alloy": "سبيكة",
"veh_class_compact": "مدمجة",
"veh_class_sedan": "سيدان",
"veh_class_suv": "دفع رباعي",
"veh_class_coupe": "كوبيه",
"veh_class_muscle": "عضلية",
"veh_class_sports_classic": "رياضية كلاسيكية",
"veh_class_sport": "رياضية",
"veh_class_super": "سوبر",
"veh_class_motorcycle": "دراجة نارية",
"veh_class_off_road": "طرق وعرة",
"veh_class_industrial": "صناعية",
"veh_class_utility": "خدمية",
"veh_class_van": "فان",
"veh_class_cycle": "دراجة",
"veh_class_boat": "قارب",
"veh_class_helicopter": "مروحية",
"veh_class_plane": "طائرة",
"veh_class_service": "خدمة",
"veh_class_emergency": "طوارئ",
"veh_class_military": "عسكرية",
"veh_class_commercial": "تجارية",
"veh_class_train": "قطار",
"veh_class_open_wheel": "مفتوح العجلات"
}

134
locales/da.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "Din Discord blev ikke fundet, deltag i vores Discord her: %s.",
"connecting": "Forbinder...",
"identifier_not_found": "Din %s blev ikke fundet.",
"deposit": "Indsæt",
"withdraw": "Hæv",
"vehicle_ownership_transfered": "Ejerskab overført",
"vehicle_ownership_transfered2": "Ejerskab af køretøj %s er blevet overført.",
"vehicle_ownership_received": "Ejerskab modtaget",
"vehicle_ownership_received2": "Modtog ejerskab af køretøj %s.",
"keys_shared": "Nøgler delt",
"keys_shared2": "Du har delt bilnøgler med %s.",
"keys_received": "Nøgler modtaget",
"keys_received2": "Du har modtaget bilnøgler fra %s.",
"no_signal": "Ingen signal",
"veh_key_disabled": "Bilnøgle deaktiveret.",
"veh_key_disabled2": "Denne bilnøgle er blevet deaktiveret og kan ikke bruges længere.",
"veh_far_away": "Køretøj for langt væk.",
"veh_locked": "LÅST",
"veh_locked2": "Dit køretøj er nu låst.",
"veh_unlocked": "ULÅST",
"veh_unlocked2": "Dit køretøj er nu ulåst.",
"garage": "Garage",
"no_owned_veh_nearby": "Ingen ejet køretøj fundet i nærheden.",
"veh_stored_in_garage": "Køretøj gemt i garage.",
"impound_reclaim": "Hent køretøj fra beslaglæggelse",
"impound": "Beslaglæggelse",
"impound_not_enough": "Prisen for at hente er %d kr, du har ikke nok!",
"impound_paid": "Betalt %d kr for at hente køretøj!",
"success": "succes",
"staff_action": "Personalehandling",
"staff_money_removed": "Fjernede %d kr (%s) fra %s",
"user_money_removed": "fjernede %d kr fra %s",
"staff_money_added": "Tilføjede %d kr (%s) til %s",
"user_money_added": "tilføjede %d kr til %s",
"staff_money_set": "Satte %s's (%s) til %d kr",
"user_money_set": "satte %s til %d kr",
"job_updated_noti": "Job opdateret til %s, rang %s.",
"group_added_noti": "Tilføjet til gruppe %s, rang %s.",
"group_removed_noti": "Fjernet fra gruppe %s.",
"no_player_found": "Ingen spiller fundet",
"not_enough_cash": "Du har ikke nok kontanter",
"player_not_nearby": "Spiller ikke i nærheden",
"cant_give_money": "Kunne ikke give penge",
"money_received": "Penge modtaget",
"money_received2": "Modtog %d kr i kontanter",
"money_given": "Penge givet",
"money_given2": "Du gav %d kr i kontanter",
"player_in_veh": "Spiller i køretøj!",
"perfect": "Perfekt",
"good": "God",
"bad": "Dårlig",
"very_bad": "Meget dårlig",
"veh_make_brand": "Mærke",
"veh_model": "Model",
"veh_plate": "Nummerplade",
"engine_status": "Motorstatus",
"vehicle": "Køretøj",
"not_found": "ikke fundet",
"park_veh": "Parkér køretøj",
"no_vehs_found": "Ingen køretøjer fundet",
"vehicle_impound": "Køretøj beslaglagt",
"parking_garage": "Parkeringsgarage",
"impound_w_location": "Beslaglæggelse (%s)",
"garage_w_location": "Parkeringsgarage (%s)",
"view_impounded_vehs": "Se beslaglagte køretøjer",
"view_garage": "Se garage",
"personal_vehicle": "Personligt køretøj",
"keybind_carkey": "Lås/lås op for køretøj (dobbeltklik)",
"progress_hotwiring": "Koble tænding",
"cruise_control": "Fartpilot",
"cruise_control_enabled": "Fartpilot aktiveret",
"cruise_control_disabled": "Fartpilot deaktiveret",
"cruise_control_toggle": "Skift fartpilot",
"cruise_control_increase": "Øget fartpilot hastighed",
"seat_shuffle": "Skift sæde",
"discord_text": "Spiller: %s som %s %s",
"discord_text_small": "Spiller som: %s %s",
"discord_text_server": "Spiller: %s",
"pause_menu_cash": "Kontanter: %d",
"pause_menu_bank": "Bank: %d",
"veh_color_black": "Sort",
"veh_color_silver": "Sølv",
"veh_color_grey": "Grå",
"veh_color_metal": "Metal",
"veh_color_graphite": "Grafit",
"veh_color_red": "Rød",
"veh_color_orange": "Orange",
"veh_color_gold": "Guld",
"veh_color_yellow": "Gul",
"veh_color_green": "Grøn",
"veh_color_blue": "Blå",
"veh_color_bblue": "Lyseblå",
"veh_color_bronze": "Bronze",
"veh_color_lime": "Lime",
"veh_color_champagne": "Champagne",
"veh_color_beige": "Beige",
"veh_color_ivory": "Elfenben",
"veh_color_brown": "Brun",
"veh_color_beechwood": "Bøgetræ",
"veh_color_sand": "Sand",
"veh_color_cream": "Creme",
"veh_color_white": "Hvid",
"veh_color_steel": "Stål",
"veh_color_aluminium": "Aluminium",
"veh_color_chrome": "Krom",
"veh_color_pink": "Pink",
"veh_color_purple": "Lilla",
"veh_color_tan": "Tan",
"veh_color_alloy": "Legering",
"veh_class_compact": "Kompakt",
"veh_class_sedan": "Sedan",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupé",
"veh_class_muscle": "Muskelbil",
"veh_class_sports_classic": "Klassisk sportsvogn",
"veh_class_sport": "Sport",
"veh_class_super": "Superbil",
"veh_class_motorcycle": "Motorcykel",
"veh_class_off_road": "Off-road",
"veh_class_industrial": "Industriel",
"veh_class_utility": "Utility",
"veh_class_van": "Varevogn",
"veh_class_cycle": "Cykel",
"veh_class_boat": "Båd",
"veh_class_helicopter": "Helikopter",
"veh_class_plane": "Fly",
"veh_class_service": "Service",
"veh_class_emergency": "Nød",
"veh_class_military": "Militær",
"veh_class_commercial": "Kommerciel",
"veh_class_train": "Tog",
"veh_class_open_wheel": "Åben hjul"
}

134
locales/de.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "Dein Discord wurde nicht gefunden, trete unserem Discord hier bei: %s.",
"connecting": "Verbinde...",
"identifier_not_found": "Dein %s wurde nicht gefunden.",
"deposit": "Einzahlen",
"withdraw": "Abheben",
"vehicle_ownership_transfered": "Besitz übertragen",
"vehicle_ownership_transfered2": "Fahrzeugbesitz von %s wurde übertragen.",
"vehicle_ownership_received": "Besitz erhalten",
"vehicle_ownership_received2": "Fahrzeugbesitz von %s erhalten.",
"keys_shared": "Schlüssel geteilt",
"keys_shared2": "Du hast die Fahrzeugschlüssel an %s gegeben.",
"keys_received": "Schlüssel erhalten",
"keys_received2": "Du hast die Fahrzeugschlüssel von %s erhalten.",
"no_signal": "Kein Signal",
"veh_key_disabled": "Fahrzeugschlüssel deaktiviert.",
"veh_key_disabled2": "Dieser Fahrzeugschlüssel wurde deaktiviert und kann nicht mehr verwendet werden.",
"veh_far_away": "Fahrzeug zu weit entfernt.",
"veh_locked": "GESCHLOSSEN",
"veh_locked2": "Dein Fahrzeug ist jetzt abgeschlossen.",
"veh_unlocked": "GEÖFFNET",
"veh_unlocked2": "Dein Fahrzeug ist jetzt geöffnet.",
"garage": "Garage",
"no_owned_veh_nearby": "Kein eigenes Fahrzeug in der Nähe gefunden.",
"veh_stored_in_garage": "Fahrzeug in Garage gespeichert.",
"impound_reclaim": "Fahrzeug aus dem Abschlepphof zurückholen",
"impound": "Abschlepphof",
"impound_not_enough": "Der Preis zur Rückholung beträgt %d €, du hast nicht genug!",
"impound_paid": "%d € bezahlt, um das Fahrzeug zurückzuholen!",
"success": "Erfolg",
"staff_action": "Mitarbeiteraktion",
"staff_money_removed": "%d € (%s) von %s entfernt",
"user_money_removed": "%d € von %s entfernt",
"staff_money_added": "%d € (%s) zu %s hinzugefügt",
"user_money_added": "%d € zu %s hinzugefügt",
"staff_money_set": "%s's (%s) auf %d € gesetzt",
"user_money_set": "%s auf %d € gesetzt",
"job_updated_noti": "Job auf %s, Rang %s aktualisiert.",
"group_added_noti": "Zur Gruppe %s, Rang %s hinzugefügt.",
"group_removed_noti": "Aus der Gruppe %s entfernt.",
"no_player_found": "Kein Spieler gefunden",
"not_enough_cash": "Du hast nicht genug Bargeld",
"player_not_nearby": "Spieler nicht in der Nähe",
"cant_give_money": "Konnte kein Geld geben",
"money_received": "Geld erhalten",
"money_received2": "%d € in bar erhalten",
"money_given": "Geld gegeben",
"money_given2": "Du hast %d € in bar gegeben",
"player_in_veh": "Spieler im Fahrzeug!",
"perfect": "Perfekt",
"good": "Gut",
"bad": "Schlecht",
"very_bad": "Sehr schlecht",
"veh_make_brand": "Marke",
"veh_model": "Modell",
"veh_plate": "Kennzeichen",
"engine_status": "Motorstatus",
"vehicle": "Fahrzeug",
"not_found": "nicht gefunden",
"park_veh": "Fahrzeug parken",
"no_vehs_found": "Keine Fahrzeuge gefunden",
"vehicle_impound": "Fahrzeugabschlepphof",
"parking_garage": "Parkgarage",
"impound_w_location": "Abschlepphof (%s)",
"garage_w_location": "Parkgarage (%s)",
"view_impounded_vehs": "Abgeschleppte Fahrzeuge anzeigen",
"view_garage": "Garage anzeigen",
"personal_vehicle": "Privatfahrzeug",
"keybind_carkey": "Fahrzeug auf-/zusperren (Doppelklick)",
"progress_hotwiring": "Kurzschließen",
"cruise_control": "Tempomat",
"cruise_control_enabled": "Tempomat aktiviert",
"cruise_control_disabled": "Tempomat deaktiviert",
"cruise_control_toggle": "Tempomat umschalten",
"cruise_control_increase": "Tempomatgeschwindigkeit erhöht",
"seat_shuffle": "Sitz wechseln",
"discord_text": "Spielt: %s als %s %s",
"discord_text_small": "Spielt als: %s %s",
"discord_text_server": "Spielt: %s",
"pause_menu_cash": "Bargeld: €%d",
"pause_menu_bank": "Bank: €%d",
"veh_color_black": "Schwarz",
"veh_color_silver": "Silber",
"veh_color_grey": "Grau",
"veh_color_metal": "Metall",
"veh_color_graphite": "Graphit",
"veh_color_red": "Rot",
"veh_color_orange": "Orange",
"veh_color_gold": "Gold",
"veh_color_yellow": "Gelb",
"veh_color_green": "Grün",
"veh_color_blue": "Blau",
"veh_color_bblue": "Hellblau",
"veh_color_bronze": "Bronze",
"veh_color_lime": "Limette",
"veh_color_champagne": "Champagner",
"veh_color_beige": "Beige",
"veh_color_ivory": "Elfenbein",
"veh_color_brown": "Braun",
"veh_color_beechwood": "Buchenholz",
"veh_color_sand": "Sand",
"veh_color_cream": "Creme",
"veh_color_white": "Weiß",
"veh_color_steel": "Stahl",
"veh_color_aluminium": "Aluminium",
"veh_color_chrome": "Chrom",
"veh_color_pink": "Pink",
"veh_color_purple": "Lila",
"veh_color_tan": "Hellbraun",
"veh_color_alloy": "Legierung",
"veh_class_compact": "Kompakt",
"veh_class_sedan": "Limousine",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupé",
"veh_class_muscle": "Muscle-Car",
"veh_class_sports_classic": "Klassischer Sportwagen",
"veh_class_sport": "Sportwagen",
"veh_class_super": "Supersportwagen",
"veh_class_motorcycle": "Motorrad",
"veh_class_off_road": "Geländewagen",
"veh_class_industrial": "Industriefahrzeug",
"veh_class_utility": "Nutzfahrzeug",
"veh_class_van": "Van",
"veh_class_cycle": "Fahrrad",
"veh_class_boat": "Boot",
"veh_class_helicopter": "Hubschrauber",
"veh_class_plane": "Flugzeug",
"veh_class_service": "Service",
"veh_class_emergency": "Notfall",
"veh_class_military": "Militär",
"veh_class_commercial": "Gewerblich",
"veh_class_train": "Zug",
"veh_class_open_wheel": "Offenes Rad"
}

136
locales/en.json Normal file
View File

@@ -0,0 +1,136 @@
{
"not_in_discord": "Your discord was not found, join our discord here: %s.",
"connecting": "Connecting...",
"identifier_not_found": "Your %s was not found.",
"deposit": "Deposit",
"withdraw": "Withdraw",
"vehicle_ownership_transfered": "Ownership transfered",
"vehicle_ownership_transfered2": "Vehicle ownership of %s has been transfered.",
"vehicle_ownership_received": "Ownership received",
"vehicle_ownership_received2": "Received vehicle ownership of %s.",
"keys_shared": "Keys shared",
"keys_shared2": "You've shared vehicle keys to %s.",
"keys_received": "Keys received",
"keys_received2": "You've shared vehicle keys to %s.",
"no_signal": "No signal",
"veh_key_disabled": "Vehicle key disabled.",
"veh_key_disabled2": "This vehicle key has been disabled and cannot be used anymore.",
"veh_far_away": "Vehicle to far away.",
"veh_locked": "LOCKED",
"veh_locked2": "Your vehicle has now been locked.",
"veh_unlocked": "UNLOCKED",
"veh_unlocked2": "Your vehicle has now been unlocked.",
"garage": "Garage",
"no_owned_veh_nearby": "No owned vehicle found nearby.",
"veh_stored_in_garage": "Vehicle stored in garage.",
"impound_reclaim": "Vehicle impound reclaim",
"impound": "Impound",
"impound_not_enough": "Price to reclaim is $%d, you don't have enough!",
"impound_paid": "Paid $%d to reclaim vehicle!",
"success": "success",
"staff_action": "Staff action",
"staff_money_removed": "Removed $%d (%s) from %s",
"user_money_removed": "removed $%d from %s",
"staff_money_added": "Added $%d (%s) to %s",
"user_money_added": "added $%d to %s",
"staff_money_set": "Set %s's (%s) to $%d",
"user_money_set": "set %s to $%d",
"job_updated_noti": "Job updated to %s, rank %s.",
"group_added_noti": "Added to group %s, rank %s.",
"group_removed_noti": "Removed from group %s.",
"no_player_found": "No player found",
"not_enough_cash": "You don't have enough cash",
"player_not_nearby": "Player is not nearby",
"cant_give_money": "Couldn't give money",
"money_received": "Money received",
"money_received2": "Received $%d in cash",
"money_given": "Money given",
"money_given2": "You gave $%d in cash",
"player_in_veh": "Player in vehicle!",
"perfect": "Perfect",
"good": "Good",
"bad": "Bad",
"very_bad": "Very bad",
"veh_make_brand": "Make",
"veh_model": "Model",
"veh_plate": "Plate",
"engine_status": "Engine status",
"vehicle": "Vehicle",
"not_found": "not found",
"park_veh": "Park vehicle",
"no_vehs_found": "No vehicles found",
"vehicle_impound": "Vehicle impound",
"parking_garage": "Parking garage",
"impound_w_location": "Impound (%s)",
"garage_w_location": "Parking garage (%s)",
"view_impounded_vehs": "View impounded vehicles",
"view_garage": "View garage",
"personal_vehicle": "Personal vehicle",
"group_vehicle": "Vehicle",
"keybind_carkey": "Unlock/lock vehicle (double click)",
"progress_hotwiring": "Hotwiring",
"cruise_control": "Cruise control",
"cruise_control_enabled": "Vehicle cruise control enabled",
"cruise_control_disabled": "Vehicle cruise control disabled",
"cruise_control_toggle": "Toggle vehicle cruise control",
"cruise_control_increase": "Increased cruise control speed",
"seat_shuffle": "Shuffle vehicle seat",
"discord_text": "Playing: %s as %s %s",
"discord_text_small": "Playing as: %s %s",
"discord_text_server": "Playing: %s",
"pause_menu_cash": "Cash: $%d",
"pause_menu_bank": "Bank: $%d",
"veh_color_black": "Black",
"veh_color_silver": "Silver",
"veh_color_grey": "Grey",
"veh_color_metal": "Metal",
"veh_color_graphite": "Graphite",
"veh_color_red": "Red",
"veh_color_orange": "Orange",
"veh_color_gold": "Gold",
"veh_color_yellow": "Yellow",
"veh_color_green": "Green",
"veh_color_blue": "Blue",
"veh_color_bblue": "Bblue",
"veh_color_bronze": "Bronze",
"veh_color_lime": "Lime",
"veh_color_champagne": "Champagne",
"veh_color_beige": "Beige",
"veh_color_ivory": "Ivory",
"veh_color_brown": "Brown",
"veh_color_beechwood": "Beechwood",
"veh_color_sand": "Sand",
"veh_color_cream": "Cream",
"veh_color_white": "White",
"veh_color_steel": "Steel",
"veh_color_aluminium": "Aluminium",
"veh_color_chrome": "Chrome",
"veh_color_pink": "Pink",
"veh_color_purple": "Purple",
"veh_color_tan": "Tan",
"veh_color_alloy": "Alloy",
"veh_class_compact": "Compact",
"veh_class_sedan": "Sedan",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupe",
"veh_class_muscle": "Muscle",
"veh_class_sports_classic": "Sports Classic",
"veh_class_sport": "Sport",
"veh_class_super": "Super",
"veh_class_motorcycle": "Motorcycle",
"veh_class_off_road": "Off-road",
"veh_class_industrial": "Industrial",
"veh_class_utility": "Utility",
"veh_class_van": "Van",
"veh_class_cycle": "Cycle",
"veh_class_boat": "Boat",
"veh_class_helicopter": "Helicopter",
"veh_class_plane": "Plane",
"veh_class_service": "Service",
"veh_class_emergency": "Emergency",
"veh_class_military": "Military",
"veh_class_commercial": "Commercial",
"veh_class_train": "Train",
"veh_class_open_wheel": "Open wheel"
}

134
locales/es.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "No se encontró tu Discord, únete a nuestro Discord aquí: %s.",
"connecting": "Conectando...",
"identifier_not_found": "No se encontró tu %s.",
"deposit": "Depositar",
"withdraw": "Retirar",
"vehicle_ownership_transfered": "Propiedad transferida",
"vehicle_ownership_transfered2": "La propiedad del vehículo %s ha sido transferida.",
"vehicle_ownership_received": "Propiedad recibida",
"vehicle_ownership_received2": "Has recibido la propiedad del vehículo %s.",
"keys_shared": "Llaves compartidas",
"keys_shared2": "Has compartido las llaves del vehículo con %s.",
"keys_received": "Llaves recibidas",
"keys_received2": "Has recibido las llaves del vehículo de %s.",
"no_signal": "Sin señal",
"veh_key_disabled": "Llave del vehículo desactivada.",
"veh_key_disabled2": "Esta llave ha sido desactivada y no se puede usar más.",
"veh_far_away": "Vehículo demasiado lejos.",
"veh_locked": "BLOQUEADO",
"veh_locked2": "Tu vehículo ha sido bloqueado.",
"veh_unlocked": "DESBLOQUEADO",
"veh_unlocked2": "Tu vehículo ha sido desbloqueado.",
"garage": "Garaje",
"no_owned_veh_nearby": "No se encontró un vehículo propio cerca.",
"veh_stored_in_garage": "Vehículo guardado en el garaje.",
"impound_reclaim": "Recuperar vehículo del depósito",
"impound": "Depósito",
"impound_not_enough": "El precio para recuperar es %d €, ¡no tienes suficiente!",
"impound_paid": "¡Has pagado %d € para recuperar el vehículo!",
"success": "Éxito",
"staff_action": "Acción del personal",
"staff_money_removed": "Se retiraron %d € (%s) de %s",
"user_money_removed": "se retiraron %d € de %s",
"staff_money_added": "Se añadieron %d € (%s) a %s",
"user_money_added": "se añadieron %d € a %s",
"staff_money_set": "Se estableció %s's (%s) en %d €",
"user_money_set": "se estableció %s en %d €",
"job_updated_noti": "Trabajo actualizado a %s, rango %s.",
"group_added_noti": "Añadido al grupo %s, rango %s.",
"group_removed_noti": "Eliminado del grupo %s.",
"no_player_found": "No se encontró jugador",
"not_enough_cash": "No tienes suficiente dinero en efectivo",
"player_not_nearby": "El jugador no está cerca",
"cant_give_money": "No se pudo dar dinero",
"money_received": "Dinero recibido",
"money_received2": "Has recibido %d € en efectivo",
"money_given": "Dinero entregado",
"money_given2": "Has dado %d € en efectivo",
"player_in_veh": "¡Jugador en el vehículo!",
"perfect": "Perfecto",
"good": "Bueno",
"bad": "Malo",
"very_bad": "Muy malo",
"veh_make_brand": "Marca",
"veh_model": "Modelo",
"veh_plate": "Matrícula",
"engine_status": "Estado del motor",
"vehicle": "Vehículo",
"not_found": "no encontrado",
"park_veh": "Aparcar vehículo",
"no_vehs_found": "No se encontraron vehículos",
"vehicle_impound": "Depósito de vehículos",
"parking_garage": "Aparcamiento",
"impound_w_location": "Depósito (%s)",
"garage_w_location": "Aparcamiento (%s)",
"view_impounded_vehs": "Ver vehículos en el depósito",
"view_garage": "Ver garaje",
"personal_vehicle": "Vehículo personal",
"keybind_carkey": "Bloquear/desbloquear vehículo (doble clic)",
"progress_hotwiring": "Forzando encendido",
"cruise_control": "Control de crucero",
"cruise_control_enabled": "Control de crucero activado",
"cruise_control_disabled": "Control de crucero desactivado",
"cruise_control_toggle": "Alternar control de crucero",
"cruise_control_increase": "Velocidad de control de crucero aumentada",
"seat_shuffle": "Cambiar de asiento",
"discord_text": "Jugando: %s como %s %s",
"discord_text_small": "Jugando como: %s %s",
"discord_text_server": "Jugando: %s",
"pause_menu_cash": "Efectivo: €%d",
"pause_menu_bank": "Banco: €%d",
"veh_color_black": "Negro",
"veh_color_silver": "Plateado",
"veh_color_grey": "Gris",
"veh_color_metal": "Metal",
"veh_color_graphite": "Grafito",
"veh_color_red": "Rojo",
"veh_color_orange": "Naranja",
"veh_color_gold": "Oro",
"veh_color_yellow": "Amarillo",
"veh_color_green": "Verde",
"veh_color_blue": "Azul",
"veh_color_bblue": "Azul claro",
"veh_color_bronze": "Bronce",
"veh_color_lime": "Lima",
"veh_color_champagne": "Champán",
"veh_color_beige": "Beige",
"veh_color_ivory": "Marfil",
"veh_color_brown": "Marrón",
"veh_color_beechwood": "Haya",
"veh_color_sand": "Arena",
"veh_color_cream": "Crema",
"veh_color_white": "Blanco",
"veh_color_steel": "Acero",
"veh_color_aluminium": "Aluminio",
"veh_color_chrome": "Cromo",
"veh_color_pink": "Rosa",
"veh_color_purple": "Púrpura",
"veh_color_tan": "Bronceado",
"veh_color_alloy": "Aleación",
"veh_class_compact": "Compacto",
"veh_class_sedan": "Sedán",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupé",
"veh_class_muscle": "Muscle car",
"veh_class_sports_classic": "Deportivo clásico",
"veh_class_sport": "Deportivo",
"veh_class_super": "Superdeportivo",
"veh_class_motorcycle": "Motocicleta",
"veh_class_off_road": "Todoterreno",
"veh_class_industrial": "Industrial",
"veh_class_utility": "Utilitario",
"veh_class_van": "Furgoneta",
"veh_class_cycle": "Bicicleta",
"veh_class_boat": "Barco",
"veh_class_helicopter": "Helicóptero",
"veh_class_plane": "Avión",
"veh_class_service": "Servicio",
"veh_class_emergency": "Emergencia",
"veh_class_military": "Militar",
"veh_class_commercial": "Comercial",
"veh_class_train": "Tren",
"veh_class_open_wheel": "Rueda abierta"
}

134
locales/fi.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "Discord-tiliäsi ei löytynyt, liity Discordiimme täällä: %s.",
"connecting": "Yhdistetään...",
"identifier_not_found": "%s-tunnistetta ei löytynyt.",
"deposit": "Talleta",
"withdraw": "Nosta",
"vehicle_ownership_transfered": "Omistajuus siirretty",
"vehicle_ownership_transfered2": "Ajoneuvon %s omistajuus on siirretty.",
"vehicle_ownership_received": "Omistajuus vastaanotettu",
"vehicle_ownership_received2": "Olet saanut ajoneuvon %s omistajuuden.",
"keys_shared": "Avaimet jaettu",
"keys_shared2": "Olet jakanut ajoneuvon avaimet käyttäjälle %s.",
"keys_received": "Avaimet vastaanotettu",
"keys_received2": "Olet saanut ajoneuvon avaimet käyttäjältä %s.",
"no_signal": "Ei signaalia",
"veh_key_disabled": "Ajoneuvoavain poistettu käytöstä.",
"veh_key_disabled2": "Tämä ajoneuvoavain on poistettu käytöstä eikä sitä voi enää käyttää.",
"veh_far_away": "Ajoneuvo liian kaukana.",
"veh_locked": "LUKITTU",
"veh_locked2": "Ajoneuvosi on nyt lukittu.",
"veh_unlocked": "AVATTU",
"veh_unlocked2": "Ajoneuvosi on nyt avattu.",
"garage": "Autotalli",
"no_owned_veh_nearby": "Lähistöltä ei löytynyt omistamaasi ajoneuvoa.",
"veh_stored_in_garage": "Ajoneuvo tallennettu autotalliin.",
"impound_reclaim": "Ajoneuvon takaisinlunastus varikolta",
"impound": "Varikko",
"impound_not_enough": "Takaisinlunastuksen hinta on %d €, sinulla ei ole tarpeeksi rahaa!",
"impound_paid": "Maksettu %d € ajoneuvon takaisinlunastuksesta!",
"success": "Onnistui",
"staff_action": "Ylläpidon toimenpide",
"staff_money_removed": "Poistettu %d € (%s) käyttäjältä %s",
"user_money_removed": "poistettu %d € käyttäjältä %s",
"staff_money_added": "Lisätty %d € (%s) käyttäjälle %s",
"user_money_added": "lisätty %d € käyttäjälle %s",
"staff_money_set": "Asetettu %s (%s) summaksi %d €",
"user_money_set": "asetettu %s summaksi %d €",
"job_updated_noti": "Työ päivitetty: %s, taso %s.",
"group_added_noti": "Lisätty ryhmään %s, taso %s.",
"group_removed_noti": "Poistettu ryhmästä %s.",
"no_player_found": "Pelaajaa ei löytynyt",
"not_enough_cash": "Sinulla ei ole tarpeeksi käteistä",
"player_not_nearby": "Pelaaja ei ole lähellä",
"cant_give_money": "Rahan antaminen epäonnistui",
"money_received": "Raha vastaanotettu",
"money_received2": "Vastaanotettu %d € käteisenä",
"money_given": "Raha annettu",
"money_given2": "Annoit %d € käteisenä",
"player_in_veh": "Pelaaja ajoneuvossa!",
"perfect": "Täydellinen",
"good": "Hyvä",
"bad": "Huono",
"very_bad": "Todella huono",
"veh_make_brand": "Merkki",
"veh_model": "Malli",
"veh_plate": "Rekisterikilpi",
"engine_status": "Moottorin tila",
"vehicle": "Ajoneuvo",
"not_found": "ei löytynyt",
"park_veh": "Pysäköi ajoneuvo",
"no_vehs_found": "Ajoneuvoja ei löytynyt",
"vehicle_impound": "Ajoneuvovarikko",
"parking_garage": "Pysäköintihalli",
"impound_w_location": "Varikko (%s)",
"garage_w_location": "Pysäköintihalli (%s)",
"view_impounded_vehs": "Näytä varikolla olevat ajoneuvot",
"view_garage": "Näytä autotalli",
"personal_vehicle": "Henkilökohtainen ajoneuvo",
"keybind_carkey": "Lukitse/avaa ajoneuvo (kaksoisklikkaus)",
"progress_hotwiring": "Käynnistetään luvatta",
"cruise_control": "Vakionopeudensäädin",
"cruise_control_enabled": "Vakionopeudensäädin käytössä",
"cruise_control_disabled": "Vakionopeudensäädin pois käytöstä",
"cruise_control_toggle": "Vaihda vakionopeudensäädin päälle/pois",
"cruise_control_increase": "Vakionopeus kasvoi",
"seat_shuffle": "Vaihda istumapaikkaa ajoneuvossa",
"discord_text": "Pelaa: %s hahmona %s %s",
"discord_text_small": "Pelaa hahmona: %s %s",
"discord_text_server": "Pelaa: %s",
"pause_menu_cash": "Käteinen: €%d",
"pause_menu_bank": "Pankki: €%d",
"veh_color_black": "Musta",
"veh_color_silver": "Hopea",
"veh_color_grey": "Harmaa",
"veh_color_metal": "Metalli",
"veh_color_graphite": "Grafiitti",
"veh_color_red": "Punainen",
"veh_color_orange": "Oranssi",
"veh_color_gold": "Kulta",
"veh_color_yellow": "Keltainen",
"veh_color_green": "Vihreä",
"veh_color_blue": "Sininen",
"veh_color_bblue": "Vaaleansininen",
"veh_color_bronze": "Pronssi",
"veh_color_lime": "Lime",
"veh_color_champagne": "Samppanja",
"veh_color_beige": "Beige",
"veh_color_ivory": "Norsunluu",
"veh_color_brown": "Ruskea",
"veh_color_beechwood": "Pyökki",
"veh_color_sand": "Hiekka",
"veh_color_cream": "Kerma",
"veh_color_white": "Valkoinen",
"veh_color_steel": "Teräs",
"veh_color_aluminium": "Alumiini",
"veh_color_chrome": "Kromi",
"veh_color_pink": "Vaaleanpunainen",
"veh_color_purple": "Violetti",
"veh_color_tan": "Vaalea ruskea",
"veh_color_alloy": "Seosmetalli",
"veh_class_compact": "Pieni auto",
"veh_class_sedan": "Sedan",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupe",
"veh_class_muscle": "Muskeliauto",
"veh_class_sports_classic": "Klassinen urheiluauto",
"veh_class_sport": "Urheiluauto",
"veh_class_super": "Superauto",
"veh_class_motorcycle": "Moottoripyörä",
"veh_class_off_road": "Maastoauto",
"veh_class_industrial": "Teollisuusajoneuvo",
"veh_class_utility": "Hyötyajoneuvo",
"veh_class_van": "Pakettiauto",
"veh_class_cycle": "Polkupyörä",
"veh_class_boat": "Vene",
"veh_class_helicopter": "Helikopteri",
"veh_class_plane": "Lentokone",
"veh_class_service": "Huoltoajoneuvo",
"veh_class_emergency": "Hätäajoneuvo",
"veh_class_military": "Sotilasajoneuvo",
"veh_class_commercial": "Kaupallinen ajoneuvo",
"veh_class_train": "Juna",
"veh_class_open_wheel": "Avoin pyörä"
}

134
locales/fr.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "Votre Discord n'a pas été trouvé, rejoignez notre Discord ici : %s.",
"connecting": "Connexion...",
"identifier_not_found": "Votre %s n'a pas été trouvé.",
"deposit": "Dépôt",
"withdraw": "Retrait",
"vehicle_ownership_transfered": "Propriété transférée",
"vehicle_ownership_transfered2": "La propriété du véhicule %s a été transférée.",
"vehicle_ownership_received": "Propriété reçue",
"vehicle_ownership_received2": "Vous avez reçu la propriété du véhicule %s.",
"keys_shared": "Clés partagées",
"keys_shared2": "Vous avez partagé les clés du véhicule avec %s.",
"keys_received": "Clés reçues",
"keys_received2": "Vous avez reçu les clés du véhicule de %s.",
"no_signal": "Pas de signal",
"veh_key_disabled": "Clé de véhicule désactivée.",
"veh_key_disabled2": "Cette clé de véhicule a été désactivée et ne peut plus être utilisée.",
"veh_far_away": "Véhicule trop éloigné.",
"veh_locked": "VERROUILLÉ",
"veh_locked2": "Votre véhicule est maintenant verrouillé.",
"veh_unlocked": "DÉVERROUILLÉ",
"veh_unlocked2": "Votre véhicule est maintenant déverrouillé.",
"garage": "Garage",
"no_owned_veh_nearby": "Aucun véhicule possédé trouvé à proximité.",
"veh_stored_in_garage": "Véhicule rangé dans le garage.",
"impound_reclaim": "Récupération de véhicule mis en fourrière",
"impound": "Fourrière",
"impound_not_enough": "Le prix pour récupérer est de %d €, vous n'avez pas assez!",
"impound_paid": "Vous avez payé %d € pour récupérer le véhicule!",
"success": "succès",
"staff_action": "Action du staff",
"staff_money_removed": "%d € (%s) retiré de %s",
"user_money_removed": "%d € retiré de %s",
"staff_money_added": "%d € (%s) ajouté à %s",
"user_money_added": "%d € ajouté à %s",
"staff_money_set": "Solde de %s (%s) défini à %d €",
"user_money_set": "Solde de %s défini à %d €",
"job_updated_noti": "Emploi mis à jour en %s, grade %s.",
"group_added_noti": "Ajouté au groupe %s, grade %s.",
"group_removed_noti": "Retiré du groupe %s.",
"no_player_found": "Aucun joueur trouvé",
"not_enough_cash": "Vous n'avez pas assez d'argent liquide",
"player_not_nearby": "Joueur non à proximité",
"cant_give_money": "Impossible de donner de l'argent",
"money_received": "Argent reçu",
"money_received2": "Vous avez reçu %d € en liquide",
"money_given": "Argent donné",
"money_given2": "Vous avez donné %d € en liquide",
"player_in_veh": "Joueur dans un véhicule!",
"perfect": "Parfait",
"good": "Bon",
"bad": "Mauvais",
"very_bad": "Très mauvais",
"veh_make_brand": "Marque",
"veh_model": "Modèle",
"veh_plate": "Plaque",
"engine_status": "État du moteur",
"vehicle": "Véhicule",
"not_found": "introuvable",
"park_veh": "Garer le véhicule",
"no_vehs_found": "Aucun véhicule trouvé",
"vehicle_impound": "Fourrière",
"parking_garage": "Garage de stationnement",
"impound_w_location": "Fourrière (%s)",
"garage_w_location": "Garage (%s)",
"view_impounded_vehs": "Voir les véhicules en fourrière",
"view_garage": "Voir le garage",
"personal_vehicle": "Véhicule personnel",
"keybind_carkey": "Verrouiller/déverrouiller le véhicule (double clic)",
"progress_hotwiring": "Démarrage forcé",
"cruise_control": "Régulateur de vitesse",
"cruise_control_enabled": "Régulateur de vitesse activé",
"cruise_control_disabled": "Régulateur de vitesse désactivé",
"cruise_control_toggle": "Activer/désactiver le régulateur de vitesse",
"cruise_control_increase": "Vitesse du régulateur augmentée",
"seat_shuffle": "Changer de siège",
"discord_text": "Joue : %s en tant que %s %s",
"discord_text_small": "Joue en tant que : %s %s",
"discord_text_server": "Joue : %s",
"pause_menu_cash": "Argent liquide : €%d",
"pause_menu_bank": "Banque : €%d",
"veh_color_black": "Noir",
"veh_color_silver": "Argent",
"veh_color_grey": "Gris",
"veh_color_metal": "Métal",
"veh_color_graphite": "Graphite",
"veh_color_red": "Rouge",
"veh_color_orange": "Orange",
"veh_color_gold": "Or",
"veh_color_yellow": "Jaune",
"veh_color_green": "Vert",
"veh_color_blue": "Bleu",
"veh_color_bblue": "Bleu clair",
"veh_color_bronze": "Bronze",
"veh_color_lime": "Citron vert",
"veh_color_champagne": "Champagne",
"veh_color_beige": "Beige",
"veh_color_ivory": "Ivoire",
"veh_color_brown": "Marron",
"veh_color_beechwood": "Hêtre",
"veh_color_sand": "Sable",
"veh_color_cream": "Crème",
"veh_color_white": "Blanc",
"veh_color_steel": "Acier",
"veh_color_aluminium": "Aluminium",
"veh_color_chrome": "Chrome",
"veh_color_pink": "Rose",
"veh_color_purple": "Violet",
"veh_color_tan": "Fauve",
"veh_color_alloy": "Alliage",
"veh_class_compact": "Compact",
"veh_class_sedan": "Berline",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupé",
"veh_class_muscle": "Muscle car",
"veh_class_sports_classic": "Sport classique",
"veh_class_sport": "Sport",
"veh_class_super": "Super",
"veh_class_motorcycle": "Moto",
"veh_class_off_road": "Tout-terrain",
"veh_class_industrial": "Industriel",
"veh_class_utility": "Utilitaire",
"veh_class_van": "Van",
"veh_class_cycle": "Vélo",
"veh_class_boat": "Bateau",
"veh_class_helicopter": "Hélicoptère",
"veh_class_plane": "Avion",
"veh_class_service": "Service",
"veh_class_emergency": "Urgence",
"veh_class_military": "Militaire",
"veh_class_commercial": "Commercial",
"veh_class_train": "Train",
"veh_class_open_wheel": "Monoplace"
}

134
locales/it.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "Il tuo account Discord non è stato trovato, unisciti al nostro Discord qui: %s.",
"connecting": "Connessione in corso...",
"identifier_not_found": "Il tuo %s non è stato trovato.",
"deposit": "Deposito",
"withdraw": "Prelievo",
"vehicle_ownership_transfered": "Proprietà trasferita",
"vehicle_ownership_transfered2": "La proprietà del veicolo %s è stata trasferita.",
"vehicle_ownership_received": "Proprietà ricevuta",
"vehicle_ownership_received2": "Hai ricevuto la proprietà del veicolo %s.",
"keys_shared": "Chiavi condivise",
"keys_shared2": "Hai condiviso le chiavi del veicolo con %s.",
"keys_received": "Chiavi ricevute",
"keys_received2": "Hai ricevuto le chiavi del veicolo da %s.",
"no_signal": "Nessun segnale",
"veh_key_disabled": "Chiave del veicolo disattivata.",
"veh_key_disabled2": "Questa chiave del veicolo è stata disattivata e non può più essere utilizzata.",
"veh_far_away": "Veicolo troppo lontano.",
"veh_locked": "CHIUSO",
"veh_locked2": "Il tuo veicolo è ora chiuso.",
"veh_unlocked": "APERTO",
"veh_unlocked2": "Il tuo veicolo è ora aperto.",
"garage": "Garage",
"no_owned_veh_nearby": "Nessun veicolo di tua proprietà trovato nelle vicinanze.",
"veh_stored_in_garage": "Veicolo riposto in garage.",
"impound_reclaim": "Riscatto veicolo dal deposito",
"impound": "Deposito",
"impound_not_enough": "Il prezzo per riscattare è %d €, non hai abbastanza soldi!",
"impound_paid": "Hai pagato %d € per riscattare il veicolo!",
"success": "successo",
"staff_action": "Azione staff",
"staff_money_removed": "Rimossi %d € (%s) da %s",
"user_money_removed": "rimossi %d € da %s",
"staff_money_added": "Aggiunti %d € (%s) a %s",
"user_money_added": "aggiunti %d € a %s",
"staff_money_set": "Impostato il saldo di %s (%s) a %d €",
"user_money_set": "impostato %s a %d €",
"job_updated_noti": "Lavoro aggiornato a %s, grado %s.",
"group_added_noti": "Aggiunto al gruppo %s, grado %s.",
"group_removed_noti": "Rimosso dal gruppo %s.",
"no_player_found": "Nessun giocatore trovato",
"not_enough_cash": "Non hai abbastanza contanti",
"player_not_nearby": "Il giocatore non è vicino",
"cant_give_money": "Impossibile dare denaro",
"money_received": "Denaro ricevuto",
"money_received2": "Ricevuti %d € in contanti",
"money_given": "Denaro dato",
"money_given2": "Hai dato %d € in contanti",
"player_in_veh": "Giocatore in veicolo!",
"perfect": "Perfetto",
"good": "Buono",
"bad": "Scarso",
"very_bad": "Molto scarso",
"veh_make_brand": "Marca",
"veh_model": "Modello",
"veh_plate": "Targa",
"engine_status": "Stato motore",
"vehicle": "Veicolo",
"not_found": "non trovato",
"park_veh": "Parcheggia veicolo",
"no_vehs_found": "Nessun veicolo trovato",
"vehicle_impound": "Deposito veicoli",
"parking_garage": "Parcheggio",
"impound_w_location": "Deposito (%s)",
"garage_w_location": "Parcheggio (%s)",
"view_impounded_vehs": "Visualizza veicoli in deposito",
"view_garage": "Visualizza garage",
"personal_vehicle": "Veicolo personale",
"keybind_carkey": "Apri/chiudi veicolo (doppio click)",
"progress_hotwiring": "Avviamento forzato",
"cruise_control": "Cruise control",
"cruise_control_enabled": "Cruise control attivato",
"cruise_control_disabled": "Cruise control disattivato",
"cruise_control_toggle": "Attiva/disattiva cruise control",
"cruise_control_increase": "Velocità cruise control aumentata",
"seat_shuffle": "Cambia posto nel veicolo",
"discord_text": "Giocando: %s come %s %s",
"discord_text_small": "Giocando come: %s %s",
"discord_text_server": "Giocando: %s",
"pause_menu_cash": "Contanti: €%d",
"pause_menu_bank": "Banca: €%d",
"veh_color_black": "Nero",
"veh_color_silver": "Argento",
"veh_color_grey": "Grigio",
"veh_color_metal": "Metallo",
"veh_color_graphite": "Grafite",
"veh_color_red": "Rosso",
"veh_color_orange": "Arancione",
"veh_color_gold": "Oro",
"veh_color_yellow": "Giallo",
"veh_color_green": "Verde",
"veh_color_blue": "Blu",
"veh_color_bblue": "Blu chiaro",
"veh_color_bronze": "Bronzo",
"veh_color_lime": "Lime",
"veh_color_champagne": "Champagne",
"veh_color_beige": "Beige",
"veh_color_ivory": "Avorio",
"veh_color_brown": "Marrone",
"veh_color_beechwood": "Faggio",
"veh_color_sand": "Sabbia",
"veh_color_cream": "Crema",
"veh_color_white": "Bianco",
"veh_color_steel": "Acciaio",
"veh_color_aluminium": "Alluminio",
"veh_color_chrome": "Cromo",
"veh_color_pink": "Rosa",
"veh_color_purple": "Viola",
"veh_color_tan": "Marrone chiaro",
"veh_color_alloy": "Lega",
"veh_class_compact": "Compatta",
"veh_class_sedan": "Berlina",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupé",
"veh_class_muscle": "Muscle car",
"veh_class_sports_classic": "Sport classico",
"veh_class_sport": "Sport",
"veh_class_super": "Super",
"veh_class_motorcycle": "Motocicletta",
"veh_class_off_road": "Fuoristrada",
"veh_class_industrial": "Industriale",
"veh_class_utility": "Utility",
"veh_class_van": "Furgone",
"veh_class_cycle": "Bicicletta",
"veh_class_boat": "Barca",
"veh_class_helicopter": "Elicottero",
"veh_class_plane": "Aereo",
"veh_class_service": "Servizio",
"veh_class_emergency": "Emergenza",
"veh_class_military": "Militare",
"veh_class_commercial": "Commerciale",
"veh_class_train": "Treno",
"veh_class_open_wheel": "Ruota scoperta"
}

134
locales/nl.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "Je Discord-account is niet gevonden, sluit je hier aan bij onze Discord: %s.",
"connecting": "Verbinden...",
"identifier_not_found": "Je %s is niet gevonden.",
"deposit": "Storten",
"withdraw": "Opnemen",
"vehicle_ownership_transfered": "Eigendom overgedragen",
"vehicle_ownership_transfered2": "Het eigendom van het voertuig %s is overgedragen.",
"vehicle_ownership_received": "Eigendom ontvangen",
"vehicle_ownership_received2": "Je hebt het eigendom van voertuig %s ontvangen.",
"keys_shared": "Sleutels gedeeld",
"keys_shared2": "Je hebt voertuig sleutels gedeeld met %s.",
"keys_received": "Sleutels ontvangen",
"keys_received2": "Je hebt voertuig sleutels ontvangen van %s.",
"no_signal": "Geen signaal",
"veh_key_disabled": "Voertuigsleutel uitgeschakeld.",
"veh_key_disabled2": "Deze voertuigsleutel is uitgeschakeld en kan niet meer worden gebruikt.",
"veh_far_away": "Voertuig te ver weg.",
"veh_locked": "GESLOTEN",
"veh_locked2": "Je voertuig is nu gesloten.",
"veh_unlocked": "GEOPEND",
"veh_unlocked2": "Je voertuig is nu geopend.",
"garage": "Garage",
"no_owned_veh_nearby": "Geen eigendom voertuig in de buurt gevonden.",
"veh_stored_in_garage": "Voertuig opgeslagen in garage.",
"impound_reclaim": "Voertuig terugvorderen uit beslag",
"impound": "Beslag",
"impound_not_enough": "Prijs voor terugvordering is €%d, je hebt niet genoeg!",
"impound_paid": "€%d betaald om voertuig terug te vorderen!",
"success": "succes",
"staff_action": "Staf actie",
"staff_money_removed": "€%d (%s) verwijderd van %s",
"user_money_removed": "€%d verwijderd van %s",
"staff_money_added": "€%d (%s) toegevoegd aan %s",
"user_money_added": "€%d toegevoegd aan %s",
"staff_money_set": "Saldo van %s (%s) ingesteld op €%d",
"user_money_set": "Saldo van %s ingesteld op €%d",
"job_updated_noti": "Baan bijgewerkt naar %s, rang %s.",
"group_added_noti": "Toegevoegd aan groep %s, rang %s.",
"group_removed_noti": "Verwijderd uit groep %s.",
"no_player_found": "Geen speler gevonden",
"not_enough_cash": "Je hebt niet genoeg contant geld",
"player_not_nearby": "Speler is niet in de buurt",
"cant_give_money": "Kon geen geld geven",
"money_received": "Geld ontvangen",
"money_received2": "€%d contant ontvangen",
"money_given": "Geld gegeven",
"money_given2": "Je hebt €%d contant gegeven",
"player_in_veh": "Speler in voertuig!",
"perfect": "Perfect",
"good": "Goed",
"bad": "Slecht",
"very_bad": "Zeer slecht",
"veh_make_brand": "Merk",
"veh_model": "Model",
"veh_plate": "Kenteken",
"engine_status": "Motorstatus",
"vehicle": "Voertuig",
"not_found": "niet gevonden",
"park_veh": "Voertuig parkeren",
"no_vehs_found": "Geen voertuigen gevonden",
"vehicle_impound": "Voertuigen in beslag",
"parking_garage": "Parkeergarage",
"impound_w_location": "Beslag (%s)",
"garage_w_location": "Parkeergarage (%s)",
"view_impounded_vehs": "Bekijk in beslag genomen voertuigen",
"view_garage": "Bekijk garage",
"personal_vehicle": "Persoonlijk voertuig",
"keybind_carkey": "Voertuig openen/sluiten (dubbelklik)",
"progress_hotwiring": "Hotwiring",
"cruise_control": "Cruise control",
"cruise_control_enabled": "Cruise control ingeschakeld",
"cruise_control_disabled": "Cruise control uitgeschakeld",
"cruise_control_toggle": "Cruise control aan/uit",
"cruise_control_increase": "Cruise control snelheid verhoogd",
"seat_shuffle": "Stoel wisselen",
"discord_text": "Spelend: %s als %s %s",
"discord_text_small": "Spelend als: %s %s",
"discord_text_server": "Spelend: %s",
"pause_menu_cash": "Contant: €%d",
"pause_menu_bank": "Bank: €%d",
"veh_color_black": "Zwart",
"veh_color_silver": "Zilver",
"veh_color_grey": "Grijs",
"veh_color_metal": "Metaal",
"veh_color_graphite": "Grafiet",
"veh_color_red": "Rood",
"veh_color_orange": "Oranje",
"veh_color_gold": "Goud",
"veh_color_yellow": "Geel",
"veh_color_green": "Groen",
"veh_color_blue": "Blauw",
"veh_color_bblue": "Helderblauw",
"veh_color_bronze": "Brons",
"veh_color_lime": "Limoen",
"veh_color_champagne": "Champagne",
"veh_color_beige": "Beige",
"veh_color_ivory": "Ivoor",
"veh_color_brown": "Bruin",
"veh_color_beechwood": "Beukenhout",
"veh_color_sand": "Zand",
"veh_color_cream": "Crème",
"veh_color_white": "Wit",
"veh_color_steel": "Staal",
"veh_color_aluminium": "Aluminium",
"veh_color_chrome": "Chroom",
"veh_color_pink": "Roze",
"veh_color_purple": "Paars",
"veh_color_tan": "Bruin-beige",
"veh_color_alloy": "Alloy",
"veh_class_compact": "Compact",
"veh_class_sedan": "Sedan",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupe",
"veh_class_muscle": "Muscle",
"veh_class_sports_classic": "Sport Classic",
"veh_class_sport": "Sport",
"veh_class_super": "Super",
"veh_class_motorcycle": "Motorfiets",
"veh_class_off_road": "Off-road",
"veh_class_industrial": "Industrieel",
"veh_class_utility": "Utility",
"veh_class_van": "Bus",
"veh_class_cycle": "Fiets",
"veh_class_boat": "Boot",
"veh_class_helicopter": "Helikopter",
"veh_class_plane": "Vliegtuig",
"veh_class_service": "Dienst",
"veh_class_emergency": "Nooddienst",
"veh_class_military": "Militair",
"veh_class_commercial": "Commercieel",
"veh_class_train": "Trein",
"veh_class_open_wheel": "Open wiel"
}

134
locales/no.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "Discorden din ble ikke funnet, bli med i vår Discord her: %s.",
"connecting": "Kobler til...",
"identifier_not_found": "Din %s ble ikke funnet.",
"deposit": "Innskudd",
"withdraw": "Ta ut",
"vehicle_ownership_transfered": "Eierskap overført",
"vehicle_ownership_transfered2": "Eierskapet til kjøretøyet %s har blitt overført.",
"vehicle_ownership_received": "Eierskap mottatt",
"vehicle_ownership_received2": "Mottatt eierskap til kjøretøyet %s.",
"keys_shared": "Nøkler delt",
"keys_shared2": "Du har delt kjøretøynøkler med %s.",
"keys_received": "Nøkler mottatt",
"keys_received2": "Du har mottatt kjøretøynøkler fra %s.",
"no_signal": "Ingen signal",
"veh_key_disabled": "Kjøretøynøkkel deaktivert.",
"veh_key_disabled2": "Denne nøkkelen er deaktivert og kan ikke brukes lenger.",
"veh_far_away": "Kjøretøyet er for langt unna.",
"veh_locked": "LÅST",
"veh_locked2": "Kjøretøyet ditt er nå låst.",
"veh_unlocked": "ÅPEN",
"veh_unlocked2": "Kjøretøyet ditt er nå åpnet.",
"garage": "Garasje",
"no_owned_veh_nearby": "Ingen eide kjøretøy i nærheten.",
"veh_stored_in_garage": "Kjøretøy lagret i garasje.",
"impound_reclaim": "Hent kjøretøy fra beslag",
"impound": "Beslag",
"impound_not_enough": "Pris for å hente er %d kr, du har ikke nok!",
"impound_paid": "Betalt %d kr for å hente kjøretøy!",
"success": "suksess",
"staff_action": "Ansatt handling",
"staff_money_removed": "Fjernet %d kr (%s) fra %s",
"user_money_removed": "Fjernet %d kr fra %s",
"staff_money_added": "Lagt til %d kr (%s) til %s",
"user_money_added": "Lagt til %d kr til %s",
"staff_money_set": "Stilt inn %s sin (%s) til %d kr",
"user_money_set": "Stilt inn %s til %d kr",
"job_updated_noti": "Jobb oppdatert til %s, rang %s.",
"group_added_noti": "Lagt til gruppe %s, rang %s.",
"group_removed_noti": "Fjernet fra gruppe %s.",
"no_player_found": "Ingen spiller funnet",
"not_enough_cash": "Du har ikke nok kontanter",
"player_not_nearby": "Spiller er ikke i nærheten",
"cant_give_money": "Kunne ikke gi penger",
"money_received": "Penger mottatt",
"money_received2": "Mottatt %d kr kontant",
"money_given": "Penger gitt",
"money_given2": "Du ga %d kr kontant",
"player_in_veh": "Spiller i kjøretøy!",
"perfect": "Perfekt",
"good": "Bra",
"bad": "Dårlig",
"very_bad": "Veldig dårlig",
"veh_make_brand": "Merke",
"veh_model": "Modell",
"veh_plate": "Skilt",
"engine_status": "Motorstatus",
"vehicle": "Kjøretøy",
"not_found": "ikke funnet",
"park_veh": "Parker kjøretøy",
"no_vehs_found": "Ingen kjøretøy funnet",
"vehicle_impound": "Kjøretøy beslag",
"parking_garage": "Parkeringsgarasje",
"impound_w_location": "Beslag (%s)",
"garage_w_location": "Garasje (%s)",
"view_impounded_vehs": "Se beslaglagte kjøretøy",
"view_garage": "Se garasje",
"personal_vehicle": "Personlig kjøretøy",
"keybind_carkey": "Lås/åpne kjøretøy (dobbeltklikk)",
"progress_hotwiring": "Tjuvstart",
"cruise_control": "Cruise control",
"cruise_control_enabled": "Cruise control aktivert",
"cruise_control_disabled": "Cruise control deaktivert",
"cruise_control_toggle": "Bytt cruise control",
"cruise_control_increase": "Cruise control hastighet økt",
"seat_shuffle": "Bytt plass i kjøretøy",
"discord_text": "Spiller: %s som %s %s",
"discord_text_small": "Spiller som: %s %s",
"discord_text_server": "Spiller: %s",
"pause_menu_cash": "Kontanter: %d",
"pause_menu_bank": "Bank: %d",
"veh_color_black": "Svart",
"veh_color_silver": "Sølv",
"veh_color_grey": "Grå",
"veh_color_metal": "Metall",
"veh_color_graphite": "Grafitt",
"veh_color_red": "Rød",
"veh_color_orange": "Oransje",
"veh_color_gold": "Gull",
"veh_color_yellow": "Gul",
"veh_color_green": "Grønn",
"veh_color_blue": "Blå",
"veh_color_bblue": "Lyseblå",
"veh_color_bronze": "Bronse",
"veh_color_lime": "Lime",
"veh_color_champagne": "Champagne",
"veh_color_beige": "Beige",
"veh_color_ivory": "Elfenben",
"veh_color_brown": "Brun",
"veh_color_beechwood": "Bøk",
"veh_color_sand": "Sand",
"veh_color_cream": "Krem",
"veh_color_white": "Hvit",
"veh_color_steel": "Stål",
"veh_color_aluminium": "Aluminium",
"veh_color_chrome": "Krom",
"veh_color_pink": "Rosa",
"veh_color_purple": "Lilla",
"veh_color_tan": "Brunlig",
"veh_color_alloy": "Aluminiumlegering",
"veh_class_compact": "Kompakt",
"veh_class_sedan": "Sedan",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupe",
"veh_class_muscle": "Muscle",
"veh_class_sports_classic": "Sports Classic",
"veh_class_sport": "Sport",
"veh_class_super": "Super",
"veh_class_motorcycle": "Motorsykkel",
"veh_class_off_road": "Terreng",
"veh_class_industrial": "Industriell",
"veh_class_utility": "Nyttig",
"veh_class_van": "Varebil",
"veh_class_cycle": "Sykkel",
"veh_class_boat": "Båt",
"veh_class_helicopter": "Helikopter",
"veh_class_plane": "Fly",
"veh_class_service": "Service",
"veh_class_emergency": "Nød",
"veh_class_military": "Militær",
"veh_class_commercial": "Kommersiell",
"veh_class_train": "Tog",
"veh_class_open_wheel": "Åpen hjul"
}

134
locales/ru.json Normal file
View File

@@ -0,0 +1,134 @@
{
"not_in_discord": "Ваш Discord не найден, присоединяйтесь к нашему Discord здесь: %s.",
"connecting": "Подключение...",
"identifier_not_found": "Ваш %s не найден.",
"deposit": "Внести",
"withdraw": "Снять",
"vehicle_ownership_transfered": "Владение передано",
"vehicle_ownership_transfered2": "Владение транспортным средством %s было передано.",
"vehicle_ownership_received": "Владение получено",
"vehicle_ownership_received2": "Вы получили транспортное средство %s.",
"keys_shared": "Ключи переданы",
"keys_shared2": "Вы передали ключи от транспортного средства %s.",
"keys_received": "Ключи получены",
"keys_received2": "Вы получили ключи от транспортного средства %s.",
"no_signal": "Нет сигнала",
"veh_key_disabled": "Ключ от транспортного средства отключен.",
"veh_key_disabled2": "Этот ключ отключен и больше не может использоваться.",
"veh_far_away": "Транспорт слишком далеко.",
"veh_locked": "ЗАПЕРТО",
"veh_locked2": "Ваш транспорт теперь заперт.",
"veh_unlocked": "ОТКРЫТО",
"veh_unlocked2": "Ваш транспорт теперь открыт.",
"garage": "Гараж",
"no_owned_veh_nearby": "Вблизи не найдено вашего транспортного средства.",
"veh_stored_in_garage": "Транспортное средство помещено в гараж.",
"impound_reclaim": "Забрать транспорт из штрафстоянки",
"impound": "Штрафстоянка",
"impound_not_enough": "Цена за возврат %d₽, у вас недостаточно!",
"impound_paid": "Оплачено %d₽ за возврат транспортного средства!",
"success": "успешно",
"staff_action": "Действие персонала",
"staff_money_removed": "Удалено %d₽ (%s) у %s",
"user_money_removed": "Удалено %d₽ у %s",
"staff_money_added": "Добавлено %d₽ (%s) к %s",
"user_money_added": "Добавлено %d₽ к %s",
"staff_money_set": "Баланс %s (%s) установлен на %d₽",
"user_money_set": "Баланс %s установлен на %d₽",
"job_updated_noti": "Работа обновлена до %s, ранг %s.",
"group_added_noti": "Добавлен в группу %s, ранг %s.",
"group_removed_noti": "Удалён из группы %s.",
"no_player_found": "Игрок не найден",
"not_enough_cash": "У вас недостаточно наличных",
"player_not_nearby": "Игрок не рядом",
"cant_give_money": "Не удалось передать деньги",
"money_received": "Деньги получены",
"money_received2": "Получено %d₽ наличными",
"money_given": "Деньги переданы",
"money_given2": "Вы передали %d₽ наличными",
"player_in_veh": "Игрок в транспортном средстве!",
"perfect": "Идеально",
"good": "Хорошо",
"bad": "Плохо",
"very_bad": "Очень плохо",
"veh_make_brand": "Марка",
"veh_model": "Модель",
"veh_plate": "Номер",
"engine_status": "Состояние двигателя",
"vehicle": "Транспортное средство",
"not_found": "не найдено",
"park_veh": "Припарковать транспорт",
"no_vehs_found": "Транспортные средства не найдены",
"vehicle_impound": "Штрафстоянка",
"parking_garage": "Парковка",
"impound_w_location": "Штрафстоянка (%s)",
"garage_w_location": "Гараж (%s)",
"view_impounded_vehs": "Просмотр транспортных средств на штрафстоянке",
"view_garage": "Просмотр гаража",
"personal_vehicle": "Личный транспорт",
"keybind_carkey": "Открыть/закрыть транспорт (двойной клик)",
"progress_hotwiring": "Вскрытие замка",
"cruise_control": "Круиз-контроль",
"cruise_control_enabled": "Круиз-контроль включен",
"cruise_control_disabled": "Круиз-контроль выключен",
"cruise_control_toggle": "Переключить круиз-контроль",
"cruise_control_increase": "Скорость круиз-контроля увеличена",
"seat_shuffle": "Смена места в транспорте",
"discord_text": "Играет: %s как %s %s",
"discord_text_small": "Играет как: %s %s",
"discord_text_server": "Играет: %s",
"pause_menu_cash": "Наличные: ₽%d",
"pause_menu_bank": "Банк: ₽%d",
"veh_color_black": "Черный",
"veh_color_silver": "Серебристый",
"veh_color_grey": "Серый",
"veh_color_metal": "Металлик",
"veh_color_graphite": "Графит",
"veh_color_red": "Красный",
"veh_color_orange": "Оранжевый",
"veh_color_gold": "Золотой",
"veh_color_yellow": "Желтый",
"veh_color_green": "Зеленый",
"veh_color_blue": "Синий",
"veh_color_bblue": "Ярко-синий",
"veh_color_bronze": "Бронзовый",
"veh_color_lime": "Лаймовый",
"veh_color_champagne": "Шампанское",
"veh_color_beige": "Бежевый",
"veh_color_ivory": "Слоновая кость",
"veh_color_brown": "Коричневый",
"veh_color_beechwood": "Бук",
"veh_color_sand": "Песочный",
"veh_color_cream": "Кремовый",
"veh_color_white": "Белый",
"veh_color_steel": "Стальной",
"veh_color_aluminium": "Алюминий",
"veh_color_chrome": "Хром",
"veh_color_pink": "Розовый",
"veh_color_purple": "Фиолетовый",
"veh_color_tan": "Загар",
"veh_color_alloy": "Легкосплавный",
"veh_class_compact": "Компакт",
"veh_class_sedan": "Седан",
"veh_class_suv": "Внедорожник",
"veh_class_coupe": "Купе",
"veh_class_muscle": "Мускул",
"veh_class_sports_classic": "Спорт Классик",
"veh_class_sport": "Спорт",
"veh_class_super": "Супер",
"veh_class_motorcycle": "Мотоцикл",
"veh_class_off_road": "Внедорожник",
"veh_class_industrial": "Промышленный",
"veh_class_utility": "Утилитарный",
"veh_class_van": "Фургон",
"veh_class_cycle": "Велосипед",
"veh_class_boat": "Лодка",
"veh_class_helicopter": "Вертолет",
"veh_class_plane": "Самолет",
"veh_class_service": "Служебный",
"veh_class_emergency": "Экстренный",
"veh_class_military": "Военный",
"veh_class_commercial": "Коммерческий",
"veh_class_train": "Поезд",
"veh_class_open_wheel": "Открытые колеса"
}

136
locales/sv.json Normal file
View File

@@ -0,0 +1,136 @@
{
"not_in_discord": "Din Discord hittades inte, gå med i vår Discord här: %s.",
"connecting": "Ansluter...",
"identifier_not_found": "Din %s hittades inte.",
"deposit": "Sätt in",
"withdraw": "Ta ut",
"vehicle_ownership_transfered": "Ägarskap överfört",
"vehicle_ownership_transfered2": "Fordonets ägarskap för %s har överförts.",
"vehicle_ownership_received": "Ägarskap mottaget",
"vehicle_ownership_received2": "Mottagit fordonets ägarskap för %s.",
"keys_shared": "Nycklar delade",
"keys_shared2": "Du har delat fordonsnycklar med %s.",
"keys_received": "Nycklar mottagna",
"keys_received2": "Du har mottagit fordonsnycklar från %s.",
"no_signal": "Ingen signal",
"veh_key_disabled": "Fordonsnyckel inaktiverad.",
"veh_key_disabled2": "Denna fordonsnyckel har inaktiverats och kan inte längre användas.",
"veh_far_away": "Fordonet är för långt borta.",
"veh_locked": "LÅST",
"veh_locked2": "Ditt fordon är nu låst.",
"veh_unlocked": "OLÅST",
"veh_unlocked2": "Ditt fordon är nu olåst.",
"garage": "Garage",
"no_owned_veh_nearby": "Inget ägt fordon hittades i närheten.",
"veh_stored_in_garage": "Fordonet har placerats i garaget.",
"impound_reclaim": "Återtagning av beslagtaget fordon",
"impound": "Beslag",
"impound_not_enough": "Priset för att återta är %d kr, du har inte tillräckligt!",
"impound_paid": "Betalade %d kr för att återta fordonet!",
"success": "framgång",
"staff_action": "Personalåtgärd",
"staff_money_removed": "Tog bort %d kr (%s) från %s",
"user_money_removed": "tog bort %d kr från %s",
"staff_money_added": "Lade till %d kr (%s) till %s",
"user_money_added": "lade till %d kr till %s",
"staff_money_set": "Satte %s:s (%s) till %d kr",
"user_money_set": "satte %s till %d kr",
"job_updated_noti": "Jobb uppdaterat till %s, rang %s.",
"group_added_noti": "Tillagd i gruppen %s, rang %s.",
"group_removed_noti": "Borttagen från gruppen %s.",
"no_player_found": "Ingen spelare hittades",
"not_enough_cash": "Du har inte tillräckligt med kontanter",
"player_not_nearby": "Spelaren är inte i närheten",
"cant_give_money": "Kunde inte ge pengar",
"money_received": "Pengar mottagna",
"money_received2": "Mottog %d kr kontant",
"money_given": "Pengar givna",
"money_given2": "Du gav %d kr kontant",
"player_in_veh": "Spelaren är i fordon!",
"perfect": "Perfekt",
"good": "Bra",
"bad": "Dålig",
"very_bad": "Mycket dålig",
"veh_make_brand": "Märke",
"veh_model": "Modell",
"veh_plate": "Registreringsskylt",
"engine_status": "Motorstatus",
"vehicle": "Fordon",
"not_found": "hittades inte",
"park_veh": "Parkera fordon",
"no_vehs_found": "Inga fordon hittades",
"vehicle_impound": "Beslagtagna fordon",
"parking_garage": "Parkeringsgarage",
"impound_w_location": "Beslag (%s)",
"garage_w_location": "Parkeringsgarage (%s)",
"view_impounded_vehs": "Visa beslagtagna fordon",
"view_garage": "Visa garage",
"personal_vehicle": "Personligt fordon",
"group_vehicle": "Fordon",
"keybind_carkey": "Lås/öppna fordon (dubbelklick)",
"progress_hotwiring": "Kopplar om",
"cruise_control": "Farthållare",
"cruise_control_enabled": "Fordonets farthållare aktiverad",
"cruise_control_disabled": "Fordonets farthållare avaktiverad",
"cruise_control_toggle": "Växla fordonets farthållare",
"cruise_control_increase": "Ökade farthållarens hastighet",
"seat_shuffle": "Byt plats i fordonet",
"discord_text": "Spelar: %s som %s %s",
"discord_text_small": "Spelar som: %s %s",
"discord_text_server": "Spelar: %s",
"pause_menu_cash": "Kontanter: %d",
"pause_menu_bank": "Bank: %d",
"veh_color_black": "Svart",
"veh_color_silver": "Silver",
"veh_color_grey": "Grå",
"veh_color_metal": "Metall",
"veh_color_graphite": "Grafit",
"veh_color_red": "Röd",
"veh_color_orange": "Orange",
"veh_color_gold": "Guld",
"veh_color_yellow": "Gul",
"veh_color_green": "Grön",
"veh_color_blue": "Blå",
"veh_color_bblue": "Ljusblå",
"veh_color_bronze": "Brons",
"veh_color_lime": "Lime",
"veh_color_champagne": "Champagne",
"veh_color_beige": "Beige",
"veh_color_ivory": "Elfenben",
"veh_color_brown": "Brun",
"veh_color_beechwood": "Bokträ",
"veh_color_sand": "Sand",
"veh_color_cream": "Kräm",
"veh_color_white": "Vit",
"veh_color_steel": "Stål",
"veh_color_aluminium": "Aluminium",
"veh_color_chrome": "Krom",
"veh_color_pink": "Rosa",
"veh_color_purple": "Lila",
"veh_color_tan": "Ljusbrun",
"veh_color_alloy": "Legering",
"veh_class_compact": "Kompakt",
"veh_class_sedan": "Sedan",
"veh_class_suv": "SUV",
"veh_class_coupe": "Coupé",
"veh_class_muscle": "Muskelbil",
"veh_class_sports_classic": "Klassisk sportbil",
"veh_class_sport": "Sport",
"veh_class_super": "Superbil",
"veh_class_motorcycle": "Motorcykel",
"veh_class_off_road": "Terräng",
"veh_class_industrial": "Industri",
"veh_class_utility": "Nyttolast",
"veh_class_van": "Skåpbil",
"veh_class_cycle": "Cykel",
"veh_class_boat": "Båt",
"veh_class_helicopter": "Helikopter",
"veh_class_plane": "Flygplan",
"veh_class_service": "Service",
"veh_class_emergency": "Utryckning",
"veh_class_military": "Militär",
"veh_class_commercial": "Kommersiell",
"veh_class_train": "Tåg",
"veh_class_open_wheel": "Öppen hjulbil"
}

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,483 @@
NDCore.Functions.AddCommand("setmoney", "Admin command, manage player money.", function(source, args, rawCommand) local moneyTypes = {"bank", "cash"}
if not NDCore.Functions.IsPlayerAdmin(source) then return end local moneyActions = {
remove = function(player, account, amount)
local target = tonumber(args[1]) player.deductMoney(account, amount, locale("staff_action"))
local action = args[2] return locale("staff_money_removed", amount, account, player.name), locale("user_money_removed", amount, account)
local moneyType = args[3]:lower() end,
local amount = tonumber(args[4]) add = function(player, account, amount)
player.addMoney(account, amount, locale("staff_action"))
if not target or GetPlayerPing(target) == 0 then return end return locale("staff_money_added", amount, account, player.name), locale("user_money_added", amount, account)
if action ~= "remove" and action ~= "add" and action ~= "set" then return end end,
if moneyType ~= "bank" and moneyType ~= "cash" then return end set = function(player, account, amount)
player.setData(account, amount, locale("staff_action"))
if action == "remove" then return locale("staff_money_set", player.name, account, amount), locale("user_money_set", account, amount)
if not amount or amount < 1 then return end
NDCore.Functions.DeductMoney(amount, target, moneyType, "Server staff action.")
elseif action == "add" then
if not amount or amount < 1 then return end
NDCore.Functions.AddMoney(amount, target, moneyType, "Server staff action.")
elseif action == "set" then
local character = NDCore.Functions.GetPlayer(target)
NDCore.Functions.SetPlayerData(character.id, moneyType, amount, "Server staff action.")
end end
end, true, { }
{ name="player", help="Player server id" },
{ name="action", help="remove/add/set" },
{ name="type", help="bank/cash" },
{ name="amount" }
})
NDCore.Functions.AddCommand("setjob", "Admin command, set player job.", function(source, args, rawCommand) lib.addCommand("setmoney", {
if not NDCore.Functions.IsPlayerAdmin(source) then help = "Admin command, set a players money.",
return { restricted = "group.admin",
color = {255, 0, 0}, params = {
args = {"Error", "you don't have access to this command."} {
name = "target",
type = "playerId",
help = "Target player's server id"
},
{
name = "action",
type = "string",
help = "remove/add/set"
},
{
name = "type",
type = "string",
help = "bank/cash"
},
{
name = "amount",
type = "number"
} }
}
}, function(source, args, raw)
local action = moneyActions[args.action]
local moneyType = args.type:lower()
if not action or not lib.table.contains(moneyTypes, moneyType) then return end
local player = NDCore.getPlayer(args.target)
if not player then return end
local staffMessage, userMessage = action(player, moneyType, args.amount)
player.notify({
title = locale("staff_action"),
description = userMessage,
type = "inform",
duration = 10000
})
if not source or source == 0 then return end
TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {locale("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 = locale("staff_action"),
description = locale("job_updated_noti", jobInfo.label, jobInfo.rankName),
type = "inform",
duration = 10000
})
if not source or source == 0 then return end
TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {locale("staff_action"), locale("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 = locale("staff_action"),
description = locale("group_added_noti", 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 = locale("staff_action"),
description = locale("group_removed_noti", groupInfo.label),
type = "inform",
duration = 10000
})
else
return
end end
local target = tonumber(args[1]) if not source or source == 0 then return end
if not target or GetPlayerPing(target) == 0 then TriggerClientEvent("chat:addMessage", source, {
return { color = {50, 100, 235},
color = {255, 0, 0}, multiline = true,
args = {"Error", "target player not found."} args = {locale("staff_action"), locale("success")}
})
end)
lib.addCommand("skin", {
help = "Admin command, show character clothing menu for player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
} }
}
}, function(source, args, raw)
TriggerClientEvent("ND:clothingMenu", args.target)
end)
lib.addCommand("character", {
help = "Admin command, show character selection menu for player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
TriggerClientEvent("ND:characterMenu", args.target)
end)
lib.addCommand("pay", {
help = "give money to nearby player.",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
},
{
name = "amount",
type = "number",
help = "Amount of cash to give"
}
}
}, function(source, args, raw)
if not source or source == 0 then return end
local player = NDCore.getPlayer(source)
local targetPlayer = NDCore.getPlayer(args.target)
if not player or not targetPlayer then
return player.notify({
title = "Error",
description = locale("no_player_found"),
type = "error"
})
end end
local job = args[2] if player.cash < args.amount then
if not job then return player.notify({
return { title = "Error",
color = {255, 0, 0}, description = locale("not_enough_cash"),
args = {"Error", "job required."} type = "error"
} })
end end
local character = NDCore.Functions.GetPlayer(target) local playerCoords = GetEntityCoords(GetPlayerPed(player.source))
NDCore.Functions.SetPlayerJob(character.id, job, args[3]) local targetCoords = GetEntityCoords(GetPlayerPed(targetPlayer.source))
return { if #(playerCoords-targetCoords) > 2.0 then
color = {0, 255, 0}, return player.notify({
args = {"Success", GetPlayerName(target) .. " job set to " .. job .. (args[3] and " rank " .. args[3] or " rank 1.")} title = "Error",
} description = locale("player_not_nearby"),
end, false, { type = "error"
{ name="player", help="Player server id" }, })
{ name="job name" },
{ name="rank", help="This should be a number, default value is 1." }
})
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."}
}
end end
local group = args[3] local success = player.deductMoney("cash", args.amount) and targetPlayer.addMoney("cash", args.amount)
if not group then if not success then
return { return player.notify({
color = {255, 0, 0}, title = locale("cant_give_money"),
args = {"Error", "group required."} type = "error",
} duration = 5000
})
end end
local character = NDCore.Functions.GetPlayer(target) targetPlayer.notify({
if args[2] == "remove" then title = locale("money_received"),
NDCore.Functions.RemovePlayerFromGroup(character.id, group) description = locale("money_received2", args.amount),
return { type = "inform",
color = {0, 255, 0}, duration = 5000
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) player.notify({
local amount = args[1] title = locale("money_given"),
if not amount or amount == 0 then return end description = locale("money_given2", args.amount),
NDCore.Functions.GiveCashToNearbyPlayer(source, amount) type = "inform",
return { duration = 5000
color = {0, 255, 0}, })
args = {"Success", amount .. " paid."} end)
lib.addCommand("unlock", {
help = "Admin Command, force unlock a vehicle.",
restricted = "group.admin",
}, function(source, args, raw)
local ped = GetPlayerPed(source)
local coords = GetEntityCoords(ped)
local veh = lib.getClosestVehicle(coords, 2.5, true)
if not veh then return end
local state = Entity(veh).state
state.hotwired = true
state.locked = false
end)
lib.addCommand("revive", {
help = "Admin command, revive a player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
} }
end, true, { }
{ name="Amount" }, }, function(source, args, raw)
}) local player = NDCore.getPlayer(args.target)
if not player then return end
player.revive()
end)
lib.addCommand("dv", {
help = "Admin command, delete vehicles within the range or the closest.",
restricted = "group.admin",
params = {
{
name = "range",
type = "number",
help = "Range to delete vehicles in",
optional = true
}
}
}, function(source, args, raw)
local ped = GetPlayerPed(source)
local coords = GetEntityCoords(ped)
local count = 0
local message = nil
local veh = lib.getClosestVehicle(coords, 3.0)
if args.range then
local vehicles = lib.getNearbyVehicles(coords, args.range)
count = #vehicles
for i=1, count do
local veh = vehicles[i]
DeleteEntity(veh.vehicle)
end
elseif veh then
DeleteEntity(veh)
count = 1
end
local messageLabel = ("%s [dv]"):format(locale("staff_action"))
if count == 0 then
message = {messageLabel, "no vehicles found"}
elseif count == 1 then
message = {messageLabel, "deleted 1 vehicle"}
else
message = {messageLabel, ("deleted %d vehicles"):format(count)}
end
TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = message
})
end)
lib.addCommand("goto", {
help = "Admin command, teleport to a player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
if args.target == source then return end
local target = GetPlayerPed(args.target)
local coords = GetEntityCoords(target)
local ped = GetPlayerPed(source)
SetEntityCoords(ped, coords.x, coords.y, coords.z)
end)
lib.addCommand("bring", {
help = "Admin command, teleport a player to you.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
if args.target == source then return end
local ped = GetPlayerPed(source)
local coords = GetEntityCoords(ped)
local targetPed = GetPlayerPed(args.target)
SetEntityCoords(targetPed, coords.x, coords.y, coords.z)
end)
lib.addCommand("freeze", {
help = "Admin command, freeze a player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
local ped = GetPlayerPed(args.target)
FreezeEntityPosition(ped, true)
end)
lib.addCommand("unfreeze", {
help = "Admin command, unfreeze a player.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
local ped = GetPlayerPed(args.target)
FreezeEntityPosition(ped, false)
end)
lib.addCommand("vehicle", {
help = "Admin command, spawn a vehicle.",
restricted = "group.admin",
params = {
{
name = "model",
type = "string",
help = "Name of the vehicle to spawn"
}
}
}, function(source, args, raw)
local player = NDCore.getPlayer(source)
if not player then return end
local ped = GetPlayerPed(source)
local coords = GetEntityCoords(ped)
local heading = GetEntityHeading(ped)
local info = NDCore.createVehicle({
owner = player.id,
coords = coords,
heading = heading,
model = GetHashKey(args.model)
})
local veh = info.entity
for i=1, 10 do
if GetPedInVehicleSeat(veh, -1) ~= ped then
SetPedIntoVehicle(ped, veh, -1)
else
break
end
Wait(100)
end
end)
lib.addCommand("claim-veh", {
help = "Admin command, add a copy of the vehicle you're inside to players garage.",
restricted = "group.admin",
params = {
{
name = "target",
type = "playerId",
help = "Target player's server id"
}
}
}, function(source, args, raw)
local player = NDCore.getPlayer(source)
if not player then return end
local targetPlayer = NDCore.getPlayer(args.target)
if not targetPlayer then
return player.notify({
title = "Player not found!",
description = "Target player not found, make sure they select a charcter!",
type = "error"
})
end
local properties = lib.callback.await("ND_Vehicles:getPropsFromCurrentVeh", source)
if not properties then
return player.notify({
title = "Vehicle data not found!",
description = "The vehicle properties data was not found!",
type = "error"
})
end
NDCore.setVehicleOwned(targetPlayer.id, properties, true)
player.notify({
title = "Vehicle added!",
description = ("The vehicle has now been added to %s's garage!"):format(targetPlayer.name),
type = "success"
})
end)

View File

@@ -1,115 +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
NDCore.Functions.UpdateLastLocation(character.id, character.lastLocation)
end
TriggerEvent("ND:characterUnloaded", player, character)
character = nil
end)
-- Get player discord info on join.
AddEventHandler("playerJoining", function()
local src = source
local discordUserId = NDCore.Functions.GetPlayerIdentifierFromType("discord", src):gsub("discord:", "")
local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId)
NDCore.PlayersDiscordInfo[src] = discordInfo
end)
AddEventHandler("onResourceStart", function(resourceName)
if (GetCurrentResourceName() ~= resourceName) then
return
end
Wait(1000)
if not next(NDCore.PlayersDiscordInfo) then
for _, playerId in ipairs(GetPlayers()) do
local discordUserId = NDCore.Functions.GetPlayerIdentifierFromType("discord", playerId):gsub("discord:", "")
local discordInfo = NDCore.Functions.GetUserDiscordInfo(discordUserId)
NDCore.PlayersDiscordInfo[tonumber(playerId)] = discordInfo
end
end
end)

View File

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

60
server/groupadmin.lua Normal file
View File

@@ -0,0 +1,60 @@
lib.addCommand("groupadmin", {
help = "Open the group management admin panel.",
restricted = "group.admin"
}, function(source, args, raw)
if not source or source == 0 then return end
local groups = NDCore.getAllGroups()
TriggerClientEvent("ND:openGroupAdmin", source, groups)
end)
lib.callback.register("ND_Core:groups:create", function(source, data)
local player = NDCore.getPlayer(source)
if not player or not player.groups["admin"] then return { success = false } end
if not data or not data.name or not data.label then
return { success = false }
end
local success, err = NDCore.createNewGroup(data.name, data.label, data.isJob, data.ranks)
if not success then
return { success = false, error = err }
end
return { success = true, groups = NDCore.getAllGroups() }
end)
lib.callback.register("ND_Core:groups:edit", function(source, data)
local player = NDCore.getPlayer(source)
if not player or not player.groups["admin"] then return { success = false } end
if not data or not data.name then
return { success = false }
end
local success, err = NDCore.editGroupData(data.name, {
label = data.label,
isJob = data.isJob,
ranks = data.ranks
})
if not success then
return { success = false, error = err }
end
return { success = true, groups = NDCore.getAllGroups() }
end)
lib.callback.register("ND_Core:groups:delete", function(source, data)
local player = NDCore.getPlayer(source)
if not player or not player.groups["admin"] then return { success = false } end
if not data or not data.name then
return { success = false }
end
local success = NDCore.deleteGroup(data.name)
if not success then
return { success = false }
end
return { success = true, groups = NDCore.getAllGroups() }
end)

233
server/groups.lua Normal file
View File

@@ -0,0 +1,233 @@
-- Cache of all groups loaded from DB: Config.groups[name] = { label, isJob, ranks = { [weight] = label }, ranksData = { [weight] = { id, label, weight, isBoss } } }
local function loadGroupsFromDB()
local groups = {}
local rows = MySQL.query.await("SELECT * FROM nd_groups")
if not rows then return groups end
for _, row in ipairs(rows) do
groups[row.name] = {
label = row.label,
isJob = row.isJob,
ranks = {},
ranksData = {}
}
end
local ranks = MySQL.query.await("SELECT * FROM nd_group_ranks ORDER BY weight ASC")
if ranks then
for _, rank in ipairs(ranks) do
local g = groups[rank.group_name]
if g then
g.ranks[rank.weight] = rank.label
g.ranksData[rank.weight] = {
id = rank.id,
label = rank.label,
weight = rank.weight,
isBoss = rank.isBoss
}
end
end
end
return groups
end
--- Check if a group exists in the database/cache.
---@param name string
---@return boolean
function NDCore.doesGroupExist(name)
return Config.groups[name] ~= nil
end
--- Get group data from cache.
---@param name string
---@return table|nil
function NDCore.getGroupData(name)
return Config.groups[name]
end
--- Create a new group in the database and cache.
---@param name string Unique key
---@param label string Display name
---@param isJob boolean
---@param ranks table Array of { label = string, weight = number, isBoss = boolean }
---@return boolean success
---@return string|nil error
function NDCore.createNewGroup(name, label, isJob, ranks)
if not name or name == "" then return false, "name is required" end
if Config.groups[name] then return false, "group already exists" end
MySQL.insert.await("INSERT INTO nd_groups (`name`, `label`, `isJob`) VALUES (?, ?, ?)", {
name, label or name, isJob and 1 or 0
})
Config.groups[name] = {
label = label or name,
isJob = isJob or false,
ranks = {},
ranksData = {}
}
if ranks and #ranks > 0 then
for _, rank in ipairs(ranks) do
local id = MySQL.insert.await("INSERT INTO nd_group_ranks (`group_name`, `label`, `weight`, `isBoss`) VALUES (?, ?, ?, ?)", {
name, rank.label, rank.weight, rank.isBoss and 1 or 0
})
Config.groups[name].ranks[rank.weight] = rank.label
Config.groups[name].ranksData[rank.weight] = {
id = id,
label = rank.label,
weight = rank.weight,
isBoss = rank.isBoss or false
}
end
end
NDCore.syncGroupsToClients()
return true
end
--- Edit an existing group in the database and cache.
---@param name string Group key
---@param data table { label?, isJob?, ranks? }
---@return boolean success
---@return string|nil error
function NDCore.editGroupData(name, data)
if not name or not Config.groups[name] then return false, "group does not exist" end
if data.label ~= nil or data.isJob ~= nil then
local label = data.label or Config.groups[name].label
local isJob = data.isJob ~= nil and data.isJob
MySQL.update.await("UPDATE nd_groups SET `label` = ?, `isJob` = ? WHERE `name` = ?", {
label, isJob and 1 or 0, name
})
Config.groups[name].label = label
Config.groups[name].isJob = isJob
end
if data.ranks then
-- Delete all old ranks and replace with new set
MySQL.update.await("DELETE FROM nd_group_ranks WHERE `group_name` = ?", { name })
Config.groups[name].ranks = {}
Config.groups[name].ranksData = {}
for _, rank in ipairs(data.ranks) do
local id = MySQL.insert.await("INSERT INTO nd_group_ranks (`group_name`, `label`, `weight`, `isBoss`) VALUES (?, ?, ?, ?)", {
name, rank.label, rank.weight, rank.isBoss and 1 or 0
})
Config.groups[name].ranks[rank.weight] = rank.label
Config.groups[name].ranksData[rank.weight] = {
id = id,
label = rank.label,
weight = rank.weight,
isBoss = rank.isBoss or false
}
end
end
NDCore.syncGroupsToClients()
return true
end
--- Delete a group from the database and cache.
---@param name string
---@return boolean
function NDCore.deleteGroup(name)
if not name or not Config.groups[name] then return false end
MySQL.update.await("DELETE FROM nd_groups WHERE `name` = ?", { name })
Config.groups[name] = nil
NDCore.syncGroupsToClients()
return true
end
--- Get all groups formatted for UI or external use.
---@return table
function NDCore.getAllGroups()
local result = {}
for name, group in pairs(Config.groups) do
local ranks = {}
for weight, rank in pairs(group.ranksData) do
ranks[#ranks+1] = {
id = rank.id,
label = rank.label,
weight = rank.weight,
isBoss = rank.isBoss
}
end
table.sort(ranks, function(a, b) return a.weight < b.weight end)
result[#result+1] = {
name = name,
label = group.label,
isJob = group.isJob,
ranks = ranks
}
end
table.sort(result, function(a, b) return a.name < b.name end)
return result
end
--- Sync groups convar to all clients after changes.
function NDCore.syncGroupsToClients()
-- Build a simplified map for the convar (backwards compatible format)
local simplified = {}
for name, group in pairs(Config.groups) do
local rankLabels = {}
local sortedWeights = {}
for weight in pairs(group.ranks) do
sortedWeights[#sortedWeights+1] = weight
end
table.sort(sortedWeights)
for _, weight in ipairs(sortedWeights) do
rankLabels[#rankLabels+1] = group.ranks[weight]
end
simplified[name] = {
label = group.label,
isJob = group.isJob,
ranks = rankLabels
}
end
SetConvarReplicated("core:groups", json.encode(simplified))
TriggerClientEvent("ND:groupsUpdated", -1, simplified)
end
--- Load groups from DB into Config.groups. Called at startup.
function NDCore.loadGroups()
Config.groups = loadGroupsFromDB()
NDCore.syncGroupsToClients()
end
--- Seed the database from the old JSON config if tables are empty.
function NDCore.seedGroupsFromJson()
local count = MySQL.scalar.await("SELECT COUNT(*) FROM nd_groups")
if count and count > 0 then return false end
local groupsJson = lib.loadJson("_config.groups") or json.decode(GetConvar("core:groups", "[]"))
if not groupsJson then return false end
for name, group in pairs(groupsJson) do
MySQL.insert.await("INSERT INTO nd_groups (`name`, `label`, `isJob`) VALUES (?, ?, ?)", {
name, group.label or name, (group.isJob and 1 or 0)
})
if group.ranks then
for i, rankLabel in ipairs(group.ranks) do
local isBoss = false
if group.minimumBossRank and i >= group.minimumBossRank then
isBoss = true
end
MySQL.insert.await("INSERT INTO nd_group_ranks (`group_name`, `label`, `weight`, `isBoss`) VALUES (?, ?, ?, ?)", {
name, rankLabel, i, isBoss and 1 or 0
})
end
end
end
return true
end
for name, func in pairs(NDCore) do
if type(func) == "function" then
exports(name, func)
end
end

View File

@@ -1,35 +1,269 @@
-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 lib.locale()
NDCore = {} NDCore = {}
NDCore.Players = {} NDCore.players = {}
NDCore.Functions = {} PlayersInfo = {}
NDCore.Commands = {} local resourceName = GetCurrentResourceName()
NDCore.PlayersDiscordInfo = {} local tempPlayersInfo = {}
NDCore.Config = config -- Groups are now loaded from the database in MySQL.ready via NDCore.loadGroups()
function GetCoreObject() Config = {
return NDCore serverName = GetConvar("core:serverName", "Unconfigured ND-Core Server"),
discordInvite = GetConvar("core:discordInvite", "https://discord.gg/Z9Mxu72zZ6"),
discordMemeberRequired = GetConvarInt("core:discordMemeberRequired", 1) == 1,
discordAppId = GetConvar("core:discordAppId", "858146067018416128"),
discordAsset = GetConvar("core:discordAsset", "andyyy"),
discordAssetSmall = GetConvar("core:discordAssetSmall", "andyyy"),
discordActionText = GetConvar("core:discordActionText", "DISCORD"),
discordActionLink = GetConvar("discordActionLink", "https://discord.gg/Z9Mxu72zZ6"),
discordActionText2 = GetConvar("core:discordActionText2", "STORE"),
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
characterIdentifier = GetConvar("core:characterIdentifier", "license"),
selectIdentifiers = json.decode(GetConvar("core:selectIdentifiers", '["discord", "license", "license2", "fivem"]')),
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 = {},
admins = json.decode(GetConvar("core:admins", "[]")),
adminDiscordRoles = json.decode(GetConvar("core:adminDiscordRoles", "[]")),
groupRoles = json.decode(GetConvar("core:groupRoles", "[]")),
multiCharacter = false,
compatibility = json.decode(GetConvar("core:compatibility", "[]")),
sv_lan = GetConvar("sv_lan", "false") == "true",
platePattern = GetConvar("core:platePattern", "11AAA111")
}
SetConvarServerInfo("Discord", Config.discordInvite)
SetConvarServerInfo("NDCore", GetResourceMetadata(resourceName, "version", 0) or "invalid")
SetConvarReplicated("inventory:framework", "nd")
lib.versionCheck('ND-Framework/ND_Core')
local function getIdentifierList(src)
local list = {}
for i=0, GetNumPlayerIdentifiers(src) do
local identifier = GetPlayerIdentifier(src, i)
if identifier then
local colon = identifier:find(":")
local identifierType = identifier:sub(1, colon-1)
list[identifierType] = identifier
end
end
if Config.sv_lan then
list[Config.characterIdentifier] = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
end
return list
end end
CreateThread(function() AddEventHandler("playerJoining", function(oldId)
while true do local src = source
Wait(30000) local oldTempId = tonumber(oldId)
for player, playerInfo in pairs(NDCore.Players) do PlayersInfo[src] = tempPlayersInfo[oldTempId]
local ped = GetPlayerPed(player) tempPlayersInfo[oldTempId] = nil
if DoesEntityExist(ped) then
local lastLocation = GetEntityCoords(ped) if Config.sv_lan then
playerInfo.lastLocation = {x = lastLocation.x, y = lastLocation.y, z = lastLocation.z} lib.addPrincipal(("player.%s"):format(src), "group.admin")
TriggerClientEvent("ND:updateLastLocation", player, playerInfo.lastLocation) end
local identifiers = PlayersInfo[src] and PlayersInfo[src].identifiers or getIdentifierList(src)
local whereParts = {}
local params = {}
for identifierType, identifier in pairs(identifiers) do
local isSelected = false
for i=1, #Config.selectIdentifiers do
local selectedType = Config.selectIdentifiers[i]
if identifierType == selectedType then
isSelected = true
break
end end
end end
if isSelected then
local columnName = "id_" .. identifierType
local cleanId = identifier:gsub("^[^:]*:", "")
table.insert(whereParts, columnName .. " = ?")
table.insert(params, cleanId)
end
end
local user = nil
if #whereParts > 0 then
local query = "SELECT user_id FROM nd_users WHERE " .. table.concat(whereParts, " OR ") .. " LIMIT 1"
user = MySQL.query.await(query, params)
end
if user and user[1] then
PlayersInfo[src].userId = user[1].user_id
local updateParts = {}
local updateParams = {}
for identifierType, identifier in pairs(identifiers) do
local columnName = "id_" .. identifierType
local cleanId = identifier:gsub("^[^:]*:", "")
table.insert(updateParts, columnName .. " = ?")
table.insert(updateParams, cleanId)
end
if #updateParts > 0 then
table.insert(updateParams, user[1].user_id)
local updateQuery = "UPDATE nd_users SET " .. table.concat(updateParts, ", ") .. " WHERE user_id = ?"
MySQL.update.await(updateQuery, updateParams)
end
else
local columns = {}
local values = {}
local insertParams = {}
for identifierType, identifier in pairs(identifiers) do
local columnName = "id_" .. identifierType
local cleanId = identifier:gsub("^[^:]*:", "")
table.insert(columns, columnName)
table.insert(values, "?")
table.insert(insertParams, cleanId)
end
local insertQuery = "INSERT INTO nd_users (" .. table.concat(columns, ", ") .. ") VALUES (" .. table.concat(values, ", ") .. ")"
local user_id = MySQL.insert.await(insertQuery, insertParams)
PlayersInfo[src].userId = user_id
end
if Config.multiCharacter then return end
Wait(3000)
local characters = NDCore.fetchAllCharacters(src)
local id = next(characters)
if id then
return NDCore.setActiveCharacter(src, id)
end
local player = NDCore.newCharacter(src, {
firstname = GetPlayerName(src),
lastname = "",
dob = "",
gender = ""
})
NDCore.setActiveCharacter(src, player.id)
end)
local function checkDiscordIdentifier(identifiers)
if Config.discordBotToken == "false" or Config.discordGuildId == "false" then return end
local discordIdentifier = identifiers["discord"]
if not discordIdentifier then return end
return NDCore.getDiscordInfo(discordIdentifier:gsub("discord:", ""))
end
AddEventHandler("onResourceStart", function(name)
if name ~= resourceName then return end
for _, playerId in ipairs(GetPlayers()) do
local src = tonumber(playerId)
local identifiers = getIdentifierList(src)
PlayersInfo[src] = {
identifiers = identifiers,
discord = checkDiscordIdentifier(identifiers) or {}
}
Wait(65)
end end
end) end)
isResourceStarted("ox_inventory", function(started) AddEventHandler("playerConnecting", function(name, setKickReason, deferrals)
if not started then return end local tempSrc = source
SetConvarReplicated("inventory:framework", "nd") local identifiers = getIdentifierList(tempSrc)
local mainIdentifier = identifiers[Config.characterIdentifier]
local discordInfo = nil
deferrals.defer()
Wait(0)
if mainIdentifier and Config.discordBotToken ~= "false" and Config.discordGuildId ~= "false" then
discordInfo = checkDiscordIdentifier(identifiers)
if not discordInfo and Config.discordMemeberRequired and not Config.sv_lan then
deferrals.done(locale("not_in_discord", Config.discordInvite))
Wait(0)
end
end
deferrals.update(locale("connecting"))
Wait(0)
if Config.sv_lan then
tempPlayersInfo[tempSrc] = {
identifiers = {
[Config.characterIdentifier] = "sv_lan"
},
discord = discordInfo
}
deferrals.done()
return
end
if mainIdentifier then
tempPlayersInfo[tempSrc] = {
identifiers = identifiers,
discord = discordInfo
}
deferrals.done()
else
deferrals.done(locale("identifier_not_found", Config.characterIdentifier))
Wait(0)
end
end) end)
for _, roleid in pairs(config.adminRoles) do AddEventHandler("playerDropped", function()
ExecuteCommand("add_principal identifier.discord:" .. roleid .. " group.admin") local src = source
end local char = NDCore.players[src]
if char then char.unload() end
PlayersInfo[src] = nil
end)
AddEventHandler("onResourceStop", function(name)
if name ~= resourceName then return end
for _, player in pairs(NDCore.players) do
player.unload()
Wait(10)
end
end)
RegisterNetEvent("ND:playerEliminated", function(info)
local src = source
local player = NDCore.getPlayer(src)
if not player then return end
player.setMetadata({
dead = true,
deathInfo = info
})
end)
RegisterNetEvent("ND:updateClothing", function(clothing)
local src = source
local player = NDCore.getPlayer(src)
if not player or not clothing or type(clothing) ~= "table" then return end
player.setMetadata("clothing", clothing)
end)
MySQL.ready(function()
Wait(100)
NDCore.loadSQL({
"database/users.sql",
"database/characters.sql",
"database/vehicles.sql",
"database/moneylogs.sql",
"database/groups.sql",
"database/group_ranks.sql"
}, resourceName)
Wait(200)
NDCore.seedGroupsFromJson()
NDCore.loadGroups()
end)
-- Hourly cron, purge soft-deleted characters older than 30 days.
lib.cron.new("0 * * * *", function()
MySQL.update.await("DELETE FROM nd_characters WHERE deleted_at IS NOT NULL AND deleted_at < DATE_SUB(NOW(), INTERVAL 30 DAY)")
end)

720
server/player.lua Normal file
View File

@@ -0,0 +1,720 @@
local avoidSavingLastLocations = {}
local moneyLogs = {}
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 logMoney(charId, action, account, amount, reason, trace)
if not trace or (action == "set" and not reason) then return end
local traceText = trace:match("%((.-)%)")
traceText = traceText and traceText:gsub("%^%d", "") or traceText or ""
table.insert(moneyLogs, { charId, action, account, amount, reason or "[NO REASON GIVEN]", traceText })
end
local function createCharacterTable(info)
local playerInfo = PlayersInfo[info.source] or {}
local self = {
id = info.id,
source = info.source,
identifier = info.identifier,
user_id = info.user_id,
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
logMoney(self.id, "deduct", account, amount, reason, Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()))
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
logMoney(self.id, "add", account, amount, reason, Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()))
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, locale("deposit")) and self.addMoney("bank", amount, locale("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, locale("withdraw")) and self.addMoney("cash", amount, locale("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
logMoney(self.id, "set", k, value, reason, Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()))
TriggerEvent("ND:moneyChange", self.source, k, v, "set", reason)
end
end
else
self[key] = value
if key == "cash" or key == "bank" then
logMoney(self.id, "set", key, value, reason, Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()))
TriggerEvent("ND:moneyChange", self.source, key, value, "set", reason)
end
end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), key)
TriggerEvent("ND:updateCharacter", self, key)
end
---@param key string|table
---@param value any
---@return table
function self.setMetadata(key, value)
if type(key) == "table" then
for k, v in pairs(key) do
self.metadata[k] = v
end
else
self.metadata[key] = value
end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "metadata")
TriggerEvent("ND:updateCharacter", self, "metadata")
return self.metadata
end
-- Mark character deleted
function self.delete()
local result = MySQL.update.await("UPDATE nd_characters SET deleted_at = NOW() WHERE charid = ?", {self.id})
if result and NDCore.players[self.source] then
NDCore.players[self.source] = nil
end
return result
end
--save last location
function self.saveLastLocation()
local ped = GetPlayerPed(self.source)
if not ped or ped == 0 then return end
local coords = GetEntityCoords(ped)
local heading = GetEntityHeading(ped)
local saveLocation = true
for i=1, #avoidSavingLastLocations do
local loc = avoidSavingLastLocations[i]
if #(loc.coords-coords) < loc.dist then
saveLocation = false
end
end
if not saveLocation then return end
self.setMetadata("location", {
x = coords.x,
y = coords.y,
z = coords.z,
w = heading
})
return true
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
self.saveLastLocation()
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 user_id = ?, name = ?, firstname = ?, lastname = ?, dob = ?, gender = ?, cash = ?, bank = ?, phonenumber = ?, `groups` = ?, metadata = ? WHERE charid = ?", {
self.user_id,
self.name,
self.firstname,
self.lastname,
self.dob,
self.gender,
self.cash,
self.bank,
self.phonenumber,
json.encode(self.groups),
json.encode(self.metadata),
self.id
})
return affectedRows > 0
end
---Create a license/permit for the character
---@param licenseType string
---@param expire number
function self.createLicense(licenseType, expire)
local expireIn = tonumber(expire) or 2592000
local time = os.time()
local licenses = self.metadata.licenses
local identifier = {}
for i=1, 16 do
identifier[i] = math.random(0, 1) == 1 and string.char(math.random(65, 90)) or math.random(0, 9)
end
local license = {
type = licenseType,
status = "valid",
issued = time,
expires = time+expireIn,
identifier = table.concat(identifier)
}
if licenses then
local found = false
for i=1, #licenses do
if licenses[i].type == licenseType then
self.metadata.licenses[i] = license
found = true
break
end
end
if not found then
self.metadata.licenses[#licenses+1] = license
end
else
self.metadata.licenses = {license}
end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "metadata")
TriggerEvent("ND:updateCharacter", self, "metadata")
end
function self.getLicense(identifier)
local licenses = self.metadata.licenses or {}
for i=1, #licenses do
local data = licenses[i]
if data.identifier == identifier then
return data, i
end
end
end
function self.updateLicense(identifier, newData)
local data, i = self.getLicense(identifier)
if not data then return end
for k, v in pairs(newData) do
data[k] = v
end
self.save()
end
---@param coords vector3|vector4
---@return boolean
function self.setCoords(coords)
if not self.source or not coords then return end
local ped = GetPlayerPed(self.source)
if not DoesEntityExist(ped) then return end
SetEntityCoords(ped, coords.x, coords.y, coords.z)
if coords.w then
SetEntityHeading(ped, coords.w)
end
return true
end
---@param eventName string
---@param ... any
---@return boolean
function self.triggerEvent(eventName, ...)
if not self.source then return end
TriggerClientEvent(eventName, self.source, ...)
return true
end
function self.notify(...)
if not self.source then return end
TriggerClientEvent("ox_lib:notify", self.source, ...)
return true
end
function self.revive()
self.triggerEvent("ND:revivePlayer")
self.setMetadata({
dead = false,
deathInfo = false,
})
end
---@param reason string
function self.drop(reason)
if not self.source then return end
DropPlayer(self.source, reason)
end
-- Set the character as the players active character/currently playing character
function self.active()
local char = NDCore.players[self.source]
if char and char.id == self.id then return end
if char then char.unload() end
for identifierType, identifier in pairs(self.identifiers) do
if lib.table.contains(Config.admins, ("%s:%s"):format(identifierType, identifier)) then
self.addGroup("admin")
end
end
local roles = self.discord.roles
if roles then
local hasAdmin = false
for i=1, #Config.adminDiscordRoles do
local role = Config.adminDiscordRoles[i]
if lib.table.contains(roles, role) then
hasAdmin = true
break
end
end
if hasAdmin then
self.addGroup("admin")
elseif self.groups["admin"] then
self.removeGroup("admin")
end
local function hasRole(groupRoles)
for i=1, #groupRoles do
local role = groupRoles[i]
if lib.table.contains(roles, role) then
return true
end
end
end
for group, groupRoles in pairs(Config.groupRoles) do
if hasRole(groupRoles) then
self.addGroup(group)
elseif self.groups[group] then
self.removeGroup(group)
end
end
end
for name, _ in pairs(self.groups) do
lib.addPrincipal(self.source, ("group.%s"):format(name))
end
NDCore.players[self.source] = self
TriggerEvent("ND:characterLoaded", self)
self.triggerEvent("ND:characterLoaded", removeCharacterFunctions(self))
end
---@param name string
---@param rank number
---@param isJob boolean
---@return boolean
function self.addGroup(name, rank, customGroup, isJob)
local groupRank = tonumber(rank) or 1
local groupInfo = Config.groups?[name] or {}
local rankData = groupInfo?.ranksData?[groupRank]
local groupLabel = groupInfo?.label or name
local rankName = rankData?.label or groupRank
local isBoss = rankData?.isBoss or false
local metadata = groupInfo?.metadata or {}
if customGroup then
if customGroup.label then groupLabel = customGroup.label end
if customGroup.rankName then rankName = customGroup.rankName end
if customGroup.isBoss ~= nil then isBoss = customGroup.isBoss end
if customGroup.metadata then metadata = customGroup.metadata end
end
if isJob then
for _, group in pairs(self.groups) do
group.isJob = nil
end
end
self.groups[name] = {
name = name,
label = groupLabel,
rankName = rankName,
rank = groupRank,
isJob = isJob,
isBoss = isBoss,
metadata = metadata
}
if not isJob then
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "groups")
TriggerEvent("ND:updateCharacter", self, "groups")
end
TriggerEvent("ND:groupAdded", self, self.groups[name])
lib.addPrincipal(self.source, ("group.%s"):format(name))
return self.groups[name]
end
---@param name string
---@return table
function self.getGroup(name)
return self.groups[name]
end
---@param name string
function self.removeGroup(name)
local group = self.groups[name]
if not group then return end
if self.job == name then
self.job = nil
self.jobInfo = nil
end
self.groups[name] = nil
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "groups")
TriggerEvent("ND:updateCharacter", self, "groups")
TriggerEvent("ND:groupRemoved", self, group)
lib.removePrincipal(self.source, ("group.%s"):format(name))
return group
end
---@param name string
---@param rank number
---@return boolean
function self.setJob(name, rank, customGroup, keepGroup)
if not keepGroup then
self.removeGroup(self.job)
end
local job = self.addGroup(name, rank, customGroup, true)
if job then
self.job = job.name
self.jobInfo = job
end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "job")
TriggerEvent("ND:updateCharacter", self, "job")
return job
end
---@param job string
---@return boolean
function self.getJob()
for name, group in pairs(self.groups) do
if group.isJob then
return name, group
end
end
end
local jobName, jobInfo = self.getJob()
if jobInfo then
self.job = jobName
self.jobInfo = jobInfo
end
return self
end
function NDCore.avoidSavingLocation(coords, dist)
avoidSavingLastLocations[#avoidSavingLastLocations+1] = { coords = coords, dist = dist}
end
function NDCore.validateCharacterData(charInfo)
local errors = {}
local function checkLength(field, value, maxLength)
if type(value) ~= "string" then return end
if #value > maxLength then
table.insert(errors, string.format("%s is too long (max %d, got %d)", field, maxLength, #value))
end
end
if charInfo.name then checkLength("game name", charInfo.name, 50) end
if charInfo.firstname then checkLength("firstname", charInfo.firstname, 50) end
if charInfo.lastname then checkLength("lastname", charInfo.lastname, 50) end
if charInfo.dob then checkLength("dob", charInfo.dob, 50) end
if charInfo.gender then checkLength("gender", charInfo.gender, 50) end
if type(charInfo.cash) ~= "number" then table.insert(errors, "cash must be a number") end
if type(charInfo.bank) ~= "number" then table.insert(errors, "bank must be a number") end
if #errors > 0 then
return false, errors
else
return true, {}
end
end
---@param src number
---@param info table
---@return table
function NDCore.newCharacter(src, info)
local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
if not identifier then return end
local userId = PlayersInfo[src] and PlayersInfo[src].userId
local charInfo = {
source = src,
identifier = identifier,
user_id = userId,
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 {}
}
local valid, issues = NDCore.validateCharacterData(charInfo)
if not valid then
for _, err in ipairs(issues) do
Wait(100)
TriggerClientEvent("ND:notifyClient", src, {
description = err,
type = "error"
})
end
return
end
charInfo.id = MySQL.insert.await("INSERT INTO nd_characters (identifier, user_id, name, firstname, lastname, dob, gender, cash, bank, phonenumber, `groups`, metadata, inventory) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", {
identifier,
userId,
charInfo.name,
charInfo.firstname,
charInfo.lastname,
charInfo.dob,
charInfo.gender,
charInfo.cash,
charInfo.bank,
charInfo.phonenumber,
json.encode(charInfo.groups),
json.encode(charInfo.metadata),
json.encode(charInfo.inventory)
})
return createCharacterTable(charInfo)
end
---@param id number
---@return table
function NDCore.fetchCharacter(id, src)
local result
if src then
local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
local userId = PlayersInfo[src] and PlayersInfo[src].userId
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ? AND (identifier = ? OR user_id = ?)", {id, identifier, userId})
else
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id})
end
local info = result?[1]
if not info then return end
-- Check if character has user_id, if not assign it
if not info.user_id and src then
local userId = PlayersInfo[src] and PlayersInfo[src].userId
if userId then
MySQL.update.await("UPDATE nd_characters SET user_id = ? WHERE charid = ?", {userId, info.charid})
info.user_id = userId
end
end
return createCharacterTable({
source = src,
id = info.charid,
identifier = info.identifier,
user_id = info.user_id,
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 userId = PlayersInfo[src] and PlayersInfo[src].userId
local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
local characters = {}
-- Query by either identifier OR user_id, excluding deleted characters
local result = MySQL.query.await("SELECT * FROM nd_characters WHERE (identifier = ? OR user_id = ?) AND deleted_at IS NULL", {identifier, userId})
for i=1, #result do
local info = result[i]
-- Check if character has user_id, if not assign it
if not info.user_id and userId then
MySQL.update.await("UPDATE nd_characters SET user_id = ? WHERE charid = ?", {userId, info.charid})
info.user_id = userId
end
characters[info.charid] = createCharacterTable({
source = src,
id = info.charid,
identifier = info.identifier,
user_id = info.user_id,
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
-- runs every 10 minutes, saves all characters and money logs.
lib.cron.new("*/10 * * * *", function()
local players = NDCore.getPlayers()
for _, player in pairs(players) do
player.saveLastLocation()
player.save()
end
if #moneyLogs == 0 then return end
local placeholders = {}
local params = {}
local logs = moneyLogs
moneyLogs = {}
for i=1, #logs do
local row = logs[i]
for j = 1, 6 do
table.insert(params, row[j])
end
table.insert(placeholders, "(?, ?, ?, ?, ?, ?)")
end
local query = "INSERT INTO nd_money_log (character_id, action, account, amount, reason, trace) VALUES " .. table.concat(placeholders, ",")
MySQL.prepare(query, params)
end)

824
server/vehicle.lua Normal file
View File

@@ -0,0 +1,824 @@
local ox_inventory
local inventoryStarted = false
local spawnedPlayerVehicles = {}
local vehicleTypes = json.decode(GetResourceKvpString("ND_Core:vehTypes") or "[]")
NDCore.isResourceStarted("ox_inventory", function(started)
inventoryStarted = started
if not started then return end
ox_inventory = exports.ox_inventory
end)
local function getVehicleType(coords, model)
if vehicleTypes[model] then
return vehicleTypes[model]
end
local tempVehicle = CreateVehicle(model, coords.x, coords.y, coords.z-5.0, coords.w, true, false)
local time = os.time()
while not DoesEntityExist(tempVehicle) and os.time()-time < 5 do Wait(0) end
if not DoesEntityExist(tempVehicle) then return end
local vehType = GetVehicleType(tempVehicle)
DeleteEntity(tempVehicle)
vehicleTypes[model] = vehType
SetResourceKvp("ND_Core:vehTypes", json.encode(vehicleTypes))
return vehType
end
local function generatePlate()
local plate = {}
local i = 1
local p = 1
-- ensure pattern exists and pad to 8 chars
local pattern = Config.platePattern
if #pattern < 8 then
pattern = pattern .. string.rep(" ", 8 - #pattern)
end
while i <= 8 do
local char = pattern:sub(p, p)
if char == "^" then
-- literal next character
p = p + 1
plate[i] = pattern:sub(p, p)
elseif char == "1" then
plate[i] = tostring(math.random(0, 9))
elseif char == "A" then
plate[i] = string.char(math.random(65, 90)) -- AZ
elseif char == "." then
if math.random(0, 1) == 0 then
plate[i] = tostring(math.random(0, 9))
else
plate[i] = string.char(math.random(65, 90))
end
else
plate[i] = char
end
i = i + 1
p = p + 1
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
local properties = json.decode(vehicle.properties) or {}
properties.plate = vehicle.plate
return {
id = vehicle.id,
owner = vehicle.owner,
plate = vehicle.plate,
properties = properties,
stored = stored,
impounded = impounded,
stolen = vehicle.stolen == 1,
available = not impounded and stored,
metadata = json.decode(vehicle.metadata) or {}
}
end
function NDCore.generateVehiclePlate()
return generatePlate()
end
function NDCore.generateUniqueVehiclePlate()
return generateVehiclePlate()
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)
}
-- save properties to db
function self.saveProperties(props, propsToSave)
local properties = {}
local vehProps = props or lib.callback.await("ND_Vehicles:getProps", NetworkGetEntityOwner(entity), self.netId)
if propsToSave then
properties = self.properties
if vehProps then
for prop, value in pairs(vehProps) do
if lib.table.contains(propsToSave, prop) then
properties[prop] = value
end
end
end
else
properties = vehProps
end
if self.properties?.callsign then
properties.callsign = true
end
self.properties = properties
state.props = properties
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id})
end
--- delete the vehicle
function self.delete(saveProperties, propsToSave)
if not DoesEntityExist(entity) then return end
if saveProperties and self.id and self.owner then
self.saveProperties(false, propsToSave)
end
if DoesEntityExist(entity) then
DeleteEntity(entity)
end
return true
end
-- remvoe vehicle from db.
function self.remove(keepEntity)
if not keepEntity and DoesEntityExist(entity) then
DeleteEntity(entity)
end
MySQL.query("DELETE FROM nd_vehicles WHERE id = ?", {self.id})
end
--- set vehicle properties
---@param props table
function self.setProperties(props)
if not DoesEntityExist(entity) then return end
local properties = type(props) == "string" and json.decode(props) or props
local state = Entity(entity).state
if self.properties?.callsign then
properties.callsign = true
end
state.allPropsApplied = false
state.props = properties
self.properties = properties
if not self.id or not self.owner then return end
self.saveProperties(properties)
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.allPropsApplied = false
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, keepEntity)
if not lib.table.contains({"stored", "impounded", "stolen"}, statusType) then return end
if statusType ~= "stolen" and not keepEntity then self.delete(true) end
if not self.id or not self.owner then return end
local query = ("UPDATE nd_vehicles SET %s = ? WHERE id = ?"):format(statusType)
MySQL.query(query, {status and 1 or 0, self.id})
return true
end
function self.setMetadata(key, value)
self.metadata[key] = value
if DoesEntityExist(entity) then
local state = Entity(self.entity).state
if not state.metadata then
state.metadata = self.metadata
else
state.metadata[key] = value
end
end
if not self.id or not self.owner then
lib.print.debug(('Skipped storing metadata for vehicle %s as it does not have an id/owner associated with it.'):format(self.entity))
return
end
MySQL.query("UPDATE nd_vehicles SET metadata = ? WHERE id = ?", {json.encode(self.metadata), self.id})
end
if self.id and self.owner then
spawnedPlayerVehicles[self.id] = self
end
return self
end
--- get a characters owned vehicles
---@param characterId number
---@return table
function NDCore.getVehicles(characterId)
local result = MySQL.query.await("SELECT * FROM nd_vehicles WHERE owner = ?", {characterId})
if not result then return {} end
local vehicles = {}
for _, vehicle in pairs(result) do
local info = getVehicleDatabaseInfo(vehicle)
if info then
vehicles[#vehicles+1] = info
end
end
return vehicles
end
--- give a player keys/access to a vehicle
---@param source number
---@param vehicle number
---@param access boolean
---@param info table
function NDCore.giveVehicleAccess(source, vehicle, access, info)
if not vehicle or not DoesEntityExist(vehicle) then return end
local state = Entity(vehicle).state
local netId = info?.netId or NetworkGetNetworkIdFromEntity(vehicle)
local vehicleId = info?.vehicleId or state.id or generateTemporaryVehicleId()
if not state.id then
state.id = vehicleId
end
local player = NDCore.getPlayer(source)
if player then
local keys = state.keys or {}
keys[player.id] = access
state.keys = keys
end
if not inventoryStarted or not Config.useInventoryForKeys then return end
local hasKey = ox_inventory:GetSlotIdWithItem(source, "keys", {
vehId = vehicleId
})
if access and not hasKey then
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 ""
ox_inventory:AddItem(source, "keys", 1, {
vehOwner = owner or state.owner,
vehId = vehicleId,
vehPlate = plate,
vehModel = modelName,
keyEnabled = true,
vehNetId = netId
})
elseif not access and hasKey then
ox_inventory:RemoveItem(source, "keys", 1, nil, hasKey)
end
end
--- spawn a vehicle, set info & give keys.
---@param info table
---@return table
function NDCore.createVehicle(info)
local owner = info.owner
local vehicleId = info.vehicleId or generateTemporaryVehicleId()
local properties = info.properties or {}
local coords = info.coords
local spawnCoords = coords
local coordType = type(coords)
if coordType == "vector3" or coordType == "table" then
spawnCoords = vector4(coords.x, coords.y, coords.z, coords.w or coords.h or info.heading or 0.0)
end
if not spawnCoords then
return Citizen.Trace("NDCore.createVehicle", "spawnCoords not found")
end
local model = info.model or properties.model
local vehType = getVehicleType(spawnCoords, model)
if not vehType then
return Citizen.Trace("NDCore.createVehicle", "vehType not found")
end
local veh = CreateVehicleServerSetter(model, vehType, spawnCoords.x, spawnCoords.y, spawnCoords.z, spawnCoords.w)
local time = os.time()
while not DoesEntityExist(veh) and os.time()-time < 5 do Wait(5) end
if not veh or not DoesEntityExist(veh) then
return Citizen.Trace("NDCore.createVehicle", "vehicle entity doesn't exist")
end
local netId = NetworkGetNetworkIdFromEntity(veh)
local state = Entity(veh).state
state.locked = true
local keys = info.keys or {}
if not properties.plate or info.plate then
properties.plate = info.plate or generateVehiclePlate()
end
if owner then
keys[owner] = true
state.owner = owner
end
if info.blipGroups then
state.blipGroups = info.blipGroups
end
state.keys = keys
state.id = vehicleId
if not info.ignoreProps then
state.props = properties
end
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 = info.plate or properties?.plate,
model = model,
vehicleName = vehicleName,
owner = owner
})
end
end
end
for i=1, 3 do
if DoesEntityExist(veh) then
SetVehicleNumberPlateText(veh, properties.plate)
end
Wait(500)
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 = locale("vehicle_ownership_transfered"),
description = locale("vehicle_ownership_transfered2", vehicle.plate),
position = "bottom-right",
type = "success"
})
playerTo.notify({
title = locale("vehicle_ownership_received"),
description = locale("vehicle_ownership_received2", 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, setPlate)
local plate = setPlate or 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 = locale("keys_shared"),
description = locale("keys_shared2", plate),
position = "bottom-right",
type = "success"
})
targetPlayer.notify({
title = locale("keys_received"),
description = locale("keys_received2", 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, _vehicle)
local player = NDCore.getPlayer(source)
if not player then return end
local vehicle = _vehicle or NDCore.getVehicleById(vehicleId)
if not vehicle or vehicle.owner ~= player.id then return end
if not vehicle.available and not vehicle.impounded then return end
MySQL.query.await("UPDATE nd_vehicles SET stored = ? WHERE id = ?", {0, vehicleId})
local properties = vehicle.properties
if properties.callsign then
local callsign = player.getMetadata("callsign")
if not callsign or callsign == "" then goto skip end
callsign = tostring(callsign)
if not callsign or callsign:len() ~= 3 then goto skip end
properties.modFender = tonumber(callsign:sub(1, 1))
properties.modRightFender = tonumber(callsign:sub(2, 2))
properties.modRoof = tonumber(callsign:sub(3, 3))
end
::skip::
return NDCore.createVehicle({
owner = player.id,
model = properties.model,
coords = vec4(coords.x, coords.y, coords.z, coords.w or coords.heading or heading),
properties = properties,
vehicleId = vehicleId,
source = source
})
end
local function toggleVehicleLock(source, entity, nearby, metadata)
local player = NDCore.getPlayer(source)
if metadata and not metadata.keyEnabled then
return player.notify({
title = locale("no_signal"),
description = locale("veh_key_disabled"),
type = "error",
position = "bottom-right",
duration = 3000
})
elseif not nearby then
return player.notify({
title = locale("no_signal"),
description = locale("veh_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 = locale("veh_locked"),
description = locale("veh_locked2"),
type = "success",
position = "bottom-right",
duration = 3000
})
end
player.notify({
title = locale("veh_unlocked"),
description = locale("veh_unlocked2"),
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
local function hasBlipGroup(player, groups)
groups = groups or {}
for i=1, #groups do
local group = groups[i]
if player.groups[group] then
return true
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
local blipGroups = state.blipGroups
if not state.id or (not owner or owner ~= player.id) and (not hasBlipGroup(player, blipGroups)) 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:propsApplied", function(netId)
local veh = NetworkGetEntityFromNetworkId(netId)
if not veh or not DoesEntityExist(veh) then return end
local state = Entity(veh).state
state:set("allPropsApplied", true, 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)
AddEventHandler("ND:characterLoaded", function(player)
local ownedExistingVehicles = {}
local ownedVehicles = NDCore.getVehicles(player.id)
local vehicles = GetAllVehicles()
local vehiclesToImpound = {}
for i=1, #vehicles do
local veh = vehicles[i]
local state = Entity(veh).state
if state.owner == player.id then
ownedExistingVehicles[#ownedExistingVehicles+1] = state.id
end
end
for i=1, #ownedVehicles do
local veh = ownedVehicles[i]
if veh and not veh.stored and not veh.impounded and not lib.table.contains(ownedExistingVehicles, veh.id) then
vehiclesToImpound[#vehiclesToImpound+1] = veh.id
end
end
if #vehiclesToImpound == 0 then return end
local query = ("UPDATE nd_vehicles SET stored = ? WHERE owner = ? AND id IN (%s)"):format(table.concat(vehiclesToImpound, ", "))
MySQL.rawExecute(query, {1, 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)

13
ui/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="style.css">
<script type="module" src="main.js" defer></script>
<title>Group Management</title>
</head>
<body>
</body>
</html>

283
ui/main.js Normal file
View File

@@ -0,0 +1,283 @@
import van from "https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.3.min.js";
import { nuiMessage } from "./modules/listener.js";
import { fetchCallback } from "./modules/fetch.js";
import { closeUI, setOpen } from "./modules/close.js";
const { div, h1, h2, h3, p, button, input, label, span, i: icon } = van.tags;
let groups = [];
let container = null;
let editPanel = null;
let currentEdit = null;
function createGroupCard(group) {
const card = div({ class: "group-card", onclick: () => openEditPanel(group) },
div({ class: "group-card-header" },
h3(group.label),
span({ class: "group-name" }, group.name)
),
div({ class: "group-card-tags" },
group.isJob ? span({ class: "tag tag-job" }, "Job") : null,
span({ class: "tag tag-ranks" }, `${group.ranks.length} rank${group.ranks.length !== 1 ? "s" : ""}`)
)
);
return card;
}
function renderGroupList() {
if (!container) return;
container.innerHTML = "";
const header = div({ class: "list-header" },
h1("Group Management"),
button({ class: "btn btn-create", onclick: () => openCreatePanel() },
icon({ class: "fa-solid fa-plus" }),
" New Group"
)
);
container.appendChild(header);
const searchRow = div({ class: "search-row" },
input({ class: "search-input", type: "text", placeholder: "Search groups...", oninput: (e) => filterGroups(e.target.value) })
);
container.appendChild(searchRow);
const grid = div({ class: "group-grid", id: "group-grid" });
groups.forEach(g => grid.appendChild(createGroupCard(g)));
container.appendChild(grid);
}
function filterGroups(query) {
const grid = document.getElementById("group-grid");
if (!grid) return;
grid.innerHTML = "";
const lower = query.toLowerCase();
const filtered = groups.filter(g => g.name.toLowerCase().includes(lower) || g.label.toLowerCase().includes(lower));
filtered.forEach(g => grid.appendChild(createGroupCard(g)));
}
function createRankRow(rank, index, ranksList) {
const row = div({ class: "rank-row", "data-index": index },
div({ class: "rank-order" },
button({ class: "btn-icon", title: "Move up", onclick: () => moveRank(ranksList, index, -1) },
icon({ class: "fa-solid fa-chevron-up" })
),
span({ class: "rank-weight" }, String(rank.weight)),
button({ class: "btn-icon", title: "Move down", onclick: () => moveRank(ranksList, index, 1) },
icon({ class: "fa-solid fa-chevron-down" })
)
),
input({ class: "rank-label-input", type: "text", value: rank.label, placeholder: "Rank label", oninput: (e) => {
ranksList[index].label = e.target.value;
}}),
div({ class: "rank-boss-toggle" },
label({ class: "checkbox-label" },
input({ type: "checkbox", checked: rank.isBoss, onchange: (e) => {
ranksList[index].isBoss = e.target.checked;
}}),
" Boss"
)
),
button({ class: "btn-icon btn-danger", title: "Delete rank", onclick: () => {
ranksList.splice(index, 1);
recalculateWeights(ranksList);
rerenderRanks(ranksList);
}},
icon({ class: "fa-solid fa-trash" })
)
);
return row;
}
function moveRank(ranksList, index, direction) {
const newIndex = index + direction;
if (newIndex < 0 || newIndex >= ranksList.length) return;
const temp = ranksList[index];
ranksList[index] = ranksList[newIndex];
ranksList[newIndex] = temp;
recalculateWeights(ranksList);
rerenderRanks(ranksList);
}
function recalculateWeights(ranksList) {
for (let i = 0; i < ranksList.length; i++) {
ranksList[i].weight = i + 1;
}
}
function rerenderRanks(ranksList) {
const ranksContainer = document.getElementById("ranks-container");
if (!ranksContainer) return;
ranksContainer.innerHTML = "";
ranksList.forEach((rank, idx) => {
ranksContainer.appendChild(createRankRow(rank, idx, ranksList));
});
}
function openEditPanel(group) {
currentEdit = JSON.parse(JSON.stringify(group));
showEditPanel(false);
}
function openCreatePanel() {
currentEdit = { name: "", label: "", isJob: false, ranks: [] };
showEditPanel(true);
}
function showEditPanel(isNew) {
if (!editPanel) return;
editPanel.innerHTML = "";
editPanel.classList.add("open");
const ranksList = currentEdit.ranks.map(r => ({ ...r }));
const panelContent = div({ class: "edit-content" },
div({ class: "edit-header" },
h2(isNew ? "Create Group" : "Edit Group"),
button({ class: "btn-icon", onclick: () => closeEditPanel() },
icon({ class: "fa-solid fa-xmark" })
)
),
div({ class: "edit-form" },
div({ class: "form-group" },
label("Name (unique key)"),
input({
class: "form-input",
type: "text",
value: currentEdit.name,
disabled: !isNew,
placeholder: "e.g. polis",
oninput: (e) => { currentEdit.name = e.target.value.toLowerCase().replace(/[^a-z0-9_-]/g, ""); e.target.value = currentEdit.name; }
})
),
div({ class: "form-group" },
label("Label"),
input({
class: "form-input",
type: "text",
value: currentEdit.label,
placeholder: "e.g. Polis",
oninput: (e) => { currentEdit.label = e.target.value; }
})
),
div({ class: "form-group form-row" },
label({ class: "checkbox-label" },
input({
type: "checkbox",
checked: currentEdit.isJob,
onchange: (e) => { currentEdit.isJob = e.target.checked; }
}),
" Is Job"
)
),
div({ class: "form-group" },
div({ class: "ranks-header" },
h3("Ranks"),
button({ class: "btn btn-small", onclick: () => {
ranksList.push({ label: "", weight: ranksList.length + 1, isBoss: false });
rerenderRanks(ranksList);
}},
icon({ class: "fa-solid fa-plus" }),
" Add Rank"
)
),
div({ class: "ranks-info" },
p("Rank 1 is the lowest. Higher weight = higher rank. Boss ranks grant boss permissions.")
),
div({ id: "ranks-container", class: "ranks-container" })
)
),
div({ class: "edit-actions" },
!isNew ? button({ class: "btn btn-danger", onclick: () => deleteGroup(currentEdit.name) },
icon({ class: "fa-solid fa-trash" }),
" Delete Group"
) : null,
div({ class: "edit-actions-right" },
button({ class: "btn btn-secondary", onclick: () => closeEditPanel() }, "Cancel"),
button({ class: "btn btn-primary", onclick: () => saveGroup(isNew, ranksList) },
icon({ class: "fa-solid fa-floppy-disk" }),
" Save"
)
)
)
);
editPanel.appendChild(panelContent);
rerenderRanks(ranksList);
}
function closeEditPanel() {
if (!editPanel) return;
editPanel.classList.remove("open");
editPanel.innerHTML = "";
currentEdit = null;
}
function saveGroup(isNew, ranksList) {
if (!currentEdit.name || !currentEdit.label) return;
const ranks = ranksList.map((r, idx) => ({
label: r.label,
weight: idx + 1,
isBoss: r.isBoss
}));
if (isNew) {
fetchCallback("groups:create", {
name: currentEdit.name,
label: currentEdit.label,
isJob: currentEdit.isJob,
ranks: ranks
}, (result) => {
if (result && result.success) {
groups = result.groups || groups;
renderGroupList();
closeEditPanel();
}
});
} else {
fetchCallback("groups:edit", {
name: currentEdit.name,
label: currentEdit.label,
isJob: currentEdit.isJob,
ranks: ranks
}, (result) => {
if (result && result.success) {
groups = result.groups || groups;
renderGroupList();
closeEditPanel();
}
});
}
}
function deleteGroup(name) {
if (!name) return;
fetchCallback("groups:delete", { name: name }, (result) => {
if (result && result.success) {
groups = result.groups || groups;
renderGroupList();
closeEditPanel();
}
});
}
function init() {
container = div({ class: "main-page" });
editPanel = div({ class: "edit-panel" });
document.body.appendChild(container);
document.body.appendChild(editPanel);
}
nuiMessage("groups:open", (info) => {
setOpen(true);
document.body.style.opacity = "1";
groups = info.groups || [];
renderGroupList();
});
nuiMessage("groups:close", () => {
closeUI();
});
init();

46
ui/modules/close.js Normal file
View File

@@ -0,0 +1,46 @@
import { fetchCallback } from "./fetch.js";
let isOpen = false;
export function closeUI() {
if (!isOpen) return;
isOpen = false;
document.body.style.opacity = "0";
setTimeout(() => {
fetchCallback("hide");
}, 400);
}
export function setOpen(state) {
isOpen = state;
}
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeUI();
});
document.addEventListener("click", (event) => {
const mainPage = document.querySelector(".main-page");
const editPanel = document.querySelector(".edit-panel");
if (!mainPage || !editPanel) return;
const mainRect = mainPage.getBoundingClientRect();
const editRect = editPanel.getBoundingClientRect();
const insideMainPage =
event.clientX >= mainRect.left &&
event.clientX <= mainRect.right &&
event.clientY >= mainRect.top &&
event.clientY <= mainRect.bottom;
const insideEditPanel =
event.clientX >= editRect.left &&
event.clientX <= editRect.right &&
event.clientY >= editRect.top &&
event.clientY <= editRect.bottom;
if (!insideMainPage && !insideEditPanel) {
closeUI();
}
});

9
ui/modules/fetch.js Normal file
View File

@@ -0,0 +1,9 @@
export function fetchCallback(name, data, cb) {
fetch(`https://${GetParentResourceName()}/${name}`, {
method: "POST",
headers: { "Content-Type": "application/json; charset=UTF-8" },
body: JSON.stringify(data)
}).then(resp => resp.json()).then(resp => {
if (cb) cb(resp);
});
}

15
ui/modules/listener.js Normal file
View File

@@ -0,0 +1,15 @@
const nuiMessages = {};
window.addEventListener("message", function(event) {
const item = event.data;
const listener = nuiMessages[item.type];
if (!listener) return;
for (let i = 0; i < listener.length; i++) {
listener[i](item);
}
});
export function nuiMessage(name, cb) {
if (!nuiMessages[name]) nuiMessages[name] = [];
nuiMessages[name].push(cb);
}

455
ui/style.css Normal file
View File

@@ -0,0 +1,455 @@
@import url('https://fonts.googleapis.com/css2?family=Abel&display=swap');
:root {
--font: "Abel", sans-serif;
--bg-primary: #1a1a1bfd;
--bg-secondary: #202020;
--bg-tertiary: #262627;
--bg-hover: #2e2e2f;
--text-primary: #d4d4d3;
--text-secondary: #999;
--accent: #0063b1;
--accent-hover: #0078d4;
--danger: #c42b1c;
--danger-hover: #e03e2d;
--border: #333;
--success: #107c10;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
user-select: none;
opacity: 0;
transition: opacity 0.4s;
font-family: var(--font);
color: var(--text-primary);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
}
.main-page {
position: relative;
background-color: var(--bg-primary);
border-radius: 12px;
width: 60rem;
max-height: 45rem;
padding: 1.5rem;
display: flex;
flex-direction: column;
overflow: hidden;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.list-header h1 {
font-size: 1.6rem;
font-weight: 400;
}
.search-row {
margin-bottom: 1rem;
}
.search-input {
width: 100%;
padding: 0.6rem 1rem;
border-radius: 8px;
border: 1px solid var(--border);
background-color: var(--bg-secondary);
color: var(--text-primary);
font-family: var(--font);
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
.search-input:focus {
border-color: var(--accent);
}
.group-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
gap: 0.75rem;
overflow-y: auto;
padding-right: 0.25rem;
max-height: 35rem;
}
.group-grid::-webkit-scrollbar {
width: 5px;
}
.group-grid::-webkit-scrollbar-track {
background: transparent;
}
.group-grid::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
.group-card {
background-color: var(--bg-secondary);
border-radius: 10px;
padding: 1rem;
cursor: pointer;
transition: background-color 0.2s, transform 0.15s;
border: 1px solid transparent;
}
.group-card:hover {
background-color: var(--bg-tertiary);
border-color: var(--border);
transform: translateY(-1px);
}
.group-card-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 0.5rem;
}
.group-card-header h3 {
font-size: 1.15rem;
font-weight: 400;
}
.group-name {
font-size: 0.85rem;
color: var(--text-secondary);
}
.group-card-tags {
display: flex;
gap: 0.4rem;
flex-wrap: wrap;
}
.tag {
font-size: 0.8rem;
padding: 0.15rem 0.5rem;
border-radius: 4px;
background-color: var(--bg-tertiary);
color: var(--text-secondary);
}
.tag-job {
background-color: rgba(0, 99, 177, 0.25);
color: var(--accent-hover);
}
.tag-ranks {
background-color: rgba(16, 124, 16, 0.2);
color: #4caf50;
}
/* Edit Panel */
.edit-panel {
position: fixed;
top: 0;
right: -35rem;
width: 35rem;
height: 100vh;
background-color: var(--bg-primary);
border-left: 1px solid var(--border);
transition: right 0.3s ease;
z-index: 100;
display: flex;
flex-direction: column;
}
.edit-panel.open {
right: 0;
}
.edit-content {
display: flex;
flex-direction: column;
height: 100%;
padding: 1.5rem;
}
.edit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.edit-header h2 {
font-size: 1.4rem;
font-weight: 400;
}
.edit-form {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1rem;
padding-right: 0.25rem;
}
.edit-form::-webkit-scrollbar {
width: 5px;
}
.edit-form::-webkit-scrollbar-track {
background: transparent;
}
.edit-form::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.form-group > label {
font-size: 0.9rem;
color: var(--text-secondary);
}
.form-row {
flex-direction: row;
align-items: center;
}
.form-input {
padding: 0.5rem 0.75rem;
border-radius: 6px;
border: 1px solid var(--border);
background-color: var(--bg-secondary);
color: var(--text-primary);
font-family: var(--font);
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
.form-input:focus {
border-color: var(--accent);
}
.form-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.4rem;
cursor: pointer;
font-size: 0.95rem;
}
.checkbox-label input[type="checkbox"] {
width: 1.1rem;
height: 1.1rem;
accent-color: var(--accent);
cursor: pointer;
}
/* Ranks */
.ranks-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.ranks-header h3 {
font-size: 1.1rem;
font-weight: 400;
}
.ranks-info {
margin-bottom: 0.5rem;
}
.ranks-info p {
font-size: 0.8rem;
color: var(--text-secondary);
}
.ranks-container {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.rank-row {
display: flex;
align-items: center;
gap: 0.5rem;
background-color: var(--bg-secondary);
padding: 0.5rem 0.6rem;
border-radius: 8px;
transition: background-color 0.15s;
}
.rank-row:hover {
background-color: var(--bg-tertiary);
}
.rank-order {
display: flex;
align-items: center;
gap: 0.15rem;
min-width: 4rem;
justify-content: center;
}
.rank-weight {
font-size: 0.85rem;
color: var(--text-secondary);
min-width: 1.5rem;
text-align: center;
}
.rank-label-input {
flex: 1;
padding: 0.35rem 0.6rem;
border-radius: 5px;
border: 1px solid var(--border);
background-color: var(--bg-primary);
color: var(--text-primary);
font-family: var(--font);
font-size: 0.95rem;
outline: none;
transition: border-color 0.2s;
}
.rank-label-input:focus {
border-color: var(--accent);
}
.rank-boss-toggle {
min-width: 3.5rem;
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.5rem 1rem;
border: none;
border-radius: 7px;
cursor: pointer;
font-family: var(--font);
font-size: 0.95rem;
transition: background-color 0.2s, opacity 0.2s;
}
.btn:hover {
opacity: 0.9;
}
.btn-primary {
background-color: var(--accent);
color: var(--text-primary);
}
.btn-primary:hover {
background-color: var(--accent-hover);
}
.btn-secondary {
background-color: var(--bg-tertiary);
color: var(--text-primary);
}
.btn-secondary:hover {
background-color: var(--bg-hover);
}
.btn-create {
background-color: var(--accent);
color: var(--text-primary);
}
.btn-create:hover {
background-color: var(--accent-hover);
}
.btn-danger {
background-color: var(--danger);
color: var(--text-primary);
}
.btn-danger:hover {
background-color: var(--danger-hover);
}
.btn-small {
padding: 0.3rem 0.7rem;
font-size: 0.85rem;
background-color: var(--accent);
color: var(--text-primary);
}
.btn-icon {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 0.2rem;
font-size: 0.85rem;
border-radius: 4px;
transition: color 0.2s, background-color 0.15s;
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
}
.btn-icon:hover {
color: var(--text-primary);
background-color: var(--bg-hover);
}
.btn-icon.btn-danger {
background: none;
color: var(--text-secondary);
}
.btn-icon.btn-danger:hover {
color: var(--danger-hover);
background-color: rgba(196, 43, 28, 0.15);
}
/* Edit Actions */
.edit-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid var(--border);
margin-top: 1rem;
}
.edit-actions-right {
display: flex;
gap: 0.5rem;
}