diff --git a/resources/[inventory]/nordi_vending/client.lua b/resources/[inventory]/nordi_vending/client.lua index 07313048b..7a2e337e8 100644 --- a/resources/[inventory]/nordi_vending/client.lua +++ b/resources/[inventory]/nordi_vending/client.lua @@ -1,147 +1,68 @@ local QBCore = exports['qb-core']:GetCoreObject() +local nearbyMachines = {} +local currentMachine = nil +local showingMenu = false --- Function to initialize targets with more robust approach -function InitializeTargets() - -- First, remove any existing targets - for _, propName in ipairs(Config.VendingProps) do - exports['qb-target']:RemoveTargetModel(propName) - Wait(50) -- Small wait to ensure removal completes - end - - -- Wait a bit before adding new targets - Wait(200) - - -- Add targets one by one with delay between each - for _, propName in ipairs(Config.VendingProps) do - exports['qb-target']:AddTargetModel(propName, { - 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 - }, - { - 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 target for prop: " .. propName) - Wait(100) -- Wait between each prop to avoid race conditions - end - - print("^2[VENDING]^7 Finished adding targets to " .. #Config.VendingProps .. " vending machine props") -end - --- More aggressive target initialization +-- Kontinuierlicher Check für Verkaufsautomaten CreateThread(function() - -- Initial delay to ensure everything is loaded - Wait(3000) - - -- First attempt - InitializeTargets() - - -- Second attempt after a delay - Wait(5000) - InitializeTargets() - - -- Third attempt after server is fully loaded - Wait(10000) - InitializeTargets() - - -- Fourth attempt after even longer delay - Wait(20000) - InitializeTargets() - - -- Set up a repeating check every 5 minutes while true do - Wait(300000) -- 5 minutes - InitializeTargets() - end -end) - --- Force refresh targets when player moves between areas -CreateThread(function() - local lastArea = 0 - - while true do - Wait(5000) local playerPed = PlayerPedId() - local coords = GetEntityCoords(playerPed) - local currentArea = math.floor(coords.x / 100) * 1000 + math.floor(coords.y / 100) + local playerCoords = GetEntityCoords(playerPed) + local sleep = 1000 - if currentArea ~= lastArea then - print("^2[VENDING]^7 Player moved to new area, refreshing targets") - InitializeTargets() - lastArea = currentArea + -- 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 + end + end + if currentMachine then break end end + + Wait(sleep) end end) --- Add a command to manually refresh targets with notification -RegisterCommand('fixvending', function() - InitializeTargets() - QBCore.Functions.Notify('Vending machine targets refreshed', 'success') -end, false) - --- Register for core events that might indicate a good time to refresh -RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() - Wait(2000) - 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(2000) - InitializeTargets() +-- 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) - --- Listen for target-specific events if they exist -RegisterNetEvent('qb-target:client:refreshTargets', function() - Wait(500) - InitializeTargets() -end) - --- Refresh targets when a new machine is registered -RegisterNetEvent('vending:client:refreshTargets', function() - Wait(500) - InitializeTargets() -end) +end -- Get precise coordinates for entity function getPreciseCoords(entity) @@ -158,48 +79,30 @@ function getPreciseCoords(entity) } end --- Check if machine is registered -function isRegisteredMachine(entity) +-- Handle machine interaction +function handleMachineInteraction(entity) + showingMenu = true local preciseCoords = getPreciseCoords(entity) - local isRegistered = false + -- Check if machine is registered QBCore.Functions.TriggerCallback('vending:server:machineExists', function(exists) - isRegistered = 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) - - -- 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 --- Check if player can manage machine -function canManageMachine(entity) - local preciseCoords = getPreciseCoords(entity) - local canManage = false - - QBCore.Functions.TriggerCallback('vending:server:canManage', function(result) - canManage = result - end, preciseCoords) - - -- 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 preciseCoords = getPreciseCoords(entity) +-- Show purchase menu (buy the machine) +function showPurchaseMenu(entity, preciseCoords) local model = GetEntityModel(entity) local prop = nil @@ -211,39 +114,67 @@ RegisterNetEvent('vending:client:buyMachine', function(data) end end - if not prop then return end + if not prop then + showingMenu = false + return + end lib.registerContext({ - id = 'vending_buy_confirm', - title = 'Verkaufsautomat kaufen', + id = 'vending_purchase', + title = 'Verkaufsautomat', options = { { - title = 'Bestätigen', - description = 'Automaten für $' .. Config.VendingMachinePrice .. ' kaufen', - icon = 'fas fa-check', + title = 'Automaten kaufen', + description = 'Kaufe diesen Automaten für $' .. Config.VendingMachinePrice, + icon = 'fas fa-dollar-sign', onSelect = function() - TriggerServerEvent('vending:server:registerMachine', preciseCoords, prop) + 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') end }, { - title = 'Abbrechen', - description = 'Kauf abbrechen', - icon = 'fas fa-times' + title = 'Schließen', + description = 'Menü schließen', + icon = 'fas fa-times', + onSelect = function() + showingMenu = false + end } } }) - lib.showContext('vending_buy_confirm') -end) + lib.showContext('vending_purchase') +end --- Open buy menu with quantity selection -RegisterNetEvent('vending:client:openBuyMenu', function(data) - local entity = data.entity - local preciseCoords = getPreciseCoords(entity) - +-- Show buy menu (buy items from machine) +function showBuyMenu(entity, preciseCoords) QBCore.Functions.TriggerCallback('vending:server:getStashItems', function(items) if #items == 0 then QBCore.Functions.Notify('Dieser Automat ist leer!', 'error') + showingMenu = false return end @@ -264,8 +195,52 @@ RegisterNetEvent('vending:client:openBuyMenu', function(data) end end - if #options == 0 then + -- 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 QBCore.Functions.Notify('Keine Artikel verfügbar!', 'error') + showingMenu = false return end @@ -277,40 +252,14 @@ RegisterNetEvent('vending:client:openBuyMenu', function(data) lib.showContext('vending_buy_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 end --- Open owner menu -RegisterNetEvent('vending:client:openOwnerMenu', function(data) - local entity = data.entity - local preciseCoords = getPreciseCoords(entity) - +-- Show owner menu +function showOwnerMenu(entity, preciseCoords) QBCore.Functions.TriggerCallback('vending:server:getMachineByCoords', function(machine) if not machine then QBCore.Functions.Notify('Automat nicht gefunden!', 'error') + showingMenu = false return end @@ -321,6 +270,7 @@ RegisterNetEvent('vending:client:openOwnerMenu', function(data) icon = 'fas fa-box', onSelect = function() TriggerServerEvent('vending:server:openStash', preciseCoords) + showingMenu = false end }, { @@ -363,7 +313,7 @@ RegisterNetEvent('vending:client:openOwnerMenu', function(data) -- 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) @@ -371,6 +321,15 @@ RegisterNetEvent('vending:client:openOwnerMenu', function(data) }) 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', @@ -379,7 +338,33 @@ RegisterNetEvent('vending:client:openOwnerMenu', function(data) lib.showContext('vending_owner_menu') end, preciseCoords) -end) +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 -- Funktion zum Verkaufen des Automaten function sellVendingMachine(preciseCoords, machineId) @@ -387,7 +372,7 @@ function sellVendingMachine(preciseCoords, machineId) { 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 } }) @@ -395,6 +380,8 @@ function sellVendingMachine(preciseCoords, machineId) if input and input[1] then TriggerServerEvent('vending:server:sellMachine', preciseCoords, machineId) end + + showingMenu = false end -- Open price menu @@ -402,6 +389,7 @@ function openPriceMenu(preciseCoords) QBCore.Functions.TriggerCallback('vending:server:getStashItems', function(items) if #items == 0 then QBCore.Functions.Notify('Keine Items im Automaten!', 'error') + showingMenu = false return end @@ -420,10 +408,18 @@ function openPriceMenu(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 = 'vending_price_menu', title = 'Preise festlegen', - menu = 'vending_owner_menu', options = options }) @@ -447,12 +443,15 @@ function setPriceForItem(preciseCoords, itemName, itemLabel) if input and input[1] then TriggerServerEvent('vending:server:setItemPrice', preciseCoords, itemName, tonumber(input[1])) end + + showingMenu = false end -- Open withdraw menu function openWithdrawMenu(preciseCoords, availableMoney) if availableMoney <= 0 then QBCore.Functions.Notify('Kein Geld im Automaten!', 'error') + showingMenu = false return end @@ -470,6 +469,8 @@ function openWithdrawMenu(preciseCoords, availableMoney) if input and input[1] then TriggerServerEvent('vending:server:withdrawMoney', preciseCoords, tonumber(input[1])) end + + showingMenu = false end -- Open stats menu @@ -477,7 +478,6 @@ function openStatsMenu(machine) lib.registerContext({ id = 'vending_stats_menu', title = 'Verkaufsstatistiken', - menu = 'vending_owner_menu', options = { { title = 'Gesamteinnahmen', @@ -493,6 +493,14 @@ 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 } } }) @@ -502,7 +510,6 @@ end -- Open managers menu function openManagersMenu(preciseCoords) - -- Get current managers QBCore.Functions.TriggerCallback('vending:server:getManagers', function(managers) local options = { { @@ -515,33 +522,38 @@ function openManagersMenu(preciseCoords) } } - -- 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' or 'Offline', + description = manager.online and 'Online - Klicken zum Entfernen' or 'Offline - Klicken zum Entfernen', icon = manager.online and 'fas fa-circle text-success' or 'fas fa-circle text-danger', onSelect = function() lib.registerContext({ - id = 'manager_options', - title = 'Verwalter: ' .. manager.name, - menu = 'managers_menu', + id = 'manager_confirm_remove', + title = 'Verwalter entfernen', options = { { - title = 'Entfernen', - description = 'Verwalter entfernen', - icon = 'fas fa-user-minus', + title = 'Bestätigen', + description = manager.name .. ' als Verwalter entfernen', + icon = 'fas fa-check', onSelect = function() TriggerServerEvent('vending:server:removeManager', preciseCoords, manager.citizenid) - Wait(500) - openManagersMenu(preciseCoords) -- Refresh the menu + showingMenu = false + end + }, + { + title = 'Abbrechen', + description = 'Zurück zur Verwalterliste', + icon = 'fas fa-times', + onSelect = function() + openManagersMenu(preciseCoords) end } } }) - lib.showContext('manager_options') + lib.showContext('manager_confirm_remove') end }) end @@ -554,10 +566,18 @@ 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 }) @@ -570,6 +590,7 @@ function openAddManagerMenu(preciseCoords) QBCore.Functions.TriggerCallback('vending:server:getOnlinePlayers', function(players) if #players == 0 then QBCore.Functions.Notify('Keine Spieler online!', 'error') + showingMenu = false return end @@ -583,16 +604,23 @@ function openAddManagerMenu(preciseCoords) icon = 'fas fa-user', onSelect = function() TriggerServerEvent('vending:server:addManager', preciseCoords, player.id) - Wait(500) - openManagersMenu(preciseCoords) -- Refresh the menu + showingMenu = false 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 }) @@ -600,34 +628,6 @@ function openAddManagerMenu(preciseCoords) end) end --- Robbery menu -RegisterNetEvent('vending:client:startRobbery', function(data) - local entity = data.entity - local preciseCoords = getPreciseCoords(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', preciseCoords) - 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) local playerPed = PlayerPedId() @@ -667,12 +667,6 @@ 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) @@ -691,35 +685,17 @@ RegisterNetEvent('vending:client:policeAlert', function(coords, streetName) QBCore.Functions.Notify('Verkaufsautomat Aufbruch gemeldet: ' .. streetName, 'error', 8000) end) --- 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') +-- Event handlers for menu closing +RegisterNetEvent('vending:client:closeMenu', function() + showingMenu = false end) --- Debug command to check props +-- Close menu when inventory is opened +AddEventHandler('inventory:client:OpenInventory', function() + showingMenu = false +end) + +-- Debug commands RegisterCommand('checkvendingprops', function() local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) @@ -763,7 +739,6 @@ 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)