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 } -- 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' }) 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' }) 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 or isHit then return end isHit = true local ped = PlayerPedId() RequestAnimDict("random@mugging3") while not HasAnimDictLoaded("random@mugging3") do Wait(1) end TaskPlayAnim(ped, "random@mugging3", "handsup_standing_base", 8.0, -8.0, -1, 50, 0, false, false, false) lib.notify({ title = 'TeamDeathmatch', description = 'Du wurdest getroffen! Gehe zurück zu deiner Team Zone!', type = 'error' }) highlightTeamZone(currentTeam) end) RegisterNetEvent('tdm:updateScore', function(team1Score, team2Score, gameStats) local displayText = string.format( '[Team 1: %d] VS [Team 2: %d] | Deine Treffer: %d | Tode: %d', team1Score, team2Score, playerStats.hits, playerStats.deaths ) 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() end) RegisterNetEvent('tdm:deathRegistered', function() playerStats.deaths = playerStats.deaths + 1 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') 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) end 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 end function removeTeamZoneBlips() for team, blip in pairs(teamZoneBlips) do if DoesBlipExist(blip) then RemoveBlip(blip) end end teamZoneBlips = {} 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 Checker Thread CreateThread(function() while true do Wait(500) if inTDM and isHit and currentTeam and currentField then local ped = PlayerPedId() local playerPos = GetEntityCoords(ped) local zone = Config.gameFields[currentField].teamZones[currentTeam] local distance = #(playerPos - zone.center) if distance <= zone.radius then isHit = false ClearPedTasks(ped) if teamZoneBlips[currentTeam] and DoesBlipExist(teamZoneBlips[currentTeam]) then SetBlipFlashes(teamZoneBlips[currentTeam], false) end lib.notify({ title = 'TeamDeathmatch', description = 'Du bist wieder im Spiel!', type = 'success' }) end end end 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 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 for _, player in pairs(GetActivePlayers()) do if GetPlayerPed(player) == damager then damagerPlayer = GetPlayerServerId(player) break end end ClearEntityLastDamageEntity(ped) 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 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 }) 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("^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)