diff --git a/resources/[standalone]/nordi_tdm/client.lua b/resources/[standalone]/nordi_tdm/client.lua index a273c5979..6b14d8a89 100644 --- a/resources/[standalone]/nordi_tdm/client.lua +++ b/resources/[standalone]/nordi_tdm/client.lua @@ -3,10 +3,12 @@ local inTDM = false local currentTeam = nil local currentGameId = nil local currentField = nil -local tdmBlip = nil +local currentLobbyField = nil -- Neue Variable für aktuelle Lobby +local tdmBlips = {} -- Mehrere Blips local teamZoneBlips = {} local isHit = false local activeGames = {} +local spawnedNPCs = {} -- Gespawnte NPCs verwalten -- Spieler Statistiken local playerStats = { @@ -15,7 +17,7 @@ local playerStats = { gamesPlayed = 0 } --- Events +-- Events (gleich wie vorher, nur leaveGame angepasst) RegisterNetEvent('tdm:updateGamesList', function(games) activeGames = games end) @@ -55,13 +57,28 @@ end) RegisterNetEvent('tdm:leaveGame', function() inTDM = false + local previousField = currentField currentTeam = nil currentGameId = nil currentField = nil isHit = false - -- Zurück zur Lobby - SetEntityCoords(PlayerPedId(), Config.lobbyPos.x, Config.lobbyPos.y, Config.lobbyPos.z) + -- Zurück zur entsprechenden Lobby (falls vorher in einem Spiel) + if previousField and Config.gameFields[previousField] then + local lobbyPos = Config.gameFields[previousField].lobby.pos + SetEntityCoords(PlayerPedId(), lobbyPos.x, lobbyPos.y, lobbyPos.z) + elseif currentLobbyField and Config.gameFields[currentLobbyField] then + -- Zurück zur aktuellen Lobby + local lobbyPos = Config.gameFields[currentLobbyField].lobby.pos + SetEntityCoords(PlayerPedId(), lobbyPos.x, lobbyPos.y, lobbyPos.z) + else + -- Fallback zur ersten Lobby + local firstField = next(Config.gameFields) + if firstField then + local lobbyPos = Config.gameFields[firstField].lobby.pos + SetEntityCoords(PlayerPedId(), lobbyPos.x, lobbyPos.y, lobbyPos.z) + end + end -- Maske entfernen SetPedComponentVariation(PlayerPedId(), 1, 0, 0, 0) @@ -78,6 +95,7 @@ RegisterNetEvent('tdm:leaveGame', function() }) end) +-- Alle anderen Events bleiben gleich... RegisterNetEvent('tdm:joinRequest', function(gameId, playerName, playerId) local alert = lib.alertDialog({ header = 'Join Anfrage', @@ -119,7 +137,6 @@ RegisterNetEvent('tdm:playerHit', function() isHit = true local ped = PlayerPedId() - -- Arme hochnehmen Animation RequestAnimDict("random@mugging3") while not HasAnimDictLoaded("random@mugging3") do Wait(1) @@ -133,7 +150,6 @@ RegisterNetEvent('tdm:playerHit', function() type = 'error' }) - -- Zone Marker hervorheben highlightTeamZone(currentTeam) end) @@ -203,78 +219,94 @@ RegisterNetEvent('tdm:gameEnded', function(winnerTeam, team1Score, team2Score) TriggerServerEvent('tdm:leaveGame') end) --- Funktionen +-- Angepasste setTeamMask Funktion 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 showHitMarker() - CreateThread(function() - local startTime = GetGameTimer() + -- Geschlecht des Spielers ermitteln + local playerGender = GetEntityModel(ped) == GetHashKey("mp_f_freemode_01") and "female" or "male" - 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) + -- Entsprechende Maske setzen + local genderMask = maskData[playerGender] + if genderMask then + SetPedComponentVariation(ped, genderMask.component, genderMask.drawable, genderMask.texture, 0) end - end) + end end -function openMainMenu() +-- Alternative Methode über QBCore Player Data +function setTeamMaskQB(team) + local ped = PlayerPedId() + local Player = QBCore.Functions.GetPlayerData() + local maskData = Config.teamMasks[team] + + if maskData and Player.charinfo then + -- Geschlecht aus QBCore Charinfo + local playerGender = Player.charinfo.gender == 1 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 + +-- Erweiterte Funktion mit Fallback +function setTeamMaskAdvanced(team) + local ped = PlayerPedId() + local maskData = Config.teamMasks[team] + + if not maskData then return end + + local playerGender = "male" -- Default + + -- Methode 1: Über Ped Model + if GetEntityModel(ped) == GetHashKey("mp_f_freemode_01") then + playerGender = "female" + end + + -- Methode 2: Über QBCore (Fallback) + if playerGender == "male" then + local Player = QBCore.Functions.GetPlayerData() + if Player.charinfo and Player.charinfo.gender == 1 then + playerGender = "female" + end + end + + -- Maske setzen + local genderMask = maskData[playerGender] + if genderMask then + SetPedComponentVariation(ped, genderMask.component, genderMask.drawable, genderMask.texture, 0) + + lib.notify({ + title = 'TeamDeathmatch', + description = 'Team-Maske (' .. playerGender .. ') angelegt!', + type = 'info', + duration = 2000 + }) + end +end + + +-- Angepasste Menü-Funktionen +function openMainMenu(fieldId) + currentLobbyField = fieldId -- Aktuelle Lobby merken TriggerServerEvent('tdm:requestGamesList') Wait(100) + local fieldName = Config.gameFields[fieldId].name + local options = { { title = 'Neues Spiel erstellen', - description = 'Erstelle ein neues TeamDeathmatch Spiel', + description = 'Erstelle ein neues Spiel für ' .. fieldName, icon = 'plus', onSelect = function() - openCreateGameMenu() + openCreateGameMenu(fieldId) end }, { @@ -282,7 +314,7 @@ function openMainMenu() description = 'Trete einem laufenden Spiel bei', icon = 'users', onSelect = function() - openJoinGameMenu() + openJoinGameMenu(fieldId) end } } @@ -300,25 +332,18 @@ function openMainMenu() end lib.registerContext({ - id = 'tdm_main_menu', - title = 'TeamDeathmatch', + id = 'tdm_main_menu_' .. fieldId, + title = 'TeamDeathmatch - ' .. fieldName, options = options }) - lib.showContext('tdm_main_menu') + lib.showContext('tdm_main_menu_' .. fieldId) end -function openCreateGameMenu() - local fieldOptions = {} +function openCreateGameMenu(fieldId) + local fieldData = Config.gameFields[fieldId] - 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', { + local input = lib.inputDialog('Neues Spiel erstellen - ' .. fieldData.name, { { type = 'input', label = 'Spiel Name', @@ -326,13 +351,6 @@ function openCreateGameMenu() required = true, max = 30 }, - { - type = 'select', - label = 'Spielfeld', - description = 'Wähle ein Spielfeld', - required = true, - options = fieldOptions - }, { type = 'select', label = 'Spiel Typ', @@ -354,83 +372,85 @@ function openCreateGameMenu() if not input then return end local gameName = input[1] - local fieldId = input[2] - local gameType = input[3] - local password = input[4] and input[4] ~= '' and input[4] or nil + local gameType = input[2] + local password = input[3] and input[3] ~= '' and input[3] or nil - if gameName and fieldId and gameType then + if gameName and gameType then TriggerServerEvent('tdm:createGame', gameName, fieldId, gameType, password) end end -function openJoinGameMenu() +function openJoinGameMenu(fieldId) 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 - 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]) + 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 - 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', + description = 'Erstelle ein neues Spiel für ' .. fieldName, icon = 'info', disabled = true }) end lib.registerContext({ - id = 'tdm_join_menu', - title = 'Spiel beitreten', - menu = 'tdm_main_menu', + id = 'tdm_join_menu_' .. fieldId, + title = 'Spiel beitreten - ' .. fieldName, + menu = 'tdm_main_menu_' .. fieldId, options = options }) - lib.showContext('tdm_join_menu') + lib.showContext('tdm_join_menu_' .. fieldId) end --- Zone Checker Thread +-- Alle Threads bleiben gleich... CreateThread(function() while true do Wait(500) @@ -460,7 +480,6 @@ CreateThread(function() end end) --- Zone Marker Renderer CreateThread(function() while true do Wait(0) @@ -496,7 +515,6 @@ CreateThread(function() end end) --- Damage Handler CreateThread(function() while true do Wait(100) @@ -523,7 +541,6 @@ CreateThread(function() end end) --- Death Handler CreateThread(function() while true do Wait(1000) @@ -547,44 +564,77 @@ CreateThread(function() end end) --- NPC Setup +-- Angepasstes NPC Setup für alle Felder 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) + -- Für jedes Spielfeld Blip und NPC erstellen + for fieldId, fieldData in pairs(Config.gameFields) do + 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 + }) 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() +-- Event für Feld-spezifisches Menü +RegisterNetEvent('tdm:openFieldMenu', function(data) + openMainMenu(data.fieldId) 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') diff --git a/resources/[standalone]/nordi_tdm/config.lua b/resources/[standalone]/nordi_tdm/config.lua index 1c4dc01a6..304077d9a 100644 --- a/resources/[standalone]/nordi_tdm/config.lua +++ b/resources/[standalone]/nordi_tdm/config.lua @@ -6,7 +6,7 @@ Config.lobbyPos = vector3(2019.5784, 2841.1104, 50.3052) -- NPC für Game Management Config.gameNPC = { model = 'S_M_Y_ArmyMech_01', - coords = vector4(2019.5784, 2841.1104, 50.3052, 240.4603) + coords = vector4(2019.5784, 2841.1104, 49.3052, 240.4603) } -- Spielfelder Konfiguration @@ -48,6 +48,15 @@ Config.gameFields = { name = "Arena 2", maxPlayers = 16, + -- Eigene Lobby für dieses Feld + lobby = { + pos = vector3(-1500.0, -3000.0, 21.4), + npc = { + model = 's_m_y_cop_01', + coords = vector4(-1500.0, -3000.0, 20.4, 180.0) + } + }, + teamSpawns = { team1 = { vector3(-2000.0, -2700.0, 25.0), @@ -73,20 +82,74 @@ Config.gameFields = { color = {r = 0, g = 0, b = 255, a = 100} } } + }, + + field3 = { + name = "Desert Arena", + maxPlayers = 24, + + -- Eigene Lobby für dieses Feld + lobby = { + pos = vector3(1500.0, 3000.0, 40.0), + npc = { + model = 's_m_y_cop_01', + coords = vector4(1500.0, 3000.0, 39.0, 90.0) + } + }, + + teamSpawns = { + team1 = { + vector3(1400.0, 2900.0, 40.0), + vector3(1395.0, 2905.0, 40.0), + vector3(1405.0, 2895.0, 40.0) + }, + team2 = { + vector3(1600.0, 3100.0, 40.0), + vector3(1605.0, 3105.0, 40.0), + vector3(1595.0, 3095.0, 40.0) + } + }, + + teamZones = { + team1 = { + center = vector3(1380.0, 2880.0, 40.0), + radius = 12.0, + color = {r = 255, g = 0, b = 0, a = 100} + }, + team2 = { + center = vector3(1620.0, 3120.0, 40.0), + radius = 12.0, + color = {r = 0, g = 0, b = 255, a = 100} + } + } } } --- Team Masken + Config.teamMasks = { team1 = { - component = 1, - drawable = 52, - texture = 0 + male = { + component = 1, + drawable = 52, + texture = 0 + }, + female = { + component = 1, + drawable = 169, + texture = 4 + } }, team2 = { - component = 1, - drawable = 54, - texture = 0 + male = { + component = 1, + drawable = 54, + texture = 0 + }, + female = { + component = 1, + drawable = 169, + texture = 3 + } } }