From 8110c003828abcf7bb3cf3aeafae4f162ba1ce3d Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Tue, 29 Jul 2025 10:53:08 +0200 Subject: [PATCH] ed --- .../[inventory]/nordi_vending/client.lua | 645 ++++++++---------- .../[inventory]/nordi_vending/server.lua | 434 ++++++------ 2 files changed, 497 insertions(+), 582 deletions(-) diff --git a/resources/[inventory]/nordi_vending/client.lua b/resources/[inventory]/nordi_vending/client.lua index 7a2e337e8..a6c7167a3 100644 --- a/resources/[inventory]/nordi_vending/client.lua +++ b/resources/[inventory]/nordi_vending/client.lua @@ -1,108 +1,138 @@ local QBCore = exports['qb-core']:GetCoreObject() -local nearbyMachines = {} -local currentMachine = nil -local showingMenu = false --- Kontinuierlicher Check für Verkaufsautomaten -CreateThread(function() - while true do - local playerPed = PlayerPedId() - local playerCoords = GetEntityCoords(playerPed) - local sleep = 1000 - - -- Reset current machine - currentMachine = nil - - -- Check for nearby vending machines - local objects = GetGamePool('CObject') - for _, obj in ipairs(objects) do - local model = GetEntityModel(obj) - for _, propName in ipairs(Config.VendingProps) do - if model == GetHashKey(propName) then - local objCoords = GetEntityCoords(obj) - local dist = #(playerCoords - objCoords) - - if dist < 2.0 then - currentMachine = obj - sleep = 0 - - -- Show help text - if not showingMenu then - DrawText3D(objCoords.x, objCoords.y, objCoords.z + 1.5, "[E] Verkaufsautomat") - - if IsControlJustPressed(0, 38) then -- E key - handleMachineInteraction(obj) - end - end - break - end +-- Function to initialize targets +function InitializeTargets() + -- Remove existing targets first to avoid duplicates + exports['qb-target']:RemoveTargetModel(Config.VendingProps) + Wait(100) + + -- Add targets + exports['qb-target']:AddTargetModel(Config.VendingProps, { + options = { + { + type = "client", + event = "vending:client:buyMachine", + icon = "fas fa-dollar-sign", + label = "Automaten kaufen ($" .. Config.VendingMachinePrice .. ")", + canInteract = function(entity) + return not isRegisteredMachine(entity) end - end - if currentMachine then break end - end - - Wait(sleep) + }, + { + type = "client", + event = "vending:client:openBuyMenu", + icon = "fas fa-shopping-cart", + label = "Kaufen", + canInteract = function(entity) + return isRegisteredMachine(entity) + end + }, + { + type = "client", + event = "vending:client:openOwnerMenu", + icon = "fas fa-cog", + label = "Verwalten", + canInteract = function(entity) + return canManageMachine(entity) + end + }, + { + type = "client", + event = "vending:client:startRobbery", + icon = "fas fa-mask", + label = "Aufbrechen", + canInteract = function(entity) + return isRegisteredMachine(entity) and not canManageMachine(entity) + end + } + }, + distance = 2.0 + }) + + print("^2[VENDING]^7 Added targets to " .. #Config.VendingProps .. " vending machine props") +end + +-- Add targets to all vending machine props with multiple attempts (Option 1) +CreateThread(function() + -- First attempt + Wait(2000) + InitializeTargets() + + -- Second attempt after a delay + Wait(5000) + InitializeTargets() + + -- Third attempt after server is fully loaded + Wait(10000) + InitializeTargets() +end) + +-- Event-based initialization (Option 2) +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + Wait(1000) + InitializeTargets() +end) + +RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() + -- Nothing to do here, but good to have for completeness +end) + +-- Listen for resource start/stop events +AddEventHandler('onResourceStart', function(resourceName) + if resourceName == 'qb-target' or resourceName == GetCurrentResourceName() then + Wait(1000) + InitializeTargets() end end) --- 3D Text function -function DrawText3D(x, y, z, text) - local onScreen, _x, _y = World3dToScreen2d(x, y, z) - local px, py, pz = table.unpack(GetGameplayCamCoords()) - - if onScreen then - SetTextScale(0.35, 0.35) - SetTextFont(4) - SetTextProportional(1) - SetTextColour(255, 255, 255, 215) - SetTextEntry("STRING") - SetTextCentre(1) - AddTextComponentString(text) - DrawText(_x, _y) - local factor = (string.len(text)) / 370 - DrawRect(_x, _y + 0.0125, 0.015 + factor, 0.03, 41, 11, 41, 68) - end -end +-- Command to manually refresh targets +RegisterCommand('refreshvendingtargets', function() + InitializeTargets() + QBCore.Functions.Notify('Vending machine targets refreshed', 'success') +end, false) --- Get precise coordinates for entity -function getPreciseCoords(entity) +-- Check if machine is registered +function isRegisteredMachine(entity) local coords = GetEntityCoords(entity) - local heading = GetEntityHeading(entity) - local model = GetEntityModel(entity) + local isRegistered = false - return { - x = coords.x, - y = coords.y, - z = coords.z, - h = heading, - model = model - } -end - --- Handle machine interaction -function handleMachineInteraction(entity) - showingMenu = true - local preciseCoords = getPreciseCoords(entity) - - -- Check if machine is registered QBCore.Functions.TriggerCallback('vending:server:machineExists', function(exists) - if exists then - -- Check if player can manage - QBCore.Functions.TriggerCallback('vending:server:canManage', function(canManage) - if canManage then - showOwnerMenu(entity, preciseCoords) - else - showBuyMenu(entity, preciseCoords) - end - end, preciseCoords) - else - showPurchaseMenu(entity, preciseCoords) - end - end, preciseCoords) + isRegistered = exists + end, coords) + + -- Wait for callback (not ideal but works for canInteract) + local timeout = 0 + while isRegistered == false and timeout < 100 do + Wait(10) + timeout = timeout + 1 + end + + return isRegistered end --- Show purchase menu (buy the machine) -function showPurchaseMenu(entity, preciseCoords) +-- Check if player can manage machine +function canManageMachine(entity) + local coords = GetEntityCoords(entity) + local canManage = false + + QBCore.Functions.TriggerCallback('vending:server:canManage', function(result) + canManage = result + end, coords) + + -- Wait for callback + local timeout = 0 + while canManage == false and timeout < 100 do + Wait(10) + timeout = timeout + 1 + end + + return canManage +end + +-- Buy vending machine +RegisterNetEvent('vending:client:buyMachine', function(data) + local entity = data.entity + local coords = GetEntityCoords(entity) local model = GetEntityModel(entity) local prop = nil @@ -114,67 +144,39 @@ function showPurchaseMenu(entity, preciseCoords) end end - if not prop then - showingMenu = false - return - end + if not prop then return end lib.registerContext({ - id = 'vending_purchase', - title = 'Verkaufsautomat', + id = 'vending_buy_confirm', + title = 'Verkaufsautomat kaufen', options = { { - title = 'Automaten kaufen', - description = 'Kaufe diesen Automaten für $' .. Config.VendingMachinePrice, - icon = 'fas fa-dollar-sign', + title = 'Bestätigen', + description = 'Automaten für $' .. Config.VendingMachinePrice .. ' kaufen', + icon = 'fas fa-check', onSelect = function() - lib.registerContext({ - id = 'vending_purchase_confirm', - title = 'Kauf bestätigen', - menu = 'vending_purchase', - options = { - { - title = 'Bestätigen', - description = 'Automaten für $' .. Config.VendingMachinePrice .. ' kaufen', - icon = 'fas fa-check', - onSelect = function() - TriggerServerEvent('vending:server:registerMachine', preciseCoords, prop) - showingMenu = false - end - }, - { - title = 'Abbrechen', - description = 'Kauf abbrechen', - icon = 'fas fa-times', - onSelect = function() - showingMenu = false - end - } - } - }) - lib.showContext('vending_purchase_confirm') + TriggerServerEvent('vending:server:registerMachine', coords, prop) end }, { - title = 'Schließen', - description = 'Menü schließen', - icon = 'fas fa-times', - onSelect = function() - showingMenu = false - end + title = 'Abbrechen', + description = 'Kauf abbrechen', + icon = 'fas fa-times' } } }) - lib.showContext('vending_purchase') -end + lib.showContext('vending_buy_confirm') +end) --- Show buy menu (buy items from machine) -function showBuyMenu(entity, preciseCoords) +-- Open buy menu with quantity selection +RegisterNetEvent('vending:client:openBuyMenu', function(data) + local entity = data.entity + local coords = GetEntityCoords(entity) + QBCore.Functions.TriggerCallback('vending:server:getStashItems', function(items) if #items == 0 then QBCore.Functions.Notify('Dieser Automat ist leer!', 'error') - showingMenu = false return end @@ -189,58 +191,14 @@ function showBuyMenu(entity, preciseCoords) description = 'Preis: $' .. item.price .. ' | Verfügbar: ' .. item.amount, icon = 'fas fa-shopping-cart', onSelect = function() - openQuantityDialog(preciseCoords, item.name, item.price, item.amount, itemLabel) + openQuantityDialog(coords, item.name, item.price, item.amount, itemLabel) end }) end end - -- Add robbery option - table.insert(options, { - title = 'Aufbrechen', - description = 'Versuche den Automaten aufzubrechen', - icon = 'fas fa-mask', - onSelect = function() - lib.registerContext({ - id = 'vending_robbery_confirm', - title = 'Verkaufsautomat aufbrechen', - menu = 'vending_buy_menu', - options = { - { - title = 'Aufbrechen', - description = 'Versuche den Automaten aufzubrechen (benötigt ' .. Config.RobberyItem .. ')', - icon = 'fas fa-mask', - onSelect = function() - TriggerServerEvent('vending:server:startRobbery', preciseCoords) - showingMenu = false - end - }, - { - title = 'Abbrechen', - description = 'Aufbruch abbrechen', - icon = 'fas fa-times', - onSelect = function() - showingMenu = false - end - } - } - }) - lib.showContext('vending_robbery_confirm') - end - }) - - table.insert(options, { - title = 'Schließen', - description = 'Menü schließen', - icon = 'fas fa-times', - onSelect = function() - showingMenu = false - end - }) - - if #options == 1 then -- Only close option + if #options == 0 then QBCore.Functions.Notify('Keine Artikel verfügbar!', 'error') - showingMenu = false return end @@ -251,15 +209,41 @@ function showBuyMenu(entity, preciseCoords) }) lib.showContext('vending_buy_menu') - end, preciseCoords) + end, coords) +end) + +-- Open quantity dialog for buying items +function openQuantityDialog(coords, itemName, price, maxAmount, itemLabel) + local input = lib.inputDialog('Menge auswählen', { + { + type = 'number', + label = itemLabel .. ' - $' .. price .. ' pro Stück', + description = 'Wie viele möchtest du kaufen? (Max: ' .. maxAmount .. ')', + required = true, + min = 1, + max = maxAmount, + default = 1 + } + }) + + if input and input[1] then + local amount = tonumber(input[1]) + if amount > 0 and amount <= maxAmount then + TriggerServerEvent('vending:server:buyItem', coords, itemName, amount) + else + QBCore.Functions.Notify('Ungültige Menge!', 'error') + end + end end --- Show owner menu -function showOwnerMenu(entity, preciseCoords) +-- Open owner menu +RegisterNetEvent('vending:client:openOwnerMenu', function(data) + local entity = data.entity + local coords = GetEntityCoords(entity) + QBCore.Functions.TriggerCallback('vending:server:getMachineByCoords', function(machine) if not machine then QBCore.Functions.Notify('Automat nicht gefunden!', 'error') - showingMenu = false return end @@ -269,8 +253,7 @@ function showOwnerMenu(entity, preciseCoords) description = 'Items hinzufügen/entfernen', icon = 'fas fa-box', onSelect = function() - TriggerServerEvent('vending:server:openStash', preciseCoords) - showingMenu = false + TriggerServerEvent('vending:server:openStash', coords) end }, { @@ -278,7 +261,7 @@ function showOwnerMenu(entity, preciseCoords) description = 'Verkaufspreise für Items setzen', icon = 'fas fa-tags', onSelect = function() - openPriceMenu(preciseCoords) + openPriceMenu(coords) end }, { @@ -286,7 +269,7 @@ function showOwnerMenu(entity, preciseCoords) description = 'Verfügbar: $' .. machine.money, icon = 'fas fa-money-bill', onSelect = function() - openWithdrawMenu(preciseCoords, machine.money) + openWithdrawMenu(coords, machine.money) end }, { @@ -306,30 +289,21 @@ function showOwnerMenu(entity, preciseCoords) description = 'Verwalter hinzufügen/entfernen', icon = 'fas fa-users-cog', onSelect = function() - openManagersMenu(preciseCoords) + openManagersMenu(coords) end }) -- Add sell option only for owner table.insert(options, { title = 'Automaten verkaufen', - description = 'Verkaufe den Automaten für $' .. math.floor(Config.VendingMachinePrice * Config.SellBackPercentage / 100), + description = 'Verkaufe den Automaten für ' .. math.floor(Config.VendingMachinePrice * Config.SellBackPercentage / 100) .. '$', icon = 'fas fa-dollar-sign', onSelect = function() - sellVendingMachine(preciseCoords, machine.id) + sellVendingMachine(coords, machine.id) end }) end - table.insert(options, { - title = 'Schließen', - description = 'Menü schließen', - icon = 'fas fa-times', - onSelect = function() - showingMenu = false - end - }) - lib.registerContext({ id = 'vending_owner_menu', title = 'Verkaufsautomat Verwaltung', @@ -337,59 +311,30 @@ function showOwnerMenu(entity, preciseCoords) }) lib.showContext('vending_owner_menu') - end, preciseCoords) -end - --- Open quantity dialog for buying items -function openQuantityDialog(preciseCoords, itemName, price, maxAmount, itemLabel) - local input = lib.inputDialog('Menge auswählen', { - { - type = 'number', - label = itemLabel .. ' - $' .. price .. ' pro Stück', - description = 'Wie viele möchtest du kaufen? (Max: ' .. maxAmount .. ')', - required = true, - min = 1, - max = maxAmount, - default = 1 - } - }) - - if input and input[1] then - local amount = tonumber(input[1]) - if amount > 0 and amount <= maxAmount then - TriggerServerEvent('vending:server:buyItem', preciseCoords, itemName, amount) - else - QBCore.Functions.Notify('Ungültige Menge!', 'error') - end - end - - showingMenu = false -end + end, coords) +end) -- Funktion zum Verkaufen des Automaten -function sellVendingMachine(preciseCoords, machineId) +function sellVendingMachine(coords, machineId) local input = lib.inputDialog('Automaten verkaufen', { { type = 'checkbox', label = 'Bestätigen', - description = 'Du erhältst $' .. math.floor(Config.VendingMachinePrice * Config.SellBackPercentage / 100) .. ' zurück. Diese Aktion kann nicht rückgängig gemacht werden!', + description = 'Du erhältst ' .. math.floor(Config.VendingMachinePrice * Config.SellBackPercentage / 100) .. '$ zurück. Diese Aktion kann nicht rückgängig gemacht werden!', required = true } }) if input and input[1] then - TriggerServerEvent('vending:server:sellMachine', preciseCoords, machineId) + TriggerServerEvent('vending:server:sellMachine', coords, machineId) end - - showingMenu = false end -- Open price menu -function openPriceMenu(preciseCoords) +function openPriceMenu(coords) QBCore.Functions.TriggerCallback('vending:server:getStashItems', function(items) if #items == 0 then QBCore.Functions.Notify('Keine Items im Automaten!', 'error') - showingMenu = false return end @@ -403,32 +348,24 @@ function openPriceMenu(preciseCoords) description = 'Aktueller Preis: $' .. item.price, icon = 'fas fa-tag', onSelect = function() - setPriceForItem(preciseCoords, item.name, itemLabel) + setPriceForItem(coords, item.name, itemLabel) end }) end - table.insert(options, { - title = 'Zurück', - description = 'Zurück zum Hauptmenü', - icon = 'fas fa-arrow-left', - onSelect = function() - showOwnerMenu(currentMachine, preciseCoords) - end - }) - lib.registerContext({ id = 'vending_price_menu', title = 'Preise festlegen', + menu = 'vending_owner_menu', options = options }) lib.showContext('vending_price_menu') - end, preciseCoords) + end, coords) end -- Set price for specific item -function setPriceForItem(preciseCoords, itemName, itemLabel) +function setPriceForItem(coords, itemName, itemLabel) local input = lib.inputDialog('Preis festlegen', { { type = 'number', @@ -441,17 +378,14 @@ function setPriceForItem(preciseCoords, itemName, itemLabel) }) if input and input[1] then - TriggerServerEvent('vending:server:setItemPrice', preciseCoords, itemName, tonumber(input[1])) + TriggerServerEvent('vending:server:setItemPrice', coords, itemName, tonumber(input[1])) end - - showingMenu = false end -- Open withdraw menu -function openWithdrawMenu(preciseCoords, availableMoney) +function openWithdrawMenu(coords, availableMoney) if availableMoney <= 0 then QBCore.Functions.Notify('Kein Geld im Automaten!', 'error') - showingMenu = false return end @@ -467,10 +401,8 @@ function openWithdrawMenu(preciseCoords, availableMoney) }) if input and input[1] then - TriggerServerEvent('vending:server:withdrawMoney', preciseCoords, tonumber(input[1])) + TriggerServerEvent('vending:server:withdrawMoney', coords, tonumber(input[1])) end - - showingMenu = false end -- Open stats menu @@ -478,6 +410,7 @@ function openStatsMenu(machine) lib.registerContext({ id = 'vending_stats_menu', title = 'Verkaufsstatistiken', + menu = 'vending_owner_menu', options = { { title = 'Gesamteinnahmen', @@ -493,14 +426,6 @@ function openStatsMenu(machine) title = 'Standort', description = 'X: ' .. math.floor(machine.coords.x) .. ' Y: ' .. math.floor(machine.coords.y), icon = 'fas fa-map-marker-alt' - }, - { - title = 'Zurück', - description = 'Zurück zum Hauptmenü', - icon = 'fas fa-arrow-left', - onSelect = function() - showOwnerMenu(currentMachine, machine.coords) - end } } }) @@ -509,7 +434,8 @@ function openStatsMenu(machine) end -- Open managers menu -function openManagersMenu(preciseCoords) +function openManagersMenu(coords) + -- Get current managers QBCore.Functions.TriggerCallback('vending:server:getManagers', function(managers) local options = { { @@ -517,43 +443,38 @@ function openManagersMenu(preciseCoords) description = 'Neuen Verwalter hinzufügen', icon = 'fas fa-user-plus', onSelect = function() - openAddManagerMenu(preciseCoords) + openAddManagerMenu(coords) end } } + -- Add existing managers with remove option if #managers > 0 then for i = 1, #managers do local manager = managers[i] table.insert(options, { title = manager.name, - description = manager.online and 'Online - Klicken zum Entfernen' or 'Offline - Klicken zum Entfernen', + description = manager.online and 'Online' or 'Offline', icon = manager.online and 'fas fa-circle text-success' or 'fas fa-circle text-danger', onSelect = function() lib.registerContext({ - id = 'manager_confirm_remove', - title = 'Verwalter entfernen', + id = 'manager_options', + title = 'Verwalter: ' .. manager.name, + menu = 'managers_menu', options = { { - title = 'Bestätigen', - description = manager.name .. ' als Verwalter entfernen', - icon = 'fas fa-check', + title = 'Entfernen', + description = 'Verwalter entfernen', + icon = 'fas fa-user-minus', onSelect = function() - TriggerServerEvent('vending:server:removeManager', preciseCoords, manager.citizenid) - showingMenu = false - end - }, - { - title = 'Abbrechen', - description = 'Zurück zur Verwalterliste', - icon = 'fas fa-times', - onSelect = function() - openManagersMenu(preciseCoords) + TriggerServerEvent('vending:server:removeManager', coords, manager.citizenid) + Wait(500) + openManagersMenu(coords) -- Refresh the menu end } } }) - lib.showContext('manager_confirm_remove') + lib.showContext('manager_options') end }) end @@ -566,31 +487,22 @@ function openManagersMenu(preciseCoords) }) end - table.insert(options, { - title = 'Zurück', - description = 'Zurück zum Hauptmenü', - icon = 'fas fa-arrow-left', - onSelect = function() - showOwnerMenu(currentMachine, preciseCoords) - end - }) - lib.registerContext({ id = 'managers_menu', title = 'Verwalter verwalten', + menu = 'vending_owner_menu', options = options }) lib.showContext('managers_menu') - end, preciseCoords) + end, coords) end -- Open add manager menu -function openAddManagerMenu(preciseCoords) +function openAddManagerMenu(coords) QBCore.Functions.TriggerCallback('vending:server:getOnlinePlayers', function(players) if #players == 0 then QBCore.Functions.Notify('Keine Spieler online!', 'error') - showingMenu = false return end @@ -603,24 +515,17 @@ function openAddManagerMenu(preciseCoords) description = 'ID: ' .. player.id, icon = 'fas fa-user', onSelect = function() - TriggerServerEvent('vending:server:addManager', preciseCoords, player.id) - showingMenu = false + TriggerServerEvent('vending:server:addManager', coords, player.id) + Wait(500) + openManagersMenu(coords) -- Refresh the menu end }) end - table.insert(options, { - title = 'Zurück', - description = 'Zurück zur Verwalterliste', - icon = 'fas fa-arrow-left', - onSelect = function() - openManagersMenu(preciseCoords) - end - }) - lib.registerContext({ id = 'add_manager_menu', title = 'Verwalter hinzufügen', + menu = 'managers_menu', options = options }) @@ -628,8 +533,36 @@ function openAddManagerMenu(preciseCoords) end) end +-- Robbery menu +RegisterNetEvent('vending:client:startRobbery', function(data) + local entity = data.entity + local coords = GetEntityCoords(entity) + + lib.registerContext({ + id = 'vending_robbery_confirm', + title = 'Verkaufsautomat aufbrechen', + options = { + { + title = 'Aufbrechen', + description = 'Versuche den Automaten aufzubrechen', + icon = 'fas fa-mask', + onSelect = function() + TriggerServerEvent('vending:server:startRobbery', coords) + end + }, + { + title = 'Abbrechen', + description = 'Aufbruch abbrechen', + icon = 'fas fa-times' + } + } + }) + + lib.showContext('vending_robbery_confirm') +end) + -- Start robbery animation and progress -RegisterNetEvent('vending:client:startRobbery', function(preciseCoords) +RegisterNetEvent('vending:client:startRobbery', function(coords) local playerPed = PlayerPedId() local robberyTime = 10000 -- 10 seconds @@ -656,17 +589,23 @@ RegisterNetEvent('vending:client:startRobbery', function(preciseCoords) }) ClearPedTasks(playerPed) - TriggerServerEvent('vending:server:completeRobbery', preciseCoords, success) + TriggerServerEvent('vending:server:completeRobbery', coords, success) else -- Fallback without progress bar Wait(robberyTime) ClearPedTasks(playerPed) - TriggerServerEvent('vending:server:completeRobbery', preciseCoords, true) + TriggerServerEvent('vending:server:completeRobbery', coords, true) end end) -- Police alert RegisterNetEvent('vending:client:policeAlert', function(coords, streetName) + local alert = { + title = "Verkaufsautomat Aufbruch", + coords = coords, + description = "Ein Verkaufsautomat wird aufgebrochen in " .. streetName + } + -- Add blip local blip = AddBlipForCoord(coords.x, coords.y, coords.z) SetBlipSprite(blip, 161) @@ -685,17 +624,40 @@ RegisterNetEvent('vending:client:policeAlert', function(coords, streetName) QBCore.Functions.Notify('Verkaufsautomat Aufbruch gemeldet: ' .. streetName, 'error', 8000) end) --- Event handlers for menu closing -RegisterNetEvent('vending:client:closeMenu', function() - showingMenu = false +-- Refresh targets (called when new machine is registered) +RegisterNetEvent('vending:client:refreshTargets', function() + InitializeTargets() end) --- Close menu when inventory is opened -AddEventHandler('inventory:client:OpenInventory', function() - showingMenu = false +-- Management menu (alternative opening method) +RegisterNetEvent('vending:client:openManagement', function(machine) + lib.registerContext({ + id = 'vending_management', + title = 'Verkaufsautomat #' .. machine.id, + options = { + { + title = 'Inventar öffnen', + description = 'Items hinzufügen oder entfernen', + icon = 'fas fa-box', + onSelect = function() + TriggerServerEvent('vending:server:openStash', machine.coords) + end + }, + { + title = 'Einnahmen: $' .. machine.money, + description = 'Geld abheben', + icon = 'fas fa-money-bill', + onSelect = function() + openWithdrawMenu(machine.coords, machine.money) + end + } + } + }) + + lib.showContext('vending_management') end) --- Debug commands +-- Debug command to check props RegisterCommand('checkvendingprops', function() local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) @@ -714,9 +676,7 @@ RegisterCommand('checkvendingprops', function() if dist < 30.0 then foundProps = foundProps + 1 - local preciseCoords = getPreciseCoords(obj) - print("Found " .. propName .. " at distance: " .. dist .. " | Coords: " .. - preciseCoords.x .. ", " .. preciseCoords.y .. ", " .. preciseCoords.z) + print("Found " .. propName .. " at distance: " .. dist) -- Add a temporary blip local blip = AddBlipForEntity(obj) @@ -739,41 +699,18 @@ RegisterCommand('checkvendingprops', function() QBCore.Functions.Notify('Found ' .. foundProps .. ' vending machines nearby', 'primary') end, false) +-- Debug commands RegisterCommand('vendingdebug', function() local playerPed = PlayerPedId() local coords = GetEntityCoords(playerPed) - -- Try to find the closest vending machine - local minDist = 3.0 - local closestEntity = nil - local objects = GetGamePool('CObject') - - for _, obj in ipairs(objects) do - local model = GetEntityModel(obj) - for _, propName in ipairs(Config.VendingProps) do - if model == GetHashKey(propName) then - local objCoords = GetEntityCoords(obj) - local dist = #(coords - objCoords) - if dist < minDist then - minDist = dist - closestEntity = obj - end - end + QBCore.Functions.TriggerCallback('vending:server:getMachineByCoords', function(machine) + if machine then + print('Machine found:', json.encode(machine)) + QBCore.Functions.Notify('Machine data logged to console', 'primary') + else + print('No machine found at current location') + QBCore.Functions.Notify('No machine found here', 'error') end - end - - if closestEntity then - local preciseCoords = getPreciseCoords(closestEntity) - QBCore.Functions.TriggerCallback('vending:server:getMachineByCoords', function(machine) - if machine then - print('Machine found:', json.encode(machine)) - QBCore.Functions.Notify('Machine #' .. machine.id .. ' | Owner: ' .. machine.owner, 'primary') - else - print('No machine found at coords:', json.encode(preciseCoords)) - QBCore.Functions.Notify('No machine found at these coords', 'error') - end - end, preciseCoords) - else - QBCore.Functions.Notify('No vending machine found nearby', 'error') - end + end, coords) end, false) diff --git a/resources/[inventory]/nordi_vending/server.lua b/resources/[inventory]/nordi_vending/server.lua index 38a841134..9e877da76 100644 --- a/resources/[inventory]/nordi_vending/server.lua +++ b/resources/[inventory]/nordi_vending/server.lua @@ -24,46 +24,16 @@ CreateThread(function() end end) --- Helper function to get machine ID by precise coordinates -function getMachineIdByCoords(preciseCoords) - -- First try to find an exact match - for id, machine in pairs(vendingMachines) do - if machine.coords.x == preciseCoords.x and - machine.coords.y == preciseCoords.y and - machine.coords.z == preciseCoords.z and - machine.coords.model == preciseCoords.model then - return id - end - end - - -- If no exact match, try with a small tolerance - local closestId = nil - local closestDist = 0.1 -- Very small tolerance - - for id, machine in pairs(vendingMachines) do - if machine.coords.model == preciseCoords.model then - local dist = #(vector3(preciseCoords.x, preciseCoords.y, preciseCoords.z) - vector3(machine.coords.x, machine.coords.y, machine.coords.z)) - if dist < closestDist then - closestDist = dist - closestId = id - end - end - end - - return closestId -end - -- Register vending machine (when player buys it) -RegisterNetEvent('vending:server:registerMachine', function(preciseCoords, prop) +RegisterNetEvent('vending:server:registerMachine', function(coords, prop) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Check if there's already a machine at these coords for id, machine in pairs(vendingMachines) do - if machine.coords.x == preciseCoords.x and - machine.coords.y == preciseCoords.y and - machine.coords.z == preciseCoords.z then + local dist = #(vector3(coords.x, coords.y, coords.z) - vector3(machine.coords.x, machine.coords.y, machine.coords.z)) + if dist < 2.0 then TriggerClientEvent('QBCore:Notify', src, 'Hier ist bereits ein Automat registriert!', 'error') return end @@ -81,7 +51,7 @@ RegisterNetEvent('vending:server:registerMachine', function(preciseCoords, prop) -- Create machine in database local machineId = MySQL.insert.await('INSERT INTO vending_machines (owner, coords, prop, money, items, prices, managers) VALUES (?, ?, ?, ?, ?, ?, ?)', { Player.PlayerData.citizenid, - json.encode(preciseCoords), + json.encode(coords), prop, 0, json.encode({}), @@ -93,7 +63,7 @@ RegisterNetEvent('vending:server:registerMachine', function(preciseCoords, prop) vendingMachines[machineId] = { id = machineId, owner = Player.PlayerData.citizenid, - coords = preciseCoords, + coords = coords, prop = prop, money = 0, items = {}, @@ -102,19 +72,19 @@ RegisterNetEvent('vending:server:registerMachine', function(preciseCoords, prop) stash = 'vending_' .. machineId } - print("^2[VENDING]^7 New vending machine registered: #" .. machineId) + print("^2[VENDING]^7 New vending machine registered: " .. machineId) TriggerClientEvent('QBCore:Notify', src, 'Verkaufsautomat erfolgreich gekauft für $' .. Config.VendingMachinePrice .. '!', 'success') TriggerClientEvent('vending:client:refreshTargets', -1) end) -- Sell vending machine -RegisterNetEvent('vending:server:sellMachine', function(preciseCoords, machineId) +RegisterNetEvent('vending:server:sellMachine', function(coords, machineId) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end if not machineId then - machineId = getMachineIdByCoords(preciseCoords) + machineId = getMachineIdByCoords(coords) end if not machineId then @@ -194,16 +164,13 @@ function canManageMachine(playerId, machineId) end -- Open management menu -RegisterNetEvent('vending:server:openManagement', function(preciseCoords) +RegisterNetEvent('vending:server:openManagement', function(coords) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end + local machineId = getMachineIdByCoords(coords) + if not machineId then return end local machine = vendingMachines[machineId] @@ -220,16 +187,13 @@ RegisterNetEvent('vending:server:openManagement', function(preciseCoords) end) -- Open stash -RegisterNetEvent('vending:server:openStash', function(preciseCoords) +RegisterNetEvent('vending:server:openStash', function(coords) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end + local machineId = getMachineIdByCoords(coords) + if not machineId then return end -- Check if player can manage if not canManageMachine(src, machineId) then @@ -246,18 +210,14 @@ RegisterNetEvent('vending:server:openStash', function(preciseCoords) label = 'Vending Machine #' .. machine.id }) end) - -- Set item price -RegisterNetEvent('vending:server:setItemPrice', function(preciseCoords, itemName, price) +RegisterNetEvent('vending:server:setItemPrice', function(coords, itemName, price) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end + local machineId = getMachineIdByCoords(coords) + if not machineId then return end -- Check if player can manage if not canManageMachine(src, machineId) then @@ -275,16 +235,13 @@ RegisterNetEvent('vending:server:setItemPrice', function(preciseCoords, itemName end) -- Withdraw money -RegisterNetEvent('vending:server:withdrawMoney', function(preciseCoords, amount) +RegisterNetEvent('vending:server:withdrawMoney', function(coords, amount) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end + local machineId = getMachineIdByCoords(coords) + if not machineId then return end -- Check if player can manage if not canManageMachine(src, machineId) then @@ -309,16 +266,13 @@ RegisterNetEvent('vending:server:withdrawMoney', function(preciseCoords, amount) end) -- Buy item from vending machine with quantity selection -RegisterNetEvent('vending:server:buyItem', function(preciseCoords, itemName, amount) +RegisterNetEvent('vending:server:buyItem', function(coords, itemName, amount) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end + local machineId = getMachineIdByCoords(coords) + if not machineId then return end local machine = vendingMachines[machineId] local price = machine.prices[itemName] or Config.DefaultPrice @@ -381,16 +335,13 @@ RegisterNetEvent('vending:server:buyItem', function(preciseCoords, itemName, amo end) -- Add manager to vending machine -RegisterNetEvent('vending:server:addManager', function(preciseCoords, targetId) +RegisterNetEvent('vending:server:addManager', function(coords, targetId) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end + local machineId = getMachineIdByCoords(coords) + if not machineId then return end local machine = vendingMachines[machineId] @@ -428,16 +379,13 @@ RegisterNetEvent('vending:server:addManager', function(preciseCoords, targetId) end) -- Remove manager from vending machine -RegisterNetEvent('vending:server:removeManager', function(preciseCoords, citizenid) +RegisterNetEvent('vending:server:removeManager', function(coords, citizenid) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end + local machineId = getMachineIdByCoords(coords) + if not machineId then return end local machine = vendingMachines[machineId] @@ -483,148 +431,9 @@ RegisterNetEvent('vending:server:removeManager', function(preciseCoords, citizen end end) --- Start robbery -RegisterNetEvent('vending:server:startRobbery', function(preciseCoords) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end - - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end - - local machine = vendingMachines[machineId] - - -- Check if player has required item - local hasItem = Player.Functions.GetItemByName(Config.RobberyItem) - if not hasItem or hasItem.amount < 1 then - TriggerClientEvent('QBCore:Notify', src, 'Du benötigst einen ' .. Config.RobberyItem, 'error') - return - end - - -- Check if already being robbed - if robberyInProgress[machineId] then - TriggerClientEvent('QBCore:Notify', src, 'Dieser Automat wird bereits aufgebrochen!', 'error') - return - end - - -- Check if machine has money - if machine.money < Config.MinRobberyAmount then - TriggerClientEvent('QBCore:Notify', src, 'Nicht genug Geld im Automaten!', 'error') - return - end - - robberyInProgress[machineId] = true - - -- Alert police - local streetHash = GetStreetNameAtCoord(machine.coords.x, machine.coords.y, machine.coords.z) - local streetName = GetStreetNameFromHashKey(streetHash) - - local players = QBCore.Functions.GetQBPlayers() - for k, v in pairs(players) do - if v.PlayerData.job.name == 'police' and v.PlayerData.job.onduty then - TriggerClientEvent('vending:client:policeAlert', v.PlayerData.source, machine.coords, streetName) - end - end - - -- Alert owner and managers - for _, playerId in ipairs(QBCore.Functions.GetPlayers()) do - local targetPlayer = QBCore.Functions.GetPlayer(playerId) - if targetPlayer then - if targetPlayer.PlayerData.citizenid == machine.owner then - TriggerClientEvent('QBCore:Notify', targetPlayer.PlayerData.source, 'Dein Verkaufsautomat wird gerade aufgebrochen! Standort: ' .. streetName, 'error', 10000) - elseif machine.managers then - for _, manager in pairs(machine.managers) do - if targetPlayer.PlayerData.citizenid == manager then - TriggerClientEvent('QBCore:Notify', targetPlayer.PlayerData.source, 'Ein Verkaufsautomat, den du verwaltest, wird gerade aufgebrochen! Standort: ' .. streetName, 'error', 10000) - break - end - end - end - end - end - - TriggerClientEvent('vending:client:startRobbery', src, preciseCoords) -end) - --- Complete robbery -RegisterNetEvent('vending:server:completeRobbery', function(preciseCoords, success) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end - - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - TriggerClientEvent('QBCore:Notify', src, 'Automat nicht gefunden!', 'error') - return - end - - local machine = vendingMachines[machineId] - robberyInProgress[machineId] = false - - if success then - local stolenAmount = math.random(Config.MinRobberyAmount, math.min(machine.money, Config.MaxRobberyAmount)) - - -- Remove money from machine - machine.money = machine.money - stolenAmount - MySQL.update('UPDATE vending_machines SET money = ? WHERE id = ?', {machine.money, machineId}) - - -- Give money to player - Player.Functions.AddMoney('cash', stolenAmount) - TriggerClientEvent('QBCore:Notify', src, 'Du hast $' .. stolenAmount .. ' gestohlen!', 'success') - - -- Remove robbery item with chance - if math.random(1, 100) <= Config.RobberyItemBreakChance then - Player.Functions.RemoveItem(Config.RobberyItem, 1) - TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[Config.RobberyItem], 'remove') - TriggerClientEvent('QBCore:Notify', src, 'Dein ' .. Config.RobberyItem .. ' ist kaputt gegangen!', 'error') - end - else - TriggerClientEvent('QBCore:Notify', src, 'Aufbruch fehlgeschlagen!', 'error') - end -end) - --- Get machine data by coordinates -QBCore.Functions.CreateCallback('vending:server:getMachineByCoords', function(source, cb, preciseCoords) - local machineId = getMachineIdByCoords(preciseCoords) - if machineId then - cb(vendingMachines[machineId]) - else - cb(nil) - end -end) - --- Get stash items for vending machine menu -QBCore.Functions.CreateCallback('vending:server:getStashItems', function(source, cb, preciseCoords) - local machineId = getMachineIdByCoords(preciseCoords) - if not machineId then - cb({}) - return - end - - local machine = vendingMachines[machineId] - - -- Get stash items using correct export - local stashItems = exports["tgiann-inventory"]:GetSecondaryInventoryItems("stash", machine.stash) - local items = {} - - if stashItems then - for slot, item in pairs(stashItems) do - if item.amount > 0 then - item.price = machine.prices[item.name] or Config.DefaultPrice - table.insert(items, item) - end - end - end - - cb(items) -end) - -- Get managers list -QBCore.Functions.CreateCallback('vending:server:getManagers', function(source, cb, preciseCoords) - local machineId = getMachineIdByCoords(preciseCoords) +QBCore.Functions.CreateCallback('vending:server:getManagers', function(source, cb, coords) + local machineId = getMachineIdByCoords(coords) if not machineId then cb({}) return @@ -676,15 +485,177 @@ QBCore.Functions.CreateCallback('vending:server:getManagers', function(source, c cb(managersList) end) --- Check if machine exists -QBCore.Functions.CreateCallback('vending:server:machineExists', function(source, cb, preciseCoords) - local machineId = getMachineIdByCoords(preciseCoords) - cb(machineId ~= nil) +-- Start robbery +RegisterNetEvent('vending:server:startRobbery', function(coords) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end + + local machineId = getMachineIdByCoords(coords) + if not machineId then return end + + local machine = vendingMachines[machineId] + + -- Check if player has required item + local hasItem = Player.Functions.GetItemByName(Config.RobberyItem) + if not hasItem or hasItem.amount < 1 then + TriggerClientEvent('QBCore:Notify', src, 'Du benötigst einen ' .. Config.RobberyItem, 'error') + return + end + + -- Check if already being robbed + if robberyInProgress[machineId] then + TriggerClientEvent('QBCore:Notify', src, 'Dieser Automat wird bereits aufgebrochen!', 'error') + return + end + + -- Check if machine has money + if machine.money < Config.MinRobberyAmount then + TriggerClientEvent('QBCore:Notify', src, 'Nicht genug Geld im Automaten!', 'error') + return + end + + robberyInProgress[machineId] = true + + -- Alert police + local streetHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z) + local streetName = GetStreetNameFromHashKey(streetHash) + + local players = QBCore.Functions.GetQBPlayers() + for k, v in pairs(players) do + if v.PlayerData.job.name == 'police' and v.PlayerData.job.onduty then + TriggerClientEvent('vending:client:policeAlert', v.PlayerData.source, coords, streetName) + end + end + + -- Alert owner and managers + for _, playerId in ipairs(QBCore.Functions.GetPlayers()) do + local targetPlayer = QBCore.Functions.GetPlayer(playerId) + if targetPlayer then + if targetPlayer.PlayerData.citizenid == machine.owner then + TriggerClientEvent('QBCore:Notify', targetPlayer.PlayerData.source, 'Dein Verkaufsautomat wird gerade aufgebrochen! Standort: ' .. streetName, 'error', 10000) + elseif machine.managers then + for _, manager in pairs(machine.managers) do + if targetPlayer.PlayerData.citizenid == manager then + TriggerClientEvent('QBCore:Notify', targetPlayer.PlayerData.source, 'Ein Verkaufsautomat, den du verwaltest, wird gerade aufgebrochen! Standort: ' .. streetName, 'error', 10000) + break + end + end + end + end + end + + TriggerClientEvent('vending:client:startRobbery', src, coords) +end) + +-- Complete robbery +RegisterNetEvent('vending:server:completeRobbery', function(coords, success) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end + + local machineId = getMachineIdByCoords(coords) + if not machineId then return end + + local machine = vendingMachines[machineId] + robberyInProgress[machineId] = false + + if success then + local stolenAmount = math.random(Config.MinRobberyAmount, math.min(machine.money, Config.MaxRobberyAmount)) + + -- Remove money from machine + machine.money = machine.money - stolenAmount + MySQL.update('UPDATE vending_machines SET money = ? WHERE id = ?', {machine.money, machineId}) + + -- Give money to player + Player.Functions.AddMoney('cash', stolenAmount) + TriggerClientEvent('QBCore:Notify', src, 'Du hast $' .. stolenAmount .. ' gestohlen!', 'success') + + -- Remove robbery item with chance + if math.random(1, 100) <= Config.RobberyItemBreakChance then + Player.Functions.RemoveItem(Config.RobberyItem, 1) + TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[Config.RobberyItem], 'remove') + TriggerClientEvent('QBCore:Notify', src, 'Dein ' .. Config.RobberyItem .. ' ist kaputt gegangen!', 'error') + end + else + TriggerClientEvent('QBCore:Notify', src, 'Aufbruch fehlgeschlagen!', 'error') + end +end) + +-- Helper function to get machine ID by coordinates +function getMachineIdByCoords(coords) + for id, machine in pairs(vendingMachines) do + local dist = #(vector3(coords.x, coords.y, coords.z) - vector3(machine.coords.x, machine.coords.y, machine.coords.z)) + if dist < 2.0 then + return id + end + end + return nil +end + +-- Get machine data by coordinates +QBCore.Functions.CreateCallback('vending:server:getMachineByCoords', function(source, cb, coords) + local machineId = getMachineIdByCoords(coords) + if machineId then + cb(vendingMachines[machineId]) + else + cb(nil) + end +end) + +-- Get stash items for vending machine menu +QBCore.Functions.CreateCallback('vending:server:getStashItems', function(source, cb, coords) + local machineId = getMachineIdByCoords(coords) + if not machineId then + cb({}) + return + end + + local machine = vendingMachines[machineId] + + -- Get stash items using correct export + local stashItems = exports["tgiann-inventory"]:GetSecondaryInventoryItems("stash", machine.stash) + local items = {} + + if stashItems then + for slot, item in pairs(stashItems) do + if item.amount > 0 then + item.price = machine.prices[item.name] or Config.DefaultPrice + table.insert(items, item) + end + end + end + + cb(items) +end) + +-- Check if player owns machine +QBCore.Functions.CreateCallback('vending:server:isOwner', function(source, cb, coords) + local Player = QBCore.Functions.GetPlayer(source) + if not Player then + cb(false) + return + end + + local machineId = getMachineIdByCoords(coords) + if not machineId then + cb(false) + return + end + + local machine = vendingMachines[machineId] + cb(machine.owner == Player.PlayerData.citizenid) end) -- Check if player can manage machine -QBCore.Functions.CreateCallback('vending:server:canManage', function(source, cb, preciseCoords) - local machineId = getMachineIdByCoords(preciseCoords) +QBCore.Functions.CreateCallback('vending:server:canManage', function(source, cb, coords) + local Player = QBCore.Functions.GetPlayer(source) + if not Player then + cb(false) + return + end + + local machineId = getMachineIdByCoords(coords) if not machineId then cb(false) return @@ -693,6 +664,12 @@ QBCore.Functions.CreateCallback('vending:server:canManage', function(source, cb, cb(canManageMachine(source, machineId)) end) +-- Check if machine exists at coords +QBCore.Functions.CreateCallback('vending:server:machineExists', function(source, cb, coords) + local machineId = getMachineIdByCoords(coords) + cb(machineId ~= nil) +end) + -- Get online players for manager selection QBCore.Functions.CreateCallback('vending:server:getOnlinePlayers', function(source, cb) local src = source @@ -735,3 +712,4 @@ QBCore.Commands.Add('vendingdebug', 'Debug vending machines (Admin Only)', {}, f end end, 'admin') +