System napadów i skoków w FiveM: banki, sklepy i skarbce
Skrypt nagradzający napady i skoki w FiveM. Fleeca, Paleto, sklepy jubilerskie, policja i najlepsze skrypty napadów na bezkompromisowe serwery roleplay.
Agency Scripts
Założyciel i główny deweloper w Agency Scripts
Przegląd architektury systemu napadów
Napady i skoki należą do najbardziej ekscytujących elementów rozgrywki na każdym serwerze FiveM roleplay. Łączą przestępców planujących skomplikowaną operację, policję reagującą z taktyczną precyzją, negocjatorów prowadzących napięte rozmowy o zakładników oraz cywilów uwięzionych w krzyżowym ogniu. Dobrze zaprojektowany system napadów wykracza daleko poza prosty komunikat interakcji dający graczom pieniądze. Wymaga warstwowej architektury zarządzającej lokalizacjami, sprawdzaniem wymagań wstępnych, kolejnymi fazami minigier, podziałem łupu, integracją z dyspozytornią policji, czasem odnowienia i zabezpieczeniami anti-exploit. System powinien składać się z trzech głównych warstw: warstwy konfiguracji definiującej każdą lokalizację napadu wraz z jej wymaganiami i nagrodami, serwerowej maszyny stanów śledzącej aktywne napady i egzekwującej wszystkie zasady oraz klienckiej warstwy prezentacji renderującej minigry, animacje i interfejsy NUI. Każda kluczowa decyzja musi zapadać po stronie serwera, ponieważ logikę po stronie klienta można banalnie łatwo zmanipulować.
Konfiguracja lokalizacji i poziomów napadów
Twój system napadów powinien obsługiwać wiele typów lokalizacji o rosnącym poziomie trudności. Sklepy spożywcze stanowią poziom podstawowy, wymagający minimalnej obecności policji i podstawowych narzędzi. Oddziały banku Fleeca znajdują się na średnim poziomie z umiarkowanymi zabezpieczeniami i wyższymi wymaganiami dotyczącymi policji. Skarbiec Pacific Standard to szczytowy napad, wymagający pełnej ekipy, zaawansowanego sprzętu i znacznej obecności służb mundurowych online. Każda lokalizacja wymaga wpisu w konfiguracji, który określa jej współrzędne, wymagane przedmioty, minimalną liczbę policjantów, czas odnowienia, zakres nagrody oraz sekwencję zabezpieczeń, które należy pokonać:
Config.Robberies = {
['store_1'] = {
label = 'LTD Gasoline - Davis',
type = 'store',
coords = vector3(-47.02, -1757.23, 29.42),
register = vector3(-43.43, -1748.60, 29.42),
tier = 1,
cooldown = 1800, -- 30 minutes
minPolice = 2,
requiredItems = {'weapon_pistol'},
reward = {min = 2500, max = 6000, type = 'cash'},
securityLayers = {
{type = 'intimidate', time = 30},
{type = 'grab_cash', time = 15},
},
},
['fleeca_legion'] = {
label = 'Fleeca Bank - Legion Square',
type = 'bank',
coords = vector3(149.73, -1042.65, 29.37),
vault = vector3(144.87, -1044.16, 29.37),
tier = 2,
cooldown = 7200, -- 2 hours
minPolice = 4,
requiredItems = {'electronickit', 'thermite'},
reward = {min = 45000, max = 90000, type = 'markedbills'},
securityLayers = {
{type = 'hack', difficulty = 'medium', time = 25},
{type = 'thermite', time = 10},
{type = 'drill', time = 40},
},
},
['pacific_standard'] = {
label = 'Pacific Standard Bank',
type = 'vault',
coords = vector3(255.85, 225.60, 101.88),
vault = vector3(262.20, 222.10, 101.68),
tier = 3,
cooldown = 14400, -- 4 hours
minPolice = 6,
requiredItems = {'electronickit', 'thermite', 'advancedlaptop', 'duffelbag'},
reward = {min = 250000, max = 500000, type = 'markedbills'},
securityLayers = {
{type = 'hack', difficulty = 'hard', time = 20},
{type = 'hack', difficulty = 'hard', time = 20},
{type = 'thermite', time = 8},
{type = 'drill', time = 60},
{type = 'hack', difficulty = 'expert', time = 15},
},
},
}
Każdy poziom powinien skalować się nie tylko pod względem nagrody, ale także złożoności i ryzyka. Napady na sklepy to szybkie akcje typu smash-and-grab trwające poniżej dwóch minut, podczas gdy napad na Pacific Standard to wieloetapowa operacja, której ukończenie może zająć piętnaście minut lub więcej, dając policji mnóstwo czasu na utworzenie kordonu.
Maszyna stanów po stronie serwera
Maszyna stanów napadu to serce systemu. Śledzi każdą lokalizację napadu przez stany jej cyklu życia: idle, in-progress, cooldown i locked. Gdy gracz rozpoczyna napad, serwer weryfikuje wszystkie wymagania wstępne przed zmianą stanu lokalizacji z idle na in-progress. W stanie in-progress serwer monitoruje, na której warstwie zabezpieczeń znajdują się obecnie przestępcy, którzy gracze biorą udział i ile czasu upłynęło. Jeśli wszyscy gracze opuszczą obszar lub zostaną aresztowani, napad automatycznie kończy się niepowodzeniem i przechodzi w skrócony stan cooldown. Po ukończeniu lokalizacja przechodzi w stan cooldown na skonfigurowany czas, zanim stanie się ponownie dostępna:
local RobberyState = {}
function InitializeRobbery(robberyId)
RobberyState[robberyId] = {
status = 'idle', -- idle | active | cooldown | locked
participants = {},
currentLayer = 0,
startedAt = 0,
cooldownUntil = 0,
}
end
RegisterNetEvent('robbery:server:startRobbery', function(robberyId)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local config = Config.Robberies[robberyId]
local state = RobberyState[robberyId]
if not config or not state then return end
-- Validate state
if state.status ~= 'idle' then
TriggerClientEvent('QBCore:Notify', src, 'This location is not available', 'error')
return
end
-- Check cooldown
if os.time() < state.cooldownUntil then
TriggerClientEvent('QBCore:Notify', src, 'Location on cooldown', 'error')
return
end
-- Check minimum police online
local policeCount = GetOnlinePolicCount()
if policeCount < config.minPolice then
TriggerClientEvent('QBCore:Notify', src, 'Not enough police online', 'error')
return
end
-- Check required items
for _, item in ipairs(config.requiredItems) do
if not Player.Functions.GetItemByName(item) then
TriggerClientEvent('QBCore:Notify', src, 'Missing required equipment', 'error')
return
end
end
-- Start robbery
state.status = 'active'
state.participants = {src}
state.currentLayer = 1
state.startedAt = os.time()
-- Alert police dispatch
TriggerEvent('dispatch:server:notify', {
type = 'robbery',
coords = config.coords,
message = 'Silent alarm triggered at ' .. config.label,
tier = config.tier,
})
-- Send first security layer to client
TriggerClientEvent('robbery:client:startLayer', src, robberyId, config.securityLayers[1])
end)
Maszyna stanów powinna również radzić sobie ze skrajnymi przypadkami, takimi jak restart serwera podczas trwającego napadu. Po uruchomieniu serwera zresetuj wszystkie stany napadów do idle i wyczyść aktywnych uczestników. Zapisuj znaczniki czasu cooldownu w trwałej tabeli, aby przetrwały restarty.
Integracja minigier
Minigry przekształcają napady z biernego czekania w aktywne wyzwanie zręcznościowe. Każdy typ warstwy zabezpieczeń powinien odpowiadać innemu stylowi minigry, który testuje różne umiejętności gracza. Minigry hakerskie mogą wykorzystywać łamigłówki pamięciowe, wyzwania łączenia obwodów lub szybkie sekwencje liczb. Podkładanie termitu wymaga precyzyjnego wyczucia czasu, aby rozmieścić ładunki, zanim zgasną. Mechanika wiercenia sprawdza stabilną kontrolę myszy, gdy gracz prowadzi wiertło przez mechanizm zamka bez przegrzania. Kluczową zasadą projektowania jest to, że trudniejsze poziomy powinny używać bardziej złożonych wariantów tych minigier z węższymi oknami czasowymi, większą liczbą elementów do śledzenia lub większą prędkością:
-- Client-side minigame dispatcher
function StartMinigame(layerType, difficulty, callback)
if layerType == 'hack' then
local config = HackDifficulty[difficulty]
exports['ps-ui']:Circle(function(success)
callback(success)
end, config.circles, config.speed)
elseif layerType == 'thermite' then
exports['ps-ui']:Thermite(function(success)
callback(success)
end, 10, 5, 3)
elseif layerType == 'drill' then
exports['drilling']:StartDrill(function(success)
callback(success)
end)
elseif layerType == 'intimidate' then
-- Store clerk intimidation: aim weapon at NPC
StartClerkIntimidation(function(success)
callback(success)
end)
end
end
RegisterNetEvent('robbery:client:startLayer', function(robberyId, layer)
local ped = PlayerPedId()
-- Play appropriate animation
if layer.type == 'hack' then
PlayHackingAnimation(ped)
elseif layer.type == 'drill' then
PlayDrillingAnimation(ped)
end
StartMinigame(layer.type, layer.difficulty, function(success)
ClearPedTasks(ped)
TriggerServerEvent('robbery:server:layerResult', robberyId, success)
end)
end)
Nieudane próby w minigrach powinny mieć konsekwencje. Nieudany hack może wywołać dodatkowe alarmy, skrócić pozostały czas lub zablokować gracza na krótki okres karny. W przypadku napadów wysokiego poziomu niepowodzenie ostatniego hackowania może całkowicie zablokować skarbiec, zmuszając ekipę do porzucenia napadu z jedynie częściowym łupem. Te stany niepowodzenia dodają napięcia i sprawiają, że udane ukończenie daje prawdziwą satysfakcję.
Integracja z dispatch i reagowaniem policji
System napadów jest kompletny tylko w połowie bez integracji z policją. Gdy rozpoczyna się napad, system dispatch powinien wysyłać stopniowane alerty w oparciu o poziom napadu. Napad na sklep spożywczy generuje proste powiadomienie o cichym alarmie z lokalizacją sklepu. Oddział Fleeca uruchamia alarm bankowy z nazwą placówki i sugerowanym poziomem reakcji. Napad na Pacific Standard aktywuje pełny komunikat alarmowy zalecający reakcję wszystkich dostępnych jednostek. Zapewnij policji aktualizacje w czasie rzeczywistym w miarę postępów napadu: gdy przestępcy przełamią kolejną warstwę zabezpieczeń, gdy wewnątrz budynku padną strzały lub gdy przestępcy podejmą próbę ucieczki. Te aktualizacje pomagają policji koordynować reakcję i tworzą dynamiczne scenariusze, w których funkcjonariusze mogą zdecydować, czy szturmować natychmiast, czy utworzyć kordon i negocjować. Zaimplementuj mechanikę zakładników, w której przestępcy mogą brać kasjerów NPC jako zakładników, dając pole do popisu negocjatorom policyjnym i zapewniając przestępcom kartę przetargową do opóźnienia reakcji policji. System zakładników powinien śledzić liczbę przetrzymywanych osób, to, czy ktokolwiek odniósł obrażenia, oraz dostarczać wskazówki negocjacyjne podczas rozmów telefonicznych między przestępcami a interweniującymi funkcjonariuszami.
Dystrybucja łupów i pranie brudnych pieniędzy
To, co gracze otrzymują z napadu, ma tak samo duże znaczenie jak sam napad. Zamiast wpłacać czystą gotówkę bezpośrednio na konta bankowe, nagradzaj przestępców oznaczonymi banknotami lub skradzionymi przedmiotami, które wymagają dodatkowych kroków, aby zamienić je na użyteczną walutę. Taki projekt wydłuża pętlę rozgrywki przestępczej poza sam napad i tworzy powiązane ze sobą systemy. Oznaczone banknoty można prać w określonych miejscach na mapie za procentową prowizję, co zmusza do kalkulacji ryzyka i zysku przy wyborze czasu oraz miejsca prania. Skradzioną biżuterię lub elektronikę trzeba sprzedać paserom NPC, którzy oferują zmienne stawki w zależności od rzadkości przedmiotu i aktualnych warunków rynkowych. Wdróż system toreb na łup, w którym fizyczny łup musi być noszony przez graczy, co ogranicza ich prędkość poruszania się i uniemożliwia użycie broni, dodając elementy taktyczne do fazy ucieczki:
RegisterNetEvent('robbery:server:distributeLoot', function(robberyId)
local src = source
local config = Config.Robberies[robberyId]
local state = RobberyState[robberyId]
if not config or not state then return end
if state.status ~= 'active' then return end
local rewardAmount = math.random(config.reward.min, config.reward.max)
local participants = state.participants
local perPlayer = math.floor(rewardAmount / #participants)
for _, playerId in ipairs(participants) do
local Player = QBCore.Functions.GetPlayer(playerId)
if Player then
if config.reward.type == 'markedbills' then
local billStacks = math.ceil(perPlayer / 5000)
for i = 1, billStacks do
local amount = math.min(5000, perPlayer - ((i - 1) * 5000))
Player.Functions.AddItem('markedbills', 1, nil, {
worth = amount,
source = config.label,
})
end
elseif config.reward.type == 'cash' then
Player.Functions.AddMoney('cash', perPlayer, 'robbery-' .. robberyId)
end
TriggerClientEvent('inventory:client:ItemBox', playerId, QBCore.Shared.Items['markedbills'], 'add')
end
end
-- Set cooldown
state.status = 'cooldown'
state.cooldownUntil = os.time() + config.cooldown
state.participants = {}
-- Log the robbery for admin review
LogRobbery(robberyId, participants, rewardAmount)
end)
Anti-exploit i zarządzanie cooldownami
Systemy napadów przyciągają osoby szukające exploitów, ponieważ generują bogactwo. Wdróż wiele warstw ochrony przed powszechnymi nadużyciami. Serwerowe sprawdzanie odległości gwarantuje, że gracze są fizycznie obecni w miejscu napadu na każdym etapie. Ograniczanie częstotliwości zapytań zapobiega spamowaniu zdarzeniami rozpoczęcia napadu. Śledzenie uczestników zapewnia, że tylko zarejestrowani gracze mogą pokonywać kolejne zabezpieczenia lub odbierać łup. Globalne czasy odnowienia zapobiegają spamowaniu napadami na całym serwerze, ograniczając liczbę jednocześnie aktywnych akcji. Czasy odnowienia dla pojedynczego gracza uniemożliwiają jednej osobie dokonywanie serii napadów raz za razem. Zapisuj każdą próbę napadu wraz ze znacznikami czasu, identyfikatorami uczestników, statusem sukcesu lub porażki oraz przyznaną nagrodą, aby administratorzy mogli badać podejrzane schematy. Monitoruj niemożliwe czasy ukończenia, gdy gracz pokonuje wszystkie zabezpieczenia szybciej, niż jest to fizycznie możliwe. Wdróż system reputacji, w którym nadmierna aktywność przestępcza przyciąga większą uwagę policji i surowsze konsekwencje, tworząc naturalny bufor zachęcający graczy do rozkładania akcji przestępczych w czasie.
Drogi ucieczki i dynamiczne wydarzenia
Faza ucieczki to moment, w którym napady stają się naprawdę nieprzewidywalne. Po zabezpieczeniu łupu przestępcy muszą uciec z obszaru, niosąc ciężkie torby ograniczające ich ruchy. Zaprojektuj system tak, aby obsługiwał wiele metod ucieczki: pojazdy lądowe zaparkowane na zewnątrz, łodzie w pobliskich dokach w przypadku banków nad wodą lub ewakuację helikopterem z dachów przy napadach wyższego poziomu. Dodaj dynamiczne zdarzenia w fazie ucieczki, takie jak kolczatki rozkładane przez policję, komunikaty radiowe ujawniające opis pojazdu podejrzanych oraz losowe blokady drogowe na kluczowych skrzyżowaniach. Serwer powinien śledzić, czy przestępcy pomyślnie opuścili strefę napadu, zdefiniowaną jako konfigurowalny promień wokół banku, i dopiero wtedy oznaczać napad jako w pełni zakończony. Jeśli wszyscy uczestnicy zginą lub zostaną aresztowani przed opuszczeniem strefy, łup powinien być możliwy do odzyskania przez policję jako dowód. Tworzy to znaczącą rozgrywkę po napadzie dla obu stron i sprawia, że ucieczka jest równie wymagająca oraz satysfakcjonująca, co sam skok.