diff --git a/client/main.lua b/client/main.lua index 0d6f6a9..4747c31 100644 --- a/client/main.lua +++ b/client/main.lua @@ -29,13 +29,13 @@ end local function confirmationCheck() local alert = lib.alertDialog({ - header = 'Wait a minute!', - content = 'Are you sure you wish to proceed?', + header = 'Espere um minuto!', + content = 'Tem certeza de que deseja prosseguir?', centered = true, cancel = true, labels = { - cancel = 'No', - confirm = 'Yes', + cancel = 'Não', + confirm = 'Sim', } }) return alert @@ -46,9 +46,9 @@ local function showVehicleFinanceMenu(data) local vehLabel = VEHICLES[data.vehicle].brand..' '..VEHICLES[data.vehicle].name local vehFinance = { { - title = 'Finance Information', + title = locale('menus.veh_finance_title'), icon = 'circle-info', - description = string.format('Name: %s\nPlate: %s\nRemaining Balance: $%s\nRecurring Payment Amount: $%s\nPayments Left: %s', vehLabel, data.vehiclePlate, lib.math.groupdigits(data.balance), lib.math.groupdigits(data.paymentAmount), data.paymentsLeft), + description = string.format('Nome: %s\nPlaca: %s\nSaldo restante: R$%s\nValor das parcelas: R$%s\nParcelas restantes: %s', vehLabel, data.vehiclePlate, lib.math.groupdigits(data.balance), lib.math.groupdigits(data.paymentAmount), data.paymentsLeft), readOnly = true, }, { @@ -152,16 +152,19 @@ end ---@param targetShowroomVehicle integer vehicleName ---@param buyVehicle string model local function openFinance(targetShowroomVehicle, buyVehicle) - local dialog = lib.inputDialog(VEHICLES[buyVehicle].brand:upper()..' '..VEHICLES[buyVehicle].name:upper()..' - $'..getVehPrice(targetShowroomVehicle), { + local dialog = lib.inputDialog(VEHICLES[buyVehicle].brand:upper()..' '..VEHICLES[buyVehicle].name:upper()..' - R$'..getVehPrice(targetShowroomVehicle), { { type = 'number', label = locale('menus.financesubmit_downpayment')..sharedConfig.finance.minimumDown..'%', + placeholder = string.format('%.2f', math.ceil(VEHICLES[buyVehicle].price * sharedConfig.finance.minimumDown / 100)), + default = math.ceil(VEHICLES[buyVehicle].price * sharedConfig.finance.minimumDown / 100), min = VEHICLES[buyVehicle].price * sharedConfig.finance.minimumDown / 100, max = VEHICLES[buyVehicle].price }, { type = 'number', label = locale('menus.financesubmit_totalpayment')..sharedConfig.finance.maximumPayments, + default = 2, min = 2, max = sharedConfig.finance.maximumPayments } @@ -273,7 +276,7 @@ end ---@param targetVehicle integer local function openCustomFinance(targetVehicle) local vehicle = config.shops[insideShop].showroomVehicles[targetVehicle].vehicle - local dialog = lib.inputDialog(getVehBrand(targetVehicle):upper()..' '..vehicle:upper()..' - $'..getVehPrice(targetVehicle), { + local dialog = lib.inputDialog(getVehBrand(targetVehicle):upper()..' '..vehicle:upper()..' - R$'..getVehPrice(targetVehicle), { { type = 'number', label = locale('menus.financesubmit_downpayment')..sharedConfig.finance.minimumDown..'%', @@ -337,6 +340,7 @@ local function openVehicleSellMenu(targetVehicle) local swapOption = { title = locale('menus.swap_header'), description = locale('menus.swap_txt'), + icon = 'right-left', onSelect = openVehicleCategoryMenu, args = { targetVehicle = targetVehicle @@ -349,6 +353,7 @@ local function openVehicleSellMenu(targetVehicle) options[#options + 1] = { title = locale('menus.test_header'), description = locale('menus.freeuse_test_txt'), + icon = 'fas fa-car', event = 'qbx_vehicleshop:client:testDrive', args = { vehicle = vehicle @@ -360,6 +365,7 @@ local function openVehicleSellMenu(targetVehicle) options[#options + 1] = { title = locale('menus.freeuse_buy_header'), description = locale('menus.freeuse_buy_txt'), + icon = 'dollar-sign', serverEvent = 'qbx_vehicleshop:server:buyShowroomVehicle', args = { buyVehicle = vehicle @@ -371,6 +377,7 @@ local function openVehicleSellMenu(targetVehicle) options[#options + 1] = { title = locale('menus.finance_header'), description = locale('menus.freeuse_finance_txt'), + icon = 'fa-solid fa-file-invoice-dollar', onSelect = function() openFinance(targetVehicle, vehicle) end @@ -382,6 +389,7 @@ local function openVehicleSellMenu(targetVehicle) options[1] = { title = locale('menus.managed_sell_header'), description = locale('menus.managed_sell_txt'), + icon = 'fa-solid fa-hand-holding-dollar', onSelect = function() sellVehicle(vehicle) end, @@ -391,6 +399,7 @@ local function openVehicleSellMenu(targetVehicle) options[#options + 1] = { title = locale('menus.test_header'), description = locale('menus.managed_test_txt'), + icon = 'fas fa-car', onSelect = function() startTestDrive(vehicle) end @@ -401,6 +410,7 @@ local function openVehicleSellMenu(targetVehicle) options[#options + 1] = { title = locale('menus.finance_header'), description = locale('menus.managed_finance_txt'), + icon = 'fa-solid fa-file-invoice-dollar', onSelect = function() openCustomFinance(targetVehicle) end @@ -412,7 +422,8 @@ local function openVehicleSellMenu(targetVehicle) lib.registerContext({ id = 'vehicleMenu', - title = getVehBrand(targetVehicle):upper()..' '..getVehName(targetVehicle):upper()..' - $'..getVehPrice(targetVehicle), + title = getVehBrand(targetVehicle):upper()..' '..getVehName(targetVehicle):upper(), + description = '**R$'..getVehPrice(targetVehicle)..'**', options = options }) lib.showContext('vehicleMenu') @@ -423,18 +434,23 @@ end local function startTestDriveTimer(time) local gameTimer = GetGameTimer() local timeMs = time * 1000 + local outVeh = false + while not IsPedInAnyVehicle(cache.ped, false) do Wait(0) end CreateThread(function() while inTestDrive do local currentGameTime = GetGameTimer() local secondsLeft = currentGameTime - gameTimer - if currentGameTime < gameTimer + timeMs and secondsLeft >= timeMs - 50 then + if currentGameTime < gameTimer + timeMs and secondsLeft >= timeMs - 50 or outVeh then TriggerServerEvent('qbx_vehicleshop:server:deleteVehicle', testDriveVeh) testDriveVeh = 0 inTestDrive = false exports.qbx_core:Notify(locale('general.testdrive_complete'), 'success') end qbx.drawText2d({ text = locale('general.testdrive_timer')..math.ceil(time - secondsLeft / 1000), coords = vec2(1.0, 1.38), scale = 0.5}) + if not IsPedInAnyVehicle(cache.ped, false) or IsPlayerDead(cache.playerId) then + outVeh = true + end Wait(0) end end) @@ -507,7 +523,7 @@ end ---@param coords vector4 ---@return number vehicleEntity local function createShowroomVehicle(model, coords) - lib.requestModel(model, 1000) + lib.requestModel(model, 5000) local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, false, false) SetModelAsNoLongerNeeded(model) SetVehicleOnGroundProperly(veh) @@ -516,6 +532,7 @@ local function createShowroomVehicle(model, coords) SetVehicleDoorsLocked(veh, 3) FreezeEntityPosition(veh, true) SetVehicleNumberPlateText(veh, 'BUY ME') + Entity(veh).state.isVehicleShopEntity = true; return veh end @@ -598,6 +615,7 @@ RegisterNetEvent('qbx_vehicleshop:client:testDrive', function(args) local plate = 'TEST'..lib.string.random('1111') local netId = lib.callback.await('qbx_vehicleshop:server:spawnVehicle', false, args.vehicle, testDrive.spawn, plate) testDriveVeh = netId + while not NetworkGetEntityFromNetworkId(netId) do Wait(100) end exports.qbx_core:Notify(locale('general.testdrive_timenoti', testDrive.limit), 'inform') startTestDriveTimer(testDrive.limit * 60) end) @@ -639,6 +657,7 @@ RegisterNetEvent('qbx_vehicleshop:client:buyShowroomVehicle', function(vehicle, local props = lib.getVehicleProperties(veh) props.plate = plate TriggerServerEvent('qb-vehicletuning:server:SaveVehicleProps', props) + exports.mri_Qcarkeys:GiveKeyItem(plate) end) local function confirmTrade(confirmationText) diff --git a/config/client.lua b/config/client.lua index 2431e2d..a515783 100644 --- a/config/client.lua +++ b/config/client.lua @@ -2,7 +2,7 @@ return { useTarget = false, debugPoly = false, enableFreeUseBuy = true, -- Allows players to buy from NPC shops - requestModelTimeout = 5000, -- load model timeout for oxlib + requestModelTimeout = 100000, -- load model timeout for oxlib enableTestDrive = true, finance = { @@ -92,7 +92,7 @@ return { }, testDrive = { limit = 5.0, - spawn = vec4(-7.84, -1081.35, 26.67, 121.83), + spawn = vec4(-12.89, -1089.17, 26.4, 158.47), }, returnLocation = vec3(-44.74, -1082.58, 26.68), vehicleSpawn = vec4(-31.69, -1090.78, 26.42, 328.79), diff --git a/config/server.lua b/config/server.lua index 9822f82..55ac1f3 100644 --- a/config/server.lua +++ b/config/server.lua @@ -3,7 +3,7 @@ return { finance = { paymentWarning = 10, -- time in minutes that player has to make payment before repo paymentInterval = 24, -- time in hours between payment being due - preventSelling = false, -- prevents players from using /transfervehicle if financed + preventSelling = true, -- prevents players from using /transfervehicle if financed }, saleTimeout = 60000 -- Delay between attempts to sell/gift a vehicle. Prevents abuse } \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index b321a96..c4ca66a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -41,6 +41,7 @@ "goback_header": "Go Back", "veh_price": "Price: $", "veh_platetxt": "Plate: ", + "veh_finance_title": "Finance Information", "veh_finance": "Vehicle Payment", "veh_finance_balance": "Total Balance Remaining", "veh_finance_currency": "$", diff --git a/locales/pt-br.json b/locales/pt-br.json new file mode 100644 index 0000000..4e89f9e --- /dev/null +++ b/locales/pt-br.json @@ -0,0 +1,84 @@ +{ + "error": { + "testdrive_alreadyin": "Já está em um test drive", + "testdrive_return": "Este não é o seu veículo de test drive", + "Invalid_ID": "ID de jogador fornecido inválido", + "playertoofar": "Este jogador não está perto o suficiente", + "notenoughmoney": "Dinheiro insuficiente", + "minimumallowed": "O pagamento mínimo permitido é $", + "overpaid": "Você pagou a mais", + "alreadypaid": "O veículo já está quitado", + "notworth": "O veículo não vale tanto", + "downtoosmall": "Pagamento inicial muito pequeno", + "exceededmax": "Excedido o valor máximo de pagamento", + "repossessed": "Seu veículo com placa %s foi recuperado", + "buyerinfo": "Não foi possível obter informações do comprador", + "notinveh": "Você deve estar no veículo que deseja transferir", + "vehinfo": "Não foi possível obter informações do veículo", + "notown": "Você não possui este veículo", + "buyertoopoor": "O comprador não tem dinheiro suficiente", + "nofinanced": "Você não possui nenhum veículo financiado", + "financed": "Este veículo está financiado", + "buyerdeclined": "O jogador recusou a transação", + "sale_timeout": "Espere um pouco antes de negociar seu veículo" + }, + "success": { + "purchased": "Parabéns pela compra!", + "earned_commission": "Você ganhou $ %s de comissão", + "gifted": "Você presenteou seu veículo", + "received_gift": "Você recebeu um veículo de presente", + "soldfor": "Você vendeu seu veículo por $", + "boughtfor": "Você comprou um veículo por $" + }, + "menus": { + "vehHeader_header": "Opções do Veículo", + "vehHeader_txt": "Interaja com o veículo atual", + "financed_header": "Veículos Financiados", + "finance_txt": "Veja seus veículos possuídos", + "returnTestDrive_header": "Finalizar Test Drive", + "categories_header": "Categorias", + "goback_header": "Voltar", + "veh_price": "Preço: $", + "veh_platetxt": "Placa: ", + "veh_finance_title": "Informações de Financiamento", + "veh_finance": "Pagamento do Veículo", + "veh_finance_balance": "Saldo Total Restante", + "veh_finance_currency": "$", + "veh_finance_total": "Pagamentos Totais Restantes", + "veh_finance_reccuring": "Valor do Pagamento Recorrente", + "veh_finance_pay": "Efetuar um pagamento", + "veh_finance_payoff": "Quitar veículo", + "veh_finance_payment": "Valor do Pagamento ($)", + "submit_text": "Enviar", + "test_header": "Test Drive", + "finance_header": "Financiar Veículo", + "owned_vehicles_header": "Veículos Possuídos", + "swap_header": "Trocar Veículo", + "swap_txt": "Troque o veículo atualmente selecionado", + "financesubmit_downpayment": "Valor do Pagamento Inicial - Mín ", + "financesubmit_totalpayment": "Pagamentos Totais - Máx ", + "freeuse_test_txt": "Teste o veículo atualmente selecionado", + "freeuse_buy_header": "Comprar Veículo", + "freeuse_buy_txt": "Compre o veículo atualmente selecionado", + "freeuse_finance_txt": "Financie o veículo atualmente selecionado", + "managed_test_txt": "Permita que o jogador faça um test drive", + "managed_sell_header": "Vender Veículo", + "managed_sell_txt": "Venda o veículo para o jogador", + "managed_finance_txt": "Financie o veículo para o jogador", + "submit_ID": "ID do Servidor (#)", + "keypress_showFinanceMenu": "[E] Abrir Menu de Finanças", + "keypress_vehicleViewMenu": "[E] Visualizar Veículo" + }, + "general": { + "testdrive_timer": "Tempo Restante do Test Drive: ", + "vehinteraction": "Interação com o Veículo", + "testdrive_timenoti": "Você tem %s minutos restantes", + "testdrive_complete": "Test drive do veículo concluído", + "paymentduein": "Seu pagamento do veículo vence em %s minutos", + "command_transfervehicle": "Presenteie ou venda seu veículo", + "command_transfervehicle_help": "ID do comprador", + "command_transfervehicle_amount": "Valor da venda (opcional)", + "transfervehicle_confirm": "Confirmar a troca de %s %s por $ %s" + } + } + \ No newline at end of file diff --git a/server/main.lua b/server/main.lua index 996bc2d..1385a78 100644 --- a/server/main.lua +++ b/server/main.lua @@ -93,16 +93,9 @@ lib.callback.register('qbx_vehicleshop:server:GetVehiclesByName', function(sourc local src = source local player = exports.qbx_core:GetPlayer(src) if not player then return end - local vehicles = FetchVehicleEntitiesByCitizenId(player.PlayerData.citizenid) local financeVehicles = FetchFinancedVehicleEntitiesByCitizenId(player.PlayerData.citizenid) - for _, v in pairs(financeVehicles) do - vehicles[v.vehicleId].balance = v.balance - vehicles[v.vehicleId].paymentamount = v.paymentamount - vehicles[v.vehicleId].paymentsleft = v.paymentsleft - vehicles[v.vehicleId].financetime = v.financetime - end - if vehicles[1] then - return vehicles + if #financeVehicles > 0 then + return financeVehicles end end) @@ -412,12 +405,11 @@ end) RegisterNetEvent('qbx_vehicleshop:server:checkFinance', function() local src = source local player = exports.qbx_core:GetPlayer(src) - local result = FetchFinancedVehicleEntitiesByCitizenId(player.PlayerData.citizenid) - if not result[1] then return end + local vehicles = FetchFinancedVehicleEntitiesByCitizenId(player.PlayerData.citizenid) + if not vehicles[1] then return end exports.qbx_core:Notify(src, locale('general.paymentduein', config.finance.paymentWarning)) Wait(config.finance.paymentWarning * 60000) - local vehicles = FetchFinancedVehicleEntitiesByCitizenId(player.PlayerData.citizenid) for _, v in pairs(vehicles) do local plate = v.plate DeleteVehicleEntity(v.id) diff --git a/server/storage.lua b/server/storage.lua index cb108d8..21dfed0 100644 --- a/server/storage.lua +++ b/server/storage.lua @@ -93,7 +93,7 @@ end ---@param citizenId string ---@return VehicleFinancingEntity function FetchFinancedVehicleEntitiesByCitizenId(citizenId) - return MySQL.query.await('SELECT vehicle_financing.* FROM vehicle_financing INNER JOIN player_vehicles ON player_vehicles.citizenid = ? WHERE vehicle_financing.vehicleId = player_vehicles.id AND vehicle_financing.balance > 0 AND vehicle_financing.financetime > 1', {citizenId}) + return MySQL.query.await('SELECT player_vehicles.vehicle, player_vehicles.plate, vehicle_financing.* FROM player_vehicles LEFT JOIN vehicle_financing ON player_vehicles.id = vehicle_financing.vehicleId WHERE citizenid = ?', {citizenId}) end ---@param license string