FiveM-Automaten-Skript: Essen, Getränke und mehr
Ergänze deinen FiveM-Server um funktionierende Automaten mit Snacks, Getränken und eigenen Items. Platziere Props und installiere Top-Skripte in wenigen Minuten.
Agency Scripts
Gründer und leitender Entwickler bei Agency Scripts
Warum Verkaufsautomaten deinem Server mehr Tiefe verleihen
Verkaufsautomaten und automatisierte Shops spielen eine wichtige Rolle in jeder Roleplay-Wirtschaft. Sie bieten rund um die Uhr Zugang zu wichtigen Artikeln wie Essen, Wasser, Zigaretten und Grundbedarf, ohne dass ein Ladenmitarbeiter online sein muss. Neben dem Komfort werden sie zu natürlichen Treffpunkten, an denen sich Spieler versammeln und spontane Roleplay-Begegnungen an Tankstellen, Krankenhäusern und Bürogebäuden entstehen. Ein gut umgesetztes Verkaufsautomatensystem geht weit über ein einfaches Kaufmenü hinaus. Es umfasst realistische Kaufanimationen, eine Lagerverwaltung, die Knappheit erzeugt, dynamische Preise, die auf Angebot und Nachfrage reagieren, verschiedene Automatentypen mit unterschiedlichen Produktkategorien, Überfallmechaniken für kriminelles Roleplay und Auffülljobs, die Spielern sinnvolle Beschäftigung bieten. Dieses Tutorial behandelt den Aufbau eines vollständigen Automaten- und Shopsystems, von der Prop-Erkennung über die Inventarintegration bis zur Wirtschaftsverwaltung.
Verkaufsautomaten-Props erkennen und konfigurieren
Die Welt von GTA V ist voller Verkaufsautomaten-Props, die du in interaktive Objekte verwandeln kannst. Zu den wichtigsten Prop-Modellen gehören prop_vend_soda_01 und prop_vend_soda_02 für Getränkeautomaten, prop_vend_water_01 für Wasserspender, prop_vend_coffe_01 für Kaffeemaschinen und prop_vend_snak_01 für Snackautomaten. Anstatt an jedem Automatenstandort auf der Karte manuell Interaktionspunkte zu platzieren, solltest du ein Erkennungssystem mit Radius verwenden, das in der Nähe des Spielers nach diesen Prop-Modellen sucht. Nähert sich ein Spieler einem passenden Prop innerhalb der Interaktionsreichweite, aktiviert das System eine Aufforderung oder Target-Zone. Dieser Ansatz funktioniert automatisch mit allen Exemplaren dieser Props auf der gesamten Karte und erkennt sogar benutzerdefinierte MLO-Innenräume, in denen diese Modelle vorkommen. Definiere für jeden Prop-Typ unterschiedliche Produktkataloge, damit Getränkeautomaten nur Getränke, Snackautomaten Lebensmittel und Kaffeemaschinen verschiedene Kaffeesorten verkaufen.
-- shared/config.lua
Config = {}
Config.InteractDistance = 1.5
Config.UseAnimation = true
Config.AnimDuration = 3000 -- ms
Config.Machines = {
soda = {
models = {
`prop_vend_soda_01`,
`prop_vend_soda_02`,
},
label = 'Soda Machine',
items = {
{name = 'cola', label = 'Cola', price = 5, stock = 20},
{name = 'sprunk', label = 'Sprunk', price = 5, stock = 20},
{name = 'ecola', label = 'E-Cola', price = 6, stock = 15},
{name = 'energydrink',label = 'Energy Drink', price = 8, stock = 10},
},
},
snack = {
models = {`prop_vend_snak_01`},
label = 'Snack Machine',
items = {
{name = 'chips', label = 'Chips', price = 4, stock = 25},
{name = 'candy', label = 'Candy Bar', price = 3, stock = 30},
{name = 'sandwich', label = 'Sandwich', price = 7, stock = 15},
{name = 'granola', label = 'Granola Bar', price = 5, stock = 20},
},
},
coffee = {
models = {`prop_vend_coffe_01`},
label = 'Coffee Machine',
items = {
{name = 'coffee', label = 'Black Coffee', price = 4, stock = 30},
{name = 'latte', label = 'Latte', price = 6, stock = 20},
{name = 'espresso', label = 'Espresso', price = 5, stock = 25},
},
},
water = {
models = {`prop_vend_water_01`},
label = 'Water Cooler',
items = {
{name = 'water', label = 'Water Bottle', price = 3, stock = 40},
},
},
}
Clientseitige Interaktions- und Animationslogik
Wenn sich ein Spieler einem Verkaufsautomaten nähert und die Interaktionstaste drückt, laufen mehrere Schritte nacheinander ab, um ein immersives Kauferlebnis zu schaffen. Zuerst wird die Bewegung des Spielers eingeschränkt, damit er während der Animation nicht weggehen kann. Anschließend dreht sich der Charakter mit TaskTurnPedToFaceEntityzum Automaten. Eine Kaufanimation wird mit dem MINI@SPRUNK Animations-Dictionary für Getränkeautomaten oder ANIM@AM_HOLD_UP@MALE für allgemeine Interaktionen abgespielt. Der Automat spielt mit PlaySoundFromEntity einen Soundeffekt ab, der das mechanische Ausgeben simuliert. Nach Abschluss der Animation sendet der Client eine Kaufanfrage an den Server, der die Transaktion überprüft und den Gegenstand zum Inventar des Spielers hinzufügt. Falls die Interaktion einen Münzeinwurf umfasst, füge dem Spieler während der Animation mit AttachEntityToEntity ein kleines Prop an der Hand hinzu, um die Darstellung realistischer zu machen. Die gesamte Sequenz sollte etwa 3 Sekunden dauern, lang genug für ein realistisches Gefühl, aber kurz genug, um Spieler nicht zu frustrieren.
-- client/interaction.lua
local isBuying = false
function PurchaseFromMachine(machineEntity, machineType, itemIndex)
if isBuying then return end
isBuying = true
local ped = PlayerPedId()
local machineCoords = GetEntityCoords(machineEntity)
-- Face the machine
TaskTurnPedToFaceEntity(ped, machineEntity, 1000)
Wait(1000)
-- Play animation
if Config.UseAnimation then
RequestAnimDict('mini@sprunk')
while not HasAnimDictLoaded('mini@sprunk') do Wait(10) end
TaskPlayAnim(ped, 'mini@sprunk', 'plyr_buy_drink_pt1',
8.0, -8.0, 2000, 0, 0, false, false, false)
Wait(1500)
TaskPlayAnim(ped, 'mini@sprunk', 'plyr_buy_drink_pt2',
8.0, -8.0, 1500, 0, 0, false, false, false)
-- Machine sound effect
PlaySoundFromEntity(-1, 'vending_machine_purchase',
machineEntity, 'dlc_vw_table_games', false, 0)
Wait(1500)
end
-- Request purchase from server
TriggerServerEvent('vendingmachine:purchase', machineType, itemIndex)
ClearPedTasks(ped)
isBuying = false
end
Serverseitige Lagerverwaltung und Wirtschaft
Die Lagerverwaltung macht Verkaufsautomaten zu dynamischen Wirtschaftselementen statt zu unbegrenzten Itemspendern. Jeder Automatenstandort verwaltet seinen Bestand unabhängig. Wenn ein bestimmter Artikel in einem Automaten ausverkauft ist, wird er den Spielern im Kaufmenü als nicht verfügbar angezeigt. Die Lagerbestände bleiben auch nach Serverneustarts in der Datenbank erhalten. Mit einem Auffülljob können Spieler als Lieferfahrer arbeiten, Vorräte aus einem Lager abholen und sie zu leeren Automaten in der Stadt bringen. So entsteht ein kompletter Wirtschaftskreislauf: Lieferanten füllen Automaten auf, Automaten verkaufen an Kunden, Einnahmen fließen an den Automatenbesitzer zurück und der Lieferfahrer verdient Lohn. Dynamische Preise schaffen eine weitere Ebene: Beliebte Artikel werden schrittweise teurer, wenn der Bestand sinkt, und die Preise werden nach dem Auffüllen des Automaten zurückgesetzt. Das gleicht den Verbrauch auf natürliche Weise aus und erzeugt Dringlichkeit, wenn ein begehrter Artikel knapp wird.
-- server/stock.lua
local machineStock = {}
function GetMachineKey(machineType, coords)
return machineType .. ':' ..
math.floor(coords.x) .. ':' ..
math.floor(coords.y) .. ':' ..
math.floor(coords.z)
end
function GetStock(machineKey, itemIndex)
if not machineStock[machineKey] then
-- Load from database or initialize defaults
machineStock[machineKey] = {}
end
return machineStock[machineKey][itemIndex] or
Config.Machines[machineKey:match('^(%w+):')].items[itemIndex].stock
end
RegisterNetEvent('vendingmachine:purchase', function(machineType, itemIndex)
local src = source
local config = Config.Machines[machineType]
if not config or not config.items[itemIndex] then return end
local item = config.items[itemIndex]
local machineKey = machineType -- simplified; use coords in production
-- Check stock
local stock = GetStock(machineKey, itemIndex)
if stock <= 0 then
TriggerClientEvent('ox_lib:notify', src, {
title = 'Sold Out',
description = item.label .. ' is out of stock',
type = 'error'
})
return
end
-- Calculate dynamic price
local basePrice = item.price
local stockRatio = stock / item.stock
local dynamicPrice = math.ceil(basePrice * (1 + (1 - stockRatio) * 0.5))
-- Deduct money
local paid = exports['framework']:RemoveMoney(src, dynamicPrice, 'cash')
if not paid then
TriggerClientEvent('ox_lib:notify', src, {
title = 'No Cash',
description = 'You need $' .. dynamicPrice,
type = 'error'
})
return
end
-- Add item to inventory
local added = exports['ox_inventory']:AddItem(src, item.name, 1)
if not added then
exports['framework']:AddMoney(src, dynamicPrice, 'cash')
return
end
-- Decrease stock
machineStock[machineKey] = machineStock[machineKey] or {}
machineStock[machineKey][itemIndex] = stock - 1
TriggerClientEvent('ox_lib:notify', src, {
title = 'Purchased',
description = item.label .. ' - $' .. dynamicPrice,
type = 'success'
})
end)
Überfallmechaniken für kriminelles Roleplay
Verkaufsautomaten sind ein naheliegendes Überfallziel für kriminelle Charaktere. Implementiere eine Mechanik mit Dietrich oder Brecheisen, mit der Kriminelle Automaten aufbrechen und das darin befindliche Bargeld stehlen können. Der Überfall sollte Zeit in Anspruch nehmen, ein Minispiel wie ein Schlossknackrätsel erfordern, Lärm erzeugen, der Spieler in der Nähe alarmiert und möglicherweise einen Polizeieinsatz auslöst, und eine variable Belohnung abhängig davon bieten, wie viele Käufe seit dem letzten Auffüllen oder Überfall über den Automaten abgewickelt wurden. Erfasse die Bargeldsumme pro Automat anhand tatsächlicher Spielerkäufe, damit die Belohnung realistisch ist und auf echter Wirtschaftsaktivität basiert. Nach einem erfolgreichen Überfall ist der Automat beschädigt und kann erst wieder Käufe abwickeln, wenn ein Mechaniker ihn repariert oder eine Abklingzeit abgelaufen ist. Dadurch hat kriminelles Verhalten Konsequenzen für die gesamte Community und es entsteht zusätzliche Arbeit für Mechaniker, was die Wirtschaft weiter bereichert.
Spielereigene Shops und individuelle Ladenflächen
Erweitere das Verkaufsautomaten-Framework zu einem vollständigen Kassensystem für spielereigene Unternehmen. Geschäftsinhaber können auf ihrem Grundstück individuelle Verkaufstheken platzieren, konfigurieren, welche Artikel sie verkaufen, eigene Preise festlegen und ihren Bestand über eine Verwaltungs-NUI verwalten. Das Shopsystem nutzt dieselben grundlegenden Kauf- und Lagermechaniken wie Verkaufsautomaten, ergänzt sie aber um eine Besitzerebene mit Einnahmenübersicht, Ausgabenverwaltung, Mitarbeiterberechtigungen und Gewinnberichten. Mitarbeiter, die im Betrieb eingestempelt sind, können die Kasse für manuelle Verkäufe und Roleplay-Interaktionen nutzen, während das automatisierte System Verkäufe übernimmt, wenn niemand vor Ort ist. Einnahmen aus automatisierten Verkäufen fließen abzüglich eines konfigurierbaren Steueranteils, der in die Staatskasse des Servers geht, auf das Geschäftskonto. So entsteht eine vollständige Handelswirtschaft, in der Spieler Waren herstellen oder beschaffen, ihre Läden bestücken, mit anderen Unternehmen um Preise konkurrieren und mit ihrer Investition passives Einkommen erzielen.
Performanceoptimierung und Target-Integration
Die Prop-Modelle in jedem Frame zu durchsuchen, würde die Serverperformance ruinieren. Verwende stattdessen ein mehrstufiges Erkennungssystem. Führe alle 2 Sekunden eine umfassende Suche in einem Radius von 15 Metern durch, um Verkaufsautomaten-Props in der Nähe zu finden. Speichere gefundene Automatenstandorte zwischen und überprüfe in den folgenden Frames nur noch die genaue Entfernung zu den zwischengespeicherten Standorten. Bewegt sich der Spieler mehr als 20 Meter von allen zwischengespeicherten Automaten entfernt, leere den Cache und starte eine neue Suche. Auf Servern mit Target-Systemen wie ox_target oder qb-target solltest du für erkannte Props Target-Zonen registrieren, anstatt Nähe-Aufforderungen zu verwenden. Target-Zonen sind performanter, weil sie nur aktiviert werden, wenn der Spieler auf das Objekt zielt. Dadurch entfällt die kontinuierliche Entfernungsprüfung vollständig. Außerdem bietet der Target-Ansatz eine übersichtlichere UI mit kontextabhängigen Interaktionsoptionen, die direkt am Automatenmodell statt als schwebender Text angezeigt werden.
-- client/target.lua (ox_target integration)
local registeredMachines = {}
CreateThread(function()
for machineType, config in pairs(Config.Machines) do
for _, model in ipairs(config.models) do
exports.ox_target:addModel(model, {
{
name = 'vending_' .. machineType,
icon = 'fas fa-shopping-cart',
label = 'Use ' .. config.label,
onSelect = function(data)
OpenMachineMenu(machineType, data.entity)
end,
distance = Config.InteractDistance,
},
})
end
end
end)