1
0
Fork 0
forked from Simnation/Main
Main/resources/[standalone]/nordi_tdm/client.lua
2025-07-26 21:25:55 +02:00

760 lines
23 KiB
Lua

local QBCore = exports['qb-core']:GetCoreObject()
local inTDM = false
local currentTeam = nil
local currentGameId = nil
local currentField = nil
local currentLobbyField = nil
local tdmBlips = {}
local teamZoneBlips = {}
local isHit = false
local activeGames = {}
local spawnedNPCs = {}
-- Spieler Statistiken
local playerStats = {
hits = 0,
deaths = 0,
gamesPlayed = 0
}
-- 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
-- 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'
})
end)
RegisterNetEvent('tdm:leaveGame', function()
inTDM = false
local previousField = currentField
currentTeam = nil
currentGameId = nil
currentField = nil
isHit = false
-- Sichere Rückkehr zur Lobby
local lobbyPos = nil
-- Versuche zuerst die vorherige Feld-Lobby
if previousField and Config.gameFields[previousField] and Config.gameFields[previousField].lobby then
lobbyPos = Config.gameFields[previousField].lobby.pos
-- Dann die aktuelle Lobby-Feld
elseif currentLobbyField and Config.gameFields[currentLobbyField] and Config.gameFields[currentLobbyField].lobby then
lobbyPos = Config.gameFields[currentLobbyField].lobby.pos
-- Fallback zur ersten verfügbaren Lobby
else
for fieldId, fieldData in pairs(Config.gameFields) do
if fieldData.lobby and fieldData.lobby.pos then
lobbyPos = fieldData.lobby.pos
break
end
end
end
-- Teleport zur Lobby (mit Fallback-Position)
if lobbyPos then
SetEntityCoords(PlayerPedId(), lobbyPos.x, lobbyPos.y, lobbyPos.z)
else
-- Notfall-Fallback Position (anpassen an deine Map)
SetEntityCoords(PlayerPedId(), -1042.4, -2745.8, 21.4)
print("^3[TDM WARNING]^7 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'
})
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()
-- Animation für kurze Zeit
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! Respawn in 3 Sekunden...',
type = 'error'
})
-- Nach 3 Sekunden automatisch respawnen
CreateThread(function()
Wait(3000)
if inTDM and isHit and currentTeam and currentField then
-- Respawn zum Team Spawn
local fieldConfig = Config.gameFields[currentField]
local spawnPoints = fieldConfig.teamSpawns[currentTeam]
local randomSpawn = spawnPoints[math.random(#spawnPoints)]
-- Animation stoppen
ClearPedTasks(ped)
-- Teleport zum Spawn
SetEntityCoords(ped, randomSpawn.x, randomSpawn.y, randomSpawn.z)
-- Spieler ist wieder aktiv
isHit = false
-- Zone Blip Flash stoppen
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)
RegisterNetEvent('tdm:updateScore', function(team1Score, team2Score, gameStats)
-- Verwende gameStats falls verfügbar, sonst lokale Stats
local myHits = gameStats and gameStats.hits or playerStats.hits
local myDeaths = gameStats and gameStats.deaths or playerStats.deaths
local displayText = string.format(
'[Team 1: %d] VS [Team 2: %d] | Deine Treffer: %d | Tode: %d',
team1Score,
team2Score,
myHits,
myDeaths
)
lib.showTextUI(displayText, {
position = "top-center",
icon = 'crosshairs'
})
end)
RegisterNetEvent('tdm:hitRegistered', function()
playerStats.hits = playerStats.hits + 1
lib.notify({
title = 'Treffer!',
description = 'Du hast einen Gegner getroffen! (+1 Punkt)',
type = 'success',
duration = 2000
})
showHitMarker()
-- Score sofort aktualisieren
TriggerServerEvent('tdm:requestScoreUpdate', currentGameId)
end)
RegisterNetEvent('tdm:deathRegistered', function()
playerStats.deaths = playerStats.deaths + 1
-- Score sofort aktualisieren
TriggerServerEvent('tdm:requestScoreUpdate', currentGameId)
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')
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)
end
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 showHitMarker()
CreateThread(function()
local startTime = GetGameTimer()
while GetGameTimer() - startTime < 500 do
Wait(0)
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)
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)
-- Damage Handler (erweitert)
CreateThread(function()
while true do
Wait(100)
if inTDM and not isHit then
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)
-- Lokale Stats sofort updaten
playerStats.deaths = playerStats.deaths + 1
TriggerEvent('tdm:playerHit')
TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, damagerPlayer)
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 für alle Felder
CreateThread(function()
-- Für jedes Spielfeld Blip und NPC erstellen
for fieldId, fieldData in pairs(Config.gameFields) do
if fieldData.lobby and fieldData.lobby.pos and fieldData.lobby.npc then
local lobbyPos = fieldData.lobby.pos
local npcData = fieldData.lobby.npc
-- Blip erstellen
local blip = AddBlipForCoord(lobbyPos.x, lobbyPos.y, lobbyPos.z)
SetBlipSprite(blip, 432)
SetBlipDisplay(blip, 4)
SetBlipScale(blip, 0.8)
SetBlipColour(blip, 1)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName("STRING")
AddTextComponentString("TDM - " .. fieldData.name)
EndTextCommandSetBlipName(blip)
tdmBlips[fieldId] = blip
-- NPC erstellen
RequestModel(GetHashKey(npcData.model))
while not HasModelLoaded(GetHashKey(npcData.model)) do
Wait(1)
end
local npc = CreatePed(4, GetHashKey(npcData.model), npcData.coords.x, npcData.coords.y, npcData.coords.z, npcData.coords.w, false, true)
SetEntityInvincible(npc, true)
FreezeEntityPosition(npc, true)
SetBlockingOfNonTemporaryEvents(npc, true)
spawnedNPCs[fieldId] = npc
-- Target für diesen NPC
exports['qb-target']:AddTargetEntity(npc, {
options = {
{
type = "client",
event = "tdm:openFieldMenu",
icon = "fas fa-crosshairs",
label = "TeamDeathmatch - " .. fieldData.name,
fieldId = fieldId
},
},
distance = 2.5
})
else
print("^3[TDM WARNING]^7 Feld " .. fieldId .. " hat keine vollständige Lobby-Konfiguration!")
end
end
end)
-- Event für Feld-spezifisches Menü
RegisterNetEvent('tdm:openFieldMenu', function(data)
if data and data.fieldId then
openMainMenu(data.fieldId)
else
lib.notify({
title = 'Fehler',
description = 'Keine Feld-ID übertragen!',
type = 'error'
})
end
end)
-- Chat Command zum Spiel verlassen
RegisterCommand('leavetdm', function()
if inTDM then
TriggerServerEvent('tdm:leaveGame')
lib.notify({
title = 'TeamDeathmatch',
description = 'Du hast das Spiel über Command verlassen!',
type = 'info'
})
else
lib.notify({
title = 'TeamDeathmatch',
description = 'Du bist in keinem Spiel!',
type = 'error'
})
end
end, false)
-- Keybind zum Spiel verlassen
RegisterKeyMapping('leavetdm', 'TeamDeathmatch verlassen', 'keyboard', 'F7')
-- Debug Command zum Testen der Config
RegisterCommand('debugtdm', function()
print("^2[TDM DEBUG]^7 Aktuelle Werte:")
print("inTDM: " .. tostring(inTDM))
print("currentField: " .. tostring(currentField))
print("currentLobbyField: " .. tostring(currentLobbyField))
print("currentTeam: " .. tostring(currentTeam))
print("^2[TDM DEBUG]^7 Verfügbare Felder:")
for fieldId, fieldData in pairs(Config.gameFields) do
local hasLobby = fieldData.lobby and fieldData.lobby.pos and "" or ""
print("- " .. fieldId .. ": " .. fieldData.name .. " " .. hasLobby)
end
end, false)
-- Debug Commands für Masken
RegisterCommand('testmask', function(source, args)
if not args[1] or not args[2] then
lib.notify({
title = 'Debug',
description = 'Verwendung: /testmask [team1/team2] [male/female]',
type = 'error'
})
return
end
local team = args[1]
local gender = args[2]
if Config.teamMasks[team] and Config.teamMasks[team][gender] then
local maskData = Config.teamMasks[team][gender]
local ped = PlayerPedId()
SetPedComponentVariation(ped, maskData.component, maskData.drawable, maskData.texture, 0)
lib.notify({
title = 'Debug',
description = 'Maske gesetzt: ' .. team .. ' (' .. gender .. ')',
type = 'success'
})
else
lib.notify({
title = 'Debug',
description = 'Maske nicht gefunden!',
type = 'error'
})
end
end, false)
-- Command zum Entfernen der Maske
RegisterCommand('removemask', function()
SetPedComponentVariation(PlayerPedId(), 1, 0, 0, 0)
lib.notify({
title = 'Debug',
description = 'Maske entfernt!',
type = 'info'
})
end, false)