local QBCore = exports['qb-core']:GetCoreObject() local Config = require 'config' -- SQL Setup local function SetupDatabase() local query = [[ CREATE TABLE IF NOT EXISTS player_vending_machines ( id INT AUTO_INCREMENT PRIMARY KEY, owner VARCHAR(50) NOT NULL, model_hash BIGINT NOT NULL, location LONGTEXT NOT NULL, stock INT DEFAULT 0, earnings DECIMAL(10,2) DEFAULT 0, last_collected BIGINT DEFAULT 0, UNIQUE KEY unique_machine (owner, model_hash, location(255)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ]] MySQL.ready(function() MySQL.query(query, {}, function(result) print('^2[Vending Machines]^7 Database table initialized') end) end) end -- Global state for 24/7 machine robberies local robberyCooldown = {} -- Register usable items QBCore.Functions.CreateUseableItem(Config.RobberyItem, function(source) TriggerClientEvent('vendingmachines:client:startRobbery', source) end) if Config.PlayerOwnedMachines.RestockItem then QBCore.Functions.CreateUseableItem(Config.PlayerOwnedMachines.RestockItem, function(source) TriggerClientEvent('vendingmachines:client:startRestock', source) end) end -- Player-owned machine management local function GetPlayerMachines(citizenid) local result = MySQL.query.await('SELECT * FROM player_vending_machines WHERE owner = ?', { citizenid }) return result end local function GetMachineById(id) local result = MySQL.query.await('SELECT * FROM player_vending_machines WHERE id = ?', { id }) return result[1] end local function RegisterPlayerMachine(source, modelHash, coords) local Player = QBCore.Functions.GetPlayer(source) if not Player then return false end local location = json.encode({ x = coords.x, y = coords.y, z = coords.z }) local result = MySQL.insert.await([[ INSERT INTO player_vending_machines (owner, model_hash, location) VALUES (?, ?, ?) ]], { Player.PlayerData.citizenid, modelHash, location }) if result then TriggerClientEvent('vendingmachines:client:refreshPlayerMachines', -1) return true end return false end local function RestockMachine(machineId, amount) local machine = GetMachineById(machineId) if not machine then return false end local newStock = math.min(machine.stock + amount, Config.PlayerOwnedMachines.MaxStock) MySQL.update.await('UPDATE player_vending_machines SET stock = ? WHERE id = ?', { newStock, machineId }) return true end local function CollectEarnings(machineId) local machine = GetMachineById(machineId) if not machine or machine.earnings <= 0 then return 0 end local amount = machine.earnings MySQL.update.await('UPDATE player_vending_machines SET earnings = 0, last_collected = ? WHERE id = ?', { os.time(), machineId }) return amount end -- Handle vending machine purchases local function ProcessPurchase(source, isPlayerOwned, machineId, itemName, amount) local Player = QBCore.Functions.GetPlayer(source) if not Player then return false end if isPlayerOwned then -- Player-owned machine purchase local machine = GetMachineById(machineId) if not machine or machine.stock < amount then return false end local itemData = nil for _, item in pairs(Config.DefaultMachines.Models[machine.model_hash].items) do if item.name == itemName then itemData = item break end end if not itemData then return false end local totalPrice = itemData.price * amount if Player.Functions.RemoveMoney('cash', totalPrice) then local earnings = totalPrice local ownerCut = math.floor(earnings * Config.PlayerOwnedMachines.OwnerCut) local businessCut = earnings - ownerCut MySQL.update.await('UPDATE player_vending_machines SET stock = stock - ?, earnings = earnings + ? WHERE id = ?', { amount, businessCut, machineId }) -- Give item to player Player.Functions.AddItem(itemName, amount) return true end else -- 24/7 machine purchase local modelHash = tonumber(machineId) local machineItems = Config.DefaultMachines.Models[modelHash] if not machineItems then return false end local itemData = nil for _, item in pairs(machineItems.items) do if item.name == itemName then itemData = item break end end if not itemData then return false end local totalPrice = itemData.price * amount if Player.Functions.RemoveMoney('cash', totalPrice) then Player.Functions.AddItem(itemName, amount) return true end end end -- Robbery functions local function AlertPolice(machineCoords) if Config.Dispatch == 'ps-dispatch' then exports['ps-dispatch']:VendingMachineRobbery(machineCoords) elseif Config.Dispatch == 'qb-dispatch' then TriggerEvent('qb-dispatch:officerDistress', { dispatchCode = '10-31', firstStreet = GetStreetAndZone(machineCoords), priority = 2, origin = { x = machineCoords.x, y = machineCoords.y, z = machineCoords.z }, blip = { sprite = 52, scale = 1.5, color = 1, flashes = true, text = 'Vending Machine Robbery', time = 5 } }) end end local function CompleteRobbery(source, isPlayerOwned, machineId) local Player = QBCore.Functions.GetPlayer(source) if not Player then return false end if robberyCooldown[source] and os.time() - robberyCooldown[source] < 300 then TriggerClientEvent('QBCore:Notify', source, 'You need to wait before robbing again', 'error') return false end if Player.Functions.RemoveItem(Config.RobberyItem, 1) then local reward = math.random(Config.MinRobberyCash, Config.MaxRobberyCash) Player.Functions.AddMoney('cash', reward) if math.random(1, 100) <= Config.RobberyAlertChance then local coords = GetEntityCoords(GetPlayerPed(source)) AlertPolice(coords) end robberyCooldown[source] = os.time() if isPlayerOwned then local machine = GetMachineById(machineId) if machine then MySQL.update.await('UPDATE player_vending_machines SET stock = GREATEST(0, stock - ?) WHERE id = ?', { math.random(5, 15), machineId }) end end return true end return false end -- Server Events RegisterNetEvent('vendingmachines:server:registerMachine', function(modelHash, coords) local src = source if RegisterPlayerMachine(src, modelHash, coords) then TriggerClientEvent('QBCore:Notify', src, 'Vending machine registered successfully', 'success') else TriggerClientEvent('QBCore:Notify', src, 'Failed to register vending machine', 'error') end end) RegisterNetEvent('vendingmachines:server:processPurchase', function(isPlayerOwned, machineId, itemName, amount) local src = source if ProcessPurchase(src, isPlayerOwned, machineId, itemName, amount) then TriggerClientEvent('QBCore:Notify', src, 'Purchase successful', 'success') else TriggerClientEvent('QBCore:Notify', src, 'Purchase failed', 'error') end end) RegisterNetEvent('vendingmachines:server:restockMachine', function(machineId) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end local jobName = Player.PlayerData.job.name local allowed = false for _, job in pairs(Config.PlayerOwnedMachines.AllowedJobs) do if job == jobName then allowed = true break end end if not allowed then TriggerClientEvent('QBCore:Notify', src, 'You are not authorized to restock machines', 'error') return end if Player.Functions.RemoveItem(Config.PlayerOwnedMachines.RestockItem, 1) then if RestockMachine(machineId, Config.PlayerOwnedMachines.StockAmount) then TriggerClientEvent('QBCore:Notify', src, 'Machine restocked successfully', 'success') else Player.Functions.AddItem(Config.PlayerOwnedMachines.RestockItem, 1) TriggerClientEvent('QBCore:Notify', src, 'Failed to restock machine', 'error') end else TriggerClientEvent('QBCore:Notify', src, 'You don\'t have the restock item', 'error') end end) RegisterNetEvent('vendingmachines:server:collectEarnings', function(machineId) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end local jobName = Player.PlayerData.job.name local allowed = false for _, job in pairs(Config.PlayerOwnedMachines.AllowedJobs) do if job == jobName then allowed = true break end end if not allowed then TriggerClientEvent('QBCore:Notify', src, 'You are not authorized to collect earnings', 'error') return end local amount = CollectEarnings(machineId) if amount > 0 then Player.Functions.AddMoney('cash', amount) TriggerClientEvent('QBCore:Notify', src, string.format('Collected $%d from the machine', amount), 'success') else TriggerClientEvent('QBCore:Notify', src, 'No earnings to collect', 'error') end end) RegisterNetEvent('vendingmachines:server:completeRobbery', function(isPlayerOwned, machineId) local src = source if CompleteRobbery(src, isPlayerOwned, machineId) then TriggerClientEvent('QBCore:Notify', src, 'Robbery successful', 'success') else TriggerClientEvent('QBCore:Notify', src, 'Robbery failed', 'error') end end) -- Commands QBCore.Commands.Add('registermachine', 'Register a player-owned vending machine', {}, false, function(source) TriggerClientEvent('vendingmachines:client:registerMachine', source) end, 'admin') QBCore.Commands.Add('mymachines', 'View your owned vending machines', {}, false, function(source) local Player = QBCore.Functions.GetPlayer(source) if not Player then return end local machines = GetPlayerMachines(Player.PlayerData.citizenid) if #machines > 0 then local msg = "Your machines:\n" for _, machine in pairs(machines) do msg = msg .. string.format("ID: %d | Stock: %d | Earnings: $%.2f\n", machine.id, machine.stock, machine.earnings) end TriggerClientEvent('chat:addMessage', source, { args = { 'SYSTEM', msg } }) else TriggerClientEvent('QBCore:Notify', source, 'You don\'t own any machines', 'error') end end) -- Initialize SetupDatabase()