local QBCore = exports['qb-core']:GetCoreObject() local inTDM = false local currentTeam = nil local currentGameId = nil local currentField = nil local tdmBlip = nil local teamZoneBlips = {} local isHit = false local activeGames = {} -- 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 currentTeam = nil currentGameId = nil currentField = nil isHit = false -- Zurück zur Lobby SetEntityCoords(PlayerPedId(), Config.lobbyPos.x, Config.lobbyPos.y, Config.lobbyPos.z) -- 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() -- Arme hochnehmen Animation 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' }) -- Zone Marker hervorheben 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 SetPedComponentVariation(ped, maskData.component, maskData.drawable, maskData.texture, 0) 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) -- Rotes X in der Mitte des Bildschirms DrawRect(0.5, 0.5, 0.02, 0.002, 255, 0, 0, 255) -- Horizontale Linie DrawRect(0.5, 0.5, 0.002, 0.02, 255, 0, 0, 255) -- Vertikale Linie -- Text 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() TriggerServerEvent('tdm:requestGamesList') Wait(100) local options = { { title = 'Neues Spiel erstellen', description = 'Erstelle ein neues TeamDeathmatch Spiel', icon = 'plus', onSelect = function() openCreateGameMenu() end }, { title = 'Spiel beitreten', description = 'Trete einem laufenden Spiel bei', icon = 'users', onSelect = function() openJoinGameMenu() 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', title = 'TeamDeathmatch', options = options }) lib.showContext('tdm_main_menu') end function openCreateGameMenu() local fieldOptions = {} for fieldId, fieldData in pairs(Config.gameFields) do table.insert(fieldOptions, { value = fieldId, label = fieldData.name .. ' (Max: ' .. fieldData.maxPlayers .. ')' }) end local input = lib.inputDialog('Neues Spiel erstellen', { { type = 'input', label = 'Spiel Name', description = 'Gib deinem Spiel einen Namen', required = true, max = 30 }, { type = 'select', label = 'Spielfeld', description = 'Wähle ein Spielfeld', required = true, options = fieldOptions }, { 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 fieldId = input[2] local gameType = input[3] local password = input[4] and input[4] ~= '' and input[4] or nil if gameName and fieldId and gameType then TriggerServerEvent('tdm:createGame', gameName, fieldId, gameType, password) end end function openJoinGameMenu() TriggerServerEvent('tdm:requestGamesList') Wait(200) local options = {} for gameId, gameData in pairs(activeGames) do local playerCount = #gameData.team1 + #gameData.team2 local maxPlayers = Config.gameFields[gameData.fieldId].maxPlayers local fieldName = Config.gameFields[gameData.fieldId].name 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 .. ' | Feld: ' .. fieldName .. ' | 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 if #options == 0 then table.insert(options, { title = 'Keine Spiele verfügbar', description = 'Erstelle ein neues Spiel', icon = 'info', disabled = true }) end lib.registerContext({ id = 'tdm_join_menu', title = 'Spiel beitreten', menu = 'tdm_main_menu', options = options }) lib.showContext('tdm_join_menu') 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 CreateThread(function() -- Blip erstellen tdmBlip = AddBlipForCoord(Config.lobbyPos.x, Config.lobbyPos.y, Config.lobbyPos.z) SetBlipSprite(tdmBlip, 432) SetBlipDisplay(tdmBlip, 4) SetBlipScale(tdmBlip, 0.8) SetBlipColour(tdmBlip, 1) SetBlipAsShortRange(tdmBlip, true) BeginTextCommandSetBlipName("STRING") AddTextComponentString("TeamDeathmatch") EndTextCommandSetBlipName(tdmBlip) -- NPC erstellen RequestModel(GetHashKey(Config.gameNPC.model)) while not HasModelLoaded(GetHashKey(Config.gameNPC.model)) do Wait(1) end local npc = CreatePed(4, GetHashKey(Config.gameNPC.model), Config.gameNPC.coords.x, Config.gameNPC.coords.y, Config.gameNPC.coords.z, Config.gameNPC.coords.w, false, true) SetEntityInvincible(npc, true) FreezeEntityPosition(npc, true) SetBlockingOfNonTemporaryEvents(npc, true) end) -- Target für NPC exports['qb-target']:AddTargetModel(Config.gameNPC.model, { options = { { type = "client", event = "tdm:openMainMenu", icon = "fas fa-crosshairs", label = "TeamDeathmatch", }, }, distance = 2.5 }) RegisterNetEvent('tdm:openMainMenu', function() openMainMenu() end)