diff --git a/resources/[standalone]/nordi_tdm/client.lua b/resources/[standalone]/nordi_tdm/client.lua index 32d698f24..a273c5979 100644 --- a/resources/[standalone]/nordi_tdm/client.lua +++ b/resources/[standalone]/nordi_tdm/client.lua @@ -8,6 +8,13 @@ local teamZoneBlips = {} local isHit = false local activeGames = {} +-- Spieler Statistiken +local playerStats = { + hits = 0, + deaths = 0, + gamesPlayed = 0 +} + -- Events RegisterNetEvent('tdm:updateGamesList', function(games) activeGames = games @@ -20,6 +27,11 @@ RegisterNetEvent('tdm:joinGame', function(gameId, team, 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 @@ -57,6 +69,8 @@ RegisterNetEvent('tdm:leaveGame', function() -- Zone Blips entfernen removeTeamZoneBlips() + lib.hideTextUI() + lib.notify({ title = 'TeamDeathmatch', description = 'Du hast das Spiel verlassen!', @@ -123,19 +137,64 @@ RegisterNetEvent('tdm:playerHit', function() highlightTeamZone(currentTeam) end) -RegisterNetEvent('tdm:updateScore', function(team1Score, team2Score) - lib.showTextUI('[Team 1: ' .. team1Score .. '] VS [Team 2: ' .. team2Score .. ']', { +RegisterNetEvent('tdm:updateScore', function(team1Score, team2Score, gameStats) + local displayText = string.format( + '[Team 1: %d] VS [Team 2: %d] | Deine Treffer: %d | Tode: %d', + team1Score, + team2Score, + playerStats.hits, + playerStats.deaths + ) + + lib.showTextUI(displayText, { position = "top-center", icon = 'crosshairs' }) end) +RegisterNetEvent('tdm:hitRegistered', function() + playerStats.hits = playerStats.hits + 1 + + lib.notify({ + title = 'Treffer!', + description = 'Du hast einen Gegner getroffen! (+1 Punkt)', + type = 'success', + duration = 2000 + }) + + showHitMarker() +end) + +RegisterNetEvent('tdm:deathRegistered', function() + playerStats.deaths = playerStats.deaths + 1 +end) + RegisterNetEvent('tdm:gameEnded', function(winnerTeam, team1Score, team2Score) lib.hideTextUI() + local wonGame = (currentTeam == 'team1' and winnerTeam == 'team1') or (currentTeam == 'team2' and winnerTeam == 'team2') + local resultText = wonGame and '🏆 GEWONNEN!' or '💀 VERLOREN!' + + local statsText = string.format( + '%s\n\n' .. + 'Endergebnis:\n' .. + 'Team 1: %d Punkte\n' .. + 'Team 2: %d Punkte\n\n' .. + 'Deine Statistiken:\n' .. + '🎯 Treffer: %d\n' .. + '💀 Tode: %d\n' .. + '📊 K/D: %.2f', + resultText, + team1Score, + team2Score, + playerStats.hits, + playerStats.deaths, + playerStats.deaths > 0 and (playerStats.hits / playerStats.deaths) or playerStats.hits + ) + local alert = lib.alertDialog({ header = 'Spiel beendet!', - content = 'Team ' .. winnerTeam .. ' hat gewonnen!\n\nTeam 1: ' .. team1Score .. '\nTeam 2: ' .. team2Score, + content = statsText, centered = true, cancel = false }) @@ -180,10 +239,34 @@ function highlightTeamZone(team) end end +function showHitMarker() + CreateThread(function() + local startTime = GetGameTimer() + + while GetGameTimer() - startTime < 500 do + Wait(0) + + -- Rotes X in der Mitte des Bildschirms + DrawRect(0.5, 0.5, 0.02, 0.002, 255, 0, 0, 255) -- Horizontale Linie + DrawRect(0.5, 0.5, 0.002, 0.02, 255, 0, 0, 255) -- Vertikale Linie + + -- Text + SetTextFont(4) + SetTextProportional(1) + SetTextScale(0.5, 0.5) + SetTextColour(255, 0, 0, 255) + SetTextEntry("STRING") + AddTextComponentString("TREFFER!") + SetTextCentre(true) + DrawText(0.5, 0.45) + end + end) +end + function openMainMenu() TriggerServerEvent('tdm:requestGamesList') - Wait(100) -- Kurz warten für Server Response + Wait(100) local options = { { @@ -228,7 +311,6 @@ end function openCreateGameMenu() local fieldOptions = {} - -- Spielfelder zu Options hinzufügen for fieldId, fieldData in pairs(Config.gameFields) do table.insert(fieldOptions, { value = fieldId, @@ -273,7 +355,7 @@ function openCreateGameMenu() local gameName = input[1] local fieldId = input[2] - local gameType = input[3] -- 'public' oder 'private' + local gameType = input[3] local password = input[4] and input[4] ~= '' and input[4] or nil if gameName and fieldId and gameType then @@ -348,7 +430,6 @@ function openJoinGameMenu() lib.showContext('tdm_join_menu') end - -- Zone Checker Thread CreateThread(function() while true do @@ -424,9 +505,19 @@ CreateThread(function() local ped = PlayerPedId() if HasEntityBeenDamagedByAnyPed(ped) then + local damager = GetPedSourceOfDeath(ped) + local damagerPlayer = nil + + for _, player in pairs(GetActivePlayers()) do + if GetPlayerPed(player) == damager then + damagerPlayer = GetPlayerServerId(player) + break + end + end + ClearEntityLastDamageEntity(ped) TriggerEvent('tdm:playerHit') - TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam) + TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, damagerPlayer) end end end diff --git a/resources/[standalone]/nordi_tdm/server.lua b/resources/[standalone]/nordi_tdm/server.lua index e20ecf5db..e1da8b70a 100644 --- a/resources/[standalone]/nordi_tdm/server.lua +++ b/resources/[standalone]/nordi_tdm/server.lua @@ -28,10 +28,10 @@ RegisterNetEvent('tdm:createGame', function(gameName, fieldId, gameType, passwor fieldId = fieldId, admin = src, adminName = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname, - gameType = gameType, -- 'public' oder 'private' + gameType = gameType, password = password, hasPassword = password ~= nil, - status = 'waiting', -- waiting, active, finished + status = 'waiting', team1 = {}, team2 = {}, score = {team1 = 0, team2 = 0}, @@ -43,10 +43,14 @@ RegisterNetEvent('tdm:createGame', function(gameName, fieldId, gameType, passwor 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:requestGamesList', function() + local src = source + TriggerClientEvent('tdm:updateGamesList', src, activeGames) +end) + RegisterNetEvent('tdm:requestJoinGame', function(gameId, password) local src = source local Player = QBCore.Functions.GetPlayer(src) @@ -96,17 +100,14 @@ RegisterNetEvent('tdm:requestJoinGame', function(gameId, password) -- 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] @@ -124,7 +125,6 @@ end) RegisterNetEvent('tdm:leaveGame', function() local src = source - -- Spieler aus allen Spielen entfernen for gameId, game in pairs(activeGames) do removePlayerFromGame(src, gameId) end @@ -132,29 +132,50 @@ RegisterNetEvent('tdm:leaveGame', function() TriggerClientEvent('tdm:leaveGame', src) end) -RegisterNetEvent('tdm:playerWasHit', function(gameId, victimTeam) +RegisterNetEvent('tdm:playerWasHit', function(gameId, victimTeam, shooterId) 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 + local shooterTeam = nil + if shooterId then + for _, playerId in ipairs(game.team1) do + if playerId == shooterId then + shooterTeam = 'team1' + break + end + end + if not shooterTeam then + for _, playerId in ipairs(game.team2) do + if playerId == shooterId then + shooterTeam = 'team2' + break + end + end + end + end - -- Score Update an alle Spieler - updateScoreForGame(gameId) - - -- Prüfen ob Spiel gewonnen - if game.score[scoringTeam] >= game.maxHits then - endGame(gameId, scoringTeam) + if shooterTeam and shooterTeam ~= victimTeam then + game.score[shooterTeam] = game.score[shooterTeam] + 1 + + if shooterId then + TriggerClientEvent('tdm:hitRegistered', shooterId) + end + + TriggerClientEvent('tdm:deathRegistered', src) + + updateScoreForGame(gameId) + + if game.score[shooterTeam] >= game.maxHits then + endGame(gameId, shooterTeam) + end end end) RegisterNetEvent('tdm:playerDied', function(gameId) local src = source removePlayerFromGame(src, gameId) - checkGameEnd(gameId) end) -- Funktionen @@ -167,20 +188,25 @@ function joinPlayerToGame(playerId, gameId) table.insert(game[team], playerId) + -- Spiel starten wenn mindestens 2 Spieler + if #game.team1 + #game.team2 >= 2 and game.status == 'waiting' then + game.status = 'active' + game.startTime = os.time() + + -- Game Timer starten + startGameTimer(gameId) + end + TriggerClientEvent('tdm:joinGame', playerId, gameId, team, game.fieldId) - - -- Spiel starten wenn genug Spieler - checkGameStart(gameId) - - updateGamesListForAll() updateScoreForGame(gameId) + updateGamesListForAll() end function removePlayerFromGame(playerId, gameId) local game = activeGames[gameId] if not game then return end - -- Aus Team 1 entfernen + -- Spieler aus Teams entfernen for i, id in ipairs(game.team1) do if id == playerId then table.remove(game.team1, i) @@ -188,7 +214,6 @@ function removePlayerFromGame(playerId, gameId) end end - -- Aus Team 2 entfernen for i, id in ipairs(game.team2) do if id == playerId then table.remove(game.team2, i) @@ -206,90 +231,58 @@ function removePlayerFromGame(playerId, 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 + local allPlayers = {} 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 + table.insert(allPlayers, playerId) 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 + table.insert(allPlayers, playerId) end - -- Spiel nach 15 Sekunden löschen - CreateThread(function() - Wait(15000) + -- Game End Event an alle Spieler + for _, playerId in ipairs(allPlayers) do + 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() end) + + if reason then + print('[TDM] Spiel ' .. gameId .. ' beendet: ' .. reason) + 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() + + while game and game.status == 'active' and (os.time() - startTime) < maxTime do + Wait(1000) + game = activeGames[gameId] -- Refresh game data + 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 + endGame(gameId, winnerTeam, 'Zeit abgelaufen') + end + 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 @@ -299,7 +292,6 @@ function checkGameEnd(gameId) 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 @@ -309,17 +301,20 @@ function updateScoreForGame(gameId) local game = activeGames[gameId] if not game then return end - -- Score an alle Spieler des Spiels senden + local gameStats = { + totalHits = game.score.team1 + game.score.team2, + gameTime = game.startTime and (os.time() - game.startTime) or 0 + } + for _, playerId in ipairs(game.team1) do - TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2) + TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2, gameStats) end for _, playerId in ipairs(game.team2) do - TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2) + TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2, gameStats) 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) @@ -330,7 +325,6 @@ end AddEventHandler('playerDropped', function() local src = source - -- Spieler aus allen Spielen entfernen for gameId, game in pairs(activeGames) do removePlayerFromGame(src, gameId) end @@ -341,7 +335,29 @@ AddEventHandler('onResourceStart', function(resourceName) if GetCurrentResourceName() == resourceName then activeGames = {} gameIdCounter = 1 + print('[TDM] 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 = {} + print('[TDM] TeamDeathmatch System gestoppt!') + end +end) +