local QBCore = exports['qb-core']:GetCoreObject() local PlayerData = {} local currentBooth = nil local isPlaying = false local currentVolume = Config.DefaultVolume local currentSong = nil local activeBooths = {} local isUIOpen = false local isDJBooth = false local nearestBooth = nil -- Events RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() PlayerData = QBCore.Functions.GetPlayerData() -- Frage Server nach aktiven DJ-Booths TriggerServerEvent('dj:server:requestActiveDJs') end) RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo) PlayerData.job = JobInfo end) -- Key Mapping RegisterKeyMapping('opendj', 'Open DJ Menu', 'keyboard', Config.OpenMenuKey) RegisterCommand('opendj', function() if not CanUseDJScript() then lib.notify({ title = 'DJ System', description = 'Du hast keine Berechtigung das DJ System zu nutzen!', type = 'error' }) return end if not isDJBooth then lib.notify({ title = 'DJ System', description = 'Du musst an einem DJ Pult stehen!', type = 'error' }) return end OpenDJInterface() end) -- Musik abspielen (von Server empfangen) RegisterNetEvent('dj:client:playMusic', function(data) -- Speichere Booth-Informationen activeBooths[data.booth.name] = { url = data.url, title = data.title, volume = data.volume, coords = data.booth.coords, range = data.range } -- Prüfe ob Spieler in Reichweite ist local playerCoords = GetEntityCoords(PlayerPedId()) local distance = #(playerCoords - data.booth.coords) if distance <= data.range then -- Berechne Lautstärke basierend auf Distanz local volumeMultiplier = 1.0 - (distance / data.range) local adjustedVolume = math.floor(data.volume * volumeMultiplier) -- Spiele Musik ab SendNUIMessage({ type = 'playMusic', url = data.url, volume = adjustedVolume, title = data.title }) -- Setze lokale Variablen currentBooth = { name = data.booth.name, coords = data.booth.coords } isPlaying = true currentSong = { title = data.title, url = data.url } -- Zeige Benachrichtigung lib.notify({ title = 'DJ System', description = 'Spielt: ' .. data.title, type = 'info' }) end end) -- Musik stoppen (von Server empfangen) RegisterNetEvent('dj:client:stopMusic', function(boothName) -- Entferne Booth-Informationen if activeBooths[boothName] then activeBooths[boothName] = nil end -- Wenn der Spieler diese Musik hört, stoppe sie if currentBooth and currentBooth.name == boothName then SendNUIMessage({ type = 'stopMusic' }) currentBooth = nil isPlaying = false currentSong = nil lib.notify({ title = 'DJ System', description = 'Musik gestoppt', type = 'info' }) end end) -- Lautstärke ändern (von Server empfangen) RegisterNetEvent('dj:client:setVolume', function(data) -- Aktualisiere Booth-Informationen if activeBooths[data.booth.name] then activeBooths[data.booth.name].volume = data.volume activeBooths[data.booth.name].range = data.range end -- Wenn der Spieler diese Musik hört, ändere die Lautstärke if currentBooth and currentBooth.name == data.booth.name then -- Berechne Lautstärke basierend auf Distanz local playerCoords = GetEntityCoords(PlayerPedId()) local distance = #(playerCoords - data.booth.coords) local volumeMultiplier = 1.0 - (distance / data.range) local adjustedVolume = math.floor(data.volume * volumeMultiplier) SendNUIMessage({ type = 'setVolume', volume = adjustedVolume }) currentVolume = data.volume end end) -- Empfange aktive DJs RegisterNetEvent('dj:client:receiveActiveDJs', function(booths) activeBooths = booths -- Prüfe ob Spieler in Reichweite eines aktiven DJ-Booths ist local playerCoords = GetEntityCoords(PlayerPedId()) for boothName, boothData in pairs(activeBooths) do local distance = #(playerCoords - boothData.coords) if distance <= boothData.range then -- Berechne Lautstärke basierend auf Distanz local volumeMultiplier = 1.0 - (distance / boothData.range) local adjustedVolume = math.floor(boothData.volume * volumeMultiplier) -- Spiele Musik ab SendNUIMessage({ type = 'playMusic', url = boothData.url, volume = adjustedVolume, title = boothData.title }) currentBooth = { name = boothName, coords = boothData.coords } isPlaying = true currentSong = { title = boothData.title, url = boothData.url } lib.notify({ title = 'DJ System', description = 'Spielt: ' .. boothData.title, type = 'info' }) break end end end) RegisterNUICallback('djInterfaceClosed', function(data, cb) SetNuiFocus(false, false) isUIOpen = false -- Kurze Verzögerung, um sicherzustellen, dass alle Animationen abgeschlossen sind Wait(100) -- Aktiviere alle Controls wieder EnableAllControlActions(0) -- Musik NICHT stoppen, wenn stopMusic nicht explizit true ist if data.stopMusic then -- Stoppe Musik nur wenn explizit angefordert if currentBooth then TriggerServerEvent('dj:server:stopMusic', currentBooth.name) end end cb('ok') end) RegisterNUICallback('deckStateChanged', function(data, cb) if Config.Debug then print(string.format('[DJ System] Deck %s %s: %s', data.deck, data.isPlaying and 'playing' or 'stopped', data.track and data.track.title or 'No track' )) end -- Wenn Deck A oder B abspielt, sende an Server if data.isPlaying and data.track then PlayMusicAsDJ(data.track.title, data.track.url, currentVolume) end cb('ok') end) RegisterNUICallback('volumeChanged', function(data, cb) if data.volume then SetVolumeAsDJ(data.volume) end cb('ok') end) RegisterNUICallback('stopMusic', function(data, cb) StopMusicAsDJ() cb('ok') end) RegisterNUICallback('audioError', function(data, cb) lib.notify({ title = 'DJ System', description = 'Audio Fehler: ' .. (data.error or 'Unbekannter Fehler'), type = 'error' }) cb('ok') end) RegisterNUICallback('songEnded', function(data, cb) -- Wenn ein Song endet, kann hier Playlist-Logik implementiert werden if Config.Debug then print('[DJ System] Song ended') end cb('ok') end) RegisterNUICallback('loadTrack', function(data, cb) if data.deck and data.track then -- Lade Track in Deck if data.deck == "A" or data.deck == "B" then -- Hier könntest du zusätzliche Logik hinzufügen cb({success = true}) else cb({success = false, error = "Ungültiges Deck"}) end else cb({success = false, error = "Fehlende Daten"}) end end) RegisterNUICallback('playTrack', function(data, cb) if data.deck and data.track then -- Spiele Track ab PlayMusicAsDJ(data.track.title, data.track.url, currentVolume) cb({success = true}) else cb({success = false, error = "Fehlende Daten"}) end end) RegisterNUICallback('getPlaylists', function(data, cb) -- Hole Playlists vom Server QBCore.Functions.TriggerCallback('dj:server:getPlaylists', function(playlists) cb({success = true, playlists = playlists}) end) end) RegisterNUICallback('createPlaylist', function(data, cb) if data.name then -- Erstelle Playlist TriggerServerEvent('dj:server:createPlaylist', data.name, data.description, data.isPublic) cb({success = true}) else cb({success = false, error = "Fehlender Name"}) end end) RegisterNUICallback('addToPlaylist', function(data, cb) if data.playlistId and data.track then -- Füge Track zu Playlist hinzu TriggerServerEvent('dj:server:addSongToPlaylist', data.playlistId, data.track) cb({success = true}) else cb({success = false, error = "Fehlende Daten"}) end end) RegisterNUICallback('getSessionHistory', function(data, cb) -- Hole Session-Historie vom Server QBCore.Functions.TriggerCallback('dj:server:getSessionHistory', function(history) cb({success = true, history = history}) end, data.limit) end) -- Functions function CanUseDJScript() if not Config.UseJobRestriction then return true end if not PlayerData.job then return false end for _, job in pairs(Config.AllowedJobs) do if PlayerData.job.name == job then return true end end return false end function OpenDJInterface() if not isDJBooth then lib.notify({ title = 'DJ System', description = 'Du musst an einem DJ Pult stehen', type = 'error' }) return end SetNuiFocus(true, true) SendNUIMessage({ type = 'showDJInterface' }) isUIOpen = true -- Disable controls while UI is open CreateThread(function() while isUIOpen do DisableAllControlActions(0) EnableControlAction(0, 1, true) -- Mouse look EnableControlAction(0, 2, true) -- Mouse look EnableControlAction(0, 3, true) -- Mouse movement EnableControlAction(0, 4, true) -- Mouse movement EnableControlAction(0, 5, true) -- Mouse wheel EnableControlAction(0, 6, true) -- Mouse wheel Wait(0) end end) end function PlayMusicAsDJ(title, url, volume) if not nearestBooth then lib.notify({ title = 'DJ System', description = 'Du musst an einem DJ-Pult stehen!', type = 'error' }) return end -- Bereinige YouTube URL von Playlist-Parametern local cleanUrl = url if string.find(url, "youtube") then cleanUrl = string.gsub(url, "&list=.-$", "") cleanUrl = string.gsub(cleanUrl, "&start_radio=.-$", "") cleanUrl = string.gsub(cleanUrl, "&index=.-$", "") end -- Sende an Server zur Synchronisation TriggerServerEvent('dj:server:playMusic', { title = title, url = cleanUrl, volume = volume or Config.DefaultVolume, booth = { name = nearestBooth, coords = Config.DJBooths[nearestBooth].coords }, range = CalculateRange(volume or Config.DefaultVolume) }) -- Lokale Variablen aktualisieren isPlaying = true currentVolume = volume or Config.DefaultVolume currentSong = { title = title, url = cleanUrl } currentBooth = { name = nearestBooth, coords = Config.DJBooths[nearestBooth].coords } end function StopMusicAsDJ() if not nearestBooth then lib.notify({ title = 'DJ System', description = 'Du musst an einem DJ-Pult stehen!', type = 'error' }) return end -- Sende an Server zur Synchronisation TriggerServerEvent('dj:server:stopMusic', nearestBooth) -- Lokale Variablen aktualisieren isPlaying = false currentSong = nil currentBooth = nil end function SetVolumeAsDJ(volume) if not nearestBooth then lib.notify({ title = 'DJ System', description = 'Du musst an einem DJ-Pult stehen!', type = 'error' }) return end -- Sende an Server zur Synchronisation TriggerServerEvent('dj:server:setVolume', { volume = volume, booth = { name = nearestBooth, coords = Config.DJBooths[nearestBooth].coords }, range = CalculateRange(volume) }) -- Lokale Variablen aktualisieren currentVolume = volume end function CalculateRange(volume) if not nearestBooth or not Config.DJBooths[nearestBooth] then return 30.0 end local booth = Config.DJBooths[nearestBooth] local baseRange = booth.range or 30.0 local maxRange = booth.maxRange or 50.0 local volumePercent = volume / 100 return baseRange + ((maxRange - baseRange) * volumePercent) end -- Regelmäßige Prüfung der Distanz zu DJ-Booths und aktiven Musiken CreateThread(function() while true do Wait(Config.DistanceVolume and Config.DistanceVolume.updateInterval or 1000) local playerCoords = GetEntityCoords(PlayerPedId()) local foundBooth = false nearestBooth = nil local nearestDistance = 999999.9 -- Prüfe Nähe zu DJ-Booths for boothName, booth in pairs(Config.DJBooths) do local distance = #(playerCoords - booth.coords) -- Finde das nächste DJ-Booth if distance < nearestDistance then nearestDistance = distance nearestBooth = boothName end -- Prüfe ob Spieler an einem DJ-Booth steht if distance <= 2.0 then foundBooth = true break end end isDJBooth = foundBooth -- Prüfe Distanz zu aktiven Musiken if not isPlaying and next(activeBooths) then -- Spieler hört keine Musik, prüfe ob er in Reichweite eines aktiven DJ-Booths ist for boothName, boothData in pairs(activeBooths) do local distance = #(playerCoords - boothData.coords) if distance <= boothData.range then -- Berechne Lautstärke basierend auf Distanz local volumeMultiplier = 1.0 - (distance / boothData.range) local adjustedVolume = math.floor(boothData.volume * volumeMultiplier) -- Spiele Musik ab SendNUIMessage({ type = 'playMusic', url = boothData.url, volume = adjustedVolume, title = boothData.title }) currentBooth = { name = boothName, coords = boothData.coords } isPlaying = true currentSong = { title = boothData.title, url = boothData.url } lib.notify({ title = 'DJ System', description = 'Spielt: ' .. boothData.title, type = 'info' }) break end end elseif isPlaying and currentBooth then -- Spieler hört Musik, prüfe ob er noch in Reichweite ist local boothData = activeBooths[currentBooth.name] if boothData then local distance = #(playerCoords - boothData.coords) if distance > boothData.range then -- Spieler hat Reichweite verlassen, stoppe Musik SendNUIMessage({ type = 'stopMusic' }) currentBooth = nil isPlaying = false currentSong = nil lib.notify({ title = 'DJ System', description = 'Musik außer Reichweite', type = 'info' }) else -- Spieler ist noch in Reichweite, passe Lautstärke an local volumeMultiplier = 1.0 - (distance / boothData.range) local adjustedVolume = math.floor(boothData.volume * volumeMultiplier) SendNUIMessage({ type = 'setVolume', volume = adjustedVolume }) end else -- Booth existiert nicht mehr, stoppe Musik SendNUIMessage({ type = 'stopMusic' }) currentBooth = nil isPlaying = false currentSong = nil end end end end) -- DJ Booth Blips und Interaktionen CreateThread(function() -- Erstelle Blips für DJ-Booths for boothName, booth in pairs(Config.DJBooths) do local blip = AddBlipForCoord(booth.coords) SetBlipSprite(blip, 614) SetBlipDisplay(blip, 4) SetBlipScale(blip, 0.7) SetBlipColour(blip, 47) SetBlipAsShortRange(blip, true) BeginTextCommandSetBlipName("STRING") AddTextComponentString("DJ Booth - " .. boothName) EndTextCommandSetBlipName(blip) end -- Erstelle Interaktionspunkte für DJ-Booths for boothName, booth in pairs(Config.DJBooths) do exports['qb-target']:AddBoxZone("dj_booth_" .. boothName, booth.coords, 1.5, 1.5, { name = "dj_booth_" .. boothName, heading = 0, debugPoly = Config.Debug, minZ = booth.coords.z - 1.0, maxZ = booth.coords.z + 1.0 }, { options = { { type = "client", event = "dj:client:openDJMenu", icon = "fas fa-music", label = "DJ System öffnen", boothName = boothName, job = Config.UseJobRestriction and Config.AllowedJobs or nil } }, distance = 2.0 }) end end) -- Event für Target-Integration RegisterNetEvent('dj:client:openDJMenu', function(data) nearestBooth = data.boothName isDJBooth = true OpenDJInterface() end) -- Debug if Config.Debug then RegisterCommand('djdebug', function() print('--- DJ SYSTEM DEBUG ---') print('isPlaying:', isPlaying) print('isDJBooth:', isDJBooth) print('nearestBooth:', nearestBooth) print('currentBooth:', currentBooth and currentBooth.name or 'nil') print('currentVolume:', currentVolume) print('currentSong:', currentSong and currentSong.title or 'nil') print('activeBooths:') for boothName, boothData in pairs(activeBooths) do print(' -', boothName, boothData.title, boothData.volume) end end) end function OpenDJInterface() if not isDJBooth then lib.notify({ title = 'DJ System', description = 'Du musst an einem DJ Pult stehen', type = 'error' }) return end SetNuiFocus(true, true) SendNUIMessage({ type = 'showDJInterface', center = true, -- Hinzugefügt: Zentriere das Interface reset = true -- Hinzugefügt: Setze Position zurück }) isUIOpen = true -- Disable controls while UI is open CreateThread(function() while isUIOpen do DisableAllControlActions(0) EnableControlAction(0, 1, true) -- Mouse look EnableControlAction(0, 2, true) -- Mouse look EnableControlAction(0, 3, true) -- Mouse movement EnableControlAction(0, 4, true) -- Mouse movement EnableControlAction(0, 5, true) -- Mouse wheel EnableControlAction(0, 6, true) -- Mouse wheel Wait(0) end end) end -- Callback-Handler für NUI RegisterNUICallback('callback', function(data, cb) if data.callbackId then SendNUIMessage({ type = 'callbackResponse', callbackId = data.callbackId, response = data.response or {} }) end cb('ok') end) -- Verbesserte Error-Handling RegisterNUICallback('error', function(data, cb) print("DJ System Error: " .. (data.error or "Unknown error")) cb('ok') -- Benachrichtige Spieler lib.notify({ title = 'DJ System Error', description = data.error or "Ein unbekannter Fehler ist aufgetreten", type = 'error' }) end) -- Debug-Funktion für NUI-Status function DebugNUIStatus() local nuiFocus = {GetNuiFocus()} print("NUI Focus:", json.encode(nuiFocus)) print("isUIOpen:", isUIOpen) print("isDJBooth:", isDJBooth) print("nearestBooth:", nearestBooth) end -- Debug-Befehl RegisterCommand('djdebug', function() DebugNUIStatus() end, false)