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

View File

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

View File

@@ -33,56 +33,8 @@ function NDCore.getConfig(info)
return Config[info]
end
function NDCore.revivePlayer(reset, keepDead)
local usingAmbulance = GetResourceState("ND_Ambulance") == "started"
if not keepDead then
LocalPlayer.state.dead = false
if usingAmbulance then
local state = Player(cache.serverId).state
state:set("isDead", false, true)
end
end
local veh = GetVehiclePedIsIn(cache.ped)
local seat = cache.seat
local coords = GetEntityCoords(cache.ped)
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, GetEntityHeading(cache.ped), true, true, false)
local ped = PlayerPedId()
if cache.ped ~= ped then
DeleteEntity(cache.ped)
ClearAreaOfPeds(coords.x, coords.y, coords.z, 0.2, false)
end
SetEntityInvincible(ped, false)
FreezeEntityPosition(ped, false)
SetEntityVisible(ped, true)
SetEveryoneIgnorePlayer(ped, false)
SetPedCanBeTargetted(ped, true)
SetEntityCanBeDamaged(ped, true)
SetBlockingOfNonTemporaryEvents(ped, false)
SetPedCanRagdollFromPlayerImpact(ped, true)
ClearPedTasksImmediately(ped)
if veh and veh ~= 0 then
SetPedIntoVehicle(ped, veh, seat)
end
if reset and GetPedMovementClipset(ped) == `move_m@injured` then
ClearEntityLastDamageEntity(ped)
SetPedMoveRateOverride(ped, 1.0)
ResetPedMovementClipset(ped, 0)
end
if reset and usingAmbulance then
exports["ND_Ambulance"]:resetBodyDamage()
end
end
function NDCore.notify(...)
if GetResourceState("ModernHUD") == "started" then
exports["ModernHUD"]:notify(...)
elseif GetResourceState("ox_lib") == "started" then
lib.notify(...)
end
lib.notify(...)
end
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 = {}
Config = {
@@ -7,7 +8,7 @@ Config = {
discordAsset = GetConvar("core:discordAsset", "andyyy"),
discordAssetSmall = GetConvar("core:discordAssetSmall", "andyyy"),
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"),
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
disableVehicleAirControl = GetConvarInt("core:disableVehicleAirControl", 1) == 1,
@@ -15,7 +16,8 @@ Config = {
requireKeys = GetConvarInt("core:requireKeys", 1) == 1,
useInventoryForKeys = GetConvarInt("core:useInventoryForKeys", 1) == 1,
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.
@@ -25,11 +27,11 @@ CreateThread(function()
SetDiscordRichPresenceAssetSmall(Config.discordAssetSmall)
SetDiscordRichPresenceAction(0, Config.discordActionText, Config.discordActionLink)
SetDiscordRichPresenceAction(1, Config.discordActionText2, Config.discordActionLink2)
local presenceText = ("Playing: %s"):format(Config.serverName)
local presenceText = locale("discord_text_server", Config.serverName)
while true do
if NDCore.player then
local presence = ("Playing: %s as %s %s"):format(Config.serverName, NDCore.player.firstname, NDCore.player.lastname)
local presenceTextSmall = ("Playing as: %s %s"):format(NDCore.player.firstname, NDCore.player.lastname)
local presence = locale("discord_text", Config.serverName, NDCore.player.firstname, NDCore.player.lastname)
local presenceTextSmall = locale("discord_text_small", NDCore.player.firstname, NDCore.player.lastname)
SetRichPresence(presence)
SetDiscordRichPresenceAssetText(presenceText)
SetDiscordRichPresenceAssetSmallText(presenceTextSmall)
@@ -48,8 +50,8 @@ CreateThread(function()
sleep = 0
BeginScaleformMovieMethodOnFrontendHeader("SET_HEADING_DETAILS")
ScaleformMovieMethodAddParamPlayerNameString(("%s %s"):format(NDCore.player.firstname, NDCore.player.lastname))
ScaleformMovieMethodAddParamTextureNameString(("Cash: $%d"):format(NDCore.player.cash))
ScaleformMovieMethodAddParamTextureNameString(("Bank: $%d"):format(NDCore.player.bank))
ScaleformMovieMethodAddParamTextureNameString(locale("pause_menu_cash", NDCore.player.cash))
ScaleformMovieMethodAddParamTextureNameString(locale("pause_menu_bank", NDCore.player.bank))
EndScaleformMovieMethod()
elseif sleep == 0 then
sleep = 500

View File

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

View File

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

View File

@@ -9,3 +9,5 @@ NDCore = setmetatable({}, {
return self[index]
end
})
return NDCore

134
locales/ar.json Normal file
View File

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

134
locales/da.json Normal file
View File

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

134
locales/de.json Normal file
View File

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

136
locales/en.json Normal file
View File

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

134
locales/es.json Normal file
View File

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

134
locales/fi.json Normal file
View File

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

134
locales/fr.json Normal file
View File

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

134
locales/it.json Normal file
View File

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

134
locales/nl.json Normal file
View File

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

134
locales/no.json Normal file
View File

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

134
locales/ru.json Normal file
View File

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

136
locales/sv.json Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

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