Zurück zum Blog
Tutorial8 Min. Lesezeit

FiveM-Abschleppsystem: Professionelle Abschleppjobs per Script

Betreibe einen Abschleppjob in FiveM. NPC-Aufträge, Abschlepphof-Logik, Animationen für Tieflader und Scripts mit Mechaniker- und Polizeianbindung.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Übersicht über das Abschleppsystem

Ein Abschlepp- und Verwahrstellensystem schafft auf deinem FiveM-Server ein ganzes Job-Ökosystem und bietet Spielern einen zivilen Karriereweg, der auf natürliche Weise mit der Polizei, Mechanikern und anderen Fahrern interagiert. Das System besteht aus mehreren miteinander verbundenen Komponenten: einer Jobverwaltung für das An- und Abmelden zum Dienst, einem Dispatch-System, das Abschleppaufträge verfügbaren Fahrern zuweist, Fahrzeugmechaniken zum realistischen Abschleppen, einer Verwahrstelle für beschlagnahmte und verlassene Fahrzeuge sowie einer Gebührenstruktur, die festlegt, wie viel die Auslösung verwahrter Fahrzeuge kostet. Bei guter Umsetzung wird der Abschleppjob zu einer der sozial interaktivsten Rollen auf dem Server, denn jeder Auftrag umfasst den direkten Kontakt mit einem anderen Spieler, der Hilfe mit einem liegen gebliebenen oder illegal geparkten Fahrzeug benötigt.

Job einrichten und Dienstsystem

Definiere zunächst den Abschleppjob im Job-System deines Frameworks. Spieler sollten sich an einem Abschlepphof zum Dienst melden, einen Firmen-Abschleppwagen erhalten und Dispatch-Aufträge annehmen können. Das Dienstsystem sollte aktive Abschleppfahrer erfassen, damit das Dispatch-System weiß, wer verfügbar ist. Wenn sich ein Spieler zum Dienst meldet, erzeuge einen Pritschenwagen aus einer vordefinierten Liste von Abschleppfahrzeugmodellen und weise ihn diesem Spieler zu. Meldet er sich ab, sollte das Firmenfahrzeug zurückgegeben oder gelöscht werden. Speichere den Dienststatus des Spielers serverseitig, damit er erhalten bleibt und vom Dispatch-System abgefragt werden kann:

Config.TowJob = {
    jobName = 'tow',
    clockInLocation = vector3(409.09, -1622.57, 29.29),
    vehicleSpawn = vector4(405.87, -1631.14, 29.29, 228.35),
    towVehicles = {
        {model = 'flatbed', label = 'Flatbed Truck', rank = 0},
        {model = 'towtruck', label = 'Tow Truck (Hook)', rank = 1},
        {model = 'towtruck2', label = 'Heavy Tow Truck', rank = 2},
    },
    impoundLot = vector3(409.59, -1637.87, 29.29),
    impoundReturn = vector4(403.12, -1640.23, 29.29, 138.92),
    payPerTow = {min = 350, max = 750},
    payPerImpound = 500,
}

RegisterNetEvent('tow:server:clockIn', function()
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    if Player.PlayerData.job.name ~= Config.TowJob.jobName then
        TriggerClientEvent('QBCore:Notify', src, 'You are not a tow driver', 'error')
        return
    end

    Player.Functions.SetJobDuty(true)
    TowDrivers[src] = {
        onDuty = true,
        currentCall = nil,
        vehicleNet = nil,
        towedVehicle = nil,
    }

    TriggerClientEvent('QBCore:Notify', src, 'You are now on duty', 'success')
    TriggerClientEvent('tow:client:spawnVehicle', src)
end)

Fahrzeugbefestigung

Die zentrale technische Herausforderung eines Abschleppsystems besteht darin, ein Fahrzeug so an einem anderen zu befestigen, dass es realistisch aussieht und sich beim Fahren korrekt verhält. FiveM bietet dafür den AttachEntityToEntity Native, doch um die richtigen Offset-Positionen zu finden, müssen sie für jedes Abschleppwagenmodell sorgfältig angepasst werden. Für die meisten Fahrzeuge eignet sich die Pritschenwagen-Methode am besten: Das abzuschleppende Auto wird mit passenden Offsets auf der Ladefläche platziert, damit es natürlich auf der Pritsche sitzt. Bei Abschleppwagen mit Haken werden die Vorderräder des abgeschleppten Fahrzeugs angehoben, während die Hinterräder auf dem Boden bleiben. Für jede Befestigungsart sind andere Offset-Vektoren und Rotationswerte erforderlich. Teste diese Werte im Spiel und passe sie in kleinen Schritten an, bis das Fahrzeug korrekt sitzt und nicht durch den Abschleppwagen clippt:

function AttachVehicleToFlatbed(towTruck, targetVehicle)
    local towModel = GetEntityModel(towTruck)

    -- Offsets calibrated for the flatbed model
    local offsets = {
        ['flatbed'] = {x = 0.0, y = -0.5, z = 1.1, rx = 0.0, ry = 0.0, rz = 0.0},
    }

    local offset = offsets['flatbed']
    if not offset then return false end

    -- Freeze target vehicle physics
    SetEntityCollision(targetVehicle, false, false)
    FreezeEntityPosition(targetVehicle, true)

    -- Attach to flatbed
    AttachEntityToEntity(
        targetVehicle, towTruck,
        0, -- bone index
        offset.x, offset.y, offset.z,
        offset.rx, offset.ry, offset.rz,
        false, false, false, false, 0, true
    )

    -- Store attachment state
    TriggerServerEvent('tow:server:vehicleAttached', VehToNet(towTruck), VehToNet(targetVehicle))
    return true
end

function DetachVehicleFromFlatbed(towTruck, targetVehicle)
    DetachEntity(targetVehicle, true, true)
    SetEntityCollision(targetVehicle, true, true)
    FreezeEntityPosition(targetVehicle, false)

    -- Place vehicle on ground behind the truck
    local truckCoords = GetEntityCoords(towTruck)
    local truckHeading = GetEntityHeading(towTruck)
    local behind = GetOffsetFromEntityInWorldCoords(towTruck, 0.0, -12.0, 0.0)

    SetEntityCoords(targetVehicle, behind.x, behind.y, behind.z)
    SetEntityHeading(targetVehicle, truckHeading)
    SetVehicleOnGroundProperly(targetVehicle)

    TriggerServerEvent('tow:server:vehicleDetached', VehToNet(towTruck))
end

Ein wichtiges Detail ist, die Kollision des befestigten Fahrzeugs zu deaktivieren, um Physikfehler zu verhindern. Haben zwei Fahrzeuge eine aktive Kollision und sind gleichzeitig miteinander verbunden, kann die Spiel-Engine heftiges Ruckeln verursachen oder die Fahrzeuge in die Luft schleudern. Diese Probleme lassen sich vollständig vermeiden, indem du die Position des Zielfahrzeugs einfrierst und seine Kollision während des Abschleppens deaktivierst.

Dispatch- und Abschleppauftragssystem

Das Dispatch-System verbindet Spieler, die einen Abschleppdienst benötigen, mit verfügbaren Abschleppfahrern. Spieler können über ihr Telefon, einen NPC in einer Werkstatt oder über am Straßenrand auf der Karte platzierte Notrufkästen einen Abschleppdienst anfordern. Geht eine Anfrage ein, prüft der Server, welche Abschleppfahrer verfügbar sind, und leitet den Auftrag an den nächstgelegenen weiter. Ist kein Fahrer im Dienst, biete einen Ersatzdienst mit einem NPC-Abschlepper an, der eine höhere Gebühr verlangt und das Fahrzeug nach einer Verzögerung zur Verwahrstelle teleportiert. Die Dispatch-Benachrichtigung sollte den Standort des Anfragenden, das Fahrzeugmodell und den Abschleppgrund enthalten, zum Beispiel eine Panne, einen Unfall oder illegales Parken. Gib dem Abschleppfahrer die Möglichkeit, den Auftrag anzunehmen oder abzulehnen. Abgelehnte Aufträge werden an den nächstgelegenen Fahrer weitergeleitet:

RegisterNetEvent('tow:server:requestTow', function(reason)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local playerCoords = GetEntityCoords(GetPlayerPed(src))
    local vehicle = GetVehiclePedIsIn(GetPlayerPed(src), true)
    local vehicleModel = vehicle ~= 0 and GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) or 'Unknown'

    -- Find nearest available tow driver
    local nearestDriver = nil
    local nearestDist = math.huge

    for driverId, data in pairs(TowDrivers) do
        if data.onDuty and not data.currentCall then
            local driverCoords = GetEntityCoords(GetPlayerPed(driverId))
            local dist = #(playerCoords - driverCoords)
            if dist < nearestDist then
                nearestDist = dist
                nearestDriver = driverId
            end
        end
    end

    if nearestDriver then
        TowDrivers[nearestDriver].currentCall = {
            requesterId = src,
            coords = playerCoords,
            vehicle = vehicleModel,
            reason = reason,
            timestamp = os.time(),
        }

        TriggerClientEvent('tow:client:incomingCall', nearestDriver, {
            coords = playerCoords,
            vehicle = vehicleModel,
            reason = reason,
            playerName = Player.PlayerData.charinfo.firstname,
        })

        TriggerClientEvent('QBCore:Notify', src, 'Tow truck dispatched. ETA based on driver location.', 'success')
    else
        TriggerClientEvent('QBCore:Notify', src, 'No tow drivers available. NPC service will arrive shortly.', 'info')
        StartNPCTowService(src, playerCoords, vehicle)
    end
end)

Verwahrstelle verwalten

In der Verwahrstelle werden abgeschleppte und beschlagnahmte Fahrzeuge aufbewahrt, bis ihre Besitzer sie abholen. Entwirf die Datenbank der Verwahrstelle so, dass erfasst wird, welche Fahrzeuge derzeit dort stehen, wer sie dorthin gebracht hat, wann sie dort abgestellt wurden und welche Gebühr für ihre Freigabe anfällt. Polizisten sollten Fahrzeuge während Verkehrskontrollen oder nach Verfolgungsjagden direkt beschlagnahmen können. Für diese Fahrzeuge können höhere Gebühren oder längere Pflichtverwahrzeiten gelten. Die Benutzeroberfläche der Verwahrstelle sollte eine Liste der dort stehenden Fahrzeuge des Spielers mit Angaben wie Fahrzeugmodell, Kennzeichen, Datum und Grund der Verwahrung sowie Freigabegebühr anzeigen. Spieler zahlen die Gebühr am Schalter der Verwahrstelle und ihr Fahrzeug wird an einem nahegelegenen, festgelegten Rückgabeort erzeugt:

CREATE TABLE IF NOT EXISTS impounded_vehicles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    plate VARCHAR(10) NOT NULL,
    citizenid VARCHAR(50) NOT NULL,
    vehicle_data LONGTEXT NOT NULL,
    impound_reason ENUM('tow', 'police', 'abandoned', 'parking') DEFAULT 'tow',
    impound_fee INT DEFAULT 250,
    impounded_by VARCHAR(50) DEFAULT NULL,
    impounded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    hold_until TIMESTAMP DEFAULT NULL,
    released_at TIMESTAMP DEFAULT NULL,
    INDEX idx_plate (plate),
    INDEX idx_citizen (citizenid),
    INDEX idx_released (released_at)
);

-- Server-side: Impound a vehicle
RegisterNetEvent('tow:server:impoundVehicle', function(vehicleNet, reason)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local vehicle = NetworkGetEntityFromNetworkId(vehicleNet)
    if not DoesEntityExist(vehicle) then return end

    local plate = GetVehicleNumberPlateText(vehicle)
    local vehicleData = QBCore.Functions.GetVehicleProperties(vehicle)
    local fee = Config.ImpoundFees[reason] or 250

    MySQL.insert('INSERT INTO impounded_vehicles (plate, citizenid, vehicle_data, impound_reason, impound_fee, impounded_by) VALUES (?, ?, ?, ?, ?, ?)', {
        plate:trim(),
        GetVehicleOwner(plate),
        json.encode(vehicleData),
        reason,
        fee,
        Player.PlayerData.citizenid,
    })

    DeleteEntity(vehicle)
    local pay = math.random(Config.TowJob.payPerImpound - 100, Config.TowJob.payPerImpound + 100)
    Player.Functions.AddMoney('bank', pay, 'tow-impound')
    TriggerClientEvent('QBCore:Notify', src, 'Vehicle impounded. Earned $' .. pay, 'success')
end)

Automatisierte Parkraumüberwachung

Füge ein automatisiertes System hinzu, das in Sperrzonen geparkte Fahrzeuge erkennt und sie nach einer konfigurierbaren Kulanzzeit zum Abschleppen meldet. Definiere Parkverbotszonen um Hydranten, Bushaltestellen, Polizeistationen und Notaufnahmen von Krankenhäusern. Wenn ein Spieler in einer Sperrzone parkt und sich vom Fahrzeug entfernt, starte einen Timer. Bleibt das Fahrzeug nach Ablauf der Kulanzzeit unbeaufsichtigt, erstelle einen Abschleppauftrag und benachrichtige verfügbare Abschleppfahrer. So entsteht auf natürliche Weise Arbeit für Abschleppfahrer, ohne dass Spieler manuell einen Dienst rufen müssen. Melde nur spielereigene Fahrzeuge, da NPC-Fahrzeuge von der Spiel-Engine verwaltet und auf natürliche Weise entfernt werden. Benachrichtige den Fahrzeugbesitzer darüber, dass sein Auto zum Abschleppen gemeldet wurde. So hat er die Möglichkeit, zurückzukommen und es umzuparken, bevor der Abschleppwagen eintrifft.

Einnahmen und Fortschritt

Strukturiere den Abschleppjob mit einem Fortschrittssystem, das regelmäßige Arbeit belohnt. Neue Abschleppfahrer beginnen auf Rang null, haben Zugriff auf den einfachen Pritschenwagen und verdienen einen Grundbetrag pro abgeschlossenem Abschleppauftrag. Mit steigender Zahl abgeschlossener Aufträge steigen sie im Rang auf und schalten schwerere Abschleppfahrzeuge frei, mit denen sie größere Fahrzeuge wie Lkw und Busse transportieren können. Sie erhalten Zugang zu besser bezahlten Polizeiaufträgen zur Verwahrung und können einen eigenen Abschleppwagen personalisieren. Erfasse Statistiken wie die Gesamtzahl abgeschlossener Abschleppaufträge, die mit einem Fahrzeug am Haken zurückgelegte Strecke und die durchschnittliche Reaktionszeit auf Dispatch-Aufträge. Zeige diese Statistiken in einem Job-Dashboard an, das über einen NPC oder eine Telefon-App erreichbar ist. Implementiere ein Reputationssystem, bei dem Abschleppfahrer, die regelmäßig schnell reagieren und Aufträge ohne Fahrzeugschäden abschließen, Gehaltsboni erhalten. Fahrer, die Aufträge ignorieren oder beim Abschleppen Fahrzeuge beschädigen, müssen hingegen mit Gehaltsabzügen rechnen. So entsteht ein natürlicher Anreiz für gute Arbeit, der der gesamten Server-Community zugutekommt, weil Abschleppdienste dadurch zuverlässig und professionell sind.

Sonderfälle und Networking behandeln

Abschleppsysteme haben mehrere knifflige Sonderfälle, die sorgfältig behandelt werden müssen. Wenn die Verbindung eines Abschleppfahrers während des Abschleppens abbricht, sollte der Server die Verbindung erkennen und das abgeschleppte Fahrzeug sicher abkoppeln und auf den Boden setzen, damit es weder in der Luft schwebt noch durch die Karte fällt. Loggt sich der Fahrzeugbesitzer ein, während sein Auto an einem Abschleppwagen befestigt ist, sollte es in seiner Fahrzeugliste als abgeschleppt markiert sein, zusammen mit Informationen über den Abschleppfahrer. Verwalte den Besitz von Fahrzeug-Entities sorgfältig, denn die Attachment-Native funktioniert nur dann zuverlässig, wenn der Client, der die Befestigung ausführt, beide Entities besitzt. Verwende NetworkRequestControlOfEntity damit sichergestellt ist, dass der Tow-Fahrer beide Fahrzeuge kontrolliert, bevor er versucht, sie zu verbinden. Für die Synchronisierung im Mehrspielermodus überträgst du den Verbindungsstatus an alle Spieler in der Nähe, damit sie das abgeschleppte Fahrzeug auf ihren Bildschirmen an der richtigen Position sehen. State Bags eignen sich dafür gut, weil sie automatisch an alle Clients in Reichweite des Entities repliziert werden.

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.