forked from Simnation/Main
1549 lines
57 KiB
Lua
1549 lines
57 KiB
Lua
local QBCore = exports['qb-core']:GetCoreObject()
|
|
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
|
|
}
|
|
|
|
print("^2[TAXI DEBUG]^7 Main script loaded")
|
|
|
|
-- Taxi rufen Command
|
|
RegisterCommand('taxi', function()
|
|
print("^2[TAXI DEBUG]^7 Taxi command executed")
|
|
|
|
if currentTaxi and DoesEntityExist(currentTaxi) then
|
|
print("^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()
|
|
print("^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)
|
|
|
|
print("^2[TAXI DEBUG]^7 Player coords: " .. tostring(playerCoords))
|
|
|
|
-- Verbesserte Spawn-Position für Taxi finden
|
|
local spawnCoords = GetImprovedTaxiSpawnPosition(playerCoords)
|
|
if not spawnCoords then
|
|
print("^1[TAXI DEBUG]^7 No spawn position found")
|
|
lib.notify({
|
|
title = 'Taxi Service',
|
|
description = 'Kein geeigneter Spawn-Punkt gefunden',
|
|
type = 'error'
|
|
})
|
|
return
|
|
end
|
|
|
|
print("^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
|
|
print("^1[TAXI DEBUG]^7 Failed to spawn taxi")
|
|
lib.notify({
|
|
title = 'Taxi Service',
|
|
description = 'Taxi konnte nicht gespawnt werden',
|
|
type = 'error'
|
|
})
|
|
return
|
|
end
|
|
|
|
print("^2[TAXI DEBUG]^7 Taxi spawned: " .. taxi)
|
|
currentTaxi = taxi
|
|
|
|
-- Fahrer spawnen
|
|
local driver = SpawnTaxiDriver(taxi)
|
|
if driver then
|
|
currentDriver = driver
|
|
print("^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
|
|
print("^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)
|
|
print("^2[TAXI DEBUG]^7 Finding improved spawn position...")
|
|
|
|
-- Maximale Entfernung, die wir akzeptieren
|
|
local maxAcceptableDistance = 100.0
|
|
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
|
|
foundNode, nodePos = GetClosestVehicleNode(playerCoords.x, playerCoords.y, playerCoords.z, 1, 3.0, 0)
|
|
|
|
if foundNode then
|
|
local nodeDistance = #(playerCoords - nodePos)
|
|
if nodeDistance < maxAcceptableDistance then
|
|
roadPosition = nodePos
|
|
print("^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 then
|
|
bestDistance = nodeDistance
|
|
bestPosition = {x = nodePos.x, y = nodePos.y, z = nodePos.z, w = 0.0}
|
|
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 < maxAcceptableDistance then
|
|
roadPosition = nodePos
|
|
print("^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 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))
|
|
table.insert(sortedSpawns, {
|
|
coords = spawnPos,
|
|
distance = distance
|
|
})
|
|
end
|
|
|
|
-- Nach Entfernung sortieren (nächste zuerst)
|
|
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
|
|
print("^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 nahe Position gefunden
|
|
-- Wenn wir eine "beste" Position haben, die nur etwas zu weit weg ist, verwenden wir diese
|
|
if bestPosition then
|
|
print("^3[TAXI DEBUG]^7 No position within " .. maxAcceptableDistance .. "m found, using best available at " .. bestDistance .. "m")
|
|
return bestPosition
|
|
end
|
|
|
|
-- Wenn alles fehlschlägt: Generiere eine zufällige Position in der Nähe des Spielers
|
|
print("^3[TAXI DEBUG]^7 Generating random position within " .. maxAcceptableDistance .. "m of player")
|
|
|
|
-- Versuche bis zu 10 Mal, eine gültige Position zu finden
|
|
for attempt = 1, 10 do
|
|
-- Zufällige Position im Umkreis
|
|
local angle = math.random() * 2 * math.pi
|
|
local distance = math.random(30, 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
|
|
print("^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
|
|
local angle = math.random() * 2 * math.pi
|
|
local distance = math.random(30, 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
|
|
|
|
print("^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)
|
|
print("^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
|
|
|
|
print("^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
|
|
print("^3[TAXI DEBUG]^7 Waiting for taxi model to load...")
|
|
Wait(100)
|
|
end
|
|
end
|
|
|
|
if not modelLoaded then
|
|
print("^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
|
|
print("^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
|
|
print("^1[TAXI DEBUG]^7 Failed to create taxi vehicle!")
|
|
return nil
|
|
end
|
|
|
|
print("^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
|
|
print("^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
|
|
|
|
print("^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, 25), -- 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
|
|
print("^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
|
|
print("^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
|
|
print("^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
|
|
print("^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
|
|
|
|
print("^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
|
|
|
|
print("^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
|
|
print("^3[TAXI DEBUG]^7 Recovery: Backing up")
|
|
TaskVehicleTempAction(driver, vehicle, 8, 2000) -- Reverse
|
|
Wait(2000)
|
|
|
|
if forwardClear then
|
|
-- Wenn vorne auch frei, einfach weiterfahren
|
|
print("^3[TAXI DEBUG]^7 Recovery: Path clear, continuing")
|
|
TaxiDriverContinueRoute(driver, vehicle)
|
|
else
|
|
-- Sonst versuchen zu wenden
|
|
print("^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
|
|
print("^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
|
|
print("^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
|
|
print("^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)
|
|
print("^2[TAXI DEBUG]^7 Spawning taxi driver...")
|
|
|
|
-- Bessere Fahrer-Models mit Fallbacks
|
|
local driverModels = {
|
|
"s_m_y_taxidriver_01", -- 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
|
|
print("^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
|
|
print("^3[TAXI DEBUG]^7 Still waiting for model " .. modelName .. " (attempt " .. attempts .. ")")
|
|
end
|
|
Wait(100)
|
|
end
|
|
|
|
if HasModelLoaded(driverHash) then
|
|
print("^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
|
|
print("^2[TAXI DEBUG]^7 Driver created successfully with model: " .. modelName .. " (ID: " .. driver .. ")")
|
|
break
|
|
else
|
|
print("^1[TAXI DEBUG]^7 Failed to create driver with loaded model: " .. modelName)
|
|
SetModelAsNoLongerNeeded(driverHash)
|
|
end
|
|
else
|
|
print("^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
|
|
print("^1[TAXI DEBUG]^7 Could not create any driver! Continuing without driver...")
|
|
return nil
|
|
end
|
|
|
|
-- Fahrer konfigurieren
|
|
print("^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
|
|
print("^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
|
|
|
|
print("^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)
|
|
print("^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
|
|
print("^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
|
|
print("^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)
|
|
print("^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
|
|
print("^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
|
|
print("^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()
|
|
print("^2[TAXI DEBUG]^7 Player entering taxi")
|
|
|
|
if not currentTaxi or not DoesEntityExist(currentTaxi) then
|
|
print("^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
|
|
print("^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
|
|
print("^1[TAXI DEBUG]^7 Player failed to enter taxi")
|
|
lib.notify({
|
|
title = 'Taxi Service',
|
|
description = 'Einsteigen fehlgeschlagen',
|
|
type = 'error'
|
|
})
|
|
end
|
|
end)
|
|
end)
|
|
|
|
function OpenDestinationMenu()
|
|
print("^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)
|
|
print("^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
|
|
print("^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)
|
|
print("^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)
|
|
|
|
print("^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
|
|
print("^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()
|
|
print("^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()
|
|
print("^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()
|
|
print("^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
|
|
print("^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
|
|
print("^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
|
|
print("^2[TAXI DEBUG]^7 Taxi deleted")
|
|
end
|
|
print("^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
|
|
print("^2[TAXI DEBUG]^7 Taxi deleted")
|
|
end
|
|
|
|
print("^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()
|
|
print("^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
|
|
print("^2[TAXI DEBUG]^7 Cleaning up main script...")
|
|
|
|
-- UI ausblenden
|
|
lib.hideTextUI()
|
|
|
|
-- Taxi despawnen
|
|
DespawnTaxi()
|
|
|
|
print("^2[TAXI DEBUG]^7 Main cleanup completed")
|
|
end
|
|
end)
|
|
|
|
|
|
|