1595 lines
		
	
	
	
		
			60 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1595 lines
		
	
	
	
		
			60 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
 | 
						|
local currentTaxi = nil
 | 
						|
local currentDriver = nil
 | 
						|
local taxiBlip = nil
 | 
						|
local mapBlip = nil
 | 
						|
local destinationBlip = nil
 | 
						|
local taxiMeter = {
 | 
						|
    isRunning = false,
 | 
						|
    startCoords = nil,
 | 
						|
    currentFare = 0,
 | 
						|
    pricePerKm = 0
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function DebugPrint(type, message)
 | 
						|
    if Config.Debug then
 | 
						|
        if type == "error" then
 | 
						|
            print("^1[TAXI DEBUG]^7 " .. message)
 | 
						|
        elseif type == "warning" then
 | 
						|
            print("^3[TAXI DEBUG]^7 " .. message)
 | 
						|
        else -- success/info
 | 
						|
            print("^2[TAXI DEBUG]^7 " .. message)
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
local QBCore = exports['qb-core']:GetCoreObject()
 | 
						|
local currentTaxi = nil
 | 
						|
 | 
						|
DebugPrint("^2[TAXI DEBUG]^7 Main script loaded")
 | 
						|
 | 
						|
-- Taxi rufen Command
 | 
						|
RegisterCommand('taxi', function()
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Taxi command executed")
 | 
						|
    
 | 
						|
    if currentTaxi and DoesEntityExist(currentTaxi) then
 | 
						|
        DebugPrint("^1[TAXI DEBUG]^7 Taxi already exists")
 | 
						|
        lib.notify({
 | 
						|
            title = 'Taxi Service',
 | 
						|
            description = 'Du hast bereits ein Taxi gerufen',
 | 
						|
            type = 'error'
 | 
						|
        })
 | 
						|
        return
 | 
						|
    end
 | 
						|
    
 | 
						|
    CallTaxi()
 | 
						|
end)
 | 
						|
 | 
						|
function CallTaxi()
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 CallTaxi function started")
 | 
						|
    
 | 
						|
    lib.notify({
 | 
						|
        title = 'Taxi Service',
 | 
						|
        description = 'Taxi wird gerufen...',
 | 
						|
        type = 'info'
 | 
						|
    })
 | 
						|
    
 | 
						|
    CreateThread(function()
 | 
						|
        local playerPed = PlayerPedId()
 | 
						|
        local playerCoords = GetEntityCoords(playerPed)
 | 
						|
        
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Player coords: " .. tostring(playerCoords))
 | 
						|
        
 | 
						|
        -- Verbesserte Spawn-Position für Taxi finden
 | 
						|
        local spawnCoords = GetImprovedTaxiSpawnPosition(playerCoords)
 | 
						|
        if not spawnCoords then
 | 
						|
            DebugPrint("^1[TAXI DEBUG]^7 No spawn position found")
 | 
						|
            lib.notify({
 | 
						|
                title = 'Taxi Service',
 | 
						|
                description = 'Kein geeigneter Spawn-Punkt gefunden',
 | 
						|
                type = 'error'
 | 
						|
            })
 | 
						|
            return
 | 
						|
        end
 | 
						|
        
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Spawn coords found: " .. tostring(spawnCoords.x) .. ", " .. tostring(spawnCoords.y) .. ", " .. tostring(spawnCoords.z))
 | 
						|
        
 | 
						|
        -- Taxi spawnen
 | 
						|
        local taxi = SpawnTaxi(spawnCoords)
 | 
						|
        if not taxi then
 | 
						|
            DebugPrint("^1[TAXI DEBUG]^7 Failed to spawn taxi")
 | 
						|
            lib.notify({
 | 
						|
                title = 'Taxi Service',
 | 
						|
                description = 'Taxi konnte nicht gespawnt werden',
 | 
						|
                type = 'error'
 | 
						|
            })
 | 
						|
            return
 | 
						|
        end
 | 
						|
        
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Taxi spawned: " .. taxi)
 | 
						|
        currentTaxi = taxi
 | 
						|
        
 | 
						|
        -- Fahrer spawnen
 | 
						|
        local driver = SpawnTaxiDriver(taxi)
 | 
						|
        if driver then
 | 
						|
            currentDriver = driver
 | 
						|
            DebugPrint("^2[TAXI DEBUG]^7 Driver spawned: " .. driver)
 | 
						|
            
 | 
						|
            -- Verbesserte Navigation zum Spieler
 | 
						|
            NavigateToPlayer(driver, taxi, playerCoords)
 | 
						|
            
 | 
						|
            -- Blip für Taxi erstellen (Entity-Blip)
 | 
						|
            CreateTaxiBlips(taxi)
 | 
						|
            
 | 
						|
            -- Überwachung der Ankunft
 | 
						|
            MonitorTaxiArrival(taxi, driver, playerCoords)
 | 
						|
            
 | 
						|
            lib.notify({
 | 
						|
                title = 'Taxi Service',
 | 
						|
                description = 'Taxi ist unterwegs zu dir! Verfolge es auf der Karte.',
 | 
						|
                type = 'success'
 | 
						|
            })
 | 
						|
        else
 | 
						|
            DebugPrint("^1[TAXI DEBUG]^7 Failed to spawn driver")
 | 
						|
            lib.notify({
 | 
						|
                title = 'Taxi Service',
 | 
						|
                description = 'Taxi ohne Fahrer gespawnt - Du kannst es selbst fahren',
 | 
						|
                type = 'warning'
 | 
						|
            })
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
function GetImprovedTaxiSpawnPosition(playerCoords)
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Finding improved spawn position...")
 | 
						|
    
 | 
						|
    -- Minimale und maximale Entfernung zum Spieler
 | 
						|
    local minAcceptableDistance = 50.0  -- Mindestens 50 Meter entfernt
 | 
						|
    local maxAcceptableDistance = 150.0 -- Maximal 150 Meter entfernt
 | 
						|
    local bestPosition = nil
 | 
						|
    local bestDistance = 999999.0
 | 
						|
    
 | 
						|
    -- Versuche zuerst einen Straßenknotenpunkt zu finden
 | 
						|
    local roadPosition = nil
 | 
						|
    local foundNode = false
 | 
						|
    local nodePos = vector3(0.0, 0.0, 0.0)
 | 
						|
    
 | 
						|
    -- Versuche einen Straßenknotenpunkt in optimaler Entfernung zu finden
 | 
						|
    -- Suche in einem größeren Radius, um mehr Optionen zu haben
 | 
						|
    foundNode, nodePos = GetClosestVehicleNode(playerCoords.x, playerCoords.y, playerCoords.z, 1, 3.0, 0)
 | 
						|
    
 | 
						|
    if foundNode then
 | 
						|
        local nodeDistance = #(playerCoords - nodePos)
 | 
						|
        if nodeDistance >= minAcceptableDistance and nodeDistance <= maxAcceptableDistance then
 | 
						|
            roadPosition = nodePos
 | 
						|
            DebugPrint("^2[TAXI DEBUG]^7 Found road node for spawn at distance: " .. nodeDistance)
 | 
						|
            return {x = roadPosition.x, y = roadPosition.y, z = roadPosition.z, w = 0.0}
 | 
						|
        else
 | 
						|
            -- Speichern für später, falls wir nichts Besseres finden
 | 
						|
            if nodeDistance < bestDistance and nodeDistance >= minAcceptableDistance then
 | 
						|
                bestDistance = nodeDistance
 | 
						|
                bestPosition = {x = nodePos.x, y = nodePos.y, z = nodePos.z, w = 0.0}
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Versuche mehrere Knotenpunkte in verschiedenen Richtungen zu finden
 | 
						|
    for i = 1, 8 do
 | 
						|
        local angle = (i - 1) * 45.0 -- 8 Richtungen (0, 45, 90, 135, 180, 225, 270, 315 Grad)
 | 
						|
        local searchDistance = (minAcceptableDistance + maxAcceptableDistance) / 2
 | 
						|
        local searchX = playerCoords.x + math.cos(math.rad(angle)) * searchDistance
 | 
						|
        local searchY = playerCoords.y + math.sin(math.rad(angle)) * searchDistance
 | 
						|
        
 | 
						|
        foundNode, nodePos = GetClosestVehicleNodeWithHeading(searchX, searchY, playerCoords.z, 1, 3.0, 0)
 | 
						|
        
 | 
						|
        if foundNode then
 | 
						|
            local nodeDistance = #(playerCoords - nodePos)
 | 
						|
            if nodeDistance >= minAcceptableDistance and nodeDistance <= maxAcceptableDistance then
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Found directional node for spawn at distance: " .. nodeDistance .. " in direction " .. angle)
 | 
						|
                return {x = nodePos.x, y = nodePos.y, z = nodePos.z, w = 0.0}
 | 
						|
            else
 | 
						|
                -- Speichern für später, falls wir nichts Besseres finden
 | 
						|
                if nodeDistance < bestDistance and nodeDistance >= minAcceptableDistance then
 | 
						|
                    bestDistance = nodeDistance
 | 
						|
                    bestPosition = {x = nodePos.x, y = nodePos.y, z = nodePos.z, w = 0.0}
 | 
						|
                end
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Versuche einen weiteren Knotenpunkt mit größerem Radius
 | 
						|
    foundNode, nodePos = GetClosestMajorVehicleNode(playerCoords.x, playerCoords.y, playerCoords.z, 100.0, 0)
 | 
						|
    
 | 
						|
    if foundNode then
 | 
						|
        local nodeDistance = #(playerCoords - nodePos)
 | 
						|
        if nodeDistance >= minAcceptableDistance and nodeDistance <= maxAcceptableDistance then
 | 
						|
            roadPosition = nodePos
 | 
						|
            DebugPrint("^2[TAXI DEBUG]^7 Found major road node for spawn at distance: " .. nodeDistance)
 | 
						|
            return {x = roadPosition.x, y = roadPosition.y, z = roadPosition.z, w = 0.0}
 | 
						|
        else
 | 
						|
            -- Speichern für später, falls wir nichts Besseres finden
 | 
						|
            if nodeDistance < bestDistance and nodeDistance >= minAcceptableDistance then
 | 
						|
                bestDistance = nodeDistance
 | 
						|
                bestPosition = {x = nodePos.x, y = nodePos.y, z = nodePos.z, w = 0.0}
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Fallback auf Config-Positionen
 | 
						|
    if Config.MobileTaxiSpawns and #Config.MobileTaxiSpawns > 0 then
 | 
						|
        -- Alle Spawn-Positionen nach Entfernung sortieren
 | 
						|
        local sortedSpawns = {}
 | 
						|
        for i, spawnPos in ipairs(Config.MobileTaxiSpawns) do
 | 
						|
            local distance = #(playerCoords - vector3(spawnPos.x, spawnPos.y, spawnPos.z))
 | 
						|
            if distance >= minAcceptableDistance then
 | 
						|
                table.insert(sortedSpawns, {
 | 
						|
                    coords = spawnPos,
 | 
						|
                    distance = distance
 | 
						|
                })
 | 
						|
            end
 | 
						|
        end
 | 
						|
        
 | 
						|
        -- Nach Entfernung sortieren (nächste zuerst, aber mindestens minAcceptableDistance entfernt)
 | 
						|
        table.sort(sortedSpawns, function(a, b)
 | 
						|
            return a.distance < b.distance
 | 
						|
        end)
 | 
						|
        
 | 
						|
        -- Prüfen ob die nächsten Positionen frei und nah genug sind
 | 
						|
        for i, spawn in ipairs(sortedSpawns) do
 | 
						|
            local spawnCoords = spawn.coords
 | 
						|
            
 | 
						|
            -- Wenn Position zu weit weg ist, überspringen
 | 
						|
            if spawn.distance > maxAcceptableDistance then
 | 
						|
                -- Speichern für später, falls wir nichts Besseres finden
 | 
						|
                if spawn.distance < bestDistance then
 | 
						|
                    bestDistance = spawn.distance
 | 
						|
                    bestPosition = spawnCoords
 | 
						|
                end
 | 
						|
                goto continue
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Prüfen ob Position frei ist
 | 
						|
            local clearArea = true
 | 
						|
            local vehicles = GetGamePool('CVehicle')
 | 
						|
            
 | 
						|
            -- Prüfen ob andere Fahrzeuge in der Nähe sind
 | 
						|
            for _, vehicle in ipairs(vehicles) do
 | 
						|
                local vehCoords = GetEntityCoords(vehicle)
 | 
						|
                if #(vector3(spawnCoords.x, spawnCoords.y, spawnCoords.z) - vehCoords) < 5.0 then
 | 
						|
                    clearArea = false
 | 
						|
                    break
 | 
						|
                end
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Wenn Position frei ist, verwenden
 | 
						|
            if clearArea then
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Using spawn position from config: " .. tostring(spawnCoords.x) .. ", " .. tostring(spawnCoords.y) .. ", " .. tostring(spawnCoords.z) .. " (Distance: " .. spawn.distance .. "m)")
 | 
						|
                return spawnCoords
 | 
						|
            end
 | 
						|
            
 | 
						|
            ::continue::
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Wenn wir hier sind, haben wir keine ideale Position gefunden
 | 
						|
    -- Wenn wir eine "beste" Position haben, die den Mindestabstand einhält, verwenden wir diese
 | 
						|
    if bestPosition and bestDistance >= minAcceptableDistance then
 | 
						|
        DebugPrint("^3[TAXI DEBUG]^7 Using best available position at " .. bestDistance .. "m")
 | 
						|
        return bestPosition
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Wenn alles fehlschlägt: Generiere eine zufällige Position in der Nähe des Spielers
 | 
						|
    DebugPrint("^3[TAXI DEBUG]^7 Generating random position between " .. minAcceptableDistance .. "m and " .. maxAcceptableDistance .. "m of player")
 | 
						|
    
 | 
						|
    -- Versuche bis zu 15 Mal, eine gültige Position zu finden
 | 
						|
    for attempt = 1, 15 do
 | 
						|
        -- Zufällige Position im Umkreis, aber mindestens minAcceptableDistance entfernt
 | 
						|
        local angle = math.random() * 2 * math.pi
 | 
						|
        local distance = math.random(minAcceptableDistance, maxAcceptableDistance)
 | 
						|
        local x = playerCoords.x + math.cos(angle) * distance
 | 
						|
        local y = playerCoords.y + math.sin(angle) * distance
 | 
						|
        local z = playerCoords.z
 | 
						|
        
 | 
						|
        -- Versuche eine gültige Z-Koordinate zu bekommen
 | 
						|
        local success, groundZ = GetGroundZFor_3dCoord(x, y, z, true)
 | 
						|
        if success then
 | 
						|
            -- Prüfe ob die Position auf einer Straße ist
 | 
						|
            local isOnRoad = IsPointOnRoad(x, y, groundZ)
 | 
						|
            
 | 
						|
            if isOnRoad then
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Found random position on road at distance: " .. distance)
 | 
						|
                return {x = x, y = y, z = groundZ, w = 0.0}
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Absolute Notfall-Fallback: Einfach irgendwo in der Nähe, aber mindestens minAcceptableDistance entfernt
 | 
						|
    local angle = math.random() * 2 * math.pi
 | 
						|
    local distance = math.random(minAcceptableDistance, maxAcceptableDistance)
 | 
						|
    local x = playerCoords.x + math.cos(angle) * distance
 | 
						|
    local y = playerCoords.y + math.sin(angle) * distance
 | 
						|
    local z = playerCoords.z
 | 
						|
    
 | 
						|
    -- Versuche eine gültige Z-Koordinate zu bekommen
 | 
						|
    local success, groundZ = GetGroundZFor_3dCoord(x, y, z, true)
 | 
						|
    if success then
 | 
						|
        z = groundZ
 | 
						|
    end
 | 
						|
    
 | 
						|
    DebugPrint("^3[TAXI DEBUG]^7 Using emergency random spawn position at distance: " .. distance)
 | 
						|
    return {x = x, y = y, z = z, w = 0.0}
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
function SpawnTaxi(coords)
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Spawning taxi at: " .. tostring(coords.x) .. ", " .. tostring(coords.y) .. ", " .. tostring(coords.z))
 | 
						|
    
 | 
						|
    -- Sicherstellen dass wir ein gültiges Taxi-Model haben
 | 
						|
    local taxiModel = nil
 | 
						|
    if Config.TaxiVehicles and #Config.TaxiVehicles > 0 then
 | 
						|
        -- Zufälliges Taxi-Model aus Config wählen
 | 
						|
        local randomIndex = math.random(1, #Config.TaxiVehicles)
 | 
						|
        taxiModel = GetHashKey(Config.TaxiVehicles[randomIndex].model)
 | 
						|
    else
 | 
						|
        taxiModel = GetHashKey("taxi") -- Fallback
 | 
						|
    end
 | 
						|
    
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Taxi model hash: " .. taxiModel)
 | 
						|
    
 | 
						|
    -- Model laden mit Timeout
 | 
						|
    RequestModel(taxiModel)
 | 
						|
    local modelLoaded = false
 | 
						|
    local timeout = GetGameTimer() + 10000
 | 
						|
    
 | 
						|
    while not modelLoaded and GetGameTimer() < timeout do
 | 
						|
        modelLoaded = HasModelLoaded(taxiModel)
 | 
						|
        if not modelLoaded then
 | 
						|
            DebugPrint("^3[TAXI DEBUG]^7 Waiting for taxi model to load...")
 | 
						|
            Wait(100)
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    if not modelLoaded then
 | 
						|
        DebugPrint("^1[TAXI DEBUG]^7 Failed to load taxi model! Trying default model.")
 | 
						|
        SetModelAsNoLongerNeeded(taxiModel)
 | 
						|
        
 | 
						|
        -- Versuche Standard-Taxi als Fallback
 | 
						|
        taxiModel = GetHashKey("taxi")
 | 
						|
        RequestModel(taxiModel)
 | 
						|
        
 | 
						|
        timeout = GetGameTimer() + 5000
 | 
						|
        while not HasModelLoaded(taxiModel) and GetGameTimer() < timeout do
 | 
						|
            Wait(100)
 | 
						|
        end
 | 
						|
        
 | 
						|
        if not HasModelLoaded(taxiModel) then
 | 
						|
            DebugPrint("^1[TAXI DEBUG]^7 Failed to load default taxi model!")
 | 
						|
            return nil
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Fahrzeug erstellen
 | 
						|
    local taxi = CreateVehicle(taxiModel, coords.x, coords.y, coords.z, coords.w or 0.0, true, false)
 | 
						|
    
 | 
						|
    if not DoesEntityExist(taxi) then
 | 
						|
        DebugPrint("^1[TAXI DEBUG]^7 Failed to create taxi vehicle!")
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Taxi created successfully: " .. taxi)
 | 
						|
    
 | 
						|
    -- Fahrzeug konfigurieren
 | 
						|
    SetEntityAsMissionEntity(taxi, true, true)
 | 
						|
    SetVehicleOnGroundProperly(taxi)
 | 
						|
    SetVehicleEngineOn(taxi, true, true, false)
 | 
						|
    SetVehicleDoorsLocked(taxi, 2) -- Locked initially
 | 
						|
    
 | 
						|
    -- Verbesserte Persistenz
 | 
						|
    SetEntityInvincible(taxi, true)
 | 
						|
    SetVehicleCanBeVisiblyDamaged(taxi, false)
 | 
						|
    SetEntityProofs(taxi, true, true, true, true, true, true, true, true)
 | 
						|
    SetVehicleExplodesOnHighExplosionDamage(taxi, false)
 | 
						|
    SetVehicleHasBeenOwnedByPlayer(taxi, true)
 | 
						|
    SetVehicleIsConsideredByPlayer(taxi, true)
 | 
						|
    
 | 
						|
    -- Taxi-Livery setzen falls verfügbar
 | 
						|
    local liveryCount = GetVehicleLiveryCount(taxi)
 | 
						|
    if liveryCount > 0 then
 | 
						|
        SetVehicleLivery(taxi, 0) -- Erste Livery verwenden
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Taxi livery set")
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Fahrzeug-Extras aktivieren falls vorhanden
 | 
						|
    for i = 1, 14 do
 | 
						|
        if DoesExtraExist(taxi, i) then
 | 
						|
            SetVehicleExtra(taxi, i, false) -- Extra aktivieren
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Fahrzeug-Farbe setzen (Gelb für Taxis)
 | 
						|
    SetVehicleColours(taxi, 88, 88) -- Taxi Yellow
 | 
						|
    
 | 
						|
    SetModelAsNoLongerNeeded(taxiModel)
 | 
						|
    return taxi
 | 
						|
end
 | 
						|
 | 
						|
-- Fortgeschrittenes Taxi-Fahrer-Verhaltenssystem
 | 
						|
function InitializeTaxiDriverAI(driver, vehicle)
 | 
						|
    if not driver or not DoesEntityExist(driver) then return end
 | 
						|
    
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Initializing advanced taxi driver AI")
 | 
						|
    
 | 
						|
    -- Fahrer-Persönlichkeit und Fähigkeiten zufällig festlegen
 | 
						|
    local driverData = {
 | 
						|
        personality = {
 | 
						|
            patience = math.random(7, 10) / 10,           -- 0.7-1.0: Wie geduldig ist der Fahrer
 | 
						|
            caution = math.random(6, 10) / 10,            -- 0.6-1.0: Wie vorsichtig fährt er
 | 
						|
            speedPreference = math.random(15, 60),        -- 15-25: Bevorzugte Geschwindigkeit
 | 
						|
            trafficRuleCompliance = math.random(8, 10)/10 -- 0.8-1.0: Wie genau hält er Verkehrsregeln ein
 | 
						|
        },
 | 
						|
        state = {
 | 
						|
            stuckCounter = 0,
 | 
						|
            lastStuckRecovery = 0,
 | 
						|
            lastRouteRecalculation = 0,
 | 
						|
            currentBehavior = "normal", -- normal, cautious, stuck, recovery
 | 
						|
            lastSpeedCheck = 0,
 | 
						|
            speedHistory = {},
 | 
						|
            lastPositions = {},
 | 
						|
            trafficLightWaitStart = 0,
 | 
						|
            isWaitingAtTrafficLight = false
 | 
						|
        },
 | 
						|
        settings = {
 | 
						|
            maxStuckCounter = math.random(15, 25),        -- Wie lange warten bis Befreiungsversuch
 | 
						|
            stuckThreshold = 0.5,                         -- Bewegungsschwelle für Steckenbleiben
 | 
						|
            checkInterval = 2000,                         -- Überprüfungsintervall in ms
 | 
						|
            positionHistorySize = 5,                      -- Anzahl der zu speichernden Positionen
 | 
						|
            minRecoveryInterval = 25000,                  -- Min. Zeit zwischen Befreiungsversuchen
 | 
						|
            minRouteRecalcInterval = 30000,               -- Min. Zeit zwischen Routenneuberechnungen
 | 
						|
            trafficLightMaxWait = 45000                   -- Max. Wartezeit an Ampeln
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    -- Fahrer-Verhalten basierend auf Persönlichkeit einstellen
 | 
						|
    SetDriverAbility(driver, driverData.personality.caution)
 | 
						|
    SetDriverAggressiveness(driver, 1.0 - driverData.personality.caution)
 | 
						|
    
 | 
						|
    -- Fahrer-Daten im Entity speichern
 | 
						|
    Entity(vehicle).state.driverData = driverData
 | 
						|
    
 | 
						|
    -- Fahrer-Verhalten-Thread starten
 | 
						|
    CreateThread(function()
 | 
						|
        local lastPos = GetEntityCoords(vehicle)
 | 
						|
        local lastCheck = GetGameTimer()
 | 
						|
        
 | 
						|
        while DoesEntityExist(vehicle) and DoesEntityExist(driver) do
 | 
						|
            Wait(driverData.settings.checkInterval)
 | 
						|
            
 | 
						|
            local currentTime = GetGameTimer()
 | 
						|
            local timeDelta = currentTime - lastCheck
 | 
						|
            lastCheck = currentTime
 | 
						|
            
 | 
						|
            -- Aktuelle Position und Geschwindigkeit
 | 
						|
            local currentPos = GetEntityCoords(vehicle)
 | 
						|
            local speed = GetEntitySpeed(vehicle)
 | 
						|
            local distanceMoved = #(lastPos - currentPos)
 | 
						|
            
 | 
						|
            -- Position für Historie speichern
 | 
						|
            table.insert(driverData.state.lastPositions, 1, currentPos)
 | 
						|
            if #driverData.state.lastPositions > driverData.settings.positionHistorySize then
 | 
						|
                table.remove(driverData.state.lastPositions)
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Geschwindigkeit für Historie speichern
 | 
						|
            table.insert(driverData.state.speedHistory, 1, speed)
 | 
						|
            if #driverData.state.speedHistory > 5 then
 | 
						|
                table.remove(driverData.state.speedHistory)
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Durchschnittsgeschwindigkeit berechnen
 | 
						|
            local avgSpeed = 0
 | 
						|
            for _, s in ipairs(driverData.state.speedHistory) do
 | 
						|
                avgSpeed = avgSpeed + s
 | 
						|
            end
 | 
						|
            avgSpeed = avgSpeed / #driverData.state.speedHistory
 | 
						|
            
 | 
						|
            -- Ampel-Erkennung
 | 
						|
            local isAtTrafficLight = IsVehicleStoppedAtTrafficLights(vehicle)
 | 
						|
            
 | 
						|
            -- Ampel-Wartezustand aktualisieren
 | 
						|
            if isAtTrafficLight and not driverData.state.isWaitingAtTrafficLight then
 | 
						|
                -- Gerade an Ampel angekommen
 | 
						|
                driverData.state.isWaitingAtTrafficLight = true
 | 
						|
                driverData.state.trafficLightWaitStart = currentTime
 | 
						|
                DebugPrint("^3[TAXI DEBUG]^7 Taxi waiting at traffic light")
 | 
						|
            elseif isAtTrafficLight and driverData.state.isWaitingAtTrafficLight then
 | 
						|
                -- Immer noch an Ampel
 | 
						|
                local waitTime = currentTime - driverData.state.trafficLightWaitStart
 | 
						|
                
 | 
						|
                -- Wenn zu lange an Ampel, versuche weiterzufahren (Ampel könnte hängen)
 | 
						|
                if waitTime > driverData.settings.trafficLightMaxWait then
 | 
						|
                    DebugPrint("^3[TAXI DEBUG]^7 Taxi waited too long at traffic light, trying to continue")
 | 
						|
                    -- Kurz vorwärts fahren um Ampel zu überwinden
 | 
						|
                    TaskVehicleTempAction(driver, vehicle, 1, 2000) -- Forward
 | 
						|
                    Wait(2000)
 | 
						|
                    -- Dann normale Fahrt fortsetzen
 | 
						|
                    TaxiDriverContinueRoute(driver, vehicle)
 | 
						|
                    driverData.state.isWaitingAtTrafficLight = false
 | 
						|
                end
 | 
						|
            elseif not isAtTrafficLight and driverData.state.isWaitingAtTrafficLight then
 | 
						|
                -- Ampel verlassen
 | 
						|
                driverData.state.isWaitingAtTrafficLight = false
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Taxi continued after traffic light")
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Steckenbleiben-Erkennung (nicht an Ampel und kaum Bewegung)
 | 
						|
            if not isAtTrafficLight and distanceMoved < driverData.settings.stuckThreshold and speed < 0.5 then
 | 
						|
                driverData.state.stuckCounter = driverData.state.stuckCounter + 1
 | 
						|
                
 | 
						|
                -- Nur alle 5 Zähler-Erhöhungen loggen
 | 
						|
                if driverData.state.stuckCounter % 5 == 0 then
 | 
						|
                    DebugPrint("^3[TAXI DEBUG]^7 Taxi might be stuck: " .. driverData.state.stuckCounter .. "/" .. driverData.settings.maxStuckCounter)
 | 
						|
                end
 | 
						|
                
 | 
						|
                -- Wenn lange genug steckengeblieben und genug Zeit seit letztem Versuch
 | 
						|
                if driverData.state.stuckCounter >= driverData.settings.maxStuckCounter and 
 | 
						|
                   (currentTime - driverData.state.lastStuckRecovery) > driverData.settings.minRecoveryInterval then
 | 
						|
                    
 | 
						|
                    DebugPrint("^1[TAXI DEBUG]^7 Taxi is stuck, attempting intelligent recovery")
 | 
						|
                    driverData.state.lastStuckRecovery = currentTime
 | 
						|
                    driverData.state.currentBehavior = "recovery"
 | 
						|
                    
 | 
						|
                    -- Intelligente Befreiung basierend auf Umgebung
 | 
						|
                    TaxiDriverIntelligentRecovery(driver, vehicle)
 | 
						|
                    
 | 
						|
                    -- Steckenbleiben-Zähler teilweise zurücksetzen
 | 
						|
                    driverData.state.stuckCounter = math.floor(driverData.settings.maxStuckCounter * 0.4)
 | 
						|
                end
 | 
						|
            else
 | 
						|
                -- Wenn sich das Fahrzeug bewegt oder an einer Ampel steht
 | 
						|
                if isAtTrafficLight then
 | 
						|
                    -- An Ampel: Zähler langsamer reduzieren
 | 
						|
                    driverData.state.stuckCounter = math.max(0, driverData.state.stuckCounter - 0.2)
 | 
						|
                else
 | 
						|
                    -- In Bewegung: Zähler reduzieren
 | 
						|
                    driverData.state.stuckCounter = math.max(0, driverData.state.stuckCounter - 1)
 | 
						|
                    
 | 
						|
                    -- Wenn Fahrzeug sich bewegt, aber sehr langsam über längere Zeit
 | 
						|
                    if avgSpeed < 2.0 and driverData.state.stuckCounter > driverData.settings.maxStuckCounter * 0.5 and
 | 
						|
                       (currentTime - driverData.state.lastRouteRecalculation) > driverData.settings.minRouteRecalcInterval then
 | 
						|
                        
 | 
						|
                        DebugPrint("^3[TAXI DEBUG]^7 Taxi moving too slow, recalculating route")
 | 
						|
                        driverData.state.lastRouteRecalculation = currentTime
 | 
						|
                        
 | 
						|
                        -- Route neu berechnen
 | 
						|
                        TaxiDriverRecalculateRoute(driver, vehicle)
 | 
						|
                    end
 | 
						|
                end
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Verhalten basierend auf Umgebung anpassen
 | 
						|
            TaxiDriverAdaptBehavior(driver, vehicle, driverData)
 | 
						|
            
 | 
						|
            lastPos = currentPos
 | 
						|
        end
 | 
						|
    end)
 | 
						|
    
 | 
						|
    return driverData
 | 
						|
end
 | 
						|
 | 
						|
-- Intelligente Befreiung bei Steckenbleiben
 | 
						|
function TaxiDriverIntelligentRecovery(driver, vehicle)
 | 
						|
    if not DoesEntityExist(vehicle) or not DoesEntityExist(driver) then return end
 | 
						|
    
 | 
						|
    -- Aktuelle Position und Umgebung analysieren
 | 
						|
    local vehicleCoords = GetEntityCoords(vehicle)
 | 
						|
    local vehicleHeading = GetEntityHeading(vehicle)
 | 
						|
    local vehicleForwardVector = GetEntityForwardVector(vehicle)
 | 
						|
    
 | 
						|
    -- Prüfen ob Hindernisse vorne, hinten, links, rechts
 | 
						|
    local forwardClear = not IsPositionOccupied(
 | 
						|
        vehicleCoords.x + vehicleForwardVector.x * 4.0,
 | 
						|
        vehicleCoords.y + vehicleForwardVector.y * 4.0,
 | 
						|
        vehicleCoords.z,
 | 
						|
        1.0, false, true, false, false, false, 0, false
 | 
						|
    )
 | 
						|
    
 | 
						|
    local backwardClear = not IsPositionOccupied(
 | 
						|
        vehicleCoords.x - vehicleForwardVector.x * 4.0,
 | 
						|
        vehicleCoords.y - vehicleForwardVector.y * 4.0,
 | 
						|
        vehicleCoords.z,
 | 
						|
        1.0, false, true, false, false, false, 0, false
 | 
						|
    )
 | 
						|
    
 | 
						|
    -- Befreiungsstrategie basierend auf Umgebung
 | 
						|
    ClearPedTasks(driver)
 | 
						|
    
 | 
						|
    if backwardClear then
 | 
						|
        -- Rückwärts fahren wenn hinten frei
 | 
						|
        DebugPrint("^3[TAXI DEBUG]^7 Recovery: Backing up")
 | 
						|
        TaskVehicleTempAction(driver, vehicle, 8, 2000) -- Reverse
 | 
						|
        Wait(2000)
 | 
						|
        
 | 
						|
        if forwardClear then
 | 
						|
            -- Wenn vorne auch frei, einfach weiterfahren
 | 
						|
            DebugPrint("^3[TAXI DEBUG]^7 Recovery: Path clear, continuing")
 | 
						|
            TaxiDriverContinueRoute(driver, vehicle)
 | 
						|
        else
 | 
						|
            -- Sonst versuchen zu wenden
 | 
						|
            DebugPrint("^3[TAXI DEBUG]^7 Recovery: Turning around")
 | 
						|
            TaskVehicleTempAction(driver, vehicle, 7, 2000) -- Turn left
 | 
						|
            Wait(1000)
 | 
						|
            TaskVehicleTempAction(driver, vehicle, 8, 1000) -- Reverse
 | 
						|
            Wait(1000)
 | 
						|
            TaskVehicleTempAction(driver, vehicle, 6, 2000) -- Turn right
 | 
						|
            Wait(1000)
 | 
						|
            TaxiDriverContinueRoute(driver, vehicle)
 | 
						|
        end
 | 
						|
    elseif forwardClear then
 | 
						|
        -- Wenn nur vorne frei, vorwärts fahren
 | 
						|
        DebugPrint("^3[TAXI DEBUG]^7 Recovery: Moving forward")
 | 
						|
        TaskVehicleTempAction(driver, vehicle, 1, 2000) -- Forward
 | 
						|
        Wait(2000)
 | 
						|
        TaxiDriverContinueRoute(driver, vehicle)
 | 
						|
    else
 | 
						|
        -- Wenn komplett eingeklemmt, versuche zu rütteln
 | 
						|
        DebugPrint("^3[TAXI DEBUG]^7 Recovery: Trying to wiggle free")
 | 
						|
        TaskVehicleTempAction(driver, vehicle, 7, 1000) -- Turn left
 | 
						|
        Wait(1000)
 | 
						|
        TaskVehicleTempAction(driver, vehicle, 8, 800) -- Reverse
 | 
						|
        Wait(800)
 | 
						|
        TaskVehicleTempAction(driver, vehicle, 6, 1000) -- Turn right
 | 
						|
        Wait(1000)
 | 
						|
        TaskVehicleTempAction(driver, vehicle, 1, 800) -- Forward
 | 
						|
        Wait(800)
 | 
						|
        TaxiDriverContinueRoute(driver, vehicle)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Route neu berechnen
 | 
						|
function TaxiDriverRecalculateRoute(driver, vehicle)
 | 
						|
    if not DoesEntityExist(vehicle) or not DoesEntityExist(driver) then return end
 | 
						|
    
 | 
						|
    -- Aktuelle Zielposition aus dem Fahrzeug-State ermitteln
 | 
						|
    local destination = Entity(vehicle).state.currentDestination
 | 
						|
    
 | 
						|
    if destination then
 | 
						|
        -- Versuche einen alternativen Weg zu finden
 | 
						|
        DebugPrint("^3[TAXI DEBUG]^7 Recalculating route to destination")
 | 
						|
        
 | 
						|
        -- Kurz anhalten
 | 
						|
        TaskVehicleTempAction(driver, vehicle, 27, 1000) -- Stop
 | 
						|
        Wait(1000)
 | 
						|
        
 | 
						|
        -- Neue Route mit leicht geändertem Fahrstil
 | 
						|
        local drivingStyle = 786603 -- Normal/Vorsichtig
 | 
						|
        TaskVehicleDriveToCoordLongrange(driver, vehicle, destination.x, destination.y, destination.z, 20.0, drivingStyle, 10.0)
 | 
						|
    else
 | 
						|
        -- Wenn kein Ziel bekannt, einfach weiterfahren
 | 
						|
        TaxiDriverContinueRoute(driver, vehicle)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Normale Fahrt fortsetzen
 | 
						|
function TaxiDriverContinueRoute(driver, vehicle)
 | 
						|
    if not DoesEntityExist(vehicle) or not DoesEntityExist(driver) then return end
 | 
						|
    
 | 
						|
    -- Ziel aus dem Fahrzeug-State holen
 | 
						|
    local destination = Entity(vehicle).state.currentDestination
 | 
						|
    
 | 
						|
    if destination then
 | 
						|
        -- Zum Ziel fahren mit angepasster Fahrweise
 | 
						|
        local drivingStyle = 786603 -- Normal/Vorsichtig
 | 
						|
        TaskVehicleDriveToCoordLongrange(driver, vehicle, destination.x, destination.y, destination.z, 20.0, drivingStyle, 10.0)
 | 
						|
    else
 | 
						|
        -- Wenn kein Ziel bekannt, einfach geradeaus fahren
 | 
						|
        TaskVehicleDriveWander(driver, vehicle, 15.0, 786603)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Fahrverhalten an Umgebung anpassen
 | 
						|
function TaxiDriverAdaptBehavior(driver, vehicle, driverData)
 | 
						|
    if not DoesEntityExist(vehicle) or not DoesEntityExist(driver) then return end
 | 
						|
    
 | 
						|
    -- Aktuelle Geschwindigkeit und Position
 | 
						|
    local speed = GetEntitySpeed(vehicle)
 | 
						|
    local vehicleCoords = GetEntityCoords(vehicle)
 | 
						|
    
 | 
						|
    -- Verkehrsdichte in der Umgebung prüfen
 | 
						|
    local vehiclesNearby = 0
 | 
						|
    local vehicles = GetGamePool('CVehicle')
 | 
						|
    for _, otherVehicle in ipairs(vehicles) do
 | 
						|
        if otherVehicle ~= vehicle then
 | 
						|
            local otherCoords = GetEntityCoords(otherVehicle)
 | 
						|
            local distance = #(vehicleCoords - otherCoords)
 | 
						|
            if distance < 15.0 then
 | 
						|
                vehiclesNearby = vehiclesNearby + 1
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Verhalten basierend auf Verkehrsdichte anpassen
 | 
						|
    local targetSpeed = driverData.personality.speedPreference
 | 
						|
    
 | 
						|
    if vehiclesNearby > 5 then
 | 
						|
        -- Viel Verkehr: langsamer und vorsichtiger
 | 
						|
        targetSpeed = targetSpeed * 0.7
 | 
						|
        SetDriverAggressiveness(driver, 0.0)
 | 
						|
    elseif vehiclesNearby > 2 then
 | 
						|
        -- Moderater Verkehr: etwas langsamer
 | 
						|
        targetSpeed = targetSpeed * 0.85
 | 
						|
        SetDriverAggressiveness(driver, 0.1)
 | 
						|
    else
 | 
						|
        -- Wenig Verkehr: normale Geschwindigkeit
 | 
						|
        SetDriverAggressiveness(driver, 0.2)
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Geschwindigkeit anpassen wenn nötig
 | 
						|
    if math.abs(speed - targetSpeed) > 5.0 then
 | 
						|
        if speed < targetSpeed then
 | 
						|
            -- Beschleunigen
 | 
						|
            TaskVehicleTempAction(driver, vehicle, 23, 500) -- Gentle forward
 | 
						|
        else
 | 
						|
            -- Abbremsen
 | 
						|
            TaskVehicleTempAction(driver, vehicle, 24, 500) -- Gentle brake
 | 
						|
        end
 | 
						|
        
 | 
						|
        -- Nach der Anpassung normale Fahrt fortsetzen
 | 
						|
        Wait(500)
 | 
						|
        TaxiDriverContinueRoute(driver, vehicle)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Hilfsfunktion zur Normalisierung eines Vektors
 | 
						|
function norm(vector)
 | 
						|
    local length = math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
 | 
						|
    if length == 0 then
 | 
						|
        return vector3(0.0, 0.0, 0.0)
 | 
						|
    end
 | 
						|
    return vector3(vector.x / length, vector.y / length, vector.z / length)
 | 
						|
end
 | 
						|
 | 
						|
function SpawnTaxiDriver(vehicle)
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Spawning taxi driver...")
 | 
						|
    
 | 
						|
    -- Bessere Fahrer-Models mit Fallbacks
 | 
						|
    local driverModels = {
 | 
						|
        "A_C_Chimp", -- Taxi Driver (erste Wahl)
 | 
						|
        "a_m_y_business_01",   -- Business Male
 | 
						|
        "a_m_m_business_01",   -- Business Male 2
 | 
						|
        "mp_m_freemode_01",    -- Male Freemode
 | 
						|
        "a_m_y_downtown_01",   -- Downtown Male
 | 
						|
        "a_m_m_farmer_01",     -- Farmer
 | 
						|
        "a_m_y_hipster_01",    -- Hipster
 | 
						|
        "a_m_y_beach_01"       -- Beach Guy
 | 
						|
    }
 | 
						|
    
 | 
						|
    local driver = nil
 | 
						|
    local driverHash = nil
 | 
						|
    
 | 
						|
    -- Versuche verschiedene Models
 | 
						|
    for i, modelName in ipairs(driverModels) do
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Trying driver model " .. i .. ": " .. modelName)
 | 
						|
        driverHash = GetHashKey(modelName)
 | 
						|
        
 | 
						|
        -- Model laden
 | 
						|
        RequestModel(driverHash)
 | 
						|
        local timeout = GetGameTimer() + 8000 -- Längere Wartezeit
 | 
						|
        local attempts = 0
 | 
						|
        
 | 
						|
        while not HasModelLoaded(driverHash) and GetGameTimer() < timeout do
 | 
						|
            attempts = attempts + 1
 | 
						|
            if attempts % 10 == 0 then -- Alle 1 Sekunde loggen
 | 
						|
                DebugPrint("^3[TAXI DEBUG]^7 Still waiting for model " .. modelName .. " (attempt " .. attempts .. ")")
 | 
						|
            end
 | 
						|
            Wait(100)
 | 
						|
        end
 | 
						|
        
 | 
						|
        if HasModelLoaded(driverHash) then
 | 
						|
            DebugPrint("^2[TAXI DEBUG]^7 Model " .. modelName .. " loaded successfully after " .. attempts .. " attempts")
 | 
						|
            
 | 
						|
            -- Fahrer erstellen
 | 
						|
            driver = CreatePedInsideVehicle(vehicle, 26, driverHash, -1, true, false)
 | 
						|
            
 | 
						|
            if DoesEntityExist(driver) then
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Driver created successfully with model: " .. modelName .. " (ID: " .. driver .. ")")
 | 
						|
                break
 | 
						|
            else
 | 
						|
                DebugPrint("^1[TAXI DEBUG]^7 Failed to create driver with loaded model: " .. modelName)
 | 
						|
                SetModelAsNoLongerNeeded(driverHash)
 | 
						|
            end
 | 
						|
        else
 | 
						|
            DebugPrint("^1[TAXI DEBUG]^7 Failed to load driver model: " .. modelName)
 | 
						|
            SetModelAsNoLongerNeeded(driverHash)
 | 
						|
        end
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Wenn immer noch kein Fahrer erstellt wurde
 | 
						|
    if not driver or not DoesEntityExist(driver) then
 | 
						|
        DebugPrint("^1[TAXI DEBUG]^7 Could not create any driver! Continuing without driver...")
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Fahrer konfigurieren
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Configuring driver...")
 | 
						|
    
 | 
						|
    SetEntityAsMissionEntity(driver, true, true)
 | 
						|
    SetBlockingOfNonTemporaryEvents(driver, true)
 | 
						|
    SetPedFleeAttributes(driver, 0, 0)
 | 
						|
    SetPedCombatAttributes(driver, 17, 1)
 | 
						|
    SetPedSeeingRange(driver, 0.0)
 | 
						|
    SetPedHearingRange(driver, 0.0)
 | 
						|
    SetPedAlertness(driver, 0)
 | 
						|
    SetPedKeepTask(driver, true)
 | 
						|
    
 | 
						|
    -- Fortgeschrittene KI-ähnliche Fahrer-Logik initialisieren
 | 
						|
    local driverData = InitializeTaxiDriverAI(driver, vehicle)
 | 
						|
 | 
						|
    -- Zufälligen Fahrer-Namen generieren
 | 
						|
    local firstNames = {"Max", "Thomas", "Ali", "Mehmet", "Hans", "Peter", "Klaus", "Michael", "Stefan", "Frank"}
 | 
						|
    local lastNames = {"Müller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker", "Schulz", "Hoffmann"}
 | 
						|
    local driverName = firstNames[math.random(#firstNames)] .. " " .. lastNames[math.random(#lastNames)]
 | 
						|
 | 
						|
    -- Fahrer-Name im Fahrzeug-State speichern
 | 
						|
    Entity(vehicle).state.driverName = driverName
 | 
						|
    
 | 
						|
    -- Fahrer-Outfit (nur wenn es ein anpassbarer Ped ist)
 | 
						|
    if driverHash == GetHashKey("mp_m_freemode_01") then
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Setting driver outfit...")
 | 
						|
        
 | 
						|
        -- Basis-Outfit für Taxi-Fahrer
 | 
						|
        SetPedComponentVariation(driver, 0, 0, 0, 0)    -- Face
 | 
						|
        SetPedComponentVariation(driver, 2, 1, 0, 0)    -- Hair
 | 
						|
        SetPedComponentVariation(driver, 8, 15, 0, 0)   -- Undershirt
 | 
						|
        SetPedComponentVariation(driver, 11, 91, 0, 0)  -- Jacket
 | 
						|
        SetPedComponentVariation(driver, 4, 10, 0, 0)   -- Pants
 | 
						|
        SetPedComponentVariation(driver, 6, 10, 0, 0)   -- Shoes
 | 
						|
        SetPedComponentVariation(driver, 1, 0, 0, 0)    -- Mask
 | 
						|
        SetPedComponentVariation(driver, 3, 0, 0, 0)    -- Arms
 | 
						|
        SetPedComponentVariation(driver, 5, 0, 0, 0)    -- Bag
 | 
						|
        SetPedComponentVariation(driver, 7, 0, 0, 0)    -- Tie
 | 
						|
        SetPedComponentVariation(driver, 9, 0, 0, 0)    -- Body Armor
 | 
						|
        SetPedComponentVariation(driver, 10, 0, 0, 0)   -- Decals
 | 
						|
        
 | 
						|
        -- Zufällige Gesichtsmerkmale
 | 
						|
        SetPedHeadBlendData(driver, math.random(0, 20), math.random(0, 20), 0, math.random(0, 20), math.random(0, 20), 0, 0.5, 0.5, 0.0, false)
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Model nicht mehr benötigt
 | 
						|
    if driverHash then
 | 
						|
        SetModelAsNoLongerNeeded(driverHash)
 | 
						|
    end
 | 
						|
    
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Driver spawn completed successfully")
 | 
						|
    return driver
 | 
						|
end
 | 
						|
 | 
						|
function CreateTaxiBlips(taxi)
 | 
						|
    -- Blip für Taxi erstellen (Entity-Blip)
 | 
						|
    taxiBlip = AddBlipForEntity(taxi)
 | 
						|
    SetBlipSprite(taxiBlip, 198)
 | 
						|
    SetBlipColour(taxiBlip, 5)
 | 
						|
    SetBlipScale(taxiBlip, 0.8)
 | 
						|
    SetBlipDisplay(taxiBlip, 2) -- Zeigt auf Minimap und großer Karte
 | 
						|
    SetBlipShowCone(taxiBlip, true) -- Zeigt Sichtkegel
 | 
						|
    SetBlipAsShortRange(taxiBlip, false) -- Immer sichtbar, egal wie weit entfernt
 | 
						|
    BeginTextCommandSetBlipName("STRING")
 | 
						|
    AddTextComponentString("Dein Taxi")
 | 
						|
    EndTextCommandSetBlipName(taxiBlip)
 | 
						|
    
 | 
						|
    -- Zusätzlicher Blip auf der Karte für bessere Sichtbarkeit
 | 
						|
    local taxiCoords = GetEntityCoords(taxi)
 | 
						|
    mapBlip = AddBlipForCoord(taxiCoords.x, taxiCoords.y, taxiCoords.z)
 | 
						|
    SetBlipSprite(mapBlip, 198)
 | 
						|
    SetBlipColour(mapBlip, 5)
 | 
						|
    SetBlipScale(mapBlip, 1.0)
 | 
						|
    SetBlipDisplay(mapBlip, 4) -- Nur auf großer Karte
 | 
						|
    BeginTextCommandSetBlipName("STRING")
 | 
						|
    AddTextComponentString("Dein Taxi")
 | 
						|
    EndTextCommandSetBlipName(mapBlip)
 | 
						|
    
 | 
						|
    -- Blip aktualisieren während Taxi unterwegs ist
 | 
						|
    CreateThread(function()
 | 
						|
        while DoesEntityExist(taxi) and mapBlip do
 | 
						|
            local taxiCoords = GetEntityCoords(taxi)
 | 
						|
            SetBlipCoords(mapBlip, taxiCoords.x, taxiCoords.y, taxiCoords.z)
 | 
						|
            Wait(1000)
 | 
						|
        end
 | 
						|
        
 | 
						|
        -- Blip entfernen wenn Thread beendet
 | 
						|
        if mapBlip then
 | 
						|
            RemoveBlip(mapBlip)
 | 
						|
            mapBlip = nil
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
function NavigateToPlayer(driver, taxi, playerCoords)
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Navigating taxi to player...")
 | 
						|
    
 | 
						|
    -- Ziel im Fahrzeug-State speichern für die KI-Logik
 | 
						|
    Entity(taxi).state.currentDestination = playerCoords
 | 
						|
    
 | 
						|
    -- Versuche einen guten Wegpunkt in der Nähe des Spielers zu finden
 | 
						|
    local success, nodePos = GetClosestVehicleNodeWithHeading(playerCoords.x, playerCoords.y, playerCoords.z, 1, 3.0, 0)
 | 
						|
    
 | 
						|
    if success then
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Found good vehicle node near player")
 | 
						|
        -- Zum Wegpunkt fahren
 | 
						|
        TaskVehicleDriveToCoordLongrange(driver, taxi, nodePos.x, nodePos.y, nodePos.z, 20.0, 786603, 10.0)
 | 
						|
    else
 | 
						|
        DebugPrint("^3[TAXI DEBUG]^7 No good vehicle node found, driving directly to player")
 | 
						|
        -- Direkt zum Spieler fahren
 | 
						|
        TaskVehicleDriveToCoordLongrange(driver, taxi, playerCoords.x, playerCoords.y, playerCoords.z, 20.0, 786603, 10.0)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
function MonitorTaxiArrival(taxi, driver, playerCoords)
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Monitoring taxi arrival...")
 | 
						|
    
 | 
						|
    local arrivalTimeout = GetGameTimer() + (120 * 1000) -- 2 minute timeout
 | 
						|
    
 | 
						|
    CreateThread(function()
 | 
						|
        while DoesEntityExist(taxi) and (not driver or DoesEntityExist(driver)) do
 | 
						|
            local taxiCoords = GetEntityCoords(taxi)
 | 
						|
            local currentPlayerCoords = GetEntityCoords(PlayerPedId())
 | 
						|
            local distance = #(currentPlayerCoords - taxiCoords)
 | 
						|
            
 | 
						|
            -- Check if taxi is close to player
 | 
						|
            if distance < 15.0 then
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Taxi arrived!")
 | 
						|
                
 | 
						|
                -- Taxi stoppen
 | 
						|
                if driver and DoesEntityExist(driver) then
 | 
						|
                    TaskVehicleTempAction(driver, taxi, 27, 3000) -- Brake
 | 
						|
                    Wait(2000)
 | 
						|
                    SetVehicleDoorsLocked(taxi, 1) -- Unlock doors
 | 
						|
                end
 | 
						|
                
 | 
						|
                lib.notify({
 | 
						|
                    title = 'Taxi Service',
 | 
						|
                    description = 'Dein Taxi ist angekommen! Steige ein.',
 | 
						|
                    type = 'success'
 | 
						|
                })
 | 
						|
                
 | 
						|
                -- qb-target für Einsteigen hinzufügen
 | 
						|
                exports['qb-target']:AddTargetEntity(taxi, {
 | 
						|
                    options = {
 | 
						|
                        {
 | 
						|
                            type = "client",
 | 
						|
                            event = "taxi:enterTaxi",
 | 
						|
                            icon = "fas fa-car-side",
 | 
						|
                            label = "Ins Taxi einsteigen"
 | 
						|
                        }
 | 
						|
                    },
 | 
						|
                    distance = 3.0
 | 
						|
                })
 | 
						|
                
 | 
						|
                break
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Check for timeout
 | 
						|
            if GetGameTimer() > arrivalTimeout then
 | 
						|
                DebugPrint("^1[TAXI DEBUG]^7 Taxi arrival timed out!")
 | 
						|
                lib.notify({
 | 
						|
                    title = 'Taxi Service',
 | 
						|
                    description = 'Dein Taxi steckt fest. Ein neues wird gerufen!',
 | 
						|
                    type = 'warning'
 | 
						|
                })
 | 
						|
                
 | 
						|
                -- Despawn current taxi and call a new one
 | 
						|
                DespawnTaxi()
 | 
						|
                Wait(1000)
 | 
						|
                CallTaxi()
 | 
						|
                break
 | 
						|
            end
 | 
						|
            
 | 
						|
            Wait(2000)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
-- Event für Einsteigen ins Taxi
 | 
						|
RegisterNetEvent('taxi:enterTaxi', function()
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Player entering taxi")
 | 
						|
    
 | 
						|
    if not currentTaxi or not DoesEntityExist(currentTaxi) then
 | 
						|
        DebugPrint("^1[TAXI DEBUG]^7 No taxi exists")
 | 
						|
        return
 | 
						|
    end
 | 
						|
    
 | 
						|
    local playerPed = PlayerPedId()
 | 
						|
    
 | 
						|
    -- Spieler hinten einsteigen lassen
 | 
						|
    local seatIndex = 1 -- Hinten links
 | 
						|
    if not IsVehicleSeatFree(currentTaxi, 1) then
 | 
						|
        seatIndex = 2 -- Hinten rechts
 | 
						|
    end
 | 
						|
    if not IsVehicleSeatFree(currentTaxi, seatIndex) then
 | 
						|
        seatIndex = 0 -- Beifahrer als Fallback
 | 
						|
    end
 | 
						|
    
 | 
						|
    TaskEnterVehicle(playerPed, currentTaxi, 10000, seatIndex, 1.0, 1, 0)
 | 
						|
    
 | 
						|
    -- Warten bis eingestiegen
 | 
						|
    CreateThread(function()
 | 
						|
        local timeout = GetGameTimer() + 10000
 | 
						|
        local entered = false
 | 
						|
        
 | 
						|
        while GetGameTimer() < timeout and not entered do
 | 
						|
            if IsPedInVehicle(playerPed, currentTaxi, false) then
 | 
						|
                entered = true
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Player entered taxi successfully")
 | 
						|
                
 | 
						|
                -- qb-target entfernen
 | 
						|
                exports['qb-target']:RemoveTargetEntity(currentTaxi)
 | 
						|
                
 | 
						|
                lib.notify({
 | 
						|
                    title = 'Taxi Service',
 | 
						|
                    description = 'Willkommen im Taxi! Wähle dein Ziel.',
 | 
						|
                    type = 'success'
 | 
						|
                })
 | 
						|
                
 | 
						|
                -- Ziel-Menu öffnen
 | 
						|
                Wait(1000)
 | 
						|
                OpenDestinationMenu()
 | 
						|
            end
 | 
						|
            Wait(100)
 | 
						|
        end
 | 
						|
        
 | 
						|
        if not entered then
 | 
						|
            DebugPrint("^1[TAXI DEBUG]^7 Player failed to enter taxi")
 | 
						|
            lib.notify({
 | 
						|
                title = 'Taxi Service',
 | 
						|
                description = 'Einsteigen fehlgeschlagen',
 | 
						|
                type = 'error'
 | 
						|
            })
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end)
 | 
						|
 | 
						|
function OpenDestinationMenu()
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Opening destination menu")
 | 
						|
    
 | 
						|
    local options = {}
 | 
						|
    
 | 
						|
    -- Bekannte Ziele hinzufügen
 | 
						|
    for _, destination in pairs(Config.KnownDestinations) do
 | 
						|
        local distance = CalculateDistanceToCoords(destination.coords) / 1000 -- in km
 | 
						|
        local price = math.max(Config.MinFare, math.ceil(distance * Config.PricePerKm))
 | 
						|
        
 | 
						|
        table.insert(options, {
 | 
						|
            title = destination.name,
 | 
						|
            description = 'Preis: $' .. price .. ' | Entfernung: ' .. math.ceil(distance * 100) / 100 .. 'km',
 | 
						|
            icon = 'map-marker',
 | 
						|
            onSelect = function()
 | 
						|
                StartTaxiRide(destination.coords, price)
 | 
						|
            end
 | 
						|
        })
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Waypoint Option
 | 
						|
    table.insert(options, {
 | 
						|
        title = 'Zu meinem Waypoint',
 | 
						|
        description = 'Fahre zu deinem gesetzten Waypoint',
 | 
						|
        icon = 'location-dot',
 | 
						|
        onSelect = function()
 | 
						|
            local waypoint = GetFirstBlipInfoId(8)
 | 
						|
            if DoesBlipExist(waypoint) then
 | 
						|
                local coords = GetBlipInfoIdCoord(waypoint)
 | 
						|
                local distance = CalculateDistanceToCoords(coords) / 1000
 | 
						|
                local price = math.max(Config.MinFare, math.ceil(distance * Config.PricePerKm))
 | 
						|
                StartTaxiRide(coords, price)
 | 
						|
            else
 | 
						|
                lib.notify({
 | 
						|
                    title = 'Taxi Service',
 | 
						|
                    description = 'Du hast keinen Waypoint gesetzt',
 | 
						|
                    type = 'error'
 | 
						|
                })
 | 
						|
                OpenDestinationMenu()
 | 
						|
            end
 | 
						|
        end
 | 
						|
    })
 | 
						|
    
 | 
						|
    -- Selbst fahren Option (wenn kein Fahrer)
 | 
						|
    if not currentDriver or not DoesEntityExist(currentDriver) then
 | 
						|
        table.insert(options, {
 | 
						|
            title = '🚗 Selbst fahren',
 | 
						|
            description = 'Du fährst das Taxi selbst',
 | 
						|
            icon = 'car',
 | 
						|
            onSelect = function()
 | 
						|
                SelfDriveTaxi()
 | 
						|
            end
 | 
						|
        })
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Aussteigen Option
 | 
						|
    table.insert(options, {
 | 
						|
        title = 'Aussteigen',
 | 
						|
        description = 'Das Taxi verlassen',
 | 
						|
        icon = 'door-open',
 | 
						|
        onSelect = function()
 | 
						|
            ExitTaxi()
 | 
						|
        end
 | 
						|
    })
 | 
						|
 | 
						|
    lib.registerContext({
 | 
						|
        id = 'taxi_destination_menu',
 | 
						|
        title = 'Taxi - Ziel wählen',
 | 
						|
        options = options
 | 
						|
    })
 | 
						|
 | 
						|
    lib.showContext('taxi_destination_menu')
 | 
						|
end
 | 
						|
 | 
						|
function StartTaxiRide(destination, price)
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Starting taxi ride to: " .. tostring(destination.x) .. ", " .. tostring(destination.y) .. ", " .. tostring(destination.z))
 | 
						|
    
 | 
						|
    if not currentTaxi or not DoesEntityExist(currentTaxi) then
 | 
						|
        DebugPrint("^1[TAXI DEBUG]^7 No taxi exists for ride")
 | 
						|
        return
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Wenn kein Fahrer, Spieler selbst fahren lassen
 | 
						|
    if not currentDriver or not DoesEntityExist(currentDriver) then
 | 
						|
        lib.notify({
 | 
						|
            title = 'Taxi Service',
 | 
						|
            description = 'Kein Fahrer verfügbar. Du musst selbst fahren!',
 | 
						|
            type = 'warning'
 | 
						|
        })
 | 
						|
        return
 | 
						|
    end
 | 
						|
    
 | 
						|
    lib.notify({
 | 
						|
        title = 'Taxi Service',
 | 
						|
        description = 'Fahrt gestartet - Preis: $' .. price,
 | 
						|
        type = 'success'
 | 
						|
    })
 | 
						|
    
 | 
						|
    -- Destination Blip erstellen
 | 
						|
    if destinationBlip then
 | 
						|
        RemoveBlip(destinationBlip)
 | 
						|
    end
 | 
						|
    
 | 
						|
    destinationBlip = AddBlipForCoord(destination.x, destination.y, destination.z)
 | 
						|
    SetBlipSprite(destinationBlip, 1)
 | 
						|
    SetBlipColour(destinationBlip, 2)
 | 
						|
    SetBlipScale(destinationBlip, 0.8)
 | 
						|
    SetBlipRoute(destinationBlip, true)
 | 
						|
    BeginTextCommandSetBlipName("STRING")
 | 
						|
    AddTextComponentString("Taxi Ziel")
 | 
						|
    EndTextCommandSetBlipName(destinationBlip)
 | 
						|
    
 | 
						|
    -- Taximeter starten
 | 
						|
    taxiMeter.isRunning = true
 | 
						|
    taxiMeter.startCoords = GetEntityCoords(currentTaxi)
 | 
						|
    taxiMeter.currentFare = 0
 | 
						|
    taxiMeter.pricePerKm = Config.PricePerKm
 | 
						|
    
 | 
						|
    -- Ziel im Fahrzeug-State speichern für die KI-Logik
 | 
						|
    Entity(currentTaxi).state.currentDestination = destination
 | 
						|
 | 
						|
    -- Zum Ziel fahren mit verbesserter Navigation
 | 
						|
    local drivingStyle = 786603 -- Normal/Vorsichtig
 | 
						|
    TaskVehicleDriveToCoordLongrange(currentDriver, currentTaxi, destination.x, destination.y, destination.z, 20.0, drivingStyle, 10.0)
 | 
						|
 | 
						|
    -- Fahrer-Dialog anzeigen
 | 
						|
    local driverName = Entity(currentTaxi).state.driverName or "Taxi-Fahrer"
 | 
						|
    local dialogOptions = {
 | 
						|
        "Ich bringe dich sicher ans Ziel.",
 | 
						|
        "Schönes Wetter heute, oder?",
 | 
						|
        "Ich kenne eine Abkürzung.",
 | 
						|
        "Bist du aus der Gegend?",
 | 
						|
        "Ich fahre schon seit 15 Jahren Taxi.",
 | 
						|
        "Entspann dich und genieße die Fahrt."
 | 
						|
    }
 | 
						|
 | 
						|
    -- Zufälligen Dialog auswählen und anzeigen
 | 
						|
    Wait(3000) -- Kurz warten bevor der Fahrer spricht
 | 
						|
    lib.notify({
 | 
						|
        title = driverName,
 | 
						|
        description = dialogOptions[math.random(#dialogOptions)],
 | 
						|
        type = 'info',
 | 
						|
        icon = 'comment',
 | 
						|
        position = 'top-center',
 | 
						|
        duration = 5000
 | 
						|
    })
 | 
						|
    
 | 
						|
    -- Fahrt überwachen
 | 
						|
    MonitorTaxiRide(destination, price)
 | 
						|
end
 | 
						|
 | 
						|
function MonitorTaxiRide(destination, price)
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Monitoring taxi ride...")
 | 
						|
    
 | 
						|
    local rideTimeout = GetGameTimer() + (10 * 60 * 1000) -- 10 Minuten Timeout
 | 
						|
    
 | 
						|
    CreateThread(function()
 | 
						|
        while DoesEntityExist(currentTaxi) and DoesEntityExist(currentDriver) do
 | 
						|
            local taxiCoords = GetEntityCoords(currentTaxi)
 | 
						|
            local distance = #(vector3(destination.x, destination.y, destination.z) - taxiCoords)
 | 
						|
            
 | 
						|
            -- Überprüfen ob wir angekommen sind
 | 
						|
            if distance < 10.0 then
 | 
						|
                -- Angekommen
 | 
						|
                TaskVehicleTempAction(currentDriver, currentTaxi, 27, 3000)
 | 
						|
                
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Arrived at destination")
 | 
						|
                lib.notify({
 | 
						|
                    title = 'Taxi Service',
 | 
						|
                    description = 'Du bist angekommen! Preis: $' .. price,
 | 
						|
                    type = 'success'
 | 
						|
                })
 | 
						|
                
 | 
						|
                -- Bezahlung
 | 
						|
                TriggerServerEvent('taxi:payFare', price)
 | 
						|
                
 | 
						|
                -- Blips entfernen
 | 
						|
                if destinationBlip then
 | 
						|
                    RemoveBlip(destinationBlip)
 | 
						|
                    destinationBlip = nil
 | 
						|
                end
 | 
						|
                
 | 
						|
                -- Fahrer-Dialog anzeigen
 | 
						|
                local driverName = Entity(currentTaxi).state.driverName or "Taxi-Fahrer"
 | 
						|
                local arrivalDialogs = {
 | 
						|
                    "Wir sind da! Das macht dann $" .. price .. ".",
 | 
						|
                    "Angekommen! $" .. price .. " bitte.",
 | 
						|
                    "Hier sind wir. $" .. price .. ", bargeldlos ist auch möglich.",
 | 
						|
                    "Ziel erreicht! Das macht $" .. price .. "."
 | 
						|
                }
 | 
						|
                
 | 
						|
                lib.notify({
 | 
						|
                    title = driverName,
 | 
						|
                    description = arrivalDialogs[math.random(#arrivalDialogs)],
 | 
						|
                    type = 'info',
 | 
						|
                    icon = 'comment',
 | 
						|
                    position = 'top-center',
 | 
						|
                    duration = 5000
 | 
						|
                })
 | 
						|
                
 | 
						|
                -- Nach 10 Sekunden Taxi despawnen
 | 
						|
                SetTimeout(10000, function()
 | 
						|
                    DespawnTaxi()
 | 
						|
                end)
 | 
						|
                
 | 
						|
                break
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Überprüfen ob die Fahrt zu lange dauert
 | 
						|
            if GetGameTimer() > rideTimeout then
 | 
						|
                DebugPrint("^1[TAXI DEBUG]^7 Taxi ride timed out!")
 | 
						|
                
 | 
						|
                -- Berechne verbleibende Distanz
 | 
						|
                local taxiCoords = GetEntityCoords(currentTaxi)
 | 
						|
                local remainingDistance = #(vector3(destination.x, destination.y, destination.z) - taxiCoords)
 | 
						|
                
 | 
						|
                lib.notify({
 | 
						|
                    title = 'Taxi Service',
 | 
						|
                    description = 'Die Fahrt dauert zu lange. Noch ' .. math.ceil(remainingDistance) .. 'm bis zum Ziel!',
 | 
						|
                    type = 'warning'
 | 
						|
                })
 | 
						|
                
 | 
						|
                -- Teleportiere Taxi näher ans Ziel (75% des Weges)
 | 
						|
                local direction = vector3(
 | 
						|
                    destination.x - taxiCoords.x,
 | 
						|
                    destination.y - taxiCoords.y,
 | 
						|
                    destination.z - taxiCoords.z
 | 
						|
                )
 | 
						|
                local normalizedDir = norm(direction)
 | 
						|
                local teleportDistance = remainingDistance * 0.75
 | 
						|
                
 | 
						|
                local newPos = vector3(
 | 
						|
                    taxiCoords.x + normalizedDir.x * teleportDistance,
 | 
						|
                    taxiCoords.y + normalizedDir.y * teleportDistance,
 | 
						|
                    taxiCoords.z
 | 
						|
                )
 | 
						|
                
 | 
						|
                -- Finde gültige Z-Koordinate
 | 
						|
                local success, groundZ = GetGroundZFor_3dCoord(newPos.x, newPos.y, newPos.z, true)
 | 
						|
                if success then
 | 
						|
                    newPos = vector3(newPos.x, newPos.y, groundZ)
 | 
						|
                end
 | 
						|
                
 | 
						|
                -- Teleportiere Taxi
 | 
						|
                SetEntityCoords(currentTaxi, newPos.x, newPos.y, newPos.z, false, false, false, false)
 | 
						|
                
 | 
						|
                -- Neues Timeout setzen (2 Minuten)
 | 
						|
                rideTimeout = GetGameTimer() + (2 * 60 * 1000)
 | 
						|
            end
 | 
						|
            
 | 
						|
            Wait(2000)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
function SelfDriveTaxi()
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Player driving taxi themselves")
 | 
						|
    
 | 
						|
    if not currentTaxi or not DoesEntityExist(currentTaxi) then
 | 
						|
        return
 | 
						|
    end
 | 
						|
    
 | 
						|
    local playerPed = PlayerPedId()
 | 
						|
    
 | 
						|
    -- Fahrer entfernen falls vorhanden
 | 
						|
    if currentDriver and DoesEntityExist(currentDriver) then
 | 
						|
        DeleteEntity(currentDriver)
 | 
						|
        currentDriver = nil
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Spieler zum Fahrersitz bewegen
 | 
						|
    TaskShuffleToNextVehicleSeat(playerPed, currentTaxi)
 | 
						|
    
 | 
						|
    lib.notify({
 | 
						|
        title = 'Taxi Service',
 | 
						|
        description = 'Du fährst das Taxi jetzt selbst. Nutze /stoptaxi um es zu beenden.',
 | 
						|
        type = 'info'
 | 
						|
    })
 | 
						|
end
 | 
						|
 | 
						|
function ExitTaxi()
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Player exiting taxi")
 | 
						|
    
 | 
						|
    if not currentTaxi or not DoesEntityExist(currentTaxi) then
 | 
						|
        return
 | 
						|
    end
 | 
						|
    
 | 
						|
    local playerPed = PlayerPedId()
 | 
						|
    TaskLeaveVehicle(playerPed, currentTaxi, 0)
 | 
						|
    
 | 
						|
    lib.notify({
 | 
						|
        title = 'Taxi Service',
 | 
						|
        description = 'Du bist ausgestiegen',
 | 
						|
        type = 'info'
 | 
						|
    })
 | 
						|
    
 | 
						|
    -- Taxi nach 5 Sekunden despawnen
 | 
						|
    SetTimeout(5000, function()
 | 
						|
        DespawnTaxi()
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
function DespawnTaxi()
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Despawning taxi")
 | 
						|
    
 | 
						|
    if not currentTaxi or not DoesEntityExist(currentTaxi) then
 | 
						|
        return
 | 
						|
    end
 | 
						|
    
 | 
						|
    -- Taxi wegfahren lassen, wenn ein Fahrer existiert
 | 
						|
    if currentDriver and DoesEntityExist(currentDriver) then
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Making taxi drive away before despawn")
 | 
						|
        
 | 
						|
        -- Zufällige Position in der Nähe finden
 | 
						|
        local taxiCoords = GetEntityCoords(currentTaxi)
 | 
						|
        local angle = math.random() * 2 * math.pi
 | 
						|
        local distance = 150.0
 | 
						|
        local driveToX = taxiCoords.x + math.cos(angle) * distance
 | 
						|
        local driveToY = taxiCoords.y + math.sin(angle) * distance
 | 
						|
        
 | 
						|
        -- Taxi wegfahren lassen
 | 
						|
        TaskVehicleDriveToCoordLongrange(currentDriver, currentTaxi, driveToX, driveToY, taxiCoords.z, 25.0, 786603, 5.0)
 | 
						|
        
 | 
						|
        -- Blips entfernen
 | 
						|
        if taxiBlip then
 | 
						|
            RemoveBlip(taxiBlip)
 | 
						|
            taxiBlip = nil
 | 
						|
        end
 | 
						|
        
 | 
						|
        if mapBlip then
 | 
						|
            RemoveBlip(mapBlip)
 | 
						|
            mapBlip = nil
 | 
						|
        end
 | 
						|
        
 | 
						|
        if destinationBlip then
 | 
						|
            RemoveBlip(destinationBlip)
 | 
						|
            destinationBlip = nil
 | 
						|
        end
 | 
						|
        
 | 
						|
        -- Nach 10 Sekunden tatsächlich löschen
 | 
						|
        SetTimeout(10000, function()
 | 
						|
            -- Fahrer löschen
 | 
						|
            if currentDriver and DoesEntityExist(currentDriver) then
 | 
						|
                DeleteEntity(currentDriver)
 | 
						|
                currentDriver = nil
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Driver deleted")
 | 
						|
            end
 | 
						|
            
 | 
						|
            -- Taxi löschen
 | 
						|
            if currentTaxi and DoesEntityExist(currentTaxi) then
 | 
						|
                exports['qb-target']:RemoveTargetEntity(currentTaxi)
 | 
						|
                DeleteEntity(currentTaxi)
 | 
						|
                currentTaxi = nil
 | 
						|
                DebugPrint("^2[TAXI DEBUG]^7 Taxi deleted")
 | 
						|
            end
 | 
						|
            DebugPrint("^2[TAXI DEBUG]^7 Taxi despawn completed")
 | 
						|
        end)
 | 
						|
    else
 | 
						|
        -- Sofort löschen wenn kein Fahrer da ist
 | 
						|
        if taxiBlip then
 | 
						|
            RemoveBlip(taxiBlip)
 | 
						|
            taxiBlip = nil
 | 
						|
        end
 | 
						|
        
 | 
						|
        if mapBlip then
 | 
						|
            RemoveBlip(mapBlip)
 | 
						|
            mapBlip = nil
 | 
						|
        end
 | 
						|
        
 | 
						|
        if destinationBlip then
 | 
						|
            RemoveBlip(destinationBlip)
 | 
						|
            destinationBlip = nil
 | 
						|
        end
 | 
						|
        
 | 
						|
        -- Taxi löschen
 | 
						|
        if currentTaxi and DoesEntityExist(currentTaxi) then
 | 
						|
            exports['qb-target']:RemoveTargetEntity(currentTaxi)
 | 
						|
            DeleteEntity(currentTaxi)
 | 
						|
            currentTaxi = nil
 | 
						|
            DebugPrint("^2[TAXI DEBUG]^7 Taxi deleted")
 | 
						|
        end
 | 
						|
        
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Taxi despawn completed (no driver)")
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
function CalculateDistanceToCoords(coords)
 | 
						|
    local playerCoords = GetEntityCoords(PlayerPedId())
 | 
						|
    return #(playerCoords - coords)
 | 
						|
end
 | 
						|
 | 
						|
-- Command um Taxi zu stoppen
 | 
						|
RegisterCommand('stoptaxi', function()
 | 
						|
    if currentTaxi and DoesEntityExist(currentTaxi) then
 | 
						|
        DespawnTaxi()
 | 
						|
        lib.notify({
 | 
						|
            title = 'Taxi Service',
 | 
						|
            description = 'Taxi-Service beendet',
 | 
						|
            type = 'info'
 | 
						|
        })
 | 
						|
    else
 | 
						|
        lib.notify({
 | 
						|
            title = 'Taxi Service',
 | 
						|
            description = 'Du hast kein aktives Taxi',
 | 
						|
            type = 'error'
 | 
						|
        })
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
-- Thread zum Überwachen der Tasten im Taxi
 | 
						|
CreateThread(function()
 | 
						|
    while true do
 | 
						|
        Wait(0)
 | 
						|
        
 | 
						|
        if currentTaxi and DoesEntityExist(currentTaxi) then
 | 
						|
            local playerPed = PlayerPedId()
 | 
						|
            
 | 
						|
            if IsPedInVehicle(playerPed, currentTaxi, false) then
 | 
						|
                -- Zeige Hinweise an
 | 
						|
                local helpText = '[E] - Ziel wählen     [F] - Fahrt beenden'
 | 
						|
                lib.showTextUI(helpText, {
 | 
						|
                    position = "top-center",
 | 
						|
                    icon = 'taxi',
 | 
						|
                    style = {
 | 
						|
                        borderRadius = 10,
 | 
						|
                        backgroundColor = '#48BB78',
 | 
						|
                        color = 'white'
 | 
						|
                    }
 | 
						|
                })
 | 
						|
                
 | 
						|
                -- Wenn E gedrückt wird, öffne Menü
 | 
						|
                if IsControlJustReleased(0, 38) then -- E Taste
 | 
						|
                    OpenDestinationMenu()
 | 
						|
                end
 | 
						|
                
 | 
						|
                -- Wenn F gedrückt wird, beende Fahrt
 | 
						|
                if IsControlJustReleased(0, 23) then -- F Taste
 | 
						|
                    lib.hideTextUI()
 | 
						|
                    EndTaxiRide()
 | 
						|
                end
 | 
						|
            else
 | 
						|
                lib.hideTextUI()
 | 
						|
            end
 | 
						|
        else
 | 
						|
            lib.hideTextUI()
 | 
						|
            Wait(1000) -- Längere Wartezeit wenn kein Taxi existiert
 | 
						|
        end
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
-- Funktion zum Beenden der Fahrt
 | 
						|
function EndTaxiRide()
 | 
						|
    DebugPrint("^2[TAXI DEBUG]^7 Ending taxi ride")
 | 
						|
    
 | 
						|
    if not currentTaxi or not DoesEntityExist(currentTaxi) then
 | 
						|
        return
 | 
						|
    end
 | 
						|
    
 | 
						|
    local playerPed = PlayerPedId()
 | 
						|
    
 | 
						|
    -- Fahrt beenden Benachrichtigung
 | 
						|
    lib.notify({
 | 
						|
        title = 'Taxi Service',
 | 
						|
        description = 'Fahrt beendet. Du steigst aus.',
 | 
						|
        type = 'info'
 | 
						|
    })
 | 
						|
    
 | 
						|
    -- Spieler aussteigen lassen
 | 
						|
    TaskLeaveVehicle(playerPed, currentTaxi, 0)
 | 
						|
    
 | 
						|
    -- Warten bis ausgestiegen
 | 
						|
    CreateThread(function()
 | 
						|
        local timeout = GetGameTimer() + 5000
 | 
						|
        while GetGameTimer() < timeout do
 | 
						|
            if not IsPedInVehicle(playerPed, currentTaxi, false) then
 | 
						|
                -- Spieler ist ausgestiegen
 | 
						|
                break
 | 
						|
            end
 | 
						|
            Wait(100)
 | 
						|
        end
 | 
						|
        
 | 
						|
        -- Taxi nach 5 Sekunden despawnen
 | 
						|
        SetTimeout(5000, function()
 | 
						|
            DespawnTaxi()
 | 
						|
        end)
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
-- Thread zum Überwachen des Einsteigens ins Taxi (ohne qb-target)
 | 
						|
CreateThread(function()
 | 
						|
    while true do
 | 
						|
        Wait(1000)
 | 
						|
        
 | 
						|
        if currentTaxi and DoesEntityExist(currentTaxi) and not IsPedInVehicle(PlayerPedId(), currentTaxi, false) then
 | 
						|
            -- Spieler ist nicht im Taxi, aber Taxi existiert
 | 
						|
            local playerPed = PlayerPedId()
 | 
						|
            local playerCoords = GetEntityCoords(playerPed)
 | 
						|
            local taxiCoords = GetEntityCoords(currentTaxi)
 | 
						|
            
 | 
						|
            if #(playerCoords - taxiCoords) < 5.0 then
 | 
						|
                -- Spieler ist in der Nähe des Taxis
 | 
						|
                lib.showTextUI('[E] - Ins Taxi einsteigen', {
 | 
						|
                    position = "top-center",
 | 
						|
                    icon = 'car-side',
 | 
						|
                    style = {
 | 
						|
                        borderRadius = 10,
 | 
						|
                        backgroundColor = '#4299E1',
 | 
						|
                        color = 'white'
 | 
						|
                    }
 | 
						|
                })
 | 
						|
                
 | 
						|
                -- Prüfen ob E gedrückt wird
 | 
						|
                if IsControlJustReleased(0, 38) then -- E Taste
 | 
						|
                    -- Spieler will einsteigen
 | 
						|
                    lib.hideTextUI()
 | 
						|
                    -- Spieler hinten einsteigen lassen
 | 
						|
                    local seatIndex = 1 -- Hinten links
 | 
						|
                    if not IsVehicleSeatFree(currentTaxi, 1) then
 | 
						|
                        seatIndex = 2 -- Hinten rechts
 | 
						|
                    end
 | 
						|
                    if not IsVehicleSeatFree(currentTaxi, seatIndex) then
 | 
						|
                        seatIndex = 0 -- Beifahrer als Fallback
 | 
						|
                    end
 | 
						|
                    
 | 
						|
                    TaskEnterVehicle(playerPed, currentTaxi, 10000, seatIndex, 1.0, 1, 0)
 | 
						|
                    
 | 
						|
                    -- Warten bis eingestiegen
 | 
						|
                    local entryTimeout = GetGameTimer() + 10000
 | 
						|
                    CreateThread(function()
 | 
						|
                        while GetGameTimer() < entryTimeout do
 | 
						|
                            if IsPedInVehicle(playerPed, currentTaxi, false) then
 | 
						|
                                -- Spieler ist eingestiegen
 | 
						|
                                Wait(1000)
 | 
						|
                                OpenDestinationMenu()
 | 
						|
                                break
 | 
						|
                            end
 | 
						|
                            Wait(100)
 | 
						|
                        end
 | 
						|
                    end)
 | 
						|
                end
 | 
						|
            else
 | 
						|
                lib.hideTextUI()
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
-- Cleanup beim Resource Stop
 | 
						|
AddEventHandler('onResourceStop', function(resourceName)
 | 
						|
    if GetCurrentResourceName() == resourceName then
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Cleaning up main script...")
 | 
						|
        
 | 
						|
        -- UI ausblenden
 | 
						|
        lib.hideTextUI()
 | 
						|
        
 | 
						|
        -- Taxi despawnen
 | 
						|
        DespawnTaxi()
 | 
						|
        
 | 
						|
        DebugPrint("^2[TAXI DEBUG]^7 Main cleanup completed")
 | 
						|
    end
 | 
						|
end)
 | 
						|
                    
 | 
						|
 | 
						|
 | 
						|
 |