932 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			932 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| 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
 | |
| 
 | |
| -- Funktion zum Prüfen, ob eine Waffe eine Airsoft-Waffe ist
 | |
| function isAirsoftWeapon(weaponHash)
 | |
|     return Config.airsoftWeapons[weaponHash] or Config.treatAllWeaponsAsAirsoft
 | |
| end
 | |
| 
 | |
| -- Funktion zum Prüfen, ob der Spieler im Ragdoll-Zustand ist
 | |
| function isPedInRagdoll(ped)
 | |
|     return IsPedRagdoll(ped) or IsPedFalling(ped) or IsPedDiving(ped)
 | |
| end
 | |
| 
 | |
| -- Events
 | |
| RegisterNetEvent('tdm:updateGamesList', function(games)
 | |
|     activeGames = games
 | |
|     debugPrint("Spieleliste aktualisiert: " .. (games and table.count(games) or 0) .. " aktive Spiele")
 | |
| 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)
 | |
|         debugPrint("WARNUNG: 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 
 | |
|         debugPrint("WARNUNG: Hit-Event empfangen, aber nicht im TDM!")
 | |
|         return 
 | |
|     end
 | |
|     
 | |
|     if isHit then 
 | |
|         debugPrint("WARNUNG: Hit-Event empfangen, aber bereits getroffen!")
 | |
|         return 
 | |
|     end
 | |
|     
 | |
|     debugPrint("Spieler wurde getroffen - Starte Respawn-Sequenz")
 | |
|     isHit = true
 | |
|     local ped = PlayerPedId()
 | |
|     
 | |
|     -- Benachrichtigung
 | |
|     lib.notify({
 | |
|         title = 'TeamDeathmatch',
 | |
|         description = 'Du wurdest getroffen! Respawn in ' .. (Config.respawnDelay / 1000) .. ' Sekunden...',
 | |
|         type = 'error'
 | |
|     })
 | |
|     
 | |
|     -- Optional: Ragdoll aktivieren
 | |
|     SetPedToRagdoll(ped, 2000, 2000, 0, true, true, false)
 | |
|     
 | |
|     -- Verbesserte Respawn-Logik mit automatischem Teleport
 | |
|     SetTimeout(Config.respawnDelay, function()
 | |
|         if not inTDM then 
 | |
|             debugPrint("Respawn abgebrochen - nicht mehr im TDM")
 | |
|             return 
 | |
|         end
 | |
|         
 | |
|         debugPrint("Respawn wird ausgeführt...")
 | |
|         
 | |
|         -- Respawn zum Team Spawn
 | |
|         local fieldConfig = Config.gameFields[currentField]
 | |
|         if not fieldConfig then
 | |
|             debugPrint("FEHLER: Feldkonfiguration nicht gefunden für Respawn!")
 | |
|             return
 | |
|         end
 | |
|         
 | |
|         local spawnPoints = fieldConfig.teamSpawns[currentTeam]
 | |
|         if not spawnPoints or #spawnPoints == 0 then
 | |
|             debugPrint("FEHLER: Keine Spawn-Punkte gefunden für Respawn!")
 | |
|             return
 | |
|         end
 | |
|         
 | |
|         local randomSpawn = spawnPoints[math.random(#spawnPoints)]
 | |
|         debugPrint("Respawn-Position: " .. randomSpawn.x .. ", " .. randomSpawn.y .. ", " .. randomSpawn.z)
 | |
|         
 | |
|         -- Teleport zum Spawn mit Fade
 | |
|         DoScreenFadeOut(500)
 | |
|         Wait(600)
 | |
|         
 | |
|         -- Stellen Sie sicher, dass der Spieler lebt
 | |
|         NetworkResurrectLocalPlayer(randomSpawn.x, randomSpawn.y, randomSpawn.z, 0.0, true, false)
 | |
|         debugPrint("Spieler wiederbelebt")
 | |
|         
 | |
|         -- Alle Animationen stoppen
 | |
|         ClearPedTasksImmediately(ped)
 | |
|         
 | |
|         -- Teleport direkt zum Spawn-Punkt
 | |
|         SetEntityCoords(ped, randomSpawn.x, randomSpawn.y, randomSpawn.z)
 | |
|         SetEntityHealth(ped, GetEntityMaxHealth(ped))
 | |
|         
 | |
|         -- Spieler ist wieder aktiv
 | |
|         isHit = false
 | |
|         debugPrint("Respawn abgeschlossen - Spieler ist wieder aktiv")
 | |
|         
 | |
|         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
 | |
|     })
 | |
|     
 | |
|     -- Spiele einen Sound ab
 | |
|     PlaySoundFrontend(-1, "WEAPON_PURCHASE", "HUD_AMMO_SHOP_SOUNDSET", true)
 | |
|     
 | |
|     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()
 | |
|     debugPrint("Zeige Hit-Marker an")
 | |
|     CreateThread(function()
 | |
|         local startTime = GetGameTimer()
 | |
|         local duration = 500 -- 500ms display
 | |
|         
 | |
|         while GetGameTimer() - startTime < duration do
 | |
|             Wait(0)
 | |
|             
 | |
|             -- Draw hit marker
 | |
|             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)
 | |
|             
 | |
|             -- Draw hit 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(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)
 | |
| 
 | |
| -- Verbesserte Damage Handler für Airsoft-Waffen
 | |
| CreateThread(function()
 | |
|     while true do
 | |
|         if inTDM and not isHit then
 | |
|             local ped = PlayerPedId()
 | |
|             
 | |
|             -- Prüfe, ob der Spieler Schaden genommen hat
 | |
|             if HasEntityBeenDamagedByAnyPed(ped) then
 | |
|                 debugPrint("Schaden erkannt - Identifiziere Angreifer und Waffe")
 | |
|                 
 | |
|                 local damager = nil
 | |
|                 local damagerPlayer = nil
 | |
|                 local weaponHash = 0
 | |
|                 
 | |
|                 -- Versuche den Angreifer und die Waffe zu identifizieren
 | |
|                 for _, player in ipairs(GetActivePlayers()) do
 | |
|                     local playerPed = GetPlayerPed(player)
 | |
|                     if HasPedBeenDamagedBy(ped, playerPed) then
 | |
|                         damager = playerPed
 | |
|                         damagerPlayer = GetPlayerServerId(player)
 | |
|                         
 | |
|                         -- Versuche die Waffe zu ermitteln
 | |
|                         weaponHash = GetSelectedPedWeapon(playerPed)
 | |
|                         debugPrint("Angreifer identifiziert: " .. damagerPlayer .. " mit Waffe: " .. weaponHash)
 | |
|                         break
 | |
|                     end
 | |
|                 end
 | |
|                 
 | |
|                 -- Prüfe, ob es eine Airsoft-Waffe ist oder alle Waffen als Airsoft behandelt werden
 | |
|                 if isAirsoftWeapon(weaponHash) then
 | |
|                     debugPrint("Airsoft-Treffer erkannt mit Waffe: " .. weaponHash)
 | |
|                     
 | |
|                     -- Schaden zurücksetzen
 | |
|                     ClearEntityLastDamageEntity(ped)
 | |
|                     ClearPedLastWeaponDamage(ped)
 | |
|                     SetEntityHealth(ped, GetEntityMaxHealth(ped))
 | |
|                     
 | |
|                     -- Lokale Stats sofort updaten
 | |
|                     playerStats.deaths = playerStats.deaths + 1
 | |
|                     
 | |
|                     debugPrint("Getroffen von: " .. (damagerPlayer or "Unbekannt"))
 | |
|                     
 | |
|                     -- Treffer-Events auslösen
 | |
|                     TriggerEvent('tdm:playerHit')
 | |
|                     TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, damagerPlayer)
 | |
|                     
 | |
|                     -- Warten um mehrfache Auslösung zu verhindern
 | |
|                     Wait(500)
 | |
|                 else
 | |
|                     debugPrint("Keine Airsoft-Waffe erkannt: " .. weaponHash)
 | |
|                     -- Schaden trotzdem zurücksetzen
 | |
|                     ClearEntityLastDamageEntity(ped)
 | |
|                     SetEntityHealth(ped, GetEntityMaxHealth(ped))
 | |
|                 end
 | |
|             end
 | |
|             Wait(0)
 | |
|         else
 | |
|             Wait(500)
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Zusätzlicher Event-Handler für zuverlässigere Treffer-Erkennung
 | |
| AddEventHandler('gameEventTriggered', function(name, args)
 | |
|     if name == "CEventNetworkEntityDamage" then
 | |
|         local victimId = args[1]
 | |
|         local attackerId = args[2]
 | |
|         local isDead = args[4] == 1
 | |
|         local weaponHash = args[5]
 | |
|         local isMelee = args[10] == 1
 | |
|         
 | |
|         if inTDM and not isHit and victimId == PlayerPedId() then
 | |
|             local attackerServerId = nil
 | |
|             
 | |
|             -- Versuche den Angreifer zu identifizieren
 | |
|             for _, player in ipairs(GetActivePlayers()) do
 | |
|                 if GetPlayerPed(player) == attackerId then
 | |
|                     attackerServerId = GetPlayerServerId(player)
 | |
|                     break
 | |
|                 end
 | |
|             end
 | |
|             
 | |
|             debugPrint("Schaden-Event erkannt: Angreifer=" .. (attackerServerId or "NPC/Unbekannt") .. ", Waffe=" .. weaponHash)
 | |
|             
 | |
|             -- Prüfe, ob es eine Airsoft-Waffe ist oder alle Waffen als Airsoft behandelt werden
 | |
|             if isAirsoftWeapon(weaponHash) and attackerServerId then
 | |
|                 -- Lokale Stats sofort updaten
 | |
|                 playerStats.deaths = playerStats.deaths + 1
 | |
|                 
 | |
|                 -- Verhindern, dass der Spieler stirbt
 | |
|                 SetEntityHealth(PlayerPedId(), GetEntityMaxHealth(PlayerPedId()))
 | |
|                 
 | |
|                 -- Treffer-Events auslösen
 | |
|                 TriggerEvent('tdm:playerHit')
 | |
|                 TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, attackerServerId)
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Direkter Waffen-Schaden Monitor für zusätzliche Zuverlässigkeit
 | |
| CreateThread(function()
 | |
|     while true do
 | |
|         Wait(0)
 | |
|         if inTDM and not isHit then
 | |
|             local ped = PlayerPedId()
 | |
|             
 | |
|             -- Prüfe auf Projektil-Treffer
 | |
|             if HasEntityBeenDamagedByWeapon(ped, 0, 2) then -- 2 = Projektilwaffen
 | |
|                 debugPrint("Projektil-Treffer erkannt")
 | |
|                 
 | |
|                 -- Schaden zurücksetzen
 | |
|                 ClearEntityLastDamageEntity(ped)
 | |
|                 ClearPedLastWeaponDamage(ped)
 | |
|                 SetEntityHealth(ped, GetEntityMaxHealth(ped))
 | |
|                 
 | |
|                 -- Lokale Stats sofort updaten
 | |
|                 playerStats.deaths = playerStats.deaths + 1
 | |
|                 
 | |
|                 -- Treffer-Events auslösen
 | |
|                 TriggerEvent('tdm:playerHit')
 | |
|                 TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam)
 | |
|                 
 | |
|                 -- Warten um mehrfache Auslösung zu verhindern
 | |
|                 Wait(500)
 | |
|             end
 | |
|         else
 | |
|             Wait(500)
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Ragdoll-Erkennung Thread
 | |
| CreateThread(function()
 | |
|     local lastDamager = nil
 | |
|     local lastDamageTime = 0
 | |
|     
 | |
|     while true do
 | |
|         Wait(100)
 | |
|         if inTDM and not isHit then
 | |
|             local ped = PlayerPedId()
 | |
|             
 | |
|             -- Speichere den letzten Angreifer, wenn Schaden genommen wurde
 | |
|             if HasEntityBeenDamagedByAnyPed(ped) then
 | |
|                 for _, player in ipairs(GetActivePlayers()) do
 | |
|                     local playerPed = GetPlayerPed(player)
 | |
|                     if HasPedBeenDamagedBy(ped, playerPed) then
 | |
|                         lastDamager = GetPlayerServerId(player)
 | |
|                         lastDamageTime = GetGameTimer()
 | |
|                         debugPrint("Letzter Angreifer gespeichert: " .. lastDamager)
 | |
|                         break
 | |
|                     end
 | |
|                 end
 | |
|                 ClearEntityLastDamageEntity(ped)
 | |
|             end
 | |
|             
 | |
|             -- Prüfe, ob der Spieler im Ragdoll-Zustand ist
 | |
|             if isPedInRagdoll(ped) then
 | |
|                 debugPrint("Ragdoll-Zustand erkannt - Zählt als Tod")
 | |
|                 
 | |
|                 -- Lokale Stats sofort updaten
 | |
|                 playerStats.deaths = playerStats.deaths + 1
 | |
|                 
 | |
|                 -- Bestimme den Angreifer (verwende den letzten Angreifer, wenn innerhalb von 3 Sekunden)
 | |
|                 local attacker = nil
 | |
|                 if lastDamager and (GetGameTimer() - lastDamageTime) < 3000 then
 | |
|                     attacker = lastDamager
 | |
|                     debugPrint("Ragdoll-Tod zugeordnet an Angreifer: " .. attacker)
 | |
|                 else
 | |
|                     debugPrint("Kein Angreifer für Ragdoll-Tod gefunden")
 | |
|                 end
 | |
|                 
 | |
|                 -- Treffer-Events auslösen
 | |
|                 TriggerEvent('tdm:playerHit')
 | |
|                 TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, attacker)
 | |
|                 
 | |
|                 -- Zurücksetzen des letzten Angreifers
 | |
|                 lastDamager = nil
 | |
|                 
 | |
|                 -- Warten um mehrfache Auslösung zu verhindern
 | |
|                 Wait(500)
 | |
|             end
 | |
|         else
 | |
|             Wait(500)
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Function to spawn NPCs for all fields
 | |
| function spawnFieldNPCs()
 | |
|     for fieldId, fieldData in pairs(Config.gameFields) do
 | |
|         if fieldData.lobby and fieldData.lobby.npc then
 | |
|             local npcData = fieldData.lobby.npc
 | |
|             local model = GetHashKey(npcData.model)
 | |
|             
 | |
|             -- Request the model
 | |
|             RequestModel(model)
 | |
|             while not HasModelLoaded(model) do
 | |
|                 Wait(10)
 | |
|             end
 | |
|             
 | |
|             -- Create the NPC
 | |
|             local npc = CreatePed(4, model, npcData.coords.x, npcData.coords.y, npcData.coords.z, npcData.coords.w, false, true)
 | |
|             SetEntityAsMissionEntity(npc, true, true)
 | |
|             SetBlockingOfNonTemporaryEvents(npc, true)
 | |
|             FreezeEntityPosition(npc, true)
 | |
|             SetEntityInvincible(npc, true)
 | |
|             
 | |
|             -- Add to spawned NPCs table
 | |
|             table.insert(spawnedNPCs, npc)
 | |
|             
 | |
|             -- Add target interaction
 | |
|             exports['qb-target']:AddTargetEntity(npc, {
 | |
|                 options = {
 | |
|                     {
 | |
|                         type = "client",
 | |
|                         event = "tdm:openMenu",
 | |
|                         icon = "fas fa-gamepad",
 | |
|                         label = "TeamDeathmatch Menu",
 | |
|                         fieldId = fieldId
 | |
|                     }
 | |
|                 },
 | |
|                 distance = 2.0
 | |
|             })
 | |
|             
 | |
|             debugPrint("Spawned NPC for field: " .. fieldId)
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- Register event handler for NPC interaction
 | |
| RegisterNetEvent('tdm:openMenu', function(data)
 | |
|     if data.fieldId then
 | |
|         openMainMenu(data.fieldId)
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Spawn NPCs when resource starts
 | |
| CreateThread(function()
 | |
|     Wait(1000) -- Wait for everything to load
 | |
|     spawnFieldNPCs()
 | |
| end)
 | |
| 
 | |
| -- Function to create blips for all TDM lobbies
 | |
| function createTDMBlips()
 | |
|     for fieldId, fieldData in pairs(Config.gameFields) do
 | |
|         if fieldData.lobby and fieldData.lobby.pos then
 | |
|             local blip = AddBlipForCoord(fieldData.lobby.pos.x, fieldData.lobby.pos.y, fieldData.lobby.pos.z)
 | |
|             SetBlipSprite(blip, 156) -- You can change this to any appropriate sprite
 | |
|             SetBlipDisplay(blip, 4)
 | |
|             SetBlipScale(blip, 0.8)
 | |
|             SetBlipColour(blip, 3)
 | |
|             SetBlipAsShortRange(blip, true)
 | |
|             BeginTextCommandSetBlipName("STRING")
 | |
|             AddTextComponentString("TDM: " .. fieldData.name)
 | |
|             EndTextCommandSetBlipName(blip)
 | |
|             
 | |
|             table.insert(tdmBlips, blip)
 | |
|             debugPrint("Created blip for TDM field: " .. fieldId)
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- Call this function when the resource starts
 | |
| CreateThread(function()
 | |
|     Wait(2000) -- Wait for everything to load
 | |
|     createTDMBlips()
 | |
| end)
 | |
| 
 | |
| -- Cleanup function
 | |
| function cleanupResources()
 | |
|     -- Remove NPCs
 | |
|     for _, npc in ipairs(spawnedNPCs) do
 | |
|         if DoesEntityExist(npc) then
 | |
|             DeleteEntity(npc)
 | |
|         end
 | |
|     end
 | |
|     spawnedNPCs = {}
 | |
|     
 | |
|     -- Remove blips
 | |
|     for _, blip in ipairs(tdmBlips) do
 | |
|         if DoesBlipExist(blip) then
 | |
|             RemoveBlip(blip)
 | |
|         end
 | |
|     end
 | |
|     tdmBlips = {}
 | |
|     
 | |
|     debugPrint("Cleaned up all NPCs and blips")
 | |
| end
 | |
| 
 | |
| -- Register cleanup when resource stops
 | |
| AddEventHandler('onResourceStop', function(resourceName)
 | |
|     if GetCurrentResourceName() == resourceName then
 | |
|         cleanupResources()
 | |
|     end
 | |
| end)
 | 
