90 Commits
v2.1.3 ... 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
41 changed files with 3995 additions and 1142 deletions

View File

@@ -15,7 +15,8 @@ local function PlayerEliminated(deathCause, killerServerId, killerClientId)
deathCause = deathCause, deathCause = deathCause,
killerServerId = killerServerId, killerServerId = killerServerId,
killerClientId = killerClientId, killerClientId = killerClientId,
damagedBones = usingAmbulance and ambulance:getBodyDamage() or {} damagedBones = usingAmbulance and ambulance:getBodyDamage() or {},
timestamp = GetCloudTimeAsInt()
} }
TriggerEvent("ND:playerEliminated", info) TriggerEvent("ND:playerEliminated", info)
TriggerServerEvent("ND:playerEliminated", info) TriggerServerEvent("ND:playerEliminated", info)
@@ -29,14 +30,13 @@ AddEventHandler("gameEventTriggered", function(name, args)
local victim = args[1] local victim = args[1]
if not IsPedAPlayer(victim) or NetworkGetPlayerIndexFromPed(victim) ~= cache.playerId then return end if not IsPedAPlayer(victim) or NetworkGetPlayerIndexFromPed(victim) ~= cache.playerId then return end
if not IsPedDeadOrDying(victim, true) then local hit, bone = GetPedLastDamageBone(victim)
local hit, bone = GetPedLastDamageBone(victim) if hit and usingAmbulance then
if hit and usingAmbulance then local damageWeapon = ambulance:getLastDamagingWeapon(victim)
local damageWeapon = ambulance:getLastDamagingWeapon(victim) ambulance:updateBodyDamage(bone, damageWeapon)
return damageWeapon and ambulance:updateBodyDamage(bone, damageWeapon)
end
return
end end
if not IsPedDeadOrDying(victim, true) or GetEntityHealth(victim) > 100 then return end
local killerEntity, deathCause = GetPedSourceOfDeath(cache.ped), GetPedCauseOfDeath(cache.ped) local killerEntity, deathCause = GetPedSourceOfDeath(cache.ped), GetPedCauseOfDeath(cache.ped)
local killerClientId = NetworkGetPlayerIndexFromPed(killerEntity) local killerClientId = NetworkGetPlayerIndexFromPed(killerEntity)

View File

@@ -1,26 +1,92 @@
RegisterNetEvent("ND:notifyClient", function(...)
NDCore.notify(...)
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)
if not NDCore.player then return end if source == "" or not NDCore.player then return end
NDCore.player.cash = cash NDCore.player.cash = cash
NDCore.player.bank = bank NDCore.player.bank = bank
end) end)
-- Sets main character. -- Sets main character.
RegisterNetEvent("ND:characterLoaded", function(character) RegisterNetEvent("ND:characterLoaded", function(character)
if source == "" then return end
NDCore.player = character NDCore.player = character
end) end)
-- Update main character info. -- Update main character info.
RegisterNetEvent("ND:updateCharacter", function(character) RegisterNetEvent("ND:updateCharacter", function(character)
if source == "" or not NDCore.player then return end
NDCore.player = character NDCore.player = character
end) end)
-- Updates last lcoation. -- Updates last lcoation.
RegisterNetEvent("ND:updateLastLocation", function(location) RegisterNetEvent("ND:updateLastLocation", function(location)
if not NDCore.player then return end if source == "" or not NDCore.player then return end
NDCore.player.lastLocation = location NDCore.player.lastLocation = location
end) end)
RegisterNetEvent("ND:revivePlayer", function() -- Unsets main character.
NDCore.revivePlayer(true) 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

@@ -33,56 +33,8 @@ function NDCore.getConfig(info)
return Config[info] return Config[info]
end end
function NDCore.revivePlayer(reset, keepDead)
local usingAmbulance = GetResourceState("ND_Ambulance") == "started"
if not keepDead then
LocalPlayer.state.dead = false
if usingAmbulance then
local state = Player(cache.serverId).state
state:set("isDead", false, true)
end
end
local veh = GetVehiclePedIsIn(cache.ped)
local seat = cache.seat
local coords = GetEntityCoords(cache.ped)
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, GetEntityHeading(cache.ped), true, true, false)
local ped = PlayerPedId()
if cache.ped ~= ped then
DeleteEntity(cache.ped)
ClearAreaOfPeds(coords.x, coords.y, coords.z, 0.2, false)
end
SetEntityInvincible(ped, false)
FreezeEntityPosition(ped, false)
SetEntityVisible(ped, true)
SetEveryoneIgnorePlayer(ped, false)
SetPedCanBeTargetted(ped, true)
SetEntityCanBeDamaged(ped, true)
SetBlockingOfNonTemporaryEvents(ped, false)
SetPedCanRagdollFromPlayerImpact(ped, true)
ClearPedTasksImmediately(ped)
if veh and veh ~= 0 then
SetPedIntoVehicle(ped, veh, seat)
end
if reset and GetPedMovementClipset(ped) == `move_m@injured` then
ClearEntityLastDamageEntity(ped)
SetPedMoveRateOverride(ped, 1.0)
ResetPedMovementClipset(ped, 0)
end
if reset and usingAmbulance then
exports["ND_Ambulance"]:resetBodyDamage()
end
end
function NDCore.notify(...) function NDCore.notify(...)
if GetResourceState("ModernHUD") == "started" then lib.notify(...)
exports["ModernHUD"]:notify(...)
elseif GetResourceState("ox_lib") == "started" then
lib.notify(...)
end
end end
for name, func in pairs(NDCore) do for name, func in pairs(NDCore) do

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,3 +1,4 @@
lib.locale()
NDCore = {} NDCore = {}
Config = { Config = {
@@ -7,7 +8,7 @@ Config = {
discordAsset = GetConvar("core:discordAsset", "andyyy"), discordAsset = GetConvar("core:discordAsset", "andyyy"),
discordAssetSmall = GetConvar("core:discordAssetSmall", "andyyy"), discordAssetSmall = GetConvar("core:discordAssetSmall", "andyyy"),
discordActionText = GetConvar("core:discordActionText", "DISCORD"), discordActionText = GetConvar("core:discordActionText", "DISCORD"),
discordActionLink = GetConvar("discordActionLink", "https://discord.gg/Z9Mxu72zZ6"), discordActionLink = GetConvar("core:discordActionLink", "https://discord.gg/Z9Mxu72zZ6"),
discordActionText2 = GetConvar("core:discordActionText2", "STORE"), discordActionText2 = GetConvar("core:discordActionText2", "STORE"),
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"), discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1, disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1,
@@ -15,7 +16,8 @@ Config = {
requireKeys = GetConvarInt("core:requireKeys", 1) == 1, requireKeys = GetConvarInt("core:requireKeys", 1) == 1,
useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1, useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1,
groups = json.decode(GetConvar("core:groups", "[]")), groups = json.decode(GetConvar("core:groups", "[]")),
compatibility = json.decode(GetConvar("core:compatibility", "[]")) compatibility = json.decode(GetConvar("core:compatibility", "[]")),
lockpickTries = GetConvarInt("core:lockpickTries", 3)
} }
-- Discord rich presence. -- Discord rich presence.
@@ -25,11 +27,11 @@ CreateThread(function()
SetDiscordRichPresenceAssetSmall(Config.discordAssetSmall) SetDiscordRichPresenceAssetSmall(Config.discordAssetSmall)
SetDiscordRichPresenceAction(0, Config.discordActionText, Config.discordActionLink) SetDiscordRichPresenceAction(0, Config.discordActionText, Config.discordActionLink)
SetDiscordRichPresenceAction(1, Config.discordActionText2, Config.discordActionLink2) SetDiscordRichPresenceAction(1, Config.discordActionText2, Config.discordActionLink2)
local presenceText = ("Playing: %s"):format(Config.serverName) local presenceText = locale("discord_text_server", Config.serverName)
while true do while true do
if NDCore.player then if NDCore.player then
local presence = ("Playing: %s as %s %s"):format(Config.serverName, NDCore.player.firstname, NDCore.player.lastname) local presence = locale("discord_text", Config.serverName, NDCore.player.firstname, NDCore.player.lastname)
local presenceTextSmall = ("Playing as: %s %s"):format(NDCore.player.firstname, NDCore.player.lastname) local presenceTextSmall = locale("discord_text_small", NDCore.player.firstname, NDCore.player.lastname)
SetRichPresence(presence) SetRichPresence(presence)
SetDiscordRichPresenceAssetText(presenceText) SetDiscordRichPresenceAssetText(presenceText)
SetDiscordRichPresenceAssetSmallText(presenceTextSmall) SetDiscordRichPresenceAssetSmallText(presenceTextSmall)
@@ -48,8 +50,8 @@ CreateThread(function()
sleep = 0 sleep = 0
BeginScaleformMovieMethodOnFrontendHeader("SET_HEADING_DETAILS") BeginScaleformMovieMethodOnFrontendHeader("SET_HEADING_DETAILS")
ScaleformMovieMethodAddParamPlayerNameString(("%s %s"):format(NDCore.player.firstname, NDCore.player.lastname)) ScaleformMovieMethodAddParamPlayerNameString(("%s %s"):format(NDCore.player.firstname, NDCore.player.lastname))
ScaleformMovieMethodAddParamTextureNameString(("Cash: $%d"):format(NDCore.player.cash)) ScaleformMovieMethodAddParamTextureNameString(locale("pause_menu_cash", NDCore.player.cash))
ScaleformMovieMethodAddParamTextureNameString(("Bank: $%d"):format(NDCore.player.bank)) ScaleformMovieMethodAddParamTextureNameString(locale("pause_menu_bank", NDCore.player.bank))
EndScaleformMovieMethod() EndScaleformMovieMethod()
elseif sleep == 0 then elseif sleep == 0 then
sleep = 500 sleep = 500

View File

@@ -101,7 +101,18 @@ local function updateBlips(playerGroups)
end end
end end
function NDCore.createAiPed(info) 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 ped
local model = type(info.model) == "string" and GetHashKey(info.model) or info.model local model = type(info.model) == "string" and GetHashKey(info.model) or info.model
local blipInfo = info.blip local blipInfo = info.blip
@@ -111,7 +122,7 @@ function NDCore.createAiPed(info)
local options = info.options local options = info.options
local blip = createBlip(coords, blipInfo) local blip = createBlip(coords, blipInfo)
local point = lib.points.new({ local point = lib.points.new({
coords = vec3(coords.x, coords.y, coords.z), coords = coords.xyz,
distance = info.distance or 25.0 distance = info.distance or 25.0
}) })
@@ -123,6 +134,10 @@ function NDCore.createAiPed(info)
resource = info.resource or GetInvokingResource() 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() function point:onEnter()
local found, ground = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, true) local found, ground = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, true)
lib.requestModel(model) lib.requestModel(model)
@@ -133,6 +148,11 @@ function NDCore.createAiPed(info)
Wait(100) Wait(100)
end end
if cb then
cb(ped)
end
disableCollisionWithModels(ped, info.disableCollisionWithModels)
configPed(ped) configPed(ped)
setClothing(ped, clothing) setClothing(ped, clothing)
locations[id].ped = ped locations[id].ped = ped
@@ -218,10 +238,10 @@ end)
RegisterCommand("getclothing", function(source, args, rawCommand) RegisterCommand("getclothing", function(source, args, rawCommand)
local info = "" local info = ""
for k, v in pairs(clothingComponents) do for k, v in pairs(clothingComponents) do
info = ("%s\n%s = {\n drawable = %s,\n texture = %s\n},"):format(info, k, GetPedDrawableVariation(cache.ped, v), GetPedTextureVariation(cache.ped, v)) info = ("%s\n%s = { drawable = %s, texture = %s },"):format(info, k, GetPedDrawableVariation(cache.ped, v), GetPedTextureVariation(cache.ped, v))
end end
for k, v in pairs(clothingProps) do for k, v in pairs(clothingProps) do
info = ("%s\n%s = {\n drawable = %s,\n texture = %s\n},"):format(info, k, GetPedPropIndex(cache.ped, v), GetPedPropTextureIndex(cache.ped, v)) info = ("%s\n%s = { drawable = %s, texture = %s },"):format(info, k, GetPedPropIndex(cache.ped, v), GetPedPropTextureIndex(cache.ped, v))
end end
lib.setClipboard(info) lib.setClipboard(info)
end, false) end, false)

View File

@@ -1,188 +1,188 @@
local vehicleColorNames = { local vehicleColorNames = {
[0] = "Black", [0] = locale("veh_color_black"),
[1] = "Black", [1] = locale("veh_color_black"),
[2] = "Black", [2] = locale("veh_color_black"),
[3] = "Silver", [3] = locale("veh_color_silver"),
[4] = "Silver", [4] = locale("veh_color_silver"),
[5] = "Silver", [5] = locale("veh_color_silver"),
[6] = "Gray", [6] = locale("veh_color_grey"),
[7] = "Silver", [7] = locale("veh_color_silver"),
[8] = "Silver", [8] = locale("veh_color_silver"),
[9] = "Silver", [9] = locale("veh_color_silver"),
[10] = "Metal", [10] = locale("veh_color_metal"),
[11] = "Grey", [11] = locale("veh_color_grey"),
[12] = "Black", [12] = locale("veh_color_black"),
[13] = "Gray", [13] = locale("veh_color_grey"),
[14] = "Grey", [14] = locale("veh_color_grey"),
[15] = "Black", [15] = locale("veh_color_black"),
[16] = "Black", [16] = locale("veh_color_black"),
[17] = "Silver", [17] = locale("veh_color_silver"),
[18] = "Silver", [18] = locale("veh_color_silver"),
[19] = "Metal", [19] = locale("veh_color_metal"),
[20] = "Silver", [20] = locale("veh_color_silver"),
[21] = "Black", [21] = locale("veh_color_black"),
[22] = "Graphite", [22] = locale("veh_color_graphite"),
[23] = "Silver", [23] = locale("veh_color_silver"),
[24] = "Silver", [24] = locale("veh_color_silver"),
[25] = "Silver", [25] = locale("veh_color_silver"),
[26] = "Silver", [26] = locale("veh_color_silver"),
[27] = "Red", [27] = locale("veh_color_red"),
[28] = "Red", [28] = locale("veh_color_red"),
[29] = "Red", [29] = locale("veh_color_red"),
[30] = "Red", [30] = locale("veh_color_red"),
[31] = "Red", [31] = locale("veh_color_red"),
[32] = "Red", [32] = locale("veh_color_red"),
[33] = "Red", [33] = locale("veh_color_red"),
[34] = "Red", [34] = locale("veh_color_red"),
[35] = "Red", [35] = locale("veh_color_red"),
[36] = "Orange", [36] = locale("veh_color_orange"),
[37] = "Gold", [37] = locale("veh_color_gold"),
[38] = "Orange", [38] = locale("veh_color_orange"),
[39] = "Red", [39] = locale("veh_color_red"),
[40] = "Red", [40] = locale("veh_color_red"),
[41] = "Orange", [41] = locale("veh_color_orange"),
[42] = "Yellow", [42] = locale("veh_color_yellow"),
[43] = "Red", [43] = locale("veh_color_red"),
[44] = "Red", [44] = locale("veh_color_red"),
[45] = "Red", [45] = locale("veh_color_red"),
[46] = "Red", [46] = locale("veh_color_red"),
[47] = "Red", [47] = locale("veh_color_red"),
[48] = "Red", [48] = locale("veh_color_red"),
[49] = "Green", [49] = locale("veh_color_green"),
[50] = "Green", [50] = locale("veh_color_green"),
[51] = "Green", [51] = locale("veh_color_green"),
[52] = "Green", [52] = locale("veh_color_green"),
[53] = "Green", [53] = locale("veh_color_green"),
[54] = "Green", [54] = locale("veh_color_green"),
[55] = "Green", [55] = locale("veh_color_green"),
[56] = "Green", [56] = locale("veh_color_green"),
[57] = "Green", [57] = locale("veh_color_green"),
[58] = "Green", [58] = locale("veh_color_green"),
[59] = "Green", [59] = locale("veh_color_green"),
[60] = "Green", [60] = locale("veh_color_green"),
[61] = "Blue", [61] = locale("veh_color_blue"),
[62] = "Blue", [62] = locale("veh_color_blue"),
[63] = "Blue", [63] = locale("veh_color_blue"),
[64] = "Blue", [64] = locale("veh_color_blue"),
[65] = "Blue", [65] = locale("veh_color_blue"),
[66] = "Blue", [66] = locale("veh_color_blue"),
[67] = "Blue", [67] = locale("veh_color_blue"),
[68] = "Blue", [68] = locale("veh_color_blue"),
[69] = "Blue", [69] = locale("veh_color_blue"),
[70] = "Blue", [70] = locale("veh_color_blue"),
[71] = "Blue", [71] = locale("veh_color_blue"),
[72] = "Blue", [72] = locale("veh_color_blue"),
[73] = "Blue", [73] = locale("veh_color_blue"),
[74] = "Blue", [74] = locale("veh_color_blue"),
[75] = "Blue", [75] = locale("veh_color_blue"),
[76] = "Blue", [76] = locale("veh_color_blue"),
[77] = "Blue", [77] = locale("veh_color_blue"),
[78] = "Blue", [78] = locale("veh_color_blue"),
[79] = "Bblue", [79] = locale("veh_color_bblue"),
[80] = "Blue", [80] = locale("veh_color_blue"),
[81] = "Blue", [81] = locale("veh_color_blue"),
[82] = "Blue", [82] = locale("veh_color_blue"),
[83] = "Blue", [83] = locale("veh_color_blue"),
[84] = "Blue", [84] = locale("veh_color_blue"),
[85] = "Blue", [85] = locale("veh_color_blue"),
[86] = "Blue", [86] = locale("veh_color_blue"),
[87] = "Blue", [87] = locale("veh_color_blue"),
[88] = "Yellow", [88] = locale("veh_color_yellow"),
[89] = "Yellow", [89] = locale("veh_color_yellow"),
[90] = "Bronze", [90] = locale("veh_color_bronze"),
[91] = "Yellow", [91] = locale("veh_color_yellow"),
[92] = "Lime", [92] = locale("veh_color_lime"),
[93] = "Champagne", [93] = locale("veh_color_champagne"),
[94] = "Beige", [94] = locale("veh_color_beige"),
[95] = "Ivory", [95] = locale("veh_color_ivory"),
[96] = "Brown", [96] = locale("veh_color_brown"),
[97] = "Brown", [97] = locale("veh_color_brown"),
[98] = "Brown", [98] = locale("veh_color_brown"),
[99] = "Beige", [99] = locale("veh_color_beige"),
[100] = "Brown", [100] = locale("veh_color_brown"),
[101] = "Brown", [101] = locale("veh_color_brown"),
[102] = "Beechwood", [102] = locale("veh_color_beechwood"),
[103] = "Beechwood", [103] = locale("veh_color_beechwood"),
[104] = "Orange", [104] = locale("veh_color_orange"),
[105] = "Sand", [105] = locale("veh_color_sand"),
[106] = "Sand", [106] = locale("veh_color_sand"),
[107] = "Cream", [107] = locale("veh_color_cream"),
[108] = "Brown", [108] = locale("veh_color_brown"),
[109] = "Brown", [109] = locale("veh_color_brown"),
[110] = "Brown", [110] = locale("veh_color_brown"),
[111] = "White", [111] = locale("veh_color_white"),
[112] = "White", [112] = locale("veh_color_white"),
[113] = "Beige", [113] = locale("veh_color_beige"),
[114] = "Brown", [114] = locale("veh_color_brown"),
[115] = "Brown", [115] = locale("veh_color_brown"),
[116] = "Beige", [116] = locale("veh_color_beige"),
[117] = "Steel", [117] = locale("veh_color_steel"),
[118] = "Steel", [118] = locale("veh_color_steel"),
[119] = "Aluminium", [119] = locale("veh_color_aluminium"),
[120] = "Chrome", [120] = locale("veh_color_chrome"),
[121] = "White", [121] = locale("veh_color_white"),
[122] = "White", [122] = locale("veh_color_white"),
[123] = "Orange", [123] = locale("veh_color_orange"),
[124] = "Orange", [124] = locale("veh_color_orange"),
[125] = "Green", [125] = locale("veh_color_green"),
[126] = "Yellow", [126] = locale("veh_color_yellow"),
[127] = "Blue", [127] = locale("veh_color_blue"),
[128] = "Green", [128] = locale("veh_color_green"),
[129] = "Brown", [129] = locale("veh_color_brown"),
[130] = "Orange", [130] = locale("veh_color_orange"),
[131] = "White", [131] = locale("veh_color_white"),
[132] = "White", [132] = locale("veh_color_white"),
[133] = "Green", [133] = locale("veh_color_green"),
[134] = "White", [134] = locale("veh_color_white"),
[135] = "Pink", [135] = locale("veh_color_pink"),
[136] = "pink", [136] = locale("veh_color_pink"),
[137] = "Pink", [137] = locale("veh_color_pink"),
[138] = "Orange", [138] = locale("veh_color_orange"),
[139] = "Green", [139] = locale("veh_color_green"),
[140] = "Blue", [140] = locale("veh_color_blue"),
[141] = "Black", [141] = locale("veh_color_black"),
[142] = "Black", [142] = locale("veh_color_black"),
[143] = "Black", [143] = locale("veh_color_black"),
[144] = "Green", [144] = locale("veh_color_green"),
[145] = "Purple", [145] = locale("veh_color_purple"),
[146] = "Blue", [146] = locale("veh_color_blue"),
[147] = "Black", [147] = locale("veh_color_black"),
[148] = "Purple", [148] = locale("veh_color_purple"),
[149] = "Purple", [149] = locale("veh_color_purple"),
[150] = "Red", [150] = locale("veh_color_red"),
[151] = "Green", [151] = locale("veh_color_green"),
[152] = "Green", [152] = locale("veh_color_green"),
[153] = "Brown", [153] = locale("veh_color_brown"),
[154] = "Tan", [154] = locale("veh_color_tan"),
[155] = "Green", [155] = locale("veh_color_green"),
[156] = "ALLOY", [156] = locale("veh_color_alloy"),
[157] = "Blue", [157] = locale("veh_color_blue"),
} }
local vehicleClassNames = { local vehicleClassNames = {
[0] = "Compact", [0] = locale("veh_class_compact"),
[1] = "Sedan", [1] = locale("veh_class_sedan"),
[2] = "SUV", [2] = locale("veh_class_suv"),
[3] = "Coupe", [3] = locale("veh_class_coupe"),
[4] = "Muscle", [4] = locale("veh_class_muscle"),
[5] = "Sports Classic", [5] = locale("veh_class_sports_classic"),
[6] = "Sport", [6] = locale("veh_class_sport"),
[7] = "Super", [7] = locale("veh_class_super"),
[8] = "Motorcycle", [8] = locale("veh_class_motorcycle"),
[9] = "Off-road", [9] = locale("veh_class_off_road"),
[10] = "Industrial", [10] = locale("veh_class_industrial"),
[11] = "Utility", [11] = locale("veh_class_utility"),
[12] = "Van", [12] = locale("veh_class_van"),
[13] = "Cycle", [13] = locale("veh_class_cycle"),
[14] = "Boat", [14] = locale("veh_class_boat"),
[15] = "Helicopter", [15] = locale("veh_class_helicopter"),
[16] = "Plane", [16] = locale("veh_class_plane"),
[17] = "Service", [17] = locale("veh_class_service"),
[18] = "Emergency", [18] = locale("veh_class_emergency"),
[19] = "Military", [19] = locale("veh_class_military"),
[20] = "Commercial", [20] = locale("veh_class_commercial"),
[21] = "Train", [21] = locale("veh_class_train"),
[22] = "Open wheel" [22] = locale("veh_class_open_wheel")
} }
local cruiseSpeedSet = 0 local cruiseSpeedSet = 0
@@ -242,30 +242,36 @@ local function getVehicleBlipSprite(entity)
end end
local function getVehFromNetId(netId) local function getVehFromNetId(netId)
local time = GetCloudTimeAsInt() local startTime = GetCloudTimeAsInt()
while not NetworkDoesNetworkIdExist(netId) or not NetworkDoesEntityExistWithNetworkId(netId) and time-GetCloudTimeAsInt() < 5 do while (not NetworkDoesNetworkIdExist(netId) or not NetworkDoesEntityExistWithNetworkId(netId)) and (GetCloudTimeAsInt() - startTime < 5) do
Wait(100) Wait(100)
end end
return NetToVeh(netId) return NetToVeh(netId)
end end
RegisterNetEvent("ND_Vehicles:blip", function(netId, status)
local veh = getVehFromNetId(netId) local function setVehicleBlip(veh, status, groupVehicle)
if not veh then return end local currentBlip = GetBlipFromEntity(veh)
if not status then if currentBlip and DoesBlipExist(currentBlip) then
local blip = GetBlipFromEntity(veh) RemoveBlip(currentBlip)
if not blip or not DoesBlipExist(blip) then return end
return RemoveBlip(blip)
end end
if not status then return end
local blip = AddBlipForEntity(veh) local blip = AddBlipForEntity(veh)
SetBlipSprite(blip, getVehicleBlipSprite(veh)) SetBlipSprite(blip, getVehicleBlipSprite(veh))
SetBlipColour(blip, 0) SetBlipColour(blip, 0)
SetBlipScale(blip, 0.8) SetBlipScale(blip, 0.8)
SetBlipAsShortRange(blip, true) SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName("STRING") BeginTextCommandSetBlipName("STRING")
AddTextComponentSubstringPlayerName("Personal vehicle") AddTextComponentSubstringPlayerName(groupVehicle and locale("group_vehicle") or locale("personal_vehicle"))
EndTextCommandSetBlipName(blip) 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) end)
RegisterNetEvent("ND_Vehicles:syncAlarm", function(netId) RegisterNetEvent("ND_Vehicles:syncAlarm", function(netId)
@@ -284,13 +290,39 @@ RegisterNetEvent("ND_VehicleSystem:setOwnedIfNot", function(netId)
end) end)
AddStateBagChangeHandler("props", nil, function(bagName, key, value, reserved, replicated) AddStateBagChangeHandler("props", nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName) if not value then return end
if not value or not DoesEntityExist(entity) or NetworkGetEntityOwner(entity) ~= cache.playerId then return end
local props = value local props = value
if type(value) == "string" then if type(value) == "string" then
props = json.decode(value) props = json.decode(value)
end 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) 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) end)
local playingKey = 0 local playingKey = 0
@@ -356,8 +388,9 @@ AddStateBagChangeHandler("locked", nil, function(bagName, key, value, reserved,
end) end)
end) end)
lib.callback.register("ND_Vehicles:getProps", function(netId) local function getProps(veh)
local veh = getVehFromNetId(netId) if not veh or not DoesEntityExist(veh) then return end
local props = lib.getVehicleProperties(veh) local props = lib.getVehicleProperties(veh)
local colorPrimary, colorSecondary = GetVehicleColours(veh) local colorPrimary, colorSecondary = GetVehicleColours(veh)
if not props then return end if not props then return end
@@ -368,7 +401,18 @@ lib.callback.register("ND_Vehicles:getProps", function(netId)
props.className = vehicleClassNames[GetVehicleClass(veh)] props.className = vehicleClassNames[GetVehicleClass(veh)]
props.makeName = GetLabelText(GetMakeNameFromVehicleModel(props.model)) props.makeName = GetLabelText(GetMakeNameFromVehicleModel(props.model))
props.modelName = GetLabelText(GetDisplayNameFromVehicleModel(props.model)) props.modelName = GetLabelText(GetDisplayNameFromVehicleModel(props.model))
return props 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) end)
lib.callback.register("ND_Vehicles:getVehicleModelMakeLabel", function(model) lib.callback.register("ND_Vehicles:getVehicleModelMakeLabel", function(model)
@@ -399,6 +443,17 @@ local function hasVehicleKeys(veh, checkEngine)
return hasKey or checkEngine and state.hotwired return hasKey or checkEngine and state.hotwired
end 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 function hasVehicleKeysCheck(veh)
local time = GetCloudTimeAsInt() local time = GetCloudTimeAsInt()
if time-keyCheckTime.lastCheck < 5 then if time-keyCheckTime.lastCheck < 5 then
@@ -435,7 +490,7 @@ end
local vehicleLockKeybind = lib.addKeybind({ local vehicleLockKeybind = lib.addKeybind({
name = "vehicleKey", name = "vehicleKey",
description = "Unlock/lock vehicle (double click)", description = locale("keybind_carkey"),
defaultKey = "E", defaultKey = "E",
onPressed = function(self) onPressed = function(self)
local time = GetCloudTimeAsInt() local time = GetCloudTimeAsInt()
@@ -457,8 +512,8 @@ NDCore.isResourceStarted("ox_inventory", function(started)
Wait(1000) Wait(1000)
vehicleLockKeybind:disable(true) vehicleLockKeybind:disable(true)
exports.ox_inventory:displayMetadata({ exports.ox_inventory:displayMetadata({
vehPlate = "Plate", vehPlate = locale("veh_plate"),
vehModel = "Model" vehModel = locale("veh_model")
}) })
end) end)
@@ -514,6 +569,33 @@ CreateThread(function()
end 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 function hotwireVehicle()
local state = playerVehicle and Entity(playerVehicle).state local state = playerVehicle and Entity(playerVehicle).state
if not playerVehicle or state.hotwired then return end if not playerVehicle or state.hotwired then return end
@@ -540,7 +622,7 @@ local function hotwireVehicle()
local success = lib.progressCircle({ local success = lib.progressCircle({
duration = math.random(10000, 20000), duration = math.random(10000, 20000),
label = "Hotwiring", label = locale("progress_hotwiring"),
useWhileDead = false, useWhileDead = false,
allowRagdoll = false, allowRagdoll = false,
allowCuffed = false, allowCuffed = false,
@@ -585,34 +667,51 @@ local function lockpickVehicle()
local veh = lib.getClosestVehicle(pos, 2.5, false) local veh = lib.getClosestVehicle(pos, 2.5, false)
if not veh then return end if not veh then return end
local dificulties = { local lockPickResource = GetResourceState("lockpick") == "started" and "lockpick" or GetResourceState("t3_lockpick") == "started" and "t3_lockpick"
"easy", if lockPickResource then
"medium", local success = exports[lockPickResource]:startLockpick("lockpick_vehicle", 4, Config.lockpickTries)
"hard"
}
local dificultyTime = {
easy = 500,
medium = 800,
hard = 1000
}
lib.requestAnimDict("veh@break_in@0h@p_m_one@")
for i=1, 7 do
TaskPlayAnimAdvanced(cache.ped, "veh@break_in@0h@p_m_one@", "std_force_entry_ds", pos.x, pos.y, pos.z+0.025, rot.x, rot.y, rot.z, 8.0, 8.0, 1800, 28, 0.1)
local dificulty = dificulties[math.random(1, #dificulties)]
local success = lib.skillCheck(dificulty)
if not success or not DoesEntityExist(veh) or #(pos-GetEntityCoords(veh)) > 2.5 then if not success or not DoesEntityExist(veh) or #(pos-GetEntityCoords(veh)) > 2.5 then
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), false) 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 return false, true
end end
Wait(dificultyTime[dificulty]) 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 end
veh = lib.getClosestVehicle(pos, 2.5, false) veh = lib.getClosestVehicle(pos, 2.5, false)
if not veh then return false, true end if not veh then return false, true end
TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), true) TriggerServerEvent("ND_Vehicles:lockpick", VehToNet(veh), true)
PlaySoundFromEntity(-1, "Remote_Control_Fob", cache.ped, "PI_Menu_Sounds", true, 0) PlaySoundFromEntity(-1, "Remote_Control_Fob", cache.ped, "PI_Menu_Sounds", true, 0)
return true, true
if lockPickResource then
return true, false
else
return true, true
end
end end
exports("lockpick", function(data, slot) exports("lockpick", function(data, slot)
@@ -635,8 +734,8 @@ exports("keyControl", function(action, slot)
if data.slot == slot then if data.slot == slot then
if metadata and not metadata.keyEnabled then if metadata and not metadata.keyEnabled then
return lib.notify({ return lib.notify({
title = "No signal", title = locale("no_signal"),
description = "Vehicle key disabled.", description = locale("veh_key_disabled"),
type = "error", type = "error",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -658,8 +757,8 @@ exports("keyControl", function(action, slot)
elseif action == "disable" then elseif action == "disable" then
TriggerServerEvent("ND_Vehicles:disableKey", slot) TriggerServerEvent("ND_Vehicles:disableKey", slot)
lib.notify({ lib.notify({
title = "Key disabled", title = locale("veh_key_disabled"),
description = "This vehicle key has been disabled and cannot be used anymore.", description = locale("veh_key_disabled2"),
type = "inform", type = "inform",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -671,8 +770,8 @@ local function cruiseControl()
cruiseSpeedVehicle = GetEntitySpeed(playerVehicle) * 2.236936 cruiseSpeedVehicle = GetEntitySpeed(playerVehicle) * 2.236936
if not playerVehicle then if not playerVehicle then
lib.notify({ lib.notify({
title = "Cruise control", title = locale("cruise_control"),
description = "Vehicle cruise control disabled.", description = locale("cruise_control_disabled"),
type = "inform", type = "inform",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -681,8 +780,8 @@ local function cruiseControl()
end end
if cruiseSpeedVehicle < cruiseSpeedSet/3 then if cruiseSpeedVehicle < cruiseSpeedSet/3 then
lib.notify({ lib.notify({
title = "Cruise control", title = locale("cruise_control"),
description = "Vehicle cruise control disabled.", description = locale("cruise_control_disabled"),
type = "inform", type = "inform",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -697,14 +796,14 @@ end
lib.addKeybind({ lib.addKeybind({
name = "vehicleCruiseControl", name = "vehicleCruiseControl",
description = "Toggle vehicle cruise control", description = locale("cruise_control_toggle"),
defaultKey = "", defaultKey = "",
onPressed = function(self) onPressed = function(self)
if cruiseControlEnabled and cruiseSpeedVehicle-1 > cruiseSpeedSet then if cruiseControlEnabled and cruiseSpeedVehicle-1 > cruiseSpeedSet then
cruiseSpeedSet = cruiseSpeedVehicle cruiseSpeedSet = cruiseSpeedVehicle
return lib.notify({ return lib.notify({
title = "Cruise control", title = locale("cruise_control"),
description = ("Increased to %d mph."):format(math.floor(cruiseSpeedSet)), description = locale("cruise_control_increase"),
type = "inform", type = "inform",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -712,8 +811,8 @@ lib.addKeybind({
elseif cruiseControlEnabled then elseif cruiseControlEnabled then
cruiseControlEnabled = false cruiseControlEnabled = false
return lib.notify({ return lib.notify({
title = "Cruise control", title = locale("cruise_control"),
description = "Vehicle cruise control disabled.", description = locale("cruise_control_disabled"),
type = "inform", type = "inform",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -729,8 +828,8 @@ lib.addKeybind({
cruiseSpeedSet = cruiseSpeedVehicle cruiseSpeedSet = cruiseSpeedVehicle
lib.notify({ lib.notify({
title = "Cruise control", title = locale("cruise_control"),
description = ("Set to %d mph."):format(math.floor(cruiseSpeedSet)), description = locale("cruise_control_enabled"),
type = "inform", type = "inform",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -744,7 +843,7 @@ lib.addKeybind({
lib.addKeybind({ lib.addKeybind({
name = "vehicleShuffleSeat", name = "vehicleShuffleSeat",
description = "Suffle vehicle seat.", description = locale("seat_shuffle"),
defaultKey = "", defaultKey = "",
onPressed = function(self) onPressed = function(self)
if not cache.vehicle then return end if not cache.vehicle then return end

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
CREATE TABLE IF NOT EXISTS `nd_characters` ( CREATE TABLE IF NOT EXISTS `nd_characters` (
`charid` INT(10) NOT NULL AUTO_INCREMENT, `charid` INT(10) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) DEFAULT NULL,
`identifier` VARCHAR(200) NOT NULL DEFAULT '0', `identifier` VARCHAR(200) NOT NULL DEFAULT '0',
`name` VARCHAR(50) DEFAULT NULL, `name` VARCHAR(50) DEFAULT NULL,
`firstname` VARCHAR(50) DEFAULT NULL, `firstname` VARCHAR(50) DEFAULT NULL,
@@ -9,8 +10,11 @@ CREATE TABLE IF NOT EXISTS `nd_characters` (
`cash` INT(10) DEFAULT '0', `cash` INT(10) DEFAULT '0',
`bank` INT(10) DEFAULT '0', `bank` INT(10) DEFAULT '0',
`phonenumber` VARCHAR(20) DEFAULT NULL, `phonenumber` VARCHAR(20) DEFAULT NULL,
`deleted_at` DATETIME NULL DEFAULT NULL,
`groups` LONGTEXT DEFAULT ('[]'), `groups` LONGTEXT DEFAULT ('[]'),
`metadata` LONGTEXT DEFAULT ('[]'), `metadata` LONGTEXT DEFAULT ('[]'),
`inventory` LONGTEXT DEFAULT ('[]'), `inventory` LONGTEXT DEFAULT ('[]'),
PRIMARY KEY (`charid`) USING BTREE 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';

View File

@@ -2,22 +2,31 @@
author "Andyyy#7666" author "Andyyy#7666"
description "ND Framework Core" description "ND Framework Core"
version "2.1.2" version "2.3.2"
fx_version "cerulean" fx_version "cerulean"
game "gta5" game "gta5"
lua54 "yes" lua54 "yes"
files {
"init.lua",
"compatibility/**/locale.lua",
"locales/*.json",
"ui/**"
}
ui_page "ui/index.html"
shared_script "@ox_lib/init.lua" shared_script "@ox_lib/init.lua"
client_scripts { client_scripts {
"client/main.lua", "client/main.lua",
"shared/functions.lua", "shared/functions.lua",
"client/peds.lua", "client/peds.lua",
"client/vehicle/main.lua", "client/vehicle.lua",
"client/vehicle/garages.lua",
"client/functions.lua", "client/functions.lua",
"client/events.lua", "client/events.lua",
"client/death.lua", "client/death.lua",
"client/groupadmin.lua",
"compatibility/**/client.lua" "compatibility/**/client.lua"
} }
server_scripts { server_scripts {
@@ -27,18 +36,17 @@ server_scripts {
"server/player.lua", "server/player.lua",
"server/vehicle.lua", "server/vehicle.lua",
"server/functions.lua", "server/functions.lua",
"server/groups.lua",
"server/groupadmin.lua",
"compatibility/**/server.lua", "compatibility/**/server.lua",
"server/commands.lua" "server/commands.lua"
} }
files { dependencies {
"init.lua", "ox_lib",
"client/vehicle/data.lua", "oxmysql"
"compatibility/**/locale.lua"
} }
dependency "oxmysql"
-- below were used with backwards compatibility but could interfere with resources checking if the resources are started. -- below were used with backwards compatibility but could interfere with resources checking if the resources are started.
-- provide "es_extended" -- provide "es_extended"
-- provide "qb-Core" -- provide "qb-Core"

View File

@@ -9,3 +9,5 @@ NDCore = setmetatable({}, {
return self[index] return self[index]
end 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,21 +1,21 @@
local moneyTypes = {"bank", "cash"} local moneyTypes = {"bank", "cash"}
local moneyActions = { local moneyActions = {
remove = function(player, account, amount) remove = function(player, account, amount)
player.deductMoney(account, amount, "Staff action") player.deductMoney(account, amount, locale("staff_action"))
return ("Removed $%d (%s) to %s"):format(amount, account, player.name), ("removed $%d from %s"):format(amount, account) return locale("staff_money_removed", amount, account, player.name), locale("user_money_removed", amount, account)
end, end,
add = function(player, account, amount) add = function(player, account, amount)
player.addMoney(account, amount, "Staff action") player.addMoney(account, amount, locale("staff_action"))
return ("Added $%d (%s) to %s"):format(amount, account, player.name), ("added $%d to %s"):format(amount, account) return locale("staff_money_added", amount, account, player.name), locale("user_money_added", amount, account)
end, end,
set = function(player, account, amount) set = function(player, account, amount)
player.setData(account, amount, "Staff action") player.setData(account, amount, locale("staff_action"))
return ("Set %s's (%s) to $%d"):format(player.name, account, amount), ("set %s to $%d"):format(account, amount) return locale("staff_money_set", player.name, account, amount), locale("user_money_set", account, amount)
end end
} }
lib.addCommand("setmoney", { lib.addCommand("setmoney", {
help = "Admin command, manage player money.", help = "Admin command, set a players money.",
restricted = "group.admin", restricted = "group.admin",
params = { params = {
{ {
@@ -48,17 +48,17 @@ lib.addCommand("setmoney", {
local staffMessage, userMessage = action(player, moneyType, args.amount) local staffMessage, userMessage = action(player, moneyType, args.amount)
player.notify({ player.notify({
title = "Staff action", title = locale("staff_action"),
description = userMessage, description = userMessage,
type = "inform", type = "inform",
duration = 10000 duration = 10000
}) })
if not source then return end if not source or source == 0 then return end
TriggerClientEvent("chat:addMessage", source, { TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235}, color = {50, 100, 235},
multiline = true, multiline = true,
args = {"Staff action", staffMessage} args = {locale("staff_action"), staffMessage}
}) })
end) end)
@@ -90,17 +90,17 @@ lib.addCommand("setjob", {
local jobInfo = player.setJob(job, args.rank) local jobInfo = player.setJob(job, args.rank)
if not player or not jobInfo then return end if not player or not jobInfo then return end
player.notify({ player.notify({
title = "Staff action", title = locale("staff_action"),
description = ("Job updated to %s, rank %s"):format(jobInfo.label, jobInfo.rankName), description = locale("job_updated_noti", jobInfo.label, jobInfo.rankName),
type = "inform", type = "inform",
duration = 10000 duration = 10000
}) })
if not source then return end if not source or source == 0 then return end
TriggerClientEvent("chat:addMessage", source, { TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235}, color = {50, 100, 235},
multiline = true, multiline = true,
args = {"Staff action", "success"} args = {locale("staff_action"), locale("success")}
}) })
end) end)
@@ -137,8 +137,8 @@ lib.addCommand("setgroup", {
local groupInfo = player.addGroup(args.group, args.rank) local groupInfo = player.addGroup(args.group, args.rank)
if not groupInfo then return end if not groupInfo then return end
player.notify({ player.notify({
title = "Staff action", title = locale("staff_action"),
description = ("Added to group %s, rank %s."):format(groupInfo.label, groupInfo.rankName), description = locale("group_added_noti", groupInfo.label, groupInfo.rankName),
type = "inform", type = "inform",
duration = 10000 duration = 10000
}) })
@@ -146,8 +146,8 @@ lib.addCommand("setgroup", {
local groupInfo = player.removeGroup(args.group) local groupInfo = player.removeGroup(args.group)
if not groupInfo then return end if not groupInfo then return end
player.notify({ player.notify({
title = "Staff action", title = locale("staff_action"),
description = ("Removed from group %s."):format(groupInfo.label), description = locale("group_removed_noti", groupInfo.label),
type = "inform", type = "inform",
duration = 10000 duration = 10000
}) })
@@ -155,16 +155,16 @@ lib.addCommand("setgroup", {
return return
end end
if not source then return end if not source or source == 0 then return end
TriggerClientEvent("chat:addMessage", source, { TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235}, color = {50, 100, 235},
multiline = true, multiline = true,
args = {"Staff action", "success"} args = {locale("staff_action"), locale("success")}
}) })
end) end)
lib.addCommand("skin", { lib.addCommand("skin", {
help = "Admin command, set player into character clothing menu.", help = "Admin command, show character clothing menu for player.",
restricted = "group.admin", restricted = "group.admin",
params = { params = {
{ {
@@ -178,7 +178,7 @@ lib.addCommand("skin", {
end) end)
lib.addCommand("character", { lib.addCommand("character", {
help = "Admin command, set player into character selection menu.", help = "Admin command, show character selection menu for player.",
restricted = "group.admin", restricted = "group.admin",
params = { params = {
{ {
@@ -206,7 +206,7 @@ lib.addCommand("pay", {
} }
} }
}, function(source, args, raw) }, function(source, args, raw)
if not source then return end if not source or source == 0 then return end
local player = NDCore.getPlayer(source) local player = NDCore.getPlayer(source)
local targetPlayer = NDCore.getPlayer(args.target) local targetPlayer = NDCore.getPlayer(args.target)
@@ -214,7 +214,7 @@ lib.addCommand("pay", {
if not player or not targetPlayer then if not player or not targetPlayer then
return player.notify({ return player.notify({
title = "Error", title = "Error",
description = "No player found", description = locale("no_player_found"),
type = "error" type = "error"
}) })
end end
@@ -222,7 +222,7 @@ lib.addCommand("pay", {
if player.cash < args.amount then if player.cash < args.amount then
return player.notify({ return player.notify({
title = "Error", title = "Error",
description = "You don't have enough cash", description = locale("not_enough_cash"),
type = "error" type = "error"
}) })
end end
@@ -232,7 +232,7 @@ lib.addCommand("pay", {
if #(playerCoords-targetCoords) > 2.0 then if #(playerCoords-targetCoords) > 2.0 then
return player.notify({ return player.notify({
title = "Error", title = "Error",
description = "Player is not nearby", description = locale("player_not_nearby"),
type = "error" type = "error"
}) })
end end
@@ -240,60 +240,44 @@ lib.addCommand("pay", {
local success = player.deductMoney("cash", args.amount) and targetPlayer.addMoney("cash", args.amount) local success = player.deductMoney("cash", args.amount) and targetPlayer.addMoney("cash", args.amount)
if not success then if not success then
return player.notify({ return player.notify({
title = "Couldn't give money", title = locale("cant_give_money"),
type = "error", type = "error",
duration = 5000 duration = 5000
}) })
end end
targetPlayer.notify({ targetPlayer.notify({
title = "Money received", title = locale("money_received"),
description = ("Received $%d in cash"):format(args.amount), description = locale("money_received2", args.amount),
type = "inform", type = "inform",
duration = 5000 duration = 5000
}) })
player.notify({ player.notify({
title = "Money given", title = locale("money_given"),
description = ("You gave $%d in cash"):format(args.amount), description = locale("money_given2", args.amount),
type = "inform", type = "inform",
duration = 5000 duration = 5000
}) })
end) end)
lib.addCommand("unlock", { lib.addCommand("unlock", {
help = "Admin force unlock vehicles", help = "Admin Command, force unlock a vehicle.",
restricted = "group.admin", restricted = "group.admin",
}, function(source, args, raw) }, function(source, args, raw)
local ped = GetPlayerPed(source) local ped = GetPlayerPed(source)
local playerVeh = GetVehiclePedIsIn(ped)
local coords = GetEntityCoords(ped) local coords = GetEntityCoords(ped)
local vehicles = GetAllVehicles() local veh = lib.getClosestVehicle(coords, 2.5, true)
local maxDistance = 2.0
local closestVehicle
if not playerVeh or playerVeh == 0 or not DoesEntityExist(playerVeh) then if not veh then return end
for i=1, #vehicles do
local vehicle = vehicles[i] local state = Entity(veh).state
local vehicleCoords = GetEntityCoords(vehicle) state.hotwired = true
local distance = #(coords-vehicleCoords) state.locked = false
if distance < maxDistance then
maxDistance = distance
closestVehicle = vehicle
end
end
if not closestVehicle or not DoesEntityExist(closestVehicle) then return end
local state = Entity(closestVehicle).state
state.locked = false
else
local state = Entity(playerVeh).state
state.hotwired = true
end
end) end)
lib.addCommand("revive", { lib.addCommand("revive", {
help = "Admin command, revive player.", help = "Admin command, revive a player.",
restricted = "group.admin", restricted = "group.admin",
params = { params = {
{ {
@@ -309,60 +293,49 @@ lib.addCommand("revive", {
end) end)
lib.addCommand("dv", { lib.addCommand("dv", {
help = "Admin command, delete vehicles within the range.", help = "Admin command, delete vehicles within the range or the closest.",
restricted = "group.admin", restricted = "group.admin",
params = { params = {
{ {
name = "range", name = "range",
type = "number", type = "number",
help = "The range to select vehicles for deleteion from", help = "Range to delete vehicles in",
optional = true optional = true
} }
} }
}, function(source, args, raw) }, function(source, args, raw)
local ped = GetPlayerPed(source) local ped = GetPlayerPed(source)
local veh = GetVehiclePedIsIn(ped)
if veh and veh ~= 0 and DoesEntityExist(veh) then
DeleteEntity(veh)
return TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235},
multiline = true,
args = {"Staff action", "deleted 1 vehicle"}
})
end
local count = 0
local coords = GetEntityCoords(ped) local coords = GetEntityCoords(ped)
local count = 0
local message = nil
local veh = lib.getClosestVehicle(coords, 3.0)
if args.range then if args.range then
for _, veh in ipairs(GetAllVehicles()) do local vehicles = lib.getNearbyVehicles(coords, args.range)
local vehDist = #(GetEntityCoords(veh) - coords) count = #vehicles
if vehDist < args.range then
DeleteEntity(veh) for i=1, count do
count += 1 local veh = vehicles[i]
end DeleteEntity(veh.vehicle)
end end
return TriggerClientEvent("chat:addMessage", source, { elseif veh then
color = {50, 100, 235}, DeleteEntity(veh)
multiline = true, count = 1
args = {"Staff action", ("deleted %d vehicles"):format(count)}
})
end end
local closest, dist local messageLabel = ("%s [dv]"):format(locale("staff_action"))
for _, veh in ipairs(GetAllVehicles()) do if count == 0 then
local vehDist = #(GetEntityCoords(veh) - coords) message = {messageLabel, "no vehicles found"}
if vehDist < 5.0 and not closest or (dist and dist > vehDist) then elseif count == 1 then
closest = veh message = {messageLabel, "deleted 1 vehicle"}
dist = vehDist else
end message = {messageLabel, ("deleted %d vehicles"):format(count)}
end end
if not closest then return "no vehicle found nearby" end
DeleteEntity(closest)
TriggerClientEvent("chat:addMessage", source, { TriggerClientEvent("chat:addMessage", source, {
color = {50, 100, 235}, color = {50, 100, 235},
multiline = true, multiline = true,
args = {"Staff action", "deleted 1 vehicle"} args = message
}) })
end) end)
@@ -433,7 +406,7 @@ lib.addCommand("unfreeze", {
end) end)
lib.addCommand("vehicle", { lib.addCommand("vehicle", {
help = "Admin command, unfreeze a player.", help = "Admin command, spawn a vehicle.",
restricted = "group.admin", restricted = "group.admin",
params = { params = {
{ {
@@ -466,3 +439,45 @@ lib.addCommand("vehicle", {
Wait(100) Wait(100)
end end
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,3 +1,10 @@
function NDCore.getPlayerIdentifierByType(src, indentifierType)
if Config.sv_lan then
return ("%s:sv_lan"):format(indentifierType)
end
return GetPlayerIdentifierByType(src, indentifierType)
end
---@param src number ---@param src number
---@return table ---@return table
function NDCore.getPlayer(src) function NDCore.getPlayer(src)
@@ -11,7 +18,16 @@ function NDCore.getPlayers(key, value, returnArray)
if not key or not value then return NDCore.players end if not key or not value then return NDCore.players end
local players = {} local players = {}
local keyTypes = {id = "id", firstname = "firstname", lastname = "lastname", gender = "gender", groups = "groups"} local keyTypes = {
id = "id",
firstname = "firstname",
lastname = "lastname",
gender = "gender",
groups = "groups",
job = "job",
gender = "gender"
}
local findBy = keyTypes[key] or "metadata" local findBy = keyTypes[key] or "metadata"
if findBy then if findBy then

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,8 +1,10 @@
lib.locale()
NDCore = {} NDCore = {}
NDCore.players = {} NDCore.players = {}
PlayersInfo = {} PlayersInfo = {}
local resourceName = GetCurrentResourceName() local resourceName = GetCurrentResourceName()
local tempPlayersInfo = {} local tempPlayersInfo = {}
-- Groups are now loaded from the database in MySQL.ready via NDCore.loadGroups()
Config = { Config = {
serverName = GetConvar("core:serverName", "Unconfigured ND-Core Server"), serverName = GetConvar("core:serverName", "Unconfigured ND-Core Server"),
@@ -16,22 +18,28 @@ Config = {
discordActionText2 = GetConvar("core:discordActionText2", "STORE"), discordActionText2 = GetConvar("core:discordActionText2", "STORE"),
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"), discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
characterIdentifier = GetConvar("core:characterIdentifier", "license"), characterIdentifier = GetConvar("core:characterIdentifier", "license"),
selectIdentifiers = json.decode(GetConvar("core:selectIdentifiers", '["discord", "license", "license2", "fivem"]')),
discordGuildId = GetConvar("core:discordGuildId", "false"), discordGuildId = GetConvar("core:discordGuildId", "false"),
discordBotToken = GetConvar("core:discordBotToken", "false"), discordBotToken = GetConvar("core:discordBotToken", "false"),
randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30), randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30),
disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1, disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1,
useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1, useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1,
groups = json.decode(GetConvar("core:groups", "[]")), groups = {},
admins = json.decode(GetConvar("core:admins", "[]")), admins = json.decode(GetConvar("core:admins", "[]")),
adminDiscordRoles = json.decode(GetConvar("core:adminDiscordRoles", "[]")), adminDiscordRoles = json.decode(GetConvar("core:adminDiscordRoles", "[]")),
groupRoles = json.decode(GetConvar("core:groupRoles", "[]")),
multiCharacter = false, multiCharacter = false,
compatibility = json.decode(GetConvar("core:compatibility", "[]")) compatibility = json.decode(GetConvar("core:compatibility", "[]")),
sv_lan = GetConvar("sv_lan", "false") == "true",
platePattern = GetConvar("core:platePattern", "11AAA111")
} }
SetConvarServerInfo("Discord", Config.discordInvite) SetConvarServerInfo("Discord", Config.discordInvite)
SetConvarServerInfo("NDCore", GetResourceMetadata(resourceName, "version", 0) or "invalid") SetConvarServerInfo("NDCore", GetResourceMetadata(resourceName, "version", 0) or "invalid")
SetConvarReplicated("inventory:framework", "nd") SetConvarReplicated("inventory:framework", "nd")
lib.versionCheck('ND-Framework/ND_Core')
local function getIdentifierList(src) local function getIdentifierList(src)
local list = {} local list = {}
for i=0, GetNumPlayerIdentifiers(src) do for i=0, GetNumPlayerIdentifiers(src) do
@@ -42,6 +50,11 @@ local function getIdentifierList(src)
list[identifierType] = identifier list[identifierType] = identifier
end end
end end
if Config.sv_lan then
list[Config.characterIdentifier] = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
end
return list return list
end end
@@ -51,6 +64,75 @@ AddEventHandler("playerJoining", function(oldId)
PlayersInfo[src] = tempPlayersInfo[oldTempId] PlayersInfo[src] = tempPlayersInfo[oldTempId]
tempPlayersInfo[oldTempId] = nil tempPlayersInfo[oldTempId] = nil
if Config.sv_lan then
lib.addPrincipal(("player.%s"):format(src), "group.admin")
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
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 if Config.multiCharacter then return end
Wait(3000) Wait(3000)
@@ -102,15 +184,26 @@ AddEventHandler("playerConnecting", function(name, setKickReason, deferrals)
if mainIdentifier and Config.discordBotToken ~= "false" and Config.discordGuildId ~= "false" then if mainIdentifier and Config.discordBotToken ~= "false" and Config.discordGuildId ~= "false" then
discordInfo = checkDiscordIdentifier(identifiers) discordInfo = checkDiscordIdentifier(identifiers)
if not discordInfo and Config.discordMemeberRequired then if not discordInfo and Config.discordMemeberRequired and not Config.sv_lan then
deferrals.done(("Your discord was not found, join our discord here: %s."):format(Config.discordInvite)) deferrals.done(locale("not_in_discord", Config.discordInvite))
Wait(0) Wait(0)
end end
end end
deferrals.update("Connecting...") deferrals.update(locale("connecting"))
Wait(0) Wait(0)
if Config.sv_lan then
tempPlayersInfo[tempSrc] = {
identifiers = {
[Config.characterIdentifier] = "sv_lan"
},
discord = discordInfo
}
deferrals.done()
return
end
if mainIdentifier then if mainIdentifier then
tempPlayersInfo[tempSrc] = { tempPlayersInfo[tempSrc] = {
identifiers = identifiers, identifiers = identifiers,
@@ -118,7 +211,7 @@ AddEventHandler("playerConnecting", function(name, setKickReason, deferrals)
} }
deferrals.done() deferrals.done()
else else
deferrals.done(("Your %s was not found."):format(Config.characterIdentifier)) deferrals.done(locale("identifier_not_found", Config.characterIdentifier))
Wait(0) Wait(0)
end end
end) end)
@@ -138,13 +231,6 @@ AddEventHandler("onResourceStop", function(name)
end end
end) end)
SetTimeout(500, function()
NDCore.loadSQL({
"database/characters.sql",
"database/vehicles.sql"
}, resourceName)
end)
RegisterNetEvent("ND:playerEliminated", function(info) RegisterNetEvent("ND:playerEliminated", function(info)
local src = source local src = source
local player = NDCore.getPlayer(src) local player = NDCore.getPlayer(src)
@@ -154,3 +240,30 @@ RegisterNetEvent("ND:playerEliminated", function(info)
deathInfo = info deathInfo = info
}) })
end) 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)

View File

@@ -1,3 +1,6 @@
local avoidSavingLastLocations = {}
local moneyLogs = {}
local function removeCharacterFunctions(character) local function removeCharacterFunctions(character)
local newData = {} local newData = {}
for k, v in pairs(character) do for k, v in pairs(character) do
@@ -8,6 +11,13 @@ local function removeCharacterFunctions(character)
return newData return newData
end 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 function createCharacterTable(info)
local playerInfo = PlayersInfo[info.source] or {} local playerInfo = PlayersInfo[info.source] or {}
@@ -15,6 +25,7 @@ local function createCharacterTable(info)
id = info.id, id = info.id,
source = info.source, source = info.source,
identifier = info.identifier, identifier = info.identifier,
user_id = info.user_id,
identifiers = playerInfo.identifiers or {}, identifiers = playerInfo.identifiers or {},
discord = playerInfo.discord or {}, discord = playerInfo.discord or {},
name = info.name, name = info.name,
@@ -43,6 +54,9 @@ local function createCharacterTable(info)
self.triggerEvent("ND:updateMoney", self.cash, self.bank) self.triggerEvent("ND:updateMoney", self.cash, self.bank)
TriggerEvent("ND:moneyChange", self.source, account, amount, "remove", reason) TriggerEvent("ND:moneyChange", self.source, account, amount, "remove", reason)
end end
logMoney(self.id, "deduct", account, amount, reason, Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()))
return true return true
end end
@@ -58,6 +72,9 @@ local function createCharacterTable(info)
self.triggerEvent("ND:updateMoney", self.cash, self.bank) self.triggerEvent("ND:updateMoney", self.cash, self.bank)
TriggerEvent("ND:moneyChange", self.source, account, amount, "add", reason) TriggerEvent("ND:moneyChange", self.source, account, amount, "add", reason)
end end
logMoney(self.id, "add", account, amount, reason, Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString()))
return true return true
end end
@@ -66,7 +83,7 @@ local function createCharacterTable(info)
function self.depositMoney(amount) function self.depositMoney(amount)
local amount = tonumber(amount) local amount = tonumber(amount)
if not amount or self.cash < amount or amount <= 0 then return end if not amount or self.cash < amount or amount <= 0 then return end
return self.deductMoney("cash", amount, "Deposit") and self.addMoney("bank", amount, "Deposit") return self.deductMoney("cash", amount, locale("deposit")) and self.addMoney("bank", amount, locale("deposit"))
end end
---@param amount number ---@param amount number
@@ -74,7 +91,7 @@ local function createCharacterTable(info)
function self.withdrawMoney(amount) function self.withdrawMoney(amount)
local amount = tonumber(amount) local amount = tonumber(amount)
if not amount or self.bank < amount or amount <= 0 then return end if not amount or self.bank < amount or amount <= 0 then return end
return self.deductMoney("bank", amount, "Withdraw") and self.addMoney("cash", amount, "Withdraw") return self.deductMoney("bank", amount, locale("withdraw")) and self.addMoney("cash", amount, locale("withdraw"))
end end
---@param data string ---@param data string
@@ -104,16 +121,19 @@ local function createCharacterTable(info)
for k, v in pairs(key) do for k, v in pairs(key) do
self[k] = v self[k] = v
if k == "cash" or k == "bank" then 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) TriggerEvent("ND:moneyChange", self.source, k, v, "set", reason)
end end
end end
else else
self[key] = value self[key] = value
if key == "cash" or key == "bank" then 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) TriggerEvent("ND:moneyChange", self.source, key, value, "set", reason)
end end
end end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self)) self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), key)
TriggerEvent("ND:updateCharacter", self, key)
end end
---@param key string|table ---@param key string|table
@@ -127,40 +147,63 @@ local function createCharacterTable(info)
else else
self.metadata[key] = value self.metadata[key] = value
end end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self)) self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "metadata")
TriggerEvent("ND:updateCharacter", self, "metadata")
return self.metadata return self.metadata
end end
-- Completely delete character -- Mark character deleted
function self.delete() function self.delete()
local result = MySQL.query.await("DELETE FROM nd_characters WHERE charid = ?", {self.id}) local result = MySQL.update.await("UPDATE nd_characters SET deleted_at = NOW() WHERE charid = ?", {self.id})
if result and NDCore.players[self.source] then if result and NDCore.players[self.source] then
NDCore.players[self.source] = nil NDCore.players[self.source] = nil
end end
return result return result
end 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 -- Unload and save character
function self.unload() function self.unload()
if not NDCore.players[self.source] then return end if not NDCore.players[self.source] then return end
for name, _ in pairs(self.groups) do for name, _ in pairs(self.groups) do
lib.removePrincipal(self.source, ("group.%s"):format(name)) lib.removePrincipal(self.source, ("group.%s"):format(name))
end end
local ped = GetPlayerPed(self.source)
if ped then self.saveLastLocation()
local coords = GetEntityCoords(ped)
local heading = GetEntityHeading(ped)
self.setMetadata("location", {
x = coords.x,
y = coords.y,
z = coords.z,
w = heading
})
end
self.triggerEvent("ND:characterUnloaded") self.triggerEvent("ND:characterUnloaded")
TriggerEvent("ND:characterUnloaded", self.source, self) TriggerEvent("ND:characterUnloaded", self.source, self)
local saved = self.save() local saved = self.save()
NDCore.players[self.source] = nil NDCore.players[self.source] = nil
return saved return saved
end end
@@ -181,7 +224,8 @@ local function createCharacterTable(info)
return affectedRows > 0 return affectedRows > 0
end end
local affectedRows = MySQL.update.await("UPDATE nd_characters SET name = ?, firstname = ?, lastname = ?, dob = ?, gender = ?, cash = ?, bank = ?, phonenumber = ?, `groups` = ?, metadata = ? WHERE charid = ?", { 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.name,
self.firstname, self.firstname,
self.lastname, self.lastname,
@@ -219,11 +263,24 @@ local function createCharacterTable(info)
} }
if licenses then if licenses then
self.metadata.licenses[#licenses+1] = license 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 else
self.metadata.licenses = {license} self.metadata.licenses = {license}
end end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self)) self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "metadata")
TriggerEvent("ND:updateCharacter", self, "metadata")
end end
function self.getLicense(identifier) function self.getLicense(identifier)
@@ -269,11 +326,7 @@ local function createCharacterTable(info)
function self.notify(...) function self.notify(...)
if not self.source then return end if not self.source then return end
if GetResourceState("ModernHUD") == "started" then TriggerClientEvent("ox_lib:notify", self.source, ...)
TriggerClientEvent("ModernHUD:notify", self.source, ...)
elseif GetResourceState("ox_lib") == "started" then
TriggerClientEvent("ox_lib:notify", self.source, ...)
end
return true return true
end end
@@ -303,11 +356,36 @@ local function createCharacterTable(info)
end end
local roles = self.discord.roles local roles = self.discord.roles
if roles then if roles then
local hasAdmin = false
for i=1, #Config.adminDiscordRoles do for i=1, #Config.adminDiscordRoles do
local role = Config.adminDiscordRoles[i] local role = Config.adminDiscordRoles[i]
if lib.table.contains(roles, role) then if lib.table.contains(roles, role) then
self.addGroup("admin") 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 end
end end
@@ -324,12 +402,23 @@ local function createCharacterTable(info)
---@param rank number ---@param rank number
---@param isJob boolean ---@param isJob boolean
---@return boolean ---@return boolean
function self.addGroup(name, rank, isJob) function self.addGroup(name, rank, customGroup, isJob)
local groupRank = tonumber(rank) or 1 local groupRank = tonumber(rank) or 1
local groupInfo = Config.groups?[name] local groupInfo = Config.groups?[name] or {}
local bossRank = groupInfo and groupInfo.minimumBossRank 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 not groupInfo then return end
if isJob then if isJob then
for _, group in pairs(self.groups) do for _, group in pairs(self.groups) do
group.isJob = nil group.isJob = nil
@@ -338,16 +427,20 @@ local function createCharacterTable(info)
self.groups[name] = { self.groups[name] = {
name = name, name = name,
label = groupInfo and groupInfo.label or name, label = groupLabel,
rankName = groupInfo and groupInfo.ranks[groupRank] or groupRank, rankName = rankName,
rank = groupRank, rank = groupRank,
isJob = isJob, isJob = isJob,
isBoss = bossRank and groupRank >= bossRank isBoss = isBoss,
metadata = metadata
} }
if not isJob then if not isJob then
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self)) self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "groups")
TriggerEvent("ND:updateCharacter", self, "groups")
end end
TriggerEvent("ND:groupAdded", self, self.groups[name])
lib.addPrincipal(self.source, ("group.%s"):format(name)) lib.addPrincipal(self.source, ("group.%s"):format(name))
return self.groups[name] return self.groups[name]
end end
@@ -369,7 +462,9 @@ local function createCharacterTable(info)
end end
self.groups[name] = nil self.groups[name] = nil
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self)) 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)) lib.removePrincipal(self.source, ("group.%s"):format(name))
return group return group
end end
@@ -377,14 +472,20 @@ local function createCharacterTable(info)
---@param name string ---@param name string
---@param rank number ---@param rank number
---@return boolean ---@return boolean
function self.setJob(name, rank) function self.setJob(name, rank, customGroup, keepGroup)
self.removeGroup(self.job) if not keepGroup then
local job = self.addGroup(name, rank, true) self.removeGroup(self.job)
end
local job = self.addGroup(name, rank, customGroup, true)
if job then if job then
self.job = job.name self.job = job.name
self.jobInfo = job self.jobInfo = job
end end
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self))
self.triggerEvent("ND:updateCharacter", removeCharacterFunctions(self), "job")
TriggerEvent("ND:updateCharacter", self, "job")
return job return job
end end
@@ -407,16 +508,48 @@ local function createCharacterTable(info)
return self return self
end 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 src number
---@param info table ---@param info table
---@return table ---@return table
function NDCore.newCharacter(src, info) function NDCore.newCharacter(src, info)
local identifier = GetPlayerIdentifierByType(src, Config.characterIdentifier) local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
if not identifier then return end if not identifier then return end
local userId = PlayersInfo[src] and PlayersInfo[src].userId
local charInfo = { local charInfo = {
source = src, source = src,
identifier = identifier, identifier = identifier,
user_id = userId,
name = GetPlayerName(src) or "", name = GetPlayerName(src) or "",
firstname = info.firstname or "", firstname = info.firstname or "",
lastname = info.lastname or "", lastname = info.lastname or "",
@@ -430,8 +563,21 @@ function NDCore.newCharacter(src, info)
inventory = info.inventory or {} inventory = info.inventory or {}
} }
charInfo.id = MySQL.insert.await("INSERT INTO nd_characters (identifier, name, firstname, lastname, dob, gender, cash, bank, phonenumber, `groups`, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", { 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, identifier,
userId,
charInfo.name, charInfo.name,
charInfo.firstname, charInfo.firstname,
charInfo.lastname, charInfo.lastname,
@@ -441,7 +587,8 @@ function NDCore.newCharacter(src, info)
charInfo.bank, charInfo.bank,
charInfo.phonenumber, charInfo.phonenumber,
json.encode(charInfo.groups), json.encode(charInfo.groups),
json.encode(charInfo.metadata) json.encode(charInfo.metadata),
json.encode(charInfo.inventory)
}) })
return createCharacterTable(charInfo) return createCharacterTable(charInfo)
@@ -452,17 +599,30 @@ end
function NDCore.fetchCharacter(id, src) function NDCore.fetchCharacter(id, src)
local result local result
if src then if src then
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ? and identifier = ?", {id, GetPlayerIdentifierByType(src, Config.characterIdentifier)}) 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 else
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id}) result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id})
end end
if not result then return end local info = result?[1]
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({ return createCharacterTable({
source = src, source = src,
id = info.charid, id = info.charid,
identifier = info.identifier, identifier = info.identifier,
user_id = info.user_id,
name = info.name, name = info.name,
firstname = info.firstname, firstname = info.firstname,
lastname = info.lastname, lastname = info.lastname,
@@ -480,15 +640,27 @@ end
---@param src number ---@param src number
---@return table ---@return table
function NDCore.fetchAllCharacters(src) function NDCore.fetchAllCharacters(src)
local userId = PlayersInfo[src] and PlayersInfo[src].userId
local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
local characters = {} local characters = {}
local result = MySQL.query.await("SELECT * FROM nd_characters WHERE identifier = ?", {GetPlayerIdentifierByType(src, Config.characterIdentifier)})
-- 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 for i=1, #result do
local info = result[i] 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({ characters[info.charid] = createCharacterTable({
source = src, source = src,
id = info.charid, id = info.charid,
identifier = info.identifier, identifier = info.identifier,
user_id = info.user_id,
name = info.name, name = info.name,
firstname = info.firstname, firstname = info.firstname,
lastname = info.lastname, lastname = info.lastname,
@@ -519,3 +691,30 @@ function NDCore.setActiveCharacter(src, id)
character.active() character.active()
return character return character
end 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)

View File

@@ -1,6 +1,7 @@
local ox_inventory local ox_inventory
local inventoryStarted = false local inventoryStarted = false
local spawnedPlayerVehicles = {} local spawnedPlayerVehicles = {}
local vehicleTypes = json.decode(GetResourceKvpString("ND_Core:vehTypes") or "[]")
NDCore.isResourceStarted("ox_inventory", function(started) NDCore.isResourceStarted("ox_inventory", function(started)
inventoryStarted = started inventoryStarted = started
@@ -8,23 +9,62 @@ NDCore.isResourceStarted("ox_inventory", function(started)
ox_inventory = exports.ox_inventory ox_inventory = exports.ox_inventory
end) end)
local function getVehicleType(model) local function getVehicleType(coords, model)
local tempVehicle = CreateVehicle(model, 0, 0, 0, 0, true, true) 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() local time = os.time()
while not DoesEntityExist(tempVehicle) and time-os.time() < 5 do Wait(5) end
while not DoesEntityExist(tempVehicle) and os.time()-time < 5 do Wait(0) end
if not DoesEntityExist(tempVehicle) then return end if not DoesEntityExist(tempVehicle) then return end
local entityType = GetVehicleType(tempVehicle)
local vehType = GetVehicleType(tempVehicle)
DeleteEntity(tempVehicle) DeleteEntity(tempVehicle)
return entityType
vehicleTypes[model] = vehType
SetResourceKvp("ND_Core:vehTypes", json.encode(vehicleTypes))
return vehType
end end
local function generatePlate() local function generatePlate()
local plate = {} local plate = {}
for i=1, 8 do local i = 1
plate[i] = math.random(0, 1) == 1 and string.char(math.random(65, 90)) or math.random(0, 9) 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 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) return table.concat(plate)
end end
@@ -44,11 +84,14 @@ local function getVehicleDatabaseInfo(vehicle)
if not vehicle then return end if not vehicle then return end
local stored = vehicle.stored == 1 local stored = vehicle.stored == 1
local impounded = vehicle.impounded == 1 local impounded = vehicle.impounded == 1
local properties = json.decode(vehicle.properties) or {}
properties.plate = vehicle.plate
return { return {
id = vehicle.id, id = vehicle.id,
owner = vehicle.owner, owner = vehicle.owner,
plate = vehicle.plate, plate = vehicle.plate,
properties = json.decode(vehicle.properties) or {}, properties = properties,
stored = stored, stored = stored,
impounded = impounded, impounded = impounded,
stolen = vehicle.stolen == 1, stolen = vehicle.stolen == 1,
@@ -57,6 +100,14 @@ local function getVehicleDatabaseInfo(vehicle)
} }
end end
function NDCore.generateVehiclePlate()
return generatePlate()
end
function NDCore.generateUniqueVehiclePlate()
return generateVehiclePlate()
end
--- get vehicle information queried from the database by the vehicle id --- get vehicle information queried from the database by the vehicle id
---@param vehicleId string | numb ---@param vehicleId string | numb
---@return table | nil ---@return table | nil
@@ -84,19 +135,53 @@ function NDCore.getVehicle(entity)
netId = NetworkGetNetworkIdFromEntity(entity) 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 --- delete the vehicle
function self.delete(saveProperties) function self.delete(saveProperties, propsToSave)
if not DoesEntityExist(entity) then return end if not DoesEntityExist(entity) then return end
if saveProperties and self.id and self.owner then if saveProperties and self.id and self.owner then
local properties = lib.callback.await("ND_Vehicles:getProps", NetworkGetEntityOwner(entity), self.netId) self.saveProperties(false, propsToSave)
if properties then
if self.properties?.callsign then
properties.callsign = true
end
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id})
end
end end
DeleteEntity(entity) 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 end
--- set vehicle properties --- set vehicle properties
@@ -110,11 +195,12 @@ function NDCore.getVehicle(entity)
properties.callsign = true properties.callsign = true
end end
state.allPropsApplied = false
state.props = properties state.props = properties
self.properties = properties self.properties = properties
if not self.id or not self.owner then return end if not self.id or not self.owner then return end
MySQL.query("UPDATE nd_vehicles SET properties = ? WHERE id = ?", {json.encode(properties), self.id}) self.saveProperties(properties)
end end
--- set vehicle locked/unlocked --- set vehicle locked/unlocked
@@ -134,6 +220,7 @@ function NDCore.getVehicle(entity)
if self.properties then if self.properties then
self.properties.plate = plate self.properties.plate = plate
local state = Entity(entity).state local state = Entity(entity).state
state.allPropsApplied = false
state.props = self.properties state.props = self.properties
end end
@@ -145,9 +232,9 @@ function NDCore.getVehicle(entity)
--- set the vehicle availability status --- set the vehicle availability status
---@param statusType string ---@param statusType string
---@param status boolean ---@param status boolean
function self.setStatus(statusType, status) function self.setStatus(statusType, status, keepEntity)
if not lib.table.contains({"stored", "impounded", "stolen"}, statusType) then return end if not lib.table.contains({"stored", "impounded", "stolen"}, statusType) then return end
if statusType ~= "stolen" then self.delete(true) end if statusType ~= "stolen" and not keepEntity then self.delete(true) end
if not self.id or not self.owner then return end if not self.id or not self.owner then return end
local query = ("UPDATE nd_vehicles SET %s = ? WHERE id = ?"):format(statusType) local query = ("UPDATE nd_vehicles SET %s = ? WHERE id = ?"):format(statusType)
MySQL.query(query, {status and 1 or 0, self.id}) MySQL.query(query, {status and 1 or 0, self.id})
@@ -156,13 +243,22 @@ function NDCore.getVehicle(entity)
function self.setMetadata(key, value) function self.setMetadata(key, value)
self.metadata[key] = value self.metadata[key] = value
if DoesEntityExist(entity) then if DoesEntityExist(entity) then
local state = Entity(self.entity).state local state = Entity(self.entity).state
local metadata = state.metadata
metadata[key] = value if not state.metadata then
state.metadata = metadata state.metadata = self.metadata
else
state.metadata[key] = value
end
end end
if not self.id or not self.owner then return 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}) MySQL.query("UPDATE nd_vehicles SET metadata = ? WHERE id = ?", {json.encode(self.metadata), self.id})
end end
@@ -213,14 +309,15 @@ function NDCore.giveVehicleAccess(source, vehicle, access, info)
end end
if not inventoryStarted or not Config.useInventoryForKeys then return end if not inventoryStarted or not Config.useInventoryForKeys then return end
local plate = info?.plate or GetVehicleNumberPlateText(vehicle)
local model = info?.model or GetEntityModel(vehicle)
local modelName = info?.modelName or model and lib.callback.await("ND_Vehicles:getVehicleModelMakeLabel", source, model) or ""
local hasKey = ox_inventory:GetSlotIdWithItem(source, "keys", { local hasKey = ox_inventory:GetSlotIdWithItem(source, "keys", {
vehId = vehicleId vehId = vehicleId
}) })
if access and not hasKey then 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, { ox_inventory:AddItem(source, "keys", 1, {
vehOwner = owner or state.owner, vehOwner = owner or state.owner,
vehId = vehicleId, vehId = vehicleId,
@@ -253,14 +350,14 @@ function NDCore.createVehicle(info)
end end
local model = info.model or properties.model local model = info.model or properties.model
local vehType = getVehicleType(model) local vehType = getVehicleType(spawnCoords, model)
if not vehType then if not vehType then
return Citizen.Trace("NDCore.createVehicle", "vehType not found") return Citizen.Trace("NDCore.createVehicle", "vehType not found")
end end
local veh = CreateVehicleServerSetter(model, vehType, spawnCoords.x, spawnCoords.y, spawnCoords.z, spawnCoords.w) local veh = CreateVehicleServerSetter(model, vehType, spawnCoords.x, spawnCoords.y, spawnCoords.z, spawnCoords.w)
local time = os.time() local time = os.time()
while not DoesEntityExist(veh) and time-os.time() < 5 do Wait(5) end while not DoesEntityExist(veh) and os.time()-time < 5 do Wait(5) end
if not veh or not DoesEntityExist(veh) then if not veh or not DoesEntityExist(veh) then
return Citizen.Trace("NDCore.createVehicle", "vehicle entity doesn't exist") return Citizen.Trace("NDCore.createVehicle", "vehicle entity doesn't exist")
end end
@@ -270,17 +367,24 @@ function NDCore.createVehicle(info)
state.locked = true state.locked = true
local keys = info.keys or {} local keys = info.keys or {}
if not properties.plate then if not properties.plate or info.plate then
properties.plate = generateVehiclePlate() properties.plate = info.plate or generateVehiclePlate()
end end
if owner then if owner then
keys[owner] = true keys[owner] = true
state.owner = owner state.owner = owner
end end
if info.blipGroups then
state.blipGroups = info.blipGroups
end
state.keys = keys state.keys = keys
state.props = properties
state.id = vehicleId state.id = vehicleId
if not info.ignoreProps then
state.props = properties
end
local vehicleName local vehicleName
if inventoryStarted and Config.useInventoryForKeys then if inventoryStarted and Config.useInventoryForKeys then
for charId, _ in pairs(keys) do for charId, _ in pairs(keys) do
@@ -296,7 +400,7 @@ function NDCore.createVehicle(info)
NDCore.giveVehicleAccess(playerSource, veh, true, { NDCore.giveVehicleAccess(playerSource, veh, true, {
vehicleId = vehicleId, vehicleId = vehicleId,
netId = netId, netId = netId,
plate = properties?.plate, plate = info.plate or properties?.plate,
model = model, model = model,
vehicleName = vehicleName, vehicleName = vehicleName,
owner = owner owner = owner
@@ -305,6 +409,13 @@ function NDCore.createVehicle(info)
end end
end end
for i=1, 3 do
if DoesEntityExist(veh) then
SetVehicleNumberPlateText(veh, properties.plate)
end
Wait(500)
end
return NDCore.getVehicle(veh) return NDCore.getVehicle(veh)
end end
@@ -347,14 +458,14 @@ function NDCore.transferVehicleOwnership(vehicleId, fromSource, toSource)
end end
playerFrom.notify({ playerFrom.notify({
title = "Ownership transfered", title = locale("vehicle_ownership_transfered"),
description = ("Vehicle ownership of %s has been transfered."):format(vehicle.plate), description = locale("vehicle_ownership_transfered2", vehicle.plate),
position = "bottom-right", position = "bottom-right",
type = "success" type = "success"
}) })
playerTo.notify({ playerTo.notify({
title = "Ownership received", title = locale("vehicle_ownership_received"),
description = ("Received vehicle ownership of %s."):format(vehicle.plate), description = locale("vehicle_ownership_received2", vehicle.plate),
position = "bottom-right" position = "bottom-right"
}) })
return true return true
@@ -365,8 +476,8 @@ end
---@param properties table ---@param properties table
---@param stored boolean ---@param stored boolean
---@return vehicleId number ---@return vehicleId number
function NDCore.setVehicleOwned(playerId, properties, stored) function NDCore.setVehicleOwned(playerId, properties, stored, setPlate)
local plate = generateVehiclePlate() local plate = setPlate or generateVehiclePlate()
properties.plate = plate 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}) return MySQL.insert.await("INSERT INTO nd_vehicles (owner, plate, properties, stored) VALUES (?, ?, ?, ?)", {playerId, plate, json.encode(properties), stored and 1 or 0})
end end
@@ -388,15 +499,15 @@ function NDCore.shareVehicleKeys(source, target, vehicle)
local plate = GetVehicleNumberPlateText(vehicle) local plate = GetVehicleNumberPlateText(vehicle)
player.notify({ player.notify({
title = "Keys shared", title = locale("keys_shared"),
description = ("You've shared vehicle keys to %s."):format(plate), description = locale("keys_shared2", plate),
position = "bottom-right", position = "bottom-right",
type = "success", type = "success"
}) })
targetPlayer.notify({ targetPlayer.notify({
title = "Keys received", title = locale("keys_received"),
description = ("Received vehicle keys to %s."):format(plate), description = locale("keys_received2", plate),
position = "bottom-right", position = "bottom-right"
}) })
return true return true
end end
@@ -406,12 +517,13 @@ end
---@param vehicleId number ---@param vehicleId number
---@param coords vector4 ---@param coords vector4
---@return table ---@return table
function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading) function NDCore.spawnOwnedVehicle(source, vehicleId, coords, heading, _vehicle)
local player = NDCore.getPlayer(source) local player = NDCore.getPlayer(source)
if not player then return end if not player then return end
local vehicle = NDCore.getVehicleById(vehicleId) local vehicle = _vehicle or NDCore.getVehicleById(vehicleId)
if not vehicle or not vehicle.available or vehicle.owner ~= player.id then return end 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}) MySQL.query.await("UPDATE nd_vehicles SET stored = ? WHERE id = ?", {0, vehicleId})
@@ -445,16 +557,16 @@ local function toggleVehicleLock(source, entity, nearby, metadata)
if metadata and not metadata.keyEnabled then if metadata and not metadata.keyEnabled then
return player.notify({ return player.notify({
title = "No signal", title = locale("no_signal"),
description = "Vehicle key disabled.", description = locale("veh_key_disabled"),
type = "error", type = "error",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
}) })
elseif not nearby then elseif not nearby then
return player.notify({ return player.notify({
title = "No signal", title = locale("no_signal"),
description = "Vehicle to far away.", description = locale("veh_far_away"),
type = "error", type = "error",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -468,16 +580,16 @@ local function toggleVehicleLock(source, entity, nearby, metadata)
player.triggerEvent("ND_Vehicles:keyFob", vehicle.netId) player.triggerEvent("ND_Vehicles:keyFob", vehicle.netId)
if locked then if locked then
return player.notify({ return player.notify({
title = "LOCKED", title = locale("veh_locked"),
description = "Your vehicle has now been locked.", description = locale("veh_locked2"),
type = "success", type = "success",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
}) })
end end
player.notify({ player.notify({
title = "UNLOCKED", title = locale("veh_unlocked"),
description = "Your vehicle has now been unlocked.", description = locale("veh_unlocked2"),
type = "inform", type = "inform",
position = "bottom-right", position = "bottom-right",
duration = 3000 duration = 3000
@@ -518,6 +630,16 @@ local function lockNearestVehicle(source, vehId, metadata)
end 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. --- inventory keys using item.
exports("keys", function(event, item, inventory, slot, data) exports("keys", function(event, item, inventory, slot, data)
if event ~= "usingItem" or not Config.useInventoryForKeys or not inventoryStarted then return end if event ~= "usingItem" or not Config.useInventoryForKeys or not inventoryStarted then return end
@@ -544,7 +666,9 @@ RegisterCommand("getkeys", function(source, args, rawCommand)
local player = NDCore.getPlayer(source) local player = NDCore.getPlayer(source)
local state = Entity(veh).state local state = Entity(veh).state
local owner = state.owner local owner = state.owner
if not owner or owner ~= player.id then return end 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 local props = state.props
ox_inventory:AddItem(source, "keys", 1, { ox_inventory:AddItem(source, "keys", 1, {
@@ -591,32 +715,32 @@ RegisterNetEvent("ND_Vehicles:toggleVehicleLock", function(netId)
end) end)
-- locking of npc vehicles, if the players spawns inside a vehicle it won't be locked. -- locking of npc vehicles, if the players spawns inside a vehicle it won't be locked.
AddEventHandler("entityCreated", function(entity) -- AddEventHandler("entityCreated", function(entity)
local time = os.time() -- local time = os.time()
while not DoesEntityExist(entity) and os.time()-time < 5 do Wait(50) end -- while not DoesEntityExist(entity) and os.time()-time < 5 do Wait(50) end
if not DoesEntityExist(entity) or GetEntityType(entity) ~= 2 then return end -- if not DoesEntityExist(entity) or GetEntityType(entity) ~= 2 then return end
local state = Entity(entity).state -- local state = Entity(entity).state
if state.owner or state.locked ~= nil then return end -- if state.owner or state.locked ~= nil then return end
time = os.time() -- time = os.time()
local driver = GetPedInVehicleSeat(entity, -1) -- local driver = GetPedInVehicleSeat(entity, -1)
while DoesEntityExist(entity) and driver == 0 and os.time()-time < 2 do -- while DoesEntityExist(entity) and driver == 0 and os.time()-time < 2 do
driver = GetPedInVehicleSeat(entity, -1) -- driver = GetPedInVehicleSeat(entity, -1)
Wait(100) -- Wait(100)
end -- end
if not DoesEntityExist(entity) then return end -- if not DoesEntityExist(entity) then return end
if DoesEntityExist(driver) and IsPedAPlayer(driver) then -- if DoesEntityExist(driver) and IsPedAPlayer(driver) then
state.locked = false -- state.locked = false
state.hotwired = true -- state.hotwired = true
end -- end
if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end -- if math.random(1, 100) <= Config.randomUnlockedVehicleChance then return end
state.locked = true -- state.locked = true
end) -- end)
-- disables inventory vehicles keys, disabled vehicles keys can no longer be used. Kinda like taking the battery out. -- disables inventory vehicles keys, disabled vehicles keys can no longer be used. Kinda like taking the battery out.
RegisterNetEvent("ND_Vehicles:disableKey", function(slot) RegisterNetEvent("ND_Vehicles:disableKey", function(slot)
@@ -658,71 +782,12 @@ RegisterNetEvent("ND_Vehicles:hotwire", function(netId)
state.hotwired = true state.hotwired = true
end) end)
RegisterNetEvent("ND_Vehicles:storeVehicle", function(netId) RegisterNetEvent("ND_Vehicles:propsApplied", function(netId)
local src = source local veh = NetworkGetEntityFromNetworkId(netId)
local vehicle = NDCore.getVehicle(NetworkGetEntityFromNetworkId(netId)) if not veh or not DoesEntityExist(veh) then return end
if not vehicle then return end
local player = NDCore.getPlayer(src) local state = Entity(veh).state
if not vehicle.setStatus("stored", true) or not player or player.id ~= vehicle.owner then state:set("allPropsApplied", true, true)
return player.notify({
title = "Garage",
description = "No owned vehicle found nearby.",
type = "error",
position = "bottom",
duration = 3000
})
end
player.notify({
title = "Garage",
description = "Vehicle stored in garage.",
type = "success",
position = "bottom",
duration = 3000
})
NDCore.giveVehicleAccess(src, vehicle.entity, false, {
vehicleId = vehicle.id,
netId = vehicle.netId,
owner = vehicle.owner
})
end)
local function isParkingAvailable(locations)
for i=1, #locations do
local loc = locations[math.random(1, #locations)]
if #getNearbyVehicles(vec3(loc.x, loc.y, loc.z), 2.0) == 0 then
return loc
end
end
end
RegisterNetEvent("ND_Vehicles:takeVehicle", function(vehId, locations)
local src = source
local vehicle = NDCore.getVehicleById(vehId)
local player = NDCore.getPlayer(src)
if not player or not vehicle or vehicle.owner ~= player.id then return end
if vehicle.impounded then
local reclaimPrice = vehicle.metadata.impoundReclaimPrice or 200
if not player.deductMoney("bank", reclaimPrice, "Vehicle impound reclaim") then
return player.notify({
title = "Impound",
description = ("Price to reclaim is $%d, you don't have enough!"):format(reclaimPrice),
type = "error",
position = "bottom"
})
end
player.notify({
title = "Impound",
description = ("Paid $%d to reclaim vehicle!"):format(reclaimPrice),
type = "success",
position = "bottom"
})
MySQL.query.await("UPDATE nd_vehicles SET impounded = ? WHERE id = ?", {0, vehicle.id})
end
local info = NDCore.spawnOwnedVehicle(src, vehicle.id, isParkingAvailable(locations))
if not info then return end
TriggerClientEvent("ND_Vehicles:blip", src, info.netId, true)
end) end)
lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src) lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src)
@@ -730,3 +795,30 @@ lib.callback.register("ND_Vehicles:getOwnedVehicles", function(src)
if not player then return end if not player then return end
return NDCore.getVehicles(player.id) return NDCore.getVehicles(player.id)
end) 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)

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;
}