System zamków do drzwi FiveM: Skryptuj bezpieczne drzwi i klucze
Dodaj zamki do drzwi na dowolnym serwerze FiveM. Konfiguracja, klucze, drzwi tylko dla frakcji i najlepsze skrypty blokad drzwi dla komisariatów policji, domów oraz wnętrz biznesowych.
Agency Scripts
Założyciel i główny deweloper w Agency Scripts
Dlaczego zamki w drzwiach mają znaczenie w Roleplay
Systemy zamków drzwi to jedne z najbardziej niedocenianych, a zarazem kluczowych elementów każdego poważnego serwera FiveM roleplay. Bez odpowiedniego zarządzania drzwiami gracze mogą bez przeszkód wchodzić do zbrojowni policji, pokojów personelu szpitala i kryjówek gangów, całkowicie psując immersję. Dobrze wdrożony system zamków pozwala właścicielom serwerów kontrolować dostęp do każdego wnętrza na mapie, od budynków rządowych po firmy należące do graczy. Tworzy naturalne granice, które napędzają interakcje roleplay, takie jak wymiana kluczy, scenariusze z użyciem wytrychów i skoordynowane wejścia siłowe. W tym poradniku przejdziemy przez tworzenie kompletnego systemu zamków drzwi w oparciu o ox_doorlock, a następnie rozbudujemy go o własną mechanikę wytrychów, uwierzytelnianie kartami magnetycznymi, grupy drzwi i kontrolę dostępu dla konkretnych biznesów.
Konfiguracja ox_doorlock
Zasób ox_doorlock to najczęściej wybierane rozwiązanie do blokowania drzwi w ekosystemie FiveM, oferujące przejrzyste API, wbudowany interfejs administracyjny do ustawiania drzwi oraz obsługę zarówno podwójnych drzwi, jak i animowanych bram garażowych. Zanim napiszesz własny kod, potrzebujesz solidnych fundamentów. Zainstaluj ox_doorlock wraz z ox_lib, od którego zależy w kwestii komponentów UI i funkcji pomocniczych. Twój server.cfg musi zapewnić uruchamianie zasobów w odpowiedniej kolejności: najpierw baza danych, potem ox_lib, a na końcu ox_doorlock. Po uruchomieniu wbudowana komenda administratorska pozwala podejść do dowolnych drzwi w świecie gry i zarejestrować je jednym kliknięciem.
-- server.cfg load order
ensure oxmysql
ensure ox_lib
ensure ox_doorlock
-- Grant admin access for door placement
add_ace group.admin command.doorlock allow
Po zarejestrowaniu drzwi przez interfejs administratora, ox_doorlock zapisuje każdy wpis w bazie danych ze współrzędnymi, headingiem, hashem modelu i stanem blokady. Możesz odpytywać je i manipulować nimi programowo. Zasób udostępnia zarówno exporty, jak i eventy do przełączania zamków, sprawdzania stanu i zarządzania dostępem. Zrozumienie struktury danych jest kluczowe przed budowaniem na jej podstawie własnych funkcji.
-- Checking if a specific door is locked
local doorId = 1
local isLocked = exports.ox_doorlock:getDoorState(doorId)
-- Toggle a door lock from server side
exports.ox_doorlock:setDoorState(doorId, not isLocked)
-- Listen for door state changes
AddEventHandler('ox_doorlock:stateChanged', function(id, state, source)
print(('Door %d changed to %s by player %s'):format(id, state and 'locked' or 'unlocked', source))
end)
Tworzenie mechaniki wytrychów
Włamywanie wytrychem dodaje warstwę rozgrywki, która nadaje zamkniętym drzwiom znaczenie dla postaci przestępczych. Zamiast być barierą nie do przejścia, drzwi stają się wyzwaniem opartym na umiejętnościach. Najlepszym podejściem jest minigra wymagająca wyczucia czasu i precyzji, dzięki czemu wynik wydaje się zasłużony, a nie losowy. Użyjemy wbudowanego w ox_lib systemu skillcheck, aby stworzyć mechanikę otwierania wytrychem, która wymaga posiadania przedmiotu lockpick w ekwipunku gracza, wyzwala animację i uruchamia wieloetapowy skillcheck. Nieudany skillcheck powinien nieść za sobą konsekwencje, takie jak złamanie wytrycha lub powiadomienie pobliskiej policji poprzez zgłoszenie dispatch.
-- client/lockpicking.lua
local function AttemptLockpick(doorId)
local hasLockpick = exports.ox_inventory:Search('count', 'lockpick')
if hasLockpick < 1 then
lib.notify({ title = 'No Lockpick', type = 'error' })
return
end
-- Play lockpicking animation
local ped = PlayerPedId()
TaskPlayAnim(ped, 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', 'machinic_loop_mechandlenry', 8.0, -8.0, -1, 1, 0, false, false, false)
-- Multi-stage skillcheck: easy, medium, hard
local success = lib.skillCheck({'easy', 'medium', 'hard'}, {'w', 'a', 's', 'd'})
ClearPedTasks(ped)
if success then
TriggerServerEvent('doorlock:lockpick:success', doorId)
lib.notify({ title = 'Lock Picked', description = 'The door clicks open.', type = 'success' })
else
TriggerServerEvent('doorlock:lockpick:fail', doorId)
lib.notify({ title = 'Failed', description = 'The lockpick snapped.', type = 'error' })
end
end
Po stronie serwera musisz zweryfikować próbę użycia wytrycha, usunąć wytrych w przypadku niepowodzenia, odblokować drzwi po udanej próbie i opcjonalnie wysłać powiadomienie do policji. Rate limiting jest tutaj ważny, aby zapobiec próbom spamu. Zapisuj cooldown na gracza i na drzwi, aby nie mógł natychmiast ponowić próby po porażce. Handler serwera powinien również sprawdzić, czy gracz faktycznie znajduje się w pobliżu drzwi, które próbuje otworzyć, co zapobiega zdalnym exploitom.
-- server/lockpicking.lua
local cooldowns = {}
RegisterNetEvent('doorlock:lockpick:success', function(doorId)
local src = source
local playerCoords = GetEntityCoords(GetPlayerPed(src))
-- Verify proximity to door (anti-cheat)
local doorData = exports.ox_doorlock:getDoor(doorId)
if not doorData then return end
if #(playerCoords - doorData.coords) > 3.0 then return end
-- Check cooldown
local key = ('%s:%s'):format(src, doorId)
if cooldowns[key] and os.time() - cooldowns[key] < 30 then return end
exports.ox_doorlock:setDoorState(doorId, 0) -- Unlock
cooldowns[key] = os.time()
-- Auto-relock after 60 seconds
SetTimeout(60000, function()
exports.ox_doorlock:setDoorState(doorId, 1)
end)
end)
RegisterNetEvent('doorlock:lockpick:fail', function(doorId)
local src = source
exports.ox_inventory:RemoveItem(src, 'lockpick', 1)
-- Alert police dispatch
TriggerEvent('dispatch:alert', {
coords = GetEntityCoords(GetPlayerPed(src)),
message = 'Attempted break-in reported',
code = '10-31',
job = 'police'
})
end)
Wdrażanie systemu kart dostępu
Karty dostępu zapewniają bardziej uporządkowany mechanizm kontroli dostępu niż zwykłe klucze. Sprawdzają się świetnie w biurowcach, obiektach rządowych i strefach zamkniętych, gdzie dostęp powinien zależeć od określonego poziomu uprawnień. System kart dostępu przypisuje każdorazowo drzwiom poziom bezpieczeństwa od 1 do 5, a gracze muszą posiadać kartę o równym lub wyższym poziomie uprawnień, aby je otworzyć. Tworzy to naturalną hierarchię, w której karta poziomu 3 otwiera wszystkie drzwi poziomów 1, 2 i 3, lecz nie pozwala wejść do stref zastrzeżonych poziomu 4 lub 5. Karty dostępu mogą być wydawane przez systemy prac, znajdowane jako łup lub wytwarzane w ramach systemu progresji.
-- shared/config.lua
Config = {}
Config.KeycardLevels = {
{ name = 'keycard_1', label = 'Green Keycard', level = 1 },
{ name = 'keycard_2', label = 'Blue Keycard', level = 2 },
{ name = 'keycard_3', label = 'Yellow Keycard', level = 3 },
{ name = 'keycard_4', label = 'Red Keycard', level = 4 },
{ name = 'keycard_5', label = 'Black Keycard', level = 5 },
}
Config.DoorSecurity = {
[10] = { level = 1, name = 'Office Lobby' },
[11] = { level = 2, name = 'Server Room' },
[12] = { level = 3, name = 'Executive Floor' },
[13] = { level = 4, name = 'Vault Anteroom' },
[14] = { level = 5, name = 'Main Vault' },
}
-- client/keycard.lua
local function TryKeycardAccess(doorId)
local security = Config.DoorSecurity[doorId]
if not security then return false end
for _, card in ipairs(Config.KeycardLevels) do
if card.level >= security.level then
local count = exports.ox_inventory:Search('count', card.name)
if count > 0 then
-- Play card swipe animation
lib.requestAnimDict('anim@heists@keycard@')
TaskPlayAnim(PlayerPedId(), 'anim@heists@keycard@', 'exit', 5.0, 1.0, -1, 16, 0, 0, 0, 0)
Wait(1200)
ClearPedTasks(PlayerPedId())
return true, card
end
end
end
return false
end
Grupy drzwi i dostęp biznesowy
Zarządzanie pojedynczymi drzwiami staje się nieskalowalne, gdy twój serwer ma setki zablokowanych drzwi. Grupy drzwi rozwiązują ten problem, pozwalając przypisać wiele drzwi do nazwanej grupy i kontrolować dostęp do całej grupy naraz. Posterunek policji może mieć 15 zablokowanych drzwi, do których dostęp powinna mieć praca policjanta. Zamiast konfigurować każde drzwi z osobna, przypisujesz je wszystkie do grupy "police_station" i przyznajesz dostęp na podstawie nazwy frakcji. Gdy administrator zatrudni nowego funkcjonariusza, zyskuje on automatycznie dostęp do wszystkich drzwi w grupie bez żadnej ręcznej konfiguracji.
-- server/door_groups.lua
local DoorGroups = {
police_station = {
doors = {1, 2, 3, 4, 5, 6, 7, 8},
access = {
{ type = 'job', name = 'police', minGrade = 0 },
{ type = 'job', name = 'sheriff', minGrade = 0 },
}
},
pillbox_hospital = {
doors = {20, 21, 22, 23, 24},
access = {
{ type = 'job', name = 'ambulance', minGrade = 0 },
{ type = 'job', name = 'doctor', minGrade = 2 },
}
},
vangelico = {
doors = {30, 31},
access = {
{ type = 'job', name = 'jeweler', minGrade = 0 },
{ type = 'item', name = 'vangelico_key' },
}
},
}
function HasGroupAccess(source, groupName)
local group = DoorGroups[groupName]
if not group then return false end
for _, rule in ipairs(group.access) do
if rule.type == 'job' then
local job = GetPlayerJob(source)
if job and job.name == rule.name and job.grade >= (rule.minGrade or 0) then
return true
end
elseif rule.type == 'item' then
local count = exports.ox_inventory:GetItem(source, rule.name, nil, true)
if count and count > 0 then return true end
end
end
return false
end
Drzwi firmowe dodają kolejny wymiar, łącząc dostęp do drzwi z danymi własności. Gdy gracz kupi biznes, powinien automatycznie uzyskać kontrolę nad wszystkimi drzwiami przypisanymi do tej nieruchomości. System musi sprawdzać własność biznesu w czasie rzeczywistym, ponieważ nieruchomości mogą być sprzedawane lub przekazywane. Zaimplementuj callback odpytujący tabelę biznesów w celu weryfikacji obecnego właściciela, a następnie buforuj wynik z krótkim czasem TTL, aby nie przeciążać bazy danych przy każdej interakcji z drzwiami.
Synchronizacja stanów drzwi między klientami
Synchronizacja drzwi to jeden z trudniejszych aspektów systemu zamków drzwi. Gdy jeden gracz odblokuje drzwi, każdy gracz w pobliżu musi widzieć je otwarte. Natywne mechanizmy kontroli drzwi FiveM działają dla każdego klienta osobno, co oznacza, że każdy klient niezależnie zarządza stanami drzwi. Bez właściwej synchronizacji jeden gracz widzi drzwi otwarte, podczas gdy inny widzi je zamknięte. ox_doorlock obsługuje podstawową synchronizację stanu, ale niestandardowe rozszerzenia muszą uważać na propagację stanu. Używaj state bags lub autorytatywnych zdarzeń serwerowych do rozsyłania zmian stanu drzwi do wszystkich graczy w rozsądnym zasięgu. Unikaj synchronizacji z całym serwerem przy każdym przełączeniu drzwi, ponieważ generuje to niepotrzebny narzut sieciowy na dużych serwerach z setkami drzwi.
-- server: broadcast door state to nearby players only
local function SyncDoorToNearby(doorId, state, coords, range)
range = range or 100.0
local players = GetPlayers()
for _, playerId in ipairs(players) do
local ped = GetPlayerPed(playerId)
if ped and DoesEntityExist(ped) then
local playerCoords = GetEntityCoords(ped)
if #(playerCoords - coords) <= range then
TriggerClientEvent('doorlock:sync', tonumber(playerId), doorId, state)
end
end
end
end
-- client: apply synced door state
RegisterNetEvent('doorlock:sync', function(doorId, state)
local door = exports.ox_doorlock:getDoor(doorId)
if door then
door.state = state
end
end)
Kwestie bezpieczeństwa i anti-exploit
Systemy zamków drzwi są częstym celem cheaterów, ponieważ ominięcie drzwi często daje dostęp do zastrzeżonych przedmiotów, broni lub pieniędzy. Każde żądanie przełączenia drzwi musi być weryfikowane po stronie serwera. Nigdy nie ufaj identyfikatorom drzwi przesyłanym przez klienta bez sprawdzenia odległości gracza i jego uprawnień dostępu. Wdróż logowanie każdej zmiany stanu drzwi, aby administratorzy mogli przeglądać podejrzane schematy, na przykład gracza odblokowującego drzwi do skarbca, do których nie powinien mieć dostępu. Zastosuj ograniczenie częstotliwości (rate limit) dla wszystkich interakcji z drzwiami, aby zapobiec próbom siłowym (brute-force) w systemie wytrychów. Rozważ dodanie rejestratora zdarzeń po stronie serwera, który zapisuje Steam ID gracza, identyfikator drzwi, znacznik czasu oraz metodę dostępu do celów audytu. Te logi stają się nieocenione podczas badania exploitów lub rozwiązywania sporów między graczami o to, kto uzyskał do czegoś dostęp i kiedy.
Łącząc wszystko w całość
Gotowy do wdrożenia na produkcji system blokady drzwi łączy wszystkie te komponenty w ujednolicony framework. Punktem wejścia jest interakcja z systemem targetu, ox_target lub qb-target, który wykrywa spojrzenie gracza na zarejestrowane drzwi i wyświetla opcje kontekstowe w oparciu o poziom uprawnień. Jeśli gracz posiada dostęp z tytułu pracy, pokaż przycisk odblokowania. Jeśli ma kartę dostępu, pokaż opcję przeciągnięcia karty. Jeśli ma wytrych, a drzwi są oznaczone jako podatne na otwarcie wytrychem, pokaż opcję wytrychu. Każda ścieżka kieruje do odpowiedniego handlera, który przeprowadza walidację po stronie serwera i synchronizuje wynik z pobliskimi klientami. Konfiguracja powinna opierać się na danych za pośrednictwem wpisów w bazie danych lub wspólnych plików konfiguracyjnych, aby właściciele serwerów mogli dodawać, usuwać i modyfikować dostęp do drzwi bez ingerencji w kod. Ta modułowa architektura oznacza, że możesz wymienić minigrę wytrycha, dodać nowe metody dostępu, np. skanery biometryczne, lub zintegrować całość z zupełnie innymi systemami ekwipunku bez konieczności przepisywania logiki bazowej.