diff --git a/resources/[inventory]/nordi_vending/client.lua b/resources/[inventory]/nordi_vending/client.lua index 2debe6cf5..f59530e91 100644 --- a/resources/[inventory]/nordi_vending/client.lua +++ b/resources/[inventory]/nordi_vending/client.lua @@ -3,7 +3,6 @@ local QBCore = exports['qb-core']:GetCoreObject() -- Add targets to all vending machine props CreateThread(function() Wait(2000) - print('[Vending] Adding targets to vending machine props') exports['qb-target']:AddTargetModel(Config.VendingProps, { options = { @@ -11,27 +10,81 @@ CreateThread(function() type = "client", event = "vending:client:buyMachine", icon = "fas fa-dollar-sign", - label = "Automaten kaufen ($" .. Config.VendingMachinePrice .. ")" + label = "Automaten kaufen ($" .. Config.VendingMachinePrice .. ")", + canInteract = function(entity, distance, data) + return not isRegisteredMachine(entity) + end }, { type = "client", event = "vending:client:openBuyMenu", icon = "fas fa-shopping-cart", - label = "Kaufen" + label = "Kaufen", + canInteract = function(entity, distance, data) + return isRegisteredMachine(entity) + end }, { type = "client", event = "vending:client:openOwnerMenu", icon = "fas fa-cog", - label = "Verwalten" + label = "Verwalten", + canInteract = function(entity, distance, data) + return isOwnerOfMachine(entity) + end + }, + { + type = "client", + event = "vending:client:startRobbery", + icon = "fas fa-mask", + label = "Aufbrechen", + canInteract = function(entity, distance, data) + return isRegisteredMachine(entity) and not isOwnerOfMachine(entity) + end } }, distance = 2.0 }) - - print('[Vending] Targets added successfully') end) +-- Check if machine is registered +function isRegisteredMachine(entity) + local coords = GetEntityCoords(entity) + local isRegistered = false + + QBCore.Functions.TriggerCallback('vending:server:machineExists', function(exists) + 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 + +-- Check if player owns machine +function isOwnerOfMachine(entity) + local coords = GetEntityCoords(entity) + local isOwner = false + + QBCore.Functions.TriggerCallback('vending:server:isOwner', function(owner) + isOwner = owner + end, coords) + + -- Wait for callback + local timeout = 0 + while isOwner == false and timeout < 100 do + Wait(10) + timeout = timeout + 1 + end + + return isOwner +end + -- Buy vending machine RegisterNetEvent('vending:client:buyMachine', function(data) local entity = data.entity @@ -39,8 +92,6 @@ RegisterNetEvent('vending:client:buyMachine', function(data) local model = GetEntityModel(entity) local prop = nil - print('[Vending] Trying to buy machine at coords:', coords.x, coords.y, coords.z) - -- Find prop name for i = 1, #Config.VendingProps do if GetHashKey(Config.VendingProps[i]) == model then @@ -49,12 +100,7 @@ RegisterNetEvent('vending:client:buyMachine', function(data) end end - if not prop then - print('[Vending] Prop not found in config') - return - end - - print('[Vending] Found prop:', prop) + if not prop then return end lib.registerContext({ id = 'vending_buy_confirm', @@ -65,7 +111,6 @@ RegisterNetEvent('vending:client:buyMachine', function(data) description = 'Automaten für $' .. Config.VendingMachinePrice .. ' kaufen', icon = 'fas fa-check', onSelect = function() - print('[Vending] Confirming purchase') TriggerServerEvent('vending:server:registerMachine', coords, prop) end }, @@ -100,6 +145,7 @@ RegisterNetEvent('vending:client:openBuyMenu', function(data) table.insert(options, { title = itemLabel, description = 'Preis: $' .. item.price .. ' | Verfügbar: ' .. item.amount, + icon = 'fas fa-shopping-cart', onSelect = function() TriggerServerEvent('vending:server:buyItem', coords, item.name) end @@ -107,6 +153,11 @@ RegisterNetEvent('vending:client:openBuyMenu', function(data) end end + if #options == 0 then + QBCore.Functions.Notify('Keine Artikel verfügbar!', 'error') + return + end + lib.registerContext({ id = 'vending_buy_menu', title = 'Verkaufsautomat', @@ -122,64 +173,295 @@ 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') + return + end + + lib.registerContext({ + id = 'vending_owner_menu', + title = 'Verkaufsautomat Verwaltung', + options = { + { + title = 'Inventar verwalten', + description = 'Items hinzufügen/entfernen', + icon = 'fas fa-box', + onSelect = function() + TriggerServerEvent('vending:server:openStash', coords) + end + }, + { + title = 'Preise festlegen', + description = 'Verkaufspreise für Items setzen', + icon = 'fas fa-tags', + onSelect = function() + openPriceMenu(coords) + end + }, + { + title = 'Geld abheben', + description = 'Verfügbar: $' .. machine.money, + icon = 'fas fa-money-bill', + onSelect = function() + openWithdrawMenu(coords, machine.money) + end + }, + { + title = 'Statistiken', + description = 'Verkaufsstatistiken anzeigen', + icon = 'fas fa-chart-bar', + onSelect = function() + openStatsMenu(machine) + end + } + } + }) + + lib.showContext('vending_owner_menu') + end, coords) +end) + +-- Open price menu +function openPriceMenu(coords) + QBCore.Functions.TriggerCallback('vending:server:getStashItems', function(items) + if #items == 0 then + QBCore.Functions.Notify('Keine Items im Automaten!', 'error') + return + end + + local options = {} + + for i = 1, #items do + local item = items[i] + local itemLabel = QBCore.Shared.Items[item.name] and QBCore.Shared.Items[item.name].label or item.name + table.insert(options, { + title = itemLabel, + description = 'Aktueller Preis: $' .. item.price, + icon = 'fas fa-tag', + onSelect = function() + setPriceForItem(coords, item.name, itemLabel) + end + }) + end + + lib.registerContext({ + id = 'vending_price_menu', + title = 'Preise festlegen', + menu = 'vending_owner_menu', + options = options + }) + + lib.showContext('vending_price_menu') + end, coords) +end + +-- Set price for specific item +function setPriceForItem(coords, itemName, itemLabel) + local input = lib.inputDialog('Preis festlegen', { + { + type = 'number', + label = 'Preis für ' .. itemLabel, + description = 'Neuen Verkaufspreis eingeben', + required = true, + min = 1, + max = 10000 + } + }) + + if input and input[1] then + TriggerServerEvent('vending:server:setItemPrice', coords, itemName, tonumber(input[1])) + end +end + +-- Open withdraw menu +function openWithdrawMenu(coords, availableMoney) + if availableMoney <= 0 then + QBCore.Functions.Notify('Kein Geld im Automaten!', 'error') + return + end + + local input = lib.inputDialog('Geld abheben', { + { + type = 'number', + label = 'Betrag (Verfügbar: $' .. availableMoney .. ')', + description = 'Wie viel möchtest du abheben?', + required = true, + min = 1, + max = availableMoney + } + }) + + if input and input[1] then + TriggerServerEvent('vending:server:withdrawMoney', coords, tonumber(input[1])) + end +end + +-- Open stats menu +function openStatsMenu(machine) lib.registerContext({ - id = 'vending_owner_menu', - title = 'Verkaufsautomat Verwaltung', + id = 'vending_stats_menu', + title = 'Verkaufsstatistiken', + menu = 'vending_owner_menu', options = { { - title = 'Inventar verwalten', - description = 'Items hinzufügen/entfernen', - icon = 'fas fa-box', + title = 'Gesamteinnahmen', + description = '$' .. machine.money, + icon = 'fas fa-dollar-sign' + }, + { + title = 'Automat ID', + description = '#' .. machine.id, + icon = 'fas fa-hashtag' + }, + { + title = 'Standort', + description = 'X: ' .. math.floor(machine.coords.x) .. ' Y: ' .. math.floor(machine.coords.y), + icon = 'fas fa-map-marker-alt' + } + } + }) + + lib.showContext('vending_stats_menu') +end + +-- Start robbery +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:openStash', coords) + TriggerServerEvent('vending:server:startRobbery', coords) end }, { - title = 'Geld abheben', - description = 'Einnahmen auszahlen lassen', + 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(coords) + local playerPed = PlayerPedId() + local robberyTime = 10000 -- 10 seconds + + -- Animation + RequestAnimDict('anim@heists@fleeca_bank@drilling') + while not HasAnimDictLoaded('anim@heists@fleeca_bank@drilling') do + Wait(100) + end + + TaskPlayAnim(playerPed, 'anim@heists@fleeca_bank@drilling', 'drill_straight_idle', 8.0, -8.0, -1, 1, 0, false, false, false) + + -- Progress bar + if lib.progressBar then + local success = lib.progressBar({ + duration = robberyTime, + label = 'Automat aufbrechen...', + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true + } + }) + + ClearPedTasks(playerPed) + TriggerServerEvent('vending:server:completeRobbery', coords, success) + else + -- Fallback without progress bar + Wait(robberyTime) + ClearPedTasks(playerPed) + 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) + SetBlipColour(blip, 1) + SetBlipScale(blip, 1.0) + SetBlipAsShortRange(blip, false) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Verkaufsautomat Aufbruch") + EndTextCommandSetBlipName(blip) + + -- Remove blip after 5 minutes + SetTimeout(300000, function() + RemoveBlip(blip) + end) + + QBCore.Functions.Notify('Verkaufsautomat Aufbruch gemeldet: ' .. streetName, 'error', 8000) +end) + +-- Refresh targets (called when new machine is registered) +RegisterNetEvent('vending:client:refreshTargets', function() + -- Targets are automatically updated by qb-target + -- This event can be used for additional refresh logic if needed +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(coords) + openWithdrawMenu(machine.coords, machine.money) end } } }) - lib.showContext('vending_owner_menu') + lib.showContext('vending_management') end) --- Open withdraw menu -function openWithdrawMenu(coords) +-- Debug commands +RegisterCommand('vendingdebug', function() + local playerPed = PlayerPedId() + local coords = GetEntityCoords(playerPed) + QBCore.Functions.TriggerCallback('vending:server:getMachineByCoords', function(machine) - if not machine then - QBCore.Functions.Notify('Automat nicht gefunden!', 'error') - return - end - - if machine.money <= 0 then - QBCore.Functions.Notify('Kein Geld im Automaten!', 'error') - return - end - - local input = lib.inputDialog('Geld abheben', { - { - type = 'number', - label = 'Betrag (Verfügbar: $' .. machine.money .. ')', - description = 'Wie viel möchtest du abheben?', - required = true, - min = 1, - max = machine.money - } - }) - - if input and input[1] then - TriggerServerEvent('vending:server:withdrawMoney', coords, tonumber(input[1])) + 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, coords) -end - --- Debug command -RegisterCommand('vendingtest', function() - print('[Vending] Testing vending machine system') - QBCore.Functions.Notify('Vending system test', 'primary') end, false) diff --git a/resources/[inventory]/nordi_vending/server.lua b/resources/[inventory]/nordi_vending/server.lua index 86076028d..352463ace 100644 --- a/resources/[inventory]/nordi_vending/server.lua +++ b/resources/[inventory]/nordi_vending/server.lua @@ -4,7 +4,6 @@ local robberyInProgress = {} -- Load vending machines from database CreateThread(function() - Wait(1000) local result = MySQL.Sync.fetchAll('SELECT * FROM vending_machines') if result then for i = 1, #result do @@ -20,7 +19,6 @@ CreateThread(function() stash = 'vending_' .. data.id } end - print('[Vending] Loaded ' .. #result .. ' vending machines') end end) @@ -30,8 +28,6 @@ RegisterNetEvent('vending:server:registerMachine', function(coords, prop) local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - print('[Vending] Player ' .. src .. ' trying to register machine at coords:', coords.x, coords.y, coords.z) - -- Check if there's already a machine at these 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)) @@ -72,8 +68,6 @@ RegisterNetEvent('vending:server:registerMachine', function(coords, prop) stash = 'vending_' .. machineId } - print('[Vending] Machine registered with ID:', machineId) - TriggerClientEvent('QBCore:Notify', src, 'Verkaufsautomat erfolgreich gekauft für $' .. Config.VendingMachinePrice .. '!', 'success') TriggerClientEvent('vending:client:refreshTargets', -1) end) @@ -96,7 +90,7 @@ RegisterNetEvent('vending:server:openManagement', function(coords) TriggerClientEvent('vending:client:openManagement', src, machine) end) --- Open stash (simplified version) +-- Open stash (korrekt für tgiann-inventory) RegisterNetEvent('vending:server:openStash', function(coords) local src = source local Player = QBCore.Functions.GetPlayer(src) @@ -111,30 +105,13 @@ RegisterNetEvent('vending:server:openStash', function(coords) return end - -- Try different inventory systems - if GetResourceState('tgiann-inventory') == 'started' then - -- tgiann-inventory - TriggerEvent('tgiann-inventory:server:openStash', src, machine.stash, { - maxweight = Config.MaxWeight, - slots = Config.MaxSlots, - label = 'Vending Machine #' .. machine.id - }) - elseif GetResourceState('qb-inventory') == 'started' then - -- qb-inventory - TriggerEvent('inventory:server:OpenInventory', 'stash', machine.stash, { - maxweight = Config.MaxWeight, - slots = Config.MaxSlots, - }) - TriggerClientEvent('inventory:client:SetCurrentStash', src, machine.stash) - elseif GetResourceState('ps-inventory') == 'started' then - -- ps-inventory - exports['ps-inventory']:OpenInventory(src, machine.stash, { - maxweight = Config.MaxWeight, - slots = Config.MaxSlots, - }) - else - TriggerClientEvent('QBCore:Notify', src, 'Kein unterstütztes Inventory-System gefunden!', 'error') - end + -- Korrekte tgiann-inventory Methode + TriggerClientEvent('tgiann-inventory:client:openStash', src, { + stashId = machine.stash, + stashLabel = 'Vending Machine #' .. machine.id, + maxweight = Config.MaxWeight, + slots = Config.MaxSlots + }) end) -- Set item price @@ -156,8 +133,7 @@ RegisterNetEvent('vending:server:setItemPrice', function(coords, itemName, price machine.prices[itemName] = price MySQL.update('UPDATE vending_machines SET prices = ? WHERE id = ?', {json.encode(machine.prices), machineId}) - local itemLabel = QBCore.Shared.Items[itemName] and QBCore.Shared.Items[itemName].label or itemName - TriggerClientEvent('QBCore:Notify', src, 'Preis für ' .. itemLabel .. ' auf $' .. price .. ' gesetzt!', 'success') + TriggerClientEvent('QBCore:Notify', src, 'Preis für ' .. (QBCore.Shared.Items[itemName] and QBCore.Shared.Items[itemName].label or itemName) .. ' auf $' .. price .. ' gesetzt!', 'success') end) -- Withdraw money @@ -189,7 +165,7 @@ RegisterNetEvent('vending:server:withdrawMoney', function(coords, amount) TriggerClientEvent('QBCore:Notify', src, 'Du hast $' .. amount .. ' abgehoben!', 'success') end) --- Buy item from vending machine (simplified) +-- Buy item from vending machine RegisterNetEvent('vending:server:buyItem', function(coords, itemName) local src = source local Player = QBCore.Functions.GetPlayer(src) @@ -207,7 +183,24 @@ RegisterNetEvent('vending:server:buyItem', function(coords, itemName) return end - -- For now, just simulate the purchase (you can add inventory checks later) + -- Get stash items using tgiann-inventory + local stashItems = exports['tgiann-inventory']:getStashItems(machine.stash) + local hasItem = false + + if stashItems then + for slot, item in pairs(stashItems) do + if item.name == itemName and item.amount > 0 then + hasItem = true + break + end + end + end + + if not hasItem then + TriggerClientEvent('QBCore:Notify', src, 'Artikel nicht verfügbar!', 'error') + return + end + -- Remove money from player Player.Functions.RemoveMoney('cash', price) @@ -215,12 +208,92 @@ RegisterNetEvent('vending:server:buyItem', function(coords, itemName) machine.money = machine.money + price MySQL.update('UPDATE vending_machines SET money = ? WHERE id = ?', {machine.money, machineId}) - -- Add item to player - Player.Functions.AddItem(itemName, 1) + -- Remove item from stash and add to player + exports['tgiann-inventory']:removeItemFromStash(machine.stash, itemName, 1) + exports['tgiann-inventory']:addItem(src, itemName, 1) TriggerClientEvent('QBCore:Notify', src, 'Artikel gekauft für $' .. price .. '!', 'success') end) +-- 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 = exports['tgiann-inventory']:getItemByName(src, 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 + + 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 + exports['tgiann-inventory']:removeItem(src, Config.RobberyItem, 1) + 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 @@ -242,7 +315,7 @@ QBCore.Functions.CreateCallback('vending:server:getMachineByCoords', function(so end end) --- Get stash items for vending machine menu (simplified) +-- 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 @@ -251,13 +324,17 @@ QBCore.Functions.CreateCallback('vending:server:getStashItems', function(source, end local machine = vendingMachines[machineId] + local stashItems = exports['tgiann-inventory']:getStashItems(machine.stash) + local items = {} - -- Return some dummy items for testing - local items = { - {name = 'water_bottle', amount = 10, price = machine.prices['water_bottle'] or Config.DefaultPrice}, - {name = 'sandwich', amount = 5, price = machine.prices['sandwich'] or Config.DefaultPrice}, - {name = 'coffee', amount = 8, price = machine.prices['coffee'] or Config.DefaultPrice} - } + 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) @@ -285,13 +362,3 @@ QBCore.Functions.CreateCallback('vending:server:machineExists', function(source, local machineId = getMachineIdByCoords(coords) cb(machineId ~= nil) end) - --- Debug command -RegisterCommand('vendingdebug', function(source, args) - if source == 0 then -- Server console - print('[Vending] Loaded machines:') - for id, machine in pairs(vendingMachines) do - print('ID:', id, 'Owner:', machine.owner, 'Money:', machine.money) - end - end -end, true)