Zurück zum Blog
Tutorial9 Min. Lesezeit

FiveM Türschlosssystem: Türen und Schlüssel absichern

Füge jedem FiveM-Server Türschlösser hinzu. Mit Konfiguration, Schlüsseln, jobgebundenen Türen und Scripts für Polizei, Häuser und Geschäfte.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Warum Türschlösser im Roleplay wichtig sind

Türschlosssysteme gehören zu den am meisten unterschätzten und zugleich unverzichtbaren Bestandteilen eines ernsthaften FiveM-Roleplay-Servers. Ohne eine ordentliche Türverwaltung können Spieler ungehindert Polizeiwachen, Personalräume in Krankenhäusern und Gangverstecke betreten, was die Immersion komplett zerstört. Mit einem gut umgesetzten Türschlosssystem können Serverbetreiber den Zugang zu jedem Innenraum auf der Karte kontrollieren, von Regierungsgebäuden bis hin zu Geschäften im Besitz von Spielern. Es schafft natürliche Grenzen, die Roleplay-Interaktionen wie den Austausch von Schlüsseln, Lockpicking-Szenarien und koordinierte Durchsuchungen ermöglichen. In diesem Guide zeigen wir dir, wie du mit ox_doorlock als Grundlage ein vollständiges Türschlosssystem aufbaust und es anschließend um eigene Lockpicking-Mechaniken, Keycard-Authentifizierung, Türgruppen und geschäftsspezifische Zugangskontrollen erweiterst.

ox_doorlock einrichten

Die Resource ox_doorlock ist die am weitesten verbreitete Lösung für Türschlösser im FiveM-Ökosystem. Sie bietet eine übersichtliche API, eine integrierte Admin-Oberfläche zum Platzieren von Türen und Unterstützung für Doppeltüren sowie animierte Türen im Garagenstil. Bevor du eigenen Code schreibst, brauchst du ein solides Fundament. Installiere ox_doorlock zusammen mit ox_lib, von dem die Resource für UI-Komponenten und Hilfsfunktionen abhängig ist. Deine server.cfg muss sicherstellen, dass die Resources in der richtigen Reihenfolge gestartet werden: zuerst die Datenbank, dann ox_lib und schließlich ox_doorlock. Sobald alles läuft, kannst du mit dem integrierten Admin-Befehl zu jeder Tür in der Spielwelt gehen und sie mit einem Klick registrieren.

-- 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

Nachdem du Türen über die Admin-Oberfläche registriert hast, speichert ox_doorlock jeden Eintrag mit Koordinaten, Ausrichtung, Model-Hash und Sperrstatus in der Datenbank. Diese Einträge kannst du programmgesteuert abfragen und bearbeiten. Die Resource stellt sowohl Exports als auch Events bereit, um Schlösser umzuschalten, den Status zu prüfen und den Zugang zu verwalten. Bevor du darauf eigene Funktionen aufbaust, musst du die Datenstruktur verstehen.

-- 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)

Eine Lockpicking-Mechanik entwickeln

Lockpicking erweitert das Gameplay um eine Ebene, durch die verschlossene Türen für kriminelle Charaktere relevant werden. Türen sind dann keine absoluten Barrieren mehr, sondern Herausforderungen, bei denen es auf Geschick ankommt. Am besten eignet sich ein Minigame, das Timing und Präzision erfordert, damit sich der Erfolg verdient und nicht zufällig anfühlt. Wir verwenden das integrierte Skillcheck-System von ox_lib, um einen Lockpicking-Ablauf zu erstellen. Der Spieler muss dafür einen Dietrich im Inventar haben, eine Animation wird abgespielt und anschließend läuft ein mehrstufiger Skillcheck. Ein fehlgeschlagener Skillcheck sollte Konsequenzen haben, etwa dass der Dietrich zerbricht oder eine Dispatch-Nachricht die Polizei in der Nähe alarmiert.

-- 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

Auf der Serverseite musst du den Lockpicking-Versuch validieren, den Dietrich bei einem Fehlschlag entfernen, die Tür bei Erfolg entriegeln und optional eine Polizeialarmierung auslösen. Eine Ratenbegrenzung ist wichtig, um Spam-Versuche zu verhindern. Speichere pro Spieler und Tür eine Abklingzeit, damit der Spieler es nach einem Fehlschlag nicht sofort erneut versuchen kann. Der Server-Handler sollte außerdem prüfen, ob sich der Spieler tatsächlich in der Nähe der Tür befindet, die er knacken will, damit Remote-Exploits verhindert werden.

-- 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)

Ein Keycard-System implementieren

Keycards bieten im Vergleich zu einfachen Schlüsseln eine strukturiertere Zugangskontrolle. Sie eignen sich gut für Bürogebäude, Regierungseinrichtungen und Sperrbereiche, in denen der Zugang an bestimmte Sicherheitsstufen gebunden sein soll. Das Keycard-System weist jeder Tür eine Sicherheitsstufe von 1 bis 5 zu. Spieler müssen eine Keycard mit einer gleich hohen oder höheren Sicherheitsstufe besitzen, um die Tür zu entriegeln. So entsteht eine natürliche Hierarchie: Eine Keycard der Stufe 3 öffnet alle Türen der Stufen 1, 2 und 3, aber nicht die Sperrbereiche der Stufen 4 und 5. Keycards können über Job-Systeme ausgegeben, als Beute gefunden oder im Rahmen eines Fortschrittssystems hergestellt werden.

-- 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

Türgruppen und Zugang zu Geschäften

Die Verwaltung einzelner Türen ist bei Hunderten verschlossenen Türen auf deinem Server nicht mehr skalierbar. Mit Türgruppen kannst du mehrere Türen einer benannten Gruppe zuordnen und den Zugang für die gesamte Gruppe gemeinsam verwalten. In einer Polizeiwache könnten 15 verschlossene Türen sein, die alle für den Polizeijob zugänglich sein sollen. Statt jede Tür einzeln zu konfigurieren, ordnest du sie alle der Gruppe "police_station" zu und vergibst den Zugang anhand des Job-Namens. Wenn ein Admin einen neuen Beamten einstellt, erhält dieser automatisch Zugang zu allen Türen der Gruppe, ganz ohne manuelle Konfiguration.

-- 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

Türen von Geschäften bieten eine weitere Möglichkeit, indem der Zugang an Besitzdaten geknüpft wird. Wenn ein Spieler ein Geschäft kauft, sollte er automatisch die Kontrolle über alle Türen erhalten, die zu diesem Geschäft gehören. Das System muss den Besitz in Echtzeit prüfen, da Immobilien verkauft oder übertragen werden können. Implementiere einen Callback, der deine Geschäftstabelle abfragt, um den aktuellen Besitzer zu ermitteln. Speichere das Ergebnis anschließend für kurze Zeit im Cache, damit die Datenbank nicht bei jeder Türinteraktion abgefragt wird.

Türstatus zwischen Clients synchronisieren

Die Synchronisierung von Türen gehört zu den anspruchsvolleren Aspekten eines Türschlosssystems. Wenn ein Spieler eine Tür entriegelt, muss jeder Spieler in der Nähe sehen, wie sie aufgeht. Die nativen Türsteuerungen von FiveM funktionieren pro Client, das heißt, jeder Client verwaltet den Türstatus unabhängig. Ohne korrekte Synchronisierung sieht ein Spieler eine offene Tür, während sie für einen anderen geschlossen ist. ox_doorlock übernimmt die grundlegende Statussynchronisierung. Bei eigenen Erweiterungen musst du jedoch auf die Weitergabe des Status achten. Verwende State Bags oder serverseitig autorisierte Events, um Türänderungen an alle Spieler in einem angemessenen Umkreis zu übertragen. Synchronisiere nicht jede Türänderung mit dem gesamten Server, denn auf großen Servern mit Hunderten von Türen entsteht dadurch unnötige Netzwerklast.

-- 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)

Sicherheit und Schutz vor Exploits

Türschlosssysteme sind ein häufiges Ziel von Exploitern, da das Umgehen einer Tür Zugang zu gesperrten Gegenständen, Waffen oder Geld verschaffen kann. Jede Anfrage zum Ändern des Türstatus muss serverseitig validiert werden. Vertraue niemals vom Client gesendeten Tür-IDs, ohne die Nähe des Spielers und dessen Zugangsrechte zu prüfen. Protokolliere jede Änderung des Türstatus, damit Admins verdächtige Muster untersuchen können, zum Beispiel wenn ein Spieler Tresortüren entriegelt, zu denen er keinen Zugang haben sollte. Begrenze die Häufigkeit aller Türinteraktionen, um Brute-Force-Versuche beim Lockpicking-System zu verhindern. Ergänze einen serverseitigen Event-Logger, der die Steam-ID des Spielers, die Tür-ID, den Zeitstempel und die Zugangsmethode für Prüfprotokolle speichert. Solche Logs sind bei der Untersuchung von Exploits oder bei Streitigkeiten zwischen Spielern darüber, wer wann worauf zugegriffen hat, äußerst hilfreich.

Alles zusammenführen

Ein produktionsreifes Türschlosssystem vereint all diese Komponenten in einem einheitlichen Framework. Als Einstiegspunkt dient eine Interaktion über ein Target-System, entweder ox_target oder qb-target. Dieses erkennt, wenn ein Spieler eine registrierte Tür anschaut, und zeigt je nach Zugangsstufe passende Optionen an. Hat der Spieler Job-Zugang, wird eine Schaltfläche zum Entriegeln angezeigt. Besitzt er eine Keycard, wird eine Option zum Durchziehen der Karte angezeigt. Hat er einen Dietrich und ist die Tür zum Knacken freigegeben, wird die entsprechende Option angezeigt. Jede dieser Aktionen wird an den passenden Handler weitergegeben, der sie auf dem Server validiert und das Ergebnis mit Clients in der Nähe synchronisiert. Die Konfiguration sollte datenbasiert über Datenbankeinträge oder gemeinsam genutzte Config-Dateien erfolgen, damit Serverbetreiber Türzugänge hinzufügen, entfernen und ändern können, ohne den Code anzufassen. Dank dieser modularen Architektur kannst du das Lockpicking-Minigame austauschen, neue Zugangsmethoden wie biometrische Scanner hinzufügen oder ganz andere Inventarsysteme integrieren, ohne die Kernlogik neu zu schreiben.

Das passende Script

Fertig für deinen Server, Updates inklusive.

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.