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 = {} local lastDamager = nil local lastDamageTime = 0 local lastDamageWeapon = 0 -- 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 -- Enhanced function to check if a player is in ragdoll state function isPedInRagdoll(ped) return IsPedRagdoll(ped) or IsPedFalling(ped) or IsPedDiving(ped) or (not IsPedStill(ped) and IsPedOnFoot(ped) and not IsPedWalking(ped) and not IsPedRunning(ped) and not IsPedSprinting(ped)) end -- Helper function to dump tables for debugging function dumpTable(table, indent) if not indent then indent = 0 end for k, v in pairs(table) do local formatting = string.rep(" ", indent) .. k .. ": " if type(v) == "table" then print(formatting) dumpTable(v, indent+1) else print(formatting .. tostring(v)) end end 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 lastDamager = nil lastDamageTime = 0 lastDamageWeapon = 0 -- 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) -- Enhanced debug output debugPrint("Score Update empfangen: Team1=" .. team1Score .. ", Team2=" .. team2Score) if gameStats then debugPrint("GameStats received: ") dumpTable(gameStats) else debugPrint("WARNING: No gameStats received from server!") end -- More robust handling of stats local myHits = 0 local myDeaths = 0 if gameStats and type(gameStats) == "table" then myHits = gameStats.hits or 0 myDeaths = gameStats.deaths or 0 debugPrint("Using server stats: Hits=" .. myHits .. ", Deaths=" .. myDeaths) else myHits = playerStats.hits myDeaths = playerStats.deaths debugPrint("Using local stats: Hits=" .. myHits .. ", Deaths=" .. myDeaths) end 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) -- Update last damager info lastDamager = damagerPlayer lastDamageTime = GetGameTimer() lastDamageWeapon = 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) -- Enhanced damage detection for ragdoll kills 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) -- Update last damager info if attackerServerId then lastDamager = attackerServerId lastDamageTime = GetGameTimer() lastDamageWeapon = weaponHash debugPrint("Last damager updated to " .. attackerServerId .. " with weapon " .. weaponHash) end -- 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) -- Improved Ragdoll-Erkennung Thread CreateThread(function() while true do Wait(50) -- Check more frequently if inTDM and not isHit then local ped = PlayerPedId() -- Prüfe, ob der Spieler im Ragdoll-Zustand ist if isPedInRagdoll(ped) then -- Bestimme den Angreifer (verwende den letzten Angreifer, wenn innerhalb von 3 Sekunden) local attacker = nil if lastDamager and (GetGameTimer() - local QBCore = exports['qb-core']:GetCoreObject() -- Game Management local activeGames = {} local gameIdCounter = 1 -- Debug-Funktion für Konsole local function debugPrint(message) print("^2[TDM SERVER]^7 " .. message) end -- Events RegisterNetEvent('tdm:createGame', function(gameName, fieldId, gameType, password) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then debugPrint("Spielerstellung fehlgeschlagen - Spieler nicht gefunden: " .. src) return end -- Prüfen ob Spielfeld bereits belegt for gameId, gameData in pairs(activeGames) do if gameData.fieldId == fieldId then debugPrint("Spielerstellung abgelehnt - Feld bereits belegt: " .. fieldId) TriggerClientEvent('QBCore:Notify', src, 'Dieses Spielfeld ist bereits belegt!', 'error') return end end local gameId = 'game_' .. gameIdCounter gameIdCounter = gameIdCounter + 1 activeGames[gameId] = { id = gameId, name = gameName, fieldId = fieldId, admin = src, adminName = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname, gameType = gameType, password = password, hasPassword = password ~= nil, status = 'waiting', team1 = {}, team2 = {}, score = {team1 = 0, team2 = 0}, startTime = nil, maxTime = Config.maxGameTime, maxHits = Config.maxHits, playerStats = {} -- Spieler-Statistiken initialisieren } local typeText = gameType == 'public' and 'öffentliches' or 'privates' TriggerClientEvent('QBCore:Notify', src, 'Dein ' .. typeText .. ' Spiel "' .. gameName .. '" wurde erstellt!', 'success') updateGamesListForAll() debugPrint("Spiel erstellt: " .. gameId .. " von " .. Player.PlayerData.name .. " (Feld: " .. fieldId .. ")") end) RegisterNetEvent('tdm:requestGamesList', function() local src = source debugPrint("Spiele-Liste angefordert von: " .. src) TriggerClientEvent('tdm:updateGamesList', src, activeGames) end) RegisterNetEvent('tdm:requestJoinGame', function(gameId, password) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player or not activeGames[gameId] then debugPrint("Spielbeitritt fehlgeschlagen - Spieler oder Spiel nicht gefunden: " .. src .. ", " .. gameId) return end local game = activeGames[gameId] local playerName = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname debugPrint("Beitrittsanfrage von " .. playerName .. " (ID: " .. src .. ") für Spiel " .. gameId) -- Passwort prüfen falls vorhanden if game.hasPassword and game.password ~= password then debugPrint("Beitritt abgelehnt - Falsches Passwort") TriggerClientEvent('QBCore:Notify', src, 'Falsches Passwort!', 'error') return end -- Spieler bereits im Spiel? for _, playerId in ipairs(game.team1) do if playerId == src then debugPrint("Beitritt abgelehnt - Spieler bereits in Team 1") TriggerClientEvent('QBCore:Notify', src, 'Du bist bereits in diesem Spiel!', 'error') return end end for _, playerId in ipairs(game.team2) do if playerId == src then debugPrint("Beitritt abgelehnt - Spieler bereits in Team 2") TriggerClientEvent('QBCore:Notify', src, 'Du bist bereits in diesem Spiel!', 'error') return end end -- Max Spieler erreicht? local currentPlayers = #game.team1 + #game.team2 local maxPlayers = Config.gameFields[game.fieldId].maxPlayers if currentPlayers >= maxPlayers then debugPrint("Beitritt abgelehnt - Spiel ist voll") TriggerClientEvent('QBCore:Notify', src, 'Spiel ist voll!', 'error') return end -- Prüfen ob Admin online ist (für private Spiele) if game.gameType == 'private' then local AdminPlayer = QBCore.Functions.GetPlayer(game.admin) if not AdminPlayer then debugPrint("Beitritt abgelehnt - Admin nicht online") TriggerClientEvent('QBCore:Notify', src, 'Der Spiel-Admin ist nicht online!', 'error') return end end -- Join Logic basierend auf Spiel Typ if game.gameType == 'public' then debugPrint("Öffentliches Spiel - Direkter Beitritt") joinPlayerToGame(src, gameId) TriggerClientEvent('QBCore:Notify', src, 'Du bist dem öffentlichen Spiel beigetreten!', 'success') else debugPrint("Privates Spiel - Sende Anfrage an Admin") TriggerClientEvent('tdm:joinRequest', game.admin, gameId, playerName, src) TriggerClientEvent('QBCore:Notify', src, 'Join-Anfrage gesendet an ' .. game.adminName, 'info') end end) RegisterNetEvent('tdm:approveJoinRequest', function(gameId, playerId, approved) local src = source local game = activeGames[gameId] if not game or game.admin ~= src then debugPrint("Join-Anfrage Bearbeitung fehlgeschlagen - Ungültiges Spiel oder nicht Admin") return end if approved then debugPrint("Join-Anfrage genehmigt für Spieler " .. playerId .. " in Spiel " .. gameId) joinPlayerToGame(playerId, gameId) TriggerClientEvent('tdm:joinRequestResult', playerId, true, game.name) else debugPrint("Join-Anfrage abgelehnt für Spieler " .. playerId .. " in Spiel " .. gameId) TriggerClientEvent('tdm:joinRequestResult', playerId, false, game.name) end end) RegisterNetEvent('tdm:leaveGame', function() local src = source debugPrint("Spieler " .. src .. " möchte alle Spiele verlassen") for gameId, game in pairs(activeGames) do removePlayerFromGame(src, gameId) end TriggerClientEvent('tdm:leaveGame', src) end) RegisterNetEvent('tdm:playerWasHit', function(gameId, victimTeam, attackerId) local victim = source if not activeGames[gameId] then debugPrint("Hit registriert, aber Spiel " .. gameId .. " existiert nicht!") return end local game = activeGames[gameId] debugPrint("Hit registriert - Opfer: " .. victim .. " (Team: " .. victimTeam .. "), Angreifer: " .. (attackerId or "Unbekannt")) -- Spieler Stats initialisieren falls nicht vorhanden if not game.playerStats then game.playerStats = {} end if not game.playerStats[victim] then game.playerStats[victim] = {hits = 0, deaths = 0} end -- Wichtig: Nur wenn ein Angreifer identifiziert wurde, zähle den Kill if attackerId then if not game.playerStats[attackerId] then game.playerStats[attackerId] = {hits = 0, deaths = 0} end -- Stats updaten game.playerStats[victim].deaths = (game.playerStats[victim].deaths or 0) + 1 game.playerStats[attackerId].hits = (game.playerStats[attackerId].hits or 0) + 1 -- Benachrichtigung an den Angreifer senden TriggerClientEvent('tdm:hitRegistered', attackerId) debugPrint("Treffer von " .. attackerId .. " gegen " .. victim .. " registriert") -- Team Score erhöhen if victimTeam == 'team1' then game.score.team2 = game.score.team2 + 1 debugPrint("Punkt für Team 2 - Neuer Score: " .. game.score.team2) else game.score.team1 = game.score.team1 + 1 debugPrint("Punkt für Team 1 - Neuer Score: " .. game.score.team1) end else debugPrint("Treffer gegen " .. victim .. " von unbekanntem Angreifer registriert - Kein Punkt vergeben") end TriggerClientEvent('tdm:deathRegistered', victim) -- Score an alle Spieler senden updateScoreForGame(gameId) -- Spiel beenden prüfen if game.score.team1 >= game.maxHits or game.score.team2 >= game.maxHits then local winnerTeam = game.score.team1 >= game.maxHits and 'team1' or 'team2' debugPrint("Max Punkte erreicht - Beende Spiel. Gewinner: " .. winnerTeam) endGame(gameId, winnerTeam) end end) RegisterNetEvent('tdm:playerDied', function(gameId) local src = source debugPrint("Spieler " .. src .. " ist gestorben in Spiel " .. gameId) removePlayerFromGame(src, gameId) end) RegisterNetEvent('tdm:requestScoreUpdate', function(gameId) local src = source if activeGames[gameId] then debugPrint("Score-Update angefordert von " .. src .. " für Spiel " .. gameId) updateScoreForGame(gameId) else debugPrint("Score-Update fehlgeschlagen - Spiel " .. gameId .. " nicht gefunden") end end) RegisterNetEvent('tdm:debugPlayerStats', function(gameId) local src = source if activeGames[gameId] and activeGames[gameId].playerStats and activeGames[gameId].playerStats[src] then local stats = activeGames[gameId].playerStats[src] debugPrint("Stats für Spieler " .. src .. ": Hits=" .. (stats.hits or 0) .. ", Deaths=" .. (stats.deaths or 0)) TriggerClientEvent('QBCore:Notify', src, 'Server Stats - Hits: ' .. (stats.hits or 0) .. ', Deaths: ' .. (stats.deaths or 0), 'info') else debugPrint("Keine Stats gefunden für Spieler " .. src .. " in Spiel " .. gameId) TriggerClientEvent('QBCore:Notify', src, 'Keine Stats gefunden!', 'error') end end) -- Funktionen function joinPlayerToGame(playerId, gameId) local game = activeGames[gameId] if not game then debugPrint("Spielbeitritt fehlgeschlagen - Spiel nicht gefunden: " .. gameId) return end -- Team mit weniger Spielern wählen local team = #game.team1 <= #game.team2 and 'team1' or 'team2' table.insert(game[team], playerId) -- Spieler-Stats initialisieren if not game.playerStats then game.playerStats = {} end game.playerStats[playerId] = {hits = 0, deaths = 0} -- Spiel starten wenn mindestens 2 Spieler if #game.team1 + #game.team2 >= 2 and game.status == 'waiting' then game.status = 'active' game.startTime = os.time() debugPrint("Spiel " .. gameId .. " gestartet - Mindestens 2 Spieler erreicht") -- Game Timer starten startGameTimer(gameId) end TriggerClientEvent('tdm:joinGame', playerId, gameId, team, game.fieldId) updateScoreForGame(gameId) updateGamesListForAll() debugPrint("Spieler " .. playerId .. " ist Spiel " .. gameId .. " beigetreten (Team: " .. team .. ")") end function removePlayerFromGame(playerId, gameId) local game = activeGames[gameId] if not game then return end -- Spieler aus Teams entfernen local removed = false for i, id in ipairs(game.team1) do if id == playerId then table.remove(game.team1, i) debugPrint("Spieler " .. playerId .. " aus Team 1 entfernt") removed = true break end end if not removed then for i, id in ipairs(game.team2) do if id == playerId then table.remove(game.team2, i) debugPrint("Spieler " .. playerId .. " aus Team 2 entfernt") removed = true break end end end if not removed then debugPrint("Spieler " .. playerId .. " nicht in Spiel " .. gameId .. " gefunden") return end -- Wenn Admin das Spiel verlässt, Spiel beenden if game.admin == playerId then debugPrint("Admin hat das Spiel verlassen - Beende Spiel " .. gameId) endGame(gameId, nil, 'Admin hat das Spiel verlassen') return end checkGameEnd(gameId) updateGamesListForAll() end function endGame(gameId, winnerTeam, reason) local game = activeGames[gameId] if not game then debugPrint("Spielende fehlgeschlagen - Spiel nicht gefunden: " .. gameId) return end game.status = 'finished' local allPlayers = {} for _, playerId in ipairs(game.team1) do table.insert(allPlayers, playerId) end for _, playerId in ipairs(game.team2) do table.insert(allPlayers, playerId) end -- Game End Event an alle Spieler for _, playerId in ipairs(allPlayers) do debugPrint("Sende Spielende-Event an Spieler " .. playerId) TriggerClientEvent('tdm:gameEnded', playerId, winnerTeam, game.score.team1, game.score.team2) end -- Spiel nach 10 Sekunden löschen SetTimeout(10000, function() activeGames[gameId] = nil updateGamesListForAll() debugPrint("Spiel " .. gameId .. " aus der Liste entfernt") end) if reason then debugPrint("Spiel " .. gameId .. " beendet: " .. reason) else debugPrint("Spiel " .. gameId .. " beendet. Gewinner: " .. (winnerTeam or "Unentschieden")) end end function startGameTimer(gameId) CreateThread(function() local game = activeGames[gameId] if not game then return end local maxTime = game.maxTime local startTime = os.time() debugPrint("Timer für Spiel " .. gameId .. " gestartet. Maximale Zeit: " .. maxTime .. " Sekunden") while game and game.status == 'active' and (os.time() - startTime) < maxTime do Wait(1000) game = activeGames[gameId] -- Refresh game data -- Alle 30 Sekunden Debug-Info if (os.time() - startTime) % 30 == 0 then debugPrint("Spiel " .. gameId .. " läuft seit " .. (os.time() - startTime) .. " Sekunden") end end -- Zeit abgelaufen if game and game.status == 'active' then local winnerTeam = game.score.team1 > game.score.team2 and 'team1' or game.score.team2 > game.score.team1 and 'team2' or nil debugPrint("Spielzeit abgelaufen - Beende Spiel " .. gameId) endGame(gameId, winnerTeam, 'Zeit abgelaufen') end end) end function checkGameEnd(gameId) local game = activeGames[gameId] if not game then return end local totalPlayers = #game.team1 + #game.team2 if totalPlayers < 2 and game.status == 'active' then debugPrint("Zu wenig Spieler - Beende Spiel " .. gameId) endGame(gameId, nil, 'Zu wenig Spieler') elseif totalPlayers == 0 then activeGames[gameId] = nil updateGamesListForAll() debugPrint("Spiel " .. gameId .. " gelöscht (keine Spieler)") end end function updateScoreForGame(gameId) local game = activeGames[gameId] if not game then debugPrint("Score-Update fehlgeschlagen - Spiel nicht gefunden: " .. gameId) return end debugPrint("Score Update für Spiel " .. gameId .. ": Team1=" .. game.score.team1 .. ", Team2=" .. game.score.team2) for _, playerId in ipairs(game.team1) do local playerStats = game.playerStats[playerId] or {hits = 0, deaths = 0} TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2, { hits = playerStats.hits or 0, deaths = playerStats.deaths or 0 }) end for _, playerId in ipairs(game.team2) do local playerStats = game.playerStats[playerId] or {hits = 0, deaths = 0} TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2, { hits = playerStats.hits or 0, deaths = playerStats.deaths or 0 }) end end function updateGamesListForAll() local players = QBCore.Functions.GetPlayers() for _, playerId in pairs(players) do TriggerClientEvent('tdm:updateGamesList', playerId, activeGames) end end -- Player Disconnect Handler AddEventHandler('playerDropped', function() local src = source for gameId, game in pairs(activeGames) do removePlayerFromGame(src, gameId) end debugPrint("Spieler " .. src .. " hat den Server verlassen - aus allen Spielen entfernt") end) -- Server Start - Games Liste leeren AddEventHandler('onResourceStart', function(resourceName) if GetCurrentResourceName() == resourceName then activeGames = {} gameIdCounter = 1 debugPrint("TeamDeathmatch System gestartet!") end end) AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then -- Alle Spieler aus TDM entfernen for gameId, game in pairs(activeGames) do local allPlayers = {} for _, playerId in ipairs(game.team1) do table.insert(allPlayers, playerId) end for _, playerId in ipairs(game.team2) do table.insert(allPlayers, playerId) end for _, playerId in ipairs(allPlayers) do TriggerClientEvent('tdm:leaveGame', playerId) end end activeGames = {} debugPrint("TeamDeathmatch System gestoppt!") end end) -- Admin-Befehle RegisterCommand('tdmreset', function(source, args) local src = source if src > 0 then -- Spieler local Player = QBCore.Functions.GetPlayer(src) if not Player or not Player.PlayerData.job or Player.PlayerData.job.name ~= 'admin' then TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung!', 'error') return end end -- Alle Spieler aus TDM entfernen for gameId, game in pairs(activeGames) do local allPlayers = {} for _, playerId in ipairs(game.team1) do table.insert(allPlayers, playerId) end for _, playerId in ipairs(game.team2) do table.insert(allPlayers, playerId) end for _, playerId in ipairs(allPlayers) do TriggerClientEvent('tdm:leaveGame', playerId) end end activeGames = {} gameIdCounter = 1 debugPrint("TeamDeathmatch System zurückgesetzt!") if src > 0 then TriggerClientEvent('QBCore:Notify', src, 'TDM System zurückgesetzt!', 'success') end end, true) -- Debug-Befehl für Server-Status RegisterCommand('tdmstatus', function(source, args) local src = source if src > 0 then -- Spieler local Player = QBCore.Functions.GetPlayer(src) if not Player or not Player.PlayerData.job or Player.PlayerData.job.name ~= 'admin' then TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung!', 'error') return end end debugPrint("=== TDM STATUS ===") debugPrint("Aktive Spiele: " .. table.count(activeGames)) for gameId, game in pairs(activeGames) do debugPrint("Spiel: " .. gameId .. " - " .. game.name) debugPrint(" Status: " .. game.status) debugPrint(" Feld: " .. game.fieldId) debugPrint(" Team 1: " .. #game.team1 .. " Spieler, Score: " .. game.score.team1) debugPrint(" Team 2: " .. #game.team2 .. " Spieler, Score: " .. game.score.team2) end if src > 0 then TriggerClientEvent('QBCore:Notify', src, 'TDM Status in Server-Konsole', 'info') end end, true) -- Hilfsfunktion für table.count table.count = function(tbl) local count = 0 for _, _ in pairs(tbl) do count = count + 1 end return count end