forked from Simnation/Main
447 lines
14 KiB
Lua
447 lines
14 KiB
Lua
![]() |
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)
|