From 4485cc31dffe88a0b3d138d8c61b74b37b52d2e1 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Sun, 27 Jul 2025 23:47:36 +0200 Subject: [PATCH] Update client.lua --- resources/[standalone]/nordi_tdm/client.lua | 784 +++++++++++++++----- 1 file changed, 606 insertions(+), 178 deletions(-) diff --git a/resources/[standalone]/nordi_tdm/client.lua b/resources/[standalone]/nordi_tdm/client.lua index f06467bc0..60719f10b 100644 --- a/resources/[standalone]/nordi_tdm/client.lua +++ b/resources/[standalone]/nordi_tdm/client.lua @@ -9,6 +9,9 @@ local teamZoneBlips = {} local isHit = false local activeGames = {} local spawnedNPCs = {} +local lastDamager = nil +local lastDamageTime = 0 +local lastDamageWeapon = 0 -- Spieler Statistiken local playerStats = { @@ -27,9 +30,26 @@ function isAirsoftWeapon(weaponHash) return Config.airsoftWeapons[weaponHash] or Config.treatAllWeaponsAsAirsoft end --- Funktion zum Prüfen, ob der Spieler im Ragdoll-Zustand ist +-- Enhanced function to check if a player is in ragdoll state function isPedInRagdoll(ped) - return IsPedRagdoll(ped) or IsPedFalling(ped) or IsPedDiving(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 @@ -80,6 +100,9 @@ RegisterNetEvent('tdm:leaveGame', function() currentGameId = nil currentField = nil isHit = false + lastDamager = nil + lastDamageTime = 0 + lastDamageWeapon = 0 -- Sichere Rückkehr zur Lobby local lobbyPos = nil @@ -242,15 +265,28 @@ RegisterNetEvent('tdm:playerHit', function() end) RegisterNetEvent('tdm:updateScore', function(team1Score, team2Score, gameStats) - -- Debug-Ausgabe + -- Enhanced debug output debugPrint("Score Update empfangen: Team1=" .. team1Score .. ", Team2=" .. team2Score) if gameStats then - debugPrint("GameStats: Hits=" .. (gameStats.hits or "nil") .. ", Deaths=" .. (gameStats.deaths or "nil")) + debugPrint("GameStats received: ") + dumpTable(gameStats) + else + debugPrint("WARNING: No gameStats received from server!") 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 + -- 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', @@ -658,6 +694,12 @@ CreateThread(function() -- 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 @@ -696,7 +738,7 @@ CreateThread(function() end end) --- Zusätzlicher Event-Handler für zuverlässigere Treffer-Erkennung +-- Enhanced damage detection for ragdoll kills AddEventHandler('gameEventTriggered', function(name, args) if name == "CEventNetworkEntityDamage" then local victimId = args[1] @@ -718,6 +760,14 @@ AddEventHandler('gameEventTriggered', function(name, args) 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 @@ -734,199 +784,577 @@ AddEventHandler('gameEventTriggered', function(name, args) end end) --- Direkter Waffen-Schaden Monitor für zusätzliche Zuverlässigkeit +-- Improved Ragdoll-Erkennung Thread CreateThread(function() while true do - Wait(0) + Wait(50) -- Check more frequently 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 + 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 - Wait(500) + 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) --- 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) +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 - - -- 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 + if not removed then + debugPrint("Spieler " .. playerId .. " nicht in Spiel " .. gameId .. " gefunden") + return end - tdmBlips = {} - debugPrint("Cleaned up all NPCs and blips") + -- 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 --- Register cleanup when resource stops +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 - cleanupResources() + -- 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