local QBCore = exports['qb-core']:GetCoreObject() local inTDM = false local currentTeam = nil local currentGameId = nil local currentField = nil local currentLobbyField = nil local tdmBlips = {} local teamZoneBlips = {} local isHit = false local activeGames = {} local spawnedNPCs = {} -- Spieler Statistiken local playerStats = { hits = 0, deaths = 0, gamesPlayed = 0 } -- Debug-Funktion für Konsole local function debugPrint(message) print("^2[TDM DEBUG]^7 " .. message) end -- Events RegisterNetEvent('tdm:updateGamesList', function(games) activeGames = games end) RegisterNetEvent('tdm:joinGame', function(gameId, team, fieldId) currentGameId = gameId currentTeam = team currentField = fieldId inTDM = true isHit = false -- Stats zurücksetzen playerStats.hits = 0 playerStats.deaths = 0 playerStats.gamesPlayed = playerStats.gamesPlayed + 1 local fieldConfig = Config.gameFields[fieldId] -- Teleport zu Team Spawn local spawnPoints = fieldConfig.teamSpawns[team] local randomSpawn = spawnPoints[math.random(#spawnPoints)] SetEntityCoords(PlayerPedId(), randomSpawn.x, randomSpawn.y, randomSpawn.z) -- Team Maske setzen setTeamMask(team) -- Team Zone Blip erstellen createTeamZoneBlip(team, fieldConfig) lib.notify({ title = 'TeamDeathmatch', description = 'Du bist dem Spiel beigetreten! Team: ' .. team, type = 'success' }) debugPrint("Spiel beigetreten: " .. gameId .. ", Team: " .. team .. ", Feld: " .. fieldId) end) RegisterNetEvent('tdm:leaveGame', function() inTDM = false local previousField = currentField currentTeam = nil currentGameId = nil currentField = nil isHit = false -- Sichere Rückkehr zur Lobby local lobbyPos = nil -- Versuche zuerst die vorherige Feld-Lobby if previousField and Config.gameFields[previousField] and Config.gameFields[previousField].lobby then lobbyPos = Config.gameFields[previousField].lobby.pos -- Dann die aktuelle Lobby-Feld elseif currentLobbyField and Config.gameFields[currentLobbyField] and Config.gameFields[currentLobbyField].lobby then lobbyPos = Config.gameFields[currentLobbyField].lobby.pos -- Fallback zur ersten verfügbaren Lobby else for fieldId, fieldData in pairs(Config.gameFields) do if fieldData.lobby and fieldData.lobby.pos then lobbyPos = fieldData.lobby.pos break end end end -- Teleport zur Lobby (mit Fallback-Position) if lobbyPos then SetEntityCoords(PlayerPedId(), lobbyPos.x, lobbyPos.y, lobbyPos.z) else -- Notfall-Fallback Position (anpassen an deine Map) SetEntityCoords(PlayerPedId(), -1042.4, -2745.8, 21.4) print("^3[TDM WARNING]^7 Keine Lobby gefunden, Fallback-Position verwendet!") end -- Maske entfernen SetPedComponentVariation(PlayerPedId(), 1, 0, 0, 0) -- Zone Blips entfernen removeTeamZoneBlips() lib.hideTextUI() lib.notify({ title = 'TeamDeathmatch', description = 'Du hast das Spiel verlassen!', type = 'error' }) debugPrint("Spiel verlassen") end) RegisterNetEvent('tdm:joinRequest', function(gameId, playerName, playerId) local alert = lib.alertDialog({ header = 'Join Anfrage', content = playerName .. ' möchte deinem Spiel beitreten.\n\nErlauben?', centered = true, cancel = true, labels = { cancel = 'Ablehnen', confirm = 'Erlauben' } }) if alert == 'confirm' then TriggerServerEvent('tdm:approveJoinRequest', gameId, playerId, true) else TriggerServerEvent('tdm:approveJoinRequest', gameId, playerId, false) end end) RegisterNetEvent('tdm:joinRequestResult', function(approved, gameName) if approved then lib.notify({ title = 'TeamDeathmatch', description = 'Deine Anfrage wurde angenommen!', type = 'success' }) else lib.notify({ title = 'TeamDeathmatch', description = 'Deine Anfrage für "' .. gameName .. '" wurde abgelehnt!', type = 'error' }) end end) RegisterNetEvent('tdm:playerHit', function() if not inTDM then print("^3[TDM WARNING]^7 Hit-Event empfangen, aber nicht im TDM!") return end if isHit then print("^3[TDM WARNING]^7 Hit-Event empfangen, aber bereits getroffen!") return end isHit = true local ped = PlayerPedId() -- Benachrichtigung lib.notify({ title = 'TeamDeathmatch', description = 'Du wurdest getroffen! Respawn in 3 Sekunden...', type = 'error' }) -- Vereinfachter Respawn ohne Animation SetTimeout(3000, function() if not inTDM then return end -- Debug-Nachricht debugPrint("Respawn wird ausgeführt...") -- Respawn zum Team Spawn local fieldConfig = Config.gameFields[currentField] if not fieldConfig then print("^1[TDM ERROR]^7 Feldkonfiguration nicht gefunden!") return end local spawnPoints = fieldConfig.teamSpawns[currentTeam] if not spawnPoints or #spawnPoints == 0 then print("^1[TDM ERROR]^7 Keine Spawn-Punkte gefunden!") return end local randomSpawn = spawnPoints[math.random(#spawnPoints)] -- Teleport zum Spawn mit Fade DoScreenFadeOut(500) Wait(600) -- Alle Animationen stoppen ClearPedTasksImmediately(ped) -- Teleport SetEntityCoords(ped, randomSpawn.x, randomSpawn.y, randomSpawn.z) -- Spieler ist wieder aktiv isHit = false Wait(100) DoScreenFadeIn(500) lib.notify({ title = 'TeamDeathmatch', description = 'Du bist wieder im Spiel!', type = 'success' }) end) end) RegisterNetEvent('tdm:updateScore', function(team1Score, team2Score, gameStats) -- Debug-Ausgabe debugPrint("Score Update empfangen: Team1=" .. team1Score .. ", Team2=" .. team2Score) if gameStats then debugPrint("GameStats: Hits=" .. (gameStats.hits or "nil") .. ", Deaths=" .. (gameStats.deaths or "nil")) end -- Verwende gameStats falls verfügbar, sonst lokale Stats local myHits = gameStats and gameStats.hits or playerStats.hits local myDeaths = gameStats and gameStats.deaths or playerStats.deaths local displayText = string.format( '[Team 1: %d] VS [Team 2: %d] | Deine Treffer: %d | Tode: %d', team1Score, team2Score, myHits, myDeaths ) lib.showTextUI(displayText, { position = "top-center", icon = 'crosshairs' }) end) RegisterNetEvent('tdm:hitRegistered', function() playerStats.hits = playerStats.hits + 1 lib.notify({ title = 'Treffer!', description = 'Du hast einen Gegner getroffen! (+1 Punkt)', type = 'success', duration = 2000 }) showHitMarker() -- Score sofort aktualisieren if currentGameId then TriggerServerEvent('tdm:requestScoreUpdate', currentGameId) end debugPrint("Treffer registriert! Neue Hits: " .. playerStats.hits) end) RegisterNetEvent('tdm:deathRegistered', function() playerStats.deaths = playerStats.deaths + 1 -- Score sofort aktualisieren if currentGameId then TriggerServerEvent('tdm:requestScoreUpdate', currentGameId) end debugPrint("Tod registriert! Neue Deaths: " .. playerStats.deaths) end) RegisterNetEvent('tdm:gameEnded', function(winnerTeam, team1Score, team2Score) lib.hideTextUI() local wonGame = (currentTeam == 'team1' and winnerTeam == 'team1') or (currentTeam == 'team2' and winnerTeam == 'team2') local resultText = wonGame and '🏆 GEWONNEN!' or '💀 VERLOREN!' local statsText = string.format( '%s\n\n' .. 'Endergebnis:\n' .. 'Team 1: %d Punkte\n' .. 'Team 2: %d Punkte\n\n' .. 'Deine Statistiken:\n' .. '🎯 Treffer: %d\n' .. '💀 Tode: %d\n' .. '📊 K/D: %.2f', resultText, team1Score, team2Score, playerStats.hits, playerStats.deaths, playerStats.deaths > 0 and (playerStats.hits / playerStats.deaths) or playerStats.hits ) local alert = lib.alertDialog({ header = 'Spiel beendet!', content = statsText, centered = true, cancel = false }) Wait(5000) TriggerServerEvent('tdm:leaveGame') debugPrint("Spiel beendet! Gewinner: " .. (winnerTeam or "Unentschieden")) end) -- Funktionen function setTeamMask(team) local ped = PlayerPedId() local maskData = Config.teamMasks[team] if maskData then -- Geschlecht des Spielers ermitteln local playerGender = GetEntityModel(ped) == GetHashKey("mp_f_freemode_01") and "female" or "male" -- Entsprechende Maske setzen local genderMask = maskData[playerGender] if genderMask then SetPedComponentVariation(ped, genderMask.component, genderMask.drawable, genderMask.texture, 0) debugPrint("Maske gesetzt: " .. team .. " (" .. playerGender .. ")") else debugPrint("Keine Maske für " .. team .. " (" .. playerGender .. ") gefunden!") end else debugPrint("Keine Masken-Daten für Team " .. team .. " gefunden!") end end function createTeamZoneBlip(team, fieldConfig) local zone = fieldConfig.teamZones[team] local blip = AddBlipForRadius(zone.center.x, zone.center.y, zone.center.z, zone.radius) SetBlipHighDetail(blip, true) SetBlipColour(blip, team == 'team1' and 1 or 3) SetBlipAlpha(blip, 128) teamZoneBlips[team] = blip debugPrint("Team Zone Blip erstellt für " .. team) end function removeTeamZoneBlips() for team, blip in pairs(teamZoneBlips) do if DoesBlipExist(blip) then RemoveBlip(blip) end end teamZoneBlips = {} debugPrint("Team Zone Blips entfernt") end function highlightTeamZone(team) if teamZoneBlips[team] and DoesBlipExist(teamZoneBlips[team]) then SetBlipFlashes(teamZoneBlips[team], true) end end function showHitMarker() CreateThread(function() local startTime = GetGameTimer() while GetGameTimer() - startTime < 500 do Wait(0) DrawRect(0.5, 0.5, 0.02, 0.002, 255, 0, 0, 255) DrawRect(0.5, 0.5, 0.002, 0.02, 255, 0, 0, 255) SetTextFont(4) SetTextProportional(1) SetTextScale(0.5, 0.5) SetTextColour(255, 0, 0, 255) SetTextEntry("STRING") AddTextComponentString("TREFFER!") SetTextCentre(true) DrawText(0.5, 0.45) end end) end function openMainMenu(fieldId) -- Sicherheitscheck if not fieldId or not Config.gameFields[fieldId] then lib.notify({ title = 'Fehler', description = 'Ungültiges Spielfeld!', type = 'error' }) return end currentLobbyField = fieldId TriggerServerEvent('tdm:requestGamesList') Wait(100) local fieldName = Config.gameFields[fieldId].name local options = { { title = 'Neues Spiel erstellen', description = 'Erstelle ein neues Spiel für ' .. fieldName, icon = 'plus', onSelect = function() openCreateGameMenu(fieldId) end }, { title = 'Spiel beitreten', description = 'Trete einem laufenden Spiel bei', icon = 'users', onSelect = function() openJoinGameMenu(fieldId) end } } if inTDM then table.insert(options, { title = 'Aktuelles Spiel verlassen', description = 'Verlasse das laufende Spiel', icon = 'door-open', iconColor = 'red', onSelect = function() TriggerServerEvent('tdm:leaveGame') end }) end lib.registerContext({ id = 'tdm_main_menu_' .. fieldId, title = 'TeamDeathmatch - ' .. fieldName, options = options }) lib.showContext('tdm_main_menu_' .. fieldId) end function openCreateGameMenu(fieldId) if not fieldId or not Config.gameFields[fieldId] then lib.notify({ title = 'Fehler', description = 'Ungültiges Spielfeld!', type = 'error' }) return end local fieldData = Config.gameFields[fieldId] local input = lib.inputDialog('Neues Spiel erstellen - ' .. fieldData.name, { { type = 'input', label = 'Spiel Name', description = 'Gib deinem Spiel einen Namen', required = true, max = 30 }, { type = 'select', label = 'Spiel Typ', description = 'Wähle den Spiel Typ', required = true, options = { {value = 'public', label = 'Öffentlich (Jeder kann beitreten)'}, {value = 'private', label = 'Privat (Nur mit Genehmigung)'} } }, { type = 'input', label = 'Passwort (Optional)', description = 'Passwort für das Spiel (leer lassen für kein Passwort)', password = true } }) if not input then return end local gameName = input[1] local gameType = input[2] local password = input[3] and input[3] ~= '' and input[3] or nil if gameName and gameType then TriggerServerEvent('tdm:createGame', gameName, fieldId, gameType, password) end end function openJoinGameMenu(fieldId) if not fieldId or not Config.gameFields[fieldId] then lib.notify({ title = 'Fehler', description = 'Ungültiges Spielfeld!', type = 'error' }) return end TriggerServerEvent('tdm:requestGamesList') Wait(200) local options = {} local fieldName = Config.gameFields[fieldId].name -- Nur Spiele für dieses Feld anzeigen for gameId, gameData in pairs(activeGames) do if gameData.fieldId == fieldId then local playerCount = #gameData.team1 + #gameData.team2 local maxPlayers = Config.gameFields[gameData.fieldId].maxPlayers local statusText = gameData.status == 'waiting' and 'Wartend' or 'Läuft' local typeText = gameData.gameType == 'public' and '🌐 Öffentlich' or '🔒 Privat' local passwordIcon = gameData.hasPassword and ' 🔑' or '' table.insert(options, { title = gameData.name .. passwordIcon, description = typeText .. ' | Spieler: ' .. playerCount .. '/' .. maxPlayers .. ' | Status: ' .. statusText, icon = gameData.gameType == 'public' and 'globe' or 'lock', iconColor = gameData.gameType == 'public' and 'green' or 'orange', args = { gameId = gameId, hasPassword = gameData.hasPassword, gameType = gameData.gameType }, onSelect = function(args) if args.hasPassword then local input = lib.inputDialog('Passwort eingeben', { { type = 'input', label = 'Passwort', description = 'Gib das Spiel-Passwort ein', required = true, password = true } }) if input and input[1] then TriggerServerEvent('tdm:requestJoinGame', args.gameId, input[1]) end else TriggerServerEvent('tdm:requestJoinGame', args.gameId) end end }) end end if #options == 0 then table.insert(options, { title = 'Keine Spiele verfügbar', description = 'Erstelle ein neues Spiel für ' .. fieldName, icon = 'info', disabled = true }) end lib.registerContext({ id = 'tdm_join_menu_' .. fieldId, title = 'Spiel beitreten - ' .. fieldName, menu = 'tdm_main_menu_' .. fieldId, options = options }) lib.showContext('tdm_join_menu_' .. fieldId) end -- Zone Marker Renderer CreateThread(function() while true do Wait(0) if inTDM and currentTeam and currentField then local zone = Config.gameFields[currentField].teamZones[currentTeam] local color = zone.color DrawMarker( 1, zone.center.x, zone.center.y, zone.center.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, zone.radius * 2, zone.radius * 2, 1.0, color.r, color.g, color.b, color.a, false, true, 2, false, nil, nil, false ) if isHit then DrawMarker( 2, zone.center.x, zone.center.y, zone.center.z + 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 255, 255, 0, 200, true, true, 2, false, nil, nil, false ) end else Wait(1000) end end end) -- Damage Handler (erweitert) CreateThread(function() while true do Wait(100) if inTDM and not isHit then local ped = PlayerPedId() if HasEntityBeenDamagedByAnyPed(ped) then local damager = GetPedSourceOfDeath(ped) local damagerPlayer = nil -- Versuche den Angreifer zu identifizieren for _, player in ipairs(GetActivePlayers()) do if GetPlayerPed(player) == damager then damagerPlayer = GetPlayerServerId(player) break end end ClearEntityLastDamageEntity(ped) -- Lokale Stats sofort updaten playerStats.deaths = playerStats.deaths + 1 debugPrint("Getroffen von: " .. (damagerPlayer or "Unbekannt")) TriggerEvent('tdm:playerHit') TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, damagerPlayer) end end end end) -- Death Handler CreateThread(function() while true do Wait(1000) if inTDM then local ped = PlayerPedId() if IsEntityDead(ped) then debugPrint("Spieler ist tot!") TriggerServerEvent('tdm:playerDied', currentGameId) lib.notify({ title = 'TeamDeathmatch', description = 'Du bist ausgeschieden!', type = 'error' }) Wait(3000) TriggerServerEvent('tdm:leaveGame') end end end end) -- NPC Setup für alle Felder CreateThread(function() -- Für jedes Spielfeld Blip und NPC erstellen for fieldId, fieldData in pairs(Config.gameFields) do if fieldData.lobby and fieldData.lobby.pos and fieldData.lobby.npc then local lobbyPos = fieldData.lobby.pos local npcData = fieldData.lobby.npc -- Blip erstellen local blip = AddBlipForCoord(lobbyPos.x, lobbyPos.y, lobbyPos.z) SetBlipSprite(blip, 432) SetBlipDisplay(blip, 4) SetBlipScale(blip, 0.8) SetBlipColour(blip, 1) SetBlipAsShortRange(blip, true) BeginTextCommandSetBlipName("STRING") AddTextComponentString("TDM - " .. fieldData.name) EndTextCommandSetBlipName(blip) tdmBlips[fieldId] = blip -- NPC erstellen RequestModel(GetHashKey(npcData.model)) while not HasModelLoaded(GetHashKey(npcData.model)) do Wait(1) end local npc = CreatePed(4, GetHashKey(npcData.model), npcData.coords.x, npcData.coords.y, npcData.coords.z, npcData.coords.w, false, true) SetEntityInvincible(npc, true) FreezeEntityPosition(npc, true) SetBlockingOfNonTemporaryEvents(npc, true) spawnedNPCs[fieldId] = npc -- Target für diesen NPC exports['qb-target']:AddTargetEntity(npc, { options = { { type = "client", event = "tdm:openFieldMenu", icon = "fas fa-crosshairs", label = "TeamDeathmatch - " .. fieldData.name, fieldId = fieldId }, }, distance = 2.5 }) debugPrint("NPC und Blip für Feld " .. fieldId .. " erstellt") else print("^3[TDM WARNING]^7 Feld " .. fieldId .. " hat keine vollständige Lobby-Konfiguration!") end end end) -- Event für Feld-spezifisches Menü RegisterNetEvent('tdm:openFieldMenu', function(data) if data and data.fieldId then openMainMenu(data.fieldId) else lib.notify({ title = 'Fehler', description = 'Keine Feld-ID übertragen!', type = 'error' }) end end) -- Chat Command zum Spiel verlassen RegisterCommand('leavetdm', function() if inTDM then TriggerServerEvent('tdm:leaveGame') lib.notify({ title = 'TeamDeathmatch', description = 'Du hast das Spiel über Command verlassen!', type = 'info' }) else lib.notify({ title = 'TeamDeathmatch', description = 'Du bist in keinem Spiel!', type = 'error' }) end end, false) -- Keybind zum Spiel verlassen RegisterKeyMapping('leavetdm', 'TeamDeathmatch verlassen', 'keyboard', 'F7') -- Debug Command zum Testen der Config RegisterCommand('debugtdm', function() print("^2[TDM DEBUG]^7 Aktuelle Werte:") print("inTDM: " .. tostring(inTDM)) print("currentField: " .. tostring(currentField)) print("currentLobbyField: " .. tostring(currentLobbyField)) print("currentTeam: " .. tostring(currentTeam)) print("currentGameId: " .. tostring(currentGameId)) print("isHit: " .. tostring(isHit)) print("Hits: " .. playerStats.hits) print("Deaths: " .. playerStats.deaths) print("^2[TDM DEBUG]^7 Verfügbare Felder:") for fieldId, fieldData in pairs(Config.gameFields) do local hasLobby = fieldData.lobby and fieldData.lobby.pos and "✅" or "❌" print("- " .. fieldId .. ": " .. fieldData.name .. " " .. hasLobby) end end, false) -- Debug Commands für Masken RegisterCommand('testmask', function(source, args) if not args[1] or not args[2] then lib.notify({ title = 'Debug', description = 'Verwendung: /testmask [team1/team2] [male/female]', type = 'error' }) return end local team = args[1] local gender = args[2] if Config.teamMasks[team] and Config.teamMasks[team][gender] then local maskData = Config.teamMasks[team][gender] local ped = PlayerPedId() SetPedComponentVariation(ped, maskData.component, maskData.drawable, maskData.texture, 0) lib.notify({ title = 'Debug', description = 'Maske gesetzt: ' .. team .. ' (' .. gender .. ')', type = 'success' }) else lib.notify({ title = 'Debug', description = 'Maske nicht gefunden!', type = 'error' }) end end, false) -- Command zum Entfernen der Maske RegisterCommand('removemask', function() SetPedComponentVariation(PlayerPedId(), 1, 0, 0, 0) lib.notify({ title = 'Debug', description = 'Maske entfernt!', type = 'info' }) end, false) -- Debug-Funktion für Respawn RegisterCommand('forcetdmrespawn', function() if inTDM and currentTeam and currentField then local ped = PlayerPedId() local fieldConfig = Config.gameFields[currentField] local spawnPoints = fieldConfig.teamSpawns[currentTeam] local randomSpawn = spawnPoints[math.random(#spawnPoints)] DoScreenFadeOut(500) Wait(600) ClearPedTasksImmediately(ped) SetEntityCoords(ped, randomSpawn.x, randomSpawn.y, randomSpawn.z) isHit = false Wait(100) DoScreenFadeIn(500) lib.notify({ title = 'Debug', description = 'Manueller Respawn durchgeführt lib.notify({ title = 'Debug', description = 'Manueller Respawn durchgeführt!', type = 'success' }) else lib.notify({ title = 'Debug', description = 'Du bist nicht in einem TDM-Spiel!', type = 'error' }) end end, false) -- Debug-Funktion für Stats RegisterCommand('showstats', function() if inTDM then print("^2[TDM DEBUG]^7 Lokale Stats:") print("Hits: " .. playerStats.hits) print("Deaths: " .. playerStats.deaths) if currentGameId then TriggerServerEvent('tdm:debugPlayerStats', currentGameId) end lib.notify({ title = 'Debug', description = 'Lokale Stats - Hits: ' .. playerStats.hits .. ', Deaths: ' .. playerStats.deaths, type = 'info' }) else lib.notify({ title = 'Debug', description = 'Du bist nicht in einem TDM-Spiel!', type = 'error' }) end end, false) -- Debug-Funktion für manuellen Hit RegisterCommand('testhit', function() if inTDM then TriggerEvent('tdm:playerHit') lib.notify({ title = 'Debug', description = 'Test-Hit ausgelöst!', type = 'info' }) else lib.notify({ title = 'Debug', description = 'Du bist nicht in einem TDM-Spiel!', type = 'error' }) end end, false) -- Debug-Funktion für manuellen Treffer RegisterCommand('testtreff', function() if inTDM then TriggerEvent('tdm:hitRegistered') lib.notify({ title = 'Debug', description = 'Test-Treffer registriert!', type = 'info' }) else lib.notify({ title = 'Debug', description = 'Du bist nicht in einem TDM-Spiel!', type = 'error' }) end end, false)