414 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local QBCore = exports['qb-core']:GetCoreObject()
 | |
| local spawnedNPCs = {}
 | |
| local activeRentalVehicles = {}
 | |
| 
 | |
| -- NPCs spawnen
 | |
| CreateThread(function()
 | |
|     -- Warte kurz, bis die Welt geladen ist
 | |
|     Wait(2000)
 | |
|     
 | |
|     for i = 1, #Config.RentalLocations do
 | |
|         local location = Config.RentalLocations[i]
 | |
|         local modelHash = GetHashKey(location.npc.model)
 | |
|         
 | |
|         -- Modell laden
 | |
|         RequestModel(modelHash)
 | |
|         local timeout = 0
 | |
|         while not HasModelLoaded(modelHash) and timeout < 30 do
 | |
|             Wait(100)
 | |
|             timeout = timeout + 1
 | |
|         end
 | |
|         
 | |
|         if HasModelLoaded(modelHash) then
 | |
|             -- NPC erstellen
 | |
|             local coords = location.npc.coords
 | |
|             local npc = CreatePed(4, modelHash, coords.x, coords.y, coords.z - 1.0, coords.w, false, false)
 | |
|             
 | |
|             -- NPC-Eigenschaften setzen
 | |
|             FreezeEntityPosition(npc, true)
 | |
|             SetEntityInvincible(npc, true)
 | |
|             SetBlockingOfNonTemporaryEvents(npc, true)
 | |
|             SetPedDefaultComponentVariation(npc)
 | |
|             
 | |
|             -- NPC speichern
 | |
|             spawnedNPCs[location.id] = npc
 | |
|             
 | |
|             -- Debug-Info
 | |
|             print("NPC spawned at location: " .. location.name)
 | |
|             
 | |
|             -- QB-Target für NPC
 | |
|             exports['qb-target']:AddTargetEntity(npc, {
 | |
|                 options = {
 | |
|                     {
 | |
|                         type = "client",
 | |
|                         event = "vehiclerental:client:openMenu",
 | |
|                         icon = "fas fa-car",
 | |
|                         label = "Fahrzeug mieten",
 | |
|                         locationId = location.id
 | |
|                     },
 | |
|                     {
 | |
|                         type = "client",
 | |
|                         event = "vehiclerental:client:returnVehicle",
 | |
|                         icon = "fas fa-car-side",
 | |
|                         label = "Fahrzeug zurückgeben",
 | |
|                         locationId = location.id
 | |
|                     }
 | |
|                 },
 | |
|                 distance = 2.0
 | |
|             })
 | |
|         else
 | |
|             print("Failed to load NPC model for location: " .. location.name)
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Mietmenü öffnen
 | |
| RegisterNetEvent('vehiclerental:client:openMenu', function(data)
 | |
|     local locationId = data.locationId
 | |
|     local location = nil
 | |
|     
 | |
|     for i = 1, #Config.RentalLocations do
 | |
|         if Config.RentalLocations[i].id == locationId then
 | |
|             location = Config.RentalLocations[i]
 | |
|             break
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     if not location then return end
 | |
| 
 | |
|     local options = {}
 | |
|     for i = 1, #location.vehicles do
 | |
|         local vehicle = location.vehicles[i]
 | |
|         table.insert(options, {
 | |
|             title = vehicle.label,
 | |
|             description = '$' .. vehicle.price .. ' pro Stunde',
 | |
|             icon = 'car',
 | |
|             onSelect = function()
 | |
|                 openRentalDialog(vehicle, location)
 | |
|             end
 | |
|         })
 | |
|     end
 | |
| 
 | |
|     lib.registerContext({
 | |
|         id = 'vehicle_rental_menu',
 | |
|         title = location.name,
 | |
|         options = options
 | |
|     })
 | |
| 
 | |
|     lib.showContext('vehicle_rental_menu')
 | |
| end)
 | |
| 
 | |
| -- Mietdialog
 | |
| function openRentalDialog(vehicle, location)
 | |
|     local input = lib.inputDialog('Fahrzeug mieten', {
 | |
|         {
 | |
|             type = 'number',
 | |
|             label = 'Mietdauer (Stunden)',
 | |
|             description = 'Maximale Mietdauer: ' .. Config.MaxRentalTime .. ' Stunden',
 | |
|             required = true,
 | |
|             min = 1,
 | |
|             max = Config.MaxRentalTime
 | |
|         }
 | |
|     })
 | |
| 
 | |
|     if not input or not input[1] then return end
 | |
| 
 | |
|     local hours = tonumber(input[1])
 | |
|     if not hours or hours < 1 or hours > Config.MaxRentalTime then
 | |
|         QBCore.Functions.Notify('Ungültige Mietdauer!', 'error')
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     local totalCost = vehicle.price * hours
 | |
|     local plate = GeneratePlate()
 | |
| 
 | |
|     QBCore.Functions.TriggerCallback('vehiclerental:server:rentVehicle', function(success)
 | |
|         if success then
 | |
|             spawnRentalVehicle(vehicle.model, location.spawnPoint, plate)
 | |
|         end
 | |
|     end, {
 | |
|         vehicleModel = vehicle.model,
 | |
|         pricePerHour = vehicle.price,
 | |
|         hours = hours,
 | |
|         locationId = location.id,
 | |
|         plate = plate
 | |
|     })
 | |
| end
 | |
| 
 | |
| -- Fahrzeug spawnen
 | |
| function spawnRentalVehicle(model, spawnPoint, plate)
 | |
|     RequestModel(model)
 | |
|     while not HasModelLoaded(model) do
 | |
|         Wait(1)
 | |
|     end
 | |
| 
 | |
|     local vehicle = CreateVehicle(model, spawnPoint.x, spawnPoint.y, spawnPoint.z, spawnPoint.w, true, false)
 | |
|     SetVehicleNumberPlateText(vehicle, plate)
 | |
|     SetEntityAsMissionEntity(vehicle, true, true)
 | |
|     TaskWarpPedIntoVehicle(PlayerPedId(), vehicle, -1)
 | |
|     
 | |
|     TriggerEvent("vehiclekeys:client:SetOwner", plate)
 | |
|     SetModelAsNoLongerNeeded(model)
 | |
|     
 | |
|     -- Speichere das Fahrzeug lokal
 | |
|     activeRentalVehicles[plate] = vehicle
 | |
|     
 | |
|     -- Speichere die initiale Position
 | |
|     local pos = GetEntityCoords(vehicle)
 | |
|     local rot = GetEntityRotation(vehicle)
 | |
|     TriggerServerEvent('vehiclerental:server:updatePosition', plate, pos, rot)
 | |
|     
 | |
|     print("Neues Mietfahrzeug erstellt: " .. plate)
 | |
| end
 | |
| 
 | |
| -- Fahrzeug zurückgeben
 | |
| RegisterNetEvent('vehiclerental:client:returnVehicle', function(data)
 | |
|     -- Hole alle aktiven Mietverhältnisse des Spielers
 | |
|     QBCore.Functions.TriggerCallback('vehiclerental:server:getPlayerRentals', function(rentals)
 | |
|         if not rentals or #rentals == 0 then
 | |
|             QBCore.Functions.Notify('Du hast keine aktiven Mietverhältnisse!', 'error')
 | |
|             return
 | |
|         end
 | |
| 
 | |
|         -- Erstelle Menü mit allen gemieteten Fahrzeugen
 | |
|         local options = {}
 | |
|         for i = 1, #rentals do
 | |
|             local rental = rentals[i]
 | |
|             
 | |
|             table.insert(options, {
 | |
|                 title = rental.vehicle_model .. " - " .. rental.vehicle_plate,
 | |
|                 description = "Zurückgeben " .. rental.timeText,
 | |
|                 icon = 'car',
 | |
|                 onSelect = function()
 | |
|                     returnSpecificVehicle(rental.vehicle_plate, data.locationId)
 | |
|                 end
 | |
|             })
 | |
|         end
 | |
| 
 | |
|         lib.registerContext({
 | |
|             id = 'return_vehicle_menu',
 | |
|             title = 'Fahrzeug zurückgeben',
 | |
|             options = options
 | |
|         })
 | |
| 
 | |
|         lib.showContext('return_vehicle_menu')
 | |
|     end)
 | |
| end)
 | |
| 
 | |
| -- Spezifisches Fahrzeug zurückgeben
 | |
| function returnSpecificVehicle(plate, locationId)
 | |
|     -- Finde die Location-Daten
 | |
|     local location = nil
 | |
|     for i = 1, #Config.RentalLocations do
 | |
|         if Config.RentalLocations[i].id == locationId then
 | |
|             location = Config.RentalLocations[i]
 | |
|             break
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     if not location then
 | |
|         QBCore.Functions.Notify('Fehler beim Finden des Rückgabeorts!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     -- Definiere den Rückgabeort
 | |
|     local returnPoint = vector3(location.returnPoint.x, location.returnPoint.y, location.returnPoint.z)
 | |
|     
 | |
|     -- Finde das Fahrzeug in der Nähe
 | |
|     local vehicle = nil
 | |
|     local closestDistance = 50.0 -- Maximale Suchentfernung
 | |
|     
 | |
|     -- Suche nach dem Fahrzeug mit dem Kennzeichen
 | |
|     for veh in EnumerateVehicles() do
 | |
|         local vehPlate = GetVehicleNumberPlateText(veh)
 | |
|         if string.gsub(vehPlate, "%s+", "") == string.gsub(plate, "%s+", "") then
 | |
|             local vehPos = GetEntityCoords(veh)
 | |
|             local distance = #(returnPoint - vehPos)
 | |
|             
 | |
|             if distance < closestDistance then
 | |
|                 vehicle = veh
 | |
|                 closestDistance = distance
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     if not vehicle then
 | |
|         QBCore.Functions.Notify('Fahrzeug nicht in der Nähe des Rückgabeorts gefunden!', 'error')
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     -- Prüfe ob das Fahrzeug nahe genug am Rückgabeort ist
 | |
|     local vehPos = GetEntityCoords(vehicle)
 | |
|     local distance = #(returnPoint - vehPos)
 | |
|     
 | |
|     if distance > Config.MaxReturnDistance then
 | |
|         QBCore.Functions.Notify('Das Fahrzeug muss näher am Rückgabeort sein! (Max. ' .. Config.MaxReturnDistance .. 'm)', 'error')
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     -- Fahrzeug zurückgeben
 | |
|     QBCore.Functions.TriggerCallback('vehiclerental:server:returnVehicle', function(success)
 | |
|         if success then
 | |
|             DeleteVehicle(vehicle)
 | |
|             activeRentalVehicles[plate] = nil
 | |
|             QBCore.Functions.Notify('Fahrzeug erfolgreich zurückgegeben!', 'success')
 | |
|         end
 | |
|     end, plate)
 | |
| end
 | |
| 
 | |
| -- Lade alle aktiven Mietfahrzeuge
 | |
| RegisterNetEvent('vehiclerental:client:loadRentals')
 | |
| AddEventHandler('vehiclerental:client:loadRentals', function(rentals)
 | |
|     -- Lösche alle vorhandenen Mietfahrzeuge
 | |
|     for plate, vehicle in pairs(activeRentalVehicles) do
 | |
|         if DoesEntityExist(vehicle) then
 | |
|             DeleteVehicle(vehicle)
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     activeRentalVehicles = {}
 | |
|     
 | |
|     -- Spawne alle aktiven Mietfahrzeuge
 | |
|     for _, rental in ipairs(rentals) do
 | |
|         -- Prüfe, ob das Fahrzeug dem aktuellen Spieler gehört
 | |
|         local playerCitizenId = QBCore.Functions.GetPlayerData().citizenid
 | |
|         
 | |
|         if rental.citizenid == playerCitizenId then
 | |
|             -- Spawne das Fahrzeug nur, wenn es eine Position hat
 | |
|             if rental.posX ~= 0 or rental.posY ~= 0 or rental.posZ ~= 0 then
 | |
|                 Citizen.CreateThread(function()
 | |
|                     local model = rental.vehicle_model
 | |
|                     local plate = rental.vehicle_plate
 | |
|                     
 | |
|                     -- Lade das Modell
 | |
|                     RequestModel(model)
 | |
|                     while not HasModelLoaded(model) do
 | |
|                         Citizen.Wait(10)
 | |
|                     end
 | |
|                     
 | |
|                     -- Spawne das Fahrzeug
 | |
|                     local vehicle = CreateVehicle(model, 
 | |
|                         rental.posX, rental.posY, rental.posZ, 
 | |
|                         rental.rotZ or 0.0, true, false)
 | |
|                     
 | |
|                     -- Setze Eigenschaften
 | |
|                     SetVehicleNumberPlateText(vehicle, plate)
 | |
|                     SetEntityAsMissionEntity(vehicle, true, true)
 | |
|                     SetVehicleDoorsLocked(vehicle, 2) -- Abgeschlossen
 | |
|                     
 | |
|                     -- Setze Rotation
 | |
|                     SetEntityRotation(vehicle, 
 | |
|                         rental.rotX or 0.0, 
 | |
|                         rental.rotY or 0.0, 
 | |
|                         rental.rotZ or 0.0, 
 | |
|                         2, true)
 | |
|                     
 | |
|                     -- Gib dem Spieler die Schlüssel
 | |
|                     TriggerEvent("vehiclekeys:client:SetOwner", plate)
 | |
|                     
 | |
|                     -- Speichere das Fahrzeug lokal
 | |
|                     activeRentalVehicles[plate] = vehicle
 | |
|                     
 | |
|                     print("Mietfahrzeug geladen: " .. plate)
 | |
|                 end)
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Fahrzeug zurückgegeben
 | |
| RegisterNetEvent('vehiclerental:client:vehicleReturned')
 | |
| AddEventHandler('vehiclerental:client:vehicleReturned', function(plate)
 | |
|     -- Lösche das Fahrzeug, wenn es existiert
 | |
|     if activeRentalVehicles[plate] and DoesEntityExist(activeRentalVehicles[plate]) then
 | |
|         DeleteVehicle(activeRentalVehicles[plate])
 | |
|         activeRentalVehicles[plate] = nil
 | |
|         print("Mietfahrzeug gelöscht: " .. plate)
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Regelmäßiges Update der Fahrzeugpositionen
 | |
| Citizen.CreateThread(function()
 | |
|     while true do
 | |
|         Citizen.Wait(30000) -- Alle 30 Sekunden
 | |
|         
 | |
|         -- Update alle aktiven Mietfahrzeuge
 | |
|         for plate, vehicle in pairs(activeRentalVehicles) do
 | |
|             if DoesEntityExist(vehicle) then
 | |
|                 local pos = GetEntityCoords(vehicle)
 | |
|                 local rot = GetEntityRotation(vehicle)
 | |
|                 TriggerServerEvent('vehiclerental:server:updatePosition', plate, pos, rot)
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Wenn der Spieler das Fahrzeug verlässt, aktualisiere die Position
 | |
| Citizen.CreateThread(function()
 | |
|     while true do
 | |
|         Citizen.Wait(500)
 | |
|         
 | |
|         local playerPed = PlayerPedId()
 | |
|         local vehicle = GetVehiclePedIsIn(playerPed, true)
 | |
|         
 | |
|         if vehicle ~= 0 then
 | |
|             local plate = GetVehicleNumberPlateText(vehicle)
 | |
|             
 | |
|             -- Wenn es ein Mietfahrzeug ist und der Spieler es gerade verlassen hat
 | |
|             if activeRentalVehicles[plate] and not IsPedInVehicle(playerPed, vehicle, false) then
 | |
|                 local pos = GetEntityCoords(vehicle)
 | |
|                 local rot = GetEntityRotation(vehicle)
 | |
|                 TriggerServerEvent('vehiclerental:server:updatePosition', plate, pos, rot)
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Fahrzeug-Enumerator
 | |
| function EnumerateVehicles()
 | |
|     return coroutine.wrap(function()
 | |
|         local handle, vehicle = FindFirstVehicle()
 | |
|         local success
 | |
|         repeat
 | |
|             coroutine.yield(vehicle)
 | |
|             success, vehicle = FindNextVehicle(handle)
 | |
|         until not success
 | |
|         EndFindVehicle(handle)
 | |
|     end)
 | |
| end
 | |
| 
 | |
| -- Kennzeichen generieren (RENT + 4 zufällige Zeichen)
 | |
| function GeneratePlate()
 | |
|     local plate = "RENT"
 | |
|     for i = 1, 4 do
 | |
|         if math.random(1, 2) == 1 then
 | |
|             plate = plate .. string.char(math.random(65, 90)) -- A-Z
 | |
|         else
 | |
|             plate = plate .. tostring(math.random(0, 9)) -- 0-9
 | |
|         end
 | |
|     end
 | |
|     return plate
 | |
| end
 | |
| 
 | |
| -- Cleanup beim Ressourcen-Stop
 | |
| AddEventHandler('onResourceStop', function(resourceName)
 | |
|     if GetCurrentResourceName() ~= resourceName then return end
 | |
|     
 | |
|     for _, npc in pairs(spawnedNPCs) do
 | |
|         if DoesEntityExist(npc) then
 | |
|             DeleteEntity(npc)
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     for plate, vehicle in pairs(activeRentalVehicles) do
 | |
|         if DoesEntityExist(vehicle) then
 | |
|             DeleteVehicle(vehicle)
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Überprüfe Mietfahrzeug-Schlüssel beim Laden des Spielers
 | |
| RegisterNetEvent('QBCore:Client:OnPlayerLoaded')
 | |
| AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
 | |
|     TriggerServerEvent('vehiclerental:server:checkRentalKeys')
 | |
| end)
 | 
