diff --git a/[esx_addons]/esx_garage/client/init.lua b/[esx_addons]/esx_garage/client/init.lua index 08e93dfa..9ff40ef5 100644 --- a/[esx_addons]/esx_garage/client/init.lua +++ b/[esx_addons]/esx_garage/client/init.lua @@ -1,106 +1,91 @@ local created_peds = {} +local return_vehicle = false +local return_vehicle_locale = (TranslateCap('park-vehicle', Keybinds.GetName())) +---@type false | integer +PLAYER_VEHICLE = false GARAGE_POINT = nil -local function openGarage() - if not GARAGE_POINT then - return +-- ~INPUT_94A9125A~ - 0x94A9125A +-- https://tools.povers.fr/hashgenerator/ If you ever change the openGarage name +ESX.RegisterInput('openGarage', 'Opens Garage Menu', 'keyboard', 'E', function() + if GARAGE_POINT then + NUI.OpenGarage() + elseif return_vehicle and PLAYER_VEHICLE then + Actions.HideVehicle() end +end) - local garage = Config.Garages[GARAGE_POINT] +AddEventHandler('esx:enteredVehicle', function(vehicle, plate, seat, displayName, netId) + PLAYER_VEHICLE = vehicle + Mileage.StartLoop() - if not garage then - ESX.ShowNotification('Couldn\'t open garage menu', 'error') - return + if return_vehicle then + exports['esx_textui']:TextUI(return_vehicle_locale) end +end) - ---@type GarageVehicleDB[] - local owned_vehicles = ESX.AwaitServerCallback('esx_garages/getOwnedVehicles') - - ---@type GarageVehicle[] - local wrapped_vehicles = {} - - for i = 1, #owned_vehicles do - local vehicle = owned_vehicles[i] - ---@type ESXVehicleProperties - local properties = json.decode(vehicle.vehicle) - - if not properties then - ESX.ShowNotification('One of your vehicles, have invalid properties. Contact server owner', 'error') - return - end +AddEventHandler('esx:exitedVehicle', function() + PLAYER_VEHICLE = false - local display_name = GetDisplayNameFromVehicleModel(properties.model):lower() - - wrapped_vehicles[i] = { - id = vehicle.plate, - plate = vehicle.plate, - model = vehicle.model or display_name:lower(), - name = display_name, - type = vehicle.type, - garageId = vehicle.parking, - impouned = Config.GarageState.IMPOUNDED == vehicle.stored, - impoundFee = Config.ImpoundFee, - mileage = 0, - fuel = properties.fuelLevel, - engine = properties.engineHealth / 10, - body = properties.bodyHealth / 10, - stored = Config.GarageState.STORED == vehicle.stored, - isFavorite = false, - customName = nil, - lastUsed = 0, - props = properties, - } + if return_vehicle then + exports['esx_textui']:HideUI() end +end) - SendNUIMessage({ - type = 'openGarage', - payload = { - garage = { - id = garage.id, - name = garage.id, - type = 'public', - label = garage.label, - }, - vehicles = wrapped_vehicles - } - }) - - SetNuiFocus(true, true) -end - -ESX.RegisterInput('openGarage', 'Opens Garage Menu', 'keyboard', 'E', openGarage) Citizen.CreateThread(function() for i = 1, #Config.Garages do local garage = Config.Garages[i] local blip = garage.blip - Utils.CreateBlip(garage.entryPoint, blip.sprite, blip.scale, blip.color, TranslateCap('parking_blip_name')) + Utils.CreateBlip(garage.entryPoint, blip.sprite, blip.scale, blip.color, TranslateCap('parking-blip')) created_peds[#created_peds + 1] = Utils.SpawnFrozenPed(garage.ped.model, garage.ped.coords) + + local access_parking = (TranslateCap('access-parking', Keybinds.GetName())) + + ESX.Point:new({ coords = garage.entryPoint, distance = 3, enter = function() GARAGE_POINT = i - exports['esx_textui']:TextUI('Press [E] to open Garage Menu') + exports['esx_textui']:TextUI(access_parking) end, leave = function() GARAGE_POINT = nil exports['esx_textui']:HideUI() end }) + + local vec3_spawn_point = vec3(garage.spawnPoint.x, garage.spawnPoint.y, garage.spawnPoint.z) + + ESX.Point:new({ + coords = vec3_spawn_point, + distance = 50, + enter = function() + return_vehicle = true + if PLAYER_VEHICLE then + exports['esx_textui']:TextUI(return_vehicle_locale) + end + end, + leave = function() + return_vehicle = false + exports['esx_textui']:HideUI() + end + }) end end) - AddEventHandler('onResourceStop', function(resource_name) if resource_name ~= GetCurrentResourceName() then return end + exports['esx_textui']:HideUI() + for i = 1, #created_peds do local ped = created_peds[i] if DoesEntityExist(ped) then @@ -108,20 +93,3 @@ AddEventHandler('onResourceStop', function(resource_name) end end end) - ----@param body { vehicleId: string, isFavorite: boolean } ----@param cb function -RegisterNUICallback('garage:toggleFavorite', function(body, cb) - local result = ESX.AwaitServerCallback('esx_garages/toggleFavorite', body) - cb(result) -end) - ----@param body { hasFocus: boolean, hasCursor: boolean } ----@param cb function -RegisterNUICallback('SetNuiFocus', function(body, cb) - SetNuiFocus(body.hasFocus, body.hasCursor) - - cb({ - success = true - }) -end) diff --git a/[esx_addons]/esx_garage/client/modules/actions.lua b/[esx_addons]/esx_garage/client/modules/actions.lua new file mode 100644 index 00000000..0eed4893 --- /dev/null +++ b/[esx_addons]/esx_garage/client/modules/actions.lua @@ -0,0 +1,182 @@ +Actions = {} + +---@return nil | GaragePayload +function Actions.GaragePayload() + if not GARAGE_POINT then + return + end + + local garage = Config.Garages[GARAGE_POINT] + + if not garage then + ESX.ShowNotification(TranslateCap('cant-open'), 'error') + return + end + + ---@type GarageVehicleDB[] | { success: false, error: string} + local owned_vehicles = ESX.AwaitServerCallback('esx_garages/getOwnedVehicles') + + if owned_vehicles?.error then + return ESX.ShowNotification(owned_vehicles.error, 'error') + end + + ---@type GarageVehicle[] + local wrapped_vehicles = {} + + for i = 1, #owned_vehicles do + local vehicle = owned_vehicles[i] + ---@type ESXVehicleProperties + local properties = json.decode(vehicle.vehicle) + + if not properties then + ESX.ShowNotification(TranslateCap('invalid-vehicle'), 'error') + return + end + + local display_name = GetDisplayNameFromVehicleModel(properties.model):lower() + + wrapped_vehicles[i] = { + id = vehicle.plate, + plate = vehicle.plate, + model = vehicle.model or display_name:lower(), + name = display_name, + type = vehicle.type, + garageId = vehicle.parking, + impouned = Config.GarageState.IMPOUNDED == vehicle.stored, + impoundFee = Config.ImpoundFee, + mileage = vehicle.mileage or 0, + fuel = properties.fuelLevel, + engine = properties.engineHealth / 10, + body = properties.bodyHealth / 10, + stored = Config.GarageState.STORED == vehicle.stored, + isFavorite = (vehicle.second_favorite or vehicle.favorite) == 1, + customName = (vehicle.second_nickname or vehicle.nickname), + lastUsed = vehicle.last_used, + props = properties, + secondOwners = vehicle.second_owners + } + end + + return { + garage = { + id = garage.id, + name = garage.id, + type = 'public', + label = garage.label, + }, + vehicles = wrapped_vehicles + } +end + +---@param data { vehicleId: string, isFavorite: boolean } +---@return CallbackResult +function Actions.ToggleFavorite(data) + local result = ESX.AwaitServerCallback('esx_garages/toggleFavorite', data) + + if result.error then + ESX.ShowNotification(result.error, 'error') + end + + return result +end + +---@param data { vehicleId: string, newName: string } +---@return CallbackResult +function Actions.RenameVehicle(data) + local result = ESX.AwaitServerCallback('esx_garages/renameVehicle', data) + + if result.error then + ESX.ShowNotification(result.error, 'error') + end + + return result +end + +---@param data { vehicleId: string, targetId: string} +---@return CallbackResult +function Actions.TransferVehicle(data) + local result = ESX.AwaitServerCallback('esx_garages/transferVehicle', data) + + if result.error then + ESX.ShowNotification(result.error, 'error') + end + + return result +end + +---@param data { vehicleId: string } +---@return CallbackResult +function Actions.RetrieveVehicle(data) + if not ESX.Game.IsSpawnPointClear(Config.Garages[GARAGE_POINT].spawnPoint, 8) then + ESX.ShowNotification(TranslateCap('no-empty-space'), 'error') + return { + success = false, + error = TranslateCap('no-empty-space') + } + end + + local result = ESX.AwaitServerCallback('esx_garages/retrieveVehicle', data) + + if result.error then + ESX.ShowNotification(result.error, 'error') + end + + return result +end + +---@return CallbackResult | nil +function Actions.HideVehicle() + if not PLAYER_VEHICLE then + return + end + + local properties = ESX.Game.GetVehicleProperties(PLAYER_VEHICLE) + + local result = ESX.AwaitServerCallback('esx_garages/hideVehicle', { + properties = properties + }) + + if result.error then + ESX.ShowNotification(result.error, 'error') + else + PLAYER_VEHICLE = false + end + + return result +end + +---@param data { vehicleId: string, targetId: string } +---@return CallbackResult +function Actions.AddSecondOwner(data) + local result = ESX.AwaitServerCallback('esx_garages/addSecondOwner', data) + + if result.error then + ESX.ShowNotification(result.error, 'error') + end + + return result +end + +---@param data { vehicleId: string, targetId: string } +---@return CallbackResult +function Actions.RemoveSecondOwner(data) + local result = ESX.AwaitServerCallback('esx_garages/removeSecondOwner', data) + + if result.error then + ESX.ShowNotification(result.error, 'error') + end + + return result +end + +---@param data { type: 'impound' | 'find', vehicleId: string } +---@return CallbackResult +function Actions.ImpoundVehicle(data) + local result = ESX.AwaitServerCallback('esx_garages/impoundVehicle', data) + + if result.error then + ESX.ShowNotification(result.error, 'error') + end + + return result +end diff --git a/[esx_addons]/esx_garage/client/modules/keybinds.lua b/[esx_addons]/esx_garage/client/modules/keybinds.lua new file mode 100644 index 00000000..c14f0baf --- /dev/null +++ b/[esx_addons]/esx_garage/client/modules/keybinds.lua @@ -0,0 +1,81 @@ +-- https://forum.cfx.re/t/how-to-advanced-registerkeymapping-with-nuifocus-in-true/4778790 +local COMMAND_HASH = 0x94A9125A +local SPECIAL_KEYCODES = { + ['b_116'] = 'WheelMouseMove.Up', + ['b_115'] = 'WheelMouseMove.Up', + ['b_100'] = 'MouseClick.LeftClick', + ['b_101'] = 'MouseClick.RightClick', + ['b_102'] = 'MouseClick.MiddleClick', + ['b_103'] = 'MouseClick.ExtraBtn1', + ['b_104'] = 'MouseClick.ExtraBtn2', + ['b_105'] = 'MouseClick.ExtraBtn3', + ['b_106'] = 'MouseClick.ExtraBtn4', + ['b_107'] = 'MouseClick.ExtraBtn5', + ['b_108'] = 'MouseClick.ExtraBtn6', + ['b_109'] = 'MouseClick.ExtraBtn7', + ['b_110'] = 'MouseClick.ExtraBtn8', + ['b_1015'] = 'AltLeft', + ['b_1000'] = 'ShiftLeft', + ['b_2000'] = 'Space', + ['b_1013'] = 'ControlLeft', + ['b_1002'] = 'Tab', + ['b_1014'] = 'ControlRight', + ['b_140'] = 'Numpad4', + ['b_142'] = 'Numpad6', + ['b_144'] = 'Numpad8', + ['b_141'] = 'Numpad5', + ['b_143'] = 'Numpad7', + ['b_145'] = 'Numpad9', + ['b_200'] = 'Insert', + ['b_1012'] = 'CapsLock', + ['b_170'] = 'F1', + ['b_171'] = 'F2', + ['b_172'] = 'F3', + ['b_173'] = 'F4', + ['b_174'] = 'F5', + ['b_175'] = 'F6', + ['b_176'] = 'F7', + ['b_177'] = 'F8', + ['b_178'] = 'F9', + ['b_179'] = 'F10', + ['b_180'] = 'F11', + ['b_181'] = 'F12', + ['b_194'] = 'ArrowUp', + ['b_195'] = 'ArrowDown', + ['b_196'] = 'ArrowLeft', + ['b_197'] = 'ArrowRight', + ['b_1003'] = 'Enter', + ['b_1004'] = 'Backspace', + ['b_198'] = 'Delete', + ['b_199'] = 'Escape', + ['b_1009'] = 'PageUp', + ['b_1010'] = 'PageDown', + ['b_1008'] = 'Home', + ['b_131'] = 'NumpadAdd', + ['b_130'] = 'NumpadSubstract', + ['b_211'] = 'Insert', + ['b_210'] = 'Delete', + ['b_212'] = 'End', + ['b_1055'] = 'Home', + ['b_1056'] = 'PageUp', +} + +Keybinds = {} + +local function getKeyName() + return GetControlInstructionalButton(2, COMMAND_HASH, true) +end + +---@param key string +---@return string +local function translateKey(key) + if (string.find(key, "t_")) then + return (string.gsub(key, "t_", "")) + else + return SPECIAL_KEYCODES[key] + end +end + +function Keybinds.GetName() + return translateKey(getKeyName()) +end diff --git a/[esx_addons]/esx_garage/client/modules/mileage.lua b/[esx_addons]/esx_garage/client/modules/mileage.lua new file mode 100644 index 00000000..559e3312 --- /dev/null +++ b/[esx_addons]/esx_garage/client/modules/mileage.lua @@ -0,0 +1,37 @@ +local ONE_MILE_IN_METERS = 1609 + +Mileage = {} + +function Mileage.StartLoop() + if not PLAYER_VEHICLE then + return + end + + local miles = Entity(PLAYER_VEHICLE).state.mileage or 0 + local last_pos = GetEntityCoords(PLAYER_VEHICLE) + + local player_ped = PlayerPedId() + + while true do + if not PLAYER_VEHICLE then + break + end + + local new_pos = GetEntityCoords(PLAYER_VEHICLE) + + if GetPedInVehicleSeat(PLAYER_VEHICLE, -1) == player_ped and GetIsVehicleEngineRunning(PLAYER_VEHICLE) then + local dist = #(new_pos - last_pos) + + local miles_done = dist / ONE_MILE_IN_METERS + miles += miles_done + + miles = ESX.Math.Round(miles, 4) + + Entity(PLAYER_VEHICLE).state:set('mileage', miles, true) + end + + last_pos = new_pos + + Citizen.Wait(1000) + end +end diff --git a/[esx_addons]/esx_garage/client/modules/nui.lua b/[esx_addons]/esx_garage/client/modules/nui.lua new file mode 100644 index 00000000..d69f168a --- /dev/null +++ b/[esx_addons]/esx_garage/client/modules/nui.lua @@ -0,0 +1,79 @@ +NUI = {} + +NUI.OpenGarage = function() + local payload = Actions.GaragePayload() + + if not payload then + return + end + + SendNUIMessage({ + type = 'openGarage', + payload = payload + }) + + SetNuiFocus(true, true) +end + +---@param body { vehicleId: string, isFavorite: boolean } +---@param cb function +RegisterNUICallback('garage:toggleFavorite', function(body, cb) + cb(Actions.ToggleFavorite(body)) +end) + +---@param body { vehicleId: string, newName: string } +---@param cb function +RegisterNUICallback('garage:renameVehicle', function(body, cb) + cb(Actions.RenameVehicle(body)) +end) +---@param body { vehicleId: string, targetId: string} +---@param cb function +RegisterNuiCallback('garage:transferVehicle', function(body, cb) + cb(Actions.TransferVehicle(body)) +end) + +---@param body { vehicleId: string } +---@param cb function +RegisterNuiCallback('garage:retrieveVehicle', function(body, cb) + cb(Actions.RetrieveVehicle(body)) +end) + +---@param body { vehicleId: string, targetId: string} +---@param cb function +RegisterNuiCallback('garage:addSecondOwner', function(body, cb) + cb(Actions.AddSecondOwner(body)) +end) + +---@param body { vehicleId: string, targetId: string} +---@param cb function +RegisterNuiCallback('garage:removeSecondOwner', function(body, cb) + cb(Actions.RemoveSecondOwner(body)) +end) + +---@param body { type: 'impound' | 'find', vehicleId: string } +---@param cb function +RegisterNuiCallback('garage:impoundVehicle', function(body, cb) + cb(Actions.ImpoundVehicle(body)) +end) + +---@param cb function +RegisterNUICallback('garage:closeUI', function(_, cb) + SetNuiFocus(false, false) + + SendNUIMessage({ + type = 'closeGarage', + payload = {} + }) + + cb({ success = true }) +end) + +---@param body { hasFocus: boolean, hasCursor: boolean } +---@param cb function +RegisterNUICallback('SetNuiFocus', function(body, cb) + SetNuiFocus(body.hasFocus, body.hasCursor) + + cb({ + success = true + }) +end) diff --git a/[esx_addons]/esx_garage/client/modules/utils.lua b/[esx_addons]/esx_garage/client/modules/utils.lua index 5266c338..80df51b4 100644 --- a/[esx_addons]/esx_garage/client/modules/utils.lua +++ b/[esx_addons]/esx_garage/client/modules/utils.lua @@ -23,7 +23,7 @@ function Utils.CreateBlip(coords, sprite, scale, color, name) end ---@param model string | number ----@param coords any +---@param coords vector4 function Utils.SpawnFrozenPed(model, coords) local model_hash = ESX.Streaming.RequestModel(model) @@ -31,6 +31,10 @@ function Utils.SpawnFrozenPed(model, coords) return end + RequestCollisionAtCoord(coords.x, coords.y, coords.y) + + Citizen.Wait(500) + local _, correct_z = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z, false) local ped = CreatePed(0, model_hash, coords.x, coords.y, correct_z, coords.w, false, true) diff --git a/[esx_addons]/esx_garage/config.lua b/[esx_addons]/esx_garage/config.lua index dd3b5fa6..c3d4c7a0 100644 --- a/[esx_addons]/esx_garage/config.lua +++ b/[esx_addons]/esx_garage/config.lua @@ -1,7 +1,7 @@ Config = {} Config.Locale = GetConvar('esx:locale', 'en') ----@type Garage[] +---@type GarageConfig[] Config.Garages = { { id = 'vespucci_boulevard', @@ -17,8 +17,6 @@ Config.Garages = { coords = vec4(-282.8655, -888.8463, 31.0806, 72.7597), model = `s_m_m_gentransport` }, - - -- impoundedName = 'LosSantos', }, } @@ -30,65 +28,4 @@ Config.GarageState = { } Config.ImpoundFee = 400 - --- Config.Impounds = { --- LosSantos = { --- GetOutPoint = { --- x = 400.7, --- y = -1630.5, --- z = 29.3, --- }, --- SpawnPoint = { --- x = 401.9, --- y = -1647.4, --- z = 29.2, --- heading = 323.3, --- }, --- Sprite = 524, --- Scale = 0.8, --- Colour = 1, --- Cost = 3000, --- }, --- PaletoBay = { --- GetOutPoint = { --- x = -211.4, --- y = 6206.5, --- z = 31.4, --- }, --- SpawnPoint = { --- x = -204.6, --- y = 6221.6, --- z = 30.5, --- heading = 227.2, --- }, --- Sprite = 524, --- Scale = 0.8, --- Colour = 1, --- Cost = 3000, --- }, --- SandyShores = { --- GetOutPoint = { --- x = 1728.2, --- y = 3709.3, --- z = 33.2, --- }, --- SpawnPoint = { --- x = 1722.7, --- y = 3713.6, --- z = 33.2, --- heading = 19.9, --- }, --- Sprite = 524, --- Scale = 0.8, --- Colour = 1, --- Cost = 3000, --- }, --- } - -exports('getGarages', function() - return Config.Garages -end) - --- exports('getImpounds', function() --- return Config.Impounds --- end) +Config.FindVehicleFee = 300 diff --git a/[esx_addons]/esx_garage/esx_garage.sql b/[esx_addons]/esx_garage/esx_garage.sql index d5711282..50419ad1 100644 --- a/[esx_addons]/esx_garage/esx_garage.sql +++ b/[esx_addons]/esx_garage/esx_garage.sql @@ -1,3 +1,18 @@ -DROP TABLE `user_parkings`; -ALTER TABLE `owned_vehicles` ADD `parking` VARCHAR(60) NULL AFTER `stored`; -ALTER TABLE `owned_vehicles` ADD `pound` VARCHAR(60) NULL AFTER `parking`; \ No newline at end of file +CREATE TABLE `second_owners` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `identifier` VARCHAR(60) NOT NULL COLLATE 'utf8mb3_general_ci', + `plate` VARCHAR(12) NOT NULL COLLATE 'utf8mb3_general_ci', + `favorite` TINYINT(4) NOT NULL DEFAULT '0', + `nickname` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci', + PRIMARY KEY (`id`) USING BTREE, + INDEX `identifier` (`identifier`) USING BTREE, + INDEX `plate_fk` (`plate`) USING BTREE, + CONSTRAINT `identifier_fk` FOREIGN KEY (`identifier`) REFERENCES `users` (`identifier`) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT `plate_fk` FOREIGN KEY (`plate`) REFERENCES `owned_vehicles` (`plate`) ON UPDATE CASCADE ON DELETE CASCADE +) + +ALTER TABLE `owned_vehicles` +ADD COLUMN `mileage` DECIMAL(20,6) NULL DEFAULT NULL AFTER `last_used`, +ADD COLUMN `model` VARCHAR(32) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci' AFTER `trunk`, +ADD COLUMN `last_used` BIGINT(20) NULL DEFAULT NULL AFTER `nickname`, +ADD COLUMN `nickname` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb3_general_ci' AFTER `favorite`; diff --git a/[esx_addons]/esx_garage/fxmanifest.lua b/[esx_addons]/esx_garage/fxmanifest.lua index 19336fa1..f7ee3663 100644 --- a/[esx_addons]/esx_garage/fxmanifest.lua +++ b/[esx_addons]/esx_garage/fxmanifest.lua @@ -17,23 +17,24 @@ files { 'web/dist/index.html', 'web/dist/**/*', 'vehicleImages/**/*', - 'locales/*.lua', } shared_scripts { '@es_extended/imports.lua', '@es_extended/locale.lua', - 'locales/*.lua', 'config.lua', } server_scripts { '@oxmysql/lib/MySQL.lua', - 'server/init.lua' + 'server/modules/**/*', + 'server/init.lua', + 'locales/*.lua', } client_scripts { '@es_extended/locale.lua', 'client/modules/**/*', - 'client/init.lua' + 'client/init.lua', + 'locales/*.lua', } diff --git a/[esx_addons]/esx_garage/locales/da.lua b/[esx_addons]/esx_garage/locales/da.lua deleted file mode 100644 index 390be8fa..00000000 --- a/[esx_addons]/esx_garage/locales/da.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["en"] = { - ["parking_blip_name"] = 'Garage', - ["Impound_blip_name"] = 'Beslaglæggelse', - ["access_parking"] = 'tryk på [E] for at få adgang til parkeringspladsen.', - ["access_Impound"] = 'tryk på [E] for at få adgang til deponeringen.', - ["park_veh"] = 'tryk på [E] for at parkere køretøjet.', - ["not_owning_veh"] = 'Du ejer ikke dette køretøj!', - ['veh_released'] = 'Succesfuldt hentet køretøj.', - ['veh_Impound_released'] = 'Succesfuldt hentet køretøj fra beslaglæggelse.', - ['veh_stored'] = 'Succesfuldt gemt køretøj', - ['veh_impounded'] = 'Beslaglage køretøj', - ['no_veh_parking'] = 'Ingen køretøjer opbevaret her.', - ['no_veh_impounded'] = 'Ingen beslaglagte køretøjer.', - ['no_veh_Impound'] = 'du har ingen beslaglagte køretøjer.', - ['veh_model'] = 'Model', - ['veh_plate'] = 'Nummerplade', - ['veh_condition'] = 'Stand', - ['veh_action'] = 'Muligheder', - ['impound_action'] = 'Beslaglæg', - ['veh_exit'] = 'Hent køretøj', - ['pay_impound'] = 'Betal beslaglægnings afgiften', - ['veh_block'] = 'Kan ikke hente køretøjet, Spawn-positionen er blokeret.', - ['pay_Impound_bill'] = 'Betalte ~g~DKK%s~s~ med succes til beslaglæggelsen.', - ['missing_money'] = 'Du har ikke penge nok.' -} \ No newline at end of file diff --git a/[esx_addons]/esx_garage/locales/de.lua b/[esx_addons]/esx_garage/locales/de.lua deleted file mode 100644 index 9a489896..00000000 --- a/[esx_addons]/esx_garage/locales/de.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["de"] = { - ["parking_blip_name"] = 'Garage', - ["Impound_blip_name"] = 'Abschlepphof', - ["access_parking"] = 'Drücke [E] um auf die Garage zuzugreifen.', - ["access_Impound"] = 'Drücke [E] um auf den Abschlepphof zuzugreifen.', - ["park_veh"] = 'Drücke [E] um das Fahrzeug einzuparken.', - ["not_owning_veh"] = 'Dies ist nicht dein Fahrzeug!', - ['veh_released'] = 'Erfolgreich zurückgeholtes Fahrzeug.', - ['veh_Impound_released'] = 'Du hastb erfolgreich dein Fahrzeug aus dem Abschlepphof geholt!', - ['veh_stored'] = 'Fahrzeug erfolgreich eingeparkt', - ['veh_impounded'] = 'Abgeschlepptes Fahrzeug', - ['no_veh_parking'] = 'Keine Fahrzeuge sind hier eingeparkt.', - ['no_veh_impounded'] = 'Kein abgeschlepptes Fahrzeug.', - ['no_veh_Impound'] = 'Du hast keine abgeschleppten Fahrzeuge.', - ['veh_model'] = 'Model', - ['veh_plate'] = 'Kennzeichen', - ['veh_condition'] = 'Condition', - ['veh_action'] = 'Aktion', - ['impound_action'] = 'Abschlepphof', - ['veh_exit'] = 'Fahrzeug zurückholen', - ['pay_impound'] = 'Abschleppkosten bezahlen', - ['veh_block'] = 'Fahrzeug konnte nicht zurückgeholt werden, Ausparkpunkt belegt.', - ['pay_Impound_bill'] = 'Du hast erfolgreich ~g~%s€~s~ an den Abschlepphof gezahlt.', - ['missing_money'] = 'Du hast nicht genug Geld!' -} diff --git a/[esx_addons]/esx_garage/locales/en.lua b/[esx_addons]/esx_garage/locales/en.lua index 2ec63652..cf857612 100644 --- a/[esx_addons]/esx_garage/locales/en.lua +++ b/[esx_addons]/esx_garage/locales/en.lua @@ -1,10 +1,24 @@ -Locales["en"] = { - ["parking_blip_name"] = 'Garage', - ["Impound_blip_name"] = 'Impound', - ["access_parking"] = 'press [E] to access the car park.', - ["access_Impound"] = 'press [E] to access the impound.', - ["park_veh"] = 'press [E] to park the vehicle.', +Locales['en'] = { + ['cant-open'] = 'Couldn\'t open garage menu', + ['invalid-vehicle'] = 'One of your vehicles, have invalid properties. Contact server owner', + ['no-empty-space'] = 'No Empty Space!', + ["parking-blip"] = 'Garage', + ["access-parking"] = 'Press [%s] to open Garage Menu', + ["park-vehicle"] = 'Press [%s] to Hide Vehicle', ["not_owning_veh"] = 'You do not own this vehicle!', + ["not-allowed"] = 'Not Allowed', + ["not-permitted"] = 'Not Permitted', + ['failed_find'] = 'Failed Find', + ['failed_create'] = 'Failed Create', + ['not_in_vehicle'] = 'Not in vehicle', + ['not_in_garage'] = 'Vehicle is not in garage', + ['is_second_owner'] = 'This player is already a second owner', + ['not-found-player'] = 'Couldn\'t find player!', + ['to-your-self'] = 'You can\'t give vehicle to yourself!', + ['not-the-owner'] = 'You are not the owner', + ['already-second-owner'] = 'This player is alread a second owner', + ['remove-from-yourself'] = 'You can\'t remove a second owner from yourself', + ['not-a-second-owner'] = 'The player is not a second owner!', ['veh_released'] = 'Successfully Retrieved Vehicle.', ['veh_Impound_released'] = 'Successfully Retrieved Vehicle From Impound.', ['veh_stored'] = 'Successfully Stored Vehicle', @@ -21,5 +35,6 @@ Locales["en"] = { ['pay_impound'] = 'Pay Impound', ['veh_block'] = 'Unable to retrieve vehicle, Spawn Position is blocked.', ['pay_Impound_bill'] = 'Successfully Paid ~g~$%s~s~ to the Impound.', - ['missing_money'] = 'You don`t have enough money.' + ['missing_money'] = 'You don`t have enough money.', + ['internal_error'] = 'Internal Error' } diff --git a/[esx_addons]/esx_garage/locales/fr.lua b/[esx_addons]/esx_garage/locales/fr.lua deleted file mode 100644 index 6ac2c165..00000000 --- a/[esx_addons]/esx_garage/locales/fr.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["fr"] = { - ["parking_blip_name"] = 'Garage', - ["Impound_blip_name"] = 'Fourrière', - ["access_parking"] = "Appuyez sur [E] pour accéder au garage.", - ["access_Impound"] = 'Appuyez sur [E] pour accéder à la fourrière.', - ["park_veh"] = "Appuyez sur [E] pour ranger le véhicule.", - ["not_owning_veh"] = "Ce véhicule ne vous appartient pas!", - ['veh_released'] = 'Le véhicule a bien été sorti du garage.', - ['veh_Impound_released'] = 'Le véhicule a été sorti de la fourrière.', - ['veh_stored'] = 'Le véhicule a bien été rangé', - ['veh_impounded'] = 'Le véhicule a bien été mis en fourrière.', - ['no_veh_parking'] = 'Il n\'y a aucun véhicule dans ce garage.', - ['no_veh_impounded'] = 'Il n\'y a aucun véhicule en fourrière.', - ['no_veh_Impound'] = 'Vous n\'avez aucun véhicule en fourrière.', - ['veh_model'] = 'Modèle', - ['veh_plate'] = 'Plaque d\'immatriculation', - ['veh_condition'] = 'État', - ['veh_action'] = 'Action', - ['impound_action'] = 'Mettre en fourrière', - ['veh_exit'] = 'Sortir le véhicule', - ['pay_Impound'] = 'Payer la fourrière', - ['veh_block'] = 'Impossible de sortir le véhicule, le point d\'apparition est encombré.', - ['pay_Impound_bill'] = 'Vous avez bien payé $%s à la fourrière.', - ['missing_money'] = 'Vous n\'avez pas assez d\'argent.' -} diff --git a/[esx_addons]/esx_garage/locales/hu.lua b/[esx_addons]/esx_garage/locales/hu.lua deleted file mode 100644 index 727b4a19..00000000 --- a/[esx_addons]/esx_garage/locales/hu.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["hu"] = { - ["parking_blip_name"] = 'Garázs', - ["Impound_blip_name"] = 'Lefoglaltak', - ["access_parking"] = 'Nyomd meg az [E] gombot a parkoló megtekintéséhez.', - ["access_Impound"] = 'Nyomd meg az [E] gombot a lefoglaltak megtekintéséhez.', - ["park_veh"] = 'Nyomd meg az [E] gombot a jármű parkolásához.', - ["not_owning_veh"] = 'Ez a jármű nem a te tulajdonod!', - ['veh_released'] = 'Sikeresen kivetted a járművet.', - ['veh_Impound_released'] = 'Jármű sikeresen kiváltva a legfolalásból.', - ['veh_stored'] = 'Jármű sikeresen tárolva', - ['veh_impounded'] = 'Lefoglalt jármű megjelölve a térképen', - ['no_veh_parking'] = 'Nincsenek itt tárolt járművek.', - ['no_veh_impounded'] = 'Nincs lefoglalt jármű.', - ['no_veh_Impound'] = 'nincs lefoglalt járműved.', - ['veh_model'] = 'Model', - ['veh_plate'] = 'Rendszám', - ['veh_condition'] = 'Állapot', - ['veh_action'] = 'Művelet', - ['impound_action'] = 'Lefoglal', - ['veh_exit'] = 'Jármű visszakérése', - ['pay_impound'] = 'Lefoglalás fizetése', - ['veh_block'] = 'Jármű lekérés sikertelen, a spawn hely foglalt.', - ['pay_Impound_bill'] = 'Kiváltottad a járművet ~g~%s$~s~-ért', - ['missing_money'] = 'Nincs elég pénzed!' -} diff --git a/[esx_addons]/esx_garage/locales/it.lua b/[esx_addons]/esx_garage/locales/it.lua deleted file mode 100644 index 3f2bd56e..00000000 --- a/[esx_addons]/esx_garage/locales/it.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["it"] = { - ["parking_blip_name"] = 'Garage', - ["Impound_blip_name"] = 'Sequestro', - ["access_parking"] = 'premi [E] per accedere al parcheggio.', - ["access_Impound"] = 'premi [E] per accedere al sequestro.', - ["park_veh"] = 'premi [E] per parcheggiare il veicolo.', - ["not_owning_veh"] = 'non possiedi questo veicolo!', - ['veh_released' ] = ' Veicolo recuperato con successo. ' , - ['veh_Impound_released'] = ' Veicolo recuperato con successo dal sequestro. ' , - ['veh_stored'] = ' Veicolo depositato con successo ' , - ['veh_impounded'] = ' Veicolo sequestrato ' , - ['no_veh_parking'] = ' Nessun veicolo immagazzinato qui. ' , - ['no_veh_impounded'] = ' Nessun veicolo sequestrato. ' , - ['no_veh_Impound'] = ' non hai veicoli sequestrati. ' , - ['veh_model'] = ' Modello ' , - ['veh_plate'] = ' Targa ' , - ['veh_condition'] = ' Condizione Veicolo ' , - ['veh_action ' ] = ' Azione ' , - ['impound_action' ] = ' Sequestro ' , - ['veh_exit' ] = ' Ritira veicolo ' , - ['pay_impound' ] = ' Paga sequestro ' , - ['veh_block' ] = ' Impossibile recuperare il veicolo, la posizione di spawn è bloccata. ' , - ['pay_Impound_bill' ] = 'hai pagato ~g~$%s~s~ al sequestro. ' , - ['missing_money' ] = ' Non hai abbastanza soldi. ' -} diff --git a/[esx_addons]/esx_garage/locales/nl.lua b/[esx_addons]/esx_garage/locales/nl.lua deleted file mode 100644 index 81502cf4..00000000 --- a/[esx_addons]/esx_garage/locales/nl.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["nl"] = { - ["parking_blip_name"] = 'Garage', - ["Impound_blip_name"] = 'In beslag genomen', - ["access_parking"] = 'klik op [E] om je garage te gebruiken.', - ["access_Impound"] = 'klik op [E] om je in beslag genomen voertuigen te krijgen.', - ["park_veh"] = 'klik op [E] om je voertuig te parkeren.', - ["not_owning_veh"] = 'Dit is niet jouw voertuig!', - ['veh_released'] = 'Succesvol je voertuig opgehaald.', - ['veh_Impound_released'] = 'Succesvol je voertuig terug gekregen van de politie.', - ['veh_stored'] = 'Succesvol je voertuig geparkeerd', - ['veh_impounded'] = 'In beslag genomen voertuig', - ['no_veh_parking'] = 'Er staan hier geen voertuigen.', - ['no_veh_impounded'] = 'Geen in beslag genomen voertuigen.', - ['no_veh_Impound'] = 'Er zijn geen voertuigen in beslag genomen.', - ['veh_model'] = 'Model', - ['veh_plate'] = 'Kenteken', - ['veh_condition'] = 'Conditie', - ['veh_action'] = 'Actie', - ['impound_action'] = 'Neem in beslag', - ['veh_exit'] = 'Haal voertuig op', - ['pay_impound'] = 'Betaal boete', - ['veh_block'] = 'Kan het voertuig niet ophalen, iets blokkeert de uitgang.', - ['pay_Impound_bill'] = 'Succesvol ~g~€%s~s~ betaald aan de politie.', - ['missing_money'] = 'Je hebt niet genoeg geld.' -} diff --git a/[esx_addons]/esx_garage/locales/sl.lua b/[esx_addons]/esx_garage/locales/sl.lua deleted file mode 100644 index b21854ef..00000000 --- a/[esx_addons]/esx_garage/locales/sl.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["sl"] = { - ["parking_blip_name"] = 'Garaza', - ["Impound_blip_name"] = 'Zasezba', - ["access_parking"] = 'Pritisni [E] Da odprete Garazo.', - ["access_Impound"] = 'Pritisni [E] Da odprete Zasezbo vozil.', - ["park_veh"] = 'Pritisni [E] da parkirate vase vozilo.', - ["not_owning_veh"] = 'Vi niste lastnik tega vozila', - ['veh_released'] = 'Uspesno predano vozilo.', - ['veh_Impound_released'] = 'Uspesno predano vozilo iz zasezbe.', - ['veh_stored'] = 'Uspesno pospravljeno vozilo', - ['veh_impounded'] = 'Zasezena vozila', - ['no_veh_parking'] = 'Ni vozil pospravljenih tukaj.', - ['no_veh_impounded'] = 'Ni zasezenih vozil.', - ['no_veh_Impound'] = 'Vi nimate zasezenih vozil.', - ['veh_model'] = 'Model', - ['veh_plate'] = 'Tablica', - ['veh_condition'] = 'Condition', - ['veh_action'] = 'Delo', - ['impound_action'] = 'Zasezba', - ['veh_exit'] = 'Pridobivanje vozila', - ['pay_impound'] = 'Placaj zaseg', - ['veh_block'] = 'Nemoremo pridobiti vozila! Odmaknite vse kar je napoti.', - ['pay_Impound_bill'] = 'Uspesno placano~g~$%s~s~ na zasezbo.', - ['missing_money'] = 'Vi nimate dovolj denarja.' -} diff --git a/[esx_addons]/esx_garage/locales/sr.lua b/[esx_addons]/esx_garage/locales/sr.lua deleted file mode 100644 index 2de2053a..00000000 --- a/[esx_addons]/esx_garage/locales/sr.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["sr"] = { - ["parking_blip_name"] = 'Garaža', - ["Impound_blip_name"] = 'Zaplena', - ["access_parking"] = 'Pritisnite [E] da pristupite garaži.', - ["access_Impound"] = 'Pritisnite [E] da pristupite zapleni.', - ["park_veh"] = 'Pritisnite [E] da parkirate vozilo.', - ["not_owning_veh"] = 'Ovo vozilo nije vaše!', - ['veh_released'] = 'Uspešno ste vratili vozilo.', - ['veh_Impound_released'] = 'Uspešno ste vratili vozilo sa zaplene.', - ['veh_stored'] = 'Uspešno ste parkirali vozilo', - ['veh_impounded'] = 'Vozilo je zaplenjeno', - ['no_veh_parking'] = 'Nemate parkiranih vozila ovde.', - ['no_veh_impounded'] = 'Nema zaplenjenih vozila.', - ['no_veh_Impound'] = 'Nemate zaplenjenih vozila.', - ['veh_model'] = 'Model', - ['veh_plate'] = 'Tablice', - ['veh_condition'] = 'Stanje', - ['veh_action'] = 'Akcije', - ['impound_action'] = 'Zapleni', - ['veh_exit'] = 'Vrati Vozilo', - ['pay_impound'] = 'Plati Zaplenu', - ['veh_block'] = 'Ne možete vratiti vozilo , parking mesto je zauzeto.', - ['pay_Impound_bill'] = 'Uspešno plaćeno ~g~$%s~s~ za zaplenu vozila', - ['missing_money'] = 'Nemate dovoljno novca.' -} diff --git a/[esx_addons]/esx_garage/locales/tr.lua b/[esx_addons]/esx_garage/locales/tr.lua deleted file mode 100644 index 42517e9a..00000000 --- a/[esx_addons]/esx_garage/locales/tr.lua +++ /dev/null @@ -1,25 +0,0 @@ -Locales["tr"] = { - ["parking_blip_name"] = 'Garaj', - ["Impound_blip_name"] = 'Depo', - ["access_parking"] = 'Araç parkına erişmek için [E] tuşuna basın.', - ["access_Impound"] = 'Depoya erişmek için [E] tuşuna basın.', - ["park_veh"] = 'Araç park etmek için [E] tuşuna basın.', - ["not_owning_veh"] = 'Bu aracın sahibi değilsiniz!', - ['veh_released'] = 'Araç başarıyla alındı.', - ['veh_Impound_released'] = 'Araç başarıyla depodan alındı.', - ['veh_stored'] = 'Araç başarıyla garaja kaldırıldı', - ['veh_impounded'] = 'Araç depolandı', - ['no_veh_parking'] = 'Burada saklanmış araç yok.', - ['no_veh_impounded'] = 'Depolanmış araç yok.', - ['no_veh_Impound'] = 'Depolanmış aracınız bulunmamaktadır.', - ['veh_model'] = 'Model', - ['veh_plate'] = 'Plaka', - ['veh_condition'] = 'Durum', - ['veh_action'] = 'İşlem', - ['impound_action'] = 'Depo', - ['veh_exit'] = 'Araç al', - ['pay_impound'] = 'Depo ücretini öde', - ['veh_block'] = 'Araç alınamıyor, Spawn konumu engellenmiş.', - ['pay_Impound_bill'] = 'Depo ücreti başarıyla ödendi ~g~$%s~s~.', - ['missing_money'] = 'Yeterli paranız yok.' -} diff --git a/[esx_addons]/esx_garage/message (21).lua b/[esx_addons]/esx_garage/message (21).lua deleted file mode 100644 index 74bd32fe..00000000 --- a/[esx_addons]/esx_garage/message (21).lua +++ /dev/null @@ -1,487 +0,0 @@ -# esx_garage - NUI Migration Changes - -## NUI Events (Lua → React) - -### Event: `openGarage` - -**Old:** -```lua -SendNUIMessage({ - showMenu = true, - type = 'garage', - vehiclesList = json.encode(vehiclesList), - vehiclesImpoundedList = json.encode(vehiclesImpoundedList), - poundName = v.ImpoundedName, - poundSpawnPoint = poundSpawnPoint, - spawnPoint = spawnPoint, - locales = { - action = TranslateCap('veh_exit'), - veh_model = TranslateCap('veh_model'), - veh_plate = TranslateCap('veh_plate'), - veh_condition = TranslateCap('veh_condition'), - veh_action = TranslateCap('veh_action'), - impound_action = TranslateCap('impound_action') - } -}) -``` - -**New:** -```lua -SendNUIMessage({ - type = 'openGarage', - payload = { - garage = { - id = string, -- 'VespucciBoulevard' - name = string, -- 'VespucciBoulevard' - type = string, -- 'public'|'private'|'job'|'gang'|'impound'|'house'|'aircraft'|'boat' - label = string, -- Display name - coords = {x, y, z, h}, -- Optional - spawns = {{coords = {x, y, z, h}, occupied = false}}, -- Optional - blip = {sprite, color, scale, display, shortRange}, -- Optional - job = string, -- Optional - gang = string, -- Optional - maxVehicles = number -- Optional - }, - vehicles = { - { - id = string, -- REQUIRED: Unique ID (plate or DB ID) - plate = string, -- REQUIRED - model = string, -- REQUIRED: Vehicle model - name = string, -- REQUIRED: Display name - type = string, -- REQUIRED: 'car'|'motorcycle'|'boat'|'aircraft'|'bicycle'|'truck'|'emergency' - stored = boolean, -- REQUIRED - garage = string, -- Optional: Garage ID - impounded = boolean, -- REQUIRED - impoundFee = number, -- Optional - mileage = number, -- REQUIRED - fuel = number, -- Optional: 0-100 - engine = number, -- Optional: 0-100 - body = number, -- Optional: 0-100 - image = string, -- Optional: URL - isFavorite = boolean, -- Optional - customName = string, -- Optional: Max 50 chars - lastUsed = number, -- Optional: Timestamp - props = table -- Optional: ESX.Game.GetVehicleProperties() - } - } - } -}) -``` - -### Event: `closeGarage` - -**Old:** -```lua -SendNUIMessage({hideAll = true}) -``` - -**New:** -```lua -SendNUIMessage({ - type = 'closeGarage', - payload = {} -}) -``` - -### Event: `updateVehicles` (NEW) - -```lua -SendNUIMessage({ - type = 'updateVehicles', - payload = vehicles -- Array of vehicle objects (same structure as above) -}) -``` - -### Event: `showNotification` (NEW - Optional) - -```lua -SendNUIMessage({ - type = 'showNotification', - payload = { - message = string, - type = 'success'|'error'|'info'|'warning' -- Default: 'info' - } -}) -``` - ---- - -## NUI Callbacks (React → Lua) - -**All callbacks must return:** -```lua -{ - success = boolean, - data = any, -- Optional: Success data - error = string -- Optional: Error message if success = false -} -``` - -### Callback: `garage:retrieveVehicle` - -**Old:** `spawnVehicle` -```lua -RegisterNUICallback('spawnVehicle', function(data, cb) - -- data.spawnPoint, data.vehicleProps, data.exitVehicleCost - cb('ok') -end) -``` - -**New:** -```lua -RegisterNUICallback('garage:retrieveVehicle', function(data, cb) - -- data = {vehicleId = string} - - if success then - cb({success = true, data = true}) - else - cb({success = false, error = 'Spawn point blocked'}) - end -end) -``` - -### Callback: `garage:storeVehicle` (NEW) - -```lua -RegisterNUICallback('garage:storeVehicle', function(data, cb) - -- data = {vehicleId = string, garageId = string} - - cb({success = true, data = true}) -end) -``` - -### Callback: `garage:renameVehicle` (NEW) - -```lua -RegisterNUICallback('garage:renameVehicle', function(data, cb) - -- data = {vehicleId = string, newName = string} - - cb({success = true, data = true}) -end) -``` - -### Callback: `garage:toggleFavorite` (NEW) - -```lua -RegisterNUICallback('garage:toggleFavorite', function(data, cb) - -- data = {vehicleId = string, isFavorite = boolean} - - cb({success = true, data = true}) -end) -``` - -### Callback: `garage:giveKeys` (NEW) - -```lua -RegisterNUICallback('garage:giveKeys', function(data, cb) - -- data = {vehicleId = string, targetId = string} - - cb({success = true, data = true}) -end) -``` - -### Callback: `garage:transferVehicle` (NEW) - -```lua -RegisterNUICallback('garage:transferVehicle', function(data, cb) - -- data = {vehicleId = string, targetId = string} - - cb({success = true, data = true}) -end) -``` - -### Callback: `garage:closeUI` - -**Old:** `escape` -```lua -RegisterNUICallback('escape', function(data, cb) - cb('ok') -end) -``` - -**New:** -```lua -RegisterNUICallback('garage:closeUI', function(data, cb) - SetNuiFocus(false, false) - cb({success = true}) -end) -``` - ---- - -## Database Schema Changes - -```sql -ALTER TABLE owned_vehicles -ADD COLUMN custom_name VARCHAR(50) NULL, -ADD COLUMN is_favorite TINYINT(1) DEFAULT 0, -ADD COLUMN last_used INT(11) NULL, -ADD COLUMN mileage INT(11) DEFAULT 0; -``` - ---- - -## Helper Functions - -### GetVehicleType -```lua -function GetVehicleType(model) - local class = GetVehicleClassFromName(model) - if class == 8 then return 'motorcycle' - elseif class == 13 then return 'bicycle' - elseif class == 14 then return 'boat' - elseif class == 15 or class == 16 then return 'aircraft' - elseif class == 18 then return 'emergency' - elseif class == 20 then return 'truck' - else return 'car' end -end -``` - -### CalculateHealth -```lua -function CalculateEngineHealth(health) - return math.floor((health / 1000.0) * 100) -end - -function CalculateBodyHealth(health) - return math.floor((health / 1000.0) * 100) -end -``` - ---- - -## Complete Implementation Example - -### Client: Open Garage -```lua -local function OpenGarage(garageName) - local garage = Config.Garages[garageName] - if not garage then return end - - ESX.TriggerServerCallback('esx_garage:getVehiclesInParking', function(vehicles) - local vehicleList = {} - - for i = 1, #vehicles do - local v = vehicles[i] - local props = json.decode(v.vehicle) - - table.insert(vehicleList, { - id = v.plate, - plate = v.plate, - model = props.model, - name = GetDisplayNameFromVehicleModel(props.model), - type = GetVehicleType(props.model), - stored = v.stored == 1, - garage = v.parking, - impounded = v.stored == 2, - impoundFee = v.stored == 2 and Config.Impounds[v.pound].Cost or nil, - mileage = v.mileage or 0, - fuel = props.fuelLevel or 100, - engine = CalculateEngineHealth(props.engineHealth or 1000), - body = CalculateBodyHealth(props.bodyHealth or 1000), - isFavorite = v.is_favorite == 1, - customName = v.custom_name, - lastUsed = v.last_used, - props = props - }) - end - - SendNUIMessage({ - type = 'openGarage', - payload = { - garage = { - id = garageName, - name = garageName, - type = 'public', - label = TranslateCap('parking_blip_name'), - coords = { - x = garage.EntryPoint.x, - y = garage.EntryPoint.y, - z = garage.EntryPoint.z - }, - spawns = {{ - coords = { - x = garage.SpawnPoint.x, - y = garage.SpawnPoint.y, - z = garage.SpawnPoint.z, - h = garage.SpawnPoint.heading - }, - occupied = false - }} - }, - vehicles = vehicleList - } - }) - - SetNuiFocus(true, true) - end, garageName) -end -``` - -### Client: Retrieve Vehicle Callback -```lua -RegisterNUICallback('garage:retrieveVehicle', function(data, cb) - local vehicleId = data.vehicleId - - if not vehicleId then - cb({success = false, error = 'Invalid vehicle ID'}) - return - end - - ESX.TriggerServerCallback('esx_garage:getVehicleById', function(vehicleData) - if not vehicleData then - cb({success = false, error = 'Vehicle not found'}) - return - end - - local props = json.decode(vehicleData.vehicle) - local garage = Config.Garages[vehicleData.parking] - local spawnPoint = vector3(garage.SpawnPoint.x, garage.SpawnPoint.y, garage.SpawnPoint.z) - - if not ESX.Game.IsSpawnPointClear(spawnPoint, 2.5) then - cb({success = false, error = 'Spawn point blocked'}) - return - end - - ESX.OneSync.SpawnVehicle(props.model, spawnPoint, garage.SpawnPoint.heading, props, function(vehicle) - local veh = NetworkGetEntityFromNetworkId(vehicle) - Wait(300) - TaskWarpPedIntoVehicle(PlayerPedId(), veh, -1) - - TriggerServerEvent('esx_garage:updateVehicleStored', vehicleId, false) - cb({success = true, data = true}) - end) - end, vehicleId) -end) -``` - -### Client: Rename Vehicle Callback -```lua -RegisterNUICallback('garage:renameVehicle', function(data, cb) - local vehicleId = data.vehicleId - local newName = data.newName - - if not vehicleId or not newName or #newName < 1 or #newName > 50 then - cb({success = false, error = 'Invalid data'}) - return - end - - TriggerServerEvent('esx_garage:renameVehicle', vehicleId, newName) - cb({success = true, data = true}) -end) -``` - -### Client: Toggle Favorite Callback -```lua -RegisterNUICallback('garage:toggleFavorite', function(data, cb) - local vehicleId = data.vehicleId - local isFavorite = data.isFavorite - - if not vehicleId or type(isFavorite) ~= 'boolean' then - cb({success = false, error = 'Invalid data'}) - return - end - - TriggerServerEvent('esx_garage:toggleFavorite', vehicleId, isFavorite) - cb({success = true, data = true}) -end) -``` - -### Server: Get Vehicle by ID -```lua -ESX.RegisterServerCallback('esx_garage:getVehicleById', function(source, cb, vehicleId) - local xPlayer = ESX.GetPlayerFromId(source) - - MySQL.query('SELECT * FROM owned_vehicles WHERE plate = ? AND owner = ?', { - vehicleId, - xPlayer.identifier - }, function(result) - cb(result[1] or nil) - end) -end) -``` - -### Server: Update Vehicle Stored -```lua -RegisterServerEvent('esx_garage:updateVehicleStored') -AddEventHandler('esx_garage:updateVehicleStored', function(vehicleId, stored) - local xPlayer = ESX.GetPlayerFromId(source) - - MySQL.update('UPDATE owned_vehicles SET stored = ?, last_used = ? WHERE plate = ? AND owner = ?', { - stored and 1 or 0, - os.time(), - vehicleId, - xPlayer.identifier - }) -end) -``` - -### Server: Rename Vehicle -```lua -RegisterServerEvent('esx_garage:renameVehicle') -AddEventHandler('esx_garage:renameVehicle', function(vehicleId, newName) - local xPlayer = ESX.GetPlayerFromId(source) - - if not vehicleId or not newName or #newName < 1 or #newName > 50 then - return - end - - MySQL.update('UPDATE owned_vehicles SET custom_name = ? WHERE plate = ? AND owner = ?', { - newName, - vehicleId, - xPlayer.identifier - }) -end) -``` - -### Server: Toggle Favorite -```lua -RegisterServerEvent('esx_garage:toggleFavorite') -AddEventHandler('esx_garage:toggleFavorite', function(vehicleId, isFavorite) - local xPlayer = ESX.GetPlayerFromId(source) - - if not vehicleId or type(isFavorite) ~= 'boolean' then - return - end - - MySQL.update('UPDATE owned_vehicles SET is_favorite = ? WHERE plate = ? AND owner = ?', { - isFavorite and 1 or 0, - vehicleId, - xPlayer.identifier - }) -end) -``` - ---- - -## Quick Reference - -### Event Names Changed -| Old | New | -|-----|-----| -| `showMenu = true` | `type = 'openGarage'` | -| `hideAll = true` | `type = 'closeGarage'` | - -### Callback Names Changed -| Old | New | -|-----|-----| -| `spawnVehicle` | `garage:retrieveVehicle` | -| `escape` | `garage:closeUI` | -| `impound` | Removed | - -### New Callbacks -- `garage:storeVehicle` -- `garage:renameVehicle` -- `garage:toggleFavorite` -- `garage:giveKeys` -- `garage:transferVehicle` - -### New Events -- `updateVehicles` -- `showNotification` - -### Data Format Changes -- No more `json.encode()` for NUI messages - send tables directly -- All callbacks require `{success, data, error}` response format -- Vehicle objects now require `id`, `type`, `mileage`, and health fields -- Locales removed from NUI messages (handled in React) diff --git a/[esx_addons]/esx_garage/server/init.lua b/[esx_addons]/esx_garage/server/init.lua index 61aeffb4..1d81a895 100644 --- a/[esx_addons]/esx_garage/server/init.lua +++ b/[esx_addons]/esx_garage/server/init.lua @@ -1,23 +1 @@ -ESX.RegisterServerCallback('esx_garages/getOwnedVehicles', function(playerId, cb) - local xPlayer = ESX.Player(playerId) - - if not xPlayer then - return - end - - local isAllowed = true - - if not isAllowed then - return cb('not-allowed') - end - - ---@type GarageVehicleDB[] - local owned_vehicles = MySQL.query.await('SELECT * FROM `owned_vehicles` WHERE owner = ?', - { xPlayer.getIdentifier() }) - - if not owned_vehicles then - return cb('no-vehicles') - end - - cb(owned_vehicles) -end) +SPAWNED_VEHICLES = {} diff --git a/[esx_addons]/esx_garage/server/modules/addons.lua b/[esx_addons]/esx_garage/server/modules/addons.lua new file mode 100644 index 00000000..4e409175 --- /dev/null +++ b/[esx_addons]/esx_garage/server/modules/addons.lua @@ -0,0 +1,354 @@ +---@param player_id integer +---@param cb fun(...) +---@param data { vehicleId: string, isFavorite: boolean } +ESX.RegisterServerCallback('esx_garages/toggleFavorite', function(player_id, cb, data) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local player_garage = Utils.GetPlayerGarage(player_coords) + + if not player_garage then + return cb({ + success = false, + TranslateCap('not-allowed') + }) + end + + local identifier = xPlayer.getIdentifier() + + local updated_rows + + if Utils.IsSecondOwner(data.vehicleId, identifier) then + updated_rows = MySQL.update.await( + 'UPDATE `second_owners` SET `favorite` = ? WHERE `identifier` = ? AND `plate` = ?', + { data.isFavorite, identifier, data.vehicleId }) + else + updated_rows = MySQL.update.await( + 'UPDATE `owned_vehicles` SET `favorite` = ? WHERE `owner` = ? AND `plate` = ?', + { data.isFavorite, identifier, data.vehicleId }) + end + + if updated_rows == 0 then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + cb({ + success = true, + data = true + }) +end) + +---@param player_id integer +---@param cb fun(...) +---@param data { vehicleId: string, targetId: string} +ESX.RegisterServerCallback('esx_garages/transferVehicle', function(player_id, cb, data) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local player_garage = Utils.GetPlayerGarage(player_coords) + + if not player_garage then + return cb({ + success = false, + TranslateCap('not-allowed') + }) + end + + local xTargetSource = tonumber(data.targetId) + + if not xTargetSource then + return cb({ + success = false, + error = TranslateCap('not-found-player') + }) + end + + local xTarget = ESX.Player(xTargetSource) + + if not xTarget then + return cb({ + success = false, + error = TranslateCap('not-found-player') + }) + end + + local player_identifier = xPlayer.getIdentifier() + + local target_identifier = xTarget.getIdentifier() + + if player_identifier == target_identifier then + return cb({ + success = false, + error = TranslateCap('to-your-self') + }) + end + + if not Utils.IsOwner(data.vehicleId, player_identifier) then + return cb({ + success = false, + error = TranslateCap('not-the-owner') + }) + end + + local updated_rows = MySQL.update.await( + 'UPDATE `owned_vehicles` SET `owner` = ? WHERE `owner` = ? AND `plate` = ?', + { target_identifier, player_identifier, data.vehicleId }) + + + if updated_rows == 0 then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + cb({ + success = true, + data = true + }) +end) + +---@param player_id integer +---@param cb fun(...) +---@param data { vehicleId: string, newName: string } +ESX.RegisterServerCallback('esx_garages/renameVehicle', function(player_id, cb, data) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local player_garage = Utils.GetPlayerGarage(player_coords) + + if not player_garage then + return cb({ + success = false, + TranslateCap('not-allowed') + }) + end + + if data.newName == "" then + data.newName = nil + end + + local identifier = xPlayer.getIdentifier() + + local updated_rows + + if Utils.IsSecondOwner(data.vehicleId, identifier) then + updated_rows = MySQL.update.await( + 'UPDATE `second_owners` SET `nickname` = ? WHERE `identifier` = ? AND `plate` = ?', + { data.newName, identifier, data.vehicleId }) + else + updated_rows = MySQL.update.await( + 'UPDATE `owned_vehicles` SET `nickname` = ? WHERE `owner` = ? AND `plate` = ?', + { data.newName, identifier, data.vehicleId }) + end + + + if updated_rows == 0 then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + cb({ + success = true, + data = true + }) +end) + + +---@param player_id any +---@param cb any +---@param data { vehicleId: string, targetId: string} +ESX.RegisterServerCallback('esx_garages/addSecondOwner', function(player_id, cb, data) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local player_garage = Utils.GetPlayerGarage(player_coords) + + if not player_garage then + return cb({ + success = false, + TranslateCap('not-allowed') + }) + end + + local xTargetSource = tonumber(data.targetId) + + if not xTargetSource then + return cb({ + success = false, + error = TranslateCap('not-found-player') + }) + end + + local xTarget = ESX.Player(xTargetSource) + + if not xTarget then + return cb({ + success = false, + error = TranslateCap('not-found-player') + }) + end + + local player_identifier = xPlayer.getIdentifier() + + local target_identifier = xTarget.getIdentifier() + + if player_identifier == target_identifier then + return cb({ + success = false, + error = TranslateCap('to-your-self') + }) + end + + if not Utils.IsOwner(data.vehicleId, player_identifier) then + return cb({ + success = false, + error = TranslateCap('not-the-owner') + }) + end + + if Utils.IsSecondOwner(data.vehicleId, target_identifier) then + return cb({ + success = false, + error = TranslateCap('already-second-owner') + }) + end + + local id = MySQL.insert.await( + 'INSERT INTO `second_owners`(`identifier`, `plate`) VALUES(?, ?)', + { target_identifier, data.vehicleId }) + + if not id then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + cb({ + success = true, + data = true + }) +end) + +---@param player_id any +---@param cb any +---@param data { vehicleId: string, targetId: string} +ESX.RegisterServerCallback('esx_garages/removeSecondOwner', function(player_id, cb, data) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local player_garage = Utils.GetPlayerGarage(player_coords) + + if not player_garage then + return cb({ + success = false, + TranslateCap('not-allowed') + }) + end + + local xTargetSource = tonumber(data.targetId) + + if not xTargetSource then + return cb({ + success = false, + error = TranslateCap('not-found-player') + }) + end + + local xTarget = ESX.Player(xTargetSource) + + if not xTarget then + return cb({ + success = false, + error = TranslateCap('not-found-player') + }) + end + + local player_identifier = xPlayer.getIdentifier() + + local target_identifier = xTarget.getIdentifier() + + if player_identifier == target_identifier then + return cb({ + success = false, + error = TranslateCap('remove-from-yourself') + }) + end + + if not Utils.IsOwner(data.vehicleId, player_identifier) then + return cb({ + success = false, + error = TranslateCap('not-the-owner') + }) + end + + if not Utils.IsSecondOwner(data.vehicleId, target_identifier) then + return cb({ + success = false, + error = TranslateCap('not-a-second-owner') + }) + end + + local updated_rows = MySQL.update.await( + 'DELETE FROM `second_owners` WHERE `identifier` = ? AND `plate` = ?', + { target_identifier, data.vehicleId }) + + + if updated_rows == 0 then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + cb({ + success = true, + data = true + }) +end) diff --git a/[esx_addons]/esx_garage/server/modules/garage.lua b/[esx_addons]/esx_garage/server/modules/garage.lua new file mode 100644 index 00000000..1fd3a613 --- /dev/null +++ b/[esx_addons]/esx_garage/server/modules/garage.lua @@ -0,0 +1,221 @@ +---@param player_id integer +---@param cb fun(...) +ESX.RegisterServerCallback('esx_garages/getOwnedVehicles', function(player_id, cb) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local player_garage = Utils.GetPlayerGarage(player_coords) + + if not player_garage then + return cb({ + success = false, + error = TranslateCap('not-allowed') + }) + end + + local identifier = xPlayer.getIdentifier() + + ---@type GarageVehicleDB[] + local owned_vehicles = MySQL.query.await('SELECT * FROM `owned_vehicles` WHERE `owner` = ?', + { identifier }) or {} + + for i = 1, #owned_vehicles do + ---@type GarageSecondOwner[] + local second_owners = MySQL.query.await([[ + SELECT `second_owners`.`identifier`, `firstname`, `lastname` + FROM `second_owners` + INNER JOIN `users` + ON `second_owners`.`identifier` = `users`.`identifier` + WHERE `second_owners`.`plate` = ? + ]], { owned_vehicles[i].plate }) + + + ---@diagnostic disable-next-line: inject-field + owned_vehicles.second_owners = second_owners or {} + end + + local second_owner_vehicles = MySQL.query.await([[ + SELECT + `owned_vehicles`.*, + `second_owners`.`favorite` AS `second_favorite`, + `second_owners`.`nickname` AS `second_nickname` + FROM `owned_vehicles` + INNER JOIN `second_owners` + ON `second_owners`.`plate` = `owned_vehicles`.`plate` + WHERE `second_owners`.`identifier` = ? + ]], { identifier }) + + if second_owner_vehicles then + for i = 1, #second_owner_vehicles do + owned_vehicles[#owned_vehicles + 1] = second_owner_vehicles[i] + end + end + + cb(owned_vehicles) +end) + + +---@param player_id integer +---@param cb fun(...) +---@param data { vehicleId: string } +ESX.RegisterServerCallback('esx_garages/retrieveVehicle', function(player_id, cb, data) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local player_garage = Utils.GetPlayerGarage(player_coords) + + if not player_garage then + return cb({ + success = false, + error = TranslateCap('not-allowed') + }) + end + + local player_identifier = xPlayer.getIdentifier() + + if not Utils.HasAccess(data.vehicleId, player_identifier) then + return cb({ + success = false, + error = TranslateCap('not-permitted') + }) + end + + local vehicle_data = MySQL.prepare.await("SELECT * FROM `owned_vehicles` WHERE `plate` = ?", { + data.vehicleId + }) + + if not vehicle_data then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + if not vehicle_data.stored == Config.GarageState.STORED then + return cb({ + success = false, + error = TranslateCap('not_in_garage') + }) + end + + local properties = json.decode(vehicle_data.vehicle) + + local garage = Config.Garages[player_garage] + + local vec3d = vec3(garage.spawnPoint.x, garage.spawnPoint.y, garage.spawnPoint.z) + + ESX.OneSync.SpawnVehicle(properties.model, vec3d, garage.spawnPoint.w, properties, function(net_id) + local entity = NetworkGetEntityFromNetworkId(net_id) + + if not DoesEntityExist(entity) then + return cb({ + success = false, + error = TranslateCap('failed_create') + }) + end + + MySQL.update.await("UPDATE `owned_vehicles` SET `stored` = ? WHERE `plate` = ?", + { Config.GarageState.NOT_STORED, vehicle_data.plate }) + + + Entity(entity).state:set("mileage", vehicle_data.mileage or 0, true) + + SPAWNED_VEHICLES[vehicle_data.plate] = entity + end) + + cb({ + success = true, + data = true + }) +end) + +---@param player_id integer +---@param cb fun(...) +---@param data { properties: ESXVehicleProperties } +ESX.RegisterServerCallback('esx_garages/hideVehicle', function(player_id, cb, data) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local is_allowed = Utils.IsNearReturnPoint(player_coords) + + if not is_allowed then + return cb({ + success = false, + error = TranslateCap('not-allowed') + }) + end + + local player_ped = GetPlayerPed(player_id) + local vehicle = GetVehiclePedIsIn(player_ped, false) + + if vehicle == 0 then + return cb({ + success = false, + error = TranslateCap('not_in_vehicle') + }) + end + + local vehicle_plate = GetVehicleNumberPlateText(vehicle) + + local player_identifier = xPlayer.getIdentifier() + + if not Utils.HasAccess(vehicle_plate, player_identifier) then + return cb({ + success = false, + error = TranslateCap('not-permitted') + }) + end + + local database_properties = MySQL.prepare.await("SELECT `vehicle` FROM `owned_vehicles` WHERE `plate` = ?", { + vehicle_plate + }) + + local properties = data.properties + + if database_properties?.vehicle then + ---already checks if nil by ? + ---@diagnostic disable-next-line: need-check-nil + database_properties = json.decode(database_properties.vehicle) + + if database_properties.model ~= properties.model then + --! Ban function + xPlayer.kick('Cheating') + end + end + + MySQL.update.await( + "UPDATE `owned_vehicles` SET `stored` = ?, `vehicle` = ?, `last_used` = ?, `mileage` = ? WHERE `plate` = ?", + { Config.GarageState.STORED, json.encode(properties), os.time(), Entity(vehicle).state.mileage or + 0, vehicle_plate }) + + DeleteEntity(vehicle) + + cb({ + success = true, + data = true + }) +end) diff --git a/[esx_addons]/esx_garage/server/modules/impound.lua b/[esx_addons]/esx_garage/server/modules/impound.lua new file mode 100644 index 00000000..5c19c99d --- /dev/null +++ b/[esx_addons]/esx_garage/server/modules/impound.lua @@ -0,0 +1,66 @@ +---@param player_id integer +---@param cb fun(...) +---@param data { type: 'impound' | 'find', vehicleId: string } +ESX.RegisterServerCallback('esx_garages/impoundVehicle', function(player_id, cb, data) + local xPlayer = ESX.Player(player_id) + + if not xPlayer then + return cb({ + success = false, + error = TranslateCap('internal_error') + }) + end + + local player_coords = xPlayer.getCoords(true, false) + + local player_garage = Utils.GetPlayerGarage(player_coords) + + if not player_garage then + return cb({ + success = false, + error = TranslateCap('not-allowed') + }) + end + + local player_identifier = xPlayer.getIdentifier() + + if not Utils.HasAccess(data.vehicleId, player_identifier) then + return cb({ + success = false, + error = TranslateCap('not-permitted') + }) + end + + local fee = data.type == 'find' and Config.FindVehicleFee or Config.ImpoundFee + + if xPlayer.getAccount('bank').money < fee then + return cb({ + success = false, + error = TranslateCap('missing_money') + }) + end + + local vehicle = SPAWNED_VEHICLES[data.vehicleId] + + if vehicle and DoesEntityExist(vehicle) then + if not Utils.IsVehicleEmpty(vehicle) then + return cb({ + success = false, + error = TranslateCap('failed_find') + }) + end + + DeleteEntity(vehicle) + end + + xPlayer.removeAccountMoney('bank', fee) + + MySQL.update.await( + "UPDATE `owned_vehicles` SET `stored` = ? WHERE `plate` = ?", + { Config.GarageState.STORED, data.vehicleId }) + + cb({ + success = true, + data = true + }) +end) diff --git a/[esx_addons]/esx_garage/server/modules/utils.lua b/[esx_addons]/esx_garage/server/modules/utils.lua new file mode 100644 index 00000000..d44f4696 --- /dev/null +++ b/[esx_addons]/esx_garage/server/modules/utils.lua @@ -0,0 +1,71 @@ +Utils = {} + +---@param plate string +---@param identifier string +---@return boolean +function Utils.IsSecondOwner(plate, identifier) + return MySQL.prepare.await( + 'SELECT 1 FROM `second_owners` WHERE `identifier` = ? AND `plate` = ?', { identifier, plate } + ) and true or false +end + +---@param plate string +---@param identifier string +---@return boolean +function Utils.IsOwner(plate, identifier) + return MySQL.prepare.await( + 'SELECT 1 FROM `owned_vehicles` WHERE `owner` = ? AND `plate` = ?', { identifier, plate } + ) and true or false +end + +---@param plate string +---@param identifier string +---@return boolean +function Utils.HasAccess(plate, identifier) + return Utils.IsOwner(plate, identifier) or Utils.IsSecondOwner(plate, identifier) +end + +---@param player_coords vector3 | vector4 +function Utils.GetPlayerGarage(player_coords) + for i = 1, #Config.Garages do + local vec3d = vec3( + Config.Garages[i].ped.coords.x, + Config.Garages[i].ped.coords.y, + Config.Garages[i].ped.coords.z) + if #(vec3d - player_coords) <= 16 then + return i + end + end +end + +---@param player_coords vector3 | vector4 +---@return boolean +function Utils.IsNearReturnPoint(player_coords) + for i = 1, #Config.Garages do + local vec3d = vec3( + Config.Garages[i].spawnPoint.x, + Config.Garages[i].spawnPoint.y, + Config.Garages[i].spawnPoint.z) + if #(vec3d - player_coords) <= 30 then + return true + end + end + + return false +end + +---@param vehicle integer +---@return boolean +function Utils.IsVehicleEmpty(vehicle) + local vehicle_model = GetEntityModel(vehicle) + local seat_count = GetVehicleModelNumberOfSeats(vehicle_model) + + for seat = -1, seat_count - 2 do + local ped = GetPedInVehicleSeat(vehicle, seat) + if ped and ped ~= 0 then + return false + end + end + + return true +end diff --git a/[esx_addons]/esx_garage/types.lua b/[esx_addons]/esx_garage/types.lua index dad8cfae..be2137cc 100644 --- a/[esx_addons]/esx_garage/types.lua +++ b/[esx_addons]/esx_garage/types.lua @@ -7,7 +7,7 @@ ---@field coords vector4 ---@field model string | number -- Model Hash ----@class Garage +---@class GarageConfig ---@field id string ---@field label string ---@field entryPoint vector3 @@ -36,6 +36,12 @@ ---@field customName string? ---@field lastUsed integer ---@field props ESXVehicleProperties +---@field secondOwners GarageSecondOwner[] + +---@class GarageSecondOwner +---@field identifier string +---@field firstname string +---@field lastname string ---@class GarageVehicleDB ---@field owner string @@ -47,3 +53,18 @@ ---@field parking string ---@field model string? ---@field favorite 0 | 1 +---@field nickname string? +---@field last_used integer|nil +---@field second_nickname string? +---@field second_favorite 0 | 1? +---@field second_owners GarageSecondOwner[] +---@field mileage integer + +---@class GaragePayload +---@field garage { id: string, name: string, type: string, label: string} +---@field vehicles GarageVehicle[] + +---@class CallbackResult +---@field success boolean +---@field data any +---@field error string diff --git a/[esx_addons]/esx_garage/web/src/store/garage.store.ts b/[esx_addons]/esx_garage/web/src/store/garage.store.ts index c9c83fc1..43c7a29a 100644 --- a/[esx_addons]/esx_garage/web/src/store/garage.store.ts +++ b/[esx_addons]/esx_garage/web/src/store/garage.store.ts @@ -45,7 +45,7 @@ const defaultFilter: VehicleFilter = { type: 'all', stored: 'all', impounded: 'all', - favorite: 'all' + favorite: 'all', }; export const useGarageStore = create()( @@ -62,48 +62,57 @@ export const useGarageStore = create()( total: 0, stored: 0, out: 0, - impounded: 0 + impounded: 0, }, // UI Actions - setOpen: (open) => set((state) => { - state.isOpen = open; - if (!open) { - // Reset state when closing - state.selectedGarage = null; - state.vehicles = []; + setOpen: (open) => + set((state) => { + state.isOpen = open; + if (!open) { + // Reset state when closing + state.selectedGarage = null; + state.vehicles = []; + state.filter = defaultFilter; + } + }), + + setLoading: (loading) => + set((state) => { + state.isLoading = loading; + }), + + selectGarage: (garage) => + set((state) => { + state.selectedGarage = garage; + }), + + selectVehicle: (vehicle) => + set((state) => { + state.selectedVehicle = vehicle; + }), + + updateVehicles: (vehicles) => + set((state) => { + state.vehicles = vehicles; + get().updateStats(); + }), + + setFilter: (filter) => + set((state) => { + state.filter = { ...state.filter, ...filter }; + }), + + resetFilter: () => + set((state) => { state.filter = defaultFilter; - } - }), - - setLoading: (loading) => set((state) => { - state.isLoading = loading; - }), - - selectGarage: (garage) => set((state) => { - state.selectedGarage = garage; - }), - - selectVehicle: (vehicle) => set((state) => { - state.selectedVehicle = vehicle; - }), - - updateVehicles: (vehicles) => set((state) => { - state.vehicles = vehicles; - get().updateStats(); - }), - - setFilter: (filter) => set((state) => { - state.filter = { ...state.filter, ...filter }; - }), - - resetFilter: () => set((state) => { - state.filter = defaultFilter; - }), + }), // Vehicle Actions retrieveVehicle: async (vehicleId) => { - set((state) => { state.isLoading = true; }); + set((state) => { + state.isLoading = true; + }); try { const result = await fetchNui( @@ -114,7 +123,7 @@ export const useGarageStore = create()( if (result) { set((state) => { - const vehicle = state.vehicles.find(v => v.id === vehicleId); + const vehicle = state.vehicles.find((v) => v.id === vehicleId); if (vehicle) { vehicle.stored = false; vehicle.garage = undefined; @@ -125,12 +134,16 @@ export const useGarageStore = create()( } catch (error) { console.error('Failed to retrieve vehicle:', error); } finally { - set((state) => { state.isLoading = false; }); + set((state) => { + state.isLoading = false; + }); } }, storeVehicle: async (vehicleId) => { - set((state) => { state.isLoading = true; }); + set((state) => { + state.isLoading = true; + }); try { const result = await fetchNui( @@ -141,7 +154,7 @@ export const useGarageStore = create()( if (result) { set((state) => { - const vehicle = state.vehicles.find(v => v.id === vehicleId); + const vehicle = state.vehicles.find((v) => v.id === vehicleId); if (vehicle) { vehicle.stored = true; vehicle.garage = state.selectedGarage?.id; @@ -152,7 +165,9 @@ export const useGarageStore = create()( } catch (error) { console.error('Failed to store vehicle:', error); } finally { - set((state) => { state.isLoading = false; }); + set((state) => { + state.isLoading = false; + }); } }, @@ -166,7 +181,7 @@ export const useGarageStore = create()( if (result) { set((state) => { - const vehicle = state.vehicles.find(v => v.id === vehicleId); + const vehicle = state.vehicles.find((v) => v.id === vehicleId); if (vehicle) { vehicle.customName = newName; } @@ -179,7 +194,7 @@ export const useGarageStore = create()( toggleFavorite: async (vehicleId) => { try { - const vehicle = get().vehicles.find(v => v.id === vehicleId); + const vehicle = get().vehicles.find((v) => v.id === vehicleId); if (!vehicle) return; const newFavoriteStatus = !vehicle.isFavorite; @@ -192,7 +207,7 @@ export const useGarageStore = create()( if (result) { set((state) => { - const vehicle = state.vehicles.find(v => v.id === vehicleId); + const vehicle = state.vehicles.find((v) => v.id === vehicleId); if (vehicle) { vehicle.isFavorite = newFavoriteStatus; } @@ -211,32 +226,33 @@ export const useGarageStore = create()( // Search filter if (state.filter.search) { const search = state.filter.search.toLowerCase(); - filtered = filtered.filter(v => - v.name.toLowerCase().includes(search) || - v.customName?.toLowerCase().includes(search) || - v.plate.toLowerCase().includes(search) || - v.model.toLowerCase().includes(search) + filtered = filtered.filter( + (v) => + v.name.toLowerCase().includes(search) || + v.customName?.toLowerCase().includes(search) || + v.plate.toLowerCase().includes(search) || + v.model.toLowerCase().includes(search) ); } // Type filter if (state.filter.type && state.filter.type !== 'all') { - filtered = filtered.filter(v => v.type === state.filter.type); + filtered = filtered.filter((v) => v.type === state.filter.type); } // Stored filter if (state.filter.stored !== 'all') { - filtered = filtered.filter(v => v.stored === state.filter.stored); + filtered = filtered.filter((v) => v.stored === state.filter.stored); } // Impounded filter if (state.filter.impounded !== 'all') { - filtered = filtered.filter(v => v.impounded === state.filter.impounded); + filtered = filtered.filter((v) => v.impounded === state.filter.impounded); } // Favorite filter if (state.filter.favorite !== 'all') { - filtered = filtered.filter(v => v.isFavorite === state.filter.favorite); + filtered = filtered.filter((v) => v.isFavorite === state.filter.favorite); } // Sort: Favorites first, then by last used @@ -249,15 +265,16 @@ export const useGarageStore = create()( return filtered; }, - updateStats: () => set((state) => { - const vehicles = state.vehicles; - state.stats = { - total: vehicles.length, - stored: vehicles.filter(v => v.stored && !v.impounded).length, - out: vehicles.filter(v => !v.stored && !v.impounded).length, - impounded: vehicles.filter(v => v.impounded).length - }; - }) + updateStats: () => + set((state) => { + const vehicles = state.vehicles; + state.stats = { + total: vehicles.length, + stored: vehicles.filter((v) => v.stored && !v.impounded).length, + out: vehicles.filter((v) => !v.stored && !v.impounded).length, + impounded: vehicles.filter((v) => v.impounded).length, + }; + }), })) ) -); \ No newline at end of file +); diff --git a/[esx_addons]/esx_garage/web/src/types/vehicle.types.ts b/[esx_addons]/esx_garage/web/src/types/vehicle.types.ts index d5258437..3244bb9b 100644 --- a/[esx_addons]/esx_garage/web/src/types/vehicle.types.ts +++ b/[esx_addons]/esx_garage/web/src/types/vehicle.types.ts @@ -26,10 +26,10 @@ export const VehicleType = { AIRCRAFT: 'aircraft', BICYCLE: 'bicycle', TRUCK: 'truck', - EMERGENCY: 'emergency' + EMERGENCY: 'emergency', } as const; -export type VehicleType = typeof VehicleType[keyof typeof VehicleType]; +export type VehicleType = (typeof VehicleType)[keyof typeof VehicleType]; export interface VehicleProps { color1?: number[]; @@ -67,4 +67,4 @@ export interface VehicleStats { stored: number; out: number; impounded: number; -} \ No newline at end of file +}