local QBCore = exports['qb-core']:GetCoreObject() local stationVehicles = {} local stationBlips = {} -- Taxi Stationen initialisieren CreateThread(function() Wait(1000) InitializeTaxiStations() end) function InitializeTaxiStations() for stationId, station in pairs(Config.TaxiStations) do -- Blip für Station erstellen local blip = AddBlipForCoord(station.blipCoords.x, station.blipCoords.y, station.blipCoords.z) SetBlipSprite(blip, 198) SetBlipColour(blip, 5) SetBlipScale(blip, 0.8) SetBlipAsShortRange(blip, true) BeginTextCommandSetBlipName("STRING") AddTextComponentString(station.name) EndTextCommandSetBlipName(blip) stationBlips[stationId] = blip -- Fahrzeuge an Station spawnen stationVehicles[stationId] = {} for vehicleId, vehicleData in pairs(station.vehicles) do SpawnStationVehicle(stationId, vehicleId, vehicleData) end end end function SpawnStationVehicle(stationId, vehicleId, vehicleData) CreateThread(function() local vehicleHash = GetHashKey(vehicleData.model) RequestModel(vehicleHash) while not HasModelLoaded(vehicleHash) do Wait(100) end local vehicle = CreateVehicle( vehicleHash, vehicleData.coords.x, vehicleData.coords.y, vehicleData.coords.z, vehicleData.coords.w, false, false ) SetEntityAsMissionEntity(vehicle, true, true) SetVehicleOnGroundProperly(vehicle) SetVehicleDoorsLocked(vehicle, 2) -- Locked SetVehicleEngineOn(vehicle, false, true, false) -- Fahrzeug-Info speichern stationVehicles[stationId][vehicleId] = { entity = vehicle, data = vehicleData, occupied = false } -- qb-target für Fahrzeug hinzufügen exports['qb-target']:AddTargetEntity(vehicle, { options = { { type = "client", event = "taxi:enterStationVehicle", icon = "fas fa-taxi", label = "Taxi nehmen ($" .. vehicleData.pricePerKm .. "/km)", stationId = stationId, vehicleId = vehicleId } }, distance = 3.0 }) SetModelAsNoLongerNeeded(vehicleHash) end) end -- Event für Einsteigen in Station-Taxi RegisterNetEvent('taxi:enterStationVehicle', function(data) local stationId = data.stationId local vehicleId = data.vehicleId if not stationVehicles[stationId] or not stationVehicles[stationId][vehicleId] then lib.notify({ title = 'Taxi Service', description = 'Dieses Taxi ist nicht verfügbar', type = 'error' }) return end local vehicleInfo = stationVehicles[stationId][vehicleId] if vehicleInfo.occupied then lib.notify({ title = 'Taxi Service', description = 'Dieses Taxi ist bereits besetzt', type = 'error' }) return end -- Spieler ins Fahrzeug setzen local playerPed = PlayerPedId() local vehicle = vehicleInfo.entity -- Türen entsperren SetVehicleDoorsLocked(vehicle, 1) -- Fahrer spawnen local driverHash = GetHashKey("a_m_m_taxi_01") RequestModel(driverHash) while not HasModelLoaded(driverHash) do Wait(100) end local driver = CreatePedInsideVehicle(vehicle, 26, driverHash, -1, true, false) SetEntityAsMissionEntity(driver, true, true) SetPedFleeAttributes(driver, 0, 0) SetPedCombatAttributes(driver, 17, 1) SetPedSeeingRange(driver, 0.0) SetPedHearingRange(driver, 0.0) SetPedAlertness(driver, 0) SetPedKeepTask(driver, true) -- Spieler einsteigen lassen TaskEnterVehicle(playerPed, vehicle, 10000, 0, 1.0, 1, 0) -- Warten bis Spieler eingestiegen ist CreateThread(function() local timeout = GetGameTimer() + 10000 while GetGameTimer() < timeout do if IsPedInVehicle(playerPed, vehicle, false) then vehicleInfo.occupied = true vehicleInfo.driver = driver -- Ziel-Menu öffnen OpenStationTaxiMenu(stationId, vehicleId, vehicle, driver, vehicleInfo.data.pricePerKm) break end Wait(100) end end) end) function OpenStationTaxiMenu(stationId, vehicleId, vehicle, driver, pricePerKm) local options = {} -- Bekannte Ziele hinzufügen for _, destination in pairs(Config.KnownDestinations) do local customPrice = math.max(Config.MinFare, math.ceil((CalculateDistanceToCoords(destination.coords) / 1000) * pricePerKm)) table.insert(options, { title = destination.name, description = 'Preis: $' .. customPrice, icon = 'map-marker', onSelect = function() StartStationTaxiRide(stationId, vehicleId, vehicle, driver, destination.coords, customPrice) 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 * pricePerKm)) StartStationTaxiRide(stationId, vehicleId, vehicle, driver, coords, price) else lib.notify({ title = 'Taxi Service', description = 'Du hast keinen Waypoint gesetzt', type = 'error' }) OpenStationTaxiMenu(stationId, vehicleId, vehicle, driver, pricePerKm) end end }) -- Aussteigen Option table.insert(options, { title = 'Aussteigen', description = 'Das Taxi verlassen', icon = 'door-open', onSelect = function() ExitStationTaxi(stationId, vehicleId, vehicle, driver) end }) lib.registerContext({ id = 'station_taxi_menu', title = 'Taxi - Ziel wählen', options = options }) lib.showContext('station_taxi_menu') end function StartStationTaxiRide(stationId, vehicleId, vehicle, driver, destination, price) lib.notify({ title = 'Taxi Service', description = 'Fahrt gestartet - Preis: $' .. price, type = 'success' }) -- Destination Blip erstellen local destinationBlip = AddBlipForCoord(destination.x, destination.y, destination.z) SetBlipSprite(destinationBlip, 1) SetBlipColour(destinationBlip, 2) SetBlipScale(destinationBlip, 0.8) BeginTextCommandSetBlipName("STRING") AddTextComponentString("Taxi Ziel") EndTextCommandSetBlipName(destinationBlip) -- Zum Ziel fahren TaskVehicleDriveToCoord(driver, vehicle, destination.x, destination.y, destination.z, 25.0, 0, GetEntityModel(vehicle), 786603, 1.0, true) -- Fahrt überwachen CreateThread(function() while DoesEntityExist(vehicle) and DoesEntityExist(driver) do local vehicleCoords = GetEntityCoords(vehicle) local distance = #(vector3(destination.x, destination.y, destination.z) - vehicleCoords) if distance < 10.0 then -- Angekommen TaskVehicleTempAction(driver, vehicle, 27, 3000) lib.notify({ title = 'Taxi Service', description = 'Du bist angekommen! Preis: $' .. price, type = 'success' }) -- Bezahlung TriggerServerEvent('taxi:payFare', price) -- Blip entfernen RemoveBlip(destinationBlip) -- Nach 10 Sekunden Taxi zurück zur Station SetTimeout(10000, function() ReturnTaxiToStation(stationId, vehicleId, vehicle, driver) end) break end Wait(2000) end end) end function ExitStationTaxi(stationId, vehicleId, vehicle, driver) local playerPed = PlayerPedId() TaskLeaveVehicle(playerPed, vehicle, 0) lib.notify({ title = 'Taxi Service', description = 'Du bist ausgestiegen', type = 'info' }) -- Taxi zurück zur Station nach 5 Sekunden SetTimeout(5000, function() ReturnTaxiToStation(stationId, vehicleId, vehicle, driver) end) end function ReturnTaxiToStation(stationId, vehicleId, vehicle, driver) if not stationVehicles[stationId] or not stationVehicles[stationId][vehicleId] then return end -- Fahrer löschen if DoesEntityExist(driver) then DeleteEntity(driver) end -- Fahrzeug löschen if DoesEntityExist(vehicle) then exports['qb-target']:RemoveTargetEntity(vehicle) DeleteEntity(vehicle) end -- Fahrzeug als nicht besetzt markieren stationVehicles[stationId][vehicleId].occupied = false stationVehicles[stationId][vehicleId].driver = nil -- Nach Respawn-Zeit neues Fahrzeug spawnen SetTimeout(Config.StationTaxiRespawnTime * 1000, function() if stationVehicles[stationId] and stationVehicles[stationId][vehicleId] then local vehicleData = stationVehicles[stationId][vehicleId].data SpawnStationVehicle(stationId, vehicleId, vehicleData) end end) end function CalculateDistanceToCoords(coords) local playerCoords = GetEntityCoords(PlayerPedId()) return #(playerCoords - coords) end -- Cleanup beim Resource Stop AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then -- Alle Station-Fahrzeuge löschen for stationId, vehicles in pairs(stationVehicles) do for vehicleId, vehicleInfo in pairs(vehicles) do if DoesEntityExist(vehicleInfo.entity) then exports['qb-target']:RemoveTargetEntity(vehicleInfo.entity) DeleteEntity(vehicleInfo.entity) end if vehicleInfo.driver and DoesEntityExist(vehicleInfo.driver) then DeleteEntity(vehicleInfo.driver) end end end -- Alle Blips entfernen for _, blip in pairs(stationBlips) do RemoveBlip(blip) end end end) -- Event für Admin Respawn RegisterNetEvent('taxi:respawnAllStations', function() -- Alle bestehenden Fahrzeuge löschen for stationId, vehicles in pairs(stationVehicles) do for vehicleId, vehicleInfo in pairs(vehicles) do if DoesEntityExist(vehicleInfo.entity) then exports['qb-target']:RemoveTargetEntity(vehicleInfo.entity) DeleteEntity(vehicleInfo.entity) end if vehicleInfo.driver and DoesEntityExist(vehicleInfo.driver) then DeleteEntity(vehicleInfo.driver) end end end -- Stationen neu initialisieren Wait(1000) InitializeTaxiStations() lib.notify({ title = 'Taxi Service', description = 'Alle Taxi-Stationen wurden neu geladen', type = 'success' }) end) -- Zusätzliche Funktionen für bessere Verwaltung function GetNearestTaxiStation() local playerCoords = GetEntityCoords(PlayerPedId()) local nearestStation = nil local nearestDistance = math.huge for stationId, station in pairs(Config.TaxiStations) do local distance = #(playerCoords - station.blipCoords) if distance < nearestDistance then nearestDistance = distance nearestStation = {id = stationId, data = station, distance = distance} end end return nearestStation end -- Command um nächste Taxi-Station zu finden RegisterCommand('nearesttaxi', function() local nearest = GetNearestTaxiStation() if nearest then lib.notify({ title = 'Taxi Service', description = 'Nächste Station: ' .. nearest.data.name .. ' (' .. math.ceil(nearest.distance) .. 'm)', type = 'info' }) -- Waypoint zur nächsten Station setzen SetNewWaypoint(nearest.data.blipCoords.x, nearest.data.blipCoords.y) else lib.notify({ title = 'Taxi Service', description = 'Keine Taxi-Station gefunden', type = 'error' }) end end) -- Erweiterte Ziel-Optionen für Station-Taxis function OpenStationTaxiMenu(stationId, vehicleId, vehicle, driver, pricePerKm) local options = {} -- Bekannte Ziele hinzufügen for _, destination in pairs(Config.KnownDestinations) do local customPrice = math.max(Config.MinFare, math.ceil((CalculateDistanceToCoords(destination.coords) / 1000) * pricePerKm)) table.insert(options, { title = destination.name, description = 'Preis: $' .. customPrice .. ' | Entfernung: ' .. math.ceil(CalculateDistanceToCoords(destination.coords) / 1000 * 100) / 100 .. 'km', icon = 'map-marker', onSelect = function() StartStationTaxiRide(stationId, vehicleId, vehicle, driver, destination.coords, customPrice) end }) end -- Andere Taxi-Stationen als Ziele table.insert(options, { title = '📍 Andere Taxi-Stationen', description = 'Fahre zu einer anderen Taxi-Station', icon = 'taxi', onSelect = function() OpenStationSelectionMenu(stationId, vehicleId, vehicle, driver, pricePerKm) 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 * pricePerKm)) StartStationTaxiRide(stationId, vehicleId, vehicle, driver, coords, price) else lib.notify({ title = 'Taxi Service', description = 'Du hast keinen Waypoint gesetzt', type = 'error' }) OpenStationTaxiMenu(stationId, vehicleId, vehicle, driver, pricePerKm) end end }) -- Aussteigen Option table.insert(options, { title = 'Aussteigen', description = 'Das Taxi verlassen', icon = 'door-open', onSelect = function() ExitStationTaxi(stationId, vehicleId, vehicle, driver) end }) lib.registerContext({ id = 'station_taxi_menu', title = 'Taxi - Ziel wählen (' .. Config.TaxiStations[stationId].name .. ')', options = options }) lib.showContext('station_taxi_menu') end function OpenStationSelectionMenu(stationId, vehicleId, vehicle, driver, pricePerKm) local options = {} for otherStationId, station in pairs(Config.TaxiStations) do if otherStationId ~= stationId then local distance = CalculateDistanceToCoords(station.blipCoords) / 1000 local price = math.max(Config.MinFare, math.ceil(distance * pricePerKm)) table.insert(options, { title = station.name, description = 'Preis: $' .. price .. ' | Entfernung: ' .. math.ceil(distance * 100) / 100 .. 'km', icon = 'building', onSelect = function() StartStationTaxiRide(stationId, vehicleId, vehicle, driver, station.blipCoords, price) end }) end end -- Zurück Option table.insert(options, { title = '← Zurück', description = 'Zurück zum Hauptmenü', icon = 'arrow-left', onSelect = function() OpenStationTaxiMenu(stationId, vehicleId, vehicle, driver, pricePerKm) end }) lib.registerContext({ id = 'station_selection_menu', title = 'Taxi-Stationen', options = options }) lib.showContext('station_selection_menu') end -- Verbesserte Fahrzeug-Respawn-Logik function RespawnStationVehicle(stationId, vehicleId) if not stationVehicles[stationId] or not stationVehicles[stationId][vehicleId] then return end local vehicleData = stationVehicles[stationId][vehicleId].data -- Prüfen ob Position frei ist local coords = vehicleData.coords local existingVehicles = GetGamePool('CVehicle') local positionBlocked = false for _, veh in pairs(existingVehicles) do local vehCoords = GetEntityCoords(veh) if #(vector3(coords.x, coords.y, coords.z) - vehCoords) < 3.0 then positionBlocked = true break end end if not positionBlocked then SpawnStationVehicle(stationId, vehicleId, vehicleData) else -- Erneut versuchen nach 30 Sekunden SetTimeout(30000, function() RespawnStationVehicle(stationId, vehicleId) end) end end