diff --git a/client/main.lua b/client/main.lua index 476e200..0c2da39 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,48 +1,47 @@ +local Config = Config local HasAlreadyEnteredMarker, IsInShopMenu = false, false local CurrentAction, CurrentActionMsg, LastZone, currentDisplayVehicle, CurrentVehicleData -local CurrentActionData, Vehicles, Categories = {}, {}, {} -local VehiclesByModel = {} -local vehiclesByCategory = {} - -function getVehicleFromModel(model) - return VehiclesByModel[model] -end - -function PlayerManagement() - if not Config.EnablePlayerManagement then - return - end - - if ESX.PlayerData.job.name ~= 'cardealer' then - Config.Zones.ShopEntering.Type = -1 - Config.Zones.BossActions.Type = -1 - Config.Zones.ResellVehicle.Type = -1 - return - end - Config.Zones.ShopEntering.Type = 1 - - if ESX.PlayerData.job.grade_name == 'boss' then - Config.Zones.BossActions.Type = 1 - end -end +local CurrentActionData, Vehicles, Categories, VehiclesByModel, vehiclesByCategory, soldVehicles, cardealerVehicles, rentedVehicles = {}, {}, {}, {}, {}, {}, {}, {} +local DoesEntityExist, NetworkRequestControlOfEntity, NetworkHasControlOfEntity, DisableControlAction, HasModelLoaded, RequestModel, DisableAllControlActions, FreezeEntityPosition, SetEntityCoords, SetEntityVisible = DoesEntityExist, NetworkRequestControlOfEntity, NetworkHasControlOfEntity, DisableControlAction, HasModelLoaded, RequestModel, DisableAllControlActions, FreezeEntityPosition, SetEntityCoords, SetEntityVisible + +Vehicles = GlobalState.vehicleShop.vehicles +Categories = GlobalState.vehicleShop.categories +VehiclesByModel = GlobalState.vehicleShop.vehiclesByModel +soldVehicles = GlobalState.vehicleShop.soldVehicles +cardealerVehicles = GlobalState.vehicleShop.cardealerVehicles +rentedVehicles = GlobalState.vehicleShop.rentedVehicles + +AddStateBagChangeHandler('vehicleShop', 'global', function(bagName, key, value) + Vehicles = value.vehicles + Categories = value.categories + VehiclesByModel = value.vehiclesByModel + soldVehicles = value.soldVehicles + cardealerVehicles = value.cardealerVehicles + rentedVehicles = value.rentedVehicles +end) -RegisterNetEvent('esx:playerLoaded') -AddEventHandler('esx:playerLoaded', function(xPlayer) - PlayerManagement() - TriggerServerEvent("esx_vehicleshop:getVehiclesAndCategories") +CreateThread(function() + while true do + Wait(60000) + collectgarbage("collect") + end end) -RegisterNetEvent('esx_vehicleshop:updateVehiclesAndCategories', function(vehicles, categories, vehiclesByModel) - Vehicles = vehicles - Categories = categories +local function getVehicleFromModel(model) + return VehiclesByModel[model] +end - VehiclesByModel = vehiclesByModel +local function Init() + TriggerEvent('esx_vehicleshop:updateTables') + + Wait(500) table.sort(Vehicles, function(a, b) return a.name < b.name end) - for _, vehicle in ipairs(Vehicles) do + for i = 1, #Vehicles, 1 do + local vehicle = Vehicles[i] if IsModelInCdimage(joaat(vehicle.model)) then local category = vehicle.category @@ -50,16 +49,81 @@ RegisterNetEvent('esx_vehicleshop:updateVehiclesAndCategories', function(vehicle vehiclesByCategory[category] = {} end - table.insert(vehiclesByCategory[category], vehicle) + TableInsert(vehiclesByCategory[category], vehicle) else print(('[^3WARNING^7] Ignoring vehicle ^5%s^7 due to invalid Model'):format(vehicle.model)) end end + + if Config.EnablePlayerManagement then + RegisterNetEvent('esx_phone:loaded') + AddEventHandler('esx_phone:loaded', function(phoneNumber, contacts) + local specialContact = { + name = TranslateCap('dealership'), + number = 'cardealer', + base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAADMzMzszM0M0M0w0M1Q1M101M2U2M242M3Y3M383Moc4MpA4Mpg5MqE5Mqk6MrI6Mro7Mrw8Mr89M71DML5EO8I+NMU/NcBMLshANctBNs5CN8RULMddKsheKs9YLtBCONZEOdlFOtxGO99HPNhMNsplKM1nKM1uJtRhLddiLt5kMNJwJ9B2JNR/IeNIPeVJPehKPuRQOuhSO+lZOOlhNuloM+p3Lep/KupwMMFORsVYUcplXc1waNJ7delUSepgVexrYe12bdeHH9iIH9qQHd2YG+udH+OEJeuGJ+uOJeuVIuChGeSpF+aqGOykHOysGeeyFeuzFuyzFuq6E+27FO+Cee3CEdaGgdqTjvCNhfKYkvOkngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJezdycAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjb9TgnoAAAQGElEQVR4Xt2d+WMUtxXHbS6bEGMPMcQQ04aEUnqYo9xJWvC6kAKmQLM2rdn//9+g0uir2Tl0PElPszP7+cnH7Fj6rPTeG2lmvfKld2azk8lk/36L/cnkZDbDIT3Sp4DZ8QS9dTI57tNDTwJOOu+4j/0TvDQz+QXMSG+7mUn+sZBZQELnNROcKhMZBXx+gS4k8+IzTpmBXAJOnqPxTDzPFRKyCODuvSKPgwwC2EZ+lxf4E4xwCzhBU7PBPQx4BWR88+fwDgNGAbMsM9/Ec8bygE3A5966L3nOlhiZBGSf+l2YggGLgBna1DMsE4FBQH9zvw1HLEgX0Evkt5GeEVIFMFztpJF6rZQm4DNasVDSEkKSgIVN/ibP0ZwoEgQsfPTPSZgH8QIG8vYr4gdBrIABvf2K2EEQKWBQb78ichBECRhE8O8SlQ5iBAQvcffFPhoYQoSAAQ5/TcQ0CBYw0OGvCZ4GoQIGF/3bhGaDQAELvfKhERgIwgQMePrPCQsEQQLwFwYPmksiQMCC1n1iCFgooQtYwLJfPPQFQ7KAUfU/wABVwMj6TzdAFDDY6tcOMR3SBIyw/1QDJAGj7D/RAEXA6Oa/hhIHCAJG23+SAb+AEfefYsArYET1nwlvTegVgBONFnTDik8ATjNi0BEbHgGjuP5147k6dgsYaQHQxF0OOAUMfv2LhnOVzCVg4OufdFwrpS4BePkSgA6ZcAhYggCocQRCu4ClCIAaeyC0CliaAKCwhgGrALxwaUC3OtgELFEAUNjCgEXAklQAdSzVgEUAXrRUoGstzAKWbgJIzJPAKGAJJ4DEOAmMAvCCpQPda2ASsJQTQGKa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', + } + + TriggerEvent('esx_phone:addSpecialContact', specialContact.name, specialContact.number, specialContact.base64Icon) + end) + end + + if Config.Blip.show then + CreateThread(function() + local blip = AddBlipForCoord(Config.Zones.ShopEntering.Pos) + + SetBlipSprite (blip, Config.Blip.Sprite) + SetBlipDisplay(blip, Config.Blip.Display) + SetBlipScale (blip, Config.Blip.Scale) + SetBlipAsShortRange(blip, true) + + BeginTextCommandSetBlipName('STRING') + AddTextComponentSubstringPlayerName(TranslateCap('car_dealer')) + EndTextCommandSetBlipName(blip) + end) + end + + return true +end + +local function PlayerManagement() + if not Config.EnablePlayerManagement then + return true + end + + if LocalPlayer.state.job ~= 'cardealer' then + Config.Zones.ShopEntering.Type = -1 + Config.Zones.BossActions.Type = -1 + Config.Zones.ResellVehicle.Type = -1 + return true + end + Config.Zones.ShopEntering.Type = 1 + + if LocalPlayer.state.job.grade_name == 'boss' then + Config.Zones.BossActions.Type = 1 + end + return true +end + +local function loadIpl() + RequestIpl('shr_int') + + local interiorID = 7170 + PinInteriorInMemory(interiorID) + ActivateInteriorEntitySet(interiorID, 'csr_beforeMission') + RefreshInterior(interiorID) +end + +RegisterNetEvent('esx:playerLoaded') +AddEventHandler('esx:playerLoaded', function(xPlayer) + Init() + PlayerManagement() + CreateThread(loadIpl) end) RegisterNetEvent('esx:setJob', PlayerManagement) -function DeleteDisplayVehicleInsideShop() +local function DeleteDisplayVehicleInsideShop() local attempt = 0 if currentDisplayVehicle and DoesEntityExist(currentDisplayVehicle) then @@ -75,48 +139,64 @@ function DeleteDisplayVehicleInsideShop() end end -function ReturnVehicleProvider() - ESX.TriggerServerCallback('esx_vehicleshop:getCommercialVehicles', function(vehicles) - local elements = {} - - for k, v in ipairs(vehicles) do - local returnPrice = ESX.Math.Round(v.price * 0.75) - local vehicleLabel = getVehicleFromModel(v.vehicle).label +local function ReturnVehicleProvider() + local elements = { + { + unselectable = true, + icon = "fas fa-car", + title = TranslateCap('car_dealer'), + }, + } + + for k, v in ipairs(cardealerVehicles) do + local returnPrice = ESX.Math.Round(v.price * 0.75) + local vehicleLabel = getVehicleFromModel(v.vehicle).label + + TableInsert(elements, { + title = ('%s [%s]'):format(vehicleLabel, TranslateCap('generic_shopitem', ESX.Math.GroupDigits(returnPrice))), + name = v.vehicle + }) + end - table.insert(elements, { - label = ('%s [%s]'):format(vehicleLabel, TranslateCap('generic_shopitem', ESX.Math.GroupDigits(returnPrice))), - value = v.vehicle - }) - end + ESX.OpenContext("right", elements, function(menu, element) + if not element.name then return ESX.CloseContext() end + TriggerServerEvent('esx_vehicleshop:returnProvider', element.name) + Wait(500) + ESX.CloseContext() + ReturnVehicleProvider() + end, function(menu) + end) +end - ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'return_provider_menu', { - title = TranslateCap('return_provider_menu'), - align = 'top-left', - elements = elements - }, function(data, menu) - TriggerServerEvent('esx_vehicleshop:returnProvider', data.current.value) +local function StartShopRestriction() + while IsInShopMenu do + Wait(0) - Wait(300) - menu.close() - ReturnVehicleProvider() - end, function(data, menu) - menu.close() - end) - end) + DisableControlAction(0, 75, true) -- Disable exit vehicle + DisableControlAction(27, 75, true) -- Disable exit vehicle + end end -function StartShopRestriction() - CreateThread(function() - while IsInShopMenu do - Wait(0) +local function WaitForVehicleToLoad(modelHash) + modelHash = (type(modelHash) == 'number' and modelHash or joaat(modelHash)) + + if not HasModelLoaded(modelHash) then + RequestModel(modelHash) - DisableControlAction(0, 75, true) -- Disable exit vehicle - DisableControlAction(27, 75, true) -- Disable exit vehicle + BeginTextCommandBusyspinnerOn('STRING') + AddTextComponentSubstringPlayerName(TranslateCap('shop_awaiting_model')) + EndTextCommandBusyspinnerOn(4) + + while not HasModelLoaded(modelHash) do + Wait(0) + DisableAllControlActions(0) end - end) + + BusyspinnerOff() + end end -function OpenShopMenu() +local function OpenShopMenu() if #Vehicles == 0 then print('[^3ERROR^7] Vehicleshop has ^50^7 vehicles, please add some!') return @@ -124,10 +204,11 @@ function OpenShopMenu() IsInShopMenu = true - StartShopRestriction() + CreateThread(StartShopRestriction) ESX.UI.Menu.CloseAll() + ESX.CloseContext() - local playerPed = PlayerPedId() + local playerPed = ESX.PlayerData.ped FreezeEntityPosition(playerPed, true) SetEntityVisible(playerPed, false) @@ -140,8 +221,7 @@ function OpenShopMenu() local category = Categories[i] local categoryVehicles = vehiclesByCategory[category.name] local options = {} - if categoryVehicles == nil then goto continue end - + for j=1, #categoryVehicles, 1 do local vehicle = categoryVehicles[j] @@ -149,12 +229,12 @@ function OpenShopMenu() firstVehicleData = vehicle end - table.insert(options, ('%s %s'):format(vehicle.name, TranslateCap('generic_shopitem', ESX.Math.GroupDigits(vehicle.price)))) + TableInsert(options, ('%s %s'):format(vehicle.name, TranslateCap('generic_shopitem', ESX.Math.GroupDigits(vehicle.price)))) end table.sort(options) - table.insert(elements, { + TableInsert(elements, { name = category.name, label = category.label, value = 0, @@ -162,7 +242,6 @@ function OpenShopMenu() max = #Categories[i], options = options }) - ::continue:: end ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'vehicle_shop', { @@ -190,7 +269,7 @@ function OpenShopMenu() CurrentActionMsg = TranslateCap('shop_menu') CurrentActionData = {} - local playerPed = PlayerPedId() + local playerPed = ESX.PlayerData.ped FreezeEntityPosition(playerPed, false) SetEntityVisible(playerPed, true) SetEntityCoords(playerPed, Config.Zones.ShopEntering.Pos) @@ -227,7 +306,7 @@ function OpenShopMenu() end, function(data, menu) menu.close() DeleteDisplayVehicleInsideShop() - local playerPed = PlayerPedId() + local playerPed = ESX.PlayerData.ped CurrentAction = 'shop_menu' CurrentActionMsg = TranslateCap('shop_menu') @@ -240,12 +319,12 @@ function OpenShopMenu() IsInShopMenu = false end, function(data, menu) local vehicleData = vehiclesByCategory[data.current.name][data.current.value + 1] - local playerPed = PlayerPedId() + local playerPed = ESX.PlayerData.ped + DeleteDisplayVehicleInsideShop() WaitForVehicleToLoad(vehicleData.model) ESX.Game.SpawnLocalVehicle(vehicleData.model, Config.Zones.ShopInside.Pos, Config.Zones.ShopInside.Heading, function(vehicle) - DeleteDisplayVehicleInsideShop() currentDisplayVehicle = vehicle TaskWarpPedIntoVehicle(playerPed, vehicle, -1) FreezeEntityPosition(vehicle, true) @@ -253,10 +332,10 @@ function OpenShopMenu() end) end) + DeleteDisplayVehicleInsideShop() WaitForVehicleToLoad(firstVehicleData.model) ESX.Game.SpawnLocalVehicle(firstVehicleData.model, Config.Zones.ShopInside.Pos, Config.Zones.ShopInside.Heading, function(vehicle) - DeleteDisplayVehicleInsideShop() currentDisplayVehicle = vehicle TaskWarpPedIntoVehicle(playerPed, vehicle, -1) FreezeEntityPosition(vehicle, true) @@ -264,44 +343,26 @@ function OpenShopMenu() end) end -function WaitForVehicleToLoad(modelHash) - modelHash = (type(modelHash) == 'number' and modelHash or joaat(modelHash)) - - if not HasModelLoaded(modelHash) then - RequestModel(modelHash) - - BeginTextCommandBusyspinnerOn('STRING') - AddTextComponentSubstringPlayerName(TranslateCap('shop_awaiting_model')) - EndTextCommandBusyspinnerOn(4) - - while not HasModelLoaded(modelHash) do - Wait(0) - DisableAllControlActions(0) - end - - BusyspinnerOff() - end -end - function OpenResellerMenu() ESX.UI.Menu.CloseAll() - - ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'reseller', { - title = TranslateCap('car_dealer'), - align = 'top-left', - elements = { - {label = TranslateCap('buy_vehicle'), value = 'buy_vehicle'}, - {label = TranslateCap('pop_vehicle'), value = 'pop_vehicle'}, - {label = TranslateCap('depop_vehicle'), value = 'depop_vehicle'}, - {label = TranslateCap('return_provider'), value = 'return_provider'}, - {label = TranslateCap('create_bill'), value = 'create_bill'}, - {label = TranslateCap('get_rented_vehicles'), value = 'get_rented_vehicles'}, - {label = TranslateCap('set_vehicle_owner_sell'), value = 'set_vehicle_owner_sell'}, - {label = TranslateCap('set_vehicle_owner_rent'), value = 'set_vehicle_owner_rent'}, - {label = TranslateCap('deposit_stock'), value = 'put_stock'}, - {label = TranslateCap('take_stock'), value = 'get_stock'} - }}, function(data, menu) - local action = data.current.value + ESX.CloseContext() + + local elements = { + {unselectable = true, icon = 'fas fa-car', title = TranslateCap('car_dealer')}, + {title = TranslateCap('buy_vehicle'), name = 'buy_vehicle'}, + {title = TranslateCap('pop_vehicle'), name = 'pop_vehicle'}, + {title = TranslateCap('depop_vehicle'), name = 'depop_vehicle'}, + {title = TranslateCap('return_provider'), name = 'return_provider'}, + {title = TranslateCap('create_bill'), name = 'create_bill'}, + {title = TranslateCap('get_rented_vehicles'), name = 'get_rented_vehicles'}, + {title = TranslateCap('set_vehicle_owner_sell'), name = 'set_vehicle_owner_sell'}, + {title = TranslateCap('set_vehicle_owner_rent'), name = 'set_vehicle_owner_rent'}, + {title = TranslateCap('deposit_stock'), name = 'put_stock'}, + {title = TranslateCap('take_stock'), name = 'get_stock'}, + } + + ESX.OpenContext('right', elements, function(menu, element) + local action = element.name if Config.OxInventory and (action == 'put_stock' or action == 'get_stock') then exports.ox_inventory:openInventory('stash', 'society_cardealer') @@ -323,28 +384,22 @@ function OpenResellerMenu() ReturnVehicleProvider() elseif action == 'create_bill' then local closestPlayer, closestDistance = ESX.Game.GetClosestPlayer() - if closestPlayer ~= -1 and closestDistance < 3 then - ESX.UI.Menu.Open('dialog', GetCurrentResourceName(), 'set_vehicle_owner_sell_amount', { - title = TranslateCap('invoice_amount') - }, function(data2, menu2) - local amount = tonumber(data2.value) - - if amount == nil then - ESX.ShowNotification(TranslateCap('invalid_amount')) - else - menu2.close() - local closestPlayer, closestDistance = ESX.Game.GetClosestPlayer() - - if closestPlayer == -1 or closestDistance > 3.0 then - ESX.ShowNotification(TranslateCap('no_players')) - else - TriggerServerEvent('esx_billing:sendBill', GetPlayerServerId(closestPlayer), 'society_cardealer', TranslateCap('car_dealer'), tonumber(data2.value)) + ESX.CloseContext() + ESX.OpenContext('right', {{title = TranslateCap('invoice_amount'), input = true, inputType = 'number', inputValue = 0, inputMin = 0, name = 'invoice_amount'}}, function(menu2, element2) + if element2.name == 'invoice_amount' then + local amount = tonumber(element2.inputValue) + if amount ~= nil then + ESX.CloseContext() + local closestPlayer, closestDistance = ESX.Game.GetClosestPlayer() + if closestPlayer == -1 or closestDistance > 3.0 then + ESX.ShowNotification(TranslateCap('no_players')) + else + TriggerServerEvent('esx_billing:sendBill', GetPlayerServerId(closestPlayer), 'society_cardealer', TranslateCap('car_dealer'), amount) + end end end - end, function(data2, menu2) - menu2.close() - end) + end, function(menu) end) else ESX.ShowNotification(TranslateCap('no_players')) end @@ -403,9 +458,7 @@ function OpenResellerMenu() ESX.ShowNotification(TranslateCap('no_current_vehicle')) end end - end, function(data, menu) - menu.close() - + end, function(menu) CurrentAction = 'reseller_menu' CurrentActionMsg = TranslateCap('shop_menu') CurrentActionData = {} @@ -413,66 +466,62 @@ function OpenResellerMenu() end function OpenPopVehicleMenu() - ESX.TriggerServerCallback('esx_vehicleshop:getCommercialVehicles', function(vehicles) - local elements = {} + local elements = {} - for k,v in ipairs(vehicles) do - local vehicleLabel = getVehicleFromModel(v.vehicle).label + for k,v in ipairs(cardealerVehicles) do + local vehicleLabel = getVehicleFromModel(v.vehicle).label - table.insert(elements, { - label = ('%s [%s]'):format(vehicleLabel, TranslateCap('generic_shopitem', ESX.Math.GroupDigits(v.price))), - value = v.vehicle - }) - end + TableInsert(elements, { + label = ('%s [%s]'):format(vehicleLabel, TranslateCap('generic_shopitem', ESX.Math.GroupDigits(v.price))), + value = v.vehicle + }) + end - ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'commercial_vehicles', { - title = TranslateCap('vehicle_dealer'), - align = 'top-left', - elements = elements - }, function(data, menu) - local model = data.current.value + ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'commercial_vehicles', { + title = TranslateCap('vehicle_dealer'), + align = 'top-left', + elements = elements + }, function(data, menu) + local model = data.current.value + DeleteDisplayVehicleInsideShop() - ESX.Game.SpawnVehicle(model, Config.Zones.ShopInside.Pos, Config.Zones.ShopInside.Heading, function(vehicle) - DeleteDisplayVehicleInsideShop() - currentDisplayVehicle = vehicle + ESX.Game.SpawnVehicle(model, Config.Zones.ShopInside.Pos, Config.Zones.ShopInside.Heading, function(vehicle) + currentDisplayVehicle = vehicle - for i=1, #Vehicles, 1 do - if model == Vehicles[i].model then - CurrentVehicleData = Vehicles[i] - break - end + for i=1, #Vehicles, 1 do + if model == Vehicles[i].model then + CurrentVehicleData = Vehicles[i] + break end - end) - end, function(data, menu) - menu.close() + end end) + end, function(data, menu) + menu.close() end) end function OpenRentedVehiclesMenu() - ESX.TriggerServerCallback('esx_vehicleshop:getRentedVehicles', function(vehicles) - local elements = {} + local elements = {} - for k,v in ipairs(vehicles) do - local vehicleLabel = getVehicleFromModel(v.name).label + for k,v in ipairs(rentedVehicles) do + local vehicleLabel = getVehicleFromModel(v.name).label - table.insert(elements, { - label = ('%s: %s - %s'):format(v.playerName, vehicleLabel, v.plate), - value = v.name - }) - end + TableInsert(elements, { + label = ('%s: %s - %s'):format(v.playerName, vehicleLabel, v.plate), + value = v.name + }) + end - ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'rented_vehicles', { - title = TranslateCap('rent_vehicle'), - align = 'top-left', - elements = elements - }, nil, function(data, menu) - menu.close() - end) + ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'rented_vehicles', { + title = TranslateCap('rent_vehicle'), + align = 'top-left', + elements = elements + }, nil, function(data, menu) + menu.close() end) end -function OpenBossActionsMenu() +local function OpenBossActionsMenu() ESX.UI.Menu.CloseAll() ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'reseller',{ @@ -488,21 +537,20 @@ function OpenBossActionsMenu() end) elseif data.current.value == 'sold_vehicles' then - ESX.TriggerServerCallback('esx_vehicleshop:getSoldVehicles', function(customers) local elements = { head = { TranslateCap('customer_client'), TranslateCap('customer_model'), TranslateCap('customer_plate'), TranslateCap('customer_soldby'), TranslateCap('customer_date') }, rows = {} } - for i=1, #customers, 1 do - table.insert(elements.rows, { - data = customers[i], + for i=1, #soldVehicles, 1 do + TableInsert(elements.rows, { + data = soldVehicles[i], cols = { - customers[i].client, - customers[i].model, - customers[i].plate, - customers[i].soldby, - customers[i].date + soldVehicles[i].client, + soldVehicles[i].model, + soldVehicles[i].plate, + soldVehicles[i].soldby, + soldVehicles[i].date } }) end @@ -512,7 +560,6 @@ function OpenBossActionsMenu() end, function(data2, menu2) menu2.close() end) - end) end end, function(data, menu) @@ -530,7 +577,7 @@ function OpenGetStocksMenu() for i=1, #items, 1 do if items[i].count > 0 then - table.insert(elements, { + TableInsert(elements, { label = 'x' .. items[i].count .. ' ' .. items[i].label, value = items[i].name }) @@ -574,7 +621,7 @@ function OpenPutStocksMenu() local item = inventory.items[i] if item.count > 0 then - table.insert(elements, { + TableInsert(elements, { label = item.label .. ' x' .. item.count, type = 'item_standard', value = item.name @@ -611,20 +658,20 @@ function OpenPutStocksMenu() end) end -function hasEnteredMarker(zone) +local function hasEnteredMarker(zone) if zone == 'ShopEntering' then if not Config.EnablePlayerManagement then CurrentAction = 'shop_menu' CurrentActionMsg = TranslateCap('shop_menu') CurrentActionData = {} end - if ESX.PlayerData.job ~= nil and ESX.PlayerData.job.name == 'cardealer' then + if LocalPlayer.state.job ~= nil and LocalPlayer.state.job.name == 'cardealer' then CurrentAction = 'reseller_menu' CurrentActionMsg = TranslateCap('shop_menu') CurrentActionData = {} end elseif zone == 'GiveBackVehicle' and Config.EnablePlayerManagement then - local playerPed = PlayerPedId() + local playerPed = ESX.PlayerData.ped if IsPedInAnyVehicle(playerPed, false) then local vehicle = GetVehiclePedIsIn(playerPed, false) @@ -634,7 +681,7 @@ function hasEnteredMarker(zone) CurrentActionData = {vehicle = vehicle} end elseif zone == 'ResellVehicle' then - local playerPed = PlayerPedId() + local playerPed = ESX.PlayerData.ped if IsPedSittingInAnyVehicle(playerPed) then local vehicle = GetVehiclePedIsIn(playerPed, false) @@ -669,71 +716,44 @@ function hasEnteredMarker(zone) end end - elseif zone == 'BossActions' and Config.EnablePlayerManagement and ESX.PlayerData.job ~= nil and ESX.PlayerData.job.name == 'cardealer' and ESX.PlayerData.job.grade_name == 'boss' then + elseif zone == 'BossActions' and Config.EnablePlayerManagement and LocalPlayer.state.job ~= nil and LocalPlayer.state.job.name == 'cardealer' and LocalPlayer.state.job.grade_name == 'boss' then CurrentAction = 'boss_actions_menu' CurrentActionMsg = TranslateCap('shop_menu') CurrentActionData = {} end end -function hasExitedMarker(zone) +local function hasExitedMarker(zone) if not IsInShopMenu then ESX.UI.Menu.CloseAll() + ESX.CloseContext() end ESX.HideUI() CurrentAction = nil end AddEventHandler('onResourceStop', function(resource) - if resource == GetCurrentResourceName() then - if IsInShopMenu then - ESX.UI.Menu.CloseAll() + if resource ~= GetCurrentResourceName() then return end + if IsInShopMenu then + ESX.UI.Menu.CloseAll() + ESX.CloseContext() - local playerPed = PlayerPedId() + local playerPed = ESX.PlayerData.ped - FreezeEntityPosition(playerPed, false) - SetEntityVisible(playerPed, true) - SetEntityCoords(playerPed, Config.Zones.ShopEntering.Pos) - end - - DeleteDisplayVehicleInsideShop() + FreezeEntityPosition(playerPed, false) + SetEntityVisible(playerPed, true) + SetEntityCoords(playerPed, Config.Zones.ShopEntering.Pos) end -end) - -if Config.EnablePlayerManagement then - RegisterNetEvent('esx_phone:loaded') - AddEventHandler('esx_phone:loaded', function(phoneNumber, contacts) - local specialContact = { - name = TranslateCap('dealership'), - number = 'cardealer', - base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAADMzMzszM0M0M0w0M1Q1M101M2U2M242M3Y3M383Moc4MpA4Mpg5MqE5Mqk6MrI6Mro7Mrw8Mr89M71DML5EO8I+NMU/NcBMLshANctBNs5CN8RULMddKsheKs9YLtBCONZEOdlFOtxGO99HPNhMNsplKM1nKM1uJtRhLddiLt5kMNJwJ9B2JNR/IeNIPeVJPehKPuRQOuhSO+lZOOlhNuloM+p3Lep/KupwMMFORsVYUcplXc1waNJ7delUSepgVexrYe12bdeHH9iIH9qQHd2YG+udH+OEJeuGJ+uOJeuVIuChGeSpF+aqGOykHOysGeeyFeuzFuyzFuq6E+27FO+Cee3CEdaGgdqTjvCNhfKYkvOkngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJezdycAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjb9TgnoAAAQGElEQVR4Xt2d+WMUtxXHbS6bEGMPMcQQ04aEUnqYo9xJWvC6kAKmQLM2rdn//9+g0uir2Tl0PElPszP7+cnH7Fj6rPTeG2lmvfKld2azk8lk/36L/cnkZDbDIT3Sp4DZ8QS9dTI57tNDTwJOOu+4j/0TvDQz+QXMSG+7mUn+sZBZQELnNROcKhMZBXx+gS4k8+IzTpmBXAJOnqPxTDzPFRKyCODuvSKPgwwC2EZ+lxf4E4xwCzhBU7PBPQx4BWR88+fwDgNGAbMsM9/Ec8bygE3A5966L3nOlhiZBGSf+l2YggGLgBna1DMsE4FBQH9zvw1HLEgX0Evkt5GeEVIFMFztpJF6rZQm4DNasVDSEkKSgIVN/ibP0ZwoEgQsfPTPSZgH8QIG8vYr4gdBrIABvf2K2EEQKWBQb78ichBECRhE8O8SlQ5iBAQvcffFPhoYQoSAAQ5/TcQ0CBYw0OGvCZ4GoQIGF/3bhGaDQAELvfKhERgIwgQMePrPCQsEQQLwFwYPmksiQMCC1n1iCFgooQtYwLJfPPQFQ7KAUfU/wABVwMj6TzdAFDDY6tcOMR3SBIyw/1QDJAGj7D/RAEXA6Oa/hhIHCAJG23+SAb+AEfefYsArYET1nwlvTegVgBONFnTDik8ATjNi0BEbHgGjuP5147k6dgsYaQHQxF0OOAUMfv2LhnOVzCVg4OufdFwrpS4BePkSgA6ZcAhYggCocQRCu4ClCIAaeyC0CliaAKCwhgGrALxwaUC3OtgELFEAUNjCgEXAklQAdSzVgEUAXrRUoGstzAKWbgJIzJPAKGAJJ4DEOAmMAvCCpQPda2ASsJQTQGKa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', - } - - TriggerEvent('esx_phone:addSpecialContact', specialContact.name, specialContact.number, specialContact.base64Icon) - end) -end - --- Create Blips -if Config.Blip.show then - CreateThread(function() - local blip = AddBlipForCoord(Config.Zones.ShopEntering.Pos) - - SetBlipSprite (blip, Config.Blip.Sprite) - SetBlipDisplay(blip, Config.Blip.Display) - SetBlipScale (blip, Config.Blip.Scale) - SetBlipAsShortRange(blip, true) - BeginTextCommandSetBlipName('STRING') - AddTextComponentSubstringPlayerName(TranslateCap('car_dealer')) - EndTextCommandSetBlipName(blip) - end) -end + ESX.HideUI() + DeleteDisplayVehicleInsideShop() +end) -- Enter / Exit marker events & Draw Markers CreateThread(function() while true do Wait(0) - local playerCoords = GetEntityCoords(PlayerPedId()) + local playerCoords = GetEntityCoords(ESX.PlayerData.ped) local isInMarker, letSleep, currentZone = false, true for k,v in pairs(Config.Zones) do @@ -764,7 +784,7 @@ CreateThread(function() end if letSleep then - Wait(500) + Wait(1000) end end end) @@ -817,18 +837,9 @@ CreateThread(function() CurrentAction = nil end else - Wait(500) + Wait(1000) end end end) -CreateThread(function() - RequestIpl('shr_int') -- Load walls and floor - - local interiorID = 7170 - PinInteriorInMemory(interiorID) - ActivateInteriorEntitySet(interiorID, 'csr_beforeMission') -- Load large window - RefreshInterior(interiorID) -end) - if ESX.PlayerLoaded then PlayerManagement() end diff --git a/client/utils.lua b/client/utils.lua index 6d05de3..5f8af39 100644 --- a/client/utils.lua +++ b/client/utils.lua @@ -1,10 +1,9 @@ -local NumberCharset = {} -local Charset = {} +local NumberCharset, Charset = {}, {} -for i = 48, 57 do table.insert(NumberCharset, string.char(i)) end +for i = 48, 57 do TableInsert(NumberCharset, string.char(i)) end -for i = 65, 90 do table.insert(Charset, string.char(i)) end -for i = 97, 122 do table.insert(Charset, string.char(i)) end +for i = 65, 90 do TableInsert(Charset, string.char(i)) end +for i = 97, 122 do TableInsert(Charset, string.char(i)) end function GeneratePlate() math.randomseed(GetGameTimer()) @@ -39,3 +38,7 @@ function GetRandomLetter(length) Wait(0) return length > 0 and GetRandomLetter(length - 1) .. Charset[math.random(1, #Charset)] or '' end + +function TableInsert(t, v) + t[#t + 1] = v +end \ No newline at end of file diff --git a/server/main.lua b/server/main.lua index d462829..2e2dd3d 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,11 +1,75 @@ -local categories, vehicles = {}, {} -local vehiclesByModel = {} - +local Config = Config + +local vehicleShop = { + categories = {}, + vehicles = {}, + vehiclesByModel = {}, + soldVehicles = {}, + cardealerVehicles = {}, + rentedVehicles = {} +} CreateThread(function() - exports["esx_society"]:registerSociety('cardealer', TranslateCap('car_dealer'), 'society_cardealer', 'society_cardealer', 'society_cardealer', {type = 'private'}) + while true do + Wait(60000) + collectgarbage("collect") + end end) +local function getCategories() + vehicleShop.categories = MySQL.query.await('SELECT * FROM vehicle_categories') + GlobalState.vehicleShop = vehicleShop + return true +end + +local function getVehicles() + vehicleShop.vehicles = MySQL.query.await('SELECT vehicles.*, vehicle_categories.label AS categoryLabel FROM vehicles JOIN vehicle_categories ON vehicles.category = vehicle_categories.name') + + for _, vehicle in pairs(vehicleShop.vehicles) do + vehicleShop.vehiclesByModel[vehicle.model] = vehicle + end + + GlobalState.vehicleShop = vehicleShop + return true +end + +local function getSoldVehicles() + vehicleShop.soldVehicles = MySQL.query.await('SELECT * FROM vehicle_sold ORDER BY DATE DESC') + GlobalState.vehicleShop = vehicleShop + return true +end + +local function getCardealerVehicles() + vehicleShop.cardealerVehicles = MySQL.query.await('SELECT * FROM cardealer_vehicles ORDER BY vehicle ASC') + GlobalState.vehicleShop = vehicleShop + return true +end + +local function getRentedVehicles() + MySQL.query('SELECT * FROM rented_vehicles ORDER BY player_name ASC', function(result) + vehicleShop.rentedVehicles = {} + + for i = 1, #result do + local vehicle = result[i] + vehicleShop.rentedVehicles[#vehicleShop.rentedVehicles + 1] = { + name = vehicle.vehicle, + plate = vehicle.plate, + playerName = vehicle.player_name + } + end + GlobalState.vehicleShop = vehicleShop + return true + end) +end + CreateThread(function() + exports["esx_society"]:registerSociety('cardealer', TranslateCap('car_dealer'), 'society_cardealer', 'society_cardealer', 'society_cardealer', {type = 'private'}) + + getCategories() + getVehicles() + getSoldVehicles() + getCardealerVehicles() + getRentedVehicles() + local char = Config.PlateLetters char = char + Config.PlateNumbers if Config.PlateUseSpace then char = char + 1 end @@ -15,95 +79,75 @@ CreateThread(function() end end) -function RemoveOwnedVehicle(plate) +local function removeOwnedVehicle(plate) MySQL.update('DELETE FROM owned_vehicles WHERE plate = ?', {plate}) end -AddEventHandler('onResourceStart', function(resourceName) - if resourceName == GetCurrentResourceName() then - SQLVehiclesAndCategories() - end -end) - -function SQLVehiclesAndCategories() - categories = MySQL.query.await('SELECT * FROM vehicle_categories') - vehicles = MySQL.query.await('SELECT vehicles.*, vehicle_categories.label AS categoryLabel FROM vehicles JOIN vehicle_categories ON vehicles.category = vehicle_categories.name') - - for _, vehicle in pairs(vehicles) do - vehiclesByModel[vehicle.model] = vehicle - end - - TriggerClientEvent("esx_vehicleshop:updateVehiclesAndCategories", -1, vehicles, categories, vehiclesByModel) -end - -function getVehicleFromModel(model) - return vehiclesByModel[model] +local function getVehicleFromModel(model) + return vehicleShop.vehiclesByModel[model] end -RegisterNetEvent("esx_vehicleshop:getVehiclesAndCategories", function() - TriggerClientEvent("esx_vehicleshop:updateVehiclesAndCategories", source, vehicles, categories, vehiclesByModel) -end) - RegisterNetEvent('esx_vehicleshop:setVehicleOwnedPlayerId') AddEventHandler('esx_vehicleshop:setVehicleOwnedPlayerId', function(playerId, vehicleProps, model, label) local xPlayer, xTarget = ESX.GetPlayerFromId(source), ESX.GetPlayerFromId(playerId) - if xPlayer.job.name ~= 'cardealer' or not xTarget then + if Player(source).state.job ~= 'cardealer' or not xTarget then return end - - MySQL.scalar('SELECT id FROM cardealer_vehicles WHERE vehicle = ?', {model}, - function(id) - if not id then - return + + if not model then return end + + for i = 1, #vehicleShop.cardealerVehicles, 1 do + local v = vehicleShop.cardealerVehicles[i] + if v.vehicle == model then + local sqlDel = MySQL.update.await('DELETE FROM cardealer_vehicles WHERE id = ?', {v.id}) + if not sqlDel then return end + table.remove(vehicleShop.cardealerVehicles, i) + GlobalState.vehicleShop = vehicleShop + break end + end - MySQL.update('DELETE FROM cardealer_vehicles WHERE id = ?', {id}, - function(rowsChanged) - if rowsChanged == 1 then - MySQL.insert('INSERT INTO owned_vehicles (owner, plate, vehicle) VALUES (?, ?, ?)', {xTarget.identifier, vehicleProps.plate, json.encode(vehicleProps)}, - function(id) - xPlayer.showNotification(TranslateCap('vehicle_set_owned', vehicleProps.plate, xTarget.getName())) - xTarget.showNotification(TranslateCap('vehicle_belongs', vehicleProps.plate)) - end) - - MySQL.insert('INSERT INTO vehicle_sold (client, model, plate, soldby, date) VALUES (?, ?, ?, ?, ?)', {xTarget.getName(), label, vehicleProps.plate, xPlayer.getName(), os.date('%Y-%m-%d %H:%M')}) - end - end) + MySQL.insert('INSERT INTO owned_vehicles (owner, plate, vehicle) VALUES (?, ?, ?)', {xTarget.identifier, vehicleProps.plate, json.encode(vehicleProps)}, function(id) + xPlayer.showNotification(TranslateCap('vehicle_set_owned', vehicleProps.plate, xTarget.getName())) + xTarget.showNotification(TranslateCap('vehicle_belongs', vehicleProps.plate)) end) -end) -ESX.RegisterServerCallback('esx_vehicleshop:getSoldVehicles', function(source, cb) - MySQL.query('SELECT client, model, plate, soldby, date FROM vehicle_sold ORDER BY DATE DESC', function(result) - cb(result) - end) + local sqlIns = MySQL.insert.await('INSERT INTO vehicle_sold (client, model, plate, soldby, date) VALUES (?, ?, ?, ?, ?)', {xTarget.getName(), label, vehicleProps.plate, xPlayer.getName(), os.date('%Y-%m-%d %H:%M')}) + if not sqlIns then return end + vehicleShop.soldVehicles[#vehicleShop.soldVehicles + 1] = {xTarget.getName(), label, vehicleProps.plate, xPlayer.getName(), os.date('%Y-%m-%d %H:%M')} + GlobalState.vehicleShop = vehicleShop end) RegisterNetEvent('esx_vehicleshop:rentVehicle') AddEventHandler('esx_vehicleshop:rentVehicle', function(vehicle, plate, rentPrice, playerId) local xPlayer, xTarget = ESX.GetPlayerFromId(source), ESX.GetPlayerFromId(playerId) - if xPlayer.job.name ~= 'cardealer' or not xTarget then + if Player(source).state.job ~= 'cardealer' or not xTarget then return end - - MySQL.single('SELECT id, price FROM cardealer_vehicles WHERE vehicle = ?', {vehicle}, - function(result) - if not result then - return + + if not vehicle or not plate or not rentPrice then return end + + local price = nil + + for i = 1, #vehicleShop.cardealerVehicles, 1 do + local v = vehicleShop.cardealerVehicles[i] + if v.vehicle == vehicle then + price = v.price + local sqlDel = MySQL.update.await('DELETE FROM cardealer_vehicles WHERE id = ?', {v.id}) + if not sqlDel then return end + table.remove(vehicleShop.cardealerVehicles, i) + GlobalState.vehicleShop = vehicleShop + break end + end + + if not price then return end - MySQL.update('DELETE FROM cardealer_vehicles WHERE id = ?', {result.id}, - function(rowsChanged) - if rowsChanged ~= 1 then - return - end - - MySQL.insert('INSERT INTO rented_vehicles (vehicle, plate, player_name, base_price, rent_price, owner) VALUES (?, ?, ?, ?, ?, ?)', {vehicle, plate, xTarget.getName(), result.price, rentPrice, xTarget.identifier}, - function(id) - xPlayer.showNotification(TranslateCap('vehicle_set_rented', plate, xTarget.getName())) - end) - end) + MySQL.insert('INSERT INTO rented_vehicles (vehicle, plate, player_name, base_price, rent_price, owner) VALUES (?, ?, ?, ?, ?, ?)', {vehicle, plate, xTarget.getName(), price, rentPrice, xTarget.identifier}, + function(id) + xPlayer.showNotification(TranslateCap('vehicle_set_rented', plate, xTarget.getName())) end) end) @@ -115,10 +159,8 @@ AddEventHandler('esx_vehicleshop:getStockItem', function(itemName, count) TriggerEvent('esx_addoninventory:getSharedInventory', 'society_cardealer', function(inventory) local item = inventory.getItem(itemName) - -- is there enough in the society? if count > 0 and item.count >= count then - -- can the player carry the said amount of x item? if not xPlayer.canCarryItem(itemName, count) then return xPlayer.showNotification(TranslateCap('player_cannot_hold')) end @@ -139,13 +181,14 @@ AddEventHandler('esx_vehicleshop:putStockItems', function(itemName, count) TriggerEvent('esx_addoninventory:getSharedInventory', 'society_cardealer', function(inventory) local item = inventory.getItem(itemName) - if item.count >= 0 then - xPlayer.removeInventoryItem(itemName, count) - inventory.addItem(itemName, count) - xPlayer.showNotification(TranslateCap('have_deposited', count, item.label)) - else + if item.count < 0 then xPlayer.showNotification(TranslateCap('invalid_amount')) + return end + + xPlayer.removeInventoryItem(itemName, count) + inventory.addItem(itemName, count) + xPlayer.showNotification(TranslateCap('have_deposited', count, item.label)) end) end) @@ -153,51 +196,55 @@ ESX.RegisterServerCallback('esx_vehicleshop:buyVehicle', function(source, cb, mo local xPlayer = ESX.GetPlayerFromId(source) local modelPrice = getVehicleFromModel(model).price - if modelPrice and xPlayer.getMoney() >= modelPrice then - xPlayer.removeMoney(modelPrice, "Vehicle Purchase") - - MySQL.insert('INSERT INTO owned_vehicles (owner, plate, vehicle) VALUES (?, ?, ?)', {xPlayer.identifier, plate, json.encode({model = joaat(model), plate = plate}) - }, function(rowsChanged) - xPlayer.showNotification(TranslateCap('vehicle_belongs', plate)) - ESX.OneSync.SpawnVehicle(joaat(model), Config.Zones.ShopOutside.Pos, Config.Zones.ShopOutside.Heading,{plate = plate}, function(vehicle) - Wait(100) - local vehicle = NetworkGetEntityFromNetworkId(vehicle) - Wait(300) - TaskWarpPedIntoVehicle(GetPlayerPed(source), vehicle, -1) - end) - cb(true) - end) - else + if not modelPrice then + cb(false) + return + end + + if xPlayer.getMoney() < modelPrice then cb(false) + return end -end) -ESX.RegisterServerCallback('esx_vehicleshop:getCommercialVehicles', function(source, cb) - MySQL.query('SELECT price, vehicle FROM cardealer_vehicles ORDER BY vehicle ASC', function(result) - cb(result) + xPlayer.removeMoney(modelPrice, "Vehicle Purchase") + + MySQL.insert('INSERT INTO owned_vehicles (owner, plate, vehicle) VALUES (?, ?, ?)', {xPlayer.identifier, plate, json.encode({model = joaat(model), plate = plate}) + }, function(rowsChanged) + xPlayer.showNotification(TranslateCap('vehicle_belongs', plate)) + ESX.OneSync.SpawnVehicle(joaat(model), Config.Zones.ShopOutside.Pos, Config.Zones.ShopOutside.Heading,{plate = plate}, function(vehicle) + Wait(100) + local vehicle = NetworkGetEntityFromNetworkId(vehicle) + Wait(300) + TaskWarpPedIntoVehicle(GetPlayerPed(source), vehicle, -1) + end) + cb(true) end) end) ESX.RegisterServerCallback('esx_vehicleshop:buyCarDealerVehicle', function(source, cb, model) - local xPlayer = ESX.GetPlayerFromId(source) - - if xPlayer.job.name ~= 'cardealer' then + if Player(source).state.job ~= 'cardealer' then return cb(false) end + local modelPrice = getVehicleFromModel(model).price if not modelPrice then return cb(false) end + TriggerEvent('esx_addonaccount:getSharedAccount', 'society_cardealer', function(account) if account.money < modelPrice then return cb(false) end - account.removeMoney(modelPrice) - MySQL.insert('INSERT INTO cardealer_vehicles (vehicle, price) VALUES (?, ?)', {model, modelPrice}, function(rowsChanged) + if not rowsChanged then + cb(false) + return + end + account.removeMoney(modelPrice) + getCardealerVehicles() cb(true) end) end) @@ -207,71 +254,67 @@ RegisterNetEvent('esx_vehicleshop:returnProvider') AddEventHandler('esx_vehicleshop:returnProvider', function(vehicleModel) local xPlayer = ESX.GetPlayerFromId(source) - if xPlayer.job.name ~= 'cardealer' then + if Player(source).state.job ~= 'cardealer' then return end - MySQL.single('SELECT id, price FROM cardealer_vehicles WHERE vehicle = ?', {vehicleModel}, - function(result) - if not result then - return print(('[^3WARNING^7] Player ^5%s^7 Attempted To Sell Invalid Vehicle - ^5%s^7!'):format(source, vehicleModel)) + + local id = nil + local price = nil + + for i = 1, #vehicleShop.cardealerVehicles, 1 do + local v = vehicleShop.cardealerVehicles[i] + if v.vehicle == vehicleModel then + id = v.id + price = v.price + local sqlDel = MySQL.update.await('DELETE FROM cardealer_vehicles WHERE id = ?', {v.id}) + if not sqlDel then return end + table.remove(vehicleShop.cardealerVehicles, i) + GlobalState.vehicleShop = vehicleShop + break end + end + + if not id or not price then return end - local id = result.id - - MySQL.update('DELETE FROM cardealer_vehicles WHERE id = ?', {id}, - function(rowsChanged) - if rowsChanged ~= 1 then - return - end - TriggerEvent('esx_addonaccount:getSharedAccount', 'society_cardealer', function(account) - local price = ESX.Math.Round(result.price * 0.75) - local vehicleLabel = getVehicleFromModel(vehicleModel).label - - account.addMoney(price) - xPlayer.showNotification(TranslateCap('vehicle_sold_for', vehicleLabel, ESX.Math.GroupDigits(price))) - end) - end) - end) -end) - -ESX.RegisterServerCallback('esx_vehicleshop:getRentedVehicles', function(source, cb) - MySQL.query('SELECT * FROM rented_vehicles ORDER BY player_name ASC', function(result) - local vehicles = {} - - for i = 1, #result do - local vehicle = result[i] - vehicles[#vehicles + 1] = { - name = vehicle.vehicle, - plate = vehicle.plate, - playerName = vehicle.player_name - } - end + TriggerEvent('esx_addonaccount:getSharedAccount', 'society_cardealer', function(account) + local vehPrice = ESX.Math.Round(price * 0.75) + local vehicleLabel = getVehicleFromModel(vehicleModel).label - cb(vehicles) + account.addMoney(vehPrice) + xPlayer.showNotification(TranslateCap('vehicle_sold_for', vehicleLabel, ESX.Math.GroupDigits(vehPrice))) end) end) ESX.RegisterServerCallback('esx_vehicleshop:giveBackVehicle', function(source, cb, plate) - MySQL.single('SELECT base_price, vehicle FROM rented_vehicles WHERE plate = ?', {plate}, - function(result) - if not result then - return cb(false) + local basePrice, vehicle = nil, nil + + if not plate then return end + + for i = 1, #vehicleShop.rentedVehicles, 1 do + local v = vehicleShop.rentedVehicles[i] + if v.plate == plate then + basePrice = v.base_price + vehicle = v.vehicle + local sqlDel = MySQL.update.await('DELETE FROM rented_vehicles WHERE plate = ?', {plate}) + if not sqlDel then return cb(false) end + table.remove(vehicleShop.rentedVehicles, i) + GlobalState.vehicleShop = vehicleShop + break end + end - MySQL.update('DELETE FROM rented_vehicles WHERE plate = ?', {plate}, - function() - MySQL.insert('INSERT INTO cardealer_vehicles (vehicle, price) VALUES (?, ?)', {result.vehicle, result.base_price}) + local sqlIns = MySQL.insert.await('INSERT INTO cardealer_vehicles (vehicle, price) VALUES (?, ?)', {vehicle, basePrice}) + if not sqlIns then return cb(false) end + getCardealerVehicles() - RemoveOwnedVehicle(plate) - cb(true) - end) - end) + removeOwnedVehicle(plate) + cb(true) end) ESX.RegisterServerCallback('esx_vehicleshop:resellVehicle', function(source, cb, plate, model) local xPlayer, resellPrice = ESX.GetPlayerFromId(source) - if xPlayer.job.name == 'cardealer' or not Config.EnablePlayerManagement then + if Player(source).state.job == 'cardealer' or not Config.EnablePlayerManagement then -- calculate the resell price for i=1, #vehicles, 1 do if joaat(vehicles[i].model) == model then @@ -284,31 +327,31 @@ ESX.RegisterServerCallback('esx_vehicleshop:resellVehicle', function(source, cb, print(('[^3WARNING^7] Player ^5%s^7 Attempted To Resell Invalid Vehicle - ^5%s^7!'):format(source, model)) return cb(false) end - MySQL.single('SELECT * FROM rented_vehicles WHERE plate = ?', {plate}, + for i = 1, #vehicleShop.rentedVehicles, 1 do + if vehicleShop.rentedVehicles[i].plate == plate then + cb(false) + return + end + end + MySQL.single('SELECT * FROM owned_vehicles WHERE owner = ? AND plate = ?', {xPlayer.identifier, plate}, function(result) - if result then -- is it a rented vehicle? - return cb(false) -- it is, don't let the player sell it since he doesn't own it + if not result then + return cb(false) end - MySQL.single('SELECT * FROM owned_vehicles WHERE owner = ? AND plate = ?', {xPlayer.identifier, plate}, - function(result) - if not result then -- does the owner match? - return - end - local vehicle = json.decode(result.vehicle) + local vehicle = json.decode(result.vehicle) - if vehicle.model ~= model then - print(('[^3WARNING^7] Player ^5%s^7 Attempted To Resell Vehicle With Invalid Model - ^5%s^7!'):format(source, model)) - return cb(false) - end - if vehicle.plate ~= plate then - print(('[^3WARNING^7] Player ^5%s^7 Attempted To Resell Vehicle With Invalid Plate - ^5%s^7!'):format(source, plate)) - return cb(false) - end + if vehicle.model ~= model then + print(('[^3WARNING^7] Player ^5%s^7 Attempted To Resell Vehicle With Invalid Model - ^5%s^7!'):format(source, model)) + return cb(false) + end + if vehicle.plate ~= plate then + print(('[^3WARNING^7] Player ^5%s^7 Attempted To Resell Vehicle With Invalid Plate - ^5%s^7!'):format(source, plate)) + return cb(false) + end - xPlayer.addMoney(resellPrice, "Sold Vehicle") - RemoveOwnedVehicle(plate) - cb(true) - end) + xPlayer.addMoney(resellPrice, "Sold Vehicle") + removeOwnedVehicle(plate) + cb(true) end) end end) @@ -344,9 +387,7 @@ end) RegisterNetEvent('esx_vehicleshop:setJobVehicleState') AddEventHandler('esx_vehicleshop:setJobVehicleState', function(plate, state) - local xPlayer = ESX.GetPlayerFromId(source) - - MySQL.update('UPDATE owned_vehicles SET `stored` = ? WHERE plate = ? AND job = ?', {state, plate, xPlayer.job.name}, + MySQL.update('UPDATE owned_vehicles SET `stored` = ? WHERE plate = ? AND job = ?', {state, plate, Player(source).state.job}, function(rowsChanged) if rowsChanged == 0 then print(('[^3WARNING^7] Player ^5%s^7 Attempted To Exploit the Garage!'):format(source, plate)) @@ -354,7 +395,7 @@ AddEventHandler('esx_vehicleshop:setJobVehicleState', function(plate, state) end) end) -function PayRent() +local function payRent() local timeStart = os.clock() print('[^2INFO^7] ^5Rent Payments^7 Initiated') @@ -439,9 +480,10 @@ function PayRent() if next(unrentals) then MySQL.prepare.await('DELETE FROM rented_vehicles WHERE owner = ? AND plate = ?', unrentals) end - + + getRentedVehicles() print(('[^2INFO^7] ^5Rent Payments^7 took ^5%s^7 ms to execute'):format(ESX.Math.Round((os.time() - timeStart) / 1000000, 2))) end) end -TriggerEvent('cron:runAt', 22, 00, PayRent) +TriggerEvent('cron:runAt', 22, 00, payRent)