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 } -- Debug-Funktion für Konsole local function debugPrint(message) print("^2[TDM DEBUG]^7 " .. message) end -- Funktion zum Prüfen, ob eine Waffe eine Airsoft-Waffe ist function isAirsoftWeapon(weaponHash) return Config.airsoftWeapons[weaponHash] or Config.treatAllWeaponsAsAirsoft end -- Funktion zum Prüfen, ob der Spieler im Ragdoll-Zustand ist function isPedInRagdoll(ped) return IsPedRagdoll(ped) or IsPedFalling(ped) or IsPedDiving(ped) end -- Events RegisterNetEvent('tdm:updateGamesList', function(games) activeGames = games debugPrint("Spieleliste aktualisiert: " .. (games and table.count(games) or 0) .. " aktive Spiele") 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' }) debugPrint("Spiel beigetreten: " .. gameId .. ", Team: " .. team .. ", Feld: " .. fieldId) 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) debugPrint("WARNUNG: 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' }) debugPrint("Spiel verlassen") 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 then debugPrint("WARNUNG: Hit-Event empfangen, aber nicht im TDM!") return end if isHit then debugPrint("WARNUNG: Hit-Event empfangen, aber bereits getroffen!") return end debugPrint("Spieler wurde getroffen - Starte Respawn-Sequenz") isHit = true local ped = PlayerPedId() -- Benachrichtigung lib.notify({ title = 'TeamDeathmatch', description = 'Du wurdest getroffen! Respawn in ' .. (Config.respawnDelay / 1000) .. ' Sekunden...', type = 'error' }) -- Optional: Ragdoll aktivieren SetPedToRagdoll(ped, 2000, 2000, 0, true, true, false) -- Verbesserte Respawn-Logik mit automatischem Teleport SetTimeout(Config.respawnDelay, function() if not inTDM then debugPrint("Respawn abgebrochen - nicht mehr im TDM") return end debugPrint("Respawn wird ausgeführt...") -- Respawn zum Team Spawn local fieldConfig = Config.gameFields[currentField] if not fieldConfig then debugPrint("FEHLER: Feldkonfiguration nicht gefunden für Respawn!") return end local spawnPoints = fieldConfig.teamSpawns[currentTeam] if not spawnPoints or #spawnPoints == 0 then debugPrint("FEHLER: Keine Spawn-Punkte gefunden für Respawn!") return end local randomSpawn = spawnPoints[math.random(#spawnPoints)] debugPrint("Respawn-Position: " .. randomSpawn.x .. ", " .. randomSpawn.y .. ", " .. randomSpawn.z) -- Teleport zum Spawn mit Fade DoScreenFadeOut(500) Wait(600) -- Stellen Sie sicher, dass der Spieler lebt NetworkResurrectLocalPlayer(randomSpawn.x, randomSpawn.y, randomSpawn.z, 0.0, true, false) debugPrint("Spieler wiederbelebt") -- Alle Animationen stoppen ClearPedTasksImmediately(ped) -- Teleport direkt zum Spawn-Punkt SetEntityCoords(ped, randomSpawn.x, randomSpawn.y, randomSpawn.z) SetEntityHealth(ped, GetEntityMaxHealth(ped)) -- Spieler ist wieder aktiv isHit = false debugPrint("Respawn abgeschlossen - Spieler ist wieder aktiv") Wait(100) DoScreenFadeIn(500) lib.notify({ title = 'TeamDeathmatch', description = 'Du bist wieder im Spiel!', type = 'success' }) end) end) RegisterNetEvent('tdm:updateScore', function(team1Score, team2Score, gameStats) -- Debug-Ausgabe debugPrint("Score Update empfangen: Team1=" .. team1Score .. ", Team2=" .. team2Score) if gameStats then debugPrint("GameStats: Hits=" .. (gameStats.hits or "nil") .. ", Deaths=" .. (gameStats.deaths or "nil")) 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 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 }) -- Spiele einen Sound ab PlaySoundFrontend(-1, "WEAPON_PURCHASE", "HUD_AMMO_SHOP_SOUNDSET", true) showHitMarker() -- Score sofort aktualisieren if currentGameId then TriggerServerEvent('tdm:requestScoreUpdate', currentGameId) end debugPrint("Treffer registriert! Neue Hits: " .. playerStats.hits) end) RegisterNetEvent('tdm:deathRegistered', function() playerStats.deaths = playerStats.deaths + 1 -- Score sofort aktualisieren if currentGameId then TriggerServerEvent('tdm:requestScoreUpdate', currentGameId) end debugPrint("Tod registriert! Neue Deaths: " .. playerStats.deaths) 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') debugPrint("Spiel beendet! Gewinner: " .. (winnerTeam or "Unentschieden")) 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) debugPrint("Maske gesetzt: " .. team .. " (" .. playerGender .. ")") else debugPrint("Keine Maske für " .. team .. " (" .. playerGender .. ") gefunden!") end else debugPrint("Keine Masken-Daten für Team " .. team .. " gefunden!") 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 debugPrint("Team Zone Blip erstellt für " .. team) end function removeTeamZoneBlips() for team, blip in pairs(teamZoneBlips) do if DoesBlipExist(blip) then RemoveBlip(blip) end end teamZoneBlips = {} debugPrint("Team Zone Blips entfernt") end function highlightTeamZone(team) if teamZoneBlips[team] and DoesBlipExist(teamZoneBlips[team]) then SetBlipFlashes(teamZoneBlips[team], true) end end function showHitMarker() debugPrint("Zeige Hit-Marker an") CreateThread(function() local startTime = GetGameTimer() local duration = 500 -- 500ms display while GetGameTimer() - startTime < duration do Wait(0) -- Draw hit marker 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) -- Draw hit 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(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) -- Verbesserte Damage Handler für Airsoft-Waffen CreateThread(function() while true do if inTDM and not isHit then local ped = PlayerPedId() -- Prüfe, ob der Spieler Schaden genommen hat if HasEntityBeenDamagedByAnyPed(ped) then debugPrint("Schaden erkannt - Identifiziere Angreifer und Waffe") local damager = nil local damagerPlayer = nil local weaponHash = 0 -- Versuche den Angreifer und die Waffe zu identifizieren for _, player in ipairs(GetActivePlayers()) do local playerPed = GetPlayerPed(player) if HasPedBeenDamagedBy(ped, playerPed) then damager = playerPed damagerPlayer = GetPlayerServerId(player) -- Versuche die Waffe zu ermitteln weaponHash = GetSelectedPedWeapon(playerPed) debugPrint("Angreifer identifiziert: " .. damagerPlayer .. " mit Waffe: " .. weaponHash) break end end -- Prüfe, ob es eine Airsoft-Waffe ist oder alle Waffen als Airsoft behandelt werden if isAirsoftWeapon(weaponHash) then debugPrint("Airsoft-Treffer erkannt mit Waffe: " .. weaponHash) -- Schaden zurücksetzen ClearEntityLastDamageEntity(ped) ClearPedLastWeaponDamage(ped) SetEntityHealth(ped, GetEntityMaxHealth(ped)) -- Lokale Stats sofort updaten playerStats.deaths = playerStats.deaths + 1 debugPrint("Getroffen von: " .. (damagerPlayer or "Unbekannt")) -- Treffer-Events auslösen TriggerEvent('tdm:playerHit') TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, damagerPlayer) -- Warten um mehrfache Auslösung zu verhindern Wait(500) else debugPrint("Keine Airsoft-Waffe erkannt: " .. weaponHash) -- Schaden trotzdem zurücksetzen ClearEntityLastDamageEntity(ped) SetEntityHealth(ped, GetEntityMaxHealth(ped)) end end Wait(0) else Wait(500) end end end) -- Zusätzlicher Event-Handler für zuverlässigere Treffer-Erkennung AddEventHandler('gameEventTriggered', function(name, args) if name == "CEventNetworkEntityDamage" then local victimId = args[1] local attackerId = args[2] local isDead = args[4] == 1 local weaponHash = args[5] local isMelee = args[10] == 1 if inTDM and not isHit and victimId == PlayerPedId() then local attackerServerId = nil -- Versuche den Angreifer zu identifizieren for _, player in ipairs(GetActivePlayers()) do if GetPlayerPed(player) == attackerId then attackerServerId = GetPlayerServerId(player) break end end debugPrint("Schaden-Event erkannt: Angreifer=" .. (attackerServerId or "NPC/Unbekannt") .. ", Waffe=" .. weaponHash) -- 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 playerStats.deaths = playerStats.deaths + 1 -- Verhindern, dass der Spieler stirbt SetEntityHealth(PlayerPedId(), GetEntityMaxHealth(PlayerPedId())) -- Treffer-Events auslösen TriggerEvent('tdm:playerHit') TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, attackerServerId) end end end end) -- Direkter Waffen-Schaden Monitor für zusätzliche Zuverlässigkeit CreateThread(function() while true do Wait(0) 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 else Wait(500) end 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) 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 end tdmBlips = {} debugPrint("Cleaned up all NPCs and blips") end -- Register cleanup when resource stops AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then cleanupResources() end end)