From f192af7af60ad5856495369e79086c51d4190adb Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Sat, 26 Jul 2025 06:21:34 +0200 Subject: [PATCH] ed --- resources/[standalone]/nordi_tdm/client.lua | 499 ++++++++++++++++++++ resources/[standalone]/nordi_tdm/config.lua | 95 ++++ resources/[standalone]/nordi_tdm/server.lua | 347 ++++++++++++++ 3 files changed, 941 insertions(+) create mode 100644 resources/[standalone]/nordi_tdm/client.lua create mode 100644 resources/[standalone]/nordi_tdm/config.lua create mode 100644 resources/[standalone]/nordi_tdm/server.lua diff --git a/resources/[standalone]/nordi_tdm/client.lua b/resources/[standalone]/nordi_tdm/client.lua new file mode 100644 index 000000000..32d698f24 --- /dev/null +++ b/resources/[standalone]/nordi_tdm/client.lua @@ -0,0 +1,499 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local inTDM = false +local currentTeam = nil +local currentGameId = nil +local currentField = nil +local tdmBlip = nil +local teamZoneBlips = {} +local isHit = false +local activeGames = {} + +-- Events +RegisterNetEvent('tdm:updateGamesList', function(games) + activeGames = games +end) + +RegisterNetEvent('tdm:joinGame', function(gameId, team, fieldId) + currentGameId = gameId + currentTeam = team + currentField = fieldId + inTDM = true + isHit = false + + local fieldConfig = Config.gameFields[fieldId] + + -- Teleport zu Team Spawn + local spawnPoints = fieldConfig.teamSpawns[team] + local randomSpawn = spawnPoints[math.random(#spawnPoints)] + + SetEntityCoords(PlayerPedId(), randomSpawn.x, randomSpawn.y, randomSpawn.z) + + -- Team Maske setzen + setTeamMask(team) + + -- Team Zone Blip erstellen + createTeamZoneBlip(team, fieldConfig) + + lib.notify({ + title = 'TeamDeathmatch', + description = 'Du bist dem Spiel beigetreten! Team: ' .. team, + type = 'success' + }) +end) + +RegisterNetEvent('tdm:leaveGame', function() + inTDM = false + currentTeam = nil + currentGameId = nil + currentField = nil + isHit = false + + -- Zurück zur Lobby + SetEntityCoords(PlayerPedId(), Config.lobbyPos.x, Config.lobbyPos.y, Config.lobbyPos.z) + + -- Maske entfernen + SetPedComponentVariation(PlayerPedId(), 1, 0, 0, 0) + + -- Zone Blips entfernen + removeTeamZoneBlips() + + lib.notify({ + title = 'TeamDeathmatch', + description = 'Du hast das Spiel verlassen!', + type = 'error' + }) +end) + +RegisterNetEvent('tdm:joinRequest', function(gameId, playerName, playerId) + local alert = lib.alertDialog({ + header = 'Join Anfrage', + content = playerName .. ' möchte deinem Spiel beitreten.\n\nErlauben?', + centered = true, + cancel = true, + labels = { + cancel = 'Ablehnen', + confirm = 'Erlauben' + } + }) + + if alert == 'confirm' then + TriggerServerEvent('tdm:approveJoinRequest', gameId, playerId, true) + else + TriggerServerEvent('tdm:approveJoinRequest', gameId, playerId, false) + end +end) + +RegisterNetEvent('tdm:joinRequestResult', function(approved, gameName) + if approved then + lib.notify({ + title = 'TeamDeathmatch', + description = 'Deine Anfrage wurde angenommen!', + type = 'success' + }) + else + lib.notify({ + title = 'TeamDeathmatch', + description = 'Deine Anfrage für "' .. gameName .. '" wurde abgelehnt!', + type = 'error' + }) + end +end) + +RegisterNetEvent('tdm:playerHit', function() + if not inTDM or isHit then return end + + isHit = true + local ped = PlayerPedId() + + -- Arme hochnehmen Animation + RequestAnimDict("random@mugging3") + while not HasAnimDictLoaded("random@mugging3") do + Wait(1) + end + + TaskPlayAnim(ped, "random@mugging3", "handsup_standing_base", 8.0, -8.0, -1, 50, 0, false, false, false) + + lib.notify({ + title = 'TeamDeathmatch', + description = 'Du wurdest getroffen! Gehe zurück zu deiner Team Zone!', + type = 'error' + }) + + -- Zone Marker hervorheben + highlightTeamZone(currentTeam) +end) + +RegisterNetEvent('tdm:updateScore', function(team1Score, team2Score) + lib.showTextUI('[Team 1: ' .. team1Score .. '] VS [Team 2: ' .. team2Score .. ']', { + position = "top-center", + icon = 'crosshairs' + }) +end) + +RegisterNetEvent('tdm:gameEnded', function(winnerTeam, team1Score, team2Score) + lib.hideTextUI() + + local alert = lib.alertDialog({ + header = 'Spiel beendet!', + content = 'Team ' .. winnerTeam .. ' hat gewonnen!\n\nTeam 1: ' .. team1Score .. '\nTeam 2: ' .. team2Score, + centered = true, + cancel = false + }) + + Wait(5000) + TriggerServerEvent('tdm:leaveGame') +end) + +-- Funktionen +function setTeamMask(team) + local ped = PlayerPedId() + local maskData = Config.teamMasks[team] + + if maskData then + SetPedComponentVariation(ped, maskData.component, maskData.drawable, maskData.texture, 0) + end +end + +function createTeamZoneBlip(team, fieldConfig) + local zone = fieldConfig.teamZones[team] + + local blip = AddBlipForRadius(zone.center.x, zone.center.y, zone.center.z, zone.radius) + SetBlipHighDetail(blip, true) + SetBlipColour(blip, team == 'team1' and 1 or 3) + SetBlipAlpha(blip, 128) + + teamZoneBlips[team] = blip +end + +function removeTeamZoneBlips() + for team, blip in pairs(teamZoneBlips) do + if DoesBlipExist(blip) then + RemoveBlip(blip) + end + end + teamZoneBlips = {} +end + +function highlightTeamZone(team) + if teamZoneBlips[team] and DoesBlipExist(teamZoneBlips[team]) then + SetBlipFlashes(teamZoneBlips[team], true) + end +end + +function openMainMenu() + TriggerServerEvent('tdm:requestGamesList') + + Wait(100) -- Kurz warten für Server Response + + local options = { + { + title = 'Neues Spiel erstellen', + description = 'Erstelle ein neues TeamDeathmatch Spiel', + icon = 'plus', + onSelect = function() + openCreateGameMenu() + end + }, + { + title = 'Spiel beitreten', + description = 'Trete einem laufenden Spiel bei', + icon = 'users', + onSelect = function() + openJoinGameMenu() + end + } + } + + if inTDM then + table.insert(options, { + title = 'Aktuelles Spiel verlassen', + description = 'Verlasse das laufende Spiel', + icon = 'door-open', + iconColor = 'red', + onSelect = function() + TriggerServerEvent('tdm:leaveGame') + end + }) + end + + lib.registerContext({ + id = 'tdm_main_menu', + title = 'TeamDeathmatch', + options = options + }) + + lib.showContext('tdm_main_menu') +end + +function openCreateGameMenu() + local fieldOptions = {} + + -- Spielfelder zu Options hinzufügen + for fieldId, fieldData in pairs(Config.gameFields) do + table.insert(fieldOptions, { + value = fieldId, + label = fieldData.name .. ' (Max: ' .. fieldData.maxPlayers .. ')' + }) + end + + local input = lib.inputDialog('Neues Spiel erstellen', { + { + type = 'input', + label = 'Spiel Name', + description = 'Gib deinem Spiel einen Namen', + required = true, + max = 30 + }, + { + type = 'select', + label = 'Spielfeld', + description = 'Wähle ein Spielfeld', + required = true, + options = fieldOptions + }, + { + type = 'select', + label = 'Spiel Typ', + description = 'Wähle den Spiel Typ', + required = true, + options = { + {value = 'public', label = 'Öffentlich (Jeder kann beitreten)'}, + {value = 'private', label = 'Privat (Nur mit Genehmigung)'} + } + }, + { + type = 'input', + label = 'Passwort (Optional)', + description = 'Passwort für das Spiel (leer lassen für kein Passwort)', + password = true + } + }) + + if not input then return end + + local gameName = input[1] + local fieldId = input[2] + local gameType = input[3] -- 'public' oder 'private' + local password = input[4] and input[4] ~= '' and input[4] or nil + + if gameName and fieldId and gameType then + TriggerServerEvent('tdm:createGame', gameName, fieldId, gameType, password) + end +end + +function openJoinGameMenu() + TriggerServerEvent('tdm:requestGamesList') + + Wait(200) + + local options = {} + + for gameId, gameData in pairs(activeGames) do + local playerCount = #gameData.team1 + #gameData.team2 + local maxPlayers = Config.gameFields[gameData.fieldId].maxPlayers + local fieldName = Config.gameFields[gameData.fieldId].name + + local statusText = gameData.status == 'waiting' and 'Wartend' or 'Läuft' + local typeText = gameData.gameType == 'public' and '🌐 Öffentlich' or '🔒 Privat' + local passwordIcon = gameData.hasPassword and ' 🔑' or '' + + table.insert(options, { + title = gameData.name .. passwordIcon, + description = typeText .. ' | Feld: ' .. fieldName .. ' | Spieler: ' .. playerCount .. '/' .. maxPlayers .. ' | Status: ' .. statusText, + icon = gameData.gameType == 'public' and 'globe' or 'lock', + iconColor = gameData.gameType == 'public' and 'green' or 'orange', + args = { + gameId = gameId, + hasPassword = gameData.hasPassword, + gameType = gameData.gameType + }, + onSelect = function(args) + if args.hasPassword then + local input = lib.inputDialog('Passwort eingeben', { + { + type = 'input', + label = 'Passwort', + description = 'Gib das Spiel-Passwort ein', + required = true, + password = true + } + }) + + if input and input[1] then + TriggerServerEvent('tdm:requestJoinGame', args.gameId, input[1]) + end + else + TriggerServerEvent('tdm:requestJoinGame', args.gameId) + end + end + }) + end + + if #options == 0 then + table.insert(options, { + title = 'Keine Spiele verfügbar', + description = 'Erstelle ein neues Spiel', + icon = 'info', + disabled = true + }) + end + + lib.registerContext({ + id = 'tdm_join_menu', + title = 'Spiel beitreten', + menu = 'tdm_main_menu', + options = options + }) + + lib.showContext('tdm_join_menu') +end + + +-- Zone Checker Thread +CreateThread(function() + while true do + Wait(500) + + if inTDM and isHit and currentTeam and currentField then + local ped = PlayerPedId() + local playerPos = GetEntityCoords(ped) + local zone = Config.gameFields[currentField].teamZones[currentTeam] + + local distance = #(playerPos - zone.center) + + if distance <= zone.radius then + isHit = false + ClearPedTasks(ped) + + if teamZoneBlips[currentTeam] and DoesBlipExist(teamZoneBlips[currentTeam]) then + SetBlipFlashes(teamZoneBlips[currentTeam], false) + end + + lib.notify({ + title = 'TeamDeathmatch', + description = 'Du bist wieder im Spiel!', + type = 'success' + }) + end + end + end +end) + +-- Zone Marker Renderer +CreateThread(function() + while true do + Wait(0) + + if inTDM and currentTeam and currentField then + local zone = Config.gameFields[currentField].teamZones[currentTeam] + local color = zone.color + + DrawMarker( + 1, + zone.center.x, zone.center.y, zone.center.z - 1.0, + 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + zone.radius * 2, zone.radius * 2, 1.0, + color.r, color.g, color.b, color.a, + false, true, 2, false, nil, nil, false + ) + + if isHit then + DrawMarker( + 2, + zone.center.x, zone.center.y, zone.center.z + 5.0, + 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + 1.0, 1.0, 1.0, + 255, 255, 0, 200, + true, true, 2, false, nil, nil, false + ) + end + else + Wait(1000) + end + end +end) + +-- Damage Handler +CreateThread(function() + while true do + Wait(100) + + if inTDM and not isHit then + local ped = PlayerPedId() + + if HasEntityBeenDamagedByAnyPed(ped) then + ClearEntityLastDamageEntity(ped) + TriggerEvent('tdm:playerHit') + TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam) + end + end + end +end) + +-- Death Handler +CreateThread(function() + while true do + Wait(1000) + + if inTDM then + local ped = PlayerPedId() + + if IsEntityDead(ped) then + TriggerServerEvent('tdm:playerDied', currentGameId) + + lib.notify({ + title = 'TeamDeathmatch', + description = 'Du bist ausgeschieden!', + type = 'error' + }) + + Wait(3000) + TriggerServerEvent('tdm:leaveGame') + end + end + end +end) + +-- NPC Setup +CreateThread(function() + -- Blip erstellen + tdmBlip = AddBlipForCoord(Config.lobbyPos.x, Config.lobbyPos.y, Config.lobbyPos.z) + SetBlipSprite(tdmBlip, 432) + SetBlipDisplay(tdmBlip, 4) + SetBlipScale(tdmBlip, 0.8) + SetBlipColour(tdmBlip, 1) + SetBlipAsShortRange(tdmBlip, true) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("TeamDeathmatch") + EndTextCommandSetBlipName(tdmBlip) + + -- NPC erstellen + RequestModel(GetHashKey(Config.gameNPC.model)) + while not HasModelLoaded(GetHashKey(Config.gameNPC.model)) do + Wait(1) + end + + local npc = CreatePed(4, GetHashKey(Config.gameNPC.model), Config.gameNPC.coords.x, Config.gameNPC.coords.y, Config.gameNPC.coords.z, Config.gameNPC.coords.w, false, true) + SetEntityInvincible(npc, true) + FreezeEntityPosition(npc, true) + SetBlockingOfNonTemporaryEvents(npc, true) +end) + +-- Target für NPC +exports['qb-target']:AddTargetModel(Config.gameNPC.model, { + options = { + { + type = "client", + event = "tdm:openMainMenu", + icon = "fas fa-crosshairs", + label = "TeamDeathmatch", + }, + }, + distance = 2.5 +}) + +RegisterNetEvent('tdm:openMainMenu', function() + openMainMenu() +end) diff --git a/resources/[standalone]/nordi_tdm/config.lua b/resources/[standalone]/nordi_tdm/config.lua new file mode 100644 index 000000000..3ab15c341 --- /dev/null +++ b/resources/[standalone]/nordi_tdm/config.lua @@ -0,0 +1,95 @@ +Config = {} + +-- Lobby Position +Config.lobbyPos = vector3(-1042.4, -2745.8, 21.4) + +-- NPC für Game Management +Config.gameNPC = { + model = 's_m_y_cop_01', + coords = vector4(-1042.4, -2745.8, 20.4, 0.0) +} + +-- Spielfelder Konfiguration +Config.gameFields = { + field1 = { + name = "Arena 1", + maxPlayers = 20, + + -- Team Spawn Punkte + teamSpawns = { + team1 = { + vector3(-1000.0, -2700.0, 25.0), + vector3(-1005.0, -2705.0, 25.0), + vector3(-995.0, -2695.0, 25.0) + }, + team2 = { + vector3(-1100.0, -2800.0, 25.0), + vector3(-1105.0, -2805.0, 25.0), + vector3(-1095.0, -2795.0, 25.0) + } + }, + + -- Team Lager Zonen (Kreismarker) + teamZones = { + team1 = { + center = vector3(-980.0, -2680.0, 25.0), + radius = 10.0, + color = {r = 255, g = 0, b = 0, a = 100} + }, + team2 = { + center = vector3(-1120.0, -2820.0, 25.0), + radius = 10.0, + color = {r = 0, g = 0, b = 255, a = 100} + } + } + }, + + field2 = { + name = "Arena 2", + maxPlayers = 16, + + teamSpawns = { + team1 = { + vector3(-2000.0, -2700.0, 25.0), + vector3(-2005.0, -2705.0, 25.0), + vector3(-1995.0, -2695.0, 25.0) + }, + team2 = { + vector3(-2100.0, -2800.0, 25.0), + vector3(-2105.0, -2805.0, 25.0), + vector3(-2095.0, -2795.0, 25.0) + } + }, + + teamZones = { + team1 = { + center = vector3(-1980.0, -2680.0, 25.0), + radius = 10.0, + color = {r = 255, g = 0, b = 0, a = 100} + }, + team2 = { + center = vector3(-2120.0, -2820.0, 25.0), + radius = 10.0, + color = {r = 0, g = 0, b = 255, a = 100} + } + } + } +} + +-- Team Masken +Config.teamMasks = { + team1 = { + component = 1, + drawable = 52, + texture = 0 + }, + team2 = { + component = 1, + drawable = 54, + texture = 0 + } +} + +-- Game Settings +Config.maxGameTime = 600 -- 10 Minuten +Config.maxHits = 30 -- Spiel endet bei 30 Treffern diff --git a/resources/[standalone]/nordi_tdm/server.lua b/resources/[standalone]/nordi_tdm/server.lua new file mode 100644 index 000000000..e20ecf5db --- /dev/null +++ b/resources/[standalone]/nordi_tdm/server.lua @@ -0,0 +1,347 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +-- Game Management +local activeGames = {} +local gameIdCounter = 1 + +-- Events +RegisterNetEvent('tdm:createGame', function(gameName, fieldId, gameType, password) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player then return end + + -- Prüfen ob Spielfeld bereits belegt + for gameId, gameData in pairs(activeGames) do + if gameData.fieldId == fieldId then + 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, -- 'public' oder 'private' + password = password, + hasPassword = password ~= nil, + status = 'waiting', -- waiting, active, finished + team1 = {}, + team2 = {}, + score = {team1 = 0, team2 = 0}, + startTime = nil, + maxTime = Config.maxGameTime, + maxHits = Config.maxHits + } + + local typeText = gameType == 'public' and 'öffentliches' or 'privates' + TriggerClientEvent('QBCore:Notify', src, 'Dein ' .. typeText .. ' Spiel "' .. gameName .. '" wurde erstellt!', 'success') + + -- Games Liste an alle senden + updateGamesListForAll() +end) + +RegisterNetEvent('tdm:requestJoinGame', function(gameId, password) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + if not Player or not activeGames[gameId] then return end + + local game = activeGames[gameId] + local playerName = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname + + -- Passwort prüfen falls vorhanden + if game.hasPassword and game.password ~= password then + TriggerClientEvent('QBCore:Notify', src, 'Falsches Passwort!', 'error') + return + end + + -- Spieler bereits im Spiel? + for _, playerId in ipairs(game.team1) do + if playerId == src then + 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 + 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 + 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 + 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 + -- Öffentliches Spiel - automatisch beitreten + joinPlayerToGame(src, gameId) + TriggerClientEvent('QBCore:Notify', src, 'Du bist dem öffentlichen Spiel beigetreten!', 'success') + else + -- Privates Spiel - Join Request an Admin senden + 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 return end + + if approved then + joinPlayerToGame(playerId, gameId) + TriggerClientEvent('tdm:joinRequestResult', playerId, true, game.name) + else + TriggerClientEvent('tdm:joinRequestResult', playerId, false, game.name) + end +end) + +RegisterNetEvent('tdm:leaveGame', function() + local src = source + + -- Spieler aus allen Spielen entfernen + for gameId, game in pairs(activeGames) do + removePlayerFromGame(src, gameId) + end + + TriggerClientEvent('tdm:leaveGame', src) +end) + +RegisterNetEvent('tdm:playerWasHit', function(gameId, victimTeam) + local src = source + local game = activeGames[gameId] + + if not game then return end + + -- Punkt für das andere Team + local scoringTeam = victimTeam == 'team1' and 'team2' or 'team1' + game.score[scoringTeam] = game.score[scoringTeam] + 1 + + -- Score Update an alle Spieler + updateScoreForGame(gameId) + + -- Prüfen ob Spiel gewonnen + if game.score[scoringTeam] >= game.maxHits then + endGame(gameId, scoringTeam) + end +end) + +RegisterNetEvent('tdm:playerDied', function(gameId) + local src = source + removePlayerFromGame(src, gameId) + checkGameEnd(gameId) +end) + +-- Funktionen +function joinPlayerToGame(playerId, gameId) + local game = activeGames[gameId] + if not game then return end + + -- Team mit weniger Spielern wählen + local team = #game.team1 <= #game.team2 and 'team1' or 'team2' + + table.insert(game[team], playerId) + + TriggerClientEvent('tdm:joinGame', playerId, gameId, team, game.fieldId) + + -- Spiel starten wenn genug Spieler + checkGameStart(gameId) + + updateGamesListForAll() + updateScoreForGame(gameId) +end + +function removePlayerFromGame(playerId, gameId) + local game = activeGames[gameId] + if not game then return end + + -- Aus Team 1 entfernen + for i, id in ipairs(game.team1) do + if id == playerId then + table.remove(game.team1, i) + break + end + end + + -- Aus Team 2 entfernen + for i, id in ipairs(game.team2) do + if id == playerId then + table.remove(game.team2, i) + break + end + end + + -- Wenn Admin das Spiel verlässt, Spiel beenden + if game.admin == playerId then + endGame(gameId, nil, 'Admin hat das Spiel verlassen') + return + end + + checkGameEnd(gameId) + updateGamesListForAll() +end + +function checkGameStart(gameId) + local game = activeGames[gameId] + if not game then return end + + local totalPlayers = #game.team1 + #game.team2 + + if totalPlayers >= 2 and game.status == 'waiting' then + startGame(gameId) + end +end + +function startGame(gameId) + local game = activeGames[gameId] + if not game then return end + + game.status = 'active' + game.startTime = os.time() + game.score = {team1 = 0, team2 = 0} + + -- Nachricht an alle Spieler + for _, playerId in ipairs(game.team1) do + TriggerClientEvent('QBCore:Notify', playerId, 'Spiel gestartet!', 'success') + end + for _, playerId in ipairs(game.team2) do + TriggerClientEvent('QBCore:Notify', playerId, 'Spiel gestartet!', 'success') + end + + updateScoreForGame(gameId) + updateGamesListForAll() + + -- Game Timer + CreateThread(function() + while activeGames[gameId] and activeGames[gameId].status == 'active' do + Wait(1000) + + local currentTime = os.time() + local elapsed = currentTime - game.startTime + + if elapsed >= game.maxTime then + local winnerTeam = game.score.team1 > game.score.team2 and 'team1' or 'team2' + endGame(gameId, winnerTeam, 'Zeit abgelaufen') + break + end + end + end) +end + +function endGame(gameId, winnerTeam, reason) + local game = activeGames[gameId] + if not game then return end + + game.status = 'finished' + + local message = reason or (winnerTeam and ('Team ' .. winnerTeam .. ' hat gewonnen!') or 'Spiel beendet') + + -- Nachricht an alle Spieler + for _, playerId in ipairs(game.team1) do + if winnerTeam then + TriggerClientEvent('tdm:gameEnded', playerId, winnerTeam, game.score.team1, game.score.team2) + else + TriggerClientEvent('QBCore:Notify', playerId, message, 'error') + TriggerClientEvent('tdm:leaveGame', playerId) + end + end + for _, playerId in ipairs(game.team2) do + if winnerTeam then + TriggerClientEvent('tdm:gameEnded', playerId, winnerTeam, game.score.team1, game.score.team2) + else + TriggerClientEvent('QBCore:Notify', playerId, message, 'error') + TriggerClientEvent('tdm:leaveGame', playerId) + end + end + + -- Spiel nach 15 Sekunden löschen + CreateThread(function() + Wait(15000) + activeGames[gameId] = nil + updateGamesListForAll() + end) +end + +function checkGameEnd(gameId) + local game = activeGames[gameId] + if not game then return 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 + endGame(gameId, nil, 'Zu wenig Spieler') + elseif totalPlayers == 0 then + -- Spiel löschen wenn keine Spieler mehr da sind + activeGames[gameId] = nil + updateGamesListForAll() + end +end + +function updateScoreForGame(gameId) + local game = activeGames[gameId] + if not game then return end + + -- Score an alle Spieler des Spiels senden + for _, playerId in ipairs(game.team1) do + TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2) + end + for _, playerId in ipairs(game.team2) do + TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2) + end +end + +function updateGamesListForAll() + -- Games Liste an alle Spieler senden + 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 + + -- Spieler aus allen Spielen entfernen + for gameId, game in pairs(activeGames) do + removePlayerFromGame(src, gameId) + end +end) + +-- Server Start - Games Liste leeren +AddEventHandler('onResourceStart', function(resourceName) + if GetCurrentResourceName() == resourceName then + activeGames = {} + gameIdCounter = 1 + end +end) + +