Skip to content

Commit

Permalink
refactor(server): split util and finance to separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
solareon committed Sep 29, 2024
1 parent c87be2c commit 0f1f6e8
Show file tree
Hide file tree
Showing 4 changed files with 493 additions and 479 deletions.
3 changes: 2 additions & 1 deletion fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ client_scripts {
server_scripts {
'@oxmysql/lib/MySQL.lua',
'server/main.lua',
'server/utils.lua',
'server/finance.lua'
}

files {
Expand All @@ -30,6 +32,5 @@ files {
'locales/*.json'
}

provide 'qb-vehicleshop'
lua54 'yes'
use_experimental_fxv2_oal 'yes'
355 changes: 355 additions & 0 deletions server/finance.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
local config = require 'config.server'
local sharedConfig = require 'config.shared'

if not sharedConfig.enableFinance then return end

local financeStorage = require 'server.storage'
local financeTimer = {}

function SetHasFinanced(src, bool)
financeTimer[src].hasFinanced = bool
end

---@param src number
---@param citizenid string
local function addPlayerToFinanceTimer(src, citizenid)
citizenid = citizenid or exports.qbx_core:GetPlayer(src).PlayerData.citizenid

local hasFinanced = financeStorage.hasFinancedVehicles(citizenid)
if hasFinanced then
exports.qbx_core:Notify(src, locale('general.paymentduein', config.finance.paymentWarning))
end

financeTimer[src] = {
citizenid = citizenid,
time = os.time(),
hasFinanced = hasFinanced
}
end

CreateThread(function()
local players = exports.qbx_core:GetPlayersData()
if players then
for i = 1, #players do
local player = players[i]
addPlayerToFinanceTimer(player.source, player.citizenid)
end
end
end)

---@param src number
local function updatePlayerFinanceTime(src)
local playerData = financeTimer[src]
if not playerData then return end

local vehicles = financeStorage.fetchFinancedVehicleEntitiesByCitizenId(playerData.citizenid)

local playTime = math.floor((os.time() - playerData.time) / 60)
for i = 1, #vehicles do
local v = vehicles[i]
local newTime = lib.math.clamp(v.financetime - playTime, 0, math.maxinteger)

if v.balance >= 1 then
financeStorage.updateVehicleEntityFinanceTime(newTime, v.vehicleId)
end
end

financeTimer[src] = nil
end

---@param src number
local function checkFinancedVehicles(src)
local financeData = financeTimer[src]
if not financeData.hasFinanced then return end

local citizenid = financeData.citizenid
local time = math.floor((os.time() - financeData.time) / 60)
local vehicles = financeStorage.fetchFinancedVehicleEntitiesByCitizenId(citizenid)

if not vehicles then
financeTimer[src].hasFinanced = false
return
end
local paymentReminder = false
for i = 1, #vehicles do
local v = vehicles[i]
local timeLeft = v.financetime - time
if timeLeft <= 0 then
if config.deleteUnpaidFinancedVehicle then
exports.qbx_vehicles:DeletePlayerVehicles('vehicleId', v.id)
else
exports.qbx_vehicles:SetPlayerVehicleOwner(v.id, nil)
end
exports.qbx_core:Notify(src, locale('error.repossessed', v.plate), 'error')
elseif timeLeft <= config.finance.paymentWarning then
paymentReminder = true
end
end

if paymentReminder then
exports.qbx_core:Notify(src, locale('general.paymentduein', config.finance.paymentWarning))
end
end

AddEventHandler('playerDropped', function()
local src = source
updatePlayerFinanceTime(src)
end)

AddEventHandler('QBCore:Server:OnPlayerUnload', function(src)
updatePlayerFinanceTime(src)
end)

RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function()
local src = source
local playerData = exports.qbx_core:GetPlayer(src).PlayerData
addPlayerToFinanceTimer(src, playerData.citizenid)
end)

lib.cron.new(config.finance.cronSchedule, function()
for src in pairs(financeTimer) do
checkFinancedVehicles(src)
end
end)

---@param vehiclePrice number
---@param downPayment number
---@param paymentamount number
---@return integer balance owed on the vehicle
---@return integer numPayments to pay off the balance
local function calculateFinance(vehiclePrice, downPayment, paymentamount)
local balance = vehiclePrice - downPayment
local vehPaymentAmount = balance / paymentamount

return lib.math.round(balance), lib.math.round(vehPaymentAmount)
end

---@param paymentAmount number paid
---@param vehData VehicleFinancingEntity
---@return integer newBalance
---@return integer newPayment
---@return integer numPaymentsLeft
local function calculateNewFinance(paymentAmount, vehData)
local newBalance = tonumber(vehData.balance - paymentAmount) --[[@as number]]
local minusPayment = vehData.paymentsleft - 1
local newPaymentsLeft = newBalance / minusPayment
local newPayment = newBalance / newPaymentsLeft

return lib.math.round(newBalance), lib.math.round(newPayment), newPaymentsLeft
end


---@param paymentAmount number
---@param vehId number
RegisterNetEvent('qbx_vehicleshop:server:financePayment', function(paymentAmount, vehId)
local src = source
local vehData = financeStorage.fetchFinancedVehicleEntityById(vehId)

paymentAmount = tonumber(paymentAmount) --[[@as number]]

local minPayment = tonumber(vehData.paymentamount) --[[@as number]]
local timer = (config.finance.paymentInterval * 60) + (math.floor((os.time() - financeTimer[src].time) / 60))
local newBalance, newPaymentsLeft, newPayment = calculateNewFinance(paymentAmount, vehData)

if newBalance <= 0 then
exports.qbx_core:Notify(src, locale('error.overpaid'), 'error')
return
end

if paymentAmount < minPayment then
exports.qbx_core:Notify(src, locale('error.minimumallowed')..lib.math.groupdigits(minPayment), 'error')
return
end

if not RemoveMoney(src, paymentAmount, 'vehicle-finance-payment') then return end

financeStorage.updateVehicleFinance({
balance = newBalance,
payment = newPayment,
paymentsLeft = newPaymentsLeft,
timer = timer
}, vehId)
end)


---@param vehId number
RegisterNetEvent('qbx_vehicleshop:server:financePaymentFull', function(vehId)
local src = source
local vehData = financeStorage.fetchFinancedVehicleEntityById(vehId)

if not RemoveMoney(src, vehData.balance, 'vehicle-finance-payment-full') then return end

financeStorage.updateVehicleFinance({
balance = 0,
payment = 0,
paymentsLeft = 0,
timer = 0,
}, vehId)
end)

---@param downPayment number
---@param paymentAmount number
---@param vehicle string
---@param playerId string|number
RegisterNetEvent('qbx_vehicleshop:server:sellfinanceVehicle', function(downPayment, paymentAmount, vehicle, playerId)
local src = source
local target = exports.qbx_core:GetPlayer(tonumber(playerId))

if not target then
return exports.qbx_core:Notify(src, locale('error.Invalid_ID'), 'error')
end

if #(GetEntityCoords(GetPlayerPed(src)) - GetEntityCoords(GetPlayerPed(target.PlayerData.source))) >= 3 then
return exports.qbx_core:Notify(src, locale('error.playertoofar'), 'error')
end

local shopId = GetShopZone(target.PlayerData.source)
local shop = sharedConfig.shops[shopId]
if not shop then return end

if not CheckVehicleList(vehicle, shopId) then
return exports.qbx_core:Notify(src, locale('error.notallowed'), 'error')
end

downPayment = tonumber(downPayment) --[[@as number]]
paymentAmount = tonumber(paymentAmount) --[[@as number]]

local vehiclePrice = coreVehicles[vehicle].price
local minDown = tonumber(lib.math.round((sharedConfig.finance.minimumDown / 100) * vehiclePrice)) --[[@as number]]

if downPayment > vehiclePrice then
return exports.qbx_core:Notify(src, locale('error.notworth'), 'error')
end

if downPayment < minDown then
return exports.qbx_core:Notify(src, locale('error.downtoosmall'), 'error')
end

if paymentAmount > sharedConfig.finance.maximumPayments then
return exports.qbx_core:Notify(src, locale('error.exceededmax'), 'error')
end

local cid = target.PlayerData.citizenid
local timer = (config.finance.paymentInterval * 60) + (math.floor((os.time() - financeTimer[src].time) / 60))
local balance, vehPaymentAmount = calculateFinance(vehiclePrice, downPayment, paymentAmount)

if not SellShowroomVehicleTransact(src, target, vehiclePrice, downPayment) then return end

local vehicleId = financeStorage.insertVehicleEntityWithFinance({
insertVehicleEntityRequest = {
citizenId = cid,
model = vehicle,
},

vehicleFinance = {
balance = balance,
payment = vehPaymentAmount,
paymentsLeft = paymentAmount,
timer = timer,
}
})

SpawnVehicle(src, {
coords = shop.vehicleSpawn,
vehicleId = vehicleId
})
financeTimer[target.PlayerData.source].hasFinanced = true
end)

---@param downPayment number
---@param paymentAmount number
---@param vehicle string
RegisterNetEvent('qbx_vehicleshop:server:financeVehicle', function(downPayment, paymentAmount, vehicle)
local src = source

local shopId = GetShopZone(src)

local shop = sharedConfig.shops[shopId]
if not shop then return end

if not CheckVehicleList(vehicle, shopId) then
return exports.qbx_core:Notify(src, locale('error.notallowed'), 'error')
end

local player = exports.qbx_core:GetPlayer(src)
local vehiclePrice = coreVehicles[vehicle].price
local minDown = tonumber(lib.math.round((sharedConfig.finance.minimumDown / 100) * vehiclePrice)) --[[@as number]]

downPayment = tonumber(downPayment) --[[@as number]]
paymentAmount = tonumber(paymentAmount) --[[@as number]]

if downPayment > vehiclePrice then
return exports.qbx_core:Notify(src, locale('error.notworth'), 'error')
end

if downPayment < minDown then
return exports.qbx_core:Notify(src, locale('error.downtoosmall'), 'error')
end

if paymentAmount > sharedConfig.finance.maximumPayments then
return exports.qbx_core:Notify(src, locale('error.exceededmax'), 'error')
end

if not RemoveMoney(src, downPayment, 'vehicle-financed-in-showroom') then
return exports.qbx_core:Notify(src, locale('error.notenoughmoney'), 'error')
end

local balance, vehPaymentAmount = calculateFinance(vehiclePrice, downPayment, paymentAmount)
local cid = player.PlayerData.citizenid
local timer = (config.finance.paymentInterval * 60) + (math.floor((os.time() - financeTimer[src].time) / 60))

local vehicleId = financeStorage.insertVehicleEntityWithFinance({
insertVehicleEntityRequest = {
citizenId = cid,
model = vehicle,
},

vehicleFinance = {
balance = balance,
payment = vehPaymentAmount,
paymentsLeft = paymentAmount,
timer = timer,
}
})

exports.qbx_core:Notify(src, locale('success.purchased'), 'success')

SpawnVehicle(src, {
coords = shop.vehicleSpawn,
vehicleId = vehicleId
})

financeTimer[src].hasFinanced = true
end)

---@param source number
lib.callback.register('qbx_vehicleshop:server:GetFinancedVehicles', function(source)
local src = source
local player = exports.qbx_core:GetPlayer(src)
if not player then return end

local financeVehicles = financeStorage.fetchFinancedVehicleEntitiesByCitizenId(player.PlayerData.citizenid)
local vehicles = {}

for i = 1, #financeVehicles do
local v = financeVehicles[i]
local vehicle = exports.qbx_vehicles:GetPlayerVehicle(v.vehicleId)

if vehicle then
vehicle.balance = v.balance
vehicle.paymentamount = v.paymentamount
vehicle.paymentsleft = v.paymentsleft
vehicle.financetime = v.financetime
end
vehicles[#vehicles+1] = vehicle
end

return vehicles[1] and vehicles
end)

---@param vehicleId integer
---@return boolean
local function isFinanced(vehicleId)
return financeStorage.fetchIsFinanced(vehicleId)
end
exports('IsFinanced', isFinanced)
Loading

0 comments on commit 0f1f6e8

Please sign in to comment.