FiveM Çekici Sistemi: Profesyonel Çekici Meslekleri Geliştirin
FiveM'de bir çekici mesleği işletin. Mekanik ve polis entegrasyonu için NPC talepleri, impound mantığı, flatbed animasyonları ve en iyi çekici script'leri.
Agency Scripts
Agency Scripts Kurucusu ve Baş Geliştiricisi
Çekici Sistemine Genel Bakış
Bir çekici ve impound sistemi FiveM sunucunuzda eksiksiz bir meslek ekosistemi yaratarak oyunculara kolluk kuvvetleri, mekanikler ve diğer sürücülerle doğal bir şekilde etkileşime giren sivil bir kariyer yolu sunar. Sistem birbiriyle bağlantılı birden fazla bileşeni içerir: mesaiye giriş ve çıkışları yöneten bir meslek yönetim katmanı, çekici taleplerini uygun sürücülere yönlendiren bir dispatch sistemi, gerçekçi çekmeyi simüle eden araç bağlama mekanikleri, el konulan ve terk edilen araçları saklayan bir impound otoparkı ve impound edilmiş araçları geri almanın maliyetini belirleyen bir ücret yapısı. İyi tasarlandığında çekici mesleği sunucudaki sosyal açıdan en ilgi çekici rollerden biri haline gelir; çünkü her çağrı arızalanmış veya yasa dışı park edilmiş aracı için yardıma ihtiyacı olan başka bir oyuncuyla doğrudan etkileşim içerir.
Meslek Kurulumu ve Mesai Sistemi
İşe, frameworkünüzün meslek sistemi içinde çekici mesleğini tanımlayarak başlayın. Oyuncular bir çekici otoparkı konumunda mesaiye başlayabilmeli, şirket çekicisini teslim alabilmeli ve dispatch çağrılarını kabul etmeye başlayabilmelidir. Görev sistemi, dispatch sisteminin kimin müsait olduğunu bilmesi için aktif çekici sürücülerini takip etmelidir. Bir oyuncu mesaiye girdiğinde, önceden tanımlanmış çekici araç modelleri listesinden bir flatbed kamyon doğurun ve bunu o oyuncuya atayın. Mesaiyi bitirdiklerinde şirket aracı iade edilmeli veya silinmelidir. Oyuncunun mesai durumunu, düzgün bir şekilde kalıcı olması ve dispatch sistemi tarafından sorgulanabilmesi için sunucu tarafında saklayın:
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)
Araç Eklenti Mekanikleri
Bir çekici sisteminin temel teknik zorluğu, bir aracı diğerine gerçekçi görünecek ve sürüş sırasında doğru davranacak şekilde bağlamaktır. FiveM bunun için şu özelliği sunar: AttachEntityToEntity bu amaç için native, ancak ofset konumlarını doğru ayarlamak her çekici modeli için dikkatli bir kalibrasyon gerektirir. Kasalı (flatbed) yaklaşım çoğu araç için en iyi sonucu verir: çekilen araba, kasa platformunun üzerine uygun ofsetlerle yerleştirilir, böylece kasanın üstünde doğal bir şekilde durur. Kancalı çekicilerde, çekilen aracın ön tekerlekleri kaldırılırken arka tekerlekleri yerde kalır. Her bağlama stili farklı ofset vektörlerine ve rotasyon değerlerine ihtiyaç duyar. Araç çekicinin içinden geçmeden düzgün bir şekilde oturana kadar küçük ayarlamalar yaparak bu değerleri oyun içinde test edin:
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
Önemli bir ayrıntı, fizik aksaklıklarını önlemek için bağlanan araçta çarpışmayı (collision) devre dışı bırakmaktır. İki aracın da çarpışması aktifken birbirine bağlanması durumunda, oyun motoru şiddetli sarsıntılara yol açabilir veya araçları havaya fırlatabilir. Hedef aracın konumunu dondurmak ve bağlıyken çarpışmasını devre dışı bırakmak bu sorunları tamamen ortadan kaldırır.
Dispatch ve Çekici Talebi Sistemi
Dispatch sistemi, çekiciye ihtiyacı olan oyuncuları müsait çekici sürücüleriyle buluşturur. Oyuncular telefonları aracılığıyla, bir tamirhane NPC'si üzerinden veya haritanın dört bir yanına yerleştirilmiş yol kenarı acil çağrı kutularından çekici talep edebilirler. Bir talep geldiğinde, sunucu müsait çekici sürücülerini kontrol eder ve çağrıyı en yakındakine yönlendirir. Görevde sürücü yoksa, daha yüksek bir ücret alan ve bir gecikmenin ardından aracı çekilmiş araç otoparkına (impound) ışınlayan yedek bir NPC çekici hizmeti sağlayın. Dispatch bildirimi, talep edenin konumunu, araç modelini ve arıza, kaza veya hatalı park etme gibi çekilme nedenini içermelidir. Çekici sürücüsüne çağrıyı kabul etme veya reddetme seçeneği sunun; reddedilen çağrılar bir sonraki en yakın sürücüye yönlendirilsin:
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)
Çekilmiş Araç Otoparkı Yönetimi
Çekilmiş ve el konulmuş araçlar, sahipleri tarafından teslim alınana kadar çekilmiş araç otoparkında (impound) saklanır. Çekilmiş araç veritabanını şu anda hangi araçların bağlı olduğunu, aracı kimin bağladığını, ne zaman bağlandığını ve serbest bırakılması için gereken ücreti takip edecek şekilde tasarlayın. Polisler trafik kontrollerinde veya kovalamacalardan sonra araçları doğrudan bağlayabilmeli, bu işlemler daha yüksek ücretler veya daha uzun zorunlu bekleme süreleri içermelidir. Otopark arayüzü, oyuncunun bağlı araçlarının listesini araç modeli, plaka, çekilme tarihi, sebep ve çıkarma ücreti gibi ayrıntılarla göstermelidir. Oyuncular ücreti otopark veznesinde öder ve araçları yakındaki belirlenmiş bir teslim noktasında doğar:
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)
Otomatik Park Denetimi
Yasaklı bölgelere park edilmiş araçları algılayan ve yapılandırılabilir bir ek sürenin ardından çekilmek üzere işaretleyen otomatik bir sistem ekleyin. Yangın muslukları, otobüs durakları, polis merkezleri ve hastane acil girişleri etrafında park yasağı bölgeleri tanımlayın. Bir oyuncu yasaklı bir bölgeye park edip uzaklaştığında bir zamanlayıcı başlatın. Ek süre dolduktan sonra araç hâlâ sahipsiz durumdaysa, bir çekici talebi oluşturun ve uygun çekici sürücülerine bildirin. Bu, oyuncuların manuel olarak servis çağırmasına gerek kalmadan çekici sürücüleri için doğal iş fırsatları yaratır. Sistem yalnızca oyunculara ait araçları işaretlemelidir, çünkü NPC araçları oyun motoru tarafından yönetilir ve doğal olarak kaybolur (despawn). Araç sahibine aracının çekilmek üzere işaretlendiğine dair bir bildirim ekleyin, böylece çekici gelmeden önce geri dönüp aracı çekme fırsatı tanıyın.
Gelir ve İlerleme
Çekici mesleğini, istikrarlı çalışmayı ödüllendiren bir ilerleme sistemiyle yapılandırın. Yeni çekici sürücüleri sıfırıncı rütbede temel çekici kamyonuna erişimle başlar ve tamamlanan çekme başına temel bir ücret kazanır. Tamamlanan çekme işlemlerini biriktirdikçe rütbe atlar ve kamyon, otobüs gibi daha büyük araçları taşıyabilen daha ağır çekici araçlarının, daha yüksek ödeme yapan polis otoparkı sözleşmelerinin ve özelleştirebilecekleri kişisel bir çekici kamyonunun kilidini açarlar. Tamamlanan toplam çekme sayısı, çekim sırasında katedilen mesafe ve çağrılara ortalama yanıt süresi gibi istatistikleri takip edin. Bu istatistikleri bir NPC veya telefon uygulaması aracılığıyla erişilebilen bir meslek panelinde görüntüleyin. Sürekli olarak hızlı yanıt veren ve araçlara zarar vermeden işleri tamamlayan çekici sürücülerinin ödemelerinde bonus çarpanları kazandığı, çağrıları görmezden gelen veya çekim sırasında araçlara zarar veren sürücülerin ise ücret kesintileriyle karşılaştığı bir itibar sistemi uygulamayı değerlendirin. Bu, çekici hizmetlerinin güvenilir ve profesyonel olmasını sağlayarak tüm sunucu topluluğuna fayda sağlayan doğal bir kalite teşviki oluşturur.
Uç Durumları ve Ağ İletişimini Yönetme
Çekici sistemlerinde, dikkatle ele alınması gereken birkaç zorlu uç durum bulunur. Bir çekici sürücüsü araç çekerken oyundan koptuğunda, sunucu bu bağlantı kopmasını tespit etmeli ve çekilen aracın havada süzülmemesi veya haritanın altına düşmemesi için aracı güvenli bir şekilde ayırıp yere indirmelidir. Araç sahibi, arabası bir çekiciye bağlıyken giriş yaparsa, araç listesinde aracını çekiliyor olarak işaretlenmiş görmeli ve çekici sürücüsü hakkında bilgileri görebilmelidir. Araç entity sahipliğini dikkatli yönetin, çünkü attachment native işlevi yalnızca bağlayan istemci her iki entity için de sahipliğe sahip olduğunda güvenilir çalışır. Kullanın: NetworkRequestControlOfEntity bağlama girişiminden önce çekici sürücüsünün istemcisinin her iki aracı da kontrol ettiğinden emin olmak için. Çok oyunculu senkronizasyonu için, bağlama durumunu yakındaki tüm oyunculara yayınlayın, böylece çekilen aracı ekranlarında doğru konumda görürler. State bag yapıları bu işlem için oldukça uygundur çünkü varlığın menzilindeki tüm istemcilere otomatik olarak kopyalanırlar.