2025-07-29 07:30:32 +02:00
local QBCore = exports [ ' qb-core ' ] : GetCoreObject ( )
-- Add targets to all vending machine props
CreateThread ( function ( )
2025-07-29 08:18:21 +02:00
Wait ( 2000 )
2025-07-29 07:30:32 +02:00
exports [ ' qb-target ' ] : AddTargetModel ( Config.VendingProps , {
options = {
{
type = " client " ,
event = " vending:client:buyMachine " ,
icon = " fas fa-dollar-sign " ,
2025-07-29 08:25:12 +02:00
label = " Automaten kaufen ($ " .. Config.VendingMachinePrice .. " ) " ,
2025-07-29 09:02:52 +02:00
canInteract = function ( entity )
2025-07-29 08:25:12 +02:00
return not isRegisteredMachine ( entity )
end
2025-07-29 07:30:32 +02:00
} ,
{
type = " client " ,
event = " vending:client:openBuyMenu " ,
icon = " fas fa-shopping-cart " ,
2025-07-29 08:25:12 +02:00
label = " Kaufen " ,
2025-07-29 09:02:52 +02:00
canInteract = function ( entity )
2025-07-29 08:25:12 +02:00
return isRegisteredMachine ( entity )
end
2025-07-29 07:30:32 +02:00
} ,
{
type = " client " ,
event = " vending:client:openOwnerMenu " ,
icon = " fas fa-cog " ,
2025-07-29 08:25:12 +02:00
label = " Verwalten " ,
2025-07-29 09:02:52 +02:00
canInteract = function ( entity )
return canManageMachine ( entity )
2025-07-29 08:25:12 +02:00
end
} ,
{
type = " client " ,
event = " vending:client:startRobbery " ,
icon = " fas fa-mask " ,
label = " Aufbrechen " ,
2025-07-29 09:02:52 +02:00
canInteract = function ( entity )
return isRegisteredMachine ( entity ) and not canManageMachine ( entity )
2025-07-29 08:25:12 +02:00
end
2025-07-29 07:30:32 +02:00
}
} ,
distance = 2.0
} )
2025-07-29 08:18:21 +02:00
end )
2025-07-29 07:30:32 +02:00
2025-07-29 08:25:12 +02:00
-- 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
2025-07-29 09:02:52 +02:00
-- Check if player can manage machine
function canManageMachine ( entity )
2025-07-29 08:25:12 +02:00
local coords = GetEntityCoords ( entity )
2025-07-29 09:02:52 +02:00
local canManage = false
2025-07-29 08:25:12 +02:00
2025-07-29 09:02:52 +02:00
QBCore.Functions . TriggerCallback ( ' vending:server:canManage ' , function ( result )
canManage = result
2025-07-29 08:25:12 +02:00
end , coords )
-- Wait for callback
local timeout = 0
2025-07-29 09:02:52 +02:00
while canManage == false and timeout < 100 do
2025-07-29 08:25:12 +02:00
Wait ( 10 )
timeout = timeout + 1
end
2025-07-29 09:02:52 +02:00
return canManage
2025-07-29 08:25:12 +02:00
end
2025-07-29 07:30:32 +02:00
-- Buy vending machine
RegisterNetEvent ( ' vending:client:buyMachine ' , function ( data )
local entity = data.entity
local coords = GetEntityCoords ( entity )
local model = GetEntityModel ( entity )
local prop = nil
-- Find prop name
for i = 1 , # Config.VendingProps do
if GetHashKey ( Config.VendingProps [ i ] ) == model then
prop = Config.VendingProps [ i ]
break
end
end
2025-07-29 08:25:12 +02:00
if not prop then return end
2025-07-29 07:30:32 +02:00
lib.registerContext ( {
id = ' vending_buy_confirm ' ,
title = ' Verkaufsautomat kaufen ' ,
options = {
{
title = ' Bestätigen ' ,
description = ' Automaten für $ ' .. Config.VendingMachinePrice .. ' kaufen ' ,
icon = ' fas fa-check ' ,
onSelect = function ( )
TriggerServerEvent ( ' vending:server:registerMachine ' , coords , prop )
end
} ,
{
title = ' Abbrechen ' ,
description = ' Kauf abbrechen ' ,
icon = ' fas fa-times '
}
}
} )
lib.showContext ( ' vending_buy_confirm ' )
end )
2025-07-29 09:02:52 +02:00
-- Open buy menu with quantity selection
2025-07-29 07:30:32 +02:00
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 ' )
return
end
local options = { }
for i = 1 , # items do
local item = items [ i ]
if item.amount > 0 then
2025-07-29 07:51:42 +02:00
local itemLabel = QBCore.Shared . Items [ item.name ] and QBCore.Shared . Items [ item.name ] . label or item.name
2025-07-29 07:30:32 +02:00
table.insert ( options , {
2025-07-29 07:51:42 +02:00
title = itemLabel ,
2025-07-29 07:30:32 +02:00
description = ' Preis: $ ' .. item.price .. ' | Verfügbar: ' .. item.amount ,
2025-07-29 08:25:12 +02:00
icon = ' fas fa-shopping-cart ' ,
2025-07-29 07:30:32 +02:00
onSelect = function ( )
2025-07-29 09:02:52 +02:00
openQuantityDialog ( coords , item.name , item.price , item.amount , itemLabel )
2025-07-29 07:30:32 +02:00
end
} )
end
end
2025-07-29 08:25:12 +02:00
if # options == 0 then
QBCore.Functions . Notify ( ' Keine Artikel verfügbar! ' , ' error ' )
return
end
2025-07-29 07:30:32 +02:00
lib.registerContext ( {
id = ' vending_buy_menu ' ,
title = ' Verkaufsautomat ' ,
options = options
} )
lib.showContext ( ' vending_buy_menu ' )
end , coords )
end )
2025-07-29 09:02:52 +02:00
-- 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
2025-07-29 07:30:32 +02:00
-- Open owner menu
RegisterNetEvent ( ' vending:client:openOwnerMenu ' , function ( data )
local entity = data.entity
local coords = GetEntityCoords ( entity )
2025-07-29 08:25:12 +02:00
QBCore.Functions . TriggerCallback ( ' vending:server:getMachineByCoords ' , function ( machine )
if not machine then
QBCore.Functions . Notify ( ' Automat nicht gefunden! ' , ' error ' )
return
end
2025-07-29 09:02:52 +02:00
local 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
}
}
-- Add manager options only for owner
if machine.isOwner then
table.insert ( options , {
title = ' Verwalter ' ,
description = ' Verwalter hinzufügen/entfernen ' ,
icon = ' fas fa-users-cog ' ,
onSelect = function ( )
openManagersMenu ( coords )
end
} )
2025-07-29 09:20:46 +02:00
-- 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 ) .. ' $ ' ,
icon = ' fas fa-dollar-sign ' ,
onSelect = function ( )
sellVendingMachine ( coords , machine.id )
end
} )
2025-07-29 09:02:52 +02:00
end
2025-07-29 08:25:12 +02:00
lib.registerContext ( {
id = ' vending_owner_menu ' ,
title = ' Verkaufsautomat Verwaltung ' ,
2025-07-29 09:02:52 +02:00
options = options
2025-07-29 08:25:12 +02:00
} )
lib.showContext ( ' vending_owner_menu ' )
end , coords )
end )
2025-07-29 09:20:46 +02:00
-- Funktion zum Verkaufen des Automaten
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! ' ,
required = true
}
} )
if input and input [ 1 ] then
TriggerServerEvent ( ' vending:server:sellMachine ' , coords , machineId )
end
end
2025-07-29 08:25:12 +02:00
-- 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 )
2025-07-29 07:30:32 +02:00
lib.registerContext ( {
2025-07-29 08:25:12 +02:00
id = ' vending_stats_menu ' ,
title = ' Verkaufsstatistiken ' ,
menu = ' vending_owner_menu ' ,
2025-07-29 07:30:32 +02:00
options = {
{
2025-07-29 08:25:12 +02:00
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
2025-07-29 09:02:52 +02:00
-- Open managers menu
function openManagersMenu ( coords )
-- Get current managers
QBCore.Functions . TriggerCallback ( ' vending:server:getManagers ' , function ( managers )
local options = {
{
title = ' Verwalter hinzufügen ' ,
description = ' Neuen Verwalter hinzufügen ' ,
icon = ' fas fa-user-plus ' ,
onSelect = function ( )
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 ' or ' Offline ' ,
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 ' ,
options = {
{
title = ' Entfernen ' ,
description = ' Verwalter entfernen ' ,
icon = ' fas fa-user-minus ' ,
onSelect = function ( )
TriggerServerEvent ( ' vending:server:removeManager ' , coords , manager.citizenid )
Wait ( 500 )
openManagersMenu ( coords ) -- Refresh the menu
end
}
}
} )
lib.showContext ( ' manager_options ' )
end
} )
end
else
table.insert ( options , {
title = ' Keine Verwalter ' ,
description = ' Es sind keine Verwalter vorhanden ' ,
icon = ' fas fa-info-circle ' ,
disabled = true
} )
end
lib.registerContext ( {
id = ' managers_menu ' ,
title = ' Verwalter verwalten ' ,
menu = ' vending_owner_menu ' ,
options = options
} )
lib.showContext ( ' managers_menu ' )
end , coords )
end
-- Open add manager menu
function openAddManagerMenu ( coords )
QBCore.Functions . TriggerCallback ( ' vending:server:getOnlinePlayers ' , function ( players )
if # players == 0 then
QBCore.Functions . Notify ( ' Keine Spieler online! ' , ' error ' )
return
end
local options = { }
for i = 1 , # players do
local player = players [ i ]
table.insert ( options , {
title = player.name ,
description = ' ID: ' .. player.id ,
icon = ' fas fa-user ' ,
onSelect = function ( )
TriggerServerEvent ( ' vending:server:addManager ' , coords , player.id )
Wait ( 500 )
openManagersMenu ( coords ) -- Refresh the menu
end
} )
end
lib.registerContext ( {
id = ' add_manager_menu ' ,
title = ' Verwalter hinzufügen ' ,
menu = ' managers_menu ' ,
options = options
} )
lib.showContext ( ' add_manager_menu ' )
end )
end
-- Robbery menu
2025-07-29 08:25:12 +02:00
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 ( 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 ' ,
2025-07-29 07:30:32 +02:00
icon = ' fas fa-box ' ,
onSelect = function ( )
2025-07-29 08:25:12 +02:00
TriggerServerEvent ( ' vending:server:openStash ' , machine.coords )
2025-07-29 07:30:32 +02:00
end
} ,
{
2025-07-29 08:25:12 +02:00
title = ' Einnahmen: $ ' .. machine.money ,
description = ' Geld abheben ' ,
2025-07-29 07:30:32 +02:00
icon = ' fas fa-money-bill ' ,
onSelect = function ( )
2025-07-29 08:25:12 +02:00
openWithdrawMenu ( machine.coords , machine.money )
2025-07-29 07:30:32 +02:00
end
}
}
} )
2025-07-29 08:25:12 +02:00
lib.showContext ( ' vending_management ' )
2025-07-29 07:30:32 +02:00
end )
2025-07-29 08:25:12 +02:00
-- Debug commands
RegisterCommand ( ' vendingdebug ' , function ( )
local playerPed = PlayerPedId ( )
local coords = GetEntityCoords ( playerPed )
2025-07-29 07:30:32 +02:00
QBCore.Functions . TriggerCallback ( ' vending:server:getMachineByCoords ' , function ( machine )
2025-07-29 08:25:12 +02:00
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 ' )
2025-07-29 07:30:32 +02:00
end
end , coords )
2025-07-29 07:51:42 +02:00
end , false )
2025-07-29 09:39:15 +02:00
-- Debug command to check props
RegisterCommand ( ' checkvendingprops ' , function ( )
local playerPed = PlayerPedId ( )
local playerCoords = GetEntityCoords ( playerPed )
local foundProps = 0
for _ , propName in ipairs ( Config.VendingProps ) do
local hash = GetHashKey ( propName )
local objects = GetGamePool ( ' CObject ' )
print ( " Checking for prop: " .. propName .. " (Hash: " .. hash .. " ) " )
for _ , obj in ipairs ( objects ) do
if GetEntityModel ( obj ) == hash then
local objCoords = GetEntityCoords ( obj )
local dist = # ( playerCoords - objCoords )
if dist < 30.0 then
foundProps = foundProps + 1
print ( " Found " .. propName .. " at distance: " .. dist )
-- Add a temporary blip
local blip = AddBlipForEntity ( obj )
SetBlipSprite ( blip , 1 )
SetBlipColour ( blip , 2 )
SetBlipScale ( blip , 0.8 )
BeginTextCommandSetBlipName ( " STRING " )
AddTextComponentString ( propName )
EndTextCommandSetBlipName ( blip )
-- Remove blip after 10 seconds
SetTimeout ( 10000 , function ( )
RemoveBlip ( blip )
end )
end
end
end
end
QBCore.Functions . Notify ( ' Found ' .. foundProps .. ' vending machines nearby ' , ' primary ' )
end , false )
2025-07-29 09:41:19 +02:00
-- Command to manually refresh targets
RegisterCommand ( ' refreshvendingtargets ' , function ( )
exports [ ' qb-target ' ] : RemoveTargetModel ( Config.VendingProps )
Wait ( 500 )
exports [ ' qb-target ' ] : AddTargetModel ( Config.VendingProps , {
options = {
{
type = " client " ,
event = " vending:client:buyMachine " ,
icon = " fas fa-dollar-sign " ,
label = " Automaten kaufen ($ " .. Config.VendingMachinePrice .. " ) "
} ,
{
type = " client " ,
event = " vending:client:openBuyMenu " ,
icon = " fas fa-shopping-cart " ,
label = " Kaufen "
} ,
{
type = " client " ,
event = " vending:client:openOwnerMenu " ,
icon = " fas fa-cog " ,
label = " Verwalten "
} ,
{
type = " client " ,
event = " vending:client:startRobbery " ,
icon = " fas fa-mask " ,
label = " Aufbrechen "
}
} ,
distance = 2.0
} )
QBCore.Functions . Notify ( ' Vending machine targets refreshed ' , ' success ' )
end , false )