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.