local QBCore = exports['qb-core']:GetCoreObject() local lib = exports.ox_lib local PlayerData = {} local nearbyTrains = {} local currentTrain = nil local isRiding = false local cinemaCam = nil RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() PlayerData = QBCore.Functions.GetPlayerData() CreateStationBlips() end) -- Bahnhof Blips erstellen function CreateStationBlips() for _, station in pairs(Config.TrainStations) do local blip = AddBlipForCoord(station.coords.x, station.coords.y, station.coords.z) SetBlipSprite(blip, station.blip.sprite) SetBlipDisplay(blip, 4) SetBlipScale(blip, station.blip.scale) SetBlipColour(blip, station.blip.color) BeginTextCommandSetBlipName("STRING") AddTextComponentString("🚂 " .. station.name) EndTextCommandSetBlipName(blip) end end -- Zug spawnen function SpawnTrainAtLocation(station) local model = GetHashKey(Config.TrainCars.main) RequestModel(model) while not HasModelLoaded(model) do Wait(500) end local train = CreateMissionTrain(24, station.coords.x, station.coords.y, station.coords.z, true) if DoesEntityExist(train) then SetEntityHeading(train, station.coords.w) SetTrainSpeed(train, 0.0) SetTrainCruiseSpeed(train, 0.0) -- Waggons hinzufügen Wait(1000) for _, carModel in pairs(Config.TrainCars.cars) do local carHash = GetHashKey(carModel) RequestModel(carHash) while not HasModelLoaded(carHash) do Wait(500) end CreateMissionTrainCar(train, carHash, false, false, false) end if Config.Debug then print("Zug gespawnt bei: " .. station.name) end return train end return nil end -- Züge in der Nähe finden function FindNearbyTrains() local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) local trains = {} local vehicles = GetGamePool('CVehicle') for _, vehicle in pairs(vehicles) do if DoesEntityExist(vehicle) then local model = GetEntityModel(vehicle) if IsThisModelATrain(model) then local trainCoords = GetEntityCoords(vehicle) local distance = #(playerCoords - trainCoords) if distance <= Config.InteractionDistance then table.insert(trains, { entity = vehicle, coords = trainCoords, distance = distance }) end end end end return trains end -- ox_lib Zielmenü öffnen function OpenDestinationMenu(train) local playerCoords = GetEntityCoords(PlayerPedId()) local currentStation = GetNearestStation(playerCoords) local options = {} for _, station in pairs(Config.TrainStations) do if station.name ~= currentStation then local icon = Config.Menu.stationIcons.city -- Standard Icon -- Icon basierend auf Station wählen if string.find(station.name:lower(), "depot") then icon = Config.Menu.stationIcons.depot elseif string.find(station.name:lower(), "island") or string.find(station.name:lower(), "terminal") then icon = Config.Menu.stationIcons.port elseif string.find(station.name:lower(), "industrial") then icon = Config.Menu.stationIcons.industrial elseif string.find(station.name:lower(), "bay") then icon = Config.Menu.stationIcons.rural end table.insert(options, { title = icon .. " " .. station.name, description = station.description .. " - " .. Config.Menu.texts.price .. ": $" .. station.price, icon = 'train', onSelect = function() SelectDestination(train, station) end, metadata = { {label = Config.Menu.texts.price, value = "$" .. station.price}, {label = "Entfernung", value = math.floor(#(playerCoords - vector3(station.coords.x, station.coords.y, station.coords.z))) .. "m"} } }) end end table.insert(options, { title = "❌ " .. Config.Menu.texts.cancel, description = "Menü schließen", icon = 'xmark' }) lib:registerContext({ id = 'train_destination_menu', title = Config.Menu.title, options = options, position = Config.Menu.position }) lib:showContext('train_destination_menu') end -- Ziel auswählen function SelectDestination(train, destination) if not DoesEntityExist(train) then lib:notify({ title = 'Fehler', description = Config.Menu.texts.trainNotAvailable, type = Config.Notifications.types.error, duration = Config.Notifications.duration.short }) return end -- Geld prüfen QBCore.Functions.TriggerCallback('train:server:canAfford', function(canAfford) if canAfford then StartTrainJourney(train, destination) else lib:notify({ title = 'Nicht genug Geld', description = Config.Menu.texts.notEnoughMoney .. " ($" .. destination.price .. ")", type = Config.Notifications.types.error, duration = Config.Notifications.duration.medium }) end end, destination.price) end -- Nächste Station finden function GetNearestStation(coords) local nearestStation = nil local nearestDistance = math.huge for _, station in pairs(Config.TrainStations) do local distance = #(coords - vector3(station.coords.x, station.coords.y, station.coords.z)) if distance < nearestDistance then nearestDistance = distance nearestStation = station.name end end return nearestStation end -- Zugfahrt starten function StartTrainJourney(train, destination) local playerPed = PlayerPedId() currentTrain = train isRiding = true -- Spieler in Zug setzen SetPedIntoVehicle(playerPed, train, 1) -- Benachrichtigung lib:notify({ title = '🚂 ' .. Config.Menu.texts.journeyStarted, description = "Fahrt nach " .. destination.name, type = Config.Notifications.types.success, duration = Config.Notifications.duration.medium }) -- Cinema Kamera starten if Config.CinemaCamera.enabled then StartCinemaCamera(train) end -- Automatische Fahrt CreateThread(function() Wait(3000) lib:notify({ title = '🚂 ' .. Config.Menu.texts.trainDeparting, description = "Nächster Halt: " .. destination.name, type = Config.Notifications.types.info, duration = Config.Notifications.duration.short }) DriveTrainToDestination(train, destination) end) -- Journey loggen if Config.DebugOptions.logJourneys then TriggerServerEvent('train:server:logJourney', GetNearestStation(GetEntityCoords(playerPed)), destination.name, destination.price) end end -- Cinema Kamera function StartCinemaCamera(train) if cinemaCam then DestroyCam(cinemaCam, false) end CreateThread(function() while isRiding and DoesEntityExist(train) do for _, camPos in pairs(Config.CinemaCamera.positions) do if not isRiding then break end if cinemaCam then DestroyCam(cinemaCam, false) end local trainCoords = GetEntityCoords(train) local trainHeading = GetEntityHeading(train) local camCoords = trainCoords + camPos.offset cinemaCam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true) SetCamCoord(cinemaCam, camCoords.x, camCoords.y, camCoords.z) SetCamRot(cinemaCam, camPos.rotation.x, camPos.rotation.y, camPos.rotation.z + trainHeading, 2) SetCamActive(cinemaCam, true) RenderScriptCams(true, true, 1000, true, true) local holdTime = math.random(Config.CinemaCamera.switchInterval.min, Config.CinemaCamera.switchInterval.max) Wait(holdTime) end end end) end -- Zug zur Destination fahren function DriveTrainToDestination(train, destination) CreateThread(function() local targetCoords = vector3(destination.coords.x, destination.coords.y, destination.coords.z) local arrived = false -- Beschleunigung for speed = 0, Config.TrainSpeed.max, Config.TrainSpeed.acceleration do if not DoesEntityExist(train) then return end SetTrainSpeed(train, speed) SetTrainCruiseSpeed(train, speed) Wait(500) end -- Fahrt while not arrived and DoesEntityExist(train) and isRiding do local trainCoords = GetEntityCoords(train) local distance = #(trainCoords - targetCoords) if distance < 200 then local newSpeed = math.max(Config.TrainSpeed.min, distance / 20) SetTrainSpeed(train, newSpeed) SetTrainCruiseSpeed(train, newSpeed) end if distance < 50 then arrived = true SetTrainSpeed(train, 0) SetTrainCruiseSpeed(train, 0) Wait(2000) lib:notify({ title = '🚂 ' .. Config.Menu.texts.arrived, description = destination.name, type = Config.Notifications.types.success, duration = Config.Notifications.duration.medium }) lib:notify({ title = Config.Menu.texts.thankYou, description = "Gute Weiterreise!", type = Config.Notifications.types.info, duration = Config.Notifications.duration.short }) EndTrainJourney() end Wait(1000) end end) end -- Zugfahrt beenden function EndTrainJourney() isRiding = false if cinemaCam then RenderScriptCams(false, true, 1000, true, true) DestroyCam(cinemaCam, false) cinemaCam = nil end if currentTrain and DoesEntityExist(currentTrain) then local playerPed = PlayerPedId() TaskLeaveVehicle(playerPed, currentTrain, 0) end currentTrain = nil Wait(3000) lib:notify({ title = Config.Menu.texts.canExit, type = Config.Notifications.types.info, duration = Config.Notifications.duration.short }) end -- 3D Text zeichnen function DrawText3D(x, y, z, text) local onScreen, _x, _y = World3dToScreen2d(x, y, z) if onScreen then SetTextScale(Config.DrawText.scale, Config.DrawText.scale) SetTextFont(Config.DrawText.font) SetTextProportional(1) SetTextColour(Config.DrawText.color.r, Config.DrawText.color.g, Config.DrawText.color.b, Config.DrawText.color.a) SetTextEntry("STRING") SetTextCentre(1) AddTextComponentString(text) DrawText(_x, _y) local factor = (string.len(text)) / 370 DrawRect(_x, _y + 0.0125, 0.015 + factor, 0.03, Config.DrawText.backgroundColor.r, Config.DrawText.backgroundColor.g, Config.DrawText.backgroundColor.b, Config.DrawText.backgroundColor.a) end end -- Haupt-Loop CreateThread(function() while true do local sleep = 1000 local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) if not isRiding and not IsPedInAnyVehicle(playerPed, false) then nearbyTrains = FindNearbyTrains() for _, train in pairs(nearbyTrains) do if train.distance <= 5.0 then sleep = 0 DrawText3D(train.coords.x, train.coords.y, train.coords.z + 2.0, Config.DrawText.interactText) if IsControlJustPressed(0, 38) then -- E OpenDestinationMenu(train.entity) end end end end if isRiding then sleep = 0 if IsControlJustPressed(0, 23) then -- F lib:notify({ title = Config.Menu.texts.emergencyExit, type = Config.Notifications.types.warning, duration = Config.Notifications.duration.short }) EndTrainJourney() end end Wait(sleep) end end) -- Commands RegisterCommand('spawntrain', function(source, args) local stationId = args[1] or Config.TrainStations[1].id local station = nil for _, s in pairs(Config.TrainStations) do if s.id == stationId then station = s break end end if station then SpawnTrainAtLocation(station) lib:notify({ title = 'Zug gespawnt', description = station.name, type = Config.Notifications.types.success }) end end) -- Auto-Spawn if Config.AutoSpawn.enabled then CreateThread(function() Wait(Config.AutoSpawn.delay) for i = 1, Config.AutoSpawn.maxTrains do local randomStation = Config.TrainStations[math.random(1, #Config.TrainStations)] SpawnTrainAtLocation(randomStation) Wait(Config.AutoSpawn.spawnInterval) end end) end -- Cleanup AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then if isRiding then EndTrainJourney() end end end)