Skrypt FiveM na automaty: Jedzenie, napoje i więcej
Dodaj działające automaty z przekąskami do swojego serwera FiveM. Przekąski, napoje, customowe przedmioty, stawianie propów i najlepsze skrypty automatów do zainstalowania w kilka minut.
Agency Scripts
Założyciel i główny deweloper w Agency Scripts
Dlaczego automaty vendingowe dodają głębi Twojemu serwerowi
Automaty z przekąskami i zautomatyzowane systemy sklepowe odgrywają kluczową rolę w każdej gospodarce roleplay. Zapewniają całodobowy dostęp do niezbędnych przedmiotów, takich jak jedzenie, woda, papierosy i podstawowe zapasy, bez konieczności obecności sprzedawcy na serwerze. Poza wygodą tworzą naturalne punkty spotkań graczy, prowadząc do spontanicznych akcji roleplay na stacjach benzynowych, w szpitalach i biurowcach. Dobrze wdrożony system automatów wykracza daleko poza proste menu zakupu. Obejmuje realistyczne animacje kupowania, zarządzanie zapasami tworzące deficyt towarów, dynamiczne ceny reagujące na podaż i popyt, różne typy automatów sprzedające odmienne kategorie produktów, mechanikę napadów dla przestępczego RP oraz zlecenia uzupełniania towaru dające graczom sensowne zatrudnienie. W tym poradniku omawiamy budowę kompletnego systemu automatów i sklepów: od wykrywania propów, przez integrację z ekwipunkiem, po zarządzanie ekonomią.
Wykrywanie i konfigurowanie propów automatów
Świat GTA V jest pełen propów automatów z przekąskami i napojami, które możesz przekształcić w interaktywne obiekty. Kluczowe modele propów obejmują prop_vend_soda_01 i prop_vend_soda_02 do automatów z napojami, prop_vend_water_01 do dystrybutorów wody, prop_vend_coffe_01 dla ekspresów do kawy, a prop_vend_snak_01 do automatów z przekąskami. Zamiast ręcznie umieszczać punkty interakcji przy każdym automacie na mapie, użyj systemu wykrywania opartego na promieniu, który skanuje otoczenie gracza pod kątem tych modeli propów. Gdy gracz zbliży się do pasującego propa w zasięgu interakcji, system aktywuje prompt lub strefę target. Takie podejście działa automatycznie z każdą instancją tych propów na całej mapie bez ręcznej konfiguracji, a nawet wykrywa niestandardowe wnętrza MLO zawierające te modele. Zdefiniuj różne katalogi produktów dla każdego typu propa, aby automaty z napojami sprzedawały tylko napoje, automaty z przekąskami jedzenie, a ekspresy do kawy warianty kawy.
-- 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},
},
},
}
System interakcji i animacji po stronie klienta
Gdy gracz podchodzi do automatu z przekąskami i naciska klawisz interakcji, po kolei następuje kilka zdarzeń tworzących realistyczne wrażenia z zakupu. Najpierw ruch gracza zostaje zablokowany, aby uniemożliwić odejście w trakcie animacji. Następnie postać gracza obraca się przodem do automatu za pomocą TaskTurnPedToFaceEntity. Odtwarzana jest animacja zakupu przy użyciu MINI@SPRUNK słownik animacji dla automatów z napojami lub ANIM@AM_HOLD_UP@MALE do ogólnych interakcji. Sama maszyna odtwarza efekt dźwiękowy przy użyciu PlaySoundFromEntity aby zasymulować mechaniczny dźwięk wydawania towaru. Po zakończeniu animacji klient wysyła żądanie zakupu do serwera, który weryfikuje transakcję i dodaje przedmiot do ekwipunku gracza. Jeśli automat posiada interakcję z wrzucaniem monet, dodaj mały prop przyczepiany do dłoni gracza podczas animacji za pomocą AttachEntityToEntity dla dodatkowego realizmu wizualnego. Cała sekwencja powinna trwać około 3 sekund: wystarczająco długo, by wyglądać realistycznie, ale wystarczająco krótko, by nie frustrować graczy.
-- 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
Zarządzanie stanem magazynowym i gospodarką po stronie serwera
Zarządzanie stanem magazynowym przekształca automaty z nielimitowanych dystrybutorów przedmiotów w dynamiczne elementy gospodarki. Każda lokalizacja automatu śledzi swój stan magazynowy niezależnie. Gdy w automacie zabraknie danego przedmiotu, gracze widzą go w menu zakupu jako wyprzedany. Stany magazynowe są zapisywane w bazie danych i zachowywane po restartach serwera. Praca zaopatrzeniowca pozwala graczom pracować jako dostawcy, którzy odbierają towary z magazynu i rozwożą je do pustych automatów w całym mieście. Tworzy to pełny obieg gospodarczy: dostawcy uzupełniają automaty, automaty sprzedają klientom, przychód trafia do właściciela automatu, a kierowca dostawczy otrzymuje wynagrodzenie. Dynamiczne ceny dodają kolejny poziom, popularne przedmioty stopniowo drożeją w miarę spadku zapasów, a ceny resetują się po uzupełnieniu automatu. W naturalny sposób równoważy to wzorce konsumpcji i wywołuje poczucie pilności, gdy w automacie kończy się pożądany towar.
-- 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)
Mechaniki napadów dla przestępczego roleplayu
Automaty są naturalnym celem napadów dla postaci ze świata przestępczego. Wdróż mechanikę wytrychu lub łomu, która pozwala przestępcom włamywać się do maszyn i kraść znajdującą się wewnątrz gotówkę. Napad powinien zajmować czas, wymagać minigry, np. zagadki z wytrychem, generować hałas alarmujący pobliskich graczy i potencjalnie wywoływać zgłoszenie do policji, a także dawać zmienną nagrodę zależną od liczby transakcji zrealizowanych przez automat od ostatniego uzupełnienia lub napadu. Śledź gromadzenie gotówki w każdym automacie na podstawie faktycznych zakupów graczy, aby łup z napadu był realistyczny i powiązany z realną aktywnością ekonomiczną. Po udanym napadzie automat przechodzi w stan uszkodzenia, w którym nie może realizować zakupów, dopóki nie zostanie naprawiony przez mechanika lub automatycznie po upływie czasu odnowienia. Tworzy to konsekwencje działalności przestępczej wpływające na całą społeczność oraz generuje zlecenia naprawy dla mechaników, dodatkowo wzbogacając ekosystem ekonomiczny.
Sklepy graczy i niestandardowe witryny
Rozbuduj framework automatów z przekąskami w pełny system kasowy dla firm należących do graczy. Właściciele firm mogą stawiać własne lady sklepowe na swoich posesjach, konfigurować sprzedawane przedmioty, ustalać własne ceny i zarządzać ekwipunkiem za pomocą interfejsu NUI. System sklepu korzysta z tej samej bazowej mechaniki zakupu i magazynu co automaty, ale dodaje warstwę właściciela ze śledzeniem przychodów, zarządzaniem wydatkami, uprawnieniami pracowników i raportami zysków. Pracownicy na służbie mogą korzystać z kasy, aby ręcznie realizować sprzedaż na potrzeby interakcji roleplay, podczas gdy zautomatyzowany system obsługuje sprzedaż pod nieobecność pracowników. Przychody z automatycznej sprzedaży trafiają na konto bankowe firmy po potrąceniu konfigurowalnego podatku procentowego, który zasila skarbiec rządu serwera. Tworzy to kompletną gospodarkę detaliczną, w której gracze wytwarzają lub pozyskują towary, zaopatrują swoje sklepy, konkurują cenowo z innymi firmami i generują dochód pasywny ze swojej inwestycji.
Optymalizacja wydajności i integracja z targetem
Skanowanie modeli propów w każdej klatce zniszczyłoby wydajność serwera. Zamiast tego zastosuj wielopoziomowe podejście do wykrywania. Uruchamiaj szerokie skanowanie co 2 sekundy w promieniu 15 metrów, aby znaleźć pobliskie automaty. Zapisuj lokalizacje wykrytych maszyn w pamięci podręcznej i w kolejnych klatkach wykonuj tylko precyzyjne sprawdzanie odległości względem cache. Gdy gracz oddali się na ponad 20 metrów od dowolnego automatu w pamięci podręcznej, wyczyść cache i uruchom ponowne skanowanie. W przypadku serwerów korzystających z systemów target, takich jak ox_target lub qb-target, rejestruj strefy target na wykrytych propach zamiast używać komunikatów zbliżeniowych. Strefy target są bardziej wydajne, ponieważ aktywują się tylko wtedy, gdy gracz celuje w obiekt, co całkowicie eliminuje potrzebę ciągłego sprawdzania odległości. Podejście oparte na target zapewnia również bardziej przejrzysty interfejs użytkownika z kontekstowymi opcjami interakcji, które pojawiają się bezpośrednio na modelu automatu, a nie jako unoszące się komunikaty tekstowe.
-- 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)