879 lines
34 KiB
Lua
879 lines
34 KiB
Lua
-- Variables
|
|
local QBCore = exports['qb-core']:GetCoreObject()
|
|
local PlayerData = QBCore.Functions.GetPlayerData()
|
|
local testDriveZone = nil
|
|
local vehicleMenu = {}
|
|
local Initialized = false
|
|
local testDriveVeh, inTestDrive = 0, false
|
|
local ClosestVehicle = 1
|
|
local zones = {}
|
|
local insideShop, tempShop = nil, nil
|
|
|
|
-- Handlers
|
|
AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
|
|
PlayerData = QBCore.Functions.GetPlayerData()
|
|
local citizenid = PlayerData.citizenid
|
|
TriggerServerEvent('qb-vehicleshop:server:addPlayer', citizenid)
|
|
TriggerServerEvent('qb-vehicleshop:server:checkFinance')
|
|
if not Initialized then Init() end
|
|
end)
|
|
|
|
AddEventHandler('onResourceStart', function(resource)
|
|
if resource ~= GetCurrentResourceName() then
|
|
return
|
|
end
|
|
if next(PlayerData) ~= nil and not Initialized then
|
|
PlayerData = QBCore.Functions.GetPlayerData()
|
|
local citizenid = PlayerData.citizenid
|
|
TriggerServerEvent('qb-vehicleshop:server:addPlayer', citizenid)
|
|
TriggerServerEvent('qb-vehicleshop:server:checkFinance')
|
|
Init()
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo)
|
|
PlayerData.job = JobInfo
|
|
end)
|
|
|
|
RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
|
|
local citizenid = PlayerData.citizenid
|
|
TriggerServerEvent('qb-vehicleshop:server:removePlayer', citizenid)
|
|
PlayerData = {}
|
|
end)
|
|
|
|
-- Static Headers
|
|
local vehHeaderMenu = {
|
|
{
|
|
header = Lang:t('menus.vehHeader_header'),
|
|
txt = Lang:t('menus.vehHeader_txt'),
|
|
icon = 'fa-solid fa-car',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:showVehOptions'
|
|
}
|
|
}
|
|
}
|
|
|
|
local financeMenu = {
|
|
{
|
|
header = Lang:t('menus.financed_header'),
|
|
txt = Lang:t('menus.finance_txt'),
|
|
icon = 'fa-solid fa-user-ninja',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:getVehicles'
|
|
}
|
|
}
|
|
}
|
|
|
|
local returnTestDrive = {
|
|
{
|
|
header = Lang:t('menus.returnTestDrive_header'),
|
|
icon = 'fa-solid fa-flag-checkered',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:TestDriveReturn'
|
|
}
|
|
}
|
|
}
|
|
|
|
-- Functions
|
|
local function drawTxt(text, font, x, y, scale, r, g, b, a)
|
|
SetTextFont(font)
|
|
SetTextScale(scale, scale)
|
|
SetTextColour(r, g, b, a)
|
|
SetTextOutline()
|
|
SetTextCentre(1)
|
|
SetTextEntry('STRING')
|
|
AddTextComponentString(text)
|
|
DrawText(x, y)
|
|
end
|
|
|
|
local function tablelength(T)
|
|
local count = 0
|
|
for _ in pairs(T) do
|
|
count = count + 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
local function comma_value(amount)
|
|
local formatted = amount
|
|
local k
|
|
while true do
|
|
formatted, k = string.gsub(formatted, '^(-?%d+)(%d%d%d)', '%1,%2')
|
|
if k == 0 then
|
|
break
|
|
end
|
|
end
|
|
return formatted
|
|
end
|
|
|
|
local function getVehName()
|
|
return QBCore.Shared.Vehicles[Config.Shops[insideShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle]['name']
|
|
end
|
|
|
|
local function getVehPrice()
|
|
return comma_value(QBCore.Shared.Vehicles[Config.Shops[insideShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle]['price'])
|
|
end
|
|
|
|
local function getVehBrand()
|
|
return QBCore.Shared.Vehicles[Config.Shops[insideShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle]['brand']
|
|
end
|
|
|
|
local function setClosestShowroomVehicle()
|
|
local pos = GetEntityCoords(PlayerPedId(), true)
|
|
local current = nil
|
|
local dist = nil
|
|
local closestShop = insideShop
|
|
for id in pairs(Config.Shops[closestShop]['ShowroomVehicles']) do
|
|
local dist2 = #(pos - vector3(Config.Shops[closestShop]['ShowroomVehicles'][id].coords.x, Config.Shops[closestShop]['ShowroomVehicles'][id].coords.y, Config.Shops[closestShop]['ShowroomVehicles'][id].coords.z))
|
|
if current then
|
|
if dist2 < dist then
|
|
current = id
|
|
dist = dist2
|
|
end
|
|
else
|
|
dist = dist2
|
|
current = id
|
|
end
|
|
end
|
|
if current ~= ClosestVehicle then
|
|
ClosestVehicle = current
|
|
end
|
|
end
|
|
|
|
local function createTestDriveReturn()
|
|
testDriveZone = BoxZone:Create(
|
|
Config.Shops[insideShop]['ReturnLocation'],
|
|
3.0,
|
|
5.0,
|
|
{
|
|
name = 'box_zone_testdrive_return_' .. insideShop,
|
|
})
|
|
|
|
testDriveZone:onPlayerInOut(function(isPointInside)
|
|
if isPointInside and IsPedInAnyVehicle(PlayerPedId()) then
|
|
SetVehicleForwardSpeed(GetVehiclePedIsIn(PlayerPedId(), false), 0)
|
|
exports['qb-menu']:openMenu(returnTestDrive)
|
|
else
|
|
exports['qb-menu']:closeMenu()
|
|
end
|
|
end)
|
|
end
|
|
|
|
local function startTestDriveTimer(testDriveTime, prevCoords)
|
|
local gameTimer = GetGameTimer()
|
|
CreateThread(function()
|
|
Wait(2000) -- Avoids the condition to run before entering vehicle
|
|
while inTestDrive do
|
|
if GetGameTimer() < gameTimer + tonumber(1000 * testDriveTime) then
|
|
local secondsLeft = GetGameTimer() - gameTimer
|
|
if secondsLeft >= tonumber(1000 * testDriveTime) - 20 or GetPedInVehicleSeat(NetToVeh(testDriveVeh), -1) ~= PlayerPedId() then
|
|
TriggerServerEvent('qb-vehicleshop:server:deleteVehicle', testDriveVeh)
|
|
testDriveVeh = 0
|
|
inTestDrive = false
|
|
SetEntityCoords(PlayerPedId(), prevCoords)
|
|
QBCore.Functions.Notify(Lang:t('general.testdrive_complete'))
|
|
end
|
|
drawTxt(Lang:t('general.testdrive_timer') .. math.ceil(testDriveTime - secondsLeft / 1000), 4, 0.5, 0.93, 0.50, 255, 255, 255, 180)
|
|
end
|
|
Wait(0)
|
|
end
|
|
end)
|
|
end
|
|
|
|
local function createVehZones(shopName, entity)
|
|
if not Config.UsingTarget then
|
|
for i = 1, #Config.Shops[shopName]['ShowroomVehicles'] do
|
|
zones[#zones + 1] = BoxZone:Create(
|
|
vector3(Config.Shops[shopName]['ShowroomVehicles'][i]['coords'].x,
|
|
Config.Shops[shopName]['ShowroomVehicles'][i]['coords'].y,
|
|
Config.Shops[shopName]['ShowroomVehicles'][i]['coords'].z),
|
|
Config.Shops[shopName]['Zone']['size'],
|
|
Config.Shops[shopName]['Zone']['size'],
|
|
{
|
|
name = 'box_zone_' .. shopName .. '_' .. i,
|
|
minZ = Config.Shops[shopName]['Zone']['minZ'],
|
|
maxZ = Config.Shops[shopName]['Zone']['maxZ'],
|
|
debugPoly = false,
|
|
})
|
|
end
|
|
local combo = ComboZone:Create(zones, { name = 'vehCombo', debugPoly = false })
|
|
combo:onPlayerInOut(function(isPointInside)
|
|
if isPointInside then
|
|
if PlayerData and PlayerData.job and (PlayerData.job.name == Config.Shops[insideShop]['Job'] or Config.Shops[insideShop]['Job'] == 'none') then
|
|
exports['qb-menu']:showHeader(vehHeaderMenu)
|
|
end
|
|
else
|
|
exports['qb-menu']:closeMenu()
|
|
end
|
|
end)
|
|
else
|
|
exports['qb-target']:AddTargetEntity(entity, {
|
|
options = {
|
|
{
|
|
type = 'client',
|
|
event = 'qb-vehicleshop:client:showVehOptions',
|
|
icon = 'fas fa-car',
|
|
label = Lang:t('general.vehinteraction'),
|
|
canInteract = function()
|
|
local closestShop = insideShop
|
|
return closestShop and (Config.Shops[closestShop]['Job'] == 'none' or PlayerData.job.name == Config.Shops[closestShop]['Job'])
|
|
end
|
|
},
|
|
},
|
|
distance = 3.0
|
|
})
|
|
end
|
|
end
|
|
|
|
-- Zones
|
|
function createFreeUseShop(shopShape, name)
|
|
local zone = PolyZone:Create(shopShape, {
|
|
name = name,
|
|
minZ = shopShape.minZ,
|
|
maxZ = shopShape.maxZ
|
|
})
|
|
|
|
zone:onPlayerInOut(function(isPointInside)
|
|
if isPointInside then
|
|
insideShop = name
|
|
CreateThread(function()
|
|
while insideShop do
|
|
setClosestShowroomVehicle()
|
|
vehicleMenu = {
|
|
{
|
|
isMenuHeader = true,
|
|
icon = 'fa-solid fa-circle-info',
|
|
header = getVehBrand():upper() .. ' ' .. getVehName():upper() .. ' - $' .. getVehPrice(),
|
|
},
|
|
{
|
|
header = Lang:t('menus.test_header'),
|
|
txt = Lang:t('menus.freeuse_test_txt'),
|
|
icon = 'fa-solid fa-car-on',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:TestDrive',
|
|
}
|
|
},
|
|
{
|
|
header = Lang:t('menus.freeuse_buy_header'),
|
|
txt = Lang:t('menus.freeuse_buy_txt'),
|
|
icon = 'fa-solid fa-hand-holding-dollar',
|
|
params = {
|
|
isServer = true,
|
|
event = 'qb-vehicleshop:server:buyShowroomVehicle',
|
|
args = {
|
|
buyVehicle = Config.Shops[insideShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle
|
|
}
|
|
}
|
|
},
|
|
{
|
|
header = Lang:t('menus.finance_header'),
|
|
txt = Lang:t('menus.freeuse_finance_txt'),
|
|
icon = 'fa-solid fa-coins',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:openFinance',
|
|
args = {
|
|
price = getVehPrice(),
|
|
buyVehicle = Config.Shops[insideShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle
|
|
}
|
|
}
|
|
},
|
|
{
|
|
header = Lang:t('menus.swap_header'),
|
|
txt = Lang:t('menus.swap_txt'),
|
|
icon = 'fa-solid fa-arrow-rotate-left',
|
|
params = {
|
|
event = Config.FilterByMake and 'qb-vehicleshop:client:vehMakes' or 'qb-vehicleshop:client:vehCategories',
|
|
}
|
|
},
|
|
}
|
|
Wait(1000)
|
|
end
|
|
end)
|
|
else
|
|
insideShop = nil
|
|
ClosestVehicle = 1
|
|
end
|
|
end)
|
|
end
|
|
|
|
function createManagedShop(shopShape, name)
|
|
local zone = PolyZone:Create(shopShape, {
|
|
name = name,
|
|
minZ = shopShape.minZ,
|
|
maxZ = shopShape.maxZ
|
|
})
|
|
|
|
zone:onPlayerInOut(function(isPointInside)
|
|
if isPointInside then
|
|
insideShop = name
|
|
CreateThread(function()
|
|
while insideShop and PlayerData.job and PlayerData.job.name == Config.Shops[name]['Job'] do
|
|
setClosestShowroomVehicle()
|
|
vehicleMenu = {
|
|
{
|
|
isMenuHeader = true,
|
|
icon = 'fa-solid fa-circle-info',
|
|
header = getVehBrand():upper() .. ' ' .. getVehName():upper() .. ' - $' .. getVehPrice(),
|
|
},
|
|
{
|
|
header = Lang:t('menus.test_header'),
|
|
txt = Lang:t('menus.managed_test_txt'),
|
|
icon = 'fa-solid fa-user-plus',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:openIdMenu',
|
|
args = {
|
|
vehicle = Config.Shops[insideShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle,
|
|
type = 'testDrive'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
header = Lang:t('menus.managed_sell_header'),
|
|
txt = Lang:t('menus.managed_sell_txt'),
|
|
icon = 'fa-solid fa-cash-register',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:openIdMenu',
|
|
args = {
|
|
vehicle = Config.Shops[insideShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle,
|
|
type = 'sellVehicle'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
header = Lang:t('menus.finance_header'),
|
|
txt = Lang:t('menus.managed_finance_txt'),
|
|
icon = 'fa-solid fa-coins',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:openCustomFinance',
|
|
args = {
|
|
price = getVehPrice(),
|
|
vehicle = Config.Shops[insideShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle
|
|
}
|
|
}
|
|
},
|
|
{
|
|
header = Lang:t('menus.swap_header'),
|
|
txt = Lang:t('menus.swap_txt'),
|
|
icon = 'fa-solid fa-arrow-rotate-left',
|
|
params = {
|
|
event = Config.FilterByMake and 'qb-vehicleshop:client:vehMakes' or 'qb-vehicleshop:client:vehCategories',
|
|
}
|
|
},
|
|
}
|
|
Wait(1000)
|
|
end
|
|
end)
|
|
else
|
|
insideShop = nil
|
|
ClosestVehicle = 1
|
|
end
|
|
end)
|
|
end
|
|
|
|
function Init()
|
|
Initialized = true
|
|
CreateThread(function()
|
|
for name, shop in pairs(Config.Shops) do
|
|
if shop['Type'] == 'free-use' then
|
|
createFreeUseShop(shop['Zone']['Shape'], name)
|
|
elseif shop['Type'] == 'managed' then
|
|
createManagedShop(shop['Zone']['Shape'], name)
|
|
end
|
|
end
|
|
end)
|
|
CreateThread(function()
|
|
local financeZone = BoxZone:Create(Config.FinanceZone, 2.0, 2.0, {
|
|
name = 'vehicleshop_financeZone',
|
|
offset = { 0.0, 0.0, 0.0 },
|
|
scale = { 1.0, 1.0, 1.0 },
|
|
minZ = Config.FinanceZone.z - 1,
|
|
maxZ = Config.FinanceZone.z + 1,
|
|
debugPoly = false,
|
|
})
|
|
|
|
financeZone:onPlayerInOut(function(isPointInside)
|
|
if isPointInside then
|
|
exports['qb-menu']:showHeader(financeMenu)
|
|
else
|
|
exports['qb-menu']:closeMenu()
|
|
end
|
|
end)
|
|
end)
|
|
CreateThread(function()
|
|
for k in pairs(Config.Shops) do
|
|
for i = 1, #Config.Shops[k]['ShowroomVehicles'] do
|
|
local model = GetHashKey(Config.Shops[k]['ShowroomVehicles'][i].defaultVehicle)
|
|
RequestModel(model)
|
|
while not HasModelLoaded(model) do
|
|
Wait(0)
|
|
end
|
|
local veh = CreateVehicle(model, Config.Shops[k]['ShowroomVehicles'][i].coords.x, Config.Shops[k]['ShowroomVehicles'][i].coords.y, Config.Shops[k]['ShowroomVehicles'][i].coords.z, false, false)
|
|
SetModelAsNoLongerNeeded(model)
|
|
SetVehicleOnGroundProperly(veh)
|
|
SetEntityInvincible(veh, true)
|
|
SetVehicleDirtLevel(veh, 0.0)
|
|
SetVehicleDoorsLocked(veh, 3)
|
|
SetEntityHeading(veh, Config.Shops[k]['ShowroomVehicles'][i].coords.w)
|
|
FreezeEntityPosition(veh, true)
|
|
SetVehicleNumberPlateText(veh, 'TAG MIG')
|
|
if Config.UsingTarget then createVehZones(k, veh) end
|
|
end
|
|
if not Config.UsingTarget then createVehZones(k) end
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- Events
|
|
RegisterNetEvent('qb-vehicleshop:client:homeMenu', function()
|
|
exports['qb-menu']:openMenu(vehicleMenu)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:showVehOptions', function()
|
|
exports['qb-menu']:openMenu(vehicleMenu, true, true)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:TestDrive', function()
|
|
if not inTestDrive and ClosestVehicle ~= 0 then
|
|
inTestDrive = true
|
|
local prevCoords = GetEntityCoords(PlayerPedId())
|
|
tempShop = insideShop -- temp hacky way of setting the shop because it changes after the callback has returned since you are outside the zone
|
|
QBCore.Functions.TriggerCallback('QBCore:Server:SpawnVehicle', function(netId)
|
|
local veh = NetToVeh(netId)
|
|
exports['qb-fuel']:SetFuel(veh, 100)
|
|
SetVehicleNumberPlateText(veh, 'TESTKRSL')
|
|
SetEntityHeading(veh, Config.Shops[tempShop]['TestDriveSpawn'].w)
|
|
TriggerEvent('vehiclekeys:client:SetOwner', QBCore.Functions.GetPlate(veh))
|
|
testDriveVeh = netId
|
|
QBCore.Functions.Notify(Lang:t('general.testdrive_timenoti', { testdrivetime = Config.Shops[tempShop]['TestDriveTimeLimit'] }))
|
|
end, Config.Shops[tempShop]['ShowroomVehicles'][ClosestVehicle].chosenVehicle, Config.Shops[tempShop]['TestDriveSpawn'], true)
|
|
createTestDriveReturn()
|
|
startTestDriveTimer(Config.Shops[tempShop]['TestDriveTimeLimit'] * 60, prevCoords)
|
|
else
|
|
QBCore.Functions.Notify(Lang:t('error.testdrive_alreadyin'), 'error')
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:customTestDrive', function(data)
|
|
if not inTestDrive then
|
|
inTestDrive = true
|
|
local vehicle = data
|
|
local prevCoords = GetEntityCoords(PlayerPedId())
|
|
tempShop = insideShop -- temp hacky way of setting the shop because it changes after the callback has returned since you are outside the zone
|
|
QBCore.Functions.TriggerCallback('QBCore:Server:SpawnVehicle', function(netId)
|
|
local veh = NetToVeh(netId)
|
|
exports['qb-fuel']:SetFuel(veh, 100)
|
|
SetVehicleNumberPlateText(veh, 'TESTKRSL')
|
|
SetEntityHeading(veh, Config.Shops[tempShop]['TestDriveSpawn'].w)
|
|
TriggerEvent('vehiclekeys:client:SetOwner', QBCore.Functions.GetPlate(veh))
|
|
testDriveVeh = netId
|
|
QBCore.Functions.Notify(Lang:t('general.testdrive_timenoti', { testdrivetime = Config.Shops[tempShop]['TestDriveTimeLimit'] }))
|
|
end, vehicle, Config.Shops[tempShop]['TestDriveSpawn'], true)
|
|
createTestDriveReturn()
|
|
startTestDriveTimer(Config.Shops[tempShop]['TestDriveTimeLimit'] * 60, prevCoords)
|
|
else
|
|
QBCore.Functions.Notify(Lang:t('error.testdrive_alreadyin'), 'error')
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:TestDriveReturn', function()
|
|
local ped = PlayerPedId()
|
|
local veh = GetVehiclePedIsIn(ped)
|
|
local entity = NetworkGetEntityFromNetworkId(testDriveVeh)
|
|
if veh == entity then
|
|
testDriveVeh = 0
|
|
inTestDrive = false
|
|
DeleteEntity(veh)
|
|
exports['qb-menu']:closeMenu()
|
|
testDriveZone:destroy()
|
|
else
|
|
QBCore.Functions.Notify(Lang:t('error.testdrive_return'), 'error')
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:vehCategories', function(data)
|
|
local catmenu = {}
|
|
local firstvalue = nil
|
|
local categoryMenu = {
|
|
{
|
|
header = Lang:t('menus.goback_header'),
|
|
icon = 'fa-solid fa-angle-left',
|
|
params = {
|
|
event = Config.FilterByMake and 'qb-vehicleshop:client:vehMakes' or 'qb-vehicleshop:client:homeMenu'
|
|
}
|
|
}
|
|
}
|
|
for k, v in pairs(QBCore.Shared.Vehicles) do
|
|
if type(QBCore.Shared.Vehicles[k]['shop']) == 'table' then
|
|
for _, shop in pairs(QBCore.Shared.Vehicles[k]['shop']) do
|
|
if shop == insideShop and (not Config.FilterByMake or QBCore.Shared.Vehicles[k]['brand'] == data.make) then
|
|
catmenu[v.category] = v.category
|
|
if firstvalue == nil then
|
|
firstvalue = v.category
|
|
end
|
|
end
|
|
end
|
|
elseif QBCore.Shared.Vehicles[k]['shop'] == insideShop and (not Config.FilterByMake or QBCore.Shared.Vehicles[k]['brand'] == data.make) then
|
|
catmenu[v.category] = v.category
|
|
if firstvalue == nil then
|
|
firstvalue = v.category
|
|
end
|
|
end
|
|
end
|
|
if Config.HideCategorySelectForOne and tablelength(catmenu) == 1 then
|
|
TriggerEvent('qb-vehicleshop:client:openVehCats', { catName = firstvalue, make = Config.FilterByMake and data.make, onecat = true })
|
|
return
|
|
end
|
|
for k, v in pairs(catmenu) do
|
|
categoryMenu[#categoryMenu + 1] = {
|
|
header = v,
|
|
icon = 'fa-solid fa-circle',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:openVehCats',
|
|
args = {
|
|
catName = k,
|
|
}
|
|
}
|
|
}
|
|
end
|
|
exports['qb-menu']:openMenu(categoryMenu, Config.SortAlphabetically, true)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:openVehCats', function(data)
|
|
local vehMenu = {
|
|
{
|
|
header = Lang:t('menus.goback_header'),
|
|
icon = 'fa-solid fa-angle-left',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:vehCategories',
|
|
args = {
|
|
make = data.make
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if data.onecat == true then
|
|
vehMenu[1].params = {
|
|
event = 'qb-vehicleshop:client:vehMakes'
|
|
}
|
|
end
|
|
for k, v in pairs(QBCore.Shared.Vehicles) do
|
|
if QBCore.Shared.Vehicles[k]['category'] == data.catName then
|
|
if type(QBCore.Shared.Vehicles[k]['shop']) == 'table' then
|
|
for _, shop in pairs(QBCore.Shared.Vehicles[k]['shop']) do
|
|
if shop == insideShop then
|
|
vehMenu[#vehMenu + 1] = {
|
|
header = v.name,
|
|
txt = Lang:t('menus.veh_price') .. v.price,
|
|
icon = 'fa-solid fa-car-side',
|
|
params = {
|
|
isServer = true,
|
|
event = 'qb-vehicleshop:server:swapVehicle',
|
|
args = {
|
|
toVehicle = v.model,
|
|
ClosestVehicle = ClosestVehicle,
|
|
ClosestShop = insideShop
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
elseif QBCore.Shared.Vehicles[k]['shop'] == insideShop then
|
|
vehMenu[#vehMenu + 1] = {
|
|
header = v.name,
|
|
txt = Lang:t('menus.veh_price') .. v.price,
|
|
icon = 'fa-solid fa-car-side',
|
|
params = {
|
|
isServer = true,
|
|
event = 'qb-vehicleshop:server:swapVehicle',
|
|
args = {
|
|
toVehicle = v.model,
|
|
ClosestVehicle = ClosestVehicle,
|
|
ClosestShop = insideShop
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
end
|
|
exports['qb-menu']:openMenu(vehMenu, Config.SortAlphabetically, true)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:vehMakes', function()
|
|
local makmenu = {}
|
|
local makeMenu = {
|
|
{
|
|
header = Lang:t('menus.goback_header'),
|
|
icon = 'fa-solid fa-angle-left',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:homeMenu'
|
|
}
|
|
}
|
|
}
|
|
for k, v in pairs(QBCore.Shared.Vehicles) do
|
|
if type(QBCore.Shared.Vehicles[k]['shop']) == 'table' then
|
|
for _, shop in pairs(QBCore.Shared.Vehicles[k]['shop']) do
|
|
if shop == insideShop then
|
|
makmenu[v.brand] = v.brand
|
|
end
|
|
end
|
|
elseif QBCore.Shared.Vehicles[k]['shop'] == insideShop then
|
|
makmenu[v.brand] = v.brand
|
|
end
|
|
end
|
|
for k, v in pairs(makmenu) do
|
|
makeMenu[#makeMenu + 1] = {
|
|
header = v,
|
|
icon = 'fa-solid fa-circle',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:vehCategories',
|
|
args = {
|
|
make = v
|
|
}
|
|
}
|
|
}
|
|
end
|
|
exports['qb-menu']:openMenu(makeMenu, Config.SortAlphabetically, true)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:openFinance', function(data)
|
|
local dialog = exports['qb-input']:ShowInput({
|
|
header = getVehBrand():upper() .. ' ' .. data.buyVehicle:upper() .. ' - $' .. data.price,
|
|
submitText = Lang:t('menus.submit_text'),
|
|
inputs = {
|
|
{
|
|
type = 'number',
|
|
isRequired = true,
|
|
name = 'downPayment',
|
|
text = Lang:t('menus.financesubmit_downpayment') .. Config.MinimumDown .. '%'
|
|
},
|
|
{
|
|
type = 'number',
|
|
isRequired = true,
|
|
name = 'paymentAmount',
|
|
text = Lang:t('menus.financesubmit_totalpayment') .. Config.MaximumPayments
|
|
}
|
|
}
|
|
})
|
|
if dialog then
|
|
if not dialog.downPayment or not dialog.paymentAmount then return end
|
|
TriggerServerEvent('qb-vehicleshop:server:financeVehicle', dialog.downPayment, dialog.paymentAmount, data.buyVehicle)
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:openCustomFinance', function(data)
|
|
local dialog = exports['qb-input']:ShowInput({
|
|
header = getVehBrand():upper() .. ' ' .. data.vehicle:upper() .. ' - $' .. data.price,
|
|
submitText = Lang:t('menus.submit_text'),
|
|
inputs = {
|
|
{
|
|
type = 'number',
|
|
isRequired = true,
|
|
name = 'downPayment',
|
|
text = Lang:t('menus.financesubmit_downpayment') .. Config.MinimumDown .. '%'
|
|
},
|
|
{
|
|
type = 'number',
|
|
isRequired = true,
|
|
name = 'paymentAmount',
|
|
text = Lang:t('menus.financesubmit_totalpayment') .. Config.MaximumPayments
|
|
},
|
|
{
|
|
text = Lang:t('menus.submit_ID'),
|
|
name = 'playerid',
|
|
type = 'number',
|
|
isRequired = true
|
|
}
|
|
}
|
|
})
|
|
if dialog then
|
|
if not dialog.downPayment or not dialog.paymentAmount or not dialog.playerid then return end
|
|
TriggerServerEvent('qb-vehicleshop:server:sellfinanceVehicle', dialog.downPayment, dialog.paymentAmount, data.vehicle, dialog.playerid)
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:swapVehicle', function(data)
|
|
local shopName = data.ClosestShop
|
|
if Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].chosenVehicle ~= data.toVehicle then
|
|
local closestVehicle, closestDistance = QBCore.Functions.GetClosestVehicle(vector3(Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].coords.x, Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].coords.y, Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].coords.z))
|
|
if closestVehicle == 0 then return end
|
|
if closestDistance < 5 then DeleteEntity(closestVehicle) end
|
|
while DoesEntityExist(closestVehicle) do
|
|
Wait(50)
|
|
end
|
|
Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].chosenVehicle = data.toVehicle
|
|
local model = GetHashKey(data.toVehicle)
|
|
RequestModel(model)
|
|
while not HasModelLoaded(model) do
|
|
Wait(50)
|
|
end
|
|
local veh = CreateVehicle(model, Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].coords.x, Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].coords.y, Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].coords.z, false, false)
|
|
while not DoesEntityExist(veh) do
|
|
Wait(50)
|
|
end
|
|
SetModelAsNoLongerNeeded(model)
|
|
SetVehicleOnGroundProperly(veh)
|
|
SetEntityInvincible(veh, true)
|
|
SetEntityHeading(veh, Config.Shops[shopName]['ShowroomVehicles'][data.ClosestVehicle].coords.w)
|
|
SetVehicleDoorsLocked(veh, 3)
|
|
FreezeEntityPosition(veh, true)
|
|
SetVehicleNumberPlateText(veh, 'TAG MIG')
|
|
if Config.UsingTarget then createVehZones(shopName, veh) end
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:buyShowroomVehicle', function(vehicle, plate)
|
|
tempShop = insideShop -- temp hacky way of setting the shop because it changes after the callback has returned since you are outside the zone
|
|
QBCore.Functions.TriggerCallback('QBCore:Server:SpawnVehicle', function(netId)
|
|
local veh = NetToVeh(netId)
|
|
exports['qb-fuel']:SetFuel(veh, 100)
|
|
SetVehicleNumberPlateText(veh, plate)
|
|
SetEntityHeading(veh, Config.Shops[tempShop]['VehicleSpawn'].w)
|
|
TriggerEvent('vehiclekeys:client:SetOwner', QBCore.Functions.GetPlate(veh))
|
|
TriggerServerEvent('qb-mechanicjob:server:SaveVehicleProps', QBCore.Functions.GetVehicleProperties(veh))
|
|
end, vehicle, Config.Shops[tempShop]['VehicleSpawn'], true)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:getVehicles', function()
|
|
QBCore.Functions.TriggerCallback('qb-vehicleshop:server:getVehicles', function(vehicles)
|
|
local ownedVehicles = {}
|
|
for _, v in pairs(vehicles) do
|
|
if v.balance ~= 0 then
|
|
local name = QBCore.Shared.Vehicles[v.vehicle]['name']
|
|
local plate = v.plate:upper()
|
|
ownedVehicles[#ownedVehicles + 1] = {
|
|
header = name,
|
|
txt = Lang:t('menus.veh_platetxt') .. plate,
|
|
icon = 'fa-solid fa-car-side',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:getVehicleFinance',
|
|
args = {
|
|
vehiclePlate = plate,
|
|
balance = v.balance,
|
|
paymentsLeft = v.paymentsleft,
|
|
paymentAmount = v.paymentamount
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
if #ownedVehicles > 0 then
|
|
exports['qb-menu']:openMenu(ownedVehicles)
|
|
else
|
|
QBCore.Functions.Notify(Lang:t('error.nofinanced'), 'error', 7500)
|
|
end
|
|
end)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:getVehicleFinance', function(data)
|
|
local vehFinance = {
|
|
{
|
|
header = Lang:t('menus.goback_header'),
|
|
params = {
|
|
event = 'qb-vehicleshop:client:getVehicles'
|
|
}
|
|
},
|
|
{
|
|
isMenuHeader = true,
|
|
icon = 'fa-solid fa-sack-dollar',
|
|
header = Lang:t('menus.veh_finance_balance'),
|
|
txt = Lang:t('menus.veh_finance_currency') .. comma_value(data.balance)
|
|
},
|
|
{
|
|
isMenuHeader = true,
|
|
icon = 'fa-solid fa-hashtag',
|
|
header = Lang:t('menus.veh_finance_total'),
|
|
txt = data.paymentsLeft
|
|
},
|
|
{
|
|
isMenuHeader = true,
|
|
icon = 'fa-solid fa-sack-dollar',
|
|
header = Lang:t('menus.veh_finance_reccuring'),
|
|
txt = Lang:t('menus.veh_finance_currency') .. comma_value(data.paymentAmount)
|
|
},
|
|
{
|
|
header = Lang:t('menus.veh_finance_pay'),
|
|
icon = 'fa-solid fa-hand-holding-dollar',
|
|
params = {
|
|
event = 'qb-vehicleshop:client:financePayment',
|
|
args = {
|
|
vehData = data,
|
|
paymentsLeft = data.paymentsleft,
|
|
paymentAmount = data.paymentamount
|
|
}
|
|
}
|
|
},
|
|
{
|
|
header = Lang:t('menus.veh_finance_payoff'),
|
|
icon = 'fa-solid fa-hand-holding-dollar',
|
|
params = {
|
|
isServer = true,
|
|
event = 'qb-vehicleshop:server:financePaymentFull',
|
|
args = {
|
|
vehBalance = data.balance,
|
|
vehPlate = data.vehiclePlate
|
|
}
|
|
}
|
|
},
|
|
}
|
|
exports['qb-menu']:openMenu(vehFinance)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:financePayment', function(data)
|
|
local dialog = exports['qb-input']:ShowInput({
|
|
header = Lang:t('menus.veh_finance'),
|
|
submitText = Lang:t('menus.veh_finance_pay'),
|
|
inputs = {
|
|
{
|
|
type = 'number',
|
|
isRequired = true,
|
|
name = 'paymentAmount',
|
|
text = Lang:t('menus.veh_finance_payment')
|
|
}
|
|
}
|
|
})
|
|
if dialog then
|
|
if not dialog.paymentAmount then return end
|
|
TriggerServerEvent('qb-vehicleshop:server:financePayment', dialog.paymentAmount, data.vehData)
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-vehicleshop:client:openIdMenu', function(data)
|
|
local dialog = exports['qb-input']:ShowInput({
|
|
header = QBCore.Shared.Vehicles[data.vehicle]['name'],
|
|
submitText = Lang:t('menus.submit_text'),
|
|
inputs = {
|
|
{
|
|
text = Lang:t('menus.submit_ID'),
|
|
name = 'playerid',
|
|
type = 'number',
|
|
isRequired = true
|
|
}
|
|
}
|
|
})
|
|
if dialog then
|
|
if not dialog.playerid then return end
|
|
if data.type == 'testDrive' then
|
|
TriggerServerEvent('qb-vehicleshop:server:customTestDrive', data.vehicle, dialog.playerid)
|
|
elseif data.type == 'sellVehicle' then
|
|
TriggerServerEvent('qb-vehicleshop:server:sellShowroomVehicle', data.vehicle, dialog.playerid)
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Threads
|
|
CreateThread(function()
|
|
for k, v in pairs(Config.Shops) do
|
|
if v.showBlip then
|
|
local Dealer = AddBlipForCoord(Config.Shops[k]['Location'])
|
|
SetBlipSprite(Dealer, Config.Shops[k]['blipSprite'])
|
|
SetBlipDisplay(Dealer, 4)
|
|
SetBlipScale(Dealer, 0.70)
|
|
SetBlipAsShortRange(Dealer, true)
|
|
SetBlipColour(Dealer, Config.Shops[k]['blipColor'])
|
|
BeginTextCommandSetBlipName('STRING')
|
|
AddTextComponentSubstringPlayerName(Config.Shops[k]['ShopLabel'])
|
|
EndTextCommandSetBlipName(Dealer)
|
|
end
|
|
end
|
|
end)
|